diff --git a/packages/hypergraph-react/src/internal/translate-filter-to-graphql.test.ts b/packages/hypergraph-react/src/internal/translate-filter-to-graphql.test.ts index 434102dd..b754d28f 100644 --- a/packages/hypergraph-react/src/internal/translate-filter-to-graphql.test.ts +++ b/packages/hypergraph-react/src/internal/translate-filter-to-graphql.test.ts @@ -346,3 +346,53 @@ describe('translateFilterToGraphql with complex nested filters', () => { }); }); }); + +describe('translateFilterToGraphql date/point filters', () => { + class Event extends Entity.Class('Event')({ + name: Type.String, + when: Type.Date, + location: Type.Point, + }) {} + + const eventMapping: Mapping.Mapping = { + Event: { + typeIds: [Id('a288444f-06a3-4037-9ace-66fe325864d0')], + properties: { + name: Id('a126ca53-0c8e-48d5-b888-82c734c38935'), + when: Id('9b53690f-ea6d-4bd8-b4d3-9ea01e7f837f'), + location: Id('45e707a5-4364-42fb-bb0b-927a5a8bc061'), + }, + }, + }; + + it('should translate Date `is` filter correctly', () => { + const dt = new Date('2024-01-01T00:00:00Z'); + const filter: Entity.EntityFilter = { when: { is: dt } }; + + const result = translateFilterToGraphql(filter, Event, eventMapping); + + expect(result).toEqual({ + values: { + some: { + propertyId: { is: '9b53690f-ea6d-4bd8-b4d3-9ea01e7f837f' }, + time: { is: Graph.serializeDate(dt) }, + }, + }, + }); + }); + + it('should translate Point `is` filter correctly', () => { + const filter: Entity.EntityFilter = { location: { is: [1, 2] as const } }; + + const result = translateFilterToGraphql(filter, Event, eventMapping); + + expect(result).toEqual({ + values: { + some: { + propertyId: { is: '45e707a5-4364-42fb-bb0b-927a5a8bc061' }, + point: { is: Graph.serializePoint([1, 2]) }, + }, + }, + }); + }); +}); diff --git a/packages/hypergraph-react/src/internal/translate-filter-to-graphql.ts b/packages/hypergraph-react/src/internal/translate-filter-to-graphql.ts index 14ea0ffa..fa1f4483 100644 --- a/packages/hypergraph-react/src/internal/translate-filter-to-graphql.ts +++ b/packages/hypergraph-react/src/internal/translate-filter-to-graphql.ts @@ -17,6 +17,14 @@ type GraphqlFilterEntry = | { propertyId: { is: string }; number: { is: string } | { greaterThan: string } | { lessThan: string }; + } + | { + propertyId: { is: string }; + time: { is: string }; + } + | { + propertyId: { is: string }; + point: { is: string }; }; }; } @@ -125,6 +133,32 @@ export function translateFilterToGraphql( }, }); } + + if (TypeUtils.isDateOrOptionalDateType(type.fields[fieldName]) && fieldFilter.is instanceof Date) { + graphqlFilter.push({ + values: { + some: { + propertyId: { is: propertyId }, + time: { is: Graph.serializeDate(fieldFilter.is) }, + }, + }, + }); + } + + if ( + TypeUtils.isPointOrOptionalPointType(type.fields[fieldName]) && + Array.isArray(fieldFilter.is) && + fieldFilter.is.length === 2 + ) { + graphqlFilter.push({ + values: { + some: { + propertyId: { is: propertyId }, + point: { is: Graph.serializePoint([...(fieldFilter.is as ReadonlyArray)]) }, + }, + }, + }); + } } } diff --git a/packages/hypergraph/src/entity/findMany.ts b/packages/hypergraph/src/entity/findMany.ts index 297d0190..6d1d116f 100644 --- a/packages/hypergraph/src/entity/findMany.ts +++ b/packages/hypergraph/src/entity/findMany.ts @@ -307,7 +307,19 @@ export function findMany( } } } + if (fieldValue instanceof Date && 'is' in fieldFilter) { + const target = (fieldFilter as { is?: Date }).is; + if (target instanceof Date) { + return fieldValue.getTime() === target.getTime(); + } + } + if (Array.isArray(fieldValue) && fieldValue.length === 2 && 'is' in fieldFilter) { + const target = (fieldFilter as { is?: ReadonlyArray }).is; + if (Array.isArray(target) && target.length === 2) { + return fieldValue[0] === target[0] && fieldValue[1] === target[1]; + } + } return true; }; diff --git a/packages/hypergraph/src/entity/types.ts b/packages/hypergraph/src/entity/types.ts index df9449a4..681b97d8 100644 --- a/packages/hypergraph/src/entity/types.ts +++ b/packages/hypergraph/src/entity/types.ts @@ -88,6 +88,14 @@ export type EntityFieldFilter = { endsWith?: string; contains?: string; } - : Record); + : T extends Date + ? { + is?: Date; + } + : T extends ReadonlyArray + ? { + is?: ReadonlyArray; + } + : Record); export type EntityFilter = CrossFieldFilter; diff --git a/packages/hypergraph/test/entity/findMany.test.ts b/packages/hypergraph/test/entity/findMany.test.ts index 23c639a3..53386b74 100644 --- a/packages/hypergraph/test/entity/findMany.test.ts +++ b/packages/hypergraph/test/entity/findMany.test.ts @@ -506,3 +506,40 @@ describe('findMany with filters', () => { }); }); }); + +describe('Date & Point filters', () => { + class Meetup extends Entity.Class('Meetup')({ + name: Type.String, + when: Type.Date, + location: Type.Point, + }) {} + + const spaceId = '9a4a5a1b-2c3d-4e5f-8a9b-1234567890ab'; + const automergeDocId = idToAutomergeId(spaceId) as AnyDocumentId; + + let repo: Repo; + let handle: DocHandle; + + beforeEach(() => { + repo = new Repo({}); + const result = repo.findWithProgress(automergeDocId); + handle = result.handle; + handle.doneLoading(); + }); + + it('filters by Date is ', () => { + Entity.create(handle, Meetup)({ name: 'A', when: new Date('2024-01-01T00:00:00Z'), location: [0, 0] }); + Entity.create(handle, Meetup)({ name: 'B', when: new Date('2025-01-01T00:00:00Z'), location: [0, 0] }); + + const r = Entity.findMany(handle, Meetup, { when: { is: new Date('2024-01-01T00:00:00Z') } }, undefined); + expect(r.entities.map((e) => e.name)).toEqual(['A']); + }); + + it('filters by Point is ', () => { + Entity.create(handle, Meetup)({ name: 'X', when: new Date('2024-01-01T00:00:00Z'), location: [1, 2] }); + Entity.create(handle, Meetup)({ name: 'Y', when: new Date('2024-01-01T00:00:00Z'), location: [3, 4] }); + + const r = Entity.findMany(handle, Meetup, { location: { is: [1, 2] } }, undefined); + expect(r.entities.map((e) => e.name)).toEqual(['X']); + }); +});