Skip to content

Commit 27f5542

Browse files
create and enter workspace
1 parent ca66910 commit 27f5542

7 files changed

Lines changed: 163 additions & 11 deletions

File tree

apps/client/src/lib/dexie.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import {
66
type WorkspaceTable,
77
} from "./schema";
88

9+
class QueryApiError extends Data.TaggedError("QueryApiError")<{
10+
cause: unknown;
11+
}> {}
12+
913
class WriteApiError extends Data.TaggedError("WriteApiError")<{
1014
cause: unknown;
1115
}> {}
@@ -36,11 +40,25 @@ export class Dexie extends Effect.Service<Dexie>()("Dexie", {
3640
};
3741

3842
db.version(1).stores({
43+
client: "clientId",
3944
workspace: "workspaceId",
4045
temp_workspace: "workspaceId",
41-
client: "clientId",
4246
});
4347

48+
const query = <T>(execute: (_: typeof db) => Promise<T>) =>
49+
Effect.tryPromise({
50+
try: () => execute(db),
51+
catch: (error) => new QueryApiError({ cause: error }),
52+
}).pipe(Effect.tapErrorCause(Effect.logError));
53+
54+
const initClient = query((_) => _.client.toCollection().last()).pipe(
55+
Effect.map((client) => client?.clientId),
56+
Effect.flatMap(Effect.fromNullable),
57+
Effect.orElse(() =>
58+
query((_) => _.client.add({ clientId: crypto.randomUUID() }))
59+
)
60+
);
61+
4462
const formAction =
4563
<const R extends string, I, T>(
4664
source: Schema.Schema<I, Record<R, string>>,
@@ -74,6 +92,6 @@ export class Dexie extends Effect.Service<Dexie>()("Dexie", {
7492
)
7593
);
7694

77-
return { db };
95+
return { db, initClient, query };
7896
}),
7997
}) {}
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import { Layer, ManagedRuntime } from "effect";
22
import { ApiClient } from "./api-client";
33
import { Dexie } from "./dexie";
4+
import { WorkspaceManager } from "./services/workspace-manager";
45

5-
const MainLayer = Layer.mergeAll(Dexie.Default, ApiClient.Default);
6+
const MainLayer = Layer.mergeAll(
7+
Dexie.Default,
8+
ApiClient.Default,
9+
WorkspaceManager.Default
10+
);
611

712
export const RuntimeClient = ManagedRuntime.make(MainLayer);

apps/client/src/lib/schema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export class WorkspaceTable extends Schema.Class<WorkspaceTable>(
1010
)({
1111
workspaceId: WorkspaceId,
1212
snapshot: Snapshot,
13-
token: Schema.String,
13+
token: Schema.NullOr(Schema.String),
1414

1515
version: Schema.Uint8Array,
1616
}) {}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { Effect } from "effect";
2+
import { Dexie } from "../dexie";
3+
4+
export class WorkspaceManager extends Effect.Service<WorkspaceManager>()(
5+
"WorkspaceManager",
6+
{
7+
accessors: true,
8+
dependencies: [Dexie.Default],
9+
effect: Effect.gen(function* () {
10+
const { query } = yield* Dexie;
11+
12+
return {
13+
getAll: query((_) => _.workspace.toArray()),
14+
15+
getById: ({ workspaceId }: { workspaceId: string }) =>
16+
query((_) =>
17+
_.workspace
18+
.where("workspaceId")
19+
.equals(workspaceId)
20+
.limit(1)
21+
.first()
22+
).pipe(Effect.flatMap(Effect.fromNullable)),
23+
24+
putCurrent: (workspaceId: string | undefined) =>
25+
query((_) =>
26+
_.workspace.toCollection().modify({ current: false })
27+
).pipe(
28+
Effect.andThen(
29+
query((_) =>
30+
_.workspace.put({
31+
workspaceId: workspaceId ?? crypto.randomUUID(),
32+
version: [],
33+
snapshot: [],
34+
token: null,
35+
})
36+
)
37+
),
38+
Effect.map((workspaceId) => ({ workspaceId }))
39+
),
40+
};
41+
}),
42+
}
43+
) {}

apps/client/src/routeTree.gen.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,17 @@
1111
// Import Routes
1212

1313
import { Route as rootRoute } from './routes/__root'
14+
import { Route as WorkspaceIdImport } from './routes/$workspaceId'
1415
import { Route as IndexImport } from './routes/index'
1516

1617
// Create/Update Routes
1718

19+
const WorkspaceIdRoute = WorkspaceIdImport.update({
20+
id: '/$workspaceId',
21+
path: '/$workspaceId',
22+
getParentRoute: () => rootRoute,
23+
} as any)
24+
1825
const IndexRoute = IndexImport.update({
1926
id: '/',
2027
path: '/',
@@ -32,39 +39,51 @@ declare module '@tanstack/react-router' {
3239
preLoaderRoute: typeof IndexImport
3340
parentRoute: typeof rootRoute
3441
}
42+
'/$workspaceId': {
43+
id: '/$workspaceId'
44+
path: '/$workspaceId'
45+
fullPath: '/$workspaceId'
46+
preLoaderRoute: typeof WorkspaceIdImport
47+
parentRoute: typeof rootRoute
48+
}
3549
}
3650
}
3751

3852
// Create and export the route tree
3953

4054
export interface FileRoutesByFullPath {
4155
'/': typeof IndexRoute
56+
'/$workspaceId': typeof WorkspaceIdRoute
4257
}
4358

4459
export interface FileRoutesByTo {
4560
'/': typeof IndexRoute
61+
'/$workspaceId': typeof WorkspaceIdRoute
4662
}
4763

4864
export interface FileRoutesById {
4965
__root__: typeof rootRoute
5066
'/': typeof IndexRoute
67+
'/$workspaceId': typeof WorkspaceIdRoute
5168
}
5269

5370
export interface FileRouteTypes {
5471
fileRoutesByFullPath: FileRoutesByFullPath
55-
fullPaths: '/'
72+
fullPaths: '/' | '/$workspaceId'
5673
fileRoutesByTo: FileRoutesByTo
57-
to: '/'
58-
id: '__root__' | '/'
74+
to: '/' | '/$workspaceId'
75+
id: '__root__' | '/' | '/$workspaceId'
5976
fileRoutesById: FileRoutesById
6077
}
6178

6279
export interface RootRouteChildren {
6380
IndexRoute: typeof IndexRoute
81+
WorkspaceIdRoute: typeof WorkspaceIdRoute
6482
}
6583

6684
const rootRouteChildren: RootRouteChildren = {
6785
IndexRoute: IndexRoute,
86+
WorkspaceIdRoute: WorkspaceIdRoute,
6887
}
6988

7089
export const routeTree = rootRoute
@@ -77,11 +96,15 @@ export const routeTree = rootRoute
7796
"__root__": {
7897
"filePath": "__root.tsx",
7998
"children": [
80-
"/"
99+
"/",
100+
"/$workspaceId"
81101
]
82102
},
83103
"/": {
84104
"filePath": "index.tsx"
105+
},
106+
"/$workspaceId": {
107+
"filePath": "$workspaceId.tsx"
85108
}
86109
}
87110
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { createFileRoute } from "@tanstack/react-router";
2+
import { RuntimeClient } from "../lib/runtime-client";
3+
import { WorkspaceManager } from "../lib/services/workspace-manager";
4+
5+
export const Route = createFileRoute("/$workspaceId")({
6+
component: RouteComponent,
7+
loader: ({ params: { workspaceId } }) =>
8+
RuntimeClient.runPromise(WorkspaceManager.getById({ workspaceId })),
9+
});
10+
11+
function RouteComponent() {
12+
const workspace = Route.useLoaderData();
13+
return <div>Hello {workspace.workspaceId}</div>;
14+
}

apps/client/src/routes/index.tsx

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,56 @@
1-
import { createFileRoute } from "@tanstack/react-router";
1+
import { createFileRoute, Link, useNavigate } from "@tanstack/react-router";
2+
import { Effect } from "effect";
3+
import { RuntimeClient } from "../lib/runtime-client";
4+
import { WorkspaceManager } from "../lib/services/workspace-manager";
5+
import { useActionEffect } from "../lib/use-action-effect";
26

3-
export const Route = createFileRoute("/")({ component: HomeComponent });
7+
export const Route = createFileRoute("/")({
8+
component: HomeComponent,
9+
loader: () => RuntimeClient.runPromise(WorkspaceManager.getAll),
10+
});
411

512
function HomeComponent() {
6-
return <></>;
13+
const allWorkspaces = Route.useLoaderData();
14+
const navigate = useNavigate();
15+
16+
const [, putWorkspace] = useActionEffect((formData: FormData | undefined) =>
17+
Effect.gen(function* () {
18+
const workspaceId = formData?.get("workspaceId") as string | null;
19+
const workspace = yield* WorkspaceManager.putCurrent(
20+
workspaceId ?? undefined
21+
);
22+
yield* Effect.sync(() =>
23+
navigate({
24+
to: `/$workspaceId`,
25+
params: { workspaceId: workspace.workspaceId },
26+
})
27+
);
28+
})
29+
);
30+
31+
return (
32+
<div>
33+
<p>Select workspace</p>
34+
{allWorkspaces.map((workspace) => (
35+
<Link
36+
key={workspace.workspaceId}
37+
to="/$workspaceId"
38+
params={{ workspaceId: workspace.workspaceId }}
39+
>
40+
{workspace.workspaceId}
41+
</Link>
42+
))}
43+
44+
<form action={putWorkspace}>
45+
<input type="text" name="workspaceId" placeholder="Workspace id" />
46+
<button type="submit">Switch workspace</button>
47+
</form>
48+
49+
<div>
50+
<button type="button" onClick={() => putWorkspace(undefined)}>
51+
Create workspace
52+
</button>
53+
</div>
54+
</div>
55+
);
756
}

0 commit comments

Comments
 (0)