Enable Stripe checkout and portal flows in the billing UI, and harden OpenAPI type extraction plus cache invalidation so tool hints remain accurate on real-world specs.
Unified · Split
| @@ -13,6 +13,14 @@ CONVEX_SITE_URL=https://your-deployment.convex.site · 12 unmodified lines | ||
| 13 | 13 | # WORKOS_COOKIE_PASSWORD=<32+ char random string, e.g. openssl rand -base64 32> |
| 14 | 14 | # WORKOS_WEBHOOK_SECRET=... |
| 15 | 15 | |
| 16 | +# ── Stripe Billing (optional — required for checkout/portal) ── | |
| 17 | +# STRIPE_SECRET_KEY=sk_test_... | |
| 18 | +# STRIPE_WEBHOOK_SECRET=whsec_... | |
| 19 | +# STRIPE_PRICE_ID=price_... | |
| 20 | +# BILLING_SUCCESS_URL=http://localhost:3000/organization?tab=billing&success=true | |
| 21 | +# BILLING_CANCEL_URL=http://localhost:3000/organization?tab=billing&canceled=true | |
| 22 | +# BILLING_RETURN_URL=http://localhost:3000/organization?tab=billing | |
| 23 | + | |
| 16 | 24 | # ── Discord bot (optional — omit to skip bot startup) ── |
| 17 | 25 | # DISCORD_BOT_TOKEN=... |
| 18 | 26 | |
| @@ -106,6 +106,7 @@ · 105 unmodified lines | ||
| 106 | 106 | "radix-ui": "^1.4.3", |
| 107 | 107 | "react": "19.2.3", |
| 108 | 108 | "react-dom": "19.2.3", |
| 109 | + "shiki": "^3.22.0", | |
| 109 | 110 | "sonner": "^2.0.7", |
| 110 | 111 | "streamdown": "^2.1.0", |
| 111 | 112 | "tailwind-merge": "^3.4.0", |
| @@ -6,6 +6,7 @@ const nextConfig: NextConfig = { · 5 unmodified lines | ||
| 6 | 6 | // This lets us keep a single root .env without NEXT_PUBLIC_ prefixes. |
| 7 | 7 | NEXT_PUBLIC_CONVEX_URL: process.env.CONVEX_URL, |
| 8 | 8 | NEXT_PUBLIC_WORKOS_CLIENT_ID: process.env.WORKOS_CLIENT_ID, |
| 9 | + NEXT_PUBLIC_STRIPE_PRICE_ID: process.env.STRIPE_PRICE_ID, | |
| 9 | 10 | }, |
| 10 | 11 | }; |
| 11 | 12 | |
| @@ -28,6 +28,7 @@ · 27 unmodified lines | ||
| 28 | 28 | "radix-ui": "^1.4.3", |
| 29 | 29 | "react": "19.2.3", |
| 30 | 30 | "react-dom": "19.2.3", |
| 31 | + "shiki": "^3.22.0", | |
| 31 | 32 | "sonner": "^2.0.7", |
| 32 | 33 | "streamdown": "^2.1.0", |
| 33 | 34 | "tailwind-merge": "^3.4.0", |
| @@ -1,20 +1,59 @@ | ||
| 1 | 1 | "use client"; |
| 2 | 2 | |
| 3 | 3 | import { useMemo, useState } from "react"; |
| 4 | +import { useSearchParams } from "next/navigation"; | |
| 5 | +import { useAction, useMutation, useQuery } from "convex/react"; | |
| 4 | 6 | import { AlertTriangle, BadgeCheck, CreditCard, RefreshCcw } from "lucide-react"; |
| 5 | 7 | import { PageHeader } from "@/components/page-header"; |
| 8 | +import { Badge } from "@/components/ui/badge"; | |
| 6 | 9 | import { Button } from "@/components/ui/button"; |
| 7 | 10 | import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; |
| 11 | +import { Input } from "@/components/ui/input"; | |
| 12 | +import { convexApi } from "@/lib/convex-api"; | |
| 8 | 13 | import { useSession } from "@/lib/session-context"; |
| 9 | 14 | |
| 10 | 15 | interface BillingViewProps { |
| 11 | 16 | showHeader?: boolean; |
| 12 | 17 | } |
| 13 | 18 | |
| 19 | +type BillingSummary = { | |
| 20 | + customer: { | |
| 21 | + stripeCustomerId: string; | |
| 22 | + } | null; | |
| 23 | + subscription: { | |
| 24 | + stripeSubscriptionId: string; | |
| 25 | + stripePriceId: string; | |
| 26 | + status: string; | |
| 27 | + currentPeriodEnd: number | null; | |
| 28 | + cancelAtPeriodEnd: boolean; | |
| 29 | + } | null; | |
| 30 | + seats: { | |
| 31 | + billableMembers: number; | |
| 32 | + desiredSeats: number; | |
| 33 | + lastAppliedSeats: number | null; | |
| 34 | + }; | |
| 35 | + sync: { | |
| 36 | + status: "ok" | "error" | "pending"; | |
| 37 | + lastSyncAt: number | null; | |
| 38 | + error: string | null; | |
| 39 | + }; | |
| 40 | +}; | |
| 41 | + | |
| 42 | +function formatTimestamp(value: number | null | undefined): string { | |
| 43 | + if (!value || !Number.isFinite(value)) { | |
| 44 | + return "-"; | |
| 45 | + } | |
| 46 | + | |
| 47 | + const millis = value > 1_000_000_000_000 ? value : value * 1_000; | |
| 48 | + return new Date(millis).toLocaleString(); | |
| 49 | +} | |
| 50 | + | |
| 14 | 51 | export function BillingView({ showHeader = true }: BillingViewProps) { |
| 15 | − const { context, organizations, workspaces } = useSession(); | |
| 16 | − const [actionState, setActionState] = useState<"idle" | "running" | "error">("idle"); | |
| 52 | + const { context, organizations, organizationsLoading, workspaces } = useSession(); | |
| 53 | + const searchParams = useSearchParams(); | |
| 54 | + const [actionState, setActionState] = useState<"idle" | "running" | "success" | "error">("idle"); | |
| 17 | 55 | const [actionMessage, setActionMessage] = useState<string | null>(null); |
| 56 | + const [priceId, setPriceId] = useState(() => process.env.NEXT_PUBLIC_STRIPE_PRICE_ID ?? ""); | |
| 18 | 57 | |
| 19 | 58 | const derivedOrganizationId = context |
| 20 | 59 | ? workspaces.find((workspace) => workspace.id === context.workspaceId)?.organizationId ?? null |
| @@ -25,13 +64,98 @@ export function BillingView({ showHeader = true }: BillingViewProps) { · 4 unmodified lines | ||
| 25 | 64 | () => organizations.find((organization) => organization.id === effectiveOrganizationId) ?? null, |
| 26 | 65 | [organizations, effectiveOrganizationId], |
| 27 | 66 | ); |
| 67 | + | |
| 28 | 68 | const canManageBilling = activeOrganization |
| 29 | − ? ["owner", "billing_admin"].includes(activeOrganization.role) | |
| 69 | + ? ["owner", "admin", "billing_admin"].includes(activeOrganization.role) | |
| 30 | 70 | : false; |
| 31 | 71 | |
| 32 | − const notReady = () => { | |
| 33 | − setActionState("error"); | |
| 34 | − setActionMessage("Billing actions are waiting on backend endpoints."); | |
| 72 | + const summary = useQuery( | |
| 73 | + convexApi.billing.getSummary, | |
| 74 | + effectiveOrganizationId | |
| 75 | + ? { | |
| 76 | + organizationId: effectiveOrganizationId, | |
| 77 | + sessionId: context?.sessionId ?? undefined, | |
| 78 | + } | |
| 79 | + : "skip", | |
| 80 | + ) as BillingSummary | undefined; | |
| 81 | + | |
| 82 | + const createSubscriptionCheckout = useAction(convexApi.billing.createSubscriptionCheckout); | |
| 83 | + const createCustomerPortal = useAction(convexApi.billing.createCustomerPortal); | |
| 84 | + const retrySeatSync = useMutation(convexApi.billing.retrySeatSync); | |
| 85 | + | |
| 86 | + const checkoutSuccess = searchParams.get("success") === "true"; | |
| 87 | + const checkoutCanceled = searchParams.get("canceled") === "true"; | |
| 88 | + | |
| 89 | + const handleStartCheckout = async () => { | |
| 90 | + if (!effectiveOrganizationId || !canManageBilling) { | |
| 91 | + return; | |
| 92 | + } | |
| 93 | + | |
| 94 | + if (!priceId.trim()) { | |
| 95 | + setActionState("error"); | |
| 96 | + setActionMessage("Set a Stripe price ID before starting checkout."); | |
| 97 | + return; | |
| 98 | + } | |
| 99 | + | |
| 100 | + setActionState("running"); | |
| 101 | + setActionMessage(null); | |
| 102 | + try { | |
| 103 | + const session = await createSubscriptionCheckout({ | |
| 104 | + organizationId: effectiveOrganizationId, | |
| 105 | + priceId: priceId.trim(), | |
| 106 | + sessionId: context?.sessionId ?? undefined, | |
| 107 | + }); | |
| 108 | + | |
| 109 | + if (session.url) { | |
| 110 | + window.location.assign(session.url); | |
| 111 | + return; | |
| 112 | + } | |
| 113 | + | |
| 114 | + setActionState("success"); | |
| 115 | + setActionMessage("Checkout session created."); | |
| 116 | + } catch (error) { | |
| 117 | + setActionState("error"); | |
| 118 | + setActionMessage(error instanceof Error ? error.message : "Failed to start checkout"); | |
| 119 | + } | |
| 120 | + }; | |
| 121 | + | |
| 122 | + const handleOpenPortal = async () => { | |
| 123 | + if (!effectiveOrganizationId || !canManageBilling) { | |
| 124 | + return; | |
| 125 | + } | |
| 126 | + | |
| 127 | + setActionState("running"); | |
| 128 | + setActionMessage(null); | |
| 129 | + try { | |
| 130 | + const result = await createCustomerPortal({ | |
| 131 | + organizationId: effectiveOrganizationId, | |
| 132 | + sessionId: context?.sessionId ?? undefined, | |
| 133 | + }); | |
| 134 | + window.location.assign(result.url); | |
| 135 | + } catch (error) { | |
| 136 | + setActionState("error"); | |
| 137 | + setActionMessage(error instanceof Error ? error.message : "Failed to open billing portal"); | |
| 138 | + } | |
| 139 | + }; | |
| 140 | + | |
| 141 | + const handleRetrySync = async () => { | |
| 142 | + if (!effectiveOrganizationId || !canManageBilling) { | |
| 143 | + return; | |
| 144 | + } | |
| 145 | + | |
| 146 | + setActionState("running"); | |
| 147 | + setActionMessage(null); | |
| 148 | + try { | |
| 149 | + await retrySeatSync({ | |
| 150 | + organizationId: effectiveOrganizationId, | |
| 151 | + sessionId: context?.sessionId ?? undefined, | |
| 152 | + }); | |
| 153 | + setActionState("success"); | |
| 154 | + setActionMessage("Seat sync queued."); | |
| 155 | + } catch (error) { | |
| 156 | + setActionState("error"); | |
| 157 | + setActionMessage(error instanceof Error ? error.message : "Failed to queue seat sync"); | |
| 158 | + } | |
| 35 | 159 | }; |
| 36 | 160 | |
| 37 | 161 | if (!effectiveOrganizationId) { |
| @@ -51,41 +175,76 @@ export function BillingView({ showHeader = true }: BillingViewProps) { · 13 unmodified lines | ||
| 51 | 175 | <div className="space-y-6"> |
| 52 | 176 | {showHeader ? <PageHeader title="Billing" description="Manage subscription, seats, and billing portal access" /> : null} |
| 53 | 177 | |
| 54 | − <Card> | |
| 55 | − <CardContent className="p-6 text-sm text-muted-foreground"> | |
| 56 | − Billing backend wiring is still in progress. This screen is ready for `billing.getSummary`, | |
| 57 | − `billing.createSubscriptionCheckout`, `billing.createCustomerPortal`, and `billing.retrySeatSync`. | |
| 58 | − </CardContent> | |
| 59 | − </Card> | |
| 178 | + {checkoutSuccess ? ( | |
| 179 | + <Card> | |
| 180 | + <CardContent className="p-4 text-sm text-terminal-green"> | |
| 181 | + Checkout completed. Billing updates may take a few seconds while Stripe webhooks process. | |
| 182 | + </CardContent> | |
| 183 | + </Card> | |
| 184 | + ) : null} | |
| 60 | 185 | |
| 61 | − <Card> | |
| 62 | − <CardContent className="p-4 text-sm text-muted-foreground flex items-center gap-2"> | |
| 63 | − <RefreshCcw className="h-4 w-4" /> | |
| 64 | − Billing sync pending. Numbers may be briefly stale during webhook processing. | |
| 65 | − </CardContent> | |
| 66 | − </Card> | |
| 186 | + {checkoutCanceled ? ( | |
| 187 | + <Card> | |
| 188 | + <CardContent className="p-4 text-sm text-muted-foreground"> | |
| 189 | + Checkout canceled. You can restart checkout at any time. | |
| 190 | + </CardContent> | |
| 191 | + </Card> | |
| 192 | + ) : null} | |
| 67 | 193 | |
| 68 | 194 | <Card> |
| 69 | − <CardContent className="p-4 text-sm text-destructive flex items-center justify-between gap-3"> | |
| 195 | + <CardContent className="p-4 text-sm text-muted-foreground flex items-center justify-between gap-3"> | |
| 70 | 196 | <span className="flex items-center gap-2"> |
| 71 | − <AlertTriangle className="h-4 w-4" /> | |
| 72 | − If seat sync fails, use retry once the backend endpoint is available. | |
| 197 | + <RefreshCcw className="h-4 w-4" /> | |
| 198 | + {summary === undefined | |
| 199 | + ? "Loading billing sync status..." | |
| 200 | + : summary.sync.status === "error" | |
| 201 | + ? "Seat sync needs attention." | |
| 202 | + : summary.sync.status === "ok" | |
| 203 | + ? "Seat sync healthy." | |
| 204 | + : "Seat sync pending. Numbers may be briefly stale during webhook processing."} | |
| 73 | 205 | </span> |
| 74 | − <Button variant="outline" size="sm" onClick={notReady} disabled={!canManageBilling || actionState === "running"}> | |
| 75 | − Retry sync | |
| 76 | − </Button> | |
| 206 | + <Badge variant="outline" className="text-[10px] font-mono uppercase tracking-wider"> | |
| 207 | + {summary?.sync.status ?? "pending"} | |
| 208 | + </Badge> | |
| 77 | 209 | </CardContent> |
| 78 | 210 | </Card> |
| 79 | 211 | |
| 212 | + {summary?.sync.error ? ( | |
| 213 | + <Card> | |
| 214 | + <CardContent className="p-4 text-sm text-destructive flex items-center justify-between gap-3"> | |
| 215 | + <span className="flex items-center gap-2"> | |
| 216 | + <AlertTriangle className="h-4 w-4" /> | |
| 217 | + {summary.sync.error} | |
| 218 | + </span> | |
| 219 | + <Button | |
| 220 | + variant="outline" | |
| 221 | + size="sm" | |
| 222 | + onClick={handleRetrySync} | |
| 223 | + disabled={!canManageBilling || actionState === "running"} | |
| 224 | + > | |
| 225 | + Retry sync | |
| 226 | + </Button> | |
| 227 | + </CardContent> | |
| 228 | + </Card> | |
| 229 | + ) : null} | |
| 230 | + | |
| 231 | + {!canManageBilling && !organizationsLoading ? ( | |
| 232 | + <Card> | |
| 233 | + <CardContent className="p-4 text-sm text-muted-foreground"> | |
| 234 | + You need owner, admin, or billing admin access to start checkout and open the billing portal. | |
| 235 | + </CardContent> | |
| 236 | + </Card> | |
| 237 | + ) : null} | |
| 238 | + | |
| 80 | 239 | <div className="grid gap-4 md:grid-cols-3"> |
| 81 | 240 | <Card> |
| 82 | 241 | <CardHeader> |
| 83 | 242 | <CardTitle className="text-sm">Subscription</CardTitle> |
| 84 | 243 | </CardHeader> |
| 85 | 244 | <CardContent className="space-y-2 text-sm"> |
| 86 | − <p>Status: pending</p> | |
| 87 | − <p>Price: -</p> | |
| 88 | − <p>Renewal: -</p> | |
| 245 | + <p>Status: {summary?.subscription?.status ?? "none"}</p> | |
| 246 | + <p>Price: {summary?.subscription?.stripePriceId ?? "-"}</p> | |
| 247 | + <p>Renewal: {formatTimestamp(summary?.subscription?.currentPeriodEnd ?? null)}</p> | |
| 89 | 248 | </CardContent> |
| 90 | 249 | </Card> |
| 91 | 250 | |
| @@ -94,9 +253,9 @@ export function BillingView({ showHeader = true }: BillingViewProps) { · 2 unmodified lines | ||
| 94 | 253 | <CardTitle className="text-sm">Seats</CardTitle> |
| 95 | 254 | </CardHeader> |
| 96 | 255 | <CardContent className="space-y-2 text-sm"> |
| 97 | − <p>Billable members: -</p> | |
| 98 | − <p>Desired seats: -</p> | |
| 99 | − <p>Last applied: -</p> | |
| 256 | + <p>Billable members: {summary?.seats.billableMembers ?? "-"}</p> | |
| 257 | + <p>Desired seats: {summary?.seats.desiredSeats ?? "-"}</p> | |
| 258 | + <p>Last applied: {summary?.seats.lastAppliedSeats ?? "-"}</p> | |
| 100 | 259 | </CardContent> |
| 101 | 260 | </Card> |
| 102 | 261 | |
| @@ -105,11 +264,26 @@ export function BillingView({ showHeader = true }: BillingViewProps) { · 2 unmodified lines | ||
| 105 | 264 | <CardTitle className="text-sm">Actions</CardTitle> |
| 106 | 265 | </CardHeader> |
| 107 | 266 | <CardContent className="space-y-2"> |
| 108 | − <Button className="w-full" onClick={notReady} disabled={!canManageBilling || actionState === "running"}> | |
| 267 | + <Input | |
| 268 | + value={priceId} | |
| 269 | + onChange={(event) => setPriceId(event.target.value)} | |
| 270 | + placeholder="price_..." | |
| 271 | + disabled={!canManageBilling || actionState === "running"} | |
| 272 | + /> | |
| 273 | + <Button | |
| 274 | + className="w-full" | |
| 275 | + onClick={handleStartCheckout} | |
| 276 | + disabled={!canManageBilling || actionState === "running"} | |
| 277 | + > | |
| 109 | 278 | <CreditCard className="mr-2 h-4 w-4" /> |
| 110 | 279 | Start checkout |
| 111 | 280 | </Button> |
| 112 | − <Button variant="outline" className="w-full" onClick={notReady} disabled={!canManageBilling || actionState === "running"}> | |
| 281 | + <Button | |
| 282 | + variant="outline" | |
| 283 | + className="w-full" | |
| 284 | + onClick={handleOpenPortal} | |
| 285 | + disabled={!canManageBilling || actionState === "running"} | |
| 286 | + > | |
| 113 | 287 | <BadgeCheck className="mr-2 h-4 w-4" /> |
| 114 | 288 | Manage billing portal |
| 115 | 289 | </Button> |
| @@ -117,8 +291,23 @@ export function BillingView({ showHeader = true }: BillingViewProps) { · 1 unmodified line | ||
| 117 | 291 | </Card> |
| 118 | 292 | </div> |
| 119 | 293 | |
| 294 | + <Card> | |
| 295 | + <CardContent className="p-4 text-sm text-muted-foreground flex items-center gap-2"> | |
| 296 | + <BadgeCheck className="h-4 w-4" /> | |
| 297 | + Customer: {summary?.customer?.stripeCustomerId ?? "No Stripe customer linked"} | |
| 298 | + </CardContent> | |
| 299 | + </Card> | |
| 300 | + | |
| 120 | 301 | {actionMessage ? ( |
| 121 | − <p className={actionState === "error" ? "text-sm text-destructive" : "text-sm text-muted-foreground"}> | |
| 302 | + <p | |
| 303 | + className={ | |
| 304 | + actionState === "error" | |
| 305 | + ? "text-sm text-destructive" | |
| 306 | + : actionState === "success" | |
| 307 | + ? "text-sm text-terminal-green" | |
| 308 | + : "text-sm text-muted-foreground" | |
| 309 | + } | |
| 310 | + > | |
| 122 | 311 | {actionMessage} |
| 123 | 312 | </p> |
| 124 | 313 | ) : null} |
| @@ -3,6 +3,7 @@ · 2 unmodified lines | ||
| 3 | 3 | import { useRef, useEffect } from "react"; |
| 4 | 4 | import Editor, { type OnMount, type BeforeMount, type Monaco } from "@monaco-editor/react"; |
| 5 | 5 | import { Loader2 } from "lucide-react"; |
| 6 | +import { useTheme } from "next-themes"; | |
| 6 | 7 | import type { ToolDescriptor } from "@/lib/types"; |
| 7 | 8 | import { cn } from "@/lib/utils"; |
| 8 | 9 | |
| @@ -34,7 +35,7 @@ function buildTree(tools: ToolDescriptor[]): NamespaceNode { · 25 unmodified lines | ||
| 34 | 35 | return root; |
| 35 | 36 | } |
| 36 | 37 | |
| 37 | −function emitToolMethod(tool: ToolDescriptor): string { | |
| 38 | +function emitToolMethod(tool: ToolDescriptor, dtsSources: Set<string>): string { | |
| 38 | 39 | const funcName = tool.path.split(".").pop()!; |
| 39 | 40 | const approvalNote = |
| 40 | 41 | tool.approval === "required" |
| @@ -44,8 +45,10 @@ function emitToolMethod(tool: ToolDescriptor): string { · 3 unmodified lines | ||
| 44 | 45 | ? `${tool.description}${approvalNote}` |
| 45 | 46 | : approvalNote || "Call this tool."; |
| 46 | 47 | |
| 47 | − // For OpenAPI tools with operationId, use indexed access types for precise IntelliSense | |
| 48 | − if (tool.operationId) { | |
| 48 | + // For OpenAPI tools with operationId, use indexed access types only when | |
| 49 | + // this source has a loaded .d.ts block. | |
| 50 | + const hasSourceDts = Boolean(tool.source && dtsSources.has(tool.source)); | |
| 51 | + if (tool.operationId && hasSourceDts) { | |
| 49 | 52 | const opKey = JSON.stringify(tool.operationId); |
| 50 | 53 | return ` /** |
| 51 | 54 | * ${desc} |
| @@ -72,11 +75,12 @@ function emitToolMethod(tool: ToolDescriptor): string { · 20 unmodified lines | ||
| 72 | 75 | function emitNamespaceInterface( |
| 73 | 76 | name: string, |
| 74 | 77 | node: NamespaceNode, |
| 78 | + dtsSources: Set<string>, | |
| 75 | 79 | out: string[], |
| 76 | 80 | ): void { |
| 77 | 81 | // First, recursively emit child namespace interfaces |
| 78 | 82 | for (const [childName, childNode] of node.children) { |
| 79 | − emitNamespaceInterface(`${name}_${childName}`, childNode, out); | |
| 83 | + emitNamespaceInterface(`${name}_${childName}`, childNode, dtsSources, out); | |
| 80 | 84 | } |
| 81 | 85 | |
| 82 | 86 | // Build this namespace's interface |
| @@ -92,7 +96,7 @@ function emitNamespaceInterface( · 9 unmodified lines | ||
| 92 | 96 | |
| 93 | 97 | // Add tool methods |
| 94 | 98 | for (const tool of node.tools) { |
| 95 | − members.push(emitToolMethod(tool)); | |
| 99 | + members.push(emitToolMethod(tool, dtsSources)); | |
| 96 | 100 | } |
| 97 | 101 | |
| 98 | 102 | out.push(`interface ToolNS_${name} {\n${members.join("\n\n")}\n}`); |
| @@ -119,11 +123,12 @@ type ToolOutput<Op> = · 20 unmodified lines | ||
| 119 | 123 | Op extends { responses: { 200: { content: { "application/json": infer R } } } } ? R : |
| 120 | 124 | Op extends { responses: { 201: { content: { "application/json": infer R } } } } ? R : |
| 121 | 125 | Op extends { responses: { 202: { content: { "application/json": infer R } } } } ? R : |
| 122 | − Op extends { responses: { 204: { content: never } } } ? void : | |
| 126 | + Op extends { responses: { 204: unknown } } ? void : | |
| 127 | + Op extends { responses: { 205: unknown } } ? void : | |
| 123 | 128 | unknown; |
| 124 | 129 | `; |
| 125 | 130 | |
| 126 | −function generateToolsDts(tools: ToolDescriptor[]): string { | |
| 131 | +function generateToolsDts(tools: ToolDescriptor[], dtsSources: Set<string>): string { | |
| 127 | 132 | const root = buildTree(tools); |
| 128 | 133 | |
| 129 | 134 | // Legacy compat: collect schemaTypes from tools that use the old format |
| @@ -141,7 +146,7 @@ function generateToolsDts(tools: ToolDescriptor[]): string { · 11 unmodified lines | ||
| 141 | 146 | |
| 142 | 147 | // Emit all namespace interfaces recursively |
| 143 | 148 | for (const [name, node] of root.children) { |
| 144 | − emitNamespaceInterface(name, node, interfaces); | |
| 149 | + emitNamespaceInterface(name, node, dtsSources, interfaces); | |
| 145 | 150 | } |
| 146 | 151 | |
| 147 | 152 | // Build root ToolsProxy interface |
| @@ -154,7 +159,7 @@ function generateToolsDts(tools: ToolDescriptor[]): string { · 6 unmodified lines | ||
| 154 | 159 | |
| 155 | 160 | // Root-level tools (rare but possible) |
| 156 | 161 | for (const tool of root.tools) { |
| 157 | − rootMembers.push(emitToolMethod(tool)); | |
| 162 | + rootMembers.push(emitToolMethod(tool, dtsSources)); | |
| 158 | 163 | } |
| 159 | 164 | |
| 160 | 165 | let dts = ` |
| @@ -241,6 +246,7 @@ export function CodeEditor({ · 80 unmodified lines | ||
| 241 | 246 | className, |
| 242 | 247 | height = "400px", |
| 243 | 248 | }: CodeEditorProps) { |
| 249 | + const { resolvedTheme } = useTheme(); | |
| 244 | 250 | const editorRef = useRef<Parameters<OnMount>[0] | null>(null); |
| 245 | 251 | const monacoRef = useRef<Monaco | null>(null); |
| 246 | 252 | const envLibDisposable = useRef<{ dispose: () => void } | null>(null); |
| @@ -248,6 +254,7 @@ export function CodeEditor({ · 1 unmodified line | ||
| 248 | 254 | const dtsLibDisposables = useRef<{ dispose: () => void }[]>([]); |
| 249 | 255 | const toolsLibVersion = useRef(0); |
| 250 | 256 | const fetchedDtsUrls = useRef<string>(""); |
| 257 | + const dtsSources = new Set(Object.keys(dtsUrls ?? {})); | |
| 251 | 258 | |
| 252 | 259 | // Fetch and register .d.ts blobs from OpenAPI sources |
| 253 | 260 | useEffect(() => { |
| @@ -318,7 +325,7 @@ export function CodeEditor({ · 64 unmodified lines | ||
| 318 | 325 | // Dispose previous tool type declarations |
| 319 | 326 | toolsLibDisposable.current?.dispose(); |
| 320 | 327 | |
| 321 | − const dts = generateToolsDts(tools); | |
| 328 | + const dts = generateToolsDts(tools, dtsSources); | |
| 322 | 329 | |
| 323 | 330 | // Use a versioned filename — disposing + re-adding the same filename |
| 324 | 331 | // can cause the TS worker to serve stale completions from its cache. |
| @@ -327,7 +334,7 @@ export function CodeEditor({ · 2 unmodified lines | ||
| 327 | 334 | dts, |
| 328 | 335 | `file:///node_modules/@types/executor-tools/v${version}.d.ts`, |
| 329 | 336 | ); |
| 330 | − }, [tools]); | |
| 337 | + }, [tools, dtsUrls]); | |
| 331 | 338 | |
| 332 | 339 | // Avoid transient semantic errors while tool metadata is still loading. |
| 333 | 340 | useEffect(() => { |
| @@ -382,14 +389,62 @@ export function CodeEditor({ · 48 unmodified lines | ||
| 382 | 389 | // Add initial tool type declarations |
| 383 | 390 | // (will be replaced by useEffect when tools load from the API) |
| 384 | 391 | toolsLibDisposable.current?.dispose(); |
| 385 | − const dts = generateToolsDts(tools); | |
| 392 | + const dts = generateToolsDts(tools, dtsSources); | |
| 386 | 393 | const version = ++toolsLibVersion.current; |
| 387 | 394 | toolsLibDisposable.current = ts.javascriptDefaults.addExtraLib( |
| 388 | 395 | dts, |
| 389 | 396 | `file:///node_modules/@types/executor-tools/v${version}.d.ts`, |
| 390 | 397 | ); |
| 391 | 398 | |
| 392 | − // Define the dark theme matching our UI | |
| 399 | + // Define themes that track the app's light/dark mode. | |
| 400 | + monaco.editor.defineTheme("executor-light", { | |
| 401 | + base: "vs", | |
| 402 | + inherit: true, | |
| 403 | + rules: [ | |
| 404 | + { token: "comment", foreground: "7f8692", fontStyle: "italic" }, | |
| 405 | + { token: "keyword", foreground: "0f8a6a" }, | |
| 406 | + { token: "string", foreground: "a46822" }, | |
| 407 | + { token: "number", foreground: "a46822" }, | |
| 408 | + { token: "type", foreground: "0f8a6a" }, | |
| 409 | + { token: "function", foreground: "5c470f" }, | |
| 410 | + { token: "variable", foreground: "1f2430" }, | |
| 411 | + { token: "operator", foreground: "6f7785" }, | |
| 412 | + ], | |
| 413 | + colors: { | |
| 414 | + "editor.background": "#ffffff", | |
| 415 | + "editor.foreground": "#1f2430", | |
| 416 | + "editor.lineHighlightBackground": "#f4f7fb", | |
| 417 | + "editor.selectionBackground": "#c7def5", | |
| 418 | + "editor.inactiveSelectionBackground": "#dbe8f7", | |
| 419 | + "editorCursor.foreground": "#0f8a6a", | |
| 420 | + "editorLineNumber.foreground": "#9aa3b2", | |
| 421 | + "editorLineNumber.activeForeground": "#6f7785", | |
| 422 | + "editorIndentGuide.background": "#e3e8ef", | |
| 423 | + "editorIndentGuide.activeBackground": "#ccd5e2", | |
| 424 | + "editor.selectionHighlightBackground": "#c7def540", | |
| 425 | + "editorWidget.background": "#ffffff", | |
| 426 | + "editorWidget.border": "#d6dde8", | |
| 427 | + "editorSuggestWidget.background": "#ffffff", | |
| 428 | + "editorSuggestWidget.border": "#d6dde8", | |
| 429 | + "editorSuggestWidget.selectedBackground": "#cfe1f6", | |
| 430 | + "editorSuggestWidget.selectedForeground": "#111827", | |
| 431 | + "editorSuggestWidget.selectedIconForeground": "#0f8a6a", | |
| 432 | + "editorSuggestWidget.highlightForeground": "#0f8a6a", | |
| 433 | + "editorHoverWidget.background": "#ffffff", | |
| 434 | + "editorHoverWidget.border": "#d6dde8", | |
| 435 | + "list.focusBackground": "#cfe1f6", | |
| 436 | + "list.focusForeground": "#111827", | |
| 437 | + "list.highlightForeground": "#0f8a6a", | |
| 438 | + "input.background": "#ffffff", | |
| 439 | + "input.border": "#d6dde8", | |
| 440 | + "scrollbar.shadow": "#00000000", | |
| 441 | + "scrollbarSlider.background": "#c2cad880", | |
| 442 | + "scrollbarSlider.hoverBackground": "#a6afbe", | |
| 443 | + "scrollbarSlider.activeBackground": "#8f98a8", | |
| 444 | + "focusBorder": "#0f8a6a30", | |
| 445 | + }, | |
| 446 | + }); | |
| 447 | + | |
| 393 | 448 | monaco.editor.defineTheme("executor-dark", { |
| 394 | 449 | base: "vs-dark", |
| 395 | 450 | inherit: true, |
| @@ -434,6 +489,8 @@ export function CodeEditor({ · 38 unmodified lines | ||
| 434 | 489 | }); |
| 435 | 490 | }; |
| 436 | 491 | |
| 492 | + const monacoTheme = resolvedTheme === "light" ? "executor-light" : "executor-dark"; | |
| 493 | + | |
| 437 | 494 | const handleMount: OnMount = (editor) => { |
| 438 | 495 | editorRef.current = editor; |
| 439 | 496 | |
| @@ -460,7 +517,7 @@ export function CodeEditor({ · 20 unmodified lines | ||
| 460 | 517 | height={height} |
| 461 | 518 | language="javascript" |
| 462 | 519 | path="task.js" |
| 463 | − theme="executor-dark" | |
| 520 | + theme={monacoTheme} | |
| 464 | 521 | value={value} |
| 465 | 522 | onChange={(v) => onChange(v ?? "")} |
| 466 | 523 | beforeMount={handleBeforeMount} |
| @@ -524,7 +581,7 @@ export function CodeEditor({ · 57 unmodified lines | ||
| 524 | 581 | smoothScrolling: true, |
| 525 | 582 | }} |
| 526 | 583 | loading={ |
| 527 | − <div className="flex items-center justify-center h-full bg-[#0f1117] text-muted-foreground text-xs font-mono"> | |
| 584 | + <div className="flex h-full items-center justify-center bg-background text-xs font-mono text-muted-foreground"> | |
| 528 | 585 | Loading editor... |
| 529 | 586 | </div> |
| 530 | 587 | } |
| @@ -256,6 +256,93 @@ function CopyButton({ · 255 unmodified lines | ||
| 256 | 256 | ); |
| 257 | 257 | } |
| 258 | 258 | |
| 259 | +// ── Pretty-print a TS type literal for display ── | |
| 260 | +// Turns `{ owner: string; repo: string; ref?: string }` into a multi-line | |
| 261 | +// indented format. Handles nested objects/arrays. Pure string transform. | |
| 262 | + | |
| 263 | +function prettyType(raw: string): string { | |
| 264 | + const trimmed = raw.trim(); | |
| 265 | + // If it's already multi-line or short enough, leave it | |
| 266 | + if (trimmed.includes("\n") || trimmed.length < 50) return trimmed; | |
| 267 | + | |
| 268 | + let out = ""; | |
| 269 | + let indent = 0; | |
| 270 | + const TAB = " "; | |
| 271 | + let i = 0; | |
| 272 | + | |
| 273 | + while (i < trimmed.length) { | |
| 274 | + const ch = trimmed[i]; | |
| 275 | + | |
| 276 | + // Array type bracket — e.g. `string[]` or `}[]` — keep inline | |
| 277 | + if (ch === "[" && trimmed[i + 1] === "]") { | |
| 278 | + out += "[]"; | |
| 279 | + i += 2; | |
| 280 | + } else if (ch === "{") { | |
| 281 | + indent++; | |
| 282 | + out += ch + "\n" + TAB.repeat(indent); | |
| 283 | + i++; | |
| 284 | + // skip whitespace after brace | |
| 285 | + while (i < trimmed.length && trimmed[i] === " ") i++; | |
| 286 | + } else if (ch === "}") { | |
| 287 | + indent = Math.max(0, indent - 1); | |
| 288 | + // trim trailing whitespace on current line | |
| 289 | + out = out.replace(/\s+$/, ""); | |
| 290 | + out += "\n" + TAB.repeat(indent) + ch; | |
| 291 | + i++; | |
| 292 | + } else if (ch === ";" && trimmed[i + 1] === " ") { | |
| 293 | + // property separator — newline after semicolon | |
| 294 | + out += ";\n" + TAB.repeat(indent); | |
| 295 | + i += 2; // skip the semicolon + space | |
| 296 | + } else if (ch === "," && indent > 0 && trimmed[i + 1] === " ") { | |
| 297 | + out += ",\n" + TAB.repeat(indent); | |
| 298 | + i += 2; | |
| 299 | + } else { | |
| 300 | + out += ch; | |
| 301 | + i++; | |
| 302 | + } | |
| 303 | + } | |
| 304 | + | |
| 305 | + return out; | |
| 306 | +} | |
| 307 | + | |
| 308 | +// ── Formatted + highlighted type signature display ── | |
| 309 | + | |
| 310 | +function TypeSignature({ raw, label }: { raw: string; label: string }) { | |
| 311 | + const formatted = useMemo(() => prettyType(raw), [raw]); | |
| 312 | + const [highlightedHtml, setHighlightedHtml] = useState<string | null>(null); | |
| 313 | + | |
| 314 | + useEffect(() => { | |
| 315 | + let cancelled = false; | |
| 316 | + import("shiki").then(({ codeToHtml }) => | |
| 317 | + codeToHtml(formatted, { | |
| 318 | + lang: "typescript", | |
| 319 | + themes: { light: "github-light", dark: "github-dark" }, | |
| 320 | + }).then((html) => { | |
| 321 | + if (!cancelled) setHighlightedHtml(html); | |
| 322 | + }), | |
| 323 | + ); | |
| 324 | + return () => { cancelled = true; }; | |
| 325 | + }, [formatted]); | |
| 326 | + | |
| 327 | + return ( | |
| 328 | + <div> | |
| 329 | + <p className="text-[9px] font-mono uppercase tracking-wider text-muted-foreground/50 mb-1"> | |
| 330 | + {label} | |
| 331 | + </p> | |
| 332 | + {highlightedHtml ? ( | |
| 333 | + <div | |
| 334 | + className="type-signature text-[11px] leading-relaxed bg-muted/40 border border-border/40 rounded-md px-2.5 py-2 overflow-x-auto [&_pre]:!bg-transparent [&_pre]:!m-0 [&_pre]:!p-0 [&_code]:!text-[11px] [&_code]:!leading-relaxed" | |
| 335 | + dangerouslySetInnerHTML={{ __html: highlightedHtml }} | |
| 336 | + /> | |
| 337 | + ) : ( | |
| 338 | + <pre className="text-[11px] font-mono leading-relaxed text-foreground/80 bg-muted/40 border border-border/40 rounded-md px-2.5 py-2 overflow-x-auto whitespace-pre"> | |
| 339 | + {formatted} | |
| 340 | + </pre> | |
| 341 | + )} | |
| 342 | + </div> | |
| 343 | + ); | |
| 344 | +} | |
| 345 | + | |
| 259 | 346 | // ── Inline tool detail (shown when tool row is expanded) ── |
| 260 | 347 | |
| 261 | 348 | function ToolDetail({ tool, depth }: { tool: ToolDescriptor; depth: number }) { |
| @@ -289,26 +376,12 @@ function ToolDetail({ tool, depth }: { tool: ToolDescriptor; depth: number }) { · 27 unmodified lines | ||
| 289 | 376 | )} |
| 290 | 377 | </div> |
| 291 | 378 | |
| 292 | − {/* Args / Returns rendered as highlighted TypeScript via Streamdown */} | |
| 379 | + {/* Args / Returns — pretty-printed type signatures */} | |
| 293 | 380 | {tool.argsType && ( |
| 294 | − <div className="tool-description"> | |
| 295 | − <p className="text-[9px] font-mono uppercase tracking-wider text-muted-foreground/50 mb-1"> | |
| 296 | − Arguments | |
| 297 | − </p> | |
| 298 | − <Streamdown plugins={{ code: codePlugin }}> | |
| 299 | − {"```ts\n" + tool.argsType + "\n```"} | |
| 300 | − </Streamdown> | |
| 301 | − </div> | |
| 381 | + <TypeSignature raw={tool.argsType} label="Arguments" /> | |
| 302 | 382 | )} |
| 303 | 383 | {tool.returnsType && ( |
| 304 | − <div className="tool-description"> | |
| 305 | − <p className="text-[9px] font-mono uppercase tracking-wider text-muted-foreground/50 mb-1"> | |
| 306 | − Returns | |
| 307 | − </p> | |
| 308 | − <Streamdown plugins={{ code: codePlugin }}> | |
| 309 | − {"```ts\n" + tool.returnsType + "\n```"} | |
| 310 | − </Streamdown> | |
| 311 | − </div> | |
| 384 | + <TypeSignature raw={tool.returnsType} label="Returns" /> | |
| 312 | 385 | )} |
| 313 | 386 | </div> |
| 314 | 387 | ); |
| @@ -207,18 +207,46 @@ function faviconForUrl(url: string | undefined | null): string | null { · 206 unmodified lines | ||
| 207 | 207 | } |
| 208 | 208 | } |
| 209 | 209 | |
| 210 | +function isTemplateHostname(hostname: string): boolean { | |
| 211 | + const lower = hostname.toLowerCase(); | |
| 212 | + return ( | |
| 213 | + lower === "localhost" || | |
| 214 | + lower === "example.com" || | |
| 215 | + lower.endsWith(".example.com") || | |
| 216 | + lower.startsWith("your-domain") || | |
| 217 | + lower.includes(".your-domain") | |
| 218 | + ); | |
| 219 | +} | |
| 220 | + | |
| 221 | +function pickFaviconTarget(urls: Array<string | undefined | null>): string | null { | |
| 222 | + const parsed = urls | |
| 223 | + .filter((value): value is string => Boolean(value)) | |
| 224 | + .map((value) => { | |
| 225 | + try { | |
| 226 | + return { value, hostname: new URL(value).hostname }; | |
| 227 | + } catch { | |
| 228 | + return null; | |
| 229 | + } | |
| 230 | + }) | |
| 231 | + .filter((entry): entry is { value: string; hostname: string } => entry !== null); | |
| 232 | + | |
| 233 | + const preferred = parsed.find((entry) => !isTemplateHostname(entry.hostname)); | |
| 234 | + if (preferred) return preferred.value; | |
| 235 | + return parsed[0]?.value ?? null; | |
| 236 | +} | |
| 237 | + | |
| 210 | 238 | function getFaviconUrl(preset: ApiPreset): string | null { |
| 211 | − return faviconForUrl(preset.baseUrl ?? preset.endpoint ?? preset.url); | |
| 239 | + return faviconForUrl(pickFaviconTarget([preset.baseUrl, preset.endpoint, preset.url, preset.spec])); | |
| 212 | 240 | } |
| 213 | 241 | |
| 214 | 242 | function getSourceFavicon(source: ToolSourceRecord): string | null { |
| 215 | − const url = | |
| 243 | + const urls = | |
| 216 | 244 | source.type === "mcp" |
| 217 | − ? (source.config.url as string) | |
| 245 | + ? [source.config.url as string] | |
| 218 | 246 | : source.type === "graphql" |
| 219 | − ? (source.config.endpoint as string) | |
| 220 | − : (source.config.baseUrl as string) ?? (source.config.spec as string); | |
| 221 | − return faviconForUrl(url); | |
| 247 | + ? [source.config.endpoint as string] | |
| 248 | + : [source.config.baseUrl as string, source.config.spec as string]; | |
| 249 | + return faviconForUrl(pickFaviconTarget(urls)); | |
| 222 | 250 | } |
| 223 | 251 | |
| 224 | 252 | function sourceKeyForSource(source: ToolSourceRecord): string | null { |
| @@ -1,3 +1,6 @@ | ||
| 1 | 1 | { |
| 2 | − "functions": "convex" | |
| 2 | + "functions": "convex", | |
| 3 | + "codegen": { | |
| 4 | + "staticApi": true | |
| 5 | + } | |
| 3 | 6 | } |
| @@ -5,15 +5,11 @@ · 4 unmodified lines | ||
| 5 | 5 | * THIS CODE IS AUTOMATICALLY GENERATED. |
| 6 | 6 | * |
| 7 | 7 | * To regenerate, run `npx convex dev`. |
| 8 | − * | |
| 9 | − * NOTE: This file has been post-processed by scripts/postcodegen.ts | |
| 10 | − * to replace recursive ApiFromModules/FilterApi types with explicit | |
| 11 | − * declarations, avoiding TS2589 depth errors. | |
| 12 | − * | |
| 13 | 8 | * @module |
| 14 | 9 | */ |
| 15 | 10 | |
| 16 | 11 | import type { FunctionReference } from "convex/server"; |
| 12 | +import type { GenericId as Id } from "convex/values"; | |
| 17 | 13 | |
| 18 | 14 | /** |
| 19 | 15 | * A utility for referencing Convex functions in your app's public API. |
| @@ -25,75 +21,413 @@ import type { FunctionReference } from "convex/server"; · 5 unmodified lines | ||
| 25 | 21 | */ |
| 26 | 22 | export declare const api: { |
| 27 | 23 | app: { |
| 28 | − getClientConfig: FunctionReference<"query", "public", any, any>; | |
| 29 | − getCurrentAccount: FunctionReference<"query", "public", any, any>; | |
| 24 | + getClientConfig: FunctionReference<"query", "public", {}, any>; | |
| 25 | + getCurrentAccount: FunctionReference< | |
| 26 | + "query", | |
| 27 | + "public", | |
| 28 | + { sessionId?: string }, | |
| 29 | + any | |
| 30 | + >; | |
| 30 | 31 | }; |
| 31 | 32 | auth: { |
| 32 | − bootstrapCurrentWorkosAccount: FunctionReference<"mutation", "public", any, any>; | |
| 33 | + bootstrapCurrentWorkosAccount: FunctionReference< | |
| 34 | + "mutation", | |
| 35 | + "public", | |
| 36 | + {}, | |
| 37 | + any | |
| 38 | + >; | |
| 33 | 39 | }; |
| 34 | 40 | billing: { |
| 35 | − getSummary: FunctionReference<"query", "public", any, any>; | |
| 36 | − createSubscriptionCheckout: FunctionReference<"action", "public", any, any>; | |
| 37 | − createCustomerPortal: FunctionReference<"action", "public", any, any>; | |
| 38 | − retrySeatSync: FunctionReference<"mutation", "public", any, any>; | |
| 41 | + createCustomerPortal: FunctionReference< | |
| 42 | + "action", | |
| 43 | + "public", | |
| 44 | + { | |
| 45 | + organizationId: Id<"organizations">; | |
| 46 | + returnUrl?: string; | |
| 47 | + sessionId?: string; | |
| 48 | + }, | |
| 49 | + { url: string } | |
| 50 | + >; | |
| 51 | + createSubscriptionCheckout: FunctionReference< | |
| 52 | + "action", | |
| 53 | + "public", | |
| 54 | + { | |
| 55 | + cancelUrl?: string; | |
| 56 | + organizationId: Id<"organizations">; | |
| 57 | + priceId: string; | |
| 58 | + sessionId?: string; | |
| 59 | + successUrl?: string; | |
| 60 | + }, | |
| 61 | + any | |
| 62 | + >; | |
| 63 | + getSummary: FunctionReference< | |
| 64 | + "query", | |
| 65 | + "public", | |
| 66 | + { organizationId: Id<"organizations">; sessionId?: string }, | |
| 67 | + any | |
| 68 | + >; | |
| 69 | + retrySeatSync: FunctionReference< | |
| 70 | + "mutation", | |
| 71 | + "public", | |
| 72 | + { organizationId: Id<"organizations">; sessionId?: string }, | |
| 73 | + any | |
| 74 | + >; | |
| 39 | 75 | }; |
| 40 | 76 | credentialsNode: { |
| 41 | − upsertCredential: FunctionReference<"action", "public", any, any>; | |
| 77 | + upsertCredential: FunctionReference< | |
| 78 | + "action", | |
| 79 | + "public", | |
| 80 | + { | |
| 81 | + actorId?: string; | |
| 82 | + id?: string; | |
| 83 | + provider?: "managed" | "workos-vault"; | |
| 84 | + scope: "workspace" | "actor"; | |
| 85 | + secretJson: any; | |
| 86 | + sessionId?: string; | |
| 87 | + sourceKey: string; | |
| 88 | + workspaceId: string; | |
| 89 | + }, | |
| 90 | + any | |
| 91 | + >; | |
| 42 | 92 | }; |
| 43 | 93 | database: { |
| 44 | − createAgentTask: FunctionReference<"mutation", "public", any, any>; | |
| 45 | − getAgentTask: FunctionReference<"query", "public", any, any>; | |
| 46 | − updateAgentTask: FunctionReference<"mutation", "public", any, any>; | |
| 94 | + createAgentTask: FunctionReference< | |
| 95 | + "mutation", | |
| 96 | + "public", | |
| 97 | + { | |
| 98 | + actorId: string; | |
| 99 | + id: string; | |
| 100 | + prompt: string; | |
| 101 | + requesterId: string; | |
| 102 | + workspaceId: string; | |
| 103 | + }, | |
| 104 | + any | |
| 105 | + >; | |
| 106 | + getAgentTask: FunctionReference< | |
| 107 | + "query", | |
| 108 | + "public", | |
| 109 | + { agentTaskId: string }, | |
| 110 | + any | |
| 111 | + >; | |
| 112 | + updateAgentTask: FunctionReference< | |
| 113 | + "mutation", | |
| 114 | + "public", | |
| 115 | + { | |
| 116 | + agentTaskId: string; | |
| 117 | + codeRuns?: number; | |
| 118 | + error?: string; | |
| 119 | + resultText?: string; | |
| 120 | + status?: "running" | "completed" | "failed"; | |
| 121 | + }, | |
| 122 | + any | |
| 123 | + >; | |
| 47 | 124 | }; |
| 48 | 125 | executor: { |
| 49 | − createTask: FunctionReference<"mutation", "public", any, any>; | |
| 50 | − resolveApproval: FunctionReference<"mutation", "public", any, any>; | |
| 126 | + createTask: FunctionReference< | |
| 127 | + "mutation", | |
| 128 | + "public", | |
| 129 | + { | |
| 130 | + actorId?: string; | |
| 131 | + clientId?: string; | |
| 132 | + code: string; | |
| 133 | + metadata?: any; | |
| 134 | + runtimeId?: string; | |
| 135 | + sessionId?: string; | |
| 136 | + timeoutMs?: number; | |
| 137 | + workspaceId: string; | |
| 138 | + }, | |
| 139 | + any | |
| 140 | + >; | |
| 141 | + resolveApproval: FunctionReference< | |
| 142 | + "mutation", | |
| 143 | + "public", | |
| 144 | + { | |
| 145 | + approvalId: string; | |
| 146 | + decision: "approved" | "denied"; | |
| 147 | + reason?: string; | |
| 148 | + reviewerId?: string; | |
| 149 | + sessionId?: string; | |
| 150 | + workspaceId: string; | |
| 151 | + }, | |
| 152 | + any | |
| 153 | + >; | |
| 51 | 154 | }; |
| 52 | 155 | executorNode: { |
| 53 | − listTools: FunctionReference<"action", "public", any, any>; | |
| 54 | − listToolsWithWarnings: FunctionReference<"action", "public", any, any>; | |
| 156 | + listTools: FunctionReference< | |
| 157 | + "action", | |
| 158 | + "public", | |
| 159 | + { | |
| 160 | + actorId?: string; | |
| 161 | + clientId?: string; | |
| 162 | + sessionId?: string; | |
| 163 | + workspaceId: string; | |
| 164 | + }, | |
| 165 | + any | |
| 166 | + >; | |
| 167 | + listToolsWithWarnings: FunctionReference< | |
| 168 | + "action", | |
| 169 | + "public", | |
| 170 | + { | |
| 171 | + actorId?: string; | |
| 172 | + clientId?: string; | |
| 173 | + sessionId?: string; | |
| 174 | + workspaceId: string; | |
| 175 | + }, | |
| 176 | + any | |
| 177 | + >; | |
| 55 | 178 | }; |
| 56 | 179 | invites: { |
| 57 | − list: FunctionReference<"query", "public", any, any>; | |
| 58 | − create: FunctionReference<"mutation", "public", any, any>; | |
| 59 | − revoke: FunctionReference<"mutation", "public", any, any>; | |
| 180 | + create: FunctionReference< | |
| 181 | + "mutation", | |
| 182 | + "public", | |
| 183 | + { | |
| 184 | + email: string; | |
| 185 | + expiresInDays?: number; | |
| 186 | + organizationId: Id<"organizations">; | |
| 187 | + role: "owner" | "admin" | "member" | "billing_admin"; | |
| 188 | + sessionId?: string; | |
| 189 | + workspaceId?: Id<"workspaces">; | |
| 190 | + }, | |
| 191 | + any | |
| 192 | + >; | |
| 193 | + list: FunctionReference< | |
| 194 | + "query", | |
| 195 | + "public", | |
| 196 | + { organizationId: Id<"organizations">; sessionId?: string }, | |
| 197 | + any | |
| 198 | + >; | |
| 199 | + revoke: FunctionReference< | |
| 200 | + "mutation", | |
| 201 | + "public", | |
| 202 | + { | |
| 203 | + inviteId: Id<"invites">; | |
| 204 | + organizationId: Id<"organizations">; | |
| 205 | + sessionId?: string; | |
| 206 | + }, | |
| 207 | + any | |
| 208 | + >; | |
| 60 | 209 | }; |
| 61 | 210 | organizationMembers: { |
| 62 | − list: FunctionReference<"query", "public", any, any>; | |
| 63 | − updateRole: FunctionReference<"mutation", "public", any, any>; | |
| 64 | − updateBillable: FunctionReference<"mutation", "public", any, any>; | |
| 65 | − remove: FunctionReference<"mutation", "public", any, any>; | |
| 211 | + list: FunctionReference< | |
| 212 | + "query", | |
| 213 | + "public", | |
| 214 | + { organizationId: Id<"organizations">; sessionId?: string }, | |
| 215 | + any | |
| 216 | + >; | |
| 217 | + remove: FunctionReference< | |
| 218 | + "mutation", | |
| 219 | + "public", | |
| 220 | + { | |
| 221 | + accountId: Id<"accounts">; | |
| 222 | + organizationId: Id<"organizations">; | |
| 223 | + sessionId?: string; | |
| 224 | + }, | |
| 225 | + any | |
| 226 | + >; | |
| 227 | + updateBillable: FunctionReference< | |
| 228 | + "mutation", | |
| 229 | + "public", | |
| 230 | + { | |
| 231 | + accountId: Id<"accounts">; | |
| 232 | + billable: boolean; | |
| 233 | + organizationId: Id<"organizations">; | |
| 234 | + sessionId?: string; | |
| 235 | + }, | |
| 236 | + any | |
| 237 | + >; | |
| 238 | + updateRole: FunctionReference< | |
| 239 | + "mutation", | |
| 240 | + "public", | |
| 241 | + { | |
| 242 | + accountId: Id<"accounts">; | |
| 243 | + organizationId: Id<"organizations">; | |
| 244 | + role: "owner" | "admin" | "member" | "billing_admin"; | |
| 245 | + sessionId?: string; | |
| 246 | + }, | |
| 247 | + any | |
| 248 | + >; | |
| 66 | 249 | }; |
| 67 | 250 | organizations: { |
| 68 | − create: FunctionReference<"mutation", "public", any, any>; | |
| 69 | − listMine: FunctionReference<"query", "public", any, any>; | |
| 70 | − getNavigationState: FunctionReference<"query", "public", any, any>; | |
| 71 | − getOrganizationAccess: FunctionReference<"query", "public", any, any>; | |
| 72 | − resolveWorkosOrganizationId: FunctionReference<"query", "public", any, any>; | |
| 251 | + create: FunctionReference< | |
| 252 | + "mutation", | |
| 253 | + "public", | |
| 254 | + { name: string; sessionId?: string }, | |
| 255 | + any | |
| 256 | + >; | |
| 257 | + getNavigationState: FunctionReference< | |
| 258 | + "query", | |
| 259 | + "public", | |
| 260 | + { sessionId?: string }, | |
| 261 | + any | |
| 262 | + >; | |
| 263 | + getOrganizationAccess: FunctionReference< | |
| 264 | + "query", | |
| 265 | + "public", | |
| 266 | + { organizationId: Id<"organizations">; sessionId?: string }, | |
| 267 | + any | |
| 268 | + >; | |
| 269 | + listMine: FunctionReference<"query", "public", { sessionId?: string }, any>; | |
| 270 | + resolveWorkosOrganizationId: FunctionReference< | |
| 271 | + "query", | |
| 272 | + "public", | |
| 273 | + { organizationId: Id<"organizations">; sessionId?: string }, | |
| 274 | + any | |
| 275 | + >; | |
| 73 | 276 | }; |
| 74 | 277 | workspace: { |
| 75 | − bootstrapAnonymousSession: FunctionReference<"mutation", "public", any, any>; | |
| 76 | − listRuntimeTargets: FunctionReference<"query", "public", any, any>; | |
| 77 | − getTask: FunctionReference<"query", "public", any, any>; | |
| 78 | − getTaskInWorkspace: FunctionReference<"query", "public", any, any>; | |
| 79 | − listTasks: FunctionReference<"query", "public", any, any>; | |
| 80 | − listApprovals: FunctionReference<"query", "public", any, any>; | |
| 81 | − listPendingApprovals: FunctionReference<"query", "public", any, any>; | |
| 82 | − listTaskEvents: FunctionReference<"query", "public", any, any>; | |
| 83 | − upsertAccessPolicy: FunctionReference<"mutation", "public", any, any>; | |
| 84 | − listAccessPolicies: FunctionReference<"query", "public", any, any>; | |
| 85 | − upsertCredential: FunctionReference<"mutation", "public", any, any>; | |
| 86 | − listCredentials: FunctionReference<"query", "public", any, any>; | |
| 87 | − listCredentialProviders: FunctionReference<"query", "public", any, any>; | |
| 88 | − resolveCredential: FunctionReference<"query", "public", any, any>; | |
| 89 | − upsertToolSource: FunctionReference<"mutation", "public", any, any>; | |
| 90 | − listToolSources: FunctionReference<"query", "public", any, any>; | |
| 91 | − deleteToolSource: FunctionReference<"mutation", "public", any, any>; | |
| 278 | + bootstrapAnonymousSession: FunctionReference< | |
| 279 | + "mutation", | |
| 280 | + "public", | |
| 281 | + { sessionId?: string }, | |
| 282 | + any | |
| 283 | + >; | |
| 284 | + deleteToolSource: FunctionReference< | |
| 285 | + "mutation", | |
| 286 | + "public", | |
| 287 | + { sessionId?: string; sourceId: string; workspaceId: string }, | |
| 288 | + any | |
| 289 | + >; | |
| 290 | + getTask: FunctionReference< | |
| 291 | + "query", | |
| 292 | + "public", | |
| 293 | + { sessionId?: string; taskId: string; workspaceId: string }, | |
| 294 | + any | |
| 295 | + >; | |
| 296 | + getTaskInWorkspace: FunctionReference< | |
| 297 | + "query", | |
| 298 | + "public", | |
| 299 | + { sessionId?: string; taskId: string; workspaceId: string }, | |
| 300 | + any | |
| 301 | + >; | |
| 302 | + listAccessPolicies: FunctionReference< | |
| 303 | + "query", | |
| 304 | + "public", | |
| 305 | + { sessionId?: string; workspaceId: string }, | |
| 306 | + any | |
| 307 | + >; | |
| 308 | + listApprovals: FunctionReference< | |
| 309 | + "query", | |
| 310 | + "public", | |
| 311 | + { | |
| 312 | + sessionId?: string; | |
| 313 | + status?: "pending" | "approved" | "denied"; | |
| 314 | + workspaceId: string; | |
| 315 | + }, | |
| 316 | + any | |
| 317 | + >; | |
| 318 | + listCredentialProviders: FunctionReference<"query", "public", {}, any>; | |
| 319 | + listCredentials: FunctionReference< | |
| 320 | + "query", | |
| 321 | + "public", | |
| 322 | + { sessionId?: string; workspaceId: string }, | |
| 323 | + any | |
| 324 | + >; | |
| 325 | + listPendingApprovals: FunctionReference< | |
| 326 | + "query", | |
| 327 | + "public", | |
| 328 | + { sessionId?: string; workspaceId: string }, | |
| 329 | + any | |
| 330 | + >; | |
| 331 | + listRuntimeTargets: FunctionReference<"query", "public", {}, any>; | |
| 332 | + listTaskEvents: FunctionReference< | |
| 333 | + "query", | |
| 334 | + "public", | |
| 335 | + { sessionId?: string; taskId: string; workspaceId: string }, | |
| 336 | + any | |
| 337 | + >; | |
| 338 | + listTasks: FunctionReference< | |
| 339 | + "query", | |
| 340 | + "public", | |
| 341 | + { sessionId?: string; workspaceId: string }, | |
| 342 | + any | |
| 343 | + >; | |
| 344 | + listToolSources: FunctionReference< | |
| 345 | + "query", | |
| 346 | + "public", | |
| 347 | + { sessionId?: string; workspaceId: string }, | |
| 348 | + any | |
| 349 | + >; | |
| 350 | + resolveCredential: FunctionReference< | |
| 351 | + "query", | |
| 352 | + "public", | |
| 353 | + { | |
| 354 | + actorId?: string; | |
| 355 | + scope: "workspace" | "actor"; | |
| 356 | + sessionId?: string; | |
| 357 | + sourceKey: string; | |
| 358 | + workspaceId: string; | |
| 359 | + }, | |
| 360 | + any | |
| 361 | + >; | |
| 362 | + upsertAccessPolicy: FunctionReference< | |
| 363 | + "mutation", | |
| 364 | + "public", | |
| 365 | + { | |
| 366 | + actorId?: string; | |
| 367 | + clientId?: string; | |
| 368 | + decision: "allow" | "require_approval" | "deny"; | |
| 369 | + id?: string; | |
| 370 | + priority?: number; | |
| 371 | + sessionId?: string; | |
| 372 | + toolPathPattern: string; | |
| 373 | + workspaceId: string; | |
| 374 | + }, | |
| 375 | + any | |
| 376 | + >; | |
| 377 | + upsertCredential: FunctionReference< | |
| 378 | + "mutation", | |
| 379 | + "public", | |
| 380 | + { | |
| 381 | + actorId?: string; | |
| 382 | + id?: string; | |
| 383 | + provider?: "managed" | "workos-vault"; | |
| 384 | + scope: "workspace" | "actor"; | |
| 385 | + secretJson: any; | |
| 386 | + sessionId?: string; | |
| 387 | + sourceKey: string; | |
| 388 | + workspaceId: string; | |
| 389 | + }, | |
| 390 | + any | |
| 391 | + >; | |
| 392 | + upsertToolSource: FunctionReference< | |
| 393 | + "mutation", | |
| 394 | + "public", | |
| 395 | + { | |
| 396 | + config: any; | |
| 397 | + enabled?: boolean; | |
| 398 | + id?: string; | |
| 399 | + name: string; | |
| 400 | + sessionId?: string; | |
| 401 | + type: "mcp" | "openapi" | "graphql"; | |
| 402 | + workspaceId: string; | |
| 403 | + }, | |
| 404 | + any | |
| 405 | + >; | |
| 92 | 406 | }; |
| 93 | 407 | workspaces: { |
| 94 | − create: FunctionReference<"mutation", "public", any, any>; | |
| 95 | − list: FunctionReference<"query", "public", any, any>; | |
| 96 | − generateWorkspaceIconUploadUrl: FunctionReference<"mutation", "public", any, any>; | |
| 408 | + create: FunctionReference< | |
| 409 | + "mutation", | |
| 410 | + "public", | |
| 411 | + { | |
| 412 | + iconStorageId?: Id<"_storage">; | |
| 413 | + name: string; | |
| 414 | + organizationId?: Id<"organizations">; | |
| 415 | + sessionId?: string; | |
| 416 | + }, | |
| 417 | + any | |
| 418 | + >; | |
| 419 | + generateWorkspaceIconUploadUrl: FunctionReference< | |
| 420 | + "mutation", | |
| 421 | + "public", | |
| 422 | + { sessionId?: string }, | |
| 423 | + any | |
| 424 | + >; | |
| 425 | + list: FunctionReference< | |
| 426 | + "query", | |
| 427 | + "public", | |
| 428 | + { organizationId?: Id<"organizations">; sessionId?: string }, | |
| 429 | + any | |
| 430 | + >; | |
| 97 | 431 | }; |
| 98 | 432 | }; |
| 99 | 433 | |
| @@ -107,79 +441,429 @@ export declare const api: { · 7 unmodified lines | ||
| 107 | 441 | */ |
| 108 | 442 | export declare const internal: { |
| 109 | 443 | auth: { |
| 110 | − authKitEvent: FunctionReference<"mutation", "internal", any, any>; | |
| 444 | + authKitEvent: FunctionReference< | |
| 445 | + "mutation", | |
| 446 | + "internal", | |
| 447 | + { data: Record<string, any>; event: string }, | |
| 448 | + null | |
| 449 | + >; | |
| 111 | 450 | }; |
| 112 | 451 | billingInternal: { |
| 113 | − getBillingAccessForRequest: FunctionReference<"query", "internal", any, any>; | |
| 114 | − getSeatSyncSnapshot: FunctionReference<"query", "internal", any, any>; | |
| 115 | − upsertCustomerLink: FunctionReference<"mutation", "internal", any, any>; | |
| 116 | − bumpSeatSyncVersion: FunctionReference<"mutation", "internal", any, any>; | |
| 117 | − upsertSeatState: FunctionReference<"mutation", "internal", any, any>; | |
| 452 | + bumpSeatSyncVersion: FunctionReference< | |
| 453 | + "mutation", | |
| 454 | + "internal", | |
| 455 | + { organizationId: Id<"organizations"> }, | |
| 456 | + any | |
| 457 | + >; | |
| 458 | + getBillingAccessForRequest: FunctionReference< | |
| 459 | + "query", | |
| 460 | + "internal", | |
| 461 | + { organizationId: Id<"organizations">; sessionId?: string }, | |
| 462 | + any | |
| 463 | + >; | |
| 464 | + getSeatSyncSnapshot: FunctionReference< | |
| 465 | + "query", | |
| 466 | + "internal", | |
| 467 | + { organizationId: Id<"organizations"> }, | |
| 468 | + any | |
| 469 | + >; | |
| 470 | + upsertCustomerLink: FunctionReference< | |
| 471 | + "mutation", | |
| 472 | + "internal", | |
| 473 | + { organizationId: Id<"organizations">; stripeCustomerId: string }, | |
| 474 | + any | |
| 475 | + >; | |
| 476 | + upsertSeatState: FunctionReference< | |
| 477 | + "mutation", | |
| 478 | + "internal", | |
| 479 | + { | |
| 480 | + bumpVersion: boolean; | |
| 481 | + desiredSeats: number; | |
| 482 | + lastAppliedSeats: number | null; | |
| 483 | + organizationId: Id<"organizations">; | |
| 484 | + syncError: string | null; | |
| 485 | + }, | |
| 486 | + any | |
| 487 | + >; | |
| 118 | 488 | }; |
| 119 | 489 | billingSync: { |
| 120 | − syncSeatQuantity: FunctionReference<"action", "internal", any, any>; | |
| 490 | + syncSeatQuantity: FunctionReference< | |
| 491 | + "action", | |
| 492 | + "internal", | |
| 493 | + { expectedVersion: number; organizationId: Id<"organizations"> }, | |
| 494 | + any | |
| 495 | + >; | |
| 121 | 496 | }; |
| 122 | 497 | database: { |
| 123 | − createTask: FunctionReference<"mutation", "internal", any, any>; | |
| 124 | − getTask: FunctionReference<"query", "internal", any, any>; | |
| 125 | − listTasks: FunctionReference<"query", "internal", any, any>; | |
| 126 | − listQueuedTaskIds: FunctionReference<"query", "internal", any, any>; | |
| 127 | − listRuntimeTargets: FunctionReference<"query", "internal", any, any>; | |
| 128 | − getTaskInWorkspace: FunctionReference<"query", "internal", any, any>; | |
| 129 | − markTaskRunning: FunctionReference<"mutation", "internal", any, any>; | |
| 130 | − markTaskFinished: FunctionReference<"mutation", "internal", any, any>; | |
| 131 | − createApproval: FunctionReference<"mutation", "internal", any, any>; | |
| 132 | − getApproval: FunctionReference<"query", "internal", any, any>; | |
| 133 | − listApprovals: FunctionReference<"query", "internal", any, any>; | |
| 134 | − listPendingApprovals: FunctionReference<"query", "internal", any, any>; | |
| 135 | − resolveApproval: FunctionReference<"mutation", "internal", any, any>; | |
| 136 | − getApprovalInWorkspace: FunctionReference<"query", "internal", any, any>; | |
| 137 | − bootstrapAnonymousSession: FunctionReference<"mutation", "internal", any, any>; | |
| 138 | − upsertAccessPolicy: FunctionReference<"mutation", "internal", any, any>; | |
| 139 | − listAccessPolicies: FunctionReference<"query", "internal", any, any>; | |
| 140 | − upsertCredential: FunctionReference<"mutation", "internal", any, any>; | |
| 141 | − listCredentials: FunctionReference<"query", "internal", any, any>; | |
| 142 | − listCredentialProviders: FunctionReference<"query", "internal", any, any>; | |
| 143 | − resolveCredential: FunctionReference<"query", "internal", any, any>; | |
| 144 | − upsertToolSource: FunctionReference<"mutation", "internal", any, any>; | |
| 145 | − listToolSources: FunctionReference<"query", "internal", any, any>; | |
| 146 | − deleteToolSource: FunctionReference<"mutation", "internal", any, any>; | |
| 147 | − createTaskEvent: FunctionReference<"mutation", "internal", any, any>; | |
| 148 | − listTaskEvents: FunctionReference<"query", "internal", any, any>; | |
| 498 | + bootstrapAnonymousSession: FunctionReference< | |
| 499 | + "mutation", | |
| 500 | + "internal", | |
| 501 | + { sessionId?: string }, | |
| 502 | + any | |
| 503 | + >; | |
| 504 | + createApproval: FunctionReference< | |
| 505 | + "mutation", | |
| 506 | + "internal", | |
| 507 | + { id: string; input?: any; taskId: string; toolPath: string }, | |
| 508 | + any | |
| 509 | + >; | |
| 510 | + createTask: FunctionReference< | |
| 511 | + "mutation", | |
| 512 | + "internal", | |
| 513 | + { | |
| 514 | + actorId: string; | |
| 515 | + clientId?: string; | |
| 516 | + code: string; | |
| 517 | + id: string; | |
| 518 | + metadata?: any; | |
| 519 | + runtimeId: string; | |
| 520 | + timeoutMs?: number; | |
| 521 | + workspaceId: string; | |
| 522 | + }, | |
| 523 | + any | |
| 524 | + >; | |
| 525 | + createTaskEvent: FunctionReference< | |
| 526 | + "mutation", | |
| 527 | + "internal", | |
| 528 | + { eventName: string; payload: any; taskId: string; type: string }, | |
| 529 | + any | |
| 530 | + >; | |
| 531 | + deleteToolSource: FunctionReference< | |
| 532 | + "mutation", | |
| 533 | + "internal", | |
| 534 | + { sourceId: string; workspaceId: string }, | |
| 535 | + any | |
| 536 | + >; | |
| 537 | + getApproval: FunctionReference< | |
| 538 | + "query", | |
| 539 | + "internal", | |
| 540 | + { approvalId: string }, | |
| 541 | + any | |
| 542 | + >; | |
| 543 | + getApprovalInWorkspace: FunctionReference< | |
| 544 | + "query", | |
| 545 | + "internal", | |
| 546 | + { approvalId: string; workspaceId: string }, | |
| 547 | + any | |
| 548 | + >; | |
| 549 | + getTask: FunctionReference<"query", "internal", { taskId: string }, any>; | |
| 550 | + getTaskInWorkspace: FunctionReference< | |
| 551 | + "query", | |
| 552 | + "internal", | |
| 553 | + { taskId: string; workspaceId: string }, | |
| 554 | + any | |
| 555 | + >; | |
| 556 | + listAccessPolicies: FunctionReference< | |
| 557 | + "query", | |
| 558 | + "internal", | |
| 559 | + { workspaceId: string }, | |
| 560 | + any | |
| 561 | + >; | |
| 562 | + listApprovals: FunctionReference< | |
| 563 | + "query", | |
| 564 | + "internal", | |
| 565 | + { status?: "pending" | "approved" | "denied"; workspaceId: string }, | |
| 566 | + any | |
| 567 | + >; | |
| 568 | + listCredentialProviders: FunctionReference<"query", "internal", {}, any>; | |
| 569 | + listCredentials: FunctionReference< | |
| 570 | + "query", | |
| 571 | + "internal", | |
| 572 | + { workspaceId: string }, | |
| 573 | + any | |
| 574 | + >; | |
| 575 | + listPendingApprovals: FunctionReference< | |
| 576 | + "query", | |
| 577 | + "internal", | |
| 578 | + { workspaceId: string }, | |
| 579 | + any | |
| 580 | + >; | |
| 581 | + listQueuedTaskIds: FunctionReference< | |
| 582 | + "query", | |
| 583 | + "internal", | |
| 584 | + { limit?: number }, | |
| 585 | + any | |
| 586 | + >; | |
| 587 | + listRuntimeTargets: FunctionReference<"query", "internal", {}, any>; | |
| 588 | + listTaskEvents: FunctionReference< | |
| 589 | + "query", | |
| 590 | + "internal", | |
| 591 | + { taskId: string }, | |
| 592 | + any | |
| 593 | + >; | |
| 594 | + listTasks: FunctionReference< | |
| 595 | + "query", | |
| 596 | + "internal", | |
| 597 | + { workspaceId: string }, | |
| 598 | + any | |
| 599 | + >; | |
| 600 | + listToolSources: FunctionReference< | |
| 601 | + "query", | |
| 602 | + "internal", | |
| 603 | + { workspaceId: string }, | |
| 604 | + any | |
| 605 | + >; | |
| 606 | + markTaskFinished: FunctionReference< | |
| 607 | + "mutation", | |
| 608 | + "internal", | |
| 609 | + { | |
| 610 | + error?: string; | |
| 611 | + exitCode?: number; | |
| 612 | + status: "completed" | "failed" | "timed_out" | "denied"; | |
| 613 | + stderr: string; | |
| 614 | + stdout: string; | |
| 615 | + taskId: string; | |
| 616 | + }, | |
| 617 | + any | |
| 618 | + >; | |
| 619 | + markTaskRunning: FunctionReference< | |
| 620 | + "mutation", | |
| 621 | + "internal", | |
| 622 | + { taskId: string }, | |
| 623 | + any | |
| 624 | + >; | |
| 625 | + resolveApproval: FunctionReference< | |
| 626 | + "mutation", | |
| 627 | + "internal", | |
| 628 | + { | |
| 629 | + approvalId: string; | |
| 630 | + decision: "approved" | "denied"; | |
| 631 | + reason?: string; | |
| 632 | + reviewerId?: string; | |
| 633 | + }, | |
| 634 | + any | |
| 635 | + >; | |
| 636 | + resolveCredential: FunctionReference< | |
| 637 | + "query", | |
| 638 | + "internal", | |
| 639 | + { | |
| 640 | + actorId?: string; | |
| 641 | + scope: "workspace" | "actor"; | |
| 642 | + sourceKey: string; | |
| 643 | + workspaceId: string; | |
| 644 | + }, | |
| 645 | + any | |
| 646 | + >; | |
| 647 | + upsertAccessPolicy: FunctionReference< | |
| 648 | + "mutation", | |
| 649 | + "internal", | |
| 650 | + { | |
| 651 | + actorId?: string; | |
| 652 | + clientId?: string; | |
| 653 | + decision: "allow" | "require_approval" | "deny"; | |
| 654 | + id?: string; | |
| 655 | + priority?: number; | |
| 656 | + toolPathPattern: string; | |
| 657 | + workspaceId: string; | |
| 658 | + }, | |
| 659 | + any | |
| 660 | + >; | |
| 661 | + upsertCredential: FunctionReference< | |
| 662 | + "mutation", | |
| 663 | + "internal", | |
| 664 | + { | |
| 665 | + actorId?: string; | |
| 666 | + id?: string; | |
| 667 | + provider?: "managed" | "workos-vault"; | |
| 668 | + scope: "workspace" | "actor"; | |
| 669 | + secretJson: any; | |
| 670 | + sourceKey: string; | |
| 671 | + workspaceId: string; | |
| 672 | + }, | |
| 673 | + any | |
| 674 | + >; | |
| 675 | + upsertToolSource: FunctionReference< | |
| 676 | + "mutation", | |
| 677 | + "internal", | |
| 678 | + { | |
| 679 | + config: any; | |
| 680 | + enabled?: boolean; | |
| 681 | + id?: string; | |
| 682 | + name: string; | |
| 683 | + type: "mcp" | "openapi" | "graphql"; | |
| 684 | + workspaceId: string; | |
| 685 | + }, | |
| 686 | + any | |
| 687 | + >; | |
| 149 | 688 | }; |
| 150 | 689 | executor: { |
| 151 | − createTaskInternal: FunctionReference<"mutation", "internal", any, any>; | |
| 152 | − resolveApprovalInternal: FunctionReference<"mutation", "internal", any, any>; | |
| 153 | − appendRuntimeOutput: FunctionReference<"mutation", "internal", any, any>; | |
| 690 | + appendRuntimeOutput: FunctionReference< | |
| 691 | + "mutation", | |
| 692 | + "internal", | |
| 693 | + { | |
| 694 | + line: string; | |
| 695 | + runId: string; | |
| 696 | + stream: "stdout" | "stderr"; | |
| 697 | + timestamp?: number; | |
| 698 | + }, | |
| 699 | + any | |
| 700 | + >; | |
| 701 | + createTaskInternal: FunctionReference< | |
| 702 | + "mutation", | |
| 703 | + "internal", | |
| 704 | + { | |
| 705 | + actorId: string; | |
| 706 | + clientId?: string; | |
| 707 | + code: string; | |
| 708 | + metadata?: any; | |
| 709 | + runtimeId?: string; | |
| 710 | + timeoutMs?: number; | |
| 711 | + workspaceId: string; | |
| 712 | + }, | |
| 713 | + any | |
| 714 | + >; | |
| 715 | + resolveApprovalInternal: FunctionReference< | |
| 716 | + "mutation", | |
| 717 | + "internal", | |
| 718 | + { | |
| 719 | + approvalId: string; | |
| 720 | + decision: "approved" | "denied"; | |
| 721 | + reason?: string; | |
| 722 | + reviewerId?: string; | |
| 723 | + workspaceId: string; | |
| 724 | + }, | |
| 725 | + any | |
| 726 | + >; | |
| 154 | 727 | }; |
| 155 | 728 | executorNode: { |
| 156 | − listToolsInternal: FunctionReference<"action", "internal", any, any>; | |
| 157 | − listToolsWithWarningsInternal: FunctionReference<"action", "internal", any, any>; | |
| 158 | − handleExternalToolCall: FunctionReference<"action", "internal", any, any>; | |
| 159 | − runTask: FunctionReference<"action", "internal", any, any>; | |
| 729 | + handleExternalToolCall: FunctionReference< | |
| 730 | + "action", | |
| 731 | + "internal", | |
| 732 | + { callId: string; input?: any; runId: string; toolPath: string }, | |
| 733 | + any | |
| 734 | + >; | |
| 735 | + listToolsInternal: FunctionReference< | |
| 736 | + "action", | |
| 737 | + "internal", | |
| 738 | + { actorId?: string; clientId?: string; workspaceId: string }, | |
| 739 | + any | |
| 740 | + >; | |
| 741 | + listToolsWithWarningsInternal: FunctionReference< | |
| 742 | + "action", | |
| 743 | + "internal", | |
| 744 | + { actorId?: string; clientId?: string; workspaceId: string }, | |
| 745 | + any | |
| 746 | + >; | |
| 747 | + runTask: FunctionReference<"action", "internal", { taskId: string }, any>; | |
| 160 | 748 | }; |
| 161 | 749 | invites: { |
| 162 | − deliverWorkosInvite: FunctionReference<"action", "internal", any, any>; | |
| 163 | − revokeWorkosInvite: FunctionReference<"action", "internal", any, any>; | |
| 164 | − getInviteDeliveryContext: FunctionReference<"query", "internal", any, any>; | |
| 165 | − linkOrganizationToWorkos: FunctionReference<"mutation", "internal", any, any>; | |
| 166 | − getInviteById: FunctionReference<"query", "internal", any, any>; | |
| 167 | − markInviteDelivered: FunctionReference<"mutation", "internal", any, any>; | |
| 168 | − markInviteDeliveryFailed: FunctionReference<"mutation", "internal", any, any>; | |
| 750 | + deliverWorkosInvite: FunctionReference< | |
| 751 | + "action", | |
| 752 | + "internal", | |
| 753 | + { | |
| 754 | + expiresInDays?: number; | |
| 755 | + inviteId: Id<"invites">; | |
| 756 | + inviterWorkosUserId: string; | |
| 757 | + roleSlug?: string; | |
| 758 | + }, | |
| 759 | + any | |
| 760 | + >; | |
| 761 | + getInviteById: FunctionReference< | |
| 762 | + "query", | |
| 763 | + "internal", | |
| 764 | + { inviteId: Id<"invites"> }, | |
| 765 | + any | |
| 766 | + >; | |
| 767 | + getInviteDeliveryContext: FunctionReference< | |
| 768 | + "query", | |
| 769 | + "internal", | |
| 770 | + { inviteId: Id<"invites"> }, | |
| 771 | + any | |
| 772 | + >; | |
| 773 | + linkOrganizationToWorkos: FunctionReference< | |
| 774 | + "mutation", | |
| 775 | + "internal", | |
| 776 | + { | |
| 777 | + organizationId: Id<"organizations">; | |
| 778 | + workosOrgId: string; | |
| 779 | + workspaceId?: Id<"workspaces">; | |
| 780 | + }, | |
| 781 | + any | |
| 782 | + >; | |
| 783 | + markInviteDelivered: FunctionReference< | |
| 784 | + "mutation", | |
| 785 | + "internal", | |
| 786 | + { inviteId: Id<"invites">; providerInviteId: string }, | |
| 787 | + any | |
| 788 | + >; | |
| 789 | + markInviteDeliveryFailed: FunctionReference< | |
| 790 | + "mutation", | |
| 791 | + "internal", | |
| 792 | + { errorMessage: string; inviteId: Id<"invites"> }, | |
| 793 | + any | |
| 794 | + >; | |
| 795 | + revokeWorkosInvite: FunctionReference< | |
| 796 | + "action", | |
| 797 | + "internal", | |
| 798 | + { inviteId: Id<"invites">; providerInviteId: string }, | |
| 799 | + any | |
| 800 | + >; | |
| 169 | 801 | }; |
| 170 | 802 | openApiSpecCache: { |
| 171 | − getEntry: FunctionReference<"query", "internal", any, any>; | |
| 172 | − putEntry: FunctionReference<"mutation", "internal", any, any>; | |
| 173 | − pruneExpired: FunctionReference<"mutation", "internal", any, any>; | |
| 803 | + getEntry: FunctionReference< | |
| 804 | + "query", | |
| 805 | + "internal", | |
| 806 | + { maxAgeMs: number; specUrl: string; version: string }, | |
| 807 | + any | |
| 808 | + >; | |
| 809 | + pruneExpired: FunctionReference< | |
| 810 | + "mutation", | |
| 811 | + "internal", | |
| 812 | + { batchSize?: number; maxAgeMs: number }, | |
| 813 | + any | |
| 814 | + >; | |
| 815 | + putEntry: FunctionReference< | |
| 816 | + "mutation", | |
| 817 | + "internal", | |
| 818 | + { | |
| 819 | + sizeBytes: number; | |
| 820 | + specUrl: string; | |
| 821 | + storageId: Id<"_storage">; | |
| 822 | + version: string; | |
| 823 | + }, | |
| 824 | + any | |
| 825 | + >; | |
| 174 | 826 | }; |
| 175 | 827 | workspaceAuthInternal: { |
| 176 | − getWorkspaceAccessForRequest: FunctionReference<"query", "internal", any, any>; | |
| 177 | − getWorkspaceAccessForWorkosSubject: FunctionReference<"query", "internal", any, any>; | |
| 828 | + getWorkspaceAccessForRequest: FunctionReference< | |
| 829 | + "query", | |
| 830 | + "internal", | |
| 831 | + { sessionId?: string; workspaceId: string }, | |
| 832 | + any | |
| 833 | + >; | |
| 834 | + getWorkspaceAccessForWorkosSubject: FunctionReference< | |
| 835 | + "query", | |
| 836 | + "internal", | |
| 837 | + { subject: string; workspaceId: string }, | |
| 838 | + any | |
| 839 | + >; | |
| 178 | 840 | }; |
| 179 | 841 | workspaceToolCache: { |
| 180 | − getEntry: FunctionReference<"query", "internal", any, any>; | |
| 181 | − putEntry: FunctionReference<"mutation", "internal", any, any>; | |
| 182 | − getDtsStorageIds: FunctionReference<"query", "internal", any, any>; | |
| 842 | + getDtsStorageIds: FunctionReference< | |
| 843 | + "query", | |
| 844 | + "internal", | |
| 845 | + { workspaceId: string }, | |
| 846 | + any | |
| 847 | + >; | |
| 848 | + getEntry: FunctionReference< | |
| 849 | + "query", | |
| 850 | + "internal", | |
| 851 | + { signature: string; workspaceId: string }, | |
| 852 | + any | |
| 853 | + >; | |
| 854 | + putEntry: FunctionReference< | |
| 855 | + "mutation", | |
| 856 | + "internal", | |
| 857 | + { | |
| 858 | + dtsStorageIds?: Array<{ sourceKey: string; storageId: Id<"_storage"> }>; | |
| 859 | + signature: string; | |
| 860 | + sizeBytes: number; | |
| 861 | + storageId: Id<"_storage">; | |
| 862 | + toolCount: number; | |
| 863 | + workspaceId: string; | |
| 864 | + }, | |
| 865 | + any | |
| 866 | + >; | |
| 183 | 867 | }; |
| 184 | 868 | }; |
| 185 | 869 | |
| @@ -573,4 +1257,3 @@ export declare const components: { · 387 unmodified lines | ||
| 573 | 1257 | }; |
| 574 | 1258 | }; |
| 575 | 1259 | }; |
| 576 | − | |
| @@ -115,8 +115,14 @@ export const createSubscriptionCheckout = action({ · 114 unmodified lines | ||
| 115 | 115 | } |
| 116 | 116 | |
| 117 | 117 | const quantity = Math.max(1, access.billableMembers); |
| 118 | − const successUrl = args.successUrl ?? process.env.BILLING_SUCCESS_URL ?? "http://localhost:3000/billing?success=true"; | |
| 119 | − const cancelUrl = args.cancelUrl ?? process.env.BILLING_CANCEL_URL ?? "http://localhost:3000/billing?canceled=true"; | |
| 118 | + const successUrl = | |
| 119 | + args.successUrl | |
| 120 | + ?? process.env.BILLING_SUCCESS_URL | |
| 121 | + ?? "http://localhost:3000/organization?tab=billing&success=true"; | |
| 122 | + const cancelUrl = | |
| 123 | + args.cancelUrl | |
| 124 | + ?? process.env.BILLING_CANCEL_URL | |
| 125 | + ?? "http://localhost:3000/organization?tab=billing&canceled=true"; | |
| 120 | 126 | |
| 121 | 127 | const session = await stripeClient.createCheckoutSession(ctx, { |
| 122 | 128 | priceId: args.priceId, |
| @@ -187,7 +193,7 @@ export const createCustomerPortal = action({ · 64 unmodified lines | ||
| 187 | 193 | |
| 188 | 194 | return await stripeClient.createCustomerPortalSession(ctx, { |
| 189 | 195 | customerId, |
| 190 | − returnUrl: args.returnUrl ?? process.env.BILLING_RETURN_URL ?? "http://localhost:3000/billing", | |
| 196 | + returnUrl: args.returnUrl ?? process.env.BILLING_RETURN_URL ?? "http://localhost:3000/organization?tab=billing", | |
| 191 | 197 | }); |
| 192 | 198 | }, |
| 193 | 199 | }); |
| @@ -56,7 +56,7 @@ function policySpecificity(policy: AccessPolicyRecord, actorId?: string, clientI · 55 unmodified lines | ||
| 56 | 56 | } |
| 57 | 57 | |
| 58 | 58 | function sourceSignature(workspaceId: string, sources: Array<{ id: string; updatedAt: number; enabled: boolean }>): string { |
| 59 | − const signatureVersion = "v2"; | |
| 59 | + const signatureVersion = "v4"; | |
| 60 | 60 | const parts = sources |
| 61 | 61 | .map((source) => `${source.id}:${source.updatedAt}:${source.enabled ? 1 : 0}`) |
| 62 | 62 | .sort(); |
| @@ -131,7 +131,7 @@ interface DtsStorageEntry { · 68 unmodified lines | ||
| 131 | 131 | const OPENAPI_SPEC_CACHE_TTL_MS = 5 * 60 * 60_000; |
| 132 | 132 | |
| 133 | 133 | /** Cache version — bump when PreparedOpenApiSpec shape changes. */ |
| 134 | −const OPENAPI_CACHE_VERSION = "v4"; | |
| 134 | +const OPENAPI_CACHE_VERSION = "v7"; | |
| 135 | 135 | |
| 136 | 136 | async function publish( |
| 137 | 137 | ctx: any, |
| @@ -314,6 +314,114 @@ describe("buildOpenApiToolsFromPrepared", () => { · 313 unmodified lines | ||
| 314 | 314 | } |
| 315 | 315 | }); |
| 316 | 316 | |
| 317 | + test("resolves shared parameter refs and maps 204 responses to void", async () => { | |
| 318 | + const spec: Record<string, unknown> = { | |
| 319 | + openapi: "3.0.3", | |
| 320 | + info: { title: "GitHub-like", version: "1.0.0" }, | |
| 321 | + servers: [{ url: "https://api.example.com" }], | |
| 322 | + components: { | |
| 323 | + parameters: { | |
| 324 | + owner: { | |
| 325 | + name: "owner", | |
| 326 | + in: "path", | |
| 327 | + required: true, | |
| 328 | + schema: { type: "string" }, | |
| 329 | + }, | |
| 330 | + repo: { | |
| 331 | + name: "repo", | |
| 332 | + in: "path", | |
| 333 | + required: true, | |
| 334 | + schema: { type: "string" }, | |
| 335 | + }, | |
| 336 | + }, | |
| 337 | + }, | |
| 338 | + paths: { | |
| 339 | + "/repos/{owner}/{repo}/subscription": { | |
| 340 | + parameters: [ | |
| 341 | + { $ref: "#/components/parameters/owner" }, | |
| 342 | + { $ref: "#/components/parameters/repo" }, | |
| 343 | + ], | |
| 344 | + delete: { | |
| 345 | + operationId: "activity/delete-repo-subscription", | |
| 346 | + tags: ["activity"], | |
| 347 | + responses: { | |
| 348 | + "204": { description: "No Content" }, | |
| 349 | + }, | |
| 350 | + }, | |
| 351 | + }, | |
| 352 | + }, | |
| 353 | + }; | |
| 354 | + | |
| 355 | + const prepared = await prepareOpenApiSpec(spec, "github-like"); | |
| 356 | + const tools = buildOpenApiToolsFromPrepared( | |
| 357 | + { | |
| 358 | + type: "openapi", | |
| 359 | + name: "github", | |
| 360 | + spec, | |
| 361 | + baseUrl: "https://api.example.com", | |
| 362 | + }, | |
| 363 | + prepared, | |
| 364 | + ); | |
| 365 | + | |
| 366 | + const tool = tools.find((t) => t.metadata?.operationId === "activity/delete-repo-subscription"); | |
| 367 | + expect(tool).toBeDefined(); | |
| 368 | + expect(tool!.metadata?.argsType).toContain("owner"); | |
| 369 | + expect(tool!.metadata?.argsType).toContain("repo"); | |
| 370 | + expect(tool!.metadata?.returnsType).toBe("void"); | |
| 371 | + }); | |
| 372 | + | |
| 373 | + test("supports Swagger 2.x parameter and response schemas for type hints", async () => { | |
| 374 | + const spec: Record<string, unknown> = { | |
| 375 | + swagger: "2.0", | |
| 376 | + info: { title: "Slack-like", version: "1.0.0" }, | |
| 377 | + host: "api.example.com", | |
| 378 | + schemes: ["https"], | |
| 379 | + basePath: "/api", | |
| 380 | + paths: { | |
| 381 | + "/admin.apps.approved.list": { | |
| 382 | + get: { | |
| 383 | + operationId: "admin_apps_approved_list", | |
| 384 | + tags: ["admin.apps.approved"], | |
| 385 | + parameters: [ | |
| 386 | + { name: "token", in: "query", required: true, type: "string" }, | |
| 387 | + { name: "limit", in: "query", required: false, type: "integer" }, | |
| 388 | + ], | |
| 389 | + responses: { | |
| 390 | + "200": { | |
| 391 | + description: "ok", | |
| 392 | + schema: { | |
| 393 | + type: "object", | |
| 394 | + properties: { | |
| 395 | + ok: { type: "boolean" }, | |
| 396 | + }, | |
| 397 | + required: ["ok"], | |
| 398 | + }, | |
| 399 | + }, | |
| 400 | + }, | |
| 401 | + }, | |
| 402 | + }, | |
| 403 | + }, | |
| 404 | + }; | |
| 405 | + | |
| 406 | + const prepared = await prepareOpenApiSpec(spec, "slack-like"); | |
| 407 | + const tools = buildOpenApiToolsFromPrepared( | |
| 408 | + { | |
| 409 | + type: "openapi", | |
| 410 | + name: "slack", | |
| 411 | + spec, | |
| 412 | + baseUrl: "https://api.example.com/api", | |
| 413 | + }, | |
| 414 | + prepared, | |
| 415 | + ); | |
| 416 | + | |
| 417 | + const tool = tools.find((t) => t.metadata?.operationId === "admin_apps_approved_list"); | |
| 418 | + expect(tool).toBeDefined(); | |
| 419 | + expect(tool!.metadata?.argsType).toContain("token: string"); | |
| 420 | + expect(tool!.metadata?.argsType).toContain("limit?: number"); | |
| 421 | + expect(tool!.metadata?.returnsType).toContain("ok"); | |
| 422 | + expect(tool!.metadata?.returnsType).not.toBe("unknown"); | |
| 423 | + }); | |
| 424 | + | |
| 317 | 425 | test("schemaTypes only attached to first tool from a source", async () => { |
| 318 | 426 | const specWithSchemas: Record<string, unknown> = { |
| 319 | 427 | openapi: "3.0.3", |
| @@ -154,6 +154,16 @@ describe("real-world OpenAPI specs", () => { · 153 unmodified lines | ||
| 154 | 154 | // The sourceDts should contain operations interface |
| 155 | 155 | const dts = withSourceDts[0].metadata!.sourceDts!; |
| 156 | 156 | expect(dts).toContain("operations"); |
| 157 | + | |
| 158 | + // Real specs should produce useful type hints for at least some operations. | |
| 159 | + const typedInputs = tools.filter( | |
| 160 | + (t) => t.metadata?.argsType && t.metadata.argsType !== "Record<string, unknown>", | |
| 161 | + ); | |
| 162 | + const typedOutputs = tools.filter( | |
| 163 | + (t) => t.metadata?.returnsType && t.metadata.returnsType !== "unknown", | |
| 164 | + ); | |
| 165 | + expect(typedInputs.length).toBeGreaterThan(0); | |
| 166 | + expect(typedOutputs.length).toBeGreaterThan(0); | |
| 157 | 167 | } |
| 158 | 168 | |
| 159 | 169 | if (prepared.warnings.length > 0) { |
| @@ -164,4 +174,64 @@ describe("real-world OpenAPI specs", () => { · 4 unmodified lines | ||
| 164 | 174 | 300_000, |
| 165 | 175 | ); |
| 166 | 176 | } |
| 177 | + | |
| 178 | + test( | |
| 179 | + "github: delete repo subscription keeps typed args and void return", | |
| 180 | + async () => { | |
| 181 | + const githubUrl = | |
| 182 | + "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.yaml"; | |
| 183 | + | |
| 184 | + const prepared = await prepareOpenApiSpec(githubUrl, "github"); | |
| 185 | + const tools = buildOpenApiToolsFromPrepared( | |
| 186 | + { | |
| 187 | + type: "openapi", | |
| 188 | + name: "github", | |
| 189 | + spec: githubUrl, | |
| 190 | + baseUrl: prepared.servers[0] || "https://api.github.com", | |
| 191 | + }, | |
| 192 | + prepared, | |
| 193 | + ); | |
| 194 | + | |
| 195 | + const tool = tools.find( | |
| 196 | + (t) => t.metadata?.operationId === "activity/delete-repo-subscription", | |
| 197 | + ); | |
| 198 | + | |
| 199 | + expect(tool).toBeDefined(); | |
| 200 | + expect(tool!.path).toBe("github.activity.activity_delete_repo_subscription"); | |
| 201 | + expect(tool!.metadata!.argsType).toContain("owner"); | |
| 202 | + expect(tool!.metadata!.argsType).toContain("repo"); | |
| 203 | + expect(tool!.metadata!.argsType).not.toBe("Record<string, unknown>"); | |
| 204 | + expect(tool!.metadata!.returnsType).toBe("void"); | |
| 205 | + }, | |
| 206 | + 300_000, | |
| 207 | + ); | |
| 208 | + | |
| 209 | + test( | |
| 210 | + "slack: approved apps list keeps typed query params and non-unknown output", | |
| 211 | + async () => { | |
| 212 | + const slackUrl = "https://api.slack.com/specs/openapi/v2/slack_web.json"; | |
| 213 | + | |
| 214 | + const prepared = await prepareOpenApiSpec(slackUrl, "slack"); | |
| 215 | + const tools = buildOpenApiToolsFromPrepared( | |
| 216 | + { | |
| 217 | + type: "openapi", | |
| 218 | + name: "slack", | |
| 219 | + spec: slackUrl, | |
| 220 | + baseUrl: "https://slack.com/api", | |
| 221 | + }, | |
| 222 | + prepared, | |
| 223 | + ); | |
| 224 | + | |
| 225 | + const tool = tools.find( | |
| 226 | + (t) => t.metadata?.operationId === "admin_apps_approved_list", | |
| 227 | + ); | |
| 228 | + | |
| 229 | + expect(tool).toBeDefined(); | |
| 230 | + expect(tool!.path).toBe("slack.admin_apps_approved.admin_apps_approved_list"); | |
| 231 | + expect(tool!.metadata!.argsType).toContain("token: string"); | |
| 232 | + expect(tool!.metadata!.argsType).toContain("limit?: number"); | |
| 233 | + expect(tool!.metadata!.returnsType).not.toBe("unknown"); | |
| 234 | + }, | |
| 235 | + 300_000, | |
| 236 | + ); | |
| 167 | 237 | }); |
| @@ -232,6 +232,78 @@ function getPreferredContentSchema(content: Record<string, unknown>): Record<str · 231 unmodified lines | ||
| 232 | 232 | return {}; |
| 233 | 233 | } |
| 234 | 234 | |
| 235 | +function getPreferredResponseSchema(responseValue: Record<string, unknown>): Record<string, unknown> { | |
| 236 | + // OpenAPI 3.x response shape | |
| 237 | + const contentSchema = getPreferredContentSchema(asRecord(responseValue.content)); | |
| 238 | + if (Object.keys(contentSchema).length > 0) { | |
| 239 | + return contentSchema; | |
| 240 | + } | |
| 241 | + | |
| 242 | + // Swagger 2.x response shape | |
| 243 | + const schema = asRecord(responseValue.schema); | |
| 244 | + if (Object.keys(schema).length > 0) { | |
| 245 | + return schema; | |
| 246 | + } | |
| 247 | + | |
| 248 | + return {}; | |
| 249 | +} | |
| 250 | + | |
| 251 | +function resolveSchemaRef( | |
| 252 | + schema: Record<string, unknown>, | |
| 253 | + componentSchemas: Record<string, unknown>, | |
| 254 | +): Record<string, unknown> { | |
| 255 | + const ref = typeof schema.$ref === "string" ? schema.$ref : ""; | |
| 256 | + const prefix = "#/components/schemas/"; | |
| 257 | + if (!ref.startsWith(prefix)) { | |
| 258 | + return schema; | |
| 259 | + } | |
| 260 | + | |
| 261 | + const key = ref.slice(prefix.length); | |
| 262 | + const resolved = asRecord(componentSchemas[key]); | |
| 263 | + if (Object.keys(resolved).length === 0) { | |
| 264 | + return schema; | |
| 265 | + } | |
| 266 | + return resolved; | |
| 267 | +} | |
| 268 | + | |
| 269 | +function parameterSchemaFromEntry(entry: Record<string, unknown>): Record<string, unknown> { | |
| 270 | + // OpenAPI 3.x (and Swagger body params) use `schema` | |
| 271 | + const schema = asRecord(entry.schema); | |
| 272 | + if (Object.keys(schema).length > 0) { | |
| 273 | + return schema; | |
| 274 | + } | |
| 275 | + | |
| 276 | + // Swagger 2.x non-body params use top-level `type`, `enum`, `items`, etc. | |
| 277 | + const type = typeof entry.type === "string" ? entry.type : ""; | |
| 278 | + if (!type) { | |
| 279 | + return {}; | |
| 280 | + } | |
| 281 | + | |
| 282 | + const fallback: Record<string, unknown> = { type }; | |
| 283 | + if (Array.isArray(entry.enum) && entry.enum.length > 0) { | |
| 284 | + fallback.enum = entry.enum; | |
| 285 | + } | |
| 286 | + const items = asRecord(entry.items); | |
| 287 | + if (Object.keys(items).length > 0) { | |
| 288 | + fallback.items = items; | |
| 289 | + } | |
| 290 | + | |
| 291 | + return fallback; | |
| 292 | +} | |
| 293 | + | |
| 294 | +function responseTypeHintFromSchema(responseSchema: Record<string, unknown>, responseStatus: string): string { | |
| 295 | + if (Object.keys(responseSchema).length > 0) { | |
| 296 | + return jsonSchemaTypeHintFallback(responseSchema); | |
| 297 | + } | |
| 298 | + | |
| 299 | + // No-content success responses should be represented as void. | |
| 300 | + if (responseStatus === "204" || responseStatus === "205") { | |
| 301 | + return "void"; | |
| 302 | + } | |
| 303 | + | |
| 304 | + return "unknown"; | |
| 305 | +} | |
| 306 | + | |
| 235 | 307 | /** Simple depth-limited type hint generator for schemas (used as fallback) */ |
| 236 | 308 | function jsonSchemaTypeHintFallback(schema: unknown, depth = 0): string { |
| 237 | 309 | if (!schema || typeof schema !== "object") return "unknown"; |
| @@ -254,6 +326,7 @@ function jsonSchemaTypeHintFallback(schema: unknown, depth = 0): string { · 16 unmodified lines | ||
| 254 | 326 | } |
| 255 | 327 | |
| 256 | 328 | const type = typeof shape.type === "string" ? shape.type : undefined; |
| 329 | + if (type === "integer") return "number"; | |
| 257 | 330 | if (type === "string" || type === "number" || type === "boolean" || type === "null") { |
| 258 | 331 | return type; |
| 259 | 332 | } |
| @@ -466,21 +539,39 @@ function buildOpenApiUrl( · 206 unmodified lines | ||
| 466 | 539 | function compactOpenApiPaths( |
| 467 | 540 | pathsValue: unknown, |
| 468 | 541 | operationTypeIds: Set<string>, |
| 542 | + componentParameters?: Record<string, unknown>, | |
| 543 | + componentSchemas?: Record<string, unknown>, | |
| 469 | 544 | ): Record<string, unknown> { |
| 470 | 545 | const paths = asRecord(pathsValue); |
| 471 | 546 | const methods = ["get", "post", "put", "delete", "patch", "head", "options"] as const; |
| 472 | 547 | const compactPaths: Record<string, unknown> = {}; |
| 548 | + const compParams = componentParameters ? asRecord(componentParameters) : {}; | |
| 549 | + const compSchemas = componentSchemas ? asRecord(componentSchemas) : {}; | |
| 550 | + | |
| 551 | + const resolveParam = (entry: Record<string, unknown>): Record<string, unknown> => { | |
| 552 | + if (typeof entry.$ref === "string") { | |
| 553 | + // Resolve "#/components/parameters/foo" → components.parameters.foo | |
| 554 | + const ref = entry.$ref; | |
| 555 | + const prefix = "#/components/parameters/"; | |
| 556 | + if (ref.startsWith(prefix)) { | |
| 557 | + const key = ref.slice(prefix.length); | |
| 558 | + const resolved = asRecord(compParams[key]); | |
| 559 | + if (Object.keys(resolved).length > 0) return resolved; | |
| 560 | + } | |
| 561 | + } | |
| 562 | + return entry; | |
| 563 | + }; | |
| 473 | 564 | |
| 474 | 565 | const normalizeParameters = (entries: unknown): Array<Record<string, unknown>> => { |
| 475 | 566 | if (!Array.isArray(entries)) return []; |
| 476 | 567 | return entries |
| 477 | − .map((entry) => asRecord(entry)) | |
| 568 | + .map((entry) => resolveParam(asRecord(entry))) | |
| 478 | 569 | .filter((entry) => typeof entry.name === "string" && typeof entry.in === "string") |
| 479 | 570 | .map((entry) => ({ |
| 480 | 571 | name: String(entry.name), |
| 481 | 572 | in: String(entry.in), |
| 482 | 573 | required: Boolean(entry.required), |
| 483 | − schema: asRecord(entry.schema), | |
| 574 | + schema: parameterSchemaFromEntry(entry), | |
| 484 | 575 | })); |
| 485 | 576 | }; |
| 486 | 577 | |
| @@ -523,15 +614,20 @@ function compactOpenApiPaths( · 36 unmodified lines | ||
| 523 | 614 | { |
| 524 | 615 | const requestBody = asRecord(operation.requestBody); |
| 525 | 616 | const requestBodyContent = asRecord(requestBody.content); |
| 526 | − const requestBodySchema = getPreferredContentSchema(requestBodyContent); | |
| 617 | + const requestBodySchema = resolveSchemaRef( | |
| 618 | + getPreferredContentSchema(requestBodyContent), | |
| 619 | + compSchemas, | |
| 620 | + ); | |
| 527 | 621 | |
| 528 | 622 | const responses = asRecord(operation.responses); |
| 529 | 623 | let responseSchema: Record<string, unknown> = {}; |
| 530 | 624 | let responseStatus = ""; |
| 531 | 625 | for (const [status, responseValue] of Object.entries(responses)) { |
| 532 | 626 | if (!status.startsWith("2")) continue; |
| 533 | − const responseContent = asRecord(asRecord(responseValue).content); | |
| 534 | − responseSchema = getPreferredContentSchema(responseContent); | |
| 627 | + responseSchema = resolveSchemaRef( | |
| 628 | + getPreferredResponseSchema(asRecord(responseValue)), | |
| 629 | + compSchemas, | |
| 630 | + ); | |
| 535 | 631 | responseStatus = status; |
| 536 | 632 | if (Object.keys(responseSchema).length > 0) break; |
| 537 | 633 | } |
| @@ -559,7 +655,7 @@ function compactOpenApiPaths( · 21 unmodified lines | ||
| 559 | 655 | ], |
| 560 | 656 | }; |
| 561 | 657 | compactOperation._argsTypeHint = jsonSchemaTypeHintFallback(combinedSchema); |
| 562 | − compactOperation._returnsTypeHint = jsonSchemaTypeHintFallback(responseSchema); | |
| 658 | + compactOperation._returnsTypeHint = responseTypeHintFromSchema(responseSchema, responseStatus); | |
| 563 | 659 | } else { |
| 564 | 660 | // Keep full schemas for the fallback path |
| 565 | 661 | if (Object.keys(requestBodySchema).length > 0) { |
| @@ -665,7 +761,12 @@ export async function prepareOpenApiSpec( · 99 unmodified lines | ||
| 665 | 761 | servers: servers |
| 666 | 762 | .map((server) => (typeof server.url === "string" ? server.url : "")) |
| 667 | 763 | .filter((url) => url.length > 0), |
| 668 | − paths: compactOpenApiPaths(bundled.paths, operationTypeIds), | |
| 764 | + paths: compactOpenApiPaths( | |
| 765 | + bundled.paths, | |
| 766 | + operationTypeIds, | |
| 767 | + asRecord(asRecord(bundled.components).parameters), | |
| 768 | + asRecord(asRecord(bundled.components).schemas), | |
| 769 | + ), | |
| 669 | 770 | dts: dts ?? undefined, |
| 670 | 771 | warnings, |
| 671 | 772 | }; |
| @@ -736,10 +837,11 @@ export function buildOpenApiToolsFromPrepared( · 64 unmodified lines | ||
| 736 | 837 | |
| 737 | 838 | const responses = asRecord(operation.responses); |
| 738 | 839 | let responseSchema: Record<string, unknown> = {}; |
| 840 | + let responseStatus = ""; | |
| 739 | 841 | for (const [status, responseValue] of Object.entries(responses)) { |
| 740 | 842 | if (!status.startsWith("2")) continue; |
| 741 | − const responseContent = asRecord(asRecord(responseValue).content); | |
| 742 | − responseSchema = getPreferredContentSchema(responseContent); | |
| 843 | + responseSchema = getPreferredResponseSchema(asRecord(responseValue)); | |
| 844 | + responseStatus = status; | |
| 743 | 845 | if (Object.keys(responseSchema).length > 0) break; |
| 744 | 846 | } |
| 745 | 847 | |
| @@ -758,7 +860,7 @@ export function buildOpenApiToolsFromPrepared( · 12 unmodified lines | ||
| 758 | 860 | }; |
| 759 | 861 | |
| 760 | 862 | argsType = jsonSchemaTypeHintFallback(combinedSchema); |
| 761 | − returnsType = jsonSchemaTypeHintFallback(responseSchema); | |
| 863 | + returnsType = responseTypeHintFromSchema(responseSchema, responseStatus); | |
| 762 | 864 | } |
| 763 | 865 | |
| 764 | 866 | const approval = config.overrides?.[operationIdRaw]?.approval |
| @@ -4,8 +4,7 @@ · 3 unmodified lines | ||
| 4 | 4 | "type": "module", |
| 5 | 5 | "scripts": { |
| 6 | 6 | "deploy": "bunx convex deploy", |
| 7 | − "codegen": "bunx convex codegen && bun scripts/postcodegen.ts", | |
| 8 | − "fix-types": "bun scripts/postcodegen.ts" | |
| 7 | + "codegen": "bunx convex codegen" | |
| 9 | 8 | }, |
| 10 | 9 | "dependencies": { |
| 11 | 10 | "convex": "^1.31.7" |
| @@ -1,289 +0,0 @@ | ||
| 1 | −/** | |
| 2 | − * Post-codegen script for Convex. | |
| 3 | − * | |
| 4 | − * Rewrites `convex/_generated/api.d.ts` to replace the deeply-recursive | |
| 5 | − * `ApiFromModules` / `FilterApi` types with explicit per-module declarations. | |
| 6 | − * | |
| 7 | − * Why: With 18+ modules and 100+ exported functions, the recursive type chain | |
| 8 | − * `ApiFromModules → ExpandModulesAndDirs → UnionToIntersection → FilterApi` | |
| 9 | − * exceeds TypeScript's instantiation depth limit of 100, causing TS2589. | |
| 10 | − * | |
| 11 | − * Usage: | |
| 12 | − * bun scripts/postcodegen.ts | |
| 13 | − * | |
| 14 | − * Run this after `bunx convex codegen` or `bunx convex dev` (via postinstall). | |
| 15 | − */ | |
| 16 | − | |
| 17 | −import { readFileSync, writeFileSync, readdirSync, existsSync } from "node:fs"; | |
| 18 | −import { join, resolve } from "node:path"; | |
| 19 | −import * as ts from "typescript"; | |
| 20 | − | |
| 21 | −const EXECUTOR_DIR = resolve(import.meta.dirname, ".."); | |
| 22 | −const CONVEX_DIR = join(EXECUTOR_DIR, "convex"); | |
| 23 | −const API_DTS_PATH = join(CONVEX_DIR, "_generated", "api.d.ts"); | |
| 24 | − | |
| 25 | −/** Read the generated api.d.ts and extract the module list + components block. */ | |
| 26 | −function parseGeneratedApi(content: string) { | |
| 27 | − const moduleImportRegex = /^import type \* as (\w+) from "\.\.\/(\w+)\.js";$/gm; | |
| 28 | − const modules: { name: string; file: string }[] = []; | |
| 29 | − let match: RegExpExecArray | null; | |
| 30 | − while ((match = moduleImportRegex.exec(content)) !== null) { | |
| 31 | − modules.push({ name: match[1], file: match[2] }); | |
| 32 | − } | |
| 33 | − | |
| 34 | − // Extract the components block (everything from `export declare const components:` to the end) | |
| 35 | − const componentsMatch = content.match( | |
| 36 | − /export declare const components:\s*\{[\s\S]*$/, | |
| 37 | − ); | |
| 38 | − const componentsBlock = componentsMatch ? componentsMatch[0] : "export declare const components: {};"; | |
| 39 | − | |
| 40 | − return { modules, componentsBlock }; | |
| 41 | −} | |
| 42 | − | |
| 43 | −type FunctionKind = "query" | "mutation" | "action"; | |
| 44 | −type FunctionVisibility = "public" | "internal"; | |
| 45 | − | |
| 46 | −interface ParsedExport { | |
| 47 | − name: string; | |
| 48 | − kind: FunctionKind; | |
| 49 | − visibility: FunctionVisibility; | |
| 50 | −} | |
| 51 | − | |
| 52 | −/** Determine the function kind and visibility from a call expression. */ | |
| 53 | −function resolveRegistration(callName: string): { kind: FunctionKind; visibility: FunctionVisibility } | null { | |
| 54 | − switch (callName) { | |
| 55 | − case "query": return { kind: "query", visibility: "public" }; | |
| 56 | − case "internalQuery": return { kind: "query", visibility: "internal" }; | |
| 57 | − case "mutation": return { kind: "mutation", visibility: "public" }; | |
| 58 | − case "internalMutation": return { kind: "mutation", visibility: "internal" }; | |
| 59 | − case "action": return { kind: "action", visibility: "public" }; | |
| 60 | − case "internalAction": return { kind: "action", visibility: "internal" }; | |
| 61 | − default: return null; | |
| 62 | − } | |
| 63 | −} | |
| 64 | − | |
| 65 | −/** Parse a Convex module file to extract exported function registrations. */ | |
| 66 | −function parseModuleExports(filePath: string): ParsedExport[] { | |
| 67 | − if (!existsSync(filePath)) return []; | |
| 68 | − | |
| 69 | − const content = readFileSync(filePath, "utf-8"); | |
| 70 | − const sourceFile = ts.createSourceFile( | |
| 71 | − filePath, | |
| 72 | − content, | |
| 73 | − ts.ScriptTarget.Latest, | |
| 74 | − true, | |
| 75 | − ); | |
| 76 | − | |
| 77 | − const exports: ParsedExport[] = []; | |
| 78 | − | |
| 79 | − function visit(node: ts.Node) { | |
| 80 | − // Match: export const foo = query({...}) / mutation({...}) / etc. | |
| 81 | − if ( | |
| 82 | − ts.isVariableStatement(node) && | |
| 83 | − node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) | |
| 84 | − ) { | |
| 85 | − for (const decl of node.declarationList.declarations) { | |
| 86 | − if (!ts.isIdentifier(decl.name) || !decl.initializer) continue; | |
| 87 | − const exportName = decl.name.text; | |
| 88 | − | |
| 89 | − let callExpr: ts.CallExpression | undefined; | |
| 90 | − | |
| 91 | − // Direct call: export const foo = query({...}) | |
| 92 | − if (ts.isCallExpression(decl.initializer)) { | |
| 93 | − callExpr = decl.initializer; | |
| 94 | − } | |
| 95 | − | |
| 96 | − // Nullish coalescing fallback: export const foo = bar?.baz ?? internalMutation({...}) | |
| 97 | − // The RHS of ?? determines the Convex registration type. | |
| 98 | − if ( | |
| 99 | − !callExpr && | |
| 100 | − ts.isBinaryExpression(decl.initializer) && | |
| 101 | − decl.initializer.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken | |
| 102 | − ) { | |
| 103 | − const rhs = decl.initializer.right; | |
| 104 | − if (ts.isCallExpression(rhs)) { | |
| 105 | − callExpr = rhs; | |
| 106 | − } | |
| 107 | − } | |
| 108 | − | |
| 109 | − // Ternary fallback: export const foo = cond ? bar : internalMutation({...}) | |
| 110 | − if ( | |
| 111 | − !callExpr && | |
| 112 | − ts.isConditionalExpression(decl.initializer) | |
| 113 | − ) { | |
| 114 | − const whenFalse = decl.initializer.whenFalse; | |
| 115 | − if (ts.isCallExpression(whenFalse)) { | |
| 116 | − callExpr = whenFalse; | |
| 117 | − } | |
| 118 | − } | |
| 119 | − | |
| 120 | − if (!callExpr) continue; | |
| 121 | − | |
| 122 | − // Get the function name being called | |
| 123 | − let calledName: string | undefined; | |
| 124 | − if (ts.isIdentifier(callExpr.expression)) { | |
| 125 | − calledName = callExpr.expression.text; | |
| 126 | − } | |
| 127 | − | |
| 128 | − if (!calledName) continue; | |
| 129 | − | |
| 130 | − const reg = resolveRegistration(calledName); | |
| 131 | − if (reg) { | |
| 132 | − exports.push({ name: exportName, ...reg }); | |
| 133 | − continue; | |
| 134 | − } | |
| 135 | − | |
| 136 | − // Custom builders (workspaceQuery, authedMutation, organizationQuery, etc.) | |
| 137 | − // These wrap standard query/mutation/action builders. We need to determine | |
| 138 | − // which base type they correspond to. | |
| 139 | − const customBuilderMap: Record<string, { kind: FunctionKind; visibility: FunctionVisibility }> = { | |
| 140 | − workspaceQuery: { kind: "query", visibility: "public" }, | |
| 141 | − workspaceMutation: { kind: "mutation", visibility: "public" }, | |
| 142 | − authedQuery: { kind: "query", visibility: "public" }, | |
| 143 | − authedMutation: { kind: "mutation", visibility: "public" }, | |
| 144 | − organizationQuery: { kind: "query", visibility: "public" }, | |
| 145 | − organizationMutation: { kind: "mutation", visibility: "public" }, | |
| 146 | − optionalAccountQuery: { kind: "query", visibility: "public" }, | |
| 147 | − internalOrganizationQuery: { kind: "query", visibility: "internal" }, | |
| 148 | − }; | |
| 149 | − | |
| 150 | − const customReg = customBuilderMap[calledName]; | |
| 151 | − if (customReg) { | |
| 152 | − exports.push({ name: exportName, ...customReg }); | |
| 153 | − } | |
| 154 | − } | |
| 155 | − } | |
| 156 | − | |
| 157 | − // Match: export default httpRouter() — skip (http module has no function refs) | |
| 158 | − // Match: export { authKitEvent } from ... — already handled above as variable declarations | |
| 159 | − | |
| 160 | − ts.forEachChild(node, visit); | |
| 161 | − } | |
| 162 | − | |
| 163 | − visit(sourceFile); | |
| 164 | − return exports; | |
| 165 | −} | |
| 166 | − | |
| 167 | −/** Group exports by visibility for a module. */ | |
| 168 | −function groupByVisibility(exports: ParsedExport[]) { | |
| 169 | − return { | |
| 170 | − public: exports.filter((e) => e.visibility === "public"), | |
| 171 | − internal: exports.filter((e) => e.visibility === "internal"), | |
| 172 | − }; | |
| 173 | −} | |
| 174 | − | |
| 175 | −/** Generate a FunctionReference type string (using `any` for args/returns to avoid deep instantiation). */ | |
| 176 | −function funcRef(kind: FunctionKind, visibility: FunctionVisibility): string { | |
| 177 | − return `FunctionReference<"${kind}", "${visibility}", any, any>`; | |
| 178 | −} | |
| 179 | − | |
| 180 | −/** Generate the type block for one namespace (module). */ | |
| 181 | −function generateModuleType(exports: ParsedExport[]): string { | |
| 182 | − if (exports.length === 0) return "Record<string, never>"; | |
| 183 | − const lines = exports.map( | |
| 184 | − (e) => ` ${e.name}: ${funcRef(e.kind, e.visibility)};`, | |
| 185 | − ); | |
| 186 | − return `{\n${lines.join("\n")}\n }`; | |
| 187 | −} | |
| 188 | − | |
| 189 | −function main() { | |
| 190 | − // 1. Read the generated api.d.ts | |
| 191 | − if (!existsSync(API_DTS_PATH)) { | |
| 192 | − console.error(`[postcodegen] ${API_DTS_PATH} not found — run 'bunx convex codegen' first`); | |
| 193 | − process.exit(1); | |
| 194 | − } | |
| 195 | − | |
| 196 | − const generatedContent = readFileSync(API_DTS_PATH, "utf-8"); | |
| 197 | − const { modules, componentsBlock } = parseGeneratedApi(generatedContent); | |
| 198 | − | |
| 199 | − if (modules.length === 0) { | |
| 200 | − console.log("[postcodegen] No modules found in api.d.ts — skipping"); | |
| 201 | − return; | |
| 202 | − } | |
| 203 | − | |
| 204 | − // 2. Parse each module's exports | |
| 205 | − const moduleExports = new Map<string, ParsedExport[]>(); | |
| 206 | − for (const mod of modules) { | |
| 207 | − const filePath = join(CONVEX_DIR, `${mod.file}.ts`); | |
| 208 | − const exports = parseModuleExports(filePath); | |
| 209 | − moduleExports.set(mod.name, exports); | |
| 210 | − } | |
| 211 | − | |
| 212 | − // 3. Build public and internal API types | |
| 213 | − const publicModules: string[] = []; | |
| 214 | − const internalModules: string[] = []; | |
| 215 | − | |
| 216 | − for (const mod of modules) { | |
| 217 | − const exports = moduleExports.get(mod.name) ?? []; | |
| 218 | − const grouped = groupByVisibility(exports); | |
| 219 | − | |
| 220 | − if (grouped.public.length > 0) { | |
| 221 | − publicModules.push(` ${mod.name}: ${generateModuleType(grouped.public)};`); | |
| 222 | − } | |
| 223 | − if (grouped.internal.length > 0) { | |
| 224 | − internalModules.push(` ${mod.name}: ${generateModuleType(grouped.internal)};`); | |
| 225 | − } | |
| 226 | − } | |
| 227 | − | |
| 228 | − // 4. Generate the new api.d.ts | |
| 229 | − const output = `/* eslint-disable */ | |
| 230 | −/** | |
| 231 | − * Generated \`api\` utility. | |
| 232 | − * | |
| 233 | − * THIS CODE IS AUTOMATICALLY GENERATED. | |
| 234 | − * | |
| 235 | − * To regenerate, run \`npx convex dev\`. | |
| 236 | − * | |
| 237 | − * NOTE: This file has been post-processed by scripts/postcodegen.ts | |
| 238 | − * to replace recursive ApiFromModules/FilterApi types with explicit | |
| 239 | − * declarations, avoiding TS2589 depth errors. | |
| 240 | − * | |
| 241 | − * @module | |
| 242 | − */ | |
| 243 | − | |
| 244 | −import type { FunctionReference } from "convex/server"; | |
| 245 | − | |
| 246 | −/** | |
| 247 | − * A utility for referencing Convex functions in your app's public API. | |
| 248 | − * | |
| 249 | − * Usage: | |
| 250 | − * \`\`\`js | |
| 251 | − * const myFunctionReference = api.myModule.myFunction; | |
| 252 | − * \`\`\` | |
| 253 | − */ | |
| 254 | −export declare const api: { | |
| 255 | −${publicModules.join("\n")} | |
| 256 | −}; | |
| 257 | − | |
| 258 | −/** | |
| 259 | − * A utility for referencing Convex functions in your app's internal API. | |
| 260 | − * | |
| 261 | − * Usage: | |
| 262 | − * \`\`\`js | |
| 263 | − * const myFunctionReference = internal.myModule.myFunction; | |
| 264 | − * \`\`\` | |
| 265 | − */ | |
| 266 | −export declare const internal: { | |
| 267 | −${internalModules.join("\n")} | |
| 268 | −}; | |
| 269 | − | |
| 270 | −${componentsBlock} | |
| 271 | −`; | |
| 272 | − | |
| 273 | − writeFileSync(API_DTS_PATH, output); | |
| 274 | − | |
| 275 | − const totalPublic = modules.reduce( | |
| 276 | − (sum, m) => sum + (moduleExports.get(m.name) ?? []).filter((e) => e.visibility === "public").length, | |
| 277 | − 0, | |
| 278 | − ); | |
| 279 | − const totalInternal = modules.reduce( | |
| 280 | − (sum, m) => sum + (moduleExports.get(m.name) ?? []).filter((e) => e.visibility === "internal").length, | |
| 281 | − 0, | |
| 282 | − ); | |
| 283 | − | |
| 284 | − console.log( | |
| 285 | − `[postcodegen] Rewrote api.d.ts: ${modules.length} modules, ${totalPublic} public + ${totalInternal} internal function refs`, | |
| 286 | − ); | |
| 287 | −} | |
| 288 | − | |
| 289 | −main(); | |