diff --git a/apps/events/src/components/debug-space-events.tsx b/apps/events/src/components/debug-space-events.tsx new file mode 100644 index 00000000..6be09304 --- /dev/null +++ b/apps/events/src/components/debug-space-events.tsx @@ -0,0 +1,15 @@ +import type { SpaceEvent } from 'graph-framework'; + +export function DebugSpaceEvents({ events }: { events: SpaceEvent[] }) { + return ( + + ); +} diff --git a/apps/events/src/components/debug-space-state.tsx b/apps/events/src/components/debug-space-state.tsx new file mode 100644 index 00000000..9a62c213 --- /dev/null +++ b/apps/events/src/components/debug-space-state.tsx @@ -0,0 +1,9 @@ +import type { SpaceState } from 'graph-framework'; + +export function DebugSpaceState(props: { state: SpaceState | undefined }) { + return ( +
+
{JSON.stringify(props, null, 2)}
+
+ ); +} diff --git a/apps/events/src/routes/playground.tsx b/apps/events/src/routes/playground.tsx index f82e394f..eb2b79a0 100644 --- a/apps/events/src/routes/playground.tsx +++ b/apps/events/src/routes/playground.tsx @@ -1,12 +1,42 @@ +import { DebugSpaceEvents } from '@/components/debug-space-events'; +import { DebugSpaceState } from '@/components/debug-space-state'; import { Button } from '@/components/ui/button'; import { assertExhaustive } from '@/lib/assertExhaustive'; import { createFileRoute } from '@tanstack/react-router'; -import { Effect } from 'effect'; +import { Effect, Exit } from 'effect'; import * as Schema from 'effect/Schema'; -import type { EventMessage, RequestListSpaces, RequestSubscribeToSpace } from 'graph-framework'; -import { ResponseMessage, createSpace } from 'graph-framework'; +import type { + EventMessage, + RequestListInvitations, + RequestListSpaces, + RequestSubscribeToSpace, + SpaceEvent, + SpaceState, +} from 'graph-framework'; +import { ResponseMessage, applyEvent, createInvitation, createSpace } from 'graph-framework'; import { useEffect, useState } from 'react'; +const availableAccounts = [ + { + accountId: '0262701b2eb1b6b37ad03e24445dfcad1b91309199e43017b657ce2604417c12f5', + signaturePrivateKey: '88bb6f20de8dc1787c722dc847f4cf3d00285b8955445f23c483d1237fe85366', + }, + { + accountId: '03bf5d2a1badf15387b08a007d1a9a13a9bfd6e1c56f681e251514d9ba10b57462', + signaturePrivateKey: '1eee32d3bc202dcb5d17c3b1454fb541d2290cb941860735408f1bfe39e7bc15', + }, + { + accountId: '0351460706cf386282d9b6ebee2ccdcb9ba61194fd024345e53037f3036242e6a2', + signaturePrivateKey: '434518a2c9a665a7c20da086232c818b6c1592e2edfeecab29a40cf5925ca8fe', + }, +]; + +type SpaceStorageEntry = { + id: string; + events: SpaceEvent[]; + state: SpaceState | undefined; +}; + const decodeResponseMessage = Schema.decodeUnknownEither(ResponseMessage); export const Route = createFileRoute('/playground')({ @@ -15,14 +45,14 @@ export const Route = createFileRoute('/playground')({ const App = ({ accountId, signaturePrivateKey }: { accountId: string; signaturePrivateKey: string }) => { const [websocketConnection, setWebsocketConnection] = useState(); - const [spaces, setSpaces] = useState<{ id: string }[]>([]); + const [spaces, setSpaces] = useState([]); useEffect(() => { // temporary until we have a way to create accounts and authenticate them const websocketConnection = new WebSocket(`ws://localhost:3030/?accountId=${accountId}`); setWebsocketConnection(websocketConnection); - const onMessage = (event: MessageEvent) => { + const onMessage = async (event: MessageEvent) => { console.log('message received', event.data); const data = JSON.parse(event.data); const message = decodeResponseMessage(data); @@ -30,17 +60,58 @@ const App = ({ accountId, signaturePrivateKey }: { accountId: string; signatureP const response = message.right; switch (response.type) { case 'list-spaces': { - setSpaces(response.spaces.map((space) => ({ id: space.id }))); + setSpaces((existingSpaces) => { + return response.spaces.map((space) => { + const existingSpace = existingSpaces.find((s) => s.id === space.id); + return { id: space.id, events: existingSpace?.events ?? [], state: existingSpace?.state }; + }); + }); + // fetch all spaces (for debugging purposes) + for (const space of response.spaces) { + const message: RequestSubscribeToSpace = { type: 'subscribe-space', id: space.id }; + websocketConnection?.send(JSON.stringify(message)); + } break; } case 'space': { - console.log('space', response); + let state: SpaceState | undefined = undefined; + + // TODO fix typing + for (const event of response.events) { + if (state === undefined) { + const applyEventResult = await Effect.runPromiseExit(applyEvent({ event })); + if (Exit.isSuccess(applyEventResult)) { + state = applyEventResult.value; + } + } else { + const applyEventResult = await Effect.runPromiseExit(applyEvent({ event, state })); + if (Exit.isSuccess(applyEventResult)) { + state = applyEventResult.value; + } + } + } + + const newState = state as SpaceState; + + setSpaces((spaces) => + spaces.map((space) => { + if (space.id === response.id) { + // TODO fix readonly type issue + return { ...space, events: response.events as SpaceEvent[], state: newState }; + } + return space; + }), + ); break; } case 'event': { console.log('event', response); break; } + case 'list-invitations': { + console.log('list-invitations', response); + break; + } default: assertExhaustive(response); } @@ -86,7 +157,7 @@ const App = ({ accountId, signaturePrivateKey }: { accountId: string; signatureP }, }), ); - const message: EventMessage = { type: 'event', event: spaceEvent }; + const message: EventMessage = { type: 'event', event: spaceEvent, spaceId: spaceEvent.transaction.id }; websocketConnection?.send(JSON.stringify(message)); }} > @@ -101,13 +172,22 @@ const App = ({ accountId, signaturePrivateKey }: { accountId: string; signatureP > List Spaces + +

Spaces