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.
Unified · Split
| @@ -1,3 +1,6 @@ | ||
| 1 | 1 | .reference/ |
| 2 | 2 | .vercel |
| 3 | 3 | .env*.local |
| 4 | +.next/ | |
| 5 | +.turbo/ | |
| 6 | +.executor-v2/ | |
| @@ -1,14 +1,17 @@ | ||
| 1 | 1 | import { |
| 2 | − makeLocalInProcessRuntimeAdapter, | |
| 3 | − makeToolProviderRegistry, | |
| 4 | 2 | ToolProviderRegistryService, |
| 5 | − type RuntimeExecuteError, | |
| 6 | −} from "@executor-v2/engine"; | |
| 3 | + makeToolProviderRegistry, | |
| 4 | +} from "@executor-v2/engine/tool-providers"; | |
| 7 | 5 | import type { ExecuteRunInput, ExecuteRunResult } from "@executor-v2/sdk"; |
| 8 | 6 | import * as Context from "effect/Context"; |
| 9 | 7 | import * as Effect from "effect/Effect"; |
| 10 | 8 | import * as Layer from "effect/Layer"; |
| 11 | 9 | |
| 10 | +import { | |
| 11 | + makeLocalInProcessRuntimeAdapter, | |
| 12 | + type RuntimeExecuteError, | |
| 13 | +} from "./runtime_adapter"; | |
| 14 | + | |
| 12 | 15 | export type ConvexRunExecutorService = { |
| 13 | 16 | executeRun: ( |
| 14 | 17 | input: ExecuteRunInput, |
| @@ -23,9 +26,7 @@ const runtimeAdapter = makeLocalInProcessRuntimeAdapter(); · 8 unmodified lines | ||
| 23 | 26 | |
| 24 | 27 | const formatRuntimeExecuteError = (error: RuntimeExecuteError): string => { |
| 25 | 28 | switch (error._tag) { |
| 26 | − case "RuntimeAdapterError": | |
| 27 | 29 | case "LocalCodeRunnerError": |
| 28 | − case "DenoSubprocessRunnerError": | |
| 29 | 30 | case "ToolProviderError": |
| 30 | 31 | return error.details ? `${error.message}: ${error.details}` : error.message; |
| 31 | 32 | case "ToolProviderRegistryError": |
| @@ -1,7 +1,12 @@ | ||
| 1 | −import { executeJavaScriptWithTools } from "@executor-v2/engine/local-runner"; | |
| 2 | −import type { | |
| 3 | − CanonicalToolDescriptor, | |
| 1 | +import { | |
| 2 | + LocalCodeRunnerError, | |
| 3 | + executeJavaScriptWithTools, | |
| 4 | +} from "@executor-v2/engine/local-runner"; | |
| 5 | +import { | |
| 6 | + ToolProviderError, | |
| 7 | + ToolProviderRegistryError, | |
| 4 | 8 | ToolProviderRegistryService, |
| 9 | + type CanonicalToolDescriptor, | |
| 5 | 10 | } from "@executor-v2/engine/tool-providers"; |
| 6 | 11 | import type { Source } from "@executor-v2/schema"; |
| 7 | 12 | import * as Effect from "effect/Effect"; |
| @@ -17,12 +22,17 @@ export type RuntimeExecuteInput = { · 9 unmodified lines | ||
| 17 | 22 | timeoutMs?: number; |
| 18 | 23 | }; |
| 19 | 24 | |
| 25 | +export type RuntimeExecuteError = | |
| 26 | + | LocalCodeRunnerError | |
| 27 | + | ToolProviderRegistryError | |
| 28 | + | ToolProviderError; | |
| 29 | + | |
| 20 | 30 | export type RuntimeAdapter = { |
| 21 | 31 | kind: "local-inproc"; |
| 22 | 32 | isAvailable: () => Effect.Effect<boolean>; |
| 23 | 33 | execute: ( |
| 24 | 34 | input: RuntimeExecuteInput, |
| 25 | − ) => Effect.Effect<unknown, unknown, ToolProviderRegistryService>; | |
| 35 | + ) => Effect.Effect<unknown, RuntimeExecuteError, ToolProviderRegistryService>; | |
| 26 | 36 | }; |
| 27 | 37 | |
| 28 | 38 | export const makeLocalInProcessRuntimeAdapter = (): RuntimeAdapter => ({ |
| @@ -1,3 +1,15 @@ | ||
| 1 | 1 | # app-web |
| 2 | 2 | |
| 3 | −Web UI app scaffold for Executor v2. | |
| 3 | +Basic 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 | + | |
| 9 | +Run: | |
| 10 | + | |
| 11 | +- `bun run --cwd apps/web dev` | |
| 12 | +- Open `http://127.0.0.1:3000` | |
| 13 | + | |
| 14 | +By default, proxy target is `http://127.0.0.1:8787`. | |
| 15 | +Override with `CONTROL_PLANE_UPSTREAM_URL`. | |
| @@ -0,0 +1,114 @@ | ||
| 1 | +# Effect Atom guide for `apps/web` | |
| 2 | + | |
| 3 | +This file defines how we write Effect Atom code in this app. | |
| 4 | + | |
| 5 | +## Goal | |
| 6 | + | |
| 7 | +Keep 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 | + | |
| 36 | +Create once at module scope: | |
| 37 | + | |
| 38 | +```ts | |
| 39 | +const sourcesResultByWorkspace = Atom.family((workspaceId: WorkspaceId) => | |
| 40 | + controlPlaneClient.query("sources", "list", { | |
| 41 | + path: { workspaceId }, | |
| 42 | + }), | |
| 43 | +); | |
| 44 | +``` | |
| 45 | + | |
| 46 | +Then in component: | |
| 47 | + | |
| 48 | +```ts | |
| 49 | +const result = useAtomValue(sourcesResultByWorkspace(workspaceId)); | |
| 50 | +``` | |
| 51 | + | |
| 52 | +No `useMemo` needed. | |
| 53 | + | |
| 54 | +### 2) Derived state atom for UI projection | |
| 55 | + | |
| 56 | +Map `Result` once in atom-land: | |
| 57 | + | |
| 58 | +```ts | |
| 59 | +const 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 | + | |
| 79 | +Components switch on `sources.state`, not raw result internals. | |
| 80 | + | |
| 81 | +### 3) Mutations as atoms | |
| 82 | + | |
| 83 | +Define mutation atoms once: | |
| 84 | + | |
| 85 | +```ts | |
| 86 | +const upsertSource = controlPlaneClient.mutation("sources", "upsert"); | |
| 87 | +const removeSource = controlPlaneClient.mutation("sources", "remove"); | |
| 88 | +``` | |
| 89 | + | |
| 90 | +In 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 | |
| @@ -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 | + | |
| 16 | +html, | |
| 17 | +body { | |
| 18 | + margin: 0; | |
| 19 | + padding: 0; | |
| 20 | + min-height: 100%; | |
| 21 | +} | |
| 22 | + | |
| 23 | +body { | |
| 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 | + | |
| 32 | +a { | |
| 33 | + color: inherit; | |
| 34 | +} | |
| 35 | + | |
| 36 | +main { | |
| 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 | + | |
| 80 | +h2 { | |
| 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 | + | |
| 90 | +label { | |
| 91 | + display: block; | |
| 92 | + margin-bottom: 0.4rem; | |
| 93 | + color: var(--muted); | |
| 94 | + font-size: 0.9rem; | |
| 95 | +} | |
| 96 | + | |
| 97 | +input, | |
| 98 | +select, | |
| 99 | +button { | |
| 100 | + width: 100%; | |
| 101 | + border-radius: 10px; | |
| 102 | + border: 1px solid var(--border); | |
| 103 | + font: inherit; | |
| 104 | +} | |
| 105 | + | |
| 106 | +input, | |
| 107 | +select { | |
| 108 | + padding: 0.62rem 0.7rem; | |
| 109 | + background: #fff; | |
| 110 | +} | |
| 111 | + | |
| 112 | +button { | |
| 113 | + padding: 0.7rem 0.8rem; | |
| 114 | + font-weight: 600; | |
| 115 | + cursor: pointer; | |
| 116 | +} | |
| 117 | + | |
| 118 | +button.primary { | |
| 119 | + background: var(--accent); | |
| 120 | + color: var(--accent-ink); | |
| 121 | + border-color: transparent; | |
| 122 | +} | |
| 123 | + | |
| 124 | +button.secondary { | |
| 125 | + background: transparent; | |
| 126 | +} | |
| 127 | + | |
| 128 | +button.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 | +} | |
| @@ -0,0 +1,37 @@ | ||
| 1 | +import type { Metadata } from "next"; | |
| 2 | +import { Fraunces, IBM_Plex_Sans } from "next/font/google"; | |
| 3 | +import type { ReactNode } from "react"; | |
| 4 | + | |
| 5 | +import { Providers } from "./providers"; | |
| 6 | +import "./globals.css"; | |
| 7 | + | |
| 8 | +const bodyFont = IBM_Plex_Sans({ | |
| 9 | + subsets: ["latin"], | |
| 10 | + variable: "--font-body", | |
| 11 | + weight: ["400", "500", "600"], | |
| 12 | +}); | |
| 13 | + | |
| 14 | +const displayFont = Fraunces({ | |
| 15 | + subsets: ["latin"], | |
| 16 | + variable: "--font-display", | |
| 17 | + weight: ["500", "700"], | |
| 18 | +}); | |
| 19 | + | |
| 20 | +export const metadata: Metadata = { | |
| 21 | + title: "Executor v2 Control Plane", | |
| 22 | + description: "Basic Next.js frontend for Executor v2 control plane", | |
| 23 | +}; | |
| 24 | + | |
| 25 | +type RootLayoutProps = { | |
| 26 | + children: ReactNode; | |
| 27 | +}; | |
| 28 | + | |
| 29 | +const RootLayout = ({ children }: RootLayoutProps) => ( | |
| 30 | + <html lang="en" className={`${bodyFont.variable} ${displayFont.variable}`}> | |
| 31 | + <body> | |
| 32 | + <Providers>{children}</Providers> | |
| 33 | + </body> | |
| 34 | + </html> | |
| 35 | +); | |
| 36 | + | |
| 37 | +export default RootLayout; | |
| @@ -0,0 +1,210 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useAtomRefresh, useAtomSet, useAtomValue } from "@effect-atom/atom-react"; | |
| 4 | +import type { UpsertSourcePayload } from "@executor-v2/control-plane"; | |
| 5 | +import type { SourceId, SourceKind, WorkspaceId } from "@executor-v2/schema"; | |
| 6 | +import { useEffect, useState, useTransition, type FormEvent } from "react"; | |
| 7 | + | |
| 8 | +import { | |
| 9 | + removeSource, | |
| 10 | + sourcesByWorkspace, | |
| 11 | + sourcesLoading, | |
| 12 | + sourcesResultByWorkspace, | |
| 13 | + sourcesResultDisabled, | |
| 14 | + upsertSource, | |
| 15 | +} from "../lib/control-plane/atoms"; | |
| 16 | + | |
| 17 | +const kindOptions: ReadonlyArray<SourceKind> = [ | |
| 18 | + "openapi", | |
| 19 | + "mcp", | |
| 20 | + "graphql", | |
| 21 | + "internal", | |
| 22 | +]; | |
| 23 | + | |
| 24 | +const 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 | + | |
| 210 | +export default Page; | |
| @@ -0,0 +1,12 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { RegistryProvider } from "@effect-atom/atom-react"; | |
| 4 | +import type { ReactNode } from "react"; | |
| 5 | + | |
| 6 | +type ProvidersProps = { | |
| 7 | + children: ReactNode; | |
| 8 | +}; | |
| 9 | + | |
| 10 | +export const Providers = ({ children }: ProvidersProps) => ( | |
| 11 | + <RegistryProvider>{children}</RegistryProvider> | |
| 12 | +); | |
| @@ -0,0 +1,73 @@ | ||
| 1 | +import { Atom, Result } from "@effect-atom/atom"; | |
| 2 | +import type { Source, WorkspaceId } from "@executor-v2/schema"; | |
| 3 | +import * as Cause from "effect/Cause"; | |
| 4 | +import * as Option from "effect/Option"; | |
| 5 | + | |
| 6 | +import { controlPlaneClient } from "./client"; | |
| 7 | + | |
| 8 | +type SourcesResult = Result.Result<ReadonlyArray<Source>, unknown>; | |
| 9 | + | |
| 10 | +const emptySources: ReadonlyArray<Source> = []; | |
| 11 | + | |
| 12 | +export const sourcesResultDisabled = Atom.make( | |
| 13 | + Result.initial<ReadonlyArray<Source>, unknown>(), | |
| 14 | +); | |
| 15 | + | |
| 16 | +export 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 | + | |
| 25 | +export const upsertSource = controlPlaneClient.mutation("sources", "upsert"); | |
| 26 | +export const removeSource = controlPlaneClient.mutation("sources", "remove"); | |
| 27 | + | |
| 28 | +export 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 | + | |
| 45 | +export const sourcesLoading = Atom.make<SourcesState>({ | |
| 46 | + state: "loading", | |
| 47 | + items: emptySources, | |
| 48 | + message: null, | |
| 49 | +}); | |
| 50 | + | |
| 51 | +export 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 | +); | |
| @@ -0,0 +1,5 @@ | ||
| 1 | +import { createControlPlaneAtomClient } from "@executor-v2/control-plane"; | |
| 2 | + | |
| 3 | +export const controlPlaneClient = createControlPlaneAtomClient({ | |
| 4 | + baseUrl: process.env.NEXT_PUBLIC_CONTROL_PLANE_BASE_URL ?? "/api/control-plane", | |
| 5 | +}); | |
| @@ -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. | |
| @@ -0,0 +1,22 @@ | ||
| 1 | +import { dirname, resolve } from "node:path"; | |
| 2 | +import { fileURLToPath } from "node:url"; | |
| 3 | +import type { NextConfig } from "next"; | |
| 4 | + | |
| 5 | +const appDir = dirname(fileURLToPath(import.meta.url)); | |
| 6 | + | |
| 7 | +const controlPlaneUpstream = | |
| 8 | + process.env.CONTROL_PLANE_UPSTREAM_URL ?? "http://127.0.0.1:8787"; | |
| 9 | + | |
| 10 | +const 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 | + | |
| 22 | +export default nextConfig; | |
| @@ -3,6 +3,23 @@ · 2 unmodified lines | ||
| 3 | 3 | "private": true, |
| 4 | 4 | "type": "module", |
| 5 | 5 | "scripts": { |
| 6 | + "dev": "next dev --port 3000", | |
| 7 | + "build": "next build", | |
| 8 | + "start": "next start --port 3000", | |
| 6 | 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 | } |
| @@ -1 +0,0 @@ | ||
| 1 | −export {}; | |
| @@ -1,4 +1,34 @@ | ||
| 1 | 1 | { |
| 2 | 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 | } |
| @@ -11,6 +11,7 @@ · 10 unmodified lines | ||
| 11 | 11 | "@effect/language-service": "^0.76.0", |
| 12 | 12 | "@types/bun": "^1.2.22", |
| 13 | 13 | "@types/node": "catalog:", |
| 14 | + "turbo": "^2.8.11", | |
| 14 | 15 | "typescript": "catalog:", |
| 15 | 16 | }, |
| 16 | 17 | }, |
| @@ -54,6 +55,20 @@ · 37 unmodified lines | ||
| 54 | 55 | }, |
| 55 | 56 | "apps/web": { |
| 56 | 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 | 73 | "packages/ai-sdk-adapter": { |
| 59 | 74 | "name": "@executor-v2/ai-sdk-adapter", |
| @@ -408,23 +423,23 @@ · 348 unmodified lines | ||
| 408 | 423 | |
| 409 | 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 | 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 | 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 | 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 | 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 | 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 | 812 | "data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="], |
| 792 | 813 | |
| 793 | 814 | "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], |
| @@ -1018,7 +1039,7 @@ · 224 unmodified lines | ||
| 1018 | 1039 | |
| 1019 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 1448 | "@vercel/express/@vercel/nft/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], |
| @@ -18,6 +18,7 @@ · 17 unmodified lines | ||
| 18 | 18 | }, |
| 19 | 19 | "scripts": { |
| 20 | 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 | 22 | "typecheck": "tsc --noEmit", |
| 22 | 23 | "knip": "bunx knip --config knip.json" |
| 23 | 24 | }, |
| @@ -25,6 +26,7 @@ · 1 unmodified line | ||
| 25 | 26 | "@effect/language-service": "^0.76.0", |
| 26 | 27 | "@types/bun": "^1.2.22", |
| 27 | 28 | "@types/node": "catalog:", |
| 29 | + "turbo": "^2.8.11", | |
| 28 | 30 | "typescript": "catalog:" |
| 29 | 31 | }, |
| 30 | 32 | "dependencies": { |
| @@ -1,24 +1,16 @@ | ||
| 1 | −import { Atom, Result } from "@effect-atom/atom"; | |
| 1 | +import { AtomHttpApi } from "@effect-atom/atom"; | |
| 2 | 2 | import { |
| 3 | 3 | FetchHttpClient, |
| 4 | 4 | HttpApiClient, |
| 5 | 5 | HttpApiError, |
| 6 | 6 | HttpClientError, |
| 7 | 7 | } from "@effect/platform"; |
| 8 | −import type { | |
| 9 | − Source, | |
| 10 | − SourceId, | |
| 11 | − WorkspaceId, | |
| 12 | −} from "@executor-v2/schema"; | |
| 13 | −import * as Effect from "effect/Effect"; | |
| 14 | 8 | import * as ParseResult from "effect/ParseResult"; |
| 15 | 9 | |
| 16 | 10 | import { |
| 17 | 11 | ControlPlaneApi, |
| 18 | 12 | type ControlPlaneBadRequestError, |
| 19 | 13 | type ControlPlaneStorageError, |
| 20 | − type RemoveSourceResult, | |
| 21 | − type UpsertSourcePayload, | |
| 22 | 14 | } from "./api"; |
| 23 | 15 | |
| 24 | 16 | export type ControlPlaneClientOptions = { |
| @@ -32,128 +24,16 @@ export type ControlPlaneClientError = · 7 unmodified lines | ||
| 32 | 24 | | HttpClientError.HttpClientError |
| 33 | 25 | | ParseResult.ParseError; |
| 34 | 26 | |
| 35 | −export type ListSourcesRequest = { | |
| 36 | − workspaceId: WorkspaceId; | |
| 37 | −}; | |
| 38 | − | |
| 39 | −export type UpsertSourceRequest = { | |
| 40 | − workspaceId: WorkspaceId; | |
| 41 | − payload: UpsertSourcePayload; | |
| 42 | −}; | |
| 43 | − | |
| 44 | −export type RemoveSourceRequest = { | |
| 45 | − workspaceId: WorkspaceId; | |
| 46 | − sourceId: SourceId; | |
| 47 | −}; | |
| 48 | − | |
| 49 | −export type ControlPlaneReadOptions = { | |
| 50 | − enabled?: boolean; | |
| 51 | −}; | |
| 52 | − | |
| 53 | 27 | export const makeControlPlaneClient = (options: ControlPlaneClientOptions) => |
| 54 | 28 | HttpApiClient.make(ControlPlaneApi, { |
| 55 | 29 | baseUrl: options.baseUrl, |
| 56 | 30 | }); |
| 57 | 31 | |
| 58 | −const isReadEnabled = (options?: ControlPlaneReadOptions): boolean => | |
| 59 | − options?.enabled ?? true; | |
| 60 | − | |
| 61 | −const makeDisabledResultAtom = <Success, Error>() => | |
| 62 | − Atom.make(Result.initial<Success, Error>()); | |
| 63 | − | |
| 64 | −export 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 | − ); | |
| 32 | +export const createControlPlaneAtomClient = (options: ControlPlaneClientOptions) => | |
| 33 | + AtomHttpApi.Tag<unknown>()("@executor-v2/control-plane/AtomClient", { | |
| 34 | + api: ControlPlaneApi, | |
| 35 | + httpClient: FetchHttpClient.layer, | |
| 36 | + baseUrl: options.baseUrl, | |
| 37 | + }); | |
| 132 | 38 | |
| 133 | − return { | |
| 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 | −}; | |
| 39 | +export type ControlPlaneAtomClient = ReturnType<typeof createControlPlaneAtomClient>; | |
| @@ -35,10 +35,7 @@ export { · 34 unmodified lines | ||
| 35 | 35 | export { |
| 36 | 36 | createControlPlaneAtomClient, |
| 37 | 37 | makeControlPlaneClient, |
| 38 | + type ControlPlaneAtomClient, | |
| 38 | 39 | type ControlPlaneClientError, |
| 39 | 40 | type ControlPlaneClientOptions, |
| 40 | − type ControlPlaneReadOptions, | |
| 41 | − type ListSourcesRequest, | |
| 42 | − type RemoveSourceRequest, | |
| 43 | − type UpsertSourceRequest, | |
| 44 | 41 | } from "./client"; |
| @@ -4,6 +4,7 @@ · 3 unmodified lines | ||
| 4 | 4 | "module": "ESNext", |
| 5 | 5 | "moduleResolution": "Bundler", |
| 6 | 6 | "moduleDetection": "force", |
| 7 | + "jsx": "preserve", | |
| 7 | 8 | "strict": true, |
| 8 | 9 | "noEmit": true, |
| 9 | 10 | "verbatimModuleSyntax": true, |
| @@ -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 | +} | |