@semiont/graph 0.5.24 → 0.5.26

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.
Files changed (46) hide show
  1. package/README.md +1 -1
  2. package/dist/{chunk-7X77XSVD.js → chunk-JWG64MVA.js} +7 -7
  3. package/dist/{chunk-7X77XSVD.js.map → chunk-JWG64MVA.js.map} +1 -1
  4. package/dist/{chunk-ZPTG6NGD.js → chunk-RBRHTAJN.js} +2 -2
  5. package/dist/{chunk-NO67W7EF.js → chunk-RKAR27K2.js} +678 -265
  6. package/dist/chunk-RKAR27K2.js.map +1 -0
  7. package/dist/{chunk-FR442V6O.js → chunk-UKT6IXAM.js} +3 -3
  8. package/dist/{chunk-AJ4W3DS7.js → chunk-Y62BMZ25.js} +2 -2
  9. package/dist/{chunk-7WLIHNPO.js → chunk-YYVVWLB2.js} +1 -2
  10. package/dist/{dist-es-TBYZWFUA.js → dist-es-BGEPUOPB.js} +19 -19
  11. package/dist/dist-es-BGEPUOPB.js.map +1 -0
  12. package/dist/{dist-es-63RSTSM2.js → dist-es-BUTLC2YS.js} +12 -12
  13. package/dist/{dist-es-R36PCSLH.js → dist-es-ILOZ33QI.js} +4 -4
  14. package/dist/{dist-es-QDQUKRHU.js → dist-es-LN4LZWKV.js} +2 -2
  15. package/dist/{dist-es-5CNHCO76.js → dist-es-M72B762W.js} +4 -4
  16. package/dist/{dist-es-BTCEI257.js → dist-es-NGG7JHEH.js} +3 -3
  17. package/dist/{dist-es-OHWBFB6Z.js → dist-es-TLOL2VHU.js} +6 -6
  18. package/dist/{dist-es-C3XGVVKR.js → dist-es-WLYFLMD3.js} +39 -32
  19. package/dist/dist-es-WLYFLMD3.js.map +1 -0
  20. package/dist/{dist-es-ZOQMUI6H.js → dist-es-WS6NDSQ5.js} +4 -4
  21. package/dist/index.d.ts +29 -6
  22. package/dist/index.js +98 -114
  23. package/dist/index.js.map +1 -1
  24. package/dist/{loadSso-OMYS2YH5.js → loadSso-GOLDOL76.js} +6 -6
  25. package/dist/{signin-OZ4KQMKS.js → signin-TS4P2IEW.js} +6 -6
  26. package/dist/{sso-oidc-K2XXESPI.js → sso-oidc-QHNBBLAM.js} +6 -6
  27. package/dist/{sts-4YKAIUZO.js → sts-ZKQ7LNEG.js} +6 -6
  28. package/package.json +6 -6
  29. package/dist/chunk-NO67W7EF.js.map +0 -1
  30. package/dist/dist-es-C3XGVVKR.js.map +0 -1
  31. package/dist/dist-es-TBYZWFUA.js.map +0 -1
  32. /package/dist/{chunk-ZPTG6NGD.js.map → chunk-RBRHTAJN.js.map} +0 -0
  33. /package/dist/{chunk-FR442V6O.js.map → chunk-UKT6IXAM.js.map} +0 -0
  34. /package/dist/{chunk-AJ4W3DS7.js.map → chunk-Y62BMZ25.js.map} +0 -0
  35. /package/dist/{chunk-7WLIHNPO.js.map → chunk-YYVVWLB2.js.map} +0 -0
  36. /package/dist/{dist-es-63RSTSM2.js.map → dist-es-BUTLC2YS.js.map} +0 -0
  37. /package/dist/{dist-es-R36PCSLH.js.map → dist-es-ILOZ33QI.js.map} +0 -0
  38. /package/dist/{dist-es-QDQUKRHU.js.map → dist-es-LN4LZWKV.js.map} +0 -0
  39. /package/dist/{dist-es-5CNHCO76.js.map → dist-es-M72B762W.js.map} +0 -0
  40. /package/dist/{dist-es-BTCEI257.js.map → dist-es-NGG7JHEH.js.map} +0 -0
  41. /package/dist/{dist-es-OHWBFB6Z.js.map → dist-es-TLOL2VHU.js.map} +0 -0
  42. /package/dist/{dist-es-ZOQMUI6H.js.map → dist-es-WS6NDSQ5.js.map} +0 -0
  43. /package/dist/{loadSso-OMYS2YH5.js.map → loadSso-GOLDOL76.js.map} +0 -0
  44. /package/dist/{signin-OZ4KQMKS.js.map → signin-TS4P2IEW.js.map} +0 -0
  45. /package/dist/{sso-oidc-K2XXESPI.js.map → sso-oidc-QHNBBLAM.js.map} +0 -0
  46. /package/dist/{sts-4YKAIUZO.js.map → sts-ZKQ7LNEG.js.map} +0 -0
package/dist/index.js CHANGED
@@ -6,6 +6,54 @@ function assertMutableResourceUpdate(input) {
6
6
  throw new Error("Resources are immutable apart from archival state and entity tags.");
7
7
  }
8
8
  }
9
+ function compareByRecencyThenId(a, b) {
10
+ const aTime = a.dateCreated ? Date.parse(a.dateCreated) : 0;
11
+ const bTime = b.dateCreated ? Date.parse(b.dateCreated) : 0;
12
+ if (aTime !== bTime) return bTime - aTime;
13
+ const aId = String(a["@id"]);
14
+ const bId = String(b["@id"]);
15
+ return aId < bId ? -1 : aId > bId ? 1 : 0;
16
+ }
17
+
18
+ // src/resource-query.ts
19
+ import { getResourceEntityTypes } from "@semiont/core";
20
+ function searchTerms(query) {
21
+ return query.toLowerCase().split(/\s+/).filter(Boolean);
22
+ }
23
+ function searchRank(resource, query) {
24
+ const whole = query.trim().toLowerCase();
25
+ const name = (resource.name ?? "").toLowerCase();
26
+ if (name === whole) return 0;
27
+ if (name.startsWith(whole)) return 1;
28
+ if (searchTerms(query).every((term) => name.includes(term))) return 2;
29
+ return 3;
30
+ }
31
+ function matchesSearch(resource, terms) {
32
+ const name = (resource.name ?? "").toLowerCase();
33
+ const uri = resource.storageUri?.toLowerCase() ?? "";
34
+ const types = getResourceEntityTypes(resource).map((t) => t.toLowerCase());
35
+ return terms.every((term) => name.includes(term) || uri.includes(term) || types.some((t) => t.includes(term)));
36
+ }
37
+ function queryResources(all, filter) {
38
+ let matches = all;
39
+ if (filter.entityTypes && filter.entityTypes.length > 0) {
40
+ matches = matches.filter((doc) => filter.entityTypes.some((type) => getResourceEntityTypes(doc).includes(type)));
41
+ }
42
+ const terms = filter.search ? searchTerms(filter.search) : [];
43
+ if (terms.length > 0) {
44
+ matches = matches.filter((doc) => matchesSearch(doc, terms));
45
+ }
46
+ if (filter.archived !== void 0) {
47
+ matches = matches.filter((doc) => (doc.archived ?? false) === filter.archived);
48
+ }
49
+ const search = terms.length > 0 ? filter.search : void 0;
50
+ const ordered = [...matches].sort(
51
+ search ? (a, b) => searchRank(a, search) - searchRank(b, search) || compareByRecencyThenId(a, b) : compareByRecencyThenId
52
+ );
53
+ const offset = filter.offset ?? 0;
54
+ const limit = filter.limit ?? 20;
55
+ return { resources: ordered.slice(offset, offset + limit), total: ordered.length };
56
+ }
9
57
 
10
58
  // src/implementations/neptune.ts
11
59
  import { getEntityTypes } from "@semiont/ontology";
@@ -17,12 +65,11 @@ var DescribeDBClustersCommand;
17
65
  var gremlin;
18
66
  var process2;
19
67
  var TextP;
20
- var order;
21
68
  var cardinality;
22
69
  var __;
23
70
  async function loadDependencies() {
24
71
  if (!NeptuneClient) {
25
- const neptuneModule = await import("./dist-es-TBYZWFUA.js");
72
+ const neptuneModule = await import("./dist-es-BGEPUOPB.js");
26
73
  NeptuneClient = neptuneModule.NeptuneClient;
27
74
  DescribeDBClustersCommand = neptuneModule.DescribeDBClustersCommand;
28
75
  }
@@ -30,7 +77,6 @@ async function loadDependencies() {
30
77
  gremlin = await import("gremlin");
31
78
  process2 = gremlin.process;
32
79
  TextP = process2.TextP;
33
- order = process2.order;
34
80
  cardinality = process2.cardinality;
35
81
  __ = process2.statics;
36
82
  }
@@ -311,52 +357,33 @@ var NeptuneGraphDatabase = class {
311
357
  throw error;
312
358
  }
313
359
  }
360
+ /**
361
+ * Filtering, ranking and pagination happen in JS rather than in Gremlin.
362
+ *
363
+ * The ranking ladder (exact name over prefix over substring over path- or
364
+ * tag-assisted) has no natural Gremlin expression, and a rank applied after
365
+ * `range()` would order one page instead of the match set. JanusGraph
366
+ * post-filters for the same reason. Both share `queryResources` with the
367
+ * memory backend so search cannot mean three different things across three
368
+ * backends.
369
+ *
370
+ * The cost is explicit and accepted: this materializes every `Resource`
371
+ * vertex per call, so it is O(N) in the size of the KB rather than in the
372
+ * size of the result. Neo4j is the production path and pushes the whole
373
+ * query — filter, rank, page — into Cypher; Neptune and JanusGraph are not
374
+ * deployment targets today. If either becomes one at scale, the fix is a
375
+ * Gremlin rank expression (`choose` over `toLower`, engine-version
376
+ * permitting), not a return to per-backend search semantics.
377
+ */
314
378
  async listResources(filter) {
315
379
  try {
316
- let traversal = this.g.V().hasLabel("Resource");
317
- if (filter.entityTypes && filter.entityTypes.length > 0) {
318
- traversal = traversal.filter(
319
- process2.statics.or(
320
- ...filter.entityTypes.map(
321
- (type) => process2.statics.has("entityTypes", TextP.containing(`"${type}"`))
322
- )
323
- )
324
- );
325
- }
326
- if (filter.search) {
327
- traversal = traversal.filter(
328
- process2.statics.or(
329
- process2.statics.has("name", TextP.containing(filter.search)),
330
- process2.statics.has("storageUri", TextP.containing(filter.search))
331
- )
332
- );
333
- }
334
- const totalResult = await traversal.clone().count().next();
335
- const total = totalResult.value || 0;
336
- const offset = filter.offset || 0;
337
- const limit = filter.limit || 20;
338
- const results = await traversal.order().by("created", order.desc).range(offset, offset + limit).elementMap().toList();
339
- const resources = results.map(vertexToResource);
340
- return { resources, total };
380
+ const results = await this.g.V().hasLabel("Resource").elementMap().toList();
381
+ return queryResources(results.map(vertexToResource), filter);
341
382
  } catch (error) {
342
383
  this.logger?.error("Failed to list resources from Neptune", { error });
343
384
  throw error;
344
385
  }
345
386
  }
346
- async searchResources(query, limit = 20) {
347
- try {
348
- const results = await this.g.V().hasLabel("Resource").filter(
349
- process2.statics.or(
350
- process2.statics.has("name", TextP.containing(query)),
351
- process2.statics.has("storageUri", TextP.containing(query))
352
- )
353
- ).order().by("created", order.desc).limit(limit).elementMap().toList();
354
- return results.map(vertexToResource);
355
- } catch (error) {
356
- this.logger?.error("Failed to search resources in Neptune", { error });
357
- throw error;
358
- }
359
- }
360
387
  async createAnnotation(input) {
361
388
  const id = input.id;
362
389
  const annotation = {
@@ -1009,14 +1036,24 @@ var Neo4jGraphDatabase = class {
1009
1036
  try {
1010
1037
  let whereClause = "";
1011
1038
  const params = {};
1012
- const conditions = [];
1039
+ const conditions = ["coalesce(d.stub, false) = false"];
1040
+ if (filter.archived !== void 0) {
1041
+ conditions.push("d.archived = $archived");
1042
+ params.archived = filter.archived;
1043
+ }
1013
1044
  if (filter.entityTypes && filter.entityTypes.length > 0) {
1014
1045
  conditions.push("ANY(type IN $entityTypes WHERE type IN d.entityTypes)");
1015
1046
  params.entityTypes = filter.entityTypes;
1016
1047
  }
1017
- if (filter.search) {
1018
- conditions.push('(toLower(d.name) CONTAINS toLower($search) OR toLower(coalesce(d.storageUri, "")) CONTAINS toLower($search))');
1019
- params.search = filter.search;
1048
+ const terms = filter.search ? searchTerms(filter.search) : [];
1049
+ if (terms.length > 0) {
1050
+ conditions.push(
1051
+ `ALL(t IN $terms WHERE toLower(d.name) CONTAINS t
1052
+ OR toLower(coalesce(d.storageUri, "")) CONTAINS t
1053
+ OR ANY(e IN coalesce(d.entityTypes, []) WHERE toLower(e) CONTAINS t))`
1054
+ );
1055
+ params.terms = terms;
1056
+ params.search = filter.search.trim().toLowerCase();
1020
1057
  }
1021
1058
  if (conditions.length > 0) {
1022
1059
  whereClause = "WHERE " + conditions.join(" AND ");
@@ -1028,10 +1065,18 @@ var Neo4jGraphDatabase = class {
1028
1065
  const total = countResult.records[0].get("total").toNumber();
1029
1066
  params.skip = this.neo4j.int(filter.offset || 0);
1030
1067
  params.limit = this.neo4j.int(filter.limit || 20);
1068
+ const rankClause = terms.length > 0 ? `WITH d, CASE
1069
+ WHEN toLower(d.name) = $search THEN 0
1070
+ WHEN toLower(d.name) STARTS WITH $search THEN 1
1071
+ WHEN ALL(t IN $terms WHERE toLower(d.name) CONTAINS t) THEN 2
1072
+ ELSE 3
1073
+ END AS rank
1074
+ ` : "";
1075
+ const orderClause = terms.length > 0 ? "ORDER BY rank, d.created DESC, d.id" : "ORDER BY d.created DESC, d.id";
1031
1076
  const result = await session.run(
1032
1077
  `MATCH (d:Resource) ${whereClause}
1033
- RETURN d
1034
- ORDER BY d.updatedAt DESC
1078
+ ${rankClause}RETURN d
1079
+ ${orderClause}
1035
1080
  SKIP $skip LIMIT $limit`,
1036
1081
  params
1037
1082
  );
@@ -1041,23 +1086,6 @@ var Neo4jGraphDatabase = class {
1041
1086
  await session.close();
1042
1087
  }
1043
1088
  }
1044
- async searchResources(query, limit = 20) {
1045
- const session = this.getSession();
1046
- try {
1047
- const result = await session.run(
1048
- `MATCH (d:Resource)
1049
- WHERE toLower(d.name) CONTAINS toLower($query)
1050
- OR toLower(coalesce(d.storageUri, "")) CONTAINS toLower($query)
1051
- RETURN d
1052
- ORDER BY d.updatedAt DESC
1053
- LIMIT $limit`,
1054
- { query, limit: this.neo4j.int(limit) }
1055
- );
1056
- return result.records.map((record) => this.parseResourceNode(record.get("d")));
1057
- } finally {
1058
- await session.close();
1059
- }
1060
- }
1061
1089
  async createAnnotation(input) {
1062
1090
  const session = this.getSession();
1063
1091
  try {
@@ -2004,29 +2032,7 @@ var JanusGraphDatabase = class {
2004
2032
  }
2005
2033
  async listResources(filter) {
2006
2034
  const docs = await this.g.V().hasLabel("Resource").toList();
2007
- let resources = docs.map((v) => this.vertexToResource(v));
2008
- if (filter.search) {
2009
- const needle = filter.search.toLowerCase();
2010
- resources = resources.filter(
2011
- (doc) => (doc.name?.toLowerCase().includes(needle) ?? false) || (doc.storageUri?.toLowerCase().includes(needle) ?? false)
2012
- );
2013
- }
2014
- if (filter.entityTypes && filter.entityTypes.length > 0) {
2015
- resources = resources.filter(
2016
- (doc) => filter.entityTypes.some((type) => doc.entityTypes?.includes(type))
2017
- );
2018
- }
2019
- const total = resources.length;
2020
- const offset = filter.offset || 0;
2021
- const limit = filter.limit || 50;
2022
- return {
2023
- resources: resources.slice(offset, offset + limit),
2024
- total
2025
- };
2026
- }
2027
- async searchResources(query, limit) {
2028
- const result = await this.listResources({ search: query, limit: limit || 10 });
2029
- return result.resources;
2035
+ return queryResources(docs.map((v) => this.vertexToResource(v)), filter);
2030
2036
  }
2031
2037
  async createAnnotation(input) {
2032
2038
  const id = input.id;
@@ -2358,7 +2364,7 @@ var JanusGraphDatabase = class {
2358
2364
  // src/implementations/memorygraph.ts
2359
2365
  import { resourceId as makeResourceId2, annotationId as makeAnnotationId4 } from "@semiont/core";
2360
2366
  import { v4 as uuidv44 } from "uuid";
2361
- import { getBodySource as getBodySource4, getTargetSource as getTargetSource3, getResourceId as getResourceId3, getPrimaryRepresentation as getPrimaryRepresentation4, getResourceEntityTypes } from "@semiont/core";
2367
+ import { getBodySource as getBodySource4, getTargetSource as getTargetSource3, getResourceId as getResourceId3, getPrimaryRepresentation as getPrimaryRepresentation4, getResourceEntityTypes as getResourceEntityTypes2 } from "@semiont/core";
2362
2368
  var MemoryGraphDatabase = class {
2363
2369
  connected = false;
2364
2370
  logger;
@@ -2407,30 +2413,7 @@ var MemoryGraphDatabase = class {
2407
2413
  }
2408
2414
  }
2409
2415
  async listResources(filter) {
2410
- let docs = Array.from(this.resources.values());
2411
- if (filter.entityTypes && filter.entityTypes.length > 0) {
2412
- docs = docs.filter(
2413
- (doc) => doc.entityTypes && doc.entityTypes.some((type) => filter.entityTypes.includes(type))
2414
- );
2415
- }
2416
- if (filter.search) {
2417
- const searchLower = filter.search.toLowerCase();
2418
- docs = docs.filter(
2419
- (doc) => doc.name.toLowerCase().includes(searchLower) || (doc.storageUri?.toLowerCase().includes(searchLower) ?? false)
2420
- );
2421
- }
2422
- const total = docs.length;
2423
- const offset = filter.offset || 0;
2424
- const limit = filter.limit || 20;
2425
- docs = docs.slice(offset, offset + limit);
2426
- return { resources: docs, total };
2427
- }
2428
- async searchResources(query, limit = 20) {
2429
- const searchLower = query.toLowerCase();
2430
- const results = Array.from(this.resources.values()).filter(
2431
- (doc) => doc.name.toLowerCase().includes(searchLower) || (doc.storageUri?.toLowerCase().includes(searchLower) ?? false)
2432
- ).slice(0, limit);
2433
- return results;
2416
+ return queryResources(Array.from(this.resources.values()), filter);
2434
2417
  }
2435
2418
  async createAnnotation(input) {
2436
2419
  const id = input.id;
@@ -2583,7 +2566,7 @@ var MemoryGraphDatabase = class {
2583
2566
  async getEntityTypeStats() {
2584
2567
  const typeCounts = /* @__PURE__ */ new Map();
2585
2568
  for (const doc of this.resources.values()) {
2586
- const types = getResourceEntityTypes(doc);
2569
+ const types = getResourceEntityTypes2(doc);
2587
2570
  for (const type of types) {
2588
2571
  typeCounts.set(type, (typeCounts.get(type) || 0) + 1);
2589
2572
  }
@@ -2787,6 +2770,7 @@ export {
2787
2770
  Neo4jGraphDatabase,
2788
2771
  NeptuneGraphDatabase,
2789
2772
  closeGraphDatabase,
2773
+ compareByRecencyThenId,
2790
2774
  createGraphDatabase,
2791
2775
  getGraphDatabase
2792
2776
  };