@wiscale/velesdb-sdk 1.18.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.js CHANGED
@@ -199,7 +199,7 @@ var WASM_CAPABILITIES = Object.freeze({
199
199
  agentMemory: false,
200
200
  streamInsert: false,
201
201
  pqTraining: false,
202
- velesqlQuery: true,
202
+ velesqlQuery: false,
203
203
  collectionIntrospection: false
204
204
  });
205
205
 
@@ -437,20 +437,47 @@ async function wasmMultiQuerySearch(ctx, collectionName, vectors, options) {
437
437
  );
438
438
  return raw.map((r) => mapWasmResult(ctx, collection, r));
439
439
  }
440
- async function wasmQuery(ctx, collectionName, _queryString, params, _options) {
440
+ 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;
441
+ function parsePureNearQuery(queryString) {
442
+ const match = PURE_NEAR_QUERY.exec(queryString);
443
+ if (!match) {
444
+ throw new VelesDBError(
445
+ `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}`,
446
+ "NOT_SUPPORTED"
447
+ );
448
+ }
449
+ const parsed = { from: match[1], param: match[2] };
450
+ if (match[3] !== void 0) {
451
+ parsed.limit = Number(match[3]);
452
+ }
453
+ return parsed;
454
+ }
455
+ function resolveQueryK(limit, requestedK) {
456
+ if (limit !== void 0) {
457
+ return limit;
458
+ }
459
+ return typeof requestedK === "number" && Number.isInteger(requestedK) && requestedK > 0 ? requestedK : 10;
460
+ }
461
+ async function wasmQuery(ctx, collectionName, queryString, params, _options) {
441
462
  const collection = ctx.getCollection(collectionName);
442
463
  if (!collection) {
443
464
  throw new NotFoundError(`Collection '${collectionName}'`);
444
465
  }
445
- const paramsVector = params?.q;
466
+ const parsed = parsePureNearQuery(queryString);
467
+ if (parsed.from !== collectionName) {
468
+ throw new VelesDBError(
469
+ `Query targets collection '${parsed.from}' but was executed against '${collectionName}'.`,
470
+ "BAD_REQUEST"
471
+ );
472
+ }
473
+ const paramsVector = params?.[parsed.param];
446
474
  if (!Array.isArray(paramsVector) && !(paramsVector instanceof Float32Array)) {
447
475
  throw new VelesDBError(
448
- "WASM query() expects params.q to contain the query embedding vector.",
476
+ `WASM query() expects params.${parsed.param} to contain the query embedding vector.`,
449
477
  "BAD_REQUEST"
450
478
  );
451
479
  }
452
- const requestedK = params?.k;
453
- const k = typeof requestedK === "number" && Number.isInteger(requestedK) && requestedK > 0 ? requestedK : 10;
480
+ const k = resolveQueryK(parsed.limit, params?.k);
454
481
  const raw = collection.store.query(
455
482
  paramsVector instanceof Float32Array ? paramsVector : new Float32Array(paramsVector),
456
483
  k
@@ -950,6 +977,12 @@ async function wasmRecordEpisodicEvent(_collection, _event) {
950
977
  async function wasmRecallEpisodicEvents(_collection, _embedding, _k) {
951
978
  wasmNotSupported("Agent memory");
952
979
  }
980
+ async function wasmRecallRecentEvents(_collection, _since) {
981
+ wasmNotSupported("Agent memory");
982
+ }
983
+ async function wasmRecallOlderThanEvents(_collection, _before) {
984
+ wasmNotSupported("Agent memory");
985
+ }
953
986
  async function wasmStoreProceduralPattern(_collection, _pattern) {
954
987
  wasmNotSupported("Agent memory");
955
988
  }
@@ -997,6 +1030,18 @@ function wasmUpsertNodePayload(_c, _id, _p) {
997
1030
  function wasmGraphSearch(_c, _r) {
998
1031
  return Promise.resolve(wasmNotSupported("Graph search"));
999
1032
  }
1033
+ function wasmRelate(_c, _req) {
1034
+ return Promise.resolve(wasmNotSupported("Relation edges"));
1035
+ }
1036
+ function wasmUnrelate(_c, _id) {
1037
+ return Promise.resolve(wasmNotSupported("Relation edge removal"));
1038
+ }
1039
+ function wasmGetRelations(_c, _id) {
1040
+ return Promise.resolve(wasmNotSupported("Relation edges"));
1041
+ }
1042
+ function wasmSetTtlDurable(_c, _id, _ttl) {
1043
+ return Promise.resolve(wasmNotSupported("Durable TTL"));
1044
+ }
1000
1045
 
1001
1046
  // src/backends/wasm.ts
1002
1047
  var WasmBackend = class {
@@ -1351,6 +1396,14 @@ var WasmBackend = class {
1351
1396
  this.ensureInitialized();
1352
1397
  return wasmRecallEpisodicEvents(c, e, k);
1353
1398
  }
1399
+ async recallRecentEvents(c, since) {
1400
+ this.ensureInitialized();
1401
+ return wasmRecallRecentEvents(c, since);
1402
+ }
1403
+ async recallOlderThanEvents(c, before) {
1404
+ this.ensureInitialized();
1405
+ return wasmRecallOlderThanEvents(c, before);
1406
+ }
1354
1407
  async storeProceduralPattern(c, p) {
1355
1408
  this.ensureInitialized();
1356
1409
  return wasmStoreProceduralPattern(c, p);
@@ -1412,16 +1465,43 @@ var WasmBackend = class {
1412
1465
  this.ensureInitialized();
1413
1466
  return wasmSparseSearchNamed(c, q, idx, o);
1414
1467
  }
1468
+ async relate(c, req) {
1469
+ this.ensureInitialized();
1470
+ return wasmRelate(c, req);
1471
+ }
1472
+ async unrelate(c, edgeId) {
1473
+ this.ensureInitialized();
1474
+ return wasmUnrelate(c, edgeId);
1475
+ }
1476
+ async getRelations(c, pointId) {
1477
+ this.ensureInitialized();
1478
+ return wasmGetRelations(c, pointId);
1479
+ }
1480
+ async setTtlDurable(c, pointId, ttlSeconds) {
1481
+ this.ensureInitialized();
1482
+ return wasmSetTtlDurable(c, pointId, ttlSeconds);
1483
+ }
1415
1484
  };
1416
1485
 
1417
1486
  // src/backends/crud-backend.ts
1487
+ var U64_MAX = 18446744073709551615n;
1488
+ function coerceDecimalStringId(id) {
1489
+ if (!/^\d+$/.test(id)) return NaN;
1490
+ const big = BigInt(id);
1491
+ if (big > U64_MAX) return NaN;
1492
+ return big > BigInt(Number.MAX_SAFE_INTEGER) ? id : Number(id);
1493
+ }
1418
1494
  function parseRestPointId(id) {
1419
- if (typeof id !== "number" || !Number.isFinite(id) || id < 0 || !Number.isInteger(id) || id > Number.MAX_SAFE_INTEGER) {
1495
+ const coerced = typeof id === "string" ? coerceDecimalStringId(id) : id;
1496
+ if (typeof coerced === "string") {
1497
+ return coerced;
1498
+ }
1499
+ if (!Number.isFinite(coerced) || coerced < 0 || !Number.isInteger(coerced) || coerced > Number.MAX_SAFE_INTEGER) {
1420
1500
  throw new ValidationError(
1421
- `REST backend requires numeric u64-compatible IDs in JS safe integer range (0..${Number.MAX_SAFE_INTEGER}). Received: ${String(id)}`
1501
+ `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)}`
1422
1502
  );
1423
1503
  }
1424
- return id;
1504
+ return coerced;
1425
1505
  }
1426
1506
  function sparseVectorToRestFormat(sv) {
1427
1507
  const result = {};
@@ -1876,6 +1956,31 @@ async function graphSearch(transport, collection, request2) {
1876
1956
  return { results: items };
1877
1957
  }
1878
1958
 
1959
+ // src/backends/scroll-backend.ts
1960
+ async function scroll(transport, collection, request2) {
1961
+ const body = {};
1962
+ if (request2?.cursor !== void 0) {
1963
+ body.cursor = request2.cursor;
1964
+ }
1965
+ if (request2?.batchSize !== void 0) {
1966
+ body.batch_size = request2.batchSize;
1967
+ }
1968
+ if (request2?.filter !== void 0) {
1969
+ body.filter = request2.filter;
1970
+ }
1971
+ const response = await transport.requestJson(
1972
+ "POST",
1973
+ `${collectionPath(collection)}/points/scroll`,
1974
+ body
1975
+ );
1976
+ throwOnError(response, `Collection '${collection}'`);
1977
+ const data = response.data;
1978
+ return {
1979
+ points: data.points,
1980
+ nextCursor: data.next_cursor
1981
+ };
1982
+ }
1983
+
1879
1984
  // src/backends/agent-memory-backend.ts
1880
1985
  var _idCounter = 0;
1881
1986
  var _lastTimestamp = 0;
@@ -1893,20 +1998,28 @@ function generateUniqueId() {
1893
1998
  }
1894
1999
  return _lastTimestamp * 1e3 + _idCounter;
1895
2000
  }
2001
+ function memoryIdToString(id) {
2002
+ return String(id);
2003
+ }
2004
+ function nowUnixSeconds() {
2005
+ return Math.floor(Date.now() / 1e3);
2006
+ }
1896
2007
  async function storeSemanticFact(transport, collection, entry) {
1897
2008
  const response = await transport.requestJson(
1898
2009
  "POST",
1899
2010
  `${collectionPath(collection)}/points`,
1900
2011
  {
1901
2012
  points: [{
1902
- id: entry.id,
2013
+ id: parseRestPointId(entry.id),
1903
2014
  vector: entry.embedding,
1904
2015
  payload: {
1905
2016
  // Caller metadata is spread first so the reserved keys below
1906
- // (`_memory_type`, `text`) always win and cannot be clobbered.
2017
+ // (`_memory_type`, `content`) always win and cannot be clobbered.
1907
2018
  ...entry.metadata,
1908
2019
  _memory_type: "semantic",
1909
- text: entry.text
2020
+ // `content` matches the core semantic store and the server/Python
2021
+ // payload field (BREAKING: was `text` before this change).
2022
+ content: entry.text
1910
2023
  }
1911
2024
  }]
1912
2025
  }
@@ -1917,7 +2030,8 @@ async function searchSemanticMemory(transport, collection, embedding, k = 5) {
1917
2030
  return transport.searchVectors(collection, embedding, k, { _memory_type: "semantic" });
1918
2031
  }
1919
2032
  async function recordEpisodicEvent(transport, collection, event) {
1920
- const id = generateUniqueId();
2033
+ const id = event.id !== void 0 ? parseRestPointId(event.id) : generateUniqueId();
2034
+ const timestamp = event.timestamp ?? nowUnixSeconds();
1921
2035
  const response = await transport.requestJson(
1922
2036
  "POST",
1923
2037
  `${collectionPath(collection)}/points`,
@@ -1933,19 +2047,21 @@ async function recordEpisodicEvent(transport, collection, event) {
1933
2047
  ...event.metadata,
1934
2048
  _memory_type: "episodic",
1935
2049
  event_type: event.eventType,
1936
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
2050
+ // NUMERIC unix-seconds, mirroring the core episodic store so
2051
+ // recallRecent/recallOlderThan can range-filter on it.
2052
+ timestamp
1937
2053
  }
1938
2054
  }]
1939
2055
  }
1940
2056
  );
1941
2057
  throwOnError(response);
1942
- return id;
2058
+ return memoryIdToString(id);
1943
2059
  }
1944
2060
  async function recallEpisodicEvents(transport, collection, embedding, k = 5) {
1945
2061
  return transport.searchVectors(collection, embedding, k, { _memory_type: "episodic" });
1946
2062
  }
1947
2063
  async function storeProceduralPattern(transport, collection, pattern) {
1948
- const id = generateUniqueId();
2064
+ const id = pattern.id !== void 0 ? parseRestPointId(pattern.id) : generateUniqueId();
1949
2065
  const response = await transport.requestJson(
1950
2066
  "POST",
1951
2067
  `${collectionPath(collection)}/points`,
@@ -1966,11 +2082,41 @@ async function storeProceduralPattern(transport, collection, pattern) {
1966
2082
  }
1967
2083
  );
1968
2084
  throwOnError(response);
1969
- return id;
2085
+ return memoryIdToString(id);
1970
2086
  }
1971
2087
  async function matchProceduralPatterns(transport, collection, embedding, k = 5) {
1972
2088
  return transport.searchVectors(collection, embedding, k, { _memory_type: "procedural" });
1973
2089
  }
2090
+ function toEpisodicRecord(point) {
2091
+ const payload = point.payload ?? {};
2092
+ if (payload._memory_type !== "episodic") return void 0;
2093
+ if (typeof payload.timestamp !== "number") return void 0;
2094
+ return { id: String(point.id), timestamp: payload.timestamp, payload };
2095
+ }
2096
+ async function scrollEpisodicRecords(transport, collection) {
2097
+ const records = [];
2098
+ let cursor = null;
2099
+ do {
2100
+ const page = await scroll(transport, collection, {
2101
+ cursor: cursor ?? void 0,
2102
+ filter: { _memory_type: "episodic" }
2103
+ });
2104
+ for (const point of page.points) {
2105
+ const record = toEpisodicRecord(point);
2106
+ if (record !== void 0) records.push(record);
2107
+ }
2108
+ cursor = page.nextCursor;
2109
+ } while (cursor !== null && cursor !== void 0);
2110
+ return records;
2111
+ }
2112
+ async function recallRecentEvents(transport, collection, since) {
2113
+ const records = await scrollEpisodicRecords(transport, collection);
2114
+ return records.filter((r) => since === void 0 || r.timestamp >= since).sort((a, b) => b.timestamp - a.timestamp);
2115
+ }
2116
+ async function recallOlderThanEvents(transport, collection, before) {
2117
+ const records = await scrollEpisodicRecords(transport, collection);
2118
+ return records.filter((r) => r.timestamp < before).sort((a, b) => b.timestamp - a.timestamp);
2119
+ }
1974
2120
 
1975
2121
  // src/search-quality.ts
1976
2122
  function searchQualityToMode(quality) {
@@ -2203,6 +2349,64 @@ async function traverseParallel(transport, collection, request2) {
2203
2349
  throwOnError(response, `Collection '${collection}'`);
2204
2350
  return toTraverseResponse(response.data);
2205
2351
  }
2352
+ async function relate(transport, collection, req) {
2353
+ const response = await transport.requestJson(
2354
+ "POST",
2355
+ `${collectionPath(collection)}/relations`,
2356
+ {
2357
+ source: req.source,
2358
+ target: req.target,
2359
+ rel_type: req.relType,
2360
+ properties: req.properties ?? {}
2361
+ }
2362
+ );
2363
+ throwOnError(response, `Collection '${collection}'`);
2364
+ return { edgeId: response.data.edge_id };
2365
+ }
2366
+ async function unrelate(transport, collection, edgeId) {
2367
+ const response = await transport.requestJson(
2368
+ "DELETE",
2369
+ `${collectionPath(collection)}/relations/${encodeURIComponent(String(edgeId))}`
2370
+ );
2371
+ if (response.error !== void 0) {
2372
+ const { code, message } = response.error;
2373
+ const err = parseVelesError(code, message);
2374
+ if (err instanceof EdgeNotFoundError) {
2375
+ return false;
2376
+ }
2377
+ if (code === "NOT_FOUND") {
2378
+ return false;
2379
+ }
2380
+ throwOnError(response, `Collection '${collection}'`);
2381
+ }
2382
+ return true;
2383
+ }
2384
+ async function getRelations(transport, collection, pointId) {
2385
+ const response = await transport.requestJson(
2386
+ "GET",
2387
+ `${collectionPath(collection)}/points/${encodeURIComponent(String(pointId))}/relations`
2388
+ );
2389
+ throwOnError(response, `Collection '${collection}'`);
2390
+ const raw = response.data;
2391
+ return {
2392
+ edges: raw.edges.map((e) => ({
2393
+ id: e.id,
2394
+ source: e.source,
2395
+ target: e.target,
2396
+ relType: e.rel_type,
2397
+ properties: e.properties
2398
+ })),
2399
+ count: raw.count
2400
+ };
2401
+ }
2402
+ async function setTtlDurable(transport, collection, pointId, ttlSeconds) {
2403
+ const response = await transport.requestJson(
2404
+ "PATCH",
2405
+ `${collectionPath(collection)}/points/${encodeURIComponent(String(pointId))}/ttl`,
2406
+ { ttl_seconds: ttlSeconds }
2407
+ );
2408
+ throwOnError(response, `Collection '${collection}'`);
2409
+ }
2206
2410
 
2207
2411
  // src/backends/query-backend.ts
2208
2412
  function isLikelyAggregationQuery(queryString) {
@@ -2335,31 +2539,6 @@ async function collectionSanity(transport, collection) {
2335
2539
  };
2336
2540
  }
2337
2541
 
2338
- // src/backends/scroll-backend.ts
2339
- async function scroll(transport, collection, request2) {
2340
- const body = {};
2341
- if (request2?.cursor !== void 0) {
2342
- body.cursor = request2.cursor;
2343
- }
2344
- if (request2?.batchSize !== void 0) {
2345
- body.batch_size = request2.batchSize;
2346
- }
2347
- if (request2?.filter !== void 0) {
2348
- body.filter = request2.filter;
2349
- }
2350
- const response = await transport.requestJson(
2351
- "POST",
2352
- `${collectionPath(collection)}/points/scroll`,
2353
- body
2354
- );
2355
- throwOnError(response, `Collection '${collection}'`);
2356
- const data = response.data;
2357
- return {
2358
- points: data.points,
2359
- nextCursor: data.next_cursor
2360
- };
2361
- }
2362
-
2363
2542
  // src/backends/admin-backend.ts
2364
2543
  function mapStatsResponse(data) {
2365
2544
  let columnStats;
@@ -2545,9 +2724,17 @@ async function streamInsert(transport, collection, docs) {
2545
2724
  }
2546
2725
  }
2547
2726
  }
2727
+ function requireSafeRangeId(restId) {
2728
+ if (typeof restId === "string") {
2729
+ throw new ValidationError(
2730
+ `streamUpsertPoints requires ids in the JS safe integer range (0..${Number.MAX_SAFE_INTEGER}); use upsert/upsertBatch for string ids above it. Received: ${restId}`
2731
+ );
2732
+ }
2733
+ return restId;
2734
+ }
2548
2735
  async function streamUpsertPoints(transport, collection, docs) {
2549
2736
  const ndjsonLines = docs.map((doc) => {
2550
- const restId = transport.parseRestPointId(doc.id);
2737
+ const restId = requireSafeRangeId(transport.parseRestPointId(doc.id));
2551
2738
  const vector = toNumberArray(doc.vector);
2552
2739
  const point = {
2553
2740
  id: restId,
@@ -2738,6 +2925,22 @@ var RestBackend = class {
2738
2925
  this.ensureInitialized();
2739
2926
  return graphSearch(buildBaseTransport(this.httpConfig), c, r);
2740
2927
  }
2928
+ async relate(c, req) {
2929
+ this.ensureInitialized();
2930
+ return relate(buildCrudTransport(this.httpConfig), c, req);
2931
+ }
2932
+ async unrelate(c, edgeId) {
2933
+ this.ensureInitialized();
2934
+ return unrelate(buildCrudTransport(this.httpConfig), c, edgeId);
2935
+ }
2936
+ async getRelations(c, pointId) {
2937
+ this.ensureInitialized();
2938
+ return getRelations(buildCrudTransport(this.httpConfig), c, pointId);
2939
+ }
2940
+ async setTtlDurable(c, pointId, ttlSeconds) {
2941
+ this.ensureInitialized();
2942
+ return setTtlDurable(buildCrudTransport(this.httpConfig), c, pointId, ttlSeconds);
2943
+ }
2741
2944
  // Search
2742
2945
  async search(c, q, o) {
2743
2946
  this.ensureInitialized();
@@ -2870,6 +3073,14 @@ var RestBackend = class {
2870
3073
  this.ensureInitialized();
2871
3074
  return recallEpisodicEvents(buildAgentMemoryTransport(this.httpConfig, (col, emb, opts) => this.search(col, emb, opts)), c, e, k);
2872
3075
  }
3076
+ async recallRecentEvents(c, since) {
3077
+ this.ensureInitialized();
3078
+ return recallRecentEvents(buildAgentMemoryTransport(this.httpConfig, (col, emb, opts) => this.search(col, emb, opts)), c, since);
3079
+ }
3080
+ async recallOlderThanEvents(c, before) {
3081
+ this.ensureInitialized();
3082
+ return recallOlderThanEvents(buildAgentMemoryTransport(this.httpConfig, (col, emb, opts) => this.search(col, emb, opts)), c, before);
3083
+ }
2873
3084
  async storeProceduralPattern(c, p) {
2874
3085
  this.ensureInitialized();
2875
3086
  return storeProceduralPattern(buildAgentMemoryTransport(this.httpConfig, (col, emb, opts) => this.search(col, emb, opts)), c, p);
@@ -2907,15 +3118,29 @@ var AgentMemoryClient = class {
2907
3118
  async searchFacts(collection, embedding, k = 5) {
2908
3119
  return this.backend.searchSemanticMemory(collection, embedding, k);
2909
3120
  }
2910
- /** Record an episodic event. Returns the generated point ID. */
3121
+ /** Record an episodic event. Returns the point ID (string, u64-safe). */
2911
3122
  async recordEvent(collection, event) {
2912
3123
  return this.backend.recordEpisodicEvent(collection, event);
2913
3124
  }
2914
- /** Recall episodic events */
3125
+ /** Recall episodic events by vector similarity. */
2915
3126
  async recallEvents(collection, embedding, k = 5) {
2916
3127
  return this.backend.recallEpisodicEvents(collection, embedding, k);
2917
3128
  }
2918
- /** Store a procedural pattern. Returns the generated point ID. */
3129
+ /**
3130
+ * Recall episodic events most-recent-first, optionally bounded below by
3131
+ * `since` (inclusive unix-seconds). Mirrors core `episodic.recent(since)`.
3132
+ */
3133
+ async recallRecent(collection, since) {
3134
+ return this.backend.recallRecentEvents(collection, since);
3135
+ }
3136
+ /**
3137
+ * Recall episodic events strictly older than `before` (unix-seconds),
3138
+ * most-recent-first. Mirrors core `episodic.older_than(before)`.
3139
+ */
3140
+ async recallOlderThan(collection, before) {
3141
+ return this.backend.recallOlderThanEvents(collection, before);
3142
+ }
3143
+ /** Store a procedural pattern. Returns the point ID (string, u64-safe). */
2919
3144
  async learnProcedure(collection, pattern) {
2920
3145
  return this.backend.storeProceduralPattern(collection, pattern);
2921
3146
  }
@@ -2923,7 +3148,12 @@ var AgentMemoryClient = class {
2923
3148
  async recallProcedures(collection, embedding, k = 5) {
2924
3149
  return this.backend.matchProceduralPatterns(collection, embedding, k);
2925
3150
  }
2926
- /** Delete a memory entry (fact, event, or procedure) by its point ID. */
3151
+ /**
3152
+ * Delete a memory entry (fact, event, or procedure) by its point ID.
3153
+ *
3154
+ * Accepts the `string` ids returned by `recordEvent` / `learnProcedure`
3155
+ * (u64-safe decimal strings) as well as numeric ids.
3156
+ */
2927
3157
  async deleteMemory(collection, id) {
2928
3158
  return this.backend.delete(collection, id);
2929
3159
  }
@@ -2957,11 +3187,10 @@ function validateDocument(doc, config) {
2957
3187
  validateRestPointId(doc.id, config);
2958
3188
  }
2959
3189
  function validateRestPointId(id, config) {
2960
- if (config.backend === "rest" && (typeof id !== "number" || !Number.isInteger(id) || id < 0 || id > Number.MAX_SAFE_INTEGER)) {
2961
- throw new ValidationError(
2962
- `REST backend requires numeric u64-compatible document IDs in JS safe integer range (0..${Number.MAX_SAFE_INTEGER})`
2963
- );
3190
+ if (config.backend !== "rest") {
3191
+ return;
2964
3192
  }
3193
+ parseRestPointId(id);
2965
3194
  }
2966
3195
 
2967
3196
  // src/client/search-methods.ts
@@ -3142,6 +3371,40 @@ function graphSearch2(backend, collection, request2) {
3142
3371
  requireNonEmptyString(collection, "Collection");
3143
3372
  return backend.graphSearch(collection, request2);
3144
3373
  }
3374
+ function relate2(backend, collection, req) {
3375
+ requireNonEmptyString(collection, "Collection");
3376
+ if (!req.relType || typeof req.relType !== "string") {
3377
+ throw new ValidationError("Relation type is required and must be a string");
3378
+ }
3379
+ if (!isGraphNodeId(req.source) || !isGraphNodeId(req.target)) {
3380
+ throw new ValidationError("Source and target must be numbers or strings");
3381
+ }
3382
+ return backend.relate(collection, req);
3383
+ }
3384
+ function unrelate2(backend, collection, edgeId) {
3385
+ requireNonEmptyString(collection, "Collection");
3386
+ if (!isGraphNodeId(edgeId)) {
3387
+ throw new ValidationError("Edge ID must be a number or string");
3388
+ }
3389
+ return backend.unrelate(collection, edgeId);
3390
+ }
3391
+ function getRelations2(backend, collection, pointId) {
3392
+ requireNonEmptyString(collection, "Collection");
3393
+ if (!isGraphNodeId(pointId)) {
3394
+ throw new ValidationError("Point ID must be a number or string");
3395
+ }
3396
+ return backend.getRelations(collection, pointId);
3397
+ }
3398
+ function setTtlDurable2(backend, collection, pointId, ttlSeconds) {
3399
+ requireNonEmptyString(collection, "Collection");
3400
+ if (!isGraphNodeId(pointId)) {
3401
+ throw new ValidationError("Point ID must be a number or string");
3402
+ }
3403
+ if (typeof ttlSeconds !== "number" || ttlSeconds < 0) {
3404
+ throw new ValidationError("ttlSeconds must be a non-negative number");
3405
+ }
3406
+ return backend.setTtlDurable(collection, pointId, ttlSeconds);
3407
+ }
3145
3408
 
3146
3409
  // src/client.ts
3147
3410
  var VelesDB = class {
@@ -3439,6 +3702,22 @@ var VelesDB = class {
3439
3702
  this.ensureInitialized();
3440
3703
  return graphSearch2(this.backend, collection, request2);
3441
3704
  }
3705
+ async relate(collection, req) {
3706
+ this.ensureInitialized();
3707
+ return relate2(this.backend, collection, req);
3708
+ }
3709
+ async unrelate(collection, edgeId) {
3710
+ this.ensureInitialized();
3711
+ return unrelate2(this.backend, collection, edgeId);
3712
+ }
3713
+ async getRelations(collection, pointId) {
3714
+ this.ensureInitialized();
3715
+ return getRelations2(this.backend, collection, pointId);
3716
+ }
3717
+ async setTtlDurable(collection, pointId, ttlSeconds) {
3718
+ this.ensureInitialized();
3719
+ return setTtlDurable2(this.backend, collection, pointId, ttlSeconds);
3720
+ }
3442
3721
  // ========================================================================
3443
3722
  // Capabilities & Backend Info
3444
3723
  // ========================================================================