diff --git a/.changeset/neat-tires-tie.md b/.changeset/neat-tires-tie.md new file mode 100644 index 00000000..aacf3efd --- /dev/null +++ b/.changeset/neat-tires-tie.md @@ -0,0 +1,7 @@ +--- +"@graphprotocol/hypergraph": patch +"@graphprotocol/hypergraph-react": patch +--- + +add Entity.searchManyPublic + \ No newline at end of file diff --git a/packages/hypergraph/src/entity/index.ts b/packages/hypergraph/src/entity/index.ts index 34713594..54e826c0 100644 --- a/packages/hypergraph/src/entity/index.ts +++ b/packages/hypergraph/src/entity/index.ts @@ -5,5 +5,6 @@ export * from './find-many-public.js'; export * from './findOne.js'; export * from './removeRelation.js'; export * from './schema.js'; +export * from './search-many-public.js'; export * from './types.js'; export * from './update.js'; diff --git a/packages/hypergraph/src/entity/search-many-public.ts b/packages/hypergraph/src/entity/search-many-public.ts new file mode 100644 index 00000000..f2959d7d --- /dev/null +++ b/packages/hypergraph/src/entity/search-many-public.ts @@ -0,0 +1,232 @@ +import { Graph } from '@graphprotocol/grc-20'; +import { Constants, type Entity, Utils } from '@graphprotocol/hypergraph'; +import * as Option from 'effect/Option'; +import * as Schema from 'effect/Schema'; +import * as SchemaAST from 'effect/SchemaAST'; +import { gql, request } from 'graphql-request'; +import { parseResult } from './find-many-public.js'; + +export type SearchManyPublicParams = { + query: string; + filter?: Entity.EntityFilter> | undefined; + // TODO: for multi-level nesting it should only allow the allowed properties instead of Record> + include?: { [K in keyof Schema.Schema.Type]?: Record> } | undefined; + space: string | undefined; + first?: number | undefined; +}; + +const searchQueryDocumentLevel0 = gql` +query search($query: String!, $spaceId: UUID!, $typeIds: [UUID!]!, $first: Int, $filter: EntityFilter!) { + search( + query: $query + filter: { and: [{ + typeIds: {in: $typeIds}, + spaceIds: {in: [$spaceId]}, + }, $filter]} + spaceId: $spaceId + first: $first + offset: 0 + ) { + id + name + valuesList(filter: {spaceId: {is: $spaceId}}) { + propertyId + string + boolean + number + time + point + } + } +} +`; + +const searchQueryDocumentLevel1 = gql` +query search($query: String!, $spaceId: UUID!, $typeIds: [UUID!]!, $relationTypeIdsLevel1: [UUID!]!, $first: Int, $filter: EntityFilter!) { + search( + query: $query + filter: { and: [{ + typeIds: {in: $typeIds}, + spaceIds: {in: [$spaceId]}, + }, $filter]} + spaceId: $spaceId + first: $first + offset: 0 + ) { + id + name + valuesList(filter: {spaceId: {is: $spaceId}}) { + propertyId + string + boolean + number + time + point + } + relationsList( + filter: {spaceId: {is: $spaceId}, typeId:{ in: $relationTypeIdsLevel1}}, + ) { + id + toEntity { + id + name + valuesList(filter: {spaceId: {is: $spaceId}}) { + propertyId + string + boolean + number + time + point + } + } + typeId + } + } +} +`; + +const searchQueryDocumentLevel2 = gql` +query search($query: String!, $spaceId: UUID!, $typeIds: [UUID!]!, $relationTypeIdsLevel1: [UUID!]!, $relationTypeIdsLevel2: [UUID!]!, $first: Int, $filter: EntityFilter!) { + search( + query: $query + filter: { and: [{ + typeIds: {in: $typeIds}, + spaceIds: {in: [$spaceId]}, + }, $filter]} + spaceId: $spaceId + first: $first + offset: 0 + ) { + id + name + valuesList(filter: {spaceId: {is: $spaceId}}) { + propertyId + string + boolean + number + time + point + } + relationsList( + filter: {spaceId: {is: $spaceId}, typeId:{ in: $relationTypeIdsLevel1}}, + ) { + id + toEntity { + id + name + valuesList(filter: {spaceId: {is: $spaceId}}) { + propertyId + string + boolean + number + time + point + } + relationsList( + filter: {spaceId: {is: $spaceId}, typeId:{ in: $relationTypeIdsLevel2}}, + ) { + id + toEntity { + id + name + valuesList(filter: {spaceId: {is: $spaceId}}) { + propertyId + string + boolean + number + time + point + } + } + typeId + } + } + typeId + } + } +} +`; + +type SearchQueryResult = { + search: { + id: string; + name: string; + valuesList: { + propertyId: string; + string: string; + boolean: boolean; + number: number; + time: string; + point: string; + }[]; + relationsList: { + id: string; + toEntity: { + id: string; + name: string; + valuesList: { + propertyId: string; + string: string; + boolean: boolean; + number: number; + time: string; + point: string; + }[]; + relationsList: { + id: string; + toEntity: { + id: string; + name: string; + valuesList: { + propertyId: string; + string: string; + boolean: boolean; + number: number; + time: string; + point: string; + }[]; + }; + typeId: string; + }[]; + }; + typeId: string; + }[]; + }[]; +}; + +export const searchManyPublic = async ( + type: S, + params?: SearchManyPublicParams, +) => { + const { query, filter, include, space, first = 100 } = params ?? {}; + + // constructing the relation type ids for the query + const relationTypeIds = Utils.getRelationTypeIds(type, include); + + const typeIds = SchemaAST.getAnnotation(Constants.TypeIdsSymbol)(type.ast as SchemaAST.TypeLiteral).pipe( + Option.getOrElse(() => []), + ); + + let queryDocument = searchQueryDocumentLevel0; + if (relationTypeIds.level1.length > 0) { + queryDocument = searchQueryDocumentLevel1; + } + if (relationTypeIds.level2.length > 0) { + queryDocument = searchQueryDocumentLevel2; + } + + const filterParams = filter ? Utils.translateFilterToGraphql(filter, type) : {}; + + const result = await request(`${Graph.TESTNET_API_ORIGIN}/graphql`, queryDocument, { + spaceId: space, + typeIds, + query, + relationTypeIdsLevel1: relationTypeIds.level1, + relationTypeIdsLevel2: relationTypeIds.level2, + first, + filter: filterParams, + }); + + const { data, invalidEntities } = parseResult({ entities: result.search }, type); + return { data, invalidEntities }; +};