Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .changeset/add-space-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"@graphprotocol/hypergraph": patch
"@graphprotocol/hypergraph-react": patch
---

Add `type` field to `PublicSpace` type returned by `Space.findManyPublic()` and `usePublicSpaces()`. The type is either `"PERSONAL"` or `"DAO"`.

Add `spaceType` filter option to `Space.findManyPublic()` and `usePublicSpaces()` to filter spaces by type. Example usage:

```typescript
// Filter for DAO spaces only
const { data } = usePublicSpaces({ filter: { spaceType: 'DAO' } });

// Combine with existing filters
const { data } = usePublicSpaces({ filter: { editorId: 'xxx', spaceType: 'PERSONAL' } });
```
5 changes: 4 additions & 1 deletion .claude/settings.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
"Bash(pnpm typecheck:*)",
"Bash(pnpm check:*)",
"Bash(pnpm --filter events test:script:*)",
"Bash(pnpm test:*)"
"Bash(pnpm test:*)",
"Bash(pnpm vitest:*)",
"Bash(pnpm changeset:*)",
"Bash(npx tsc:*)"
],
"deny": [],
"ask": []
Expand Down
91 changes: 46 additions & 45 deletions packages/hypergraph/src/space/find-many-public.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { request } from 'graphql-request';

const spaceFields = `
id
type
page {
name
relationsList(filter: {
Expand All @@ -29,32 +30,13 @@ const spaceFields = `
}
`;

const spacesQueryDocument = `
query spaces {
spaces {
${spaceFields}
}
}
`;

const memberSpacesQueryDocument = `
query memberSpaces($accountId: UUID!) {
spaces(filter: {members: {some: {memberSpaceId: {is: $accountId}}}}) {
${spaceFields}
}
}
`;
export const SpaceTypeSchema = EffectSchema.Union(EffectSchema.Literal('PERSONAL'), EffectSchema.Literal('DAO'));

const editorSpacesQueryDocument = `
query editorSpaces($accountId: UUID!) {
spaces(filter: {editors: {some: {memberSpaceId: {is: $accountId}}}}) {
${spaceFields}
}
}
`;
export type SpaceType = typeof SpaceTypeSchema.Type;

export const PublicSpaceSchema = EffectSchema.Struct({
id: EffectSchema.String,
type: SpaceTypeSchema,
name: EffectSchema.String,
avatar: EffectSchema.optional(EffectSchema.String),
editorIds: EffectSchema.Array(EffectSchema.String),
Expand All @@ -66,6 +48,7 @@ export type PublicSpace = typeof PublicSpaceSchema.Type;
type SpacesQueryResult = {
spaces?: {
id: string;
type: 'PERSONAL' | 'DAO';
page: {
name?: string | null;
relationsList?: {
Expand All @@ -86,10 +69,6 @@ type SpacesQueryResult = {
}[];
};

type SpacesQueryVariables = {
accountId: string;
};

type SpaceQueryEntry = NonNullable<SpacesQueryResult['spaces']>[number];

const decodeSpace = EffectSchema.decodeUnknownEither(PublicSpaceSchema);
Expand Down Expand Up @@ -120,6 +99,7 @@ export const parseSpacesQueryResult = (queryResult: SpacesQueryResult) => {
for (const space of spaces) {
const rawSpace: Record<string, unknown> = {
id: space.id,
type: space.type,
name: space.page?.name ?? undefined,
avatar: getAvatarFromSpace(space),
editorIds: getEditorIdsFromSpace(space),
Expand All @@ -139,14 +119,49 @@ export const parseSpacesQueryResult = (queryResult: SpacesQueryResult) => {
};

export type FindManyPublicFilter =
| Readonly<{ memberId: string; editorId?: never }>
| Readonly<{ editorId: string; memberId?: never }>
| Readonly<{ memberId?: undefined; editorId?: undefined }>;
| Readonly<{ memberId: string; editorId?: never; spaceType?: SpaceType }>
| Readonly<{ editorId: string; memberId?: never; spaceType?: SpaceType }>
| Readonly<{ memberId?: undefined; editorId?: undefined; spaceType?: SpaceType }>;

export type FindManyPublicParams = Readonly<{
filter?: FindManyPublicFilter;
}>;

const buildFilterString = (filter?: FindManyPublicFilter): string | undefined => {
const conditions: string[] = [];

if (filter?.memberId) {
conditions.push(`members: {some: {memberSpaceId: {is: "${filter.memberId}"}}}`);
}

if (filter?.editorId) {
conditions.push(`editors: {some: {memberSpaceId: {is: "${filter.editorId}"}}}`);
Comment thread
nikgraf marked this conversation as resolved.
Outdated
}

if (filter?.spaceType) {
conditions.push(`type: {is: ${filter.spaceType}}`);
Comment thread
nikgraf marked this conversation as resolved.
Outdated
}

if (conditions.length === 0) {
return undefined;
}

return `filter: {${conditions.join(', ')}}`;
};

const buildSpacesQuery = (filter?: FindManyPublicFilter): string => {
const filterString = buildFilterString(filter);
const filterClause = filterString ? `(${filterString})` : '';

return `
query spaces {
spaces${filterClause} {
${spaceFields}
}
}
`;
};
Comment thread
nikgraf marked this conversation as resolved.
Outdated

export const findManyPublic = async (params?: FindManyPublicParams) => {
const filter = params?.filter;
const memberId = filter?.memberId;
Expand All @@ -157,21 +172,7 @@ export const findManyPublic = async (params?: FindManyPublicParams) => {
}

const endpoint = `${Config.getApiOrigin()}/v2/graphql`;

if (memberId) {
const queryResult = await request<SpacesQueryResult, SpacesQueryVariables>(endpoint, memberSpacesQueryDocument, {
accountId: memberId,
});
return parseSpacesQueryResult(queryResult);
}

if (editorId) {
const queryResult = await request<SpacesQueryResult, SpacesQueryVariables>(endpoint, editorSpacesQueryDocument, {
accountId: editorId,
});
return parseSpacesQueryResult(queryResult);
}

const queryResult = await request<SpacesQueryResult>(endpoint, spacesQueryDocument);
const queryDocument = buildSpacesQuery(filter);
const queryResult = await request<SpacesQueryResult>(endpoint, queryDocument);
return parseSpacesQueryResult(queryResult);
};
23 changes: 23 additions & 0 deletions packages/hypergraph/test/space/find-many-public.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@ import { parseSpacesQueryResult } from '../../src/space/find-many-public.js';

const buildQuerySpace = ({
id = 'space-id',
type = 'PERSONAL',
name = 'Space name',
avatar,
editorsList = [],
membersList = [],
}: {
id?: string;
type?: 'PERSONAL' | 'DAO';
name?: string | null;
avatar?: string | null;
editorsList?: { memberSpaceId: string }[];
membersList?: { memberSpaceId: string }[];
} = {}) => {
return {
id,
type,
page: {
name,
relationsList:
Expand Down Expand Up @@ -51,6 +54,7 @@ describe('parseSpacesQueryResult', () => {
expect(data).toEqual([
{
id: 'space-1',
type: 'PERSONAL',
name: 'Space 1',
avatar: 'https://example.com/avatar.png',
editorIds: [],
Expand All @@ -68,13 +72,30 @@ describe('parseSpacesQueryResult', () => {
expect(data).toEqual([
{
id: 'space-2',
type: 'PERSONAL',
name: 'Space 2',
editorIds: [],
memberIds: [],
},
]);
});

it('parses DAO type', () => {
const { data } = parseSpacesQueryResult({
spaces: [buildQuerySpace({ id: 'space-dao', type: 'DAO', name: 'DAO Space' })],
});

expect(data).toEqual([
{
id: 'space-dao',
type: 'DAO',
name: 'DAO Space',
editorIds: [],
memberIds: [],
},
]);
});

it('filters invalid data', () => {
const { data, invalidSpaces } = parseSpacesQueryResult({
spaces: [
Expand All @@ -86,6 +107,7 @@ describe('parseSpacesQueryResult', () => {
expect(data).toEqual([
{
id: 'space-valid',
type: 'PERSONAL',
name: 'Space valid',
avatar: 'https://example.com/a.png',
editorIds: [],
Expand All @@ -111,6 +133,7 @@ describe('parseSpacesQueryResult', () => {
expect(data).toEqual([
{
id: 'space-with-members',
type: 'PERSONAL',
name: 'Space with members',
editorIds: ['editor-1', 'editor-2'],
memberIds: ['member-1'],
Expand Down
Loading