Skip to content

Commit f5d6794

Browse files
committed
re-enable relations
1 parent c0f1538 commit f5d6794

File tree

11 files changed

+155
-57
lines changed

11 files changed

+155
-57
lines changed

apps/events/src/components/todos.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { Input } from './ui/input';
1515
export const Todos = () => {
1616
const { data: todos } = useQueryNew(TodoNew, {
1717
mode: 'private',
18-
// include: { assignees: {} },
18+
include: { assignees: {} },
1919
// filter: { or: [{ name: { startsWith: 'aa' } }, { name: { is: 'sdasd' } }] },
2020
});
2121
const { data: users } = useQueryNew(UserNew, { mode: 'private' });
@@ -67,7 +67,7 @@ export const Todos = () => {
6767
{todos.map((todo) => (
6868
<div key={todo.id} className="flex flex-row items-center gap-2">
6969
<h2>{todo.name}</h2>
70-
{/* {todo.assignees.length > 0 && (
70+
{todo.assignees.length > 0 && (
7171
<span className="text-xs text-gray-500">
7272
Assigned to:{' '}
7373
{todo.assignees.map((assignee) => (
@@ -87,7 +87,7 @@ export const Todos = () => {
8787
</span>
8888
))}
8989
</span>
90-
)} */}
90+
)}
9191
<input
9292
type="checkbox"
9393
checked={todo.completed}

apps/events/src/schema.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,7 @@ export class Todo3 extends Entity.Class<Todo3>('Todo3')({
4343
}) {}
4444

4545
export const UserNew = EntityNew(
46-
{
47-
name: TypeNew.String,
48-
},
46+
{ name: TypeNew.String },
4947
{
5048
types: [Id('bffa181e-a333-495b-949c-57f2831d7eca')],
5149
properties: {

packages/hypergraph-react/src/internal/types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,12 @@ export type QueryPublicParams<S extends Entity.AnyNoContext> = {
99
include?: { [K in keyof Schema.Schema.Type<S>]?: Record<string, Record<string, never>> } | undefined;
1010
first?: number | undefined;
1111
};
12+
13+
export type QueryPublicParamsNew<S extends Schema.Schema.AnyNoContext> = {
14+
enabled: boolean;
15+
filter?: Entity.EntityFilter<Schema.Schema.Type<S>> | undefined;
16+
// TODO: for multi-level nesting it should only allow the allowed properties instead of Record<string, Record<string, never>>
17+
include?: { [K in keyof Schema.Schema.Type<S>]?: Record<string, Record<string, never>> } | undefined;
18+
space?: string | undefined;
19+
first?: number | undefined;
20+
};

packages/hypergraph/src/entity/create.ts

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import type { DocHandle } from '@automerge/automerge-repo';
2+
import * as Option from 'effect/Option';
23
import * as Schema from 'effect/Schema';
4+
import * as SchemaAST from 'effect/SchemaAST';
35
import { generateId } from '../utils/generateId.js';
6+
import { isRelation } from '../utils/isRelation.js';
47
import { isRelationField } from '../utils/isRelationField.js';
58
import { encodeToGrc20Json } from './entity-new.js';
69
import { findOne, findOneNew } from './findOne.js';
7-
import type { AnyNoContext, DocumentContent, DocumentRelation, Entity, Insert } from './types.js';
10+
import { PropertyIdSymbol } from './internal-new.js';
11+
import type { AnyNoContext, DocumentContent, DocumentRelation, Entity, EntityNew, Insert } from './types.js';
812

913
/**
1014
* Type utility to transform relation fields to accept string arrays instead of their typed values
@@ -75,19 +79,43 @@ export const create = <const S extends AnyNoContext>(handle: DocHandle<DocumentC
7579
};
7680

7781
export const createNew = <const S extends Schema.Schema.AnyNoContext>(handle: DocHandle<DocumentContent>, type: S) => {
78-
// TODO: return type
79-
return (data: Readonly<WithRelationsAsStringArrays<Schema.Schema.Type<S>>>) => {
82+
return (data: Readonly<WithRelationsAsStringArrays<Schema.Schema.Type<S>>>): EntityNew<S> => {
8083
const entityId = generateId();
8184
const encoded = encodeToGrc20Json(type, { ...data, id: entityId });
8285

86+
const relations: Record<string, DocumentRelation> = {};
87+
88+
const ast = type.ast as SchemaAST.TypeLiteral;
89+
90+
for (const prop of ast.propertySignatures) {
91+
const result = SchemaAST.getAnnotation<string>(PropertyIdSymbol)(prop.type);
92+
if (Option.isSome(result) && isRelation(prop.type)) {
93+
const relationId = generateId();
94+
for (const toEntityId of encoded[result.value] as string[]) {
95+
relations[relationId] = {
96+
from: entityId,
97+
to: toEntityId as string,
98+
fromPropertyId: result.value,
99+
__deleted: false,
100+
};
101+
}
102+
delete encoded[result.value];
103+
}
104+
}
105+
83106
handle.change((doc) => {
84107
doc.entities ??= {};
85108
doc.entities[entityId] = {
86109
...encoded,
87110
__deleted: false,
88111
};
112+
doc.relations ??= {};
113+
// merge relations with existing relations
114+
for (const [relationId, relation] of Object.entries(relations)) {
115+
doc.relations[relationId] = relation;
116+
}
89117
});
90118

91-
return findOneNew(handle, type)(entityId);
119+
return findOneNew(handle, type)(entityId) as EntityNew<S>;
92120
};
93121
};

packages/hypergraph/src/entity/findManyNew.ts

Lines changed: 44 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { canonicalize } from '../utils/jsc.js';
77
import { decodedEntitiesCache, type DecodedEntitiesCacheEntry, type QueryEntry } from './decodedEntitiesCacheNew.js';
88
import { decodeFromGrc20Json } from './entity-new.js';
99
import { entityRelationParentsMap } from './entityRelationParentsMap.js';
10+
import { getEntityRelationsNew } from './getEntityRelationsNew.js';
1011
import { hasValidTypesProperty } from './hasValidTypesProperty.js';
1112
import { TypeIdsSymbol } from './internal-new.js';
1213
import type {
@@ -82,15 +83,17 @@ const subscribeToDocumentChanges = (handle: DocHandle<DocumentContent>) => {
8283
}
8384

8485
const oldDecodedEntry = cacheEntry.entities.get(entityId);
85-
// const relations = getEntityRelations(entityId, cacheEntry.type, doc, includeFromAllQueries);
86+
const relations = getEntityRelationsNew(entityId, cacheEntry.type, doc, includeFromAllQueries);
8687
let decoded: unknown | undefined;
8788
try {
88-
// decoded = cacheEntry.decoder({
8989
decoded = decodeFromGrc20Json(cacheEntry.type, {
9090
...entity,
91-
// ...relations,
9291
id: entityId,
9392
});
93+
decoded = {
94+
...decoded,
95+
...relations,
96+
};
9497
cacheEntry.entities.set(entityId, decoded);
9598
} catch (error) {
9699
// TODO: store the corrupt entity ids somewhere, so they can be read via the API
@@ -99,43 +102,43 @@ const subscribeToDocumentChanges = (handle: DocHandle<DocumentContent>) => {
99102

100103
if (oldDecodedEntry) {
101104
// collect all the Ids for relation entries in the `oldDecodedEntry`
102-
// const deletedRelationIds = new Set<string>();
103-
// for (const [, value] of Object.entries(oldDecodedEntry)) {
104-
// if (Array.isArray(value)) {
105-
// for (const relationEntity of value) {
106-
// deletedRelationIds.add(relationEntity.id);
107-
// }
108-
// }
109-
// }
105+
const deletedRelationIds = new Set<string>();
106+
for (const [, value] of Object.entries(oldDecodedEntry)) {
107+
if (Array.isArray(value)) {
108+
for (const relationEntity of value) {
109+
deletedRelationIds.add(relationEntity.id);
110+
}
111+
}
112+
}
110113
// it's fine to remove all of them since they are re-added below
111-
// for (const deletedRelationId of deletedRelationIds) {
112-
// const deletedRelationEntry = entityRelationParentsMap.get(deletedRelationId);
113-
// if (deletedRelationEntry) {
114-
// deletedRelationEntry.set(cacheEntry, (deletedRelationEntry.get(cacheEntry) ?? 0) - 1);
115-
// if (deletedRelationEntry.get(cacheEntry) === 0) {
116-
// deletedRelationEntry.delete(cacheEntry);
117-
// }
118-
// if (deletedRelationEntry.size === 0) {
119-
// entityRelationParentsMap.delete(deletedRelationId);
120-
// }
121-
// }
122-
// }
114+
for (const deletedRelationId of deletedRelationIds) {
115+
const deletedRelationEntry = entityRelationParentsMap.get(deletedRelationId);
116+
if (deletedRelationEntry) {
117+
deletedRelationEntry.set(cacheEntry, (deletedRelationEntry.get(cacheEntry) ?? 0) - 1);
118+
if (deletedRelationEntry.get(cacheEntry) === 0) {
119+
deletedRelationEntry.delete(cacheEntry);
120+
}
121+
if (deletedRelationEntry.size === 0) {
122+
entityRelationParentsMap.delete(deletedRelationId);
123+
}
124+
}
125+
}
123126
}
124127

125128
if (decoded) {
126129
for (const [, value] of Object.entries(decoded)) {
127-
// if (Array.isArray(value)) {
128-
// for (const relationEntity of value) {
129-
// let relationParentEntry = entityRelationParentsMap.get(relationEntity.id);
130-
// if (relationParentEntry) {
131-
// relationParentEntry.set(cacheEntry, (relationParentEntry.get(cacheEntry) ?? 0) + 1);
132-
// } else {
133-
// relationParentEntry = new Map();
134-
// entityRelationParentsMap.set(relationEntity.id, relationParentEntry);
135-
// relationParentEntry.set(cacheEntry, 1);
136-
// }
137-
// }
138-
// }
130+
if (Array.isArray(value)) {
131+
for (const relationEntity of value) {
132+
let relationParentEntry = entityRelationParentsMap.get(relationEntity.id);
133+
if (relationParentEntry) {
134+
relationParentEntry.set(cacheEntry, (relationParentEntry.get(cacheEntry) ?? 0) + 1);
135+
} else {
136+
relationParentEntry = new Map();
137+
entityRelationParentsMap.set(relationEntity.id, relationParentEntry);
138+
relationParentEntry.set(cacheEntry, 1);
139+
}
140+
}
141+
}
139142
}
140143
}
141144

@@ -247,7 +250,6 @@ export function findManyNew<const S extends Schema.Schema.AnyNoContext>(
247250
);
248251

249252
const doc = handle.doc();
250-
console.log('doc', doc);
251253
if (!doc) {
252254
return { entities: [], corruptEntityIds: [] };
253255
}
@@ -364,10 +366,13 @@ export function findManyNew<const S extends Schema.Schema.AnyNoContext>(
364366
return entity['@@types@@'].includes(typeId);
365367
})
366368
) {
367-
// const relations = getEntityRelations(id, type, doc, include);
369+
const relations = getEntityRelationsNew(id, type, doc, include);
368370
try {
369-
// const decoded = { ...decodeFromGrc20Json(type, { ...entity, ...relations, id }) };
370-
const decoded = { ...decodeFromGrc20Json(type, { ...entity, id }) };
371+
let decoded = { ...decodeFromGrc20Json(type, { ...entity, id }) };
372+
decoded = {
373+
...decoded,
374+
...relations,
375+
};
371376

372377
if (filter) {
373378
if (evaluateEntityFilter(filter, decoded)) {
@@ -426,7 +431,6 @@ export function subscribeToFindManyNew<const S extends Schema.Schema.AnyNoContex
426431
}
427432

428433
const { entities } = findManyNew(handle, type, filter, include);
429-
console.log('getEntities', entities);
430434

431435
for (const entity of entities) {
432436
cacheEntry?.entities.set(entity.id, entity);

packages/hypergraph/src/entity/findOne.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as Schema from 'effect/Schema';
44
import * as SchemaAST from 'effect/SchemaAST';
55
import { decodeFromGrc20Json } from './entity-new.js';
66
import { getEntityRelations } from './getEntityRelations.js';
7+
import { getEntityRelationsNew } from './getEntityRelationsNew.js';
78
import { hasValidTypesProperty } from './hasValidTypesProperty.js';
89
import { TypeIdsSymbol } from './internal-new.js';
910
import type { AnyNoContext, DocumentContent, Entity, EntityNew } from './types.js';
@@ -50,18 +51,18 @@ export const findOneNew = <const S extends Schema.Schema.AnyNoContext>(
5051
// an index and store the decoded values instead of re-decoding over and over again.
5152
const doc = handle.doc();
5253
const entity = doc?.entities?.[id];
53-
console.log('raw entity', entity);
54+
5455
const typeIds = SchemaAST.getAnnotation<string[]>(TypeIdsSymbol)(type.ast as SchemaAST.TypeLiteral).pipe(
5556
Option.getOrElse(() => []),
5657
);
5758

58-
// TODO: implement relations
59+
const relations = doc ? getEntityRelationsNew(id, type, doc, include) : {};
5960

6061
if (hasValidTypesProperty(entity) && typeIds.every((typeId) => entity['@@types@@'].includes(typeId))) {
6162
const decoded = { ...decodeFromGrc20Json(type, { ...entity, id }) };
6263
// injecting the schema to the entity to be able to access it in the preparePublish function
6364
decoded.__schema = type;
64-
return decoded;
65+
return { ...decoded, ...relations };
6566
}
6667

6768
return undefined;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import * as Option from 'effect/Option';
2+
import type * as Schema from 'effect/Schema';
3+
import * as SchemaAST from 'effect/SchemaAST';
4+
import { isRelation } from '../utils/isRelation.js';
5+
import { decodeFromGrc20Json } from './entity-new.js';
6+
import { PropertyIdSymbol, RelationSchemaSymbol } from './internal-new.js';
7+
import type { DocumentContent, EntityNew } from './types.js';
8+
export const getEntityRelationsNew = <const S extends Schema.Schema.AnyNoContext>(
9+
entityId: string,
10+
type: S,
11+
doc: DocumentContent,
12+
include: { [K in keyof Schema.Schema.Type<S>]?: Record<string, Record<string, never>> } | undefined,
13+
) => {
14+
const relations: Record<string, EntityNew<Schema.Schema.AnyNoContext>> = {};
15+
const ast = type.ast as SchemaAST.TypeLiteral;
16+
17+
for (const prop of ast.propertySignatures) {
18+
if (!isRelation(prop.type)) continue;
19+
20+
// TODO: should we add an empty array for relations that are not included?
21+
// Currently we still add an empty array for relations that are not included.
22+
// This is to ensure that the relation is not undefined in the decoded entity.
23+
// In the future we might want to derive a schema based on the include object.
24+
// if (!include?.[fieldName]) {
25+
// relations[fieldName] = [];
26+
// continue;
27+
// }
28+
29+
const relationEntities: Array<EntityNew<Schema.Schema.AnyNoContext>> = [];
30+
31+
for (const [relationId, relation] of Object.entries(doc.relations ?? {})) {
32+
const result = SchemaAST.getAnnotation<string>(PropertyIdSymbol)(prop.type);
33+
const schema = SchemaAST.getAnnotation<Schema.Schema.AnyNoContext>(RelationSchemaSymbol)(prop.type);
34+
if (Option.isSome(result) && Option.isSome(schema)) {
35+
if (relation.fromPropertyId !== result.value || relation.from !== entityId) continue;
36+
if (relation.__deleted) continue;
37+
38+
const relationEntity = doc.entities?.[relation.to];
39+
const decodedRelationEntity = { ...decodeFromGrc20Json(schema.value, { ...relationEntity, id: relation.to }) };
40+
// TODO: should we check if the relation entity is valid?
41+
// if (!hasValidTypesProperty(relationEntity)) continue;
42+
43+
relationEntities.push({ ...decodedRelationEntity, id: relation.to, _relation: { id: relationId } });
44+
}
45+
}
46+
relations[prop.name] = relationEntities;
47+
}
48+
49+
return relations;
50+
};

packages/hypergraph/src/entity/internal-new.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@ export const PropertyIdSymbol = Symbol.for('grc-20/property/id');
22

33
export const TypeIdsSymbol = Symbol.for('grc-20/type/ids');
44

5+
export const RelationSymbol = Symbol.for('grc-20/relation');
6+
57
export const RelationSchemaSymbol = Symbol.for('grc-20/relation/schema');

packages/hypergraph/src/entity/type-new.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as Schema from 'effect/Schema';
2-
import { PropertyIdSymbol, RelationSchemaSymbol } from './internal-new.js';
2+
import { PropertyIdSymbol, RelationSchemaSymbol, RelationSymbol } from './internal-new.js';
33

44
/**
55
* Creates a String schema with the specified GRC-20 property ID
@@ -37,6 +37,6 @@ export const Relation =
3737
<S extends Schema.Schema.AnyNoContext>(schema: S) =>
3838
(propertyId: string) => {
3939
return Schema.Array(schema).pipe(
40-
Schema.annotations({ [PropertyIdSymbol]: propertyId, [RelationSchemaSymbol]: schema }),
40+
Schema.annotations({ [PropertyIdSymbol]: propertyId, [RelationSchemaSymbol]: schema, [RelationSymbol]: true }),
4141
);
4242
};

packages/hypergraph/src/entity/types.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,7 @@ export type DocumentEntity = {
4040
export type DocumentRelation = {
4141
from: string;
4242
to: string;
43-
fromTypeName: string;
44-
fromPropertyName: string;
43+
fromPropertyId: string;
4544
__deleted: boolean;
4645
[key: string]: unknown;
4746
};

0 commit comments

Comments
 (0)