@semiont/graph 0.5.25 → 0.5.27

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 (28) hide show
  1. package/README.md +1 -1
  2. package/dist/{chunk-JWG64MVA.js → chunk-7BYJ5YHD.js} +3 -3
  3. package/dist/{chunk-JWG64MVA.js.map → chunk-7BYJ5YHD.js.map} +1 -1
  4. package/dist/{chunk-RKAR27K2.js → chunk-NYHKKGBV.js} +88 -10
  5. package/dist/chunk-NYHKKGBV.js.map +1 -0
  6. package/dist/{dist-es-BGEPUOPB.js → dist-es-2ZN6SLSG.js} +9 -9
  7. package/dist/{dist-es-BGEPUOPB.js.map → dist-es-2ZN6SLSG.js.map} +1 -1
  8. package/dist/{dist-es-BUTLC2YS.js → dist-es-7CR3IHJD.js} +5 -5
  9. package/dist/{dist-es-WS6NDSQ5.js → dist-es-J63OY2RR.js} +2 -2
  10. package/dist/{dist-es-TLOL2VHU.js → dist-es-RZ3HNVJG.js} +4 -4
  11. package/dist/{dist-es-WLYFLMD3.js → dist-es-V3UQXKNJ.js} +2 -2
  12. package/dist/index.d.ts +29 -6
  13. package/dist/index.js +98 -114
  14. package/dist/index.js.map +1 -1
  15. package/dist/{loadSso-GOLDOL76.js → loadSso-JTXSMWFI.js} +3 -3
  16. package/dist/{signin-TS4P2IEW.js → signin-BM6IHIKX.js} +3 -3
  17. package/dist/{sso-oidc-QHNBBLAM.js → sso-oidc-IBGZ52NP.js} +3 -3
  18. package/dist/{sts-ZKQ7LNEG.js → sts-44HZWQDH.js} +3 -3
  19. package/package.json +4 -4
  20. package/dist/chunk-RKAR27K2.js.map +0 -1
  21. /package/dist/{dist-es-BUTLC2YS.js.map → dist-es-7CR3IHJD.js.map} +0 -0
  22. /package/dist/{dist-es-WS6NDSQ5.js.map → dist-es-J63OY2RR.js.map} +0 -0
  23. /package/dist/{dist-es-TLOL2VHU.js.map → dist-es-RZ3HNVJG.js.map} +0 -0
  24. /package/dist/{dist-es-WLYFLMD3.js.map → dist-es-V3UQXKNJ.js.map} +0 -0
  25. /package/dist/{loadSso-GOLDOL76.js.map → loadSso-JTXSMWFI.js.map} +0 -0
  26. /package/dist/{signin-TS4P2IEW.js.map → signin-BM6IHIKX.js.map} +0 -0
  27. /package/dist/{sso-oidc-QHNBBLAM.js.map → sso-oidc-IBGZ52NP.js.map} +0 -0
  28. /package/dist/{sts-ZKQ7LNEG.js.map → sts-44HZWQDH.js.map} +0 -0
@@ -60,7 +60,7 @@ var resolveAssumeRoleCredentials = async (profileName, profiles, options, caller
60
60
  const profileData = profiles[profileName];
61
61
  const { source_profile, region } = profileData;
62
62
  if (!options.roleAssumer) {
63
- const { getDefaultRoleAssumer } = await import("./sts-ZKQ7LNEG.js");
63
+ const { getDefaultRoleAssumer } = await import("./sts-44HZWQDH.js");
64
64
  options.roleAssumer = getDefaultRoleAssumer({
65
65
  ...options.clientConfig,
66
66
  credentialProviderLogger: options.logger,
@@ -109,7 +109,7 @@ var isLoginProfile = (data) => {
109
109
  return Boolean(data && data.login_session);
110
110
  };
111
111
  var resolveLoginCredentials = async (profileName, options, callerClientConfig) => {
112
- const { fromLoginCredentials } = await import("./dist-es-WLYFLMD3.js");
112
+ const { fromLoginCredentials } = await import("./dist-es-V3UQXKNJ.js");
113
113
  const credentials = await fromLoginCredentials({
114
114
  ...options,
115
115
  profile: profileName
@@ -130,7 +130,7 @@ var resolveProcessCredentials = async (options, profile) => {
130
130
 
131
131
  // ../../node_modules/@aws-sdk/credential-provider-ini/dist-es/resolveSsoCredentials.js
132
132
  var resolveSsoCredentials = async (profile, profileData, options = {}, callerClientConfig) => {
133
- const { fromSSO } = await import("./dist-es-TLOL2VHU.js");
133
+ const { fromSSO } = await import("./dist-es-RZ3HNVJG.js");
134
134
  return fromSSO({
135
135
  profile,
136
136
  logger: options.logger,
@@ -165,7 +165,7 @@ var resolveStaticCredentials = async (profile, options) => {
165
165
  // ../../node_modules/@aws-sdk/credential-provider-ini/dist-es/resolveWebIdentityCredentials.js
166
166
  var isWebIdentityProfile = (arg) => Boolean(arg) && typeof arg === "object" && typeof arg.web_identity_token_file === "string" && typeof arg.role_arn === "string" && ["undefined", "string"].indexOf(typeof arg.role_session_name) > -1;
167
167
  var resolveWebIdentityCredentials = async (profile, options, callerClientConfig) => {
168
- const { fromTokenFile } = await import("./dist-es-WS6NDSQ5.js");
168
+ const { fromTokenFile } = await import("./dist-es-J63OY2RR.js");
169
169
  const credentials = await fromTokenFile({
170
170
  webIdentityTokenFile: profile.web_identity_token_file,
171
171
  roleArn: profile.role_arn,
@@ -217,4 +217,4 @@ var fromIni = (init = {}) => async ({ callerClientConfig } = {}) => {
217
217
  export {
218
218
  fromIni
219
219
  };
220
- //# sourceMappingURL=dist-es-BUTLC2YS.js.map
220
+ //# sourceMappingURL=dist-es-7CR3IHJD.js.map
@@ -16,7 +16,7 @@ var fromWebToken = (init) => async (awsIdentityProperties) => {
16
16
  const { roleArn, roleSessionName, webIdentityToken, providerId, policyArns, policy, durationSeconds } = init;
17
17
  let { roleAssumerWithWebIdentity } = init;
18
18
  if (!roleAssumerWithWebIdentity) {
19
- const { getDefaultRoleAssumerWithWebIdentity } = await import("./sts-ZKQ7LNEG.js");
19
+ const { getDefaultRoleAssumerWithWebIdentity } = await import("./sts-44HZWQDH.js");
20
20
  roleAssumerWithWebIdentity = getDefaultRoleAssumerWithWebIdentity({
21
21
  ...init.clientConfig,
22
22
  credentialProviderLogger: init.logger,
@@ -66,4 +66,4 @@ export {
66
66
  fromTokenFile,
67
67
  fromWebToken
68
68
  };
69
- //# sourceMappingURL=dist-es-WS6NDSQ5.js.map
69
+ //# sourceMappingURL=dist-es-J63OY2RR.js.map
@@ -21,7 +21,7 @@ var REFRESH_MESSAGE = `To refresh this SSO session run 'aws sso login' with the
21
21
 
22
22
  // ../../node_modules/@aws-sdk/token-providers/dist-es/getSsoOidcClient.js
23
23
  var getSsoOidcClient = async (ssoRegion, init = {}, callerClientConfig) => {
24
- const { SSOOIDCClient } = await import("./sso-oidc-QHNBBLAM.js");
24
+ const { SSOOIDCClient } = await import("./sso-oidc-IBGZ52NP.js");
25
25
  const coalesce = (prop) => init.clientConfig?.[prop] ?? init.parentClientConfig?.[prop] ?? callerClientConfig?.[prop];
26
26
  const ssoOidcClient = new SSOOIDCClient(Object.assign({}, init.clientConfig ?? {}, {
27
27
  region: ssoRegion ?? init.clientConfig?.region,
@@ -33,7 +33,7 @@ var getSsoOidcClient = async (ssoRegion, init = {}, callerClientConfig) => {
33
33
 
34
34
  // ../../node_modules/@aws-sdk/token-providers/dist-es/getNewSsoOidcToken.js
35
35
  var getNewSsoOidcToken = async (ssoToken, ssoRegion, init = {}, callerClientConfig) => {
36
- const { CreateTokenCommand } = await import("./sso-oidc-QHNBBLAM.js");
36
+ const { CreateTokenCommand } = await import("./sso-oidc-IBGZ52NP.js");
37
37
  const ssoOidcClient = await getSsoOidcClient(ssoRegion, init, callerClientConfig);
38
38
  return ssoOidcClient.send(new CreateTokenCommand({
39
39
  clientId: ssoToken.clientId,
@@ -181,7 +181,7 @@ var resolveSSOCredentials = async ({ ssoStartUrl, ssoSession, ssoAccountId, ssoR
181
181
  });
182
182
  }
183
183
  const { accessToken } = token;
184
- const { SSOClient, GetRoleCredentialsCommand } = await import("./loadSso-GOLDOL76.js");
184
+ const { SSOClient, GetRoleCredentialsCommand } = await import("./loadSso-JTXSMWFI.js");
185
185
  const sso = ssoClient || new SSOClient(Object.assign({}, clientConfig ?? {}, {
186
186
  logger: clientConfig?.logger ?? callerClientConfig?.logger ?? parentClientConfig?.logger,
187
187
  region: clientConfig?.region ?? ssoRegion,
@@ -314,4 +314,4 @@ export {
314
314
  isSsoProfile,
315
315
  validateSsoProfile
316
316
  };
317
- //# sourceMappingURL=dist-es-TLOL2VHU.js.map
317
+ //# sourceMappingURL=dist-es-RZ3HNVJG.js.map
@@ -64,7 +64,7 @@ var LoginCredentialsFetcher = class _LoginCredentialsFetcher {
64
64
  if (freshExpiry - Date.now() > _LoginCredentialsFetcher.REFRESH_THRESHOLD) {
65
65
  return this.toCredentials(freshToken.accessToken);
66
66
  }
67
- const { SigninClient, CreateOAuth2TokenCommand } = await import("./signin-TS4P2IEW.js");
67
+ const { SigninClient, CreateOAuth2TokenCommand } = await import("./signin-BM6IHIKX.js");
68
68
  const { logger, userAgentAppId } = this.callerClientConfig ?? {};
69
69
  const isH2 = (requestHandler2) => {
70
70
  return requestHandler2?.metadata?.handlerProtocol === "h2";
@@ -291,4 +291,4 @@ var fromLoginCredentials = (init) => async ({ callerClientConfig } = {}) => {
291
291
  export {
292
292
  fromLoginCredentials
293
293
  };
294
- //# sourceMappingURL=dist-es-WLYFLMD3.js.map
294
+ //# sourceMappingURL=dist-es-V3UQXKNJ.js.map
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,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-BGEPUOPB.js");
72
+ const neptuneModule = await import("./dist-es-2ZN6SLSG.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
  };