History

feat: add v2 web control-plane app and turbo dev orchestration

Build a basic Next.js control-plane frontend using Effect Atom HttpApi atoms, simplify the control-plane atom client to generic query/mutation primitives, and wire root v2 dev with Turbo while fixing Convex runtime imports for dev bundling.

Rhys Sullivan committed 8 months ago

Commit ef56bd51 parent fc53fa822 files · +849 −155Browse files

Unified · Split

22 files
  1. v2/.gitignore +3 −0
  2. v2/apps/convex/convex/run_executor.ts +7 −6
  3. v2/apps/convex/convex/runtime_adapter.ts +14 −4
  4. v2/apps/web/README.md +13 −1
  5. v2/apps/web/agents.md +114 −0
  6. v2/apps/web/app/globals.css +189 −0
  7. v2/apps/web/app/layout.tsx +37 −0
  8. v2/apps/web/app/page.tsx +210 −0
  9. v2/apps/web/app/providers.tsx +12 −0
  10. v2/apps/web/lib/control-plane/atoms.ts +73 −0
  11. v2/apps/web/lib/control-plane/client.ts +5 −0
  12. v2/apps/web/next-env.d.ts +6 −0
  13. v2/apps/web/next.config.ts +22 −0
  14. v2/apps/web/package.json +17 −0
  15. v2/apps/web/src/main.ts +0 −1
  16. v2/apps/web/tsconfig.json +31 −1
  17. v2/bun.lock +65 −10
  18. v2/package.json +2 −0
  19. v2/packages/control-plane/src/client.ts +8 −128
  20. v2/packages/control-plane/src/index.ts +1 −4
  21. v2/tsconfig.json +1 −0
  22. v2/turbo.json +19 −0
v2/.gitignore+3−0
@@ -1,3 +1,6 @@
1.reference/1.reference/
2.vercel2.vercel
3.env*.local3.env*.local
4.next/
5.turbo/
6.executor-v2/
v2/apps/convex/convex/run_executor.ts+7−6
@@ -1,14 +1,17 @@
1import {1import {
2 makeLocalInProcessRuntimeAdapter,
3 makeToolProviderRegistry,
4 ToolProviderRegistryService,2 ToolProviderRegistryService,
5 type RuntimeExecuteError,3 makeToolProviderRegistry,
6} from "@executor-v2/engine";4} from "@executor-v2/engine/tool-providers";
7import type { ExecuteRunInput, ExecuteRunResult } from "@executor-v2/sdk";5import type { ExecuteRunInput, ExecuteRunResult } from "@executor-v2/sdk";
8import * as Context from "effect/Context";6import * as Context from "effect/Context";
9import * as Effect from "effect/Effect";7import * as Effect from "effect/Effect";
10import * as Layer from "effect/Layer";8import * as Layer from "effect/Layer";
11 9
10import {
11 makeLocalInProcessRuntimeAdapter,
12 type RuntimeExecuteError,
13} from "./runtime_adapter";
14
12export type ConvexRunExecutorService = {15export type ConvexRunExecutorService = {
13 executeRun: (16 executeRun: (
14 input: ExecuteRunInput,17 input: ExecuteRunInput,
@@ -23,9 +26,7 @@ const runtimeAdapter = makeLocalInProcessRuntimeAdapter(); · 8 unmodified lines
23 26
24const formatRuntimeExecuteError = (error: RuntimeExecuteError): string => {27const formatRuntimeExecuteError = (error: RuntimeExecuteError): string => {
25 switch (error._tag) {28 switch (error._tag) {
26 case "RuntimeAdapterError":
27 case "LocalCodeRunnerError":29 case "LocalCodeRunnerError":
28 case "DenoSubprocessRunnerError":
29 case "ToolProviderError":30 case "ToolProviderError":
30 return error.details ? `${error.message}: ${error.details}` : error.message;31 return error.details ? `${error.message}: ${error.details}` : error.message;
31 case "ToolProviderRegistryError":32 case "ToolProviderRegistryError":
v2/apps/convex/convex/runtime_adapter.ts+14−4
@@ -1,7 +1,12 @@
1import { executeJavaScriptWithTools } from "@executor-v2/engine/local-runner";1import {
2import type {2 LocalCodeRunnerError,
3 CanonicalToolDescriptor,3 executeJavaScriptWithTools,
4} from "@executor-v2/engine/local-runner";
5import {
6 ToolProviderError,
7 ToolProviderRegistryError,
4 ToolProviderRegistryService,8 ToolProviderRegistryService,
9 type CanonicalToolDescriptor,
5} from "@executor-v2/engine/tool-providers";10} from "@executor-v2/engine/tool-providers";
6import type { Source } from "@executor-v2/schema";11import type { Source } from "@executor-v2/schema";
7import * as Effect from "effect/Effect";12import * as Effect from "effect/Effect";
@@ -17,12 +22,17 @@ export type RuntimeExecuteInput = { · 9 unmodified lines
17 timeoutMs?: number;22 timeoutMs?: number;
18};23};
19 24
25export type RuntimeExecuteError =
26 | LocalCodeRunnerError
27 | ToolProviderRegistryError
28 | ToolProviderError;
29
20export type RuntimeAdapter = {30export type RuntimeAdapter = {
21 kind: "local-inproc";31 kind: "local-inproc";
22 isAvailable: () => Effect.Effect<boolean>;32 isAvailable: () => Effect.Effect<boolean>;
23 execute: (33 execute: (
24 input: RuntimeExecuteInput,34 input: RuntimeExecuteInput,
25 ) => Effect.Effect<unknown, unknown, ToolProviderRegistryService>;35 ) => Effect.Effect<unknown, RuntimeExecuteError, ToolProviderRegistryService>;
26};36};
27 37
28export const makeLocalInProcessRuntimeAdapter = (): RuntimeAdapter => ({38export const makeLocalInProcessRuntimeAdapter = (): RuntimeAdapter => ({
v2/apps/web/README.md+13−1
@@ -1,3 +1,15 @@
1# app-web1# app-web
2 2
3Web UI app scaffold for Executor v2.3Basic Next.js frontend for Executor v2 control plane.
4
5- Uses `@executor-v2/control-plane` Effect HttpApi client
6- Uses Effect Atom (`@effect-atom/atom`, `@effect-atom/atom-react`) for query state
7- Proxies backend calls through `/api/control-plane/*` via `next.config.ts` rewrites
8
9Run:
10
11- `bun run --cwd apps/web dev`
12- Open `http://127.0.0.1:3000`
13
14By default, proxy target is `http://127.0.0.1:8787`.
15Override with `CONTROL_PLANE_UPSTREAM_URL`.
v2/apps/web/agents.md+114−0
@@ -0,0 +1,114 @@
1# Effect Atom guide for `apps/web`
2
3This file defines how we write Effect Atom code in this app.
4
5## Goal
6
7Keep React components dumb. Put async/result orchestration in atoms, and render from typed state objects.
8
9## Hard rules
10
11- Do not create query atoms in render with `useMemo`.
12- Do not hand-roll `isLoading` / `hasError` booleans from `_tag` checks in components.
13- Do not manually mirror server state with React local state.
14- Prefer `Atom.family` + derived state atoms over component-level branching.
15
16## Baseline stack
17
18- `@effect-atom/atom`
19- `@effect-atom/atom-react`
20- `@executor-v2/control-plane` client (Effect HttpApi)
21
22## Recommended structure
23
24- `lib/control-plane/client.ts`
25 - Export one shared `createControlPlaneAtomClient(...)` instance.
26- `lib/control-plane/atoms.ts`
27 - Export query/mutation atoms and derived state atoms.
28- `app/.../page.tsx`
29 - Use `useAtomValue`, `useAtomSet`, and `useAtomRefresh`.
30 - Render from derived state atom output.
31
32## Preferred patterns
33
34### 1) Parametrized query atom with `Atom.family`
35
36Create once at module scope:
37
38```ts
39const sourcesResultByWorkspace = Atom.family((workspaceId: WorkspaceId) =>
40 controlPlaneClient.query("sources", "list", {
41 path: { workspaceId },
42 }),
43);
44```
45
46Then in component:
47
48```ts
49const result = useAtomValue(sourcesResultByWorkspace(workspaceId));
50```
51
52No `useMemo` needed.
53
54### 2) Derived state atom for UI projection
55
56Map `Result` once in atom-land:
57
58```ts
59const sourcesByWorkspace = Atom.family((workspaceId: WorkspaceId) =>
60 Atom.make((get) => {
61 const result = get(sourcesResultByWorkspace(workspaceId));
62 return Result.match(result, {
63 onInitial: () => ({ state: "loading" as const, items: [] as const, message: null }),
64 onFailure: (failure) => ({
65 state: "error" as const,
66 items: Option.getOrElse(Result.value(result), () => []),
67 message: Cause.pretty(failure.cause),
68 }),
69 onSuccess: (success) => ({
70 state: success.waiting ? ("refreshing" as const) : ("ready" as const),
71 items: success.value,
72 message: null,
73 }),
74 });
75 }),
76);
77```
78
79Components switch on `sources.state`, not raw result internals.
80
81### 3) Mutations as atoms
82
83Define mutation atoms once:
84
85```ts
86const upsertSource = controlPlaneClient.mutation("sources", "upsert");
87const removeSource = controlPlaneClient.mutation("sources", "remove");
88```
89
90In components, execute with `useAtomSet(..., { mode: "promise" })`, then refresh the related query atom.
91
92## React hooks usage
93
94- `useAtomValue(atom)` for reads.
95- `useAtomSet(writableAtom, { mode: "promise" })` for mutation execution.
96- `useAtomRefresh(atom)` for explicit refresh.
97- `useAtomSuspense(...)` is preferred when route is Suspense-driven.
98
99## Error formatting
100
101- Prefer `Cause.pretty(...)` for UI/debug text when surfacing failures.
102- Keep raw `Cause` in atom-derived state when useful; do not stringify ad-hoc in JSX.
103
104## Internal references
105
106- `@effect-atom/atom` d.ts: `dist/dts/Atom.d.ts`, `dist/dts/Result.d.ts`, `dist/dts/AtomHttpApi.d.ts`
107- `@effect-atom/atom-react` d.ts: `dist/dts/Hooks.d.ts`, `dist/dts/RegistryContext.d.ts`
108
109## Anti-pattern checklist (reject in review)
110
111- `useMemo(() => client.query(...), [deps])`
112- `const isLoading = result._tag === "Initial" || ...`
113- component-level `if/else` trees over raw `Result` in many places
114- duplicating remote state into `useState` unless it is true form draft state
v2/apps/web/app/globals.css+189−0
@@ -0,0 +1,189 @@
1:root {
2 --bg: #f2f0ea;
3 --panel: #fffdf8;
4 --ink: #1b1a16;
5 --muted: #6b675d;
6 --accent: #0a7f64;
7 --accent-ink: #f4fffc;
8 --border: rgba(27, 26, 22, 0.14);
9 --danger: #a22a2a;
10}
11
12* {
13 box-sizing: border-box;
14}
15
16html,
17body {
18 margin: 0;
19 padding: 0;
20 min-height: 100%;
21}
22
23body {
24 font-family: var(--font-body), "IBM Plex Sans", sans-serif;
25 background:
26 radial-gradient(circle at 15% 20%, rgba(10, 127, 100, 0.22), transparent 45%),
27 radial-gradient(circle at 80% 10%, rgba(203, 166, 70, 0.17), transparent 36%),
28 linear-gradient(170deg, #ece9de 0%, #f6f4ee 52%, #e8e3d7 100%);
29 color: var(--ink);
30}
31
32a {
33 color: inherit;
34}
35
36main {
37 width: min(980px, calc(100% - 2.5rem));
38 margin: 2.5rem auto 3.5rem;
39}
40
41.shell {
42 border: 1px solid var(--border);
43 border-radius: 18px;
44 background: var(--panel);
45 box-shadow: 0 20px 60px rgba(18, 16, 10, 0.08);
46 overflow: hidden;
47}
48
49.hero {
50 padding: 2rem 2rem 1rem;
51 border-bottom: 1px dashed var(--border);
52}
53
54.hero h1 {
55 margin: 0;
56 font-family: var(--font-display), "Fraunces", serif;
57 font-size: clamp(1.75rem, 3.1vw, 2.4rem);
58 line-height: 1.1;
59}
60
61.hero p {
62 margin: 0.75rem 0 0;
63 color: var(--muted);
64 max-width: 60ch;
65}
66
67.grid {
68 display: grid;
69 grid-template-columns: 1fr 1fr;
70}
71
72.card {
73 padding: 1.5rem 2rem 2rem;
74}
75
76.card + .card {
77 border-left: 1px solid var(--border);
78}
79
80h2 {
81 margin: 0 0 1rem;
82 font-family: var(--font-display), "Fraunces", serif;
83 font-size: 1.3rem;
84}
85
86.field {
87 margin-bottom: 0.95rem;
88}
89
90label {
91 display: block;
92 margin-bottom: 0.4rem;
93 color: var(--muted);
94 font-size: 0.9rem;
95}
96
97input,
98select,
99button {
100 width: 100%;
101 border-radius: 10px;
102 border: 1px solid var(--border);
103 font: inherit;
104}
105
106input,
107select {
108 padding: 0.62rem 0.7rem;
109 background: #fff;
110}
111
112button {
113 padding: 0.7rem 0.8rem;
114 font-weight: 600;
115 cursor: pointer;
116}
117
118button.primary {
119 background: var(--accent);
120 color: var(--accent-ink);
121 border-color: transparent;
122}
123
124button.secondary {
125 background: transparent;
126}
127
128button.danger {
129 background: transparent;
130 color: var(--danger);
131 border-color: rgba(162, 42, 42, 0.35);
132 width: auto;
133 padding: 0.45rem 0.7rem;
134}
135
136.status {
137 margin-top: 0.7rem;
138 padding: 0.65rem 0.75rem;
139 border: 1px solid var(--border);
140 border-radius: 10px;
141 color: var(--muted);
142 font-size: 0.9rem;
143}
144
145.error {
146 color: var(--danger);
147}
148
149.list {
150 display: grid;
151 gap: 0.75rem;
152}
153
154.list-item {
155 border: 1px solid var(--border);
156 border-radius: 12px;
157 padding: 0.8rem;
158 background: rgba(255, 255, 255, 0.65);
159}
160
161.list-item header {
162 display: flex;
163 align-items: flex-start;
164 justify-content: space-between;
165 gap: 0.75rem;
166}
167
168.list-item strong {
169 display: block;
170 font-size: 1rem;
171}
172
173.meta {
174 margin-top: 0.2rem;
175 color: var(--muted);
176 font-size: 0.82rem;
177 word-break: break-all;
178}
179
180@media (max-width: 860px) {
181 .grid {
182 grid-template-columns: 1fr;
183 }
184
185 .card + .card {
186 border-left: 0;
187 border-top: 1px solid var(--border);
188 }
189}
v2/apps/web/app/layout.tsx+37−0
@@ -0,0 +1,37 @@
1import type { Metadata } from "next";
2import { Fraunces, IBM_Plex_Sans } from "next/font/google";
3import type { ReactNode } from "react";
4
5import { Providers } from "./providers";
6import "./globals.css";
7
8const bodyFont = IBM_Plex_Sans({
9 subsets: ["latin"],
10 variable: "--font-body",
11 weight: ["400", "500", "600"],
12});
13
14const displayFont = Fraunces({
15 subsets: ["latin"],
16 variable: "--font-display",
17 weight: ["500", "700"],
18});
19
20export const metadata: Metadata = {
21 title: "Executor v2 Control Plane",
22 description: "Basic Next.js frontend for Executor v2 control plane",
23};
24
25type RootLayoutProps = {
26 children: ReactNode;
27};
28
29const RootLayout = ({ children }: RootLayoutProps) => (
30 <html lang="en" className={`${bodyFont.variable} ${displayFont.variable}`}>
31 <body>
32 <Providers>{children}</Providers>
33 </body>
34 </html>
35);
36
37export default RootLayout;
v2/apps/web/app/page.tsx+210−0
@@ -0,0 +1,210 @@
1"use client";
2
3import { useAtomRefresh, useAtomSet, useAtomValue } from "@effect-atom/atom-react";
4import type { UpsertSourcePayload } from "@executor-v2/control-plane";
5import type { SourceId, SourceKind, WorkspaceId } from "@executor-v2/schema";
6import { useEffect, useState, useTransition, type FormEvent } from "react";
7
8import {
9 removeSource,
10 sourcesByWorkspace,
11 sourcesLoading,
12 sourcesResultByWorkspace,
13 sourcesResultDisabled,
14 upsertSource,
15} from "../lib/control-plane/atoms";
16
17const kindOptions: ReadonlyArray<SourceKind> = [
18 "openapi",
19 "mcp",
20 "graphql",
21 "internal",
22];
23
24const Page = () => {
25 const [workspaceIdInput, setWorkspaceIdInput] = useState("ws_demo");
26 const [name, setName] = useState("Weather API");
27 const [kind, setKind] = useState<SourceKind>("openapi");
28 const [endpoint, setEndpoint] = useState("https://example.com/openapi.json");
29 const [statusText, setStatusText] = useState<string | null>(null);
30 const [isPending, startTransition] = useTransition();
31 const [isClient, setIsClient] = useState(false);
32
33 useEffect(() => {
34 setIsClient(true);
35 }, []);
36
37 const workspaceId = workspaceIdInput as WorkspaceId;
38
39 const sources = useAtomValue(
40 isClient ? sourcesByWorkspace(workspaceId) : sourcesLoading,
41 );
42 const refreshSources = useAtomRefresh(
43 isClient ? sourcesResultByWorkspace(workspaceId) : sourcesResultDisabled,
44 );
45
46 const runUpsertSource = useAtomSet(upsertSource, { mode: "promise" });
47 const runRemoveSource = useAtomSet(removeSource, { mode: "promise" });
48
49 const handleAddSource = (event: FormEvent<HTMLFormElement>) => {
50 event.preventDefault();
51
52 const payload: UpsertSourcePayload = {
53 name,
54 kind,
55 endpoint,
56 enabled: true,
57 configJson: "{}",
58 status: "draft",
59 sourceHash: null,
60 lastError: null,
61 };
62
63 startTransition(() => {
64 void runUpsertSource({
65 path: { workspaceId },
66 payload,
67 })
68 .then(() => {
69 setStatusText("Source saved.");
70 refreshSources();
71 })
72 .catch(() => {
73 setStatusText("Source save failed.");
74 });
75 });
76 };
77
78 const handleRemoveSource = (sourceId: SourceId) => {
79 startTransition(() => {
80 void runRemoveSource({
81 path: { workspaceId, sourceId },
82 })
83 .then(() => {
84 setStatusText("Source removed.");
85 refreshSources();
86 })
87 .catch(() => {
88 setStatusText("Source removal failed.");
89 });
90 });
91 };
92
93 return (
94 <main>
95 <section className="shell">
96 <header className="hero">
97 <h1>Executor v2 Control Plane</h1>
98 <p>
99 Basic Next.js frontend wired to the shared Effect HttpApi client via
100 Effect Atom.
101 </p>
102 </header>
103
104 <div className="grid">
105 <section className="card">
106 <h2>Workspace + Source</h2>
107 <form onSubmit={handleAddSource}>
108 <div className="field">
109 <label htmlFor="workspace-id">Workspace ID</label>
110 <input
111 id="workspace-id"
112 value={workspaceIdInput}
113 onChange={(event) => setWorkspaceIdInput(event.target.value)}
114 required
115 />
116 </div>
117
118 <div className="field">
119 <label htmlFor="source-name">Name</label>
120 <input
121 id="source-name"
122 value={name}
123 onChange={(event) => setName(event.target.value)}
124 required
125 />
126 </div>
127
128 <div className="field">
129 <label htmlFor="source-kind">Kind</label>
130 <select
131 id="source-kind"
132 value={kind}
133 onChange={(event) => setKind(event.target.value as SourceKind)}
134 >
135 {kindOptions.map((option) => (
136 <option key={option} value={option}>
137 {option}
138 </option>
139 ))}
140 </select>
141 </div>
142
143 <div className="field">
144 <label htmlFor="source-endpoint">Endpoint</label>
145 <input
146 id="source-endpoint"
147 value={endpoint}
148 onChange={(event) => setEndpoint(event.target.value)}
149 required
150 />
151 </div>
152
153 <button className="primary" type="submit" disabled={isPending}>
154 {isPending ? "Saving..." : "Save Source"}
155 </button>
156 </form>
157
158 {statusText ? <p className="status">{statusText}</p> : null}
159 </section>
160
161 <section className="card">
162 <h2>Sources</h2>
163
164 {sources.state === "loading" ? (
165 <p className="status">Loading sources...</p>
166 ) : null}
167
168 {sources.state === "refreshing" ? (
169 <p className="status">Refreshing sources...</p>
170 ) : null}
171
172 {sources.state === "error" ? (
173 <p className="status error">{sources.message}</p>
174 ) : null}
175
176 {sources.state !== "loading" && sources.items.length === 0 ? (
177 <p className="status">No sources yet in this workspace.</p>
178 ) : null}
179
180 <div className="list">
181 {sources.items.map((source) => (
182 <article className="list-item" key={source.id}>
183 <header>
184 <div>
185 <strong>{source.name}</strong>
186 <div className="meta">
187 {source.kind} · {source.status}
188 </div>
189 <div className="meta">{source.endpoint}</div>
190 </div>
191 <button
192 className="danger"
193 type="button"
194 onClick={() => handleRemoveSource(source.id)}
195 disabled={isPending}
196 >
197 Remove
198 </button>
199 </header>
200 </article>
201 ))}
202 </div>
203 </section>
204 </div>
205 </section>
206 </main>
207 );
208};
209
210export default Page;
v2/apps/web/app/providers.tsx+12−0
@@ -0,0 +1,12 @@
1"use client";
2
3import { RegistryProvider } from "@effect-atom/atom-react";
4import type { ReactNode } from "react";
5
6type ProvidersProps = {
7 children: ReactNode;
8};
9
10export const Providers = ({ children }: ProvidersProps) => (
11 <RegistryProvider>{children}</RegistryProvider>
12);
v2/apps/web/lib/control-plane/atoms.ts+73−0
@@ -0,0 +1,73 @@
1import { Atom, Result } from "@effect-atom/atom";
2import type { Source, WorkspaceId } from "@executor-v2/schema";
3import * as Cause from "effect/Cause";
4import * as Option from "effect/Option";
5
6import { controlPlaneClient } from "./client";
7
8type SourcesResult = Result.Result<ReadonlyArray<Source>, unknown>;
9
10const emptySources: ReadonlyArray<Source> = [];
11
12export const sourcesResultDisabled = Atom.make(
13 Result.initial<ReadonlyArray<Source>, unknown>(),
14);
15
16export const sourcesResultByWorkspace = Atom.family(
17 (workspaceId: WorkspaceId): Atom.Atom<SourcesResult> =>
18 controlPlaneClient.query("sources", "list", {
19 path: {
20 workspaceId,
21 },
22 }) as Atom.Atom<SourcesResult>,
23);
24
25export const upsertSource = controlPlaneClient.mutation("sources", "upsert");
26export const removeSource = controlPlaneClient.mutation("sources", "remove");
27
28export type SourcesState =
29 | {
30 state: "loading";
31 items: ReadonlyArray<Source>;
32 message: null;
33 }
34 | {
35 state: "error";
36 items: ReadonlyArray<Source>;
37 message: string;
38 }
39 | {
40 state: "ready" | "refreshing";
41 items: ReadonlyArray<Source>;
42 message: null;
43 };
44
45export const sourcesLoading = Atom.make<SourcesState>({
46 state: "loading",
47 items: emptySources,
48 message: null,
49});
50
51export const sourcesByWorkspace = Atom.family((workspaceId: WorkspaceId) =>
52 Atom.make((get): SourcesState => {
53 const result = get(sourcesResultByWorkspace(workspaceId));
54
55 return Result.match(result, {
56 onInitial: () => ({
57 state: "loading",
58 items: emptySources,
59 message: null,
60 }),
61 onFailure: (failure) => ({
62 state: "error",
63 items: Option.getOrElse(Result.value(result), () => emptySources),
64 message: Cause.pretty(failure.cause),
65 }),
66 onSuccess: (success) => ({
67 state: success.waiting ? "refreshing" : "ready",
68 items: success.value,
69 message: null,
70 }),
71 });
72 }),
73);
v2/apps/web/lib/control-plane/client.ts+5−0
@@ -0,0 +1,5 @@
1import { createControlPlaneAtomClient } from "@executor-v2/control-plane";
2
3export const controlPlaneClient = createControlPlaneAtomClient({
4 baseUrl: process.env.NEXT_PUBLIC_CONTROL_PLANE_BASE_URL ?? "/api/control-plane",
5});
v2/apps/web/next-env.d.ts+6−0
@@ -0,0 +1,6 @@
1/// <reference types="next" />
2/// <reference types="next/image-types/global" />
3/// <reference path="./.next/types/routes.d.ts" />
4
5// NOTE: This file should not be edited
6// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
v2/apps/web/next.config.ts+22−0
@@ -0,0 +1,22 @@
1import { dirname, resolve } from "node:path";
2import { fileURLToPath } from "node:url";
3import type { NextConfig } from "next";
4
5const appDir = dirname(fileURLToPath(import.meta.url));
6
7const controlPlaneUpstream =
8 process.env.CONTROL_PLANE_UPSTREAM_URL ?? "http://127.0.0.1:8787";
9
10const nextConfig: NextConfig = {
11 outputFileTracingRoot: resolve(appDir, "../.."),
12 async rewrites() {
13 return [
14 {
15 source: "/api/control-plane/:path*",
16 destination: `${controlPlaneUpstream}/:path*`,
17 },
18 ];
19 },
20};
21
22export default nextConfig;
v2/apps/web/package.json+17−0
@@ -3,6 +3,23 @@ · 2 unmodified lines
3 "private": true,3 "private": true,
4 "type": "module",4 "type": "module",
5 "scripts": {5 "scripts": {
6 "dev": "next dev --port 3000",
7 "build": "next build",
8 "start": "next start --port 3000",
6 "typecheck": "bunx tsc --noEmit -p tsconfig.json"9 "typecheck": "bunx tsc --noEmit -p tsconfig.json"
10 },
11 "dependencies": {
12 "@effect-atom/atom": "^0.4.11",
13 "@effect-atom/atom-react": "^0.4.6",
14 "@executor-v2/control-plane": "workspace:*",
15 "@executor-v2/schema": "workspace:*",
16 "effect": "catalog:",
17 "next": "^15.4.6",
18 "react": "^19.1.1",
19 "react-dom": "^19.1.1"
20 },
21 "devDependencies": {
22 "@types/react": "^19.1.10",
23 "@types/react-dom": "^19.1.7"
7 }24 }
8}25}
v2/apps/web/src/main.ts+0−1
@@ -1 +0,0 @@
1export {};
v2/apps/web/tsconfig.json+31−1
@@ -1,4 +1,34 @@
1{1{
2 "extends": "../../tsconfig.json",2 "extends": "../../tsconfig.json",
3 "include": ["src"]3 "compilerOptions": {
4 "lib": [
5 "dom",
6 "dom.iterable",
7 "esnext"
8 ],
9 "jsx": "preserve",
10 "incremental": true,
11 "plugins": [
12 {
13 "name": "next"
14 },
15 {
16 "name": "@effect/language-service"
17 }
18 ],
19 "types": [
20 "node"
21 ],
22 "allowJs": true,
23 "esModuleInterop": true
24 },
25 "include": [
26 "next-env.d.ts",
27 "**/*.ts",
28 "**/*.tsx",
29 ".next/types/**/*.ts"
30 ],
31 "exclude": [
32 "node_modules"
33 ]
4}34}
v2/bun.lock+65−10
@@ -11,6 +11,7 @@ · 10 unmodified lines
11 "@effect/language-service": "^0.76.0",11 "@effect/language-service": "^0.76.0",
12 "@types/bun": "^1.2.22",12 "@types/bun": "^1.2.22",
13 "@types/node": "catalog:",13 "@types/node": "catalog:",
14 "turbo": "^2.8.11",
14 "typescript": "catalog:",15 "typescript": "catalog:",
15 },16 },
16 },17 },
@@ -54,6 +55,20 @@ · 37 unmodified lines
54 },55 },
55 "apps/web": {56 "apps/web": {
56 "name": "@executor-v2/app-web",57 "name": "@executor-v2/app-web",
58 "dependencies": {
59 "@effect-atom/atom": "^0.4.11",
60 "@effect-atom/atom-react": "^0.4.6",
61 "@executor-v2/control-plane": "workspace:*",
62 "@executor-v2/schema": "workspace:*",
63 "effect": "catalog:",
64 "next": "^15.4.6",
65 "react": "^19.1.1",
66 "react-dom": "^19.1.1",
67 },
68 "devDependencies": {
69 "@types/react": "^19.1.10",
70 "@types/react-dom": "^19.1.7",
71 },
57 },72 },
58 "packages/ai-sdk-adapter": {73 "packages/ai-sdk-adapter": {
59 "name": "@executor-v2/ai-sdk-adapter",74 "name": "@executor-v2/ai-sdk-adapter",
@@ -408,23 +423,23 @@ · 348 unmodified lines
408 423
409 "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],424 "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
410 425
411 "@next/env": ["@next/env@16.1.6", "", {}, "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ=="],426 "@next/env": ["@next/env@15.5.12", "", {}, "sha512-pUvdJN1on574wQHjaBfNGDt9Mz5utDSZFsIIQkMzPgNS8ZvT4H2mwOrOIClwsQOb6EGx5M76/CZr6G8i6pSpLg=="],
412 427
413 "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.1.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw=="],428 "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.5.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RnRjBtH8S8eXCpUNkQ+543DUc7ys8y15VxmFU9HRqlo9BG3CcBUiwNtF8SNoi2xvGCVJq1vl2yYq+3oISBS0Zg=="],
414 429
415 "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.1.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ=="],430 "@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.5.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-nqa9/7iQlboF1EFtNhWxQA0rQstmYRSBGxSM6g3GxvxHxcoeqVXfGNr9stJOme674m2V7r4E3+jEhhGvSQhJRA=="],
416 431
417 "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.1.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw=="],432 "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.5.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-dCzAjqhDHwmoB2M4eYfVKqXs99QdQxNQVpftvP1eGVppamXh/OkDAwV737Zr0KPXEqRUMN4uCjh6mjO+XtF3Mw=="],
418 433
419 "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.1.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ=="],434 "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.5.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-+fpGWvQiITgf7PUtbWY1H7qUSnBZsPPLyyq03QuAKpVoTy/QUx1JptEDTQMVvQhvizCEuNLEeghrQUyXQOekuw=="],
420 435
421 "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.1.6", "", { "os": "linux", "cpu": "x64" }, "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ=="],436 "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.5.12", "", { "os": "linux", "cpu": "x64" }, "sha512-jSLvgdRRL/hrFAPqEjJf1fFguC719kmcptjNVDJl26BnJIpjL3KH5h6mzR4mAweociLQaqvt4UyzfbFjgAdDcw=="],
422 437
423 "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.1.6", "", { "os": "linux", "cpu": "x64" }, "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg=="],438 "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.5.12", "", { "os": "linux", "cpu": "x64" }, "sha512-/uaF0WfmYqQgLfPmN6BvULwxY0dufI2mlN2JbOKqqceZh1G4hjREyi7pg03zjfyS6eqNemHAZPSoP84x17vo6w=="],
424 439
425 "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.1.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw=="],440 "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.5.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-xhsL1OvQSfGmlL5RbOmU+FV120urrgFpYLq+6U8C6KIym32gZT6XF/SDE92jKzzlPWskkbjOKCpqk5m4i8PEfg=="],
426 441
427 "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.1.6", "", { "os": "win32", "cpu": "x64" }, "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A=="],442 "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.5.12", "", { "os": "win32", "cpu": "x64" }, "sha512-Z1Dh6lhFkxvBDH1FoW6OU/L6prYwPSlwjLiZkExIAh8fbP6iI/M7iGTQAJPYJ9YFlWobCZ1PHbchFhFYb2ADkw=="],
428 443
429 "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],444 "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
430 445
@@ -612,6 +627,10 @@ · 181 unmodified lines
612 627
613 "@types/node": ["@types/node@20.19.33", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw=="],628 "@types/node": ["@types/node@20.19.33", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw=="],
614 629
630 "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
631
632 "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
633
615 "@vercel/backends": ["@vercel/backends@0.0.37", "", { "dependencies": { "@vercel/build-utils": "13.5.0", "@vercel/nft": "1.3.0", "execa": "3.2.0", "fs-extra": "11.1.0", "oxc-transform": "0.111.0", "path-to-regexp": "8.3.0", "resolve.exports": "2.0.3", "rolldown": "1.0.0-rc.1", "srvx": "0.8.9", "tsx": "4.21.0", "zod": "3.22.4" }, "peerDependencies": { "typescript": "^4.0.0 || ^5.0.0" } }, "sha512-RSUWaDIlcfv6Q2B34Lce77RhB5lsG0bTz6rOrPAB1ZzkUNpDVlmobGxIZTgSD3MHmJGJCP8MBjM2zqG9jY3XYQ=="],634 "@vercel/backends": ["@vercel/backends@0.0.37", "", { "dependencies": { "@vercel/build-utils": "13.5.0", "@vercel/nft": "1.3.0", "execa": "3.2.0", "fs-extra": "11.1.0", "oxc-transform": "0.111.0", "path-to-regexp": "8.3.0", "resolve.exports": "2.0.3", "rolldown": "1.0.0-rc.1", "srvx": "0.8.9", "tsx": "4.21.0", "zod": "3.22.4" }, "peerDependencies": { "typescript": "^4.0.0 || ^5.0.0" } }, "sha512-RSUWaDIlcfv6Q2B34Lce77RhB5lsG0bTz6rOrPAB1ZzkUNpDVlmobGxIZTgSD3MHmJGJCP8MBjM2zqG9jY3XYQ=="],
616 635
617 "@vercel/blob": ["@vercel/blob@2.3.0", "", { "dependencies": { "async-retry": "^1.3.3", "is-buffer": "^2.0.5", "is-node-process": "^1.2.0", "throttleit": "^2.1.0", "undici": "^6.23.0" } }, "sha512-oYWiJbWRQ7gz9Mj0X/NHFJ3OcLMOBzq/2b3j6zeNrQmtFo6dHwU8FAwNpxVIYddVMd+g8eqEi7iRueYx8FtM0Q=="],636 "@vercel/blob": ["@vercel/blob@2.3.0", "", { "dependencies": { "async-retry": "^1.3.3", "is-buffer": "^2.0.5", "is-node-process": "^1.2.0", "throttleit": "^2.1.0", "undici": "^6.23.0" } }, "sha512-oYWiJbWRQ7gz9Mj0X/NHFJ3OcLMOBzq/2b3j6zeNrQmtFo6dHwU8FAwNpxVIYddVMd+g8eqEi7iRueYx8FtM0Q=="],
@@ -788,6 +807,8 @@ · 170 unmodified lines
788 807
789 "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],808 "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
790 809
810 "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
811
791 "data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="],812 "data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="],
792 813
793 "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],814 "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
@@ -1018,7 +1039,7 @@ · 224 unmodified lines
1018 1039
1019 "netmask": ["netmask@2.0.2", "", {}, "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg=="],1040 "netmask": ["netmask@2.0.2", "", {}, "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg=="],
1020 1041
1021 "next": ["next@16.1.6", "", { "dependencies": { "@next/env": "16.1.6", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.1.6", "@next/swc-darwin-x64": "16.1.6", "@next/swc-linux-arm64-gnu": "16.1.6", "@next/swc-linux-arm64-musl": "16.1.6", "@next/swc-linux-x64-gnu": "16.1.6", "@next/swc-linux-x64-musl": "16.1.6", "@next/swc-win32-arm64-msvc": "16.1.6", "@next/swc-win32-x64-msvc": "16.1.6", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw=="],1042 "next": ["next@15.5.12", "", { "dependencies": { "@next/env": "15.5.12", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.5.12", "@next/swc-darwin-x64": "15.5.12", "@next/swc-linux-arm64-gnu": "15.5.12", "@next/swc-linux-arm64-musl": "15.5.12", "@next/swc-linux-x64-gnu": "15.5.12", "@next/swc-linux-x64-musl": "15.5.12", "@next/swc-win32-arm64-msvc": "15.5.12", "@next/swc-win32-x64-msvc": "15.5.12", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-Fi/wQ4Etlrn60rz78bebG1i1SR20QxvV8tVp6iJspjLUSHcZoeUXCt+vmWoEcza85ElZzExK/jJ/F6SvtGktjA=="],
1022 1043
1023 "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="],1044 "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="],
1024 1045
@@ -1232,6 +1253,20 @@ · 207 unmodified lines
1232 1253
1233 "tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="],1254 "tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="],
1234 1255
1256 "turbo": ["turbo@2.8.11", "", { "optionalDependencies": { "turbo-darwin-64": "2.8.11", "turbo-darwin-arm64": "2.8.11", "turbo-linux-64": "2.8.11", "turbo-linux-arm64": "2.8.11", "turbo-windows-64": "2.8.11", "turbo-windows-arm64": "2.8.11" }, "bin": { "turbo": "bin/turbo" } }, "sha512-H+rwSHHPLoyPOSoHdmI1zY0zy0GGj1Dmr7SeJW+nZiWLz2nex8EJ+fkdVabxXFMNEux+aywI4Sae8EqhmnOv4A=="],
1257
1258 "turbo-darwin-64": ["turbo-darwin-64@2.8.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-XKaCWaz4OCt77oYYvGCIRpvYD4c/aNaKjRkUpv+e8rN3RZb+5Xsyew4yRO+gaHdMIUhQznXNXfHlhs+/p7lIhA=="],
1259
1260 "turbo-darwin-arm64": ["turbo-darwin-arm64@2.8.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-VvynLHGUNvQ9k7GZjRPSsRcK4VkioTfFb7O7liAk4nHKjEcMdls7GqxzjVWgJiKz3hWmQGaP9hRa9UUnhVWCxA=="],
1261
1262 "turbo-linux-64": ["turbo-linux-64@2.8.11", "", { "os": "linux", "cpu": "x64" }, "sha512-cbSn37dcm+EmkQ7DD0euy7xV7o2el4GAOr1XujvkAyKjjNvQ+6QIUeDgQcwAx3D17zPpDvfDMJY2dLQadWnkmQ=="],
1263
1264 "turbo-linux-arm64": ["turbo-linux-arm64@2.8.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-+trymp2s2aBrhS04l6qFxcExzZ8ffndevuUB9c5RCeqsVpZeiWuGQlWNm5XjOmzoMayxRARZ5ma7yiWbGMiLqQ=="],
1265
1266 "turbo-windows-64": ["turbo-windows-64@2.8.11", "", { "os": "win32", "cpu": "x64" }, "sha512-3kJjFSM4yw1n9Uzmi+XkAUgCae19l/bH6RJ442xo7mnZm0tpOjo33F+FYHoSVpIWVMd0HG0LDccyafPSdylQbA=="],
1267
1268 "turbo-windows-arm64": ["turbo-windows-arm64@2.8.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-JOM4uF2vuLsJUvibdR6X9QqdZr6BhC6Nhlrw4LKFPsXZZI/9HHLoqAiYRpE4MuzIwldCH/jVySnWXrI1SKto0g=="],
1269
1235 "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],1270 "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
1236 1271
1237 "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],1272 "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
@@ -1292,6 +1327,8 @@ · 54 unmodified lines
1292 1327
1293 "@effect/platform-node-shared/ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="],1328 "@effect/platform-node-shared/ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="],
1294 1329
1330 "@executor-v2/confect/next": ["next@16.1.6", "", { "dependencies": { "@next/env": "16.1.6", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.1.6", "@next/swc-darwin-x64": "16.1.6", "@next/swc-linux-arm64-gnu": "16.1.6", "@next/swc-linux-arm64-musl": "16.1.6", "@next/swc-linux-x64-gnu": "16.1.6", "@next/swc-linux-x64-musl": "16.1.6", "@next/swc-win32-arm64-msvc": "16.1.6", "@next/swc-win32-x64-msvc": "16.1.6", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw=="],
1331
1295 "@mapbox/node-pre-gyp/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],1332 "@mapbox/node-pre-gyp/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
1296 1333
1297 "@mapbox/node-pre-gyp/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],1334 "@mapbox/node-pre-gyp/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
@@ -1388,6 +1425,24 @@ · 90 unmodified lines
1388 1425
1389 "vite-node/es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],1426 "vite-node/es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
1390 1427
1428 "@executor-v2/confect/next/@next/env": ["@next/env@16.1.6", "", {}, "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ=="],
1429
1430 "@executor-v2/confect/next/@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.1.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw=="],
1431
1432 "@executor-v2/confect/next/@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.1.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ=="],
1433
1434 "@executor-v2/confect/next/@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.1.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw=="],
1435
1436 "@executor-v2/confect/next/@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.1.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ=="],
1437
1438 "@executor-v2/confect/next/@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.1.6", "", { "os": "linux", "cpu": "x64" }, "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ=="],
1439
1440 "@executor-v2/confect/next/@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.1.6", "", { "os": "linux", "cpu": "x64" }, "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg=="],
1441
1442 "@executor-v2/confect/next/@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.1.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw=="],
1443
1444 "@executor-v2/confect/next/@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.1.6", "", { "os": "win32", "cpu": "x64" }, "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A=="],
1445
1391 "@ts-morph/common/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],1446 "@ts-morph/common/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
1392 1447
1393 "@vercel/express/@vercel/nft/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],1448 "@vercel/express/@vercel/nft/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
v2/package.json+2−0
@@ -18,6 +18,7 @@ · 17 unmodified lines
18 },18 },
19 "scripts": {19 "scripts": {
20 "prepare": "effect-language-service patch",20 "prepare": "effect-language-service patch",
21 "dev": "turbo run dev --parallel --filter=@executor-v2/app-pm --filter=@executor-v2/app-convex --filter=@executor-v2/app-web",
21 "typecheck": "tsc --noEmit",22 "typecheck": "tsc --noEmit",
22 "knip": "bunx knip --config knip.json"23 "knip": "bunx knip --config knip.json"
23 },24 },
@@ -25,6 +26,7 @@ · 1 unmodified line
25 "@effect/language-service": "^0.76.0",26 "@effect/language-service": "^0.76.0",
26 "@types/bun": "^1.2.22",27 "@types/bun": "^1.2.22",
27 "@types/node": "catalog:",28 "@types/node": "catalog:",
29 "turbo": "^2.8.11",
28 "typescript": "catalog:"30 "typescript": "catalog:"
29 },31 },
30 "dependencies": {32 "dependencies": {
v2/packages/control-plane/src/client.ts+8−128
@@ -1,24 +1,16 @@
1import { Atom, Result } from "@effect-atom/atom";1import { AtomHttpApi } from "@effect-atom/atom";
2import {2import {
3 FetchHttpClient,3 FetchHttpClient,
4 HttpApiClient,4 HttpApiClient,
5 HttpApiError,5 HttpApiError,
6 HttpClientError,6 HttpClientError,
7} from "@effect/platform";7} from "@effect/platform";
8import type {
9 Source,
10 SourceId,
11 WorkspaceId,
12} from "@executor-v2/schema";
13import * as Effect from "effect/Effect";
14import * as ParseResult from "effect/ParseResult";8import * as ParseResult from "effect/ParseResult";
15 9
16import {10import {
17 ControlPlaneApi,11 ControlPlaneApi,
18 type ControlPlaneBadRequestError,12 type ControlPlaneBadRequestError,
19 type ControlPlaneStorageError,13 type ControlPlaneStorageError,
20 type RemoveSourceResult,
21 type UpsertSourcePayload,
22} from "./api";14} from "./api";
23 15
24export type ControlPlaneClientOptions = {16export type ControlPlaneClientOptions = {
@@ -32,128 +24,16 @@ export type ControlPlaneClientError = · 7 unmodified lines
32 | HttpClientError.HttpClientError24 | HttpClientError.HttpClientError
33 | ParseResult.ParseError;25 | ParseResult.ParseError;
34 26
35export type ListSourcesRequest = {
36 workspaceId: WorkspaceId;
37};
38
39export type UpsertSourceRequest = {
40 workspaceId: WorkspaceId;
41 payload: UpsertSourcePayload;
42};
43
44export type RemoveSourceRequest = {
45 workspaceId: WorkspaceId;
46 sourceId: SourceId;
47};
48
49export type ControlPlaneReadOptions = {
50 enabled?: boolean;
51};
52
53export const makeControlPlaneClient = (options: ControlPlaneClientOptions) =>27export const makeControlPlaneClient = (options: ControlPlaneClientOptions) =>
54 HttpApiClient.make(ControlPlaneApi, {28 HttpApiClient.make(ControlPlaneApi, {
55 baseUrl: options.baseUrl,29 baseUrl: options.baseUrl,
56 });30 });
57 31
58const isReadEnabled = (options?: ControlPlaneReadOptions): boolean =>32export const createControlPlaneAtomClient = (options: ControlPlaneClientOptions) =>
59 options?.enabled ?? true;33 AtomHttpApi.Tag<unknown>()("@executor-v2/control-plane/AtomClient", {
60 34 api: ControlPlaneApi,
61const makeDisabledResultAtom = <Success, Error>() =>35 httpClient: FetchHttpClient.layer,
62 Atom.make(Result.initial<Success, Error>());36 baseUrl: options.baseUrl,
63 37 });
64export const createControlPlaneAtomClient = (
65 options: ControlPlaneClientOptions,
66) => {
67 const baseLayer = FetchHttpClient.layer;
68 const runtime = Atom.runtime(baseLayer);
69
70 const listSourcesEffect = (
71 input: ListSourcesRequest,
72 ): Effect.Effect<ReadonlyArray<Source>, ControlPlaneClientError, never> =>
73 Effect.gen(function* () {
74 const client = yield* makeControlPlaneClient(options);
75 return yield* client.sources.list({
76 path: {
77 workspaceId: input.workspaceId,
78 },
79 });
80 }).pipe(Effect.provide(baseLayer));
81
82 const upsertSourceEffect = (
83 input: UpsertSourceRequest,
84 ): Effect.Effect<Source, ControlPlaneClientError, never> =>
85 Effect.gen(function* () {
86 const client = yield* makeControlPlaneClient(options);
87 return yield* client.sources.upsert({
88 path: {
89 workspaceId: input.workspaceId,
90 },
91 payload: input.payload,
92 });
93 }).pipe(Effect.provide(baseLayer));
94
95 const removeSourceEffect = (
96 input: RemoveSourceRequest,
97 ): Effect.Effect<RemoveSourceResult, ControlPlaneClientError, never> =>
98 Effect.gen(function* () {
99 const client = yield* makeControlPlaneClient(options);
100 return yield* client.sources.remove({
101 path: {
102 workspaceId: input.workspaceId,
103 sourceId: input.sourceId,
104 },
105 });
106 }).pipe(Effect.provide(baseLayer));
107
108 const listSourcesFamily = Atom.family((workspaceId: WorkspaceId) =>
109 runtime.atom(
110 listSourcesEffect({
111 workspaceId,
112 }),
113 ),
114 );
115
116 const listDisabledAtom = makeDisabledResultAtom<
117 ReadonlyArray<Source>,
118 ControlPlaneClientError
119 >();
120
121 const upsertSourceMutate = runtime.fn<UpsertSourceRequest>()(
122 Effect.fnUntraced(function* (input) {
123 return yield* upsertSourceEffect(input);
124 }),
125 );
126
127 const removeSourceMutate = runtime.fn<RemoveSourceRequest>()(
128 Effect.fnUntraced(function* (input) {
129 return yield* removeSourceEffect(input);
130 }),
131 );
132 38
133 return {39export type ControlPlaneAtomClient = ReturnType<typeof createControlPlaneAtomClient>;
134 runtime,
135 sources: {
136 list: {
137 query: (input: ListSourcesRequest, readOptions?: ControlPlaneReadOptions) =>
138 isReadEnabled(readOptions)
139 ? listSourcesFamily(input.workspaceId)
140 : listDisabledAtom,
141 queryEffect: listSourcesEffect,
142 queryPromise: (input: ListSourcesRequest) =>
143 Effect.runPromise(listSourcesEffect(input)),
144 },
145 upsert: {
146 mutate: upsertSourceMutate,
147 mutateEffect: upsertSourceEffect,
148 mutatePromise: (input: UpsertSourceRequest) =>
149 Effect.runPromise(upsertSourceEffect(input)),
150 },
151 remove: {
152 mutate: removeSourceMutate,
153 mutateEffect: removeSourceEffect,
154 mutatePromise: (input: RemoveSourceRequest) =>
155 Effect.runPromise(removeSourceEffect(input)),
156 },
157 },
158 };
159};
v2/packages/control-plane/src/index.ts+1−4
@@ -35,10 +35,7 @@ export { · 34 unmodified lines
35export {35export {
36 createControlPlaneAtomClient,36 createControlPlaneAtomClient,
37 makeControlPlaneClient,37 makeControlPlaneClient,
38 type ControlPlaneAtomClient,
38 type ControlPlaneClientError,39 type ControlPlaneClientError,
39 type ControlPlaneClientOptions,40 type ControlPlaneClientOptions,
40 type ControlPlaneReadOptions,
41 type ListSourcesRequest,
42 type RemoveSourceRequest,
43 type UpsertSourceRequest,
44} from "./client";41} from "./client";
v2/tsconfig.json+1−0
@@ -4,6 +4,7 @@ · 3 unmodified lines
4 "module": "ESNext",4 "module": "ESNext",
5 "moduleResolution": "Bundler",5 "moduleResolution": "Bundler",
6 "moduleDetection": "force",6 "moduleDetection": "force",
7 "jsx": "preserve",
7 "strict": true,8 "strict": true,
8 "noEmit": true,9 "noEmit": true,
9 "verbatimModuleSyntax": true,10 "verbatimModuleSyntax": true,
v2/turbo.json+19−0
@@ -0,0 +1,19 @@
1{
2 "$schema": "https://turbo.build/schema.json",
3 "tasks": {
4 "dev": {
5 "cache": false,
6 "persistent": true
7 },
8 "typecheck": {
9 "outputs": []
10 },
11 "build": {
12 "dependsOn": ["^build"]
13 },
14 "test": {
15 "dependsOn": ["^test"],
16 "outputs": []
17 }
18 }
19}