@semiont/graph 0.5.25 → 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.
- package/README.md +1 -1
- package/dist/index.d.ts +29 -6
- package/dist/index.js +97 -113
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -166,7 +166,7 @@ await graph.getResource(id);
|
|
|
166
166
|
await graph.updateResource(id, updates);
|
|
167
167
|
await graph.deleteResource(id);
|
|
168
168
|
await graph.listResources({ entityTypes: ['Person'] });
|
|
169
|
-
await graph.
|
|
169
|
+
await graph.listResources({ search: 'query' });
|
|
170
170
|
|
|
171
171
|
// Annotation operations
|
|
172
172
|
await graph.createAnnotation(input);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { ResourceDescriptor, ResourceId, UpdateResourceInput, ResourceFilter, CreateAnnotationInternal, Annotation, AnnotationId, AnnotationCategory, GraphConnection, GraphPath, EntityTypeStats, GraphServiceConfig, Logger } from '@semiont/core';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Newest first, ties broken by id — the ordering every `listResources` result
|
|
5
|
+
* must carry.
|
|
6
|
+
*
|
|
7
|
+
* The tiebreak is not cosmetic: browse pages these results with offset/limit,
|
|
8
|
+
* and a partial order lets two pages repeat or drop rows. Ids compare by code
|
|
9
|
+
* point rather than locale so the JS backends agree with the engines, whose
|
|
10
|
+
* `ORDER BY` is codepoint-ordered.
|
|
11
|
+
*/
|
|
12
|
+
declare function compareByRecencyThenId(a: ResourceDescriptor, b: ResourceDescriptor): number;
|
|
3
13
|
interface GraphDatabase {
|
|
4
14
|
connect(): Promise<void>;
|
|
5
15
|
disconnect(): Promise<void>;
|
|
@@ -12,7 +22,6 @@ interface GraphDatabase {
|
|
|
12
22
|
resources: ResourceDescriptor[];
|
|
13
23
|
total: number;
|
|
14
24
|
}>;
|
|
15
|
-
searchResources(query: string, limit?: number): Promise<ResourceDescriptor[]>;
|
|
16
25
|
createAnnotation(input: CreateAnnotationInternal): Promise<Annotation>;
|
|
17
26
|
getAnnotation(id: AnnotationId): Promise<Annotation | null>;
|
|
18
27
|
updateAnnotation(id: AnnotationId, updates: Partial<Annotation>): Promise<Annotation>;
|
|
@@ -102,7 +111,6 @@ declare class Neo4jGraphDatabase implements GraphDatabase {
|
|
|
102
111
|
resources: ResourceDescriptor[];
|
|
103
112
|
total: number;
|
|
104
113
|
}>;
|
|
105
|
-
searchResources(query: string, limit?: number): Promise<ResourceDescriptor[]>;
|
|
106
114
|
createAnnotation(input: CreateAnnotationInternal): Promise<Annotation>;
|
|
107
115
|
getAnnotation(id: AnnotationId): Promise<Annotation | null>;
|
|
108
116
|
updateAnnotation(id: AnnotationId, updates: Partial<Annotation>): Promise<Annotation>;
|
|
@@ -173,11 +181,28 @@ declare class NeptuneGraphDatabase implements GraphDatabase {
|
|
|
173
181
|
getResource(id: ResourceId): Promise<ResourceDescriptor | null>;
|
|
174
182
|
updateResource(id: ResourceId, input: UpdateResourceInput): Promise<ResourceDescriptor>;
|
|
175
183
|
deleteResource(id: ResourceId): Promise<void>;
|
|
184
|
+
/**
|
|
185
|
+
* Filtering, ranking and pagination happen in JS rather than in Gremlin.
|
|
186
|
+
*
|
|
187
|
+
* The ranking ladder (exact name over prefix over substring over path- or
|
|
188
|
+
* tag-assisted) has no natural Gremlin expression, and a rank applied after
|
|
189
|
+
* `range()` would order one page instead of the match set. JanusGraph
|
|
190
|
+
* post-filters for the same reason. Both share `queryResources` with the
|
|
191
|
+
* memory backend so search cannot mean three different things across three
|
|
192
|
+
* backends.
|
|
193
|
+
*
|
|
194
|
+
* The cost is explicit and accepted: this materializes every `Resource`
|
|
195
|
+
* vertex per call, so it is O(N) in the size of the KB rather than in the
|
|
196
|
+
* size of the result. Neo4j is the production path and pushes the whole
|
|
197
|
+
* query — filter, rank, page — into Cypher; Neptune and JanusGraph are not
|
|
198
|
+
* deployment targets today. If either becomes one at scale, the fix is a
|
|
199
|
+
* Gremlin rank expression (`choose` over `toLower`, engine-version
|
|
200
|
+
* permitting), not a return to per-backend search semantics.
|
|
201
|
+
*/
|
|
176
202
|
listResources(filter: ResourceFilter): Promise<{
|
|
177
203
|
resources: ResourceDescriptor[];
|
|
178
204
|
total: number;
|
|
179
205
|
}>;
|
|
180
|
-
searchResources(query: string, limit?: number): Promise<ResourceDescriptor[]>;
|
|
181
206
|
createAnnotation(input: CreateAnnotationInternal): Promise<Annotation>;
|
|
182
207
|
getAnnotation(id: AnnotationId): Promise<Annotation | null>;
|
|
183
208
|
updateAnnotation(id: AnnotationId, updates: Partial<Annotation>): Promise<Annotation>;
|
|
@@ -253,7 +278,6 @@ declare class JanusGraphDatabase implements GraphDatabase {
|
|
|
253
278
|
resources: ResourceDescriptor[];
|
|
254
279
|
total: number;
|
|
255
280
|
}>;
|
|
256
|
-
searchResources(query: string, limit?: number): Promise<ResourceDescriptor[]>;
|
|
257
281
|
createAnnotation(input: CreateAnnotationInternal): Promise<Annotation>;
|
|
258
282
|
getAnnotation(id: AnnotationId): Promise<Annotation | null>;
|
|
259
283
|
updateAnnotation(id: AnnotationId, updates: Partial<Annotation>): Promise<Annotation>;
|
|
@@ -309,7 +333,6 @@ declare class MemoryGraphDatabase implements GraphDatabase {
|
|
|
309
333
|
resources: ResourceDescriptor[];
|
|
310
334
|
total: number;
|
|
311
335
|
}>;
|
|
312
|
-
searchResources(query: string, limit?: number): Promise<ResourceDescriptor[]>;
|
|
313
336
|
createAnnotation(input: CreateAnnotationInternal): Promise<Annotation>;
|
|
314
337
|
getAnnotation(id: AnnotationId): Promise<Annotation | null>;
|
|
315
338
|
updateAnnotation(id: AnnotationId, updates: Partial<Annotation>): Promise<Annotation>;
|
|
@@ -355,5 +378,5 @@ declare class MemoryGraphDatabase implements GraphDatabase {
|
|
|
355
378
|
clearDatabase(): Promise<void>;
|
|
356
379
|
}
|
|
357
380
|
|
|
358
|
-
export { JanusGraphDatabase, MemoryGraphDatabase, Neo4jGraphDatabase, NeptuneGraphDatabase, closeGraphDatabase, createGraphDatabase, getGraphDatabase };
|
|
381
|
+
export { JanusGraphDatabase, MemoryGraphDatabase, Neo4jGraphDatabase, NeptuneGraphDatabase, closeGraphDatabase, compareByRecencyThenId, createGraphDatabase, getGraphDatabase };
|
|
359
382
|
export type { GraphDatabase };
|
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,7 +65,6 @@ 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() {
|
|
@@ -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
|
-
|
|
317
|
-
|
|
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
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
};
|