diff --git a/frontend/packages/components/src/actors/actor-build.tsx b/frontend/packages/components/src/actors/actor-build.tsx deleted file mode 100644 index 8416f0dd4e..0000000000 --- a/frontend/packages/components/src/actors/actor-build.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { Dd, DiscreteCopyButton, Dl, Dt, Flex } from "@rivet-gg/components"; -import { formatISO } from "date-fns"; -import { useAtomValue } from "jotai"; -import { selectAtom } from "jotai/utils"; -import { useCallback } from "react"; -import { type Actor, type ActorAtom, actorBuildsAtom } from "./actor-context"; -import { ActorTags } from "./actor-tags"; - -const buildIdSelector = (a: Actor) => a.runtime?.build; - -interface ActorBuildProps { - actor: ActorAtom; -} - -export function ActorBuild({ actor }: ActorBuildProps) { - const buildId = useAtomValue(selectAtom(actor, buildIdSelector)); - - const data = useAtomValue( - selectAtom( - actorBuildsAtom, - useCallback( - (builds) => { - return builds.find((build) => build.id === buildId); - }, - [buildId], - ), - ), - ); - - if (!data) { - return null; - } - - return ( -
-
-

Build

-
- -
-
ID
-
- - {data.id} - -
-
Created
-
- - {formatISO(data.createdAt)} - -
-
Tags
-
- - - -
-
-
-
- ); -} diff --git a/frontend/packages/components/src/actors/actor-config-tab.tsx b/frontend/packages/components/src/actors/actor-config-tab.tsx deleted file mode 100644 index 2a05406888..0000000000 --- a/frontend/packages/components/src/actors/actor-config-tab.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { Button, DocsSheet, ScrollArea } from "@rivet-gg/components"; -import { Icon, faBooks } from "@rivet-gg/icons"; -import type { ActorAtom } from "./actor-context"; -import { ActorGeneral } from "./actor-general"; -import { ActorNetwork } from "./actor-network"; -import { ActorRuntime } from "./actor-runtime"; - -interface ActorConfigTabProps { - actor: ActorAtom; -} - -export function ActorConfigTab(props: ActorConfigTabProps) { - return ( - -
- - - -
- - - -
- ); -} diff --git a/frontend/packages/components/src/actors/actor-connections-tab.tsx b/frontend/packages/components/src/actors/actor-connections-tab.tsx deleted file mode 100644 index 42be046a7c..0000000000 --- a/frontend/packages/components/src/actors/actor-connections-tab.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { LiveBadge, ScrollArea } from "@rivet-gg/components"; -import { useAtomValue } from "jotai"; -import { selectAtom } from "jotai/utils"; -import type { Actor, ActorAtom } from "./actor-context"; -import { ActorObjectInspector } from "./console/actor-inspector"; -import { - useActorConnections, - useActorWorkerStatus, -} from "./worker/actor-worker-context"; - -const selector = (a: Actor) => a.destroyedAt; - -interface ActorConnectionsTabProps { - actor: ActorAtom; -} - -export function ActorConnectionsTab({ actor }: ActorConnectionsTabProps) { - const destroyedAt = useAtomValue(selectAtom(actor, selector)); - const status = useActorWorkerStatus(); - - const connections = useActorConnections(); - - if (destroyedAt) { - return ( -
- Connections Preview is unavailable for inactive Actors. -
- ); - } - - if (status.type === "error") { - return ( -
- Connections Preview is currently unavailable. -
- See console/logs for more details. -
- ); - } - - if (status.type !== "ready") { - return ( -
- Loading connections... -
- ); - } - - return ( - -
- -
-
- [c.id, c]))} - expandPaths={["$"]} - /> -
-
- ); -} diff --git a/frontend/packages/components/src/actors/actor-context.tsx b/frontend/packages/components/src/actors/actor-context.tsx deleted file mode 100644 index 7aab38c8fa..0000000000 --- a/frontend/packages/components/src/actors/actor-context.tsx +++ /dev/null @@ -1,437 +0,0 @@ -import type { Rivet } from "@rivet-gg/api"; -import { isAfter, isBefore } from "date-fns"; -import { type Atom, atom } from "jotai"; -import { atomFamily, splitAtom } from "jotai/utils"; -import { toRecord } from "../lib/utils"; -import { FilterOp, type FilterValue } from "../ui/filters"; -import { ACTOR_FRAMEWORK_TAG_VALUE } from "./actor-tags"; - -export enum ActorFeature { - Logs = "logs", - Config = "config", - Connections = "connections", - State = "state", - Console = "console", - Runtime = "runtime", - Metrics = "metrics", - InspectReconnectNotification = "inspect_reconnect_notification", -} - -export type Actor = Omit< - Rivet.actor.Actor, - "createdAt" | "runtime" | "lifecycle" | "network" | "resources" -> & { - status: "unknown" | "starting" | "running" | "stopped" | "crashed"; - - lifecycle?: Rivet.actor.Lifecycle; - endpoint?: string; - logs: LogsAtom; - metrics: MetricsAtom; - network?: Rivet.actor.Network | null; - resources?: Rivet.actor.Resources | null; - runtime?: Rivet.actor.Runtime | null; - destroy?: DestroyActorAtom; - destroyTs?: Date; - createdAt?: Date; - features?: ActorFeature[]; -}; - -export type Logs = { - id: string; - level: "error" | "info"; - timestamp: Date; - line: string; - message: string; - properties: Record; -}[]; - -export type Metrics = Record; - -export type Build = Rivet.actor.Build; -export type DestroyActor = { - isDestroying: boolean; - destroy: () => Promise; -}; - -export type ActorAtom = Atom; -export type LogsAtom = Atom<{ - logs: Logs; - // query status - status: string; -}>; -export type MetricsAtom = Atom<{ - metrics: Metrics; - updatedAt: number; - // query status - status: string; -}>; -export type BuildAtom = Atom; -export type DestroyActorAtom = Atom; - -export type CreateActor = { - create: (values: { - endpoint: string; - id: string; - tags: Record; - region?: string; - params?: Record; - }) => Promise; - isCreating: boolean; - endpoint: string | null; -}; - -export type Region = Rivet.actor.Region; - -// global atoms -export const currentActorIdAtom = atom(undefined); - -export const currentActorQueryAtom = atom<{ - isLoading: boolean; - error: string | null; -}>({ - isLoading: false, - error: null, -}); -export const actorsQueryAtom = atom<{ - isLoading: boolean; - error: string | null; -}>({ - isLoading: false, - error: null, -}); -export const actorsAtom = atom([]); -export const actorFiltersAtom = atom<{ - tags: FilterValue; - region: FilterValue; - createdAt: FilterValue; - destroyedAt: FilterValue; - status: FilterValue; - devMode: FilterValue; -}>({ - tags: undefined, - region: undefined, - createdAt: undefined, - destroyedAt: undefined, - status: undefined, - devMode: undefined, -}); -export const actorsPaginationAtom = atom({ - hasNextPage: false, - isFetchingNextPage: false, - fetchNextPage: () => {}, -}); - -export const actorRegionsAtom = atom([ - { - id: "default", - name: "Default", - }, -]); - -export const actorBuildsAtom = atom([]); - -export const actorEnvironmentAtom = atom<{ - projectNameId: string; - environmentNameId: string; -} | null>(null); - -export const actorMetricsTimeWindowAtom = atom(15 * 60 * 1000); // Default to 15 minutes - -export const actorsInternalFilterAtom = atom<{ - fn: (actor: Actor) => boolean; -}>(); - -// derived atoms - -export const currentActorRegionAtom = atom((get) => { - const actorAtom = get(currentActorAtom); - if (!actorAtom) { - return undefined; - } - const regions = get(actorRegionsAtom); - const actor = get(actorAtom); - return regions.find((region) => region.id === actor.region); -}); -export const filteredActorsAtom = atom((get) => { - const filters = get(actorFiltersAtom); - const actors = get(actorsAtom); - - const isActorInternal = get(actorsInternalFilterAtom)?.fn; - - return actors.filter((actor) => { - const satisfiesFilters = Object.entries(filters).every( - ([key, filter]) => { - if (filter === undefined) { - return true; - } - if (key === "tags") { - const filterTags = filter.value.map((tag) => - tag.split("="), - ); - const tags = toRecord(actor.tags); - - if (filter.operator === FilterOp.NOT_EQUAL) { - return Object.entries(tags).every( - ([tagKey, tagValue]) => { - return filterTags.every( - ([filterKey, filterValue]) => { - if (filterKey === tagKey) { - if (filterValue === "*") { - return false; - } - return tagValue !== filterValue; - } - return true; - }, - ); - }, - ); - } - - return Object.entries(tags).some(([tagKey, tagValue]) => { - return filterTags.some(([filterKey, filterValue]) => { - if (filterKey === tagKey) { - if (filterValue === "*") { - return true; - } - return tagValue === filterValue; - } - return false; - }); - }); - } - - if (key === "region") { - if (filter.operator === FilterOp.NOT_EQUAL) { - return !filter.value.includes(actor.region); - } - - return filter.value.includes(actor.region); - } - - if (key === "createdAt") { - if (actor.createdAt === undefined) { - return false; - } - const createdAt = new Date(actor.createdAt); - - if (filter.operator === FilterOp.AFTER) { - return isAfter(createdAt, +filter.value[0]); - } - if (filter.operator === FilterOp.BEFORE) { - return isBefore(createdAt, +filter.value[0]); - } - if (filter.operator === FilterOp.BETWEEN) { - return ( - isAfter(createdAt, +filter.value[0]) && - isBefore(createdAt, +filter.value[1]) - ); - } - return false; - } - - if (key === "destroyedAt") { - if (actor.destroyTs === undefined) { - return false; - } - const destroyedAt = new Date(actor.destroyTs); - - if (filter.operator === FilterOp.AFTER) { - return isAfter(destroyedAt, +filter.value[0]); - } - if (filter.operator === FilterOp.BEFORE) { - return isBefore(destroyedAt, +filter.value[0]); - } - if (filter.operator === FilterOp.BETWEEN) { - return ( - isAfter(destroyedAt, +filter.value[0]) && - isBefore(destroyedAt, +filter.value[1]) - ); - } - return false; - } - - if (key === "status") { - if (filter.operator === FilterOp.NOT_EQUAL) { - return !filter.value.includes(actor.status); - } - - return filter.value.includes(actor.status); - } - - return true; - }, - ); - - const isInternal = - toRecord(actor.tags).owner === "rivet" || - (isActorInternal?.(actor) ?? false); - - return ( - satisfiesFilters && ((isInternal && filters.devMode) || !isInternal) - ); - }); -}); -export const actorsAtomsAtom = splitAtom( - filteredActorsAtom, - (actor) => actor.id, -); -export const actorsCountAtom = atom((get) => get(actorsAtom).length); -export const filteredActorsCountAtom = atom( - (get) => get(filteredActorsAtom).length, -); - -export const currentActorAtom = atom((get) => { - const actorId = get(currentActorIdAtom); - return get(actorsAtomsAtom).find((actor) => get(actor).id === actorId); -}); - -export const isCurrentActorAtom = atomFamily((actor: ActorAtom) => - atom((get) => { - const actorId = get(currentActorIdAtom); - return get(actor).id === actorId; - }), -); - -export const actorFiltersCountAtom = atom((get) => { - const filters = get(actorFiltersAtom); - return Object.values(filters).filter((value) => value !== undefined).length; -}); - -// tags created by the user, not from the server -export const actorCustomTagValues = atom([]); -export const actorCustomTagKeys = atom([]); - -const actorCustomTagsAtom = atom<{ keys: string[]; values: string[] }>( - (get) => { - const keys = get(actorCustomTagKeys); - const values = get(actorCustomTagValues); - - return { keys, values }; - }, - // @ts-expect-error - (get, set, value: { key: string; value: string }) => { - set(actorCustomTagKeys, (keys) => { - const newKeys = [...keys]; - const index = newKeys.indexOf(value.key); - if (index === -1) { - newKeys.push(value.key); - } - return newKeys; - }); - set(actorCustomTagValues, (values) => { - const newValues = [...values]; - const index = newValues.indexOf(value.value); - if (index === -1) { - newValues.push(value.value); - } - return newValues; - }); - }, -); - -export const createActorAtom = atom({ - endpoint: null, - isCreating: false, - create: async () => {}, -}); - -export const actorManagerEndpointAtom = atom((get) => { - return get(createActorAtom)?.endpoint ?? null; -}); - -export const actorTagsAtom = atom((get) => { - const actorTags = get(actorsAtom).flatMap((actor) => - Object.entries(toRecord(actor.tags)).map(([key, value]) => ({ - key, - value: value as string, - })), - ); - - const keys = new Set(); - const values = new Set(); - - for (const { key, value } of actorTags) { - keys.add(key); - values.add(value); - } - - const customTags = get(actorCustomTagsAtom); - - for (const key of customTags.keys) { - keys.add(key); - } - - for (const value of customTags.values) { - values.add(value); - } - - const allTags = []; - - for (const key of keys) { - for (const value of values) { - allTags.push({ key, value }); - } - } - - return allTags; -}); - -export const actorTagValuesAtom = atom((get) => { - const tags = get(actorTagsAtom); - const values = new Set(); - for (const tag of tags) { - values.add(tag.value); - } - return [...values]; -}); - -export const actorTagKeysAtom = atom((get) => { - const tags = get(actorTagsAtom); - const keys = new Set(); - for (const tag of tags) { - keys.add(tag.key); - } - return [...keys]; -}); - -export const actorBuildsCountAtom = atom((get) => { - return get(actorBuildsAtom).length; -}); - -const commonActorFeatures = [ - ActorFeature.Logs, - ActorFeature.Config, - ActorFeature.Runtime, - // ActorFeature.Metrics, - // ActorFeature.InspectReconnectNotification, -]; - -export const currentActorFeaturesAtom = atom((get) => { - const atom = get(currentActorAtom); - if (!atom) { - return []; - } - - const actor = get(atom); - - // actors from hub - if (!actor.features) { - const tags = toRecord(actor.tags); - if (tags.framework === ACTOR_FRAMEWORK_TAG_VALUE) { - if (tags.name === "manager") { - return commonActorFeatures; - } - return [ - ...commonActorFeatures, - // ActorFeature.Connections, - // ActorFeature.State, - // ActorFeature.Console, - // ActorFeature.InspectReconnectNotification, - ]; - } - return [...commonActorFeatures, ActorFeature.Metrics]; - } - - return actor.features; -}); diff --git a/frontend/packages/components/src/actors/actor-cpu-stats.tsx b/frontend/packages/components/src/actors/actor-cpu-stats.tsx deleted file mode 100644 index 2cf694f339..0000000000 --- a/frontend/packages/components/src/actors/actor-cpu-stats.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import { format } from "date-fns"; -import { useId } from "react"; -import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; -import { timing } from "../lib/timing"; -import { - type ChartConfig, - ChartContainer, - ChartTooltip, - ChartTooltipContent, -} from "../ui/chart"; - -interface ActorCpuStatsProps { - interval?: number; - cpu: number[]; - metricsAt: number; - syncId?: string; - isRunning?: boolean; -} - -const chartConfig = { - value: { - color: "hsl(var(--chart-1))", - label: "CPU Usage", - }, -} satisfies ChartConfig; - -export function ActorCpuStats({ - interval = 15, - cpu, - metricsAt, - syncId, - isRunning = true, -}: ActorCpuStatsProps) { - // Filter out trailing zeros in the last 15 seconds only if actor is still running - let filteredCpu = [...cpu]; - if (isRunning) { - const secondsToCheck = 15; - const pointsToCheck = Math.ceil(secondsToCheck / interval); - - // Find the last non-zero value and cut off any zeros after it - for ( - let i = filteredCpu.length - 1; - i >= Math.max(0, filteredCpu.length - pointsToCheck); - i-- - ) { - if (filteredCpu[i] === 0) { - filteredCpu = filteredCpu.slice(0, i); - } else { - break; - } - } - } - - const data = filteredCpu.map((value, i) => { - let cpuPercent = 0; - - // Calculate CPU percentage using delta time between ticks - if (i > 0) { - const currentCpuTime = value; - const previousCpuTime = filteredCpu[i - 1]; - const deltaTime = interval; // seconds between measurements - - // CPU percentage = (cpu_time_delta / time_delta) * 100 - // This gives us the percentage of CPU time used in the interval - if (currentCpuTime >= previousCpuTime) { - cpuPercent = Math.min( - ((currentCpuTime - previousCpuTime) / deltaTime) * 100, - 100, - ); - } - } - - return { - x: `${(filteredCpu.length - i) * -interval}`, - value: cpuPercent / 100, // Convert to 0-1 range for chart - config: { - label: new Date( - metricsAt - - (filteredCpu.length - i) * timing.seconds(interval), - ), - }, - }; - }); - - const id = useId(); - - const fillId = `fill-${id}`; - return ( - - - - - `${value * 100}%`} - /> - { - return format(label, "HH:mm:ss"); - }} - valueFormatter={(value) => { - if (typeof value !== "number") { - return "n/a"; - } - return `${(value * 100).toFixed(2)}%`; - }} - /> - } - /> - - - - - - - - - - ); -} diff --git a/frontend/packages/components/src/actors/actor-details-settings-button.tsx b/frontend/packages/components/src/actors/actor-details-settings-button.tsx deleted file mode 100644 index 9e4684bd40..0000000000 --- a/frontend/packages/components/src/actors/actor-details-settings-button.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { - Button, - DropdownMenu, - DropdownMenuCheckboxItem, - DropdownMenuContent, - DropdownMenuTrigger, - WithTooltip, -} from "@rivet-gg/components"; -import { Icon, faCog } from "@rivet-gg/icons"; -import { useActorDetailsSettings } from "./actor-details-settings"; - -export function ActorDetailsSettingsButton() { - const [settings, setSettings] = useActorDetailsSettings(); - - return ( - - - - - } - content="Settings" - /> - - { - setSettings((old) => ({ - ...old, - showTimestamps: value, - })); - }} - > - Show timestamps - - { - setSettings((old) => ({ - ...old, - autoFollowLogs: value, - })); - }} - > - Auto follow logs when scrolled to bottom - - - - ); -} diff --git a/frontend/packages/components/src/actors/actor-details-settings.tsx b/frontend/packages/components/src/actors/actor-details-settings.tsx deleted file mode 100644 index 622ce862e4..0000000000 --- a/frontend/packages/components/src/actors/actor-details-settings.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { - type Dispatch, - type ReactNode, - type SetStateAction, - createContext, - useContext, -} from "react"; -import { useLocalStorage } from "usehooks-ts"; - -export interface Settings { - showTimestamps: boolean; - autoFollowLogs: boolean; -} - -export const ActorDetailsSettingsContext = createContext< - [Settings, Dispatch>, unknown] ->([{ showTimestamps: false, autoFollowLogs: true }, () => {}, {}]); - -export const useActorDetailsSettings = () => { - const value = useContext(ActorDetailsSettingsContext); - return value; -}; - -interface ActorDetailsSettingsProviderProps { - children: ReactNode; -} - -export const ActorDetailsSettingsProvider = ({ - children, -}: ActorDetailsSettingsProviderProps) => { - const localStorage = useLocalStorage("actor-details-settings", { - showTimestamps: false, - autoFollowLogs: true, - }); - - return ( - - {children} - - ); -}; diff --git a/frontend/packages/components/src/actors/actor-download-logs-button.tsx b/frontend/packages/components/src/actors/actor-download-logs-button.tsx deleted file mode 100644 index a261529c32..0000000000 --- a/frontend/packages/components/src/actors/actor-download-logs-button.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { Button, WithTooltip } from "@rivet-gg/components"; -import { Icon, faSave } from "@rivet-gg/icons"; -import { useAtomValue } from "jotai"; -import type { ActorAtom } from "./actor-context"; -import type { LogsTypeFilter } from "./actor-logs"; - -interface ActorDownloadLogsButtonProps { - actor: ActorAtom; - typeFilter?: LogsTypeFilter; - filter?: string; - onExportLogs?: ( - actorId: string, - typeFilter?: string, - filter?: string, - ) => Promise; - isExporting?: boolean; -} - -export function ActorDownloadLogsButton({ - actor, - typeFilter, - filter, - onExportLogs, - isExporting = false, -}: ActorDownloadLogsButtonProps) { - const actorData = useAtomValue(actor); - - const handleDownload = async () => { - if (!onExportLogs) { - console.warn("No export handler provided"); - return; - } - - try { - await onExportLogs(actorData.id, typeFilter, filter); - } catch (error) { - console.error("Failed to export logs:", error); - } - }; - - return ( - - - - } - /> - ); -} diff --git a/frontend/packages/components/src/actors/actor-editable-state.tsx b/frontend/packages/components/src/actors/actor-editable-state.tsx deleted file mode 100644 index e54e474548..0000000000 --- a/frontend/packages/components/src/actors/actor-editable-state.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import { Badge, Button, WithTooltip } from "@rivet-gg/components"; -import { - type CodeMirrorRef, - EditorView, - JsonCode, -} from "@rivet-gg/components/code-mirror"; -import { Icon, faRotateLeft, faSave } from "@rivet-gg/icons"; -import { AnimatePresence, motion } from "framer-motion"; -import { useMemo, useRef, useState } from "react"; -import { ActorStateChangeIndicator } from "./actor-state-change-indicator"; -import type { ContainerState } from "./worker/actor-worker-container"; -import { useActorWorker } from "./worker/actor-worker-context"; - -const isValidJson = (json: string | null): json is string => { - if (!json) return false; - try { - JSON.parse(json); - return true; - } catch { - return false; - } -}; - -interface ActorEditableStateProps { - state: ContainerState["state"]; -} - -export function ActorEditableState({ state }: ActorEditableStateProps) { - const container = useActorWorker(); - const [isEditing, setIsEditing] = useState(false); - const [value, setValue] = useState(null); - - const ref = useRef(null); - - const formatted = useMemo(() => { - return JSON.stringify(state.value || "{}", null, 2); - }, [state.value]); - - const isValid = isValidJson(value) ? JSON.parse(value) : false; - - return ( - <> -
-
- -
-
- - {isEditing ? ( - - - Modified - - - } - content="State has been modified and not saved." - /> - ) : null} - - { - container.setState(value || ""); - setIsEditing(false); - setValue(null); - }} - > - - - } - /> - { - setValue(null); - setIsEditing(false); - }} - > - - - } - /> -
-
-
- { - setValue(value); - setIsEditing(true); - }} - /> -
- - ); -} diff --git a/frontend/packages/components/src/actors/actor-general.tsx b/frontend/packages/components/src/actors/actor-general.tsx deleted file mode 100644 index b9269fb39e..0000000000 --- a/frontend/packages/components/src/actors/actor-general.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { Dd, DiscreteCopyButton, Dl, Dt, Flex, cn } from "@rivet-gg/components"; -import { formatISO } from "date-fns"; -import equal from "fast-deep-equal"; -import { useAtomValue } from "jotai"; -import { selectAtom } from "jotai/utils"; -import type { Actor, ActorAtom } from "./actor-context"; -import { ActorRegion } from "./actor-region"; -import { ActorTags } from "./actor-tags"; - -const selector = (a: Actor) => ({ - id: a.id, - tags: a.tags, - createdAt: a.createdAt, - destroyedAt: a.destroyedAt, - region: a.region, -}); - -export interface ActorGeneralProps { - actor: ActorAtom; -} - -export function ActorGeneral({ actor }: ActorGeneralProps) { - const { id, tags, createdAt, destroyedAt, region } = useAtomValue( - selectAtom(actor, selector, equal), - ); - - return ( -
-

General

- -
-
Region
-
- -
-
ID
-
- - {id} - -
-
Tags
-
- - - -
-
Created
-
- - {createdAt ? formatISO(createdAt) : "n/a"} - -
-
Destroyed
-
- - {destroyedAt ? formatISO(destroyedAt) : "n/a"} - -
-
-
-
- ); -} diff --git a/frontend/packages/components/src/actors/actor-logs-tab.tsx b/frontend/packages/components/src/actors/actor-logs-tab.tsx deleted file mode 100644 index 7925e94cc0..0000000000 --- a/frontend/packages/components/src/actors/actor-logs-tab.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { LogsView, ToggleGroup, ToggleGroupItem } from "@rivet-gg/components"; -import { startTransition, useState } from "react"; -import type { ActorAtom } from "./actor-context"; -import { ActorDetailsSettingsButton } from "./actor-details-settings-button"; -import { ActorDownloadLogsButton } from "./actor-download-logs-button"; -import { ActorLogs, type LogsTypeFilter } from "./actor-logs"; - -interface ActorLogsTabProps { - actor: ActorAtom; - onExportLogs?: ( - actorId: string, - typeFilter?: string, - filter?: string, - ) => Promise; - isExporting?: boolean; -} - -export function ActorLogsTab({ - actor, - onExportLogs, - isExporting, -}: ActorLogsTabProps) { - const [search, setSearch] = useState(""); - const [logsFilter, setLogsFilter] = useState("all"); - - return ( -
-
-
-
- - startTransition(() => setSearch(e.target.value)) - } - /> -
- { - if (!value) { - setLogsFilter("all"); - } else { - setLogsFilter(value as LogsTypeFilter); - } - }} - className="gap-0 text-xs p-2 border-r" - > - - all - - - output - - - errors - - - - -
-
-
- -
-
- ); -} - -ActorLogsTab.Skeleton = () => { - return ( -
- -
- ); -}; diff --git a/frontend/packages/components/src/actors/actor-logs.tsx b/frontend/packages/components/src/actors/actor-logs.tsx deleted file mode 100644 index 11427cab70..0000000000 --- a/frontend/packages/components/src/actors/actor-logs.tsx +++ /dev/null @@ -1,219 +0,0 @@ -import { ShimmerLine, VirtualScrollArea } from "@rivet-gg/components"; -import type { Virtualizer } from "@tanstack/react-virtual"; -import { useAtomValue } from "jotai"; -import { selectAtom } from "jotai/utils"; -import { memo, useCallback, useEffect, useRef } from "react"; -import { useResizeObserver } from "usehooks-ts"; -import type { Actor, ActorAtom, Logs } from "./actor-context"; -import { useActorDetailsSettings } from "./actor-details-settings"; -import { ActorConsoleMessage } from "./console/actor-console-message"; - -export type LogsTypeFilter = "all" | "output" | "errors"; - -const selector = (a: Actor) => a.logs; - -interface ActorLogsProps { - actor: ActorAtom; - typeFilter?: LogsTypeFilter; - filter?: string; -} - -export const ActorLogs = memo( - ({ typeFilter, actor, filter }: ActorLogsProps) => { - const [settings] = useActorDetailsSettings(); - const follow = useRef(true); - const shouldFollow = () => settings.autoFollowLogs && follow.current; - - const viewport = useRef(null); - const virtualizer = useRef>(null); - // Detect if the container has resized (i.e, console was opened) - useResizeObserver({ - ref: viewport, - onResize: () => { - if (shouldFollow()) { - // https://github.com/TanStack/virtual/issues/537 - requestAnimationFrame(() => { - virtualizer.current?.scrollToIndex(combined.length, { - align: "end", - }); - }); - } - }, - }); - - const logsAtom = useAtomValue(selectAtom(actor, selector)); - - const { logs, status } = useAtomValue(logsAtom); - - const combined = filterLogs({ - typeFilter: typeFilter ?? "all", - filter: filter ?? "", - logs, - }); - - // Scroll to the bottom when new logs are added - // biome-ignore lint/correctness/useExhaustiveDependencies: run this effect only when the length of the logs changes - useEffect(() => { - if (!shouldFollow()) { - return () => {}; - } - // https://github.com/TanStack/virtual/issues/537 - const rafId = requestAnimationFrame(() => { - virtualizer.current?.scrollToIndex( - virtualizer.current.options.count - 1, - { - align: "end", - }, - ); - }); - - return () => { - cancelAnimationFrame(rafId); - }; - }, [combined.length]); - - // Detect if the user has scrolled all the way to the bottom - const handleChange = useCallback( - (instance: Virtualizer, sync: boolean) => { - if (sync) { - return; - } - - follow.current = - !instance.isScrolling && - instance.range?.endIndex === instance.options.count - 1; - }, - [], - ); - - // if (isStdOutLoading || isStdErrLoading) { - // return ( - //
- // - // Loading logs... - // - //
- // ); - // } - - // const status = getActorStatus({ createdAt, startedAt, destroyedAt }); - - if (status === "starting" && combined.length === 0) { - return ( -
- - [SYSTEM]: Actor is starting... - -
- ); - } - - if (status === "pending") { - return ( - <> - -
- - Loading logs... - -
- - ); - } - - if (combined.length === 0) { - // if (!isStdOutSuccess || !isStdErrSuccess) { - // return ( - //
- // - // [SYSTEM]: Couldn't find the logs. Please try again - // later. - // - //
- // ); - // } - return ( -
- - [SYSTEM]: No logs found. Logs are retained for 3 days. - -
- ); - } - - return ( - <> - - ({ - ...combined[index], - children: - combined[index].message || combined[index].line, - variant: combined[index].level, - timestamp: settings.showTimestamps - ? combined[index].timestamp - : undefined, - })} - onChange={handleChange} - count={combined.length} - estimateSize={() => 26} - row={ActorConsoleMessage} - /> - - ); - }, -); - -interface ScrollerProps { - virtualizer: React.MutableRefObject | null>; -} - -function Scroller({ virtualizer }: ScrollerProps) { - // biome-ignore lint/correctness/useExhaustiveDependencies: scroll on mount, no need to run this effect again - useEffect(() => { - // https://github.com/TanStack/virtual/issues/537 - virtualizer.current?.scrollToIndex( - virtualizer.current.options.count - 1, - { - align: "end", - }, - ); - }, []); - - return null; -} - -export function filterLogs({ - typeFilter, - filter, - logs, -}: { typeFilter: LogsTypeFilter; filter: string; logs: Logs }) { - const output = logs?.filter((log) => { - if (typeFilter === "errors") { - return log.level === "error"; - } - if (typeFilter === "output") { - return log.level !== "error"; - } - return true; - }); - - // Search - const filtered = - filter && filter.trim() !== "" - ? output.filter((log) => log.message.includes(filter)) - : output; - - const sorted = filtered.toSorted( - (a, b) => - new Date(a.timestamp).valueOf() - new Date(b.timestamp).valueOf(), - ); - - return sorted; -} diff --git a/frontend/packages/components/src/actors/actor-memory-stats.tsx b/frontend/packages/components/src/actors/actor-memory-stats.tsx deleted file mode 100644 index f2af24d75d..0000000000 --- a/frontend/packages/components/src/actors/actor-memory-stats.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import { format } from "date-fns"; -import { filesize } from "filesize"; -import { useId } from "react"; -import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; -import { timing } from "../lib/timing"; -import { - type ChartConfig, - ChartContainer, - ChartTooltip, - ChartTooltipContent, -} from "../ui/chart"; - -interface ActorMemoryStatsProps { - metricsAt: number; - memory: number[]; - allocatedMemory?: number; - syncId?: string; - interval?: number; - isRunning?: boolean; -} - -const chartConfig = { - value: { - color: "hsl(var(--chart-1))", - label: "Memory Usage", - }, -} satisfies ChartConfig; - -export function ActorMemoryStats({ - interval = 15, - memory, - allocatedMemory, - metricsAt, - syncId, - isRunning = true, -}: ActorMemoryStatsProps) { - // Filter out trailing zeros in the last 15 seconds only if actor is still running - let filteredMemory = [...memory]; - if (isRunning) { - const secondsToCheck = 15; - const pointsToCheck = Math.ceil(secondsToCheck / interval); - - // Find the last non-zero value and cut off any zeros after it - for ( - let i = filteredMemory.length - 1; - i >= Math.max(0, filteredMemory.length - pointsToCheck); - i-- - ) { - if (filteredMemory[i] === 0) { - filteredMemory = filteredMemory.slice(0, i); - } else { - break; - } - } - } - - const data = filteredMemory.map((value, i) => ({ - x: `${(filteredMemory.length - i) * -interval}`, - value, - config: { - label: new Date( - metricsAt - - (filteredMemory.length - i) * timing.seconds(interval), - ), - }, - })); - - const max = allocatedMemory || Math.max(...filteredMemory); - - const id = useId(); - - const fillId = `fill-${id}`; - return ( - - - - - - `${Math.ceil((value / max) * 100)}%` - } - /> - { - return format(label, "HH:mm:ss"); - }} - valueFormatter={(value) => { - if (typeof value !== "number") { - return "n/a"; - } - return `${filesize(value)} (${Math.round((value / max) * 100).toFixed(2)}%)`; - }} - /> - } - /> - - - - - - - - - - ); -} diff --git a/frontend/packages/components/src/actors/actor-metrics-tab.tsx b/frontend/packages/components/src/actors/actor-metrics-tab.tsx deleted file mode 100644 index edc11a6618..0000000000 --- a/frontend/packages/components/src/actors/actor-metrics-tab.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Button, ScrollArea } from "@rivet-gg/components"; -import { Icon, faBooks } from "@rivet-gg/icons"; -import { ActorMetrics } from "./actor-metrics"; -import type { ActorAtom } from "./actor-context"; - -interface ActorMetricsTabProps { - actor: ActorAtom; -} - -export function ActorMetricsTab(props: ActorMetricsTabProps) { - return ( - -
- -
- -
- ); -} \ No newline at end of file diff --git a/frontend/packages/components/src/actors/actor-metrics.tsx b/frontend/packages/components/src/actors/actor-metrics.tsx deleted file mode 100644 index 947d1166dc..0000000000 --- a/frontend/packages/components/src/actors/actor-metrics.tsx +++ /dev/null @@ -1,704 +0,0 @@ -import { useAtomValue, useSetAtom } from "jotai"; -import { selectAtom } from "jotai/utils"; -import equal from "fast-deep-equal"; -import { useState, useMemo } from "react"; -import type { Actor, ActorAtom } from "./actor-context"; -import { ActorCpuStats } from "./actor-cpu-stats"; -import { ActorMemoryStats } from "./actor-memory-stats"; -import { Dd, Dl, Dt } from "../ui/typography"; -import { Button } from "../ui/button"; -import { Flex } from "../ui/flex"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select"; -import { actorMetricsTimeWindowAtom, actorEnvironmentAtom } from "./actor-context"; -import { useQuery } from "@tanstack/react-query"; -import { actorMetricsQueryOptions } from "@/domains/project/queries/actors/query-options"; - -const selector = (a: Actor) => ({ - metrics: a.metrics, - status: a.status, - resources: a.resources, - id: a.id, -}); - -const timeWindowOptions = [ - { label: "5 minutes", value: "5m", milliseconds: 5 * 60 * 1000 }, - { label: "15 minutes", value: "15m", milliseconds: 15 * 60 * 1000 }, - { label: "30 minutes", value: "30m", milliseconds: 30 * 60 * 1000 }, - { label: "1 hour", value: "1h", milliseconds: 60 * 60 * 1000 }, - { label: "3 hours", value: "3h", milliseconds: 3 * 60 * 60 * 1000 }, - { label: "6 hours", value: "6h", milliseconds: 6 * 60 * 60 * 1000 }, - { label: "12 hours", value: "12h", milliseconds: 12 * 60 * 60 * 1000 }, - { label: "24 hours", value: "24h", milliseconds: 24 * 60 * 60 * 1000 }, - { label: "2 days", value: "2d", milliseconds: 2 * 24 * 60 * 60 * 1000 }, -]; - -export interface ActorMetricsProps { - actor: ActorAtom; -} - -export function ActorMetrics({ actor }: ActorMetricsProps) { - const { metrics, status, resources, id } = useAtomValue( - selectAtom(actor, selector, equal), - ); - const defaultMetricsData = useAtomValue(metrics); - const [showAdvanced, setShowAdvanced] = useState(false); - - const timeWindowMs = useAtomValue(actorMetricsTimeWindowAtom); - const setTimeWindowMs = useSetAtom(actorMetricsTimeWindowAtom); - const environment = useAtomValue(actorEnvironmentAtom); - - const currentTimeWindow = timeWindowOptions.find(option => option.milliseconds === timeWindowMs) || timeWindowOptions[1]; - const [timeWindow, setTimeWindow] = useState(currentTimeWindow.value); - - const isActorRunning = status === "running"; - - // Create a query for time window-specific metrics - const { data: customMetricsData, status: customMetricsStatus } = useQuery({ - ...actorMetricsQueryOptions( - { - projectNameId: environment?.projectNameId || "", - environmentNameId: environment?.environmentNameId || "", - actorId: id, - timeWindowMs: timeWindowMs, - }, - { refetchInterval: 5000 } - ), - enabled: !!environment && !!id, - }); - - // Use custom metrics if available, otherwise fall back to default - const metricsData = customMetricsData ? { - metrics: customMetricsData.metrics, - rawData: customMetricsData.rawData, - interval: customMetricsData.interval, - status: customMetricsStatus, - updatedAt: Date.now(), - } : defaultMetricsData; - - const handleTimeWindowChange = (value: string) => { - setTimeWindow(value); - const selectedOption = timeWindowOptions.find(option => option.value === value); - if (selectedOption) { - setTimeWindowMs(selectedOption.milliseconds); - } - }; - - const formatBytes = (bytes: number | null | undefined) => { - if (!isActorRunning || bytes === null || bytes === undefined) - return "n/a"; - const mb = bytes / 1024 / 1024; - if (mb < 1024) { - return `${mb.toFixed(1)} MB`; - } - return `${(mb / 1024).toFixed(1)} GB`; - }; - - const formatCpuUsage = (cpu: number | null | undefined) => { - if (!isActorRunning || cpu === null || cpu === undefined) return "n/a"; - return `${(cpu * 100).toFixed(2)}%`; - }; - - const formatNumber = (value: number | null | undefined) => { - if (!isActorRunning || value === null || value === undefined) - return "n/a"; - return value.toLocaleString(); - }; - - const formatTimestamp = (timestamp: number | null | undefined) => { - if (!isActorRunning || timestamp === null || timestamp === undefined) - return "n/a"; - return new Date(timestamp * 1000).toLocaleString(); - }; - - // Calculate CPU percentage using time series data points - const cpuPercentage = useMemo(() => { - if (!isActorRunning) { - return "Stopped"; - } - - const data = metricsData; - if (!data || !data.rawData || !data.interval) { - return "n/a"; - } - - const cpuValues = data.rawData.cpu_usage_seconds_total; - if (!cpuValues || cpuValues.length < 2) { - return "n/a"; - } - - // Find the last valid CPU rate from the most recent data points - let cpuRate = 0; - for (let i = cpuValues.length - 1; i > 0; i--) { - const currentCpu = cpuValues[i]; - const previousCpu = cpuValues[i - 1]; - - if ( - currentCpu !== 0 && - previousCpu !== 0 && - currentCpu >= previousCpu - ) { - const cpuDelta = currentCpu - previousCpu; - const timeDelta = data.interval / 1000; // Convert ms to seconds - - // Rate calculation: CPU seconds used per second of real time - // This gives the fraction of available CPU used (0-1) - cpuRate = (cpuDelta / timeDelta) * 100; - break; - } - } - - return `${Math.min(cpuRate, 100).toFixed(2)}%`; - }, [metricsData, isActorRunning]); - - const calculateMemoryPercentage = ( - usage: number | null | undefined, - ) => { - if ( - !isActorRunning || - usage === null || - usage === undefined || - !resources || - !resources.memory || - resources.memory === 0 - ) { - return null; - } - // Convert usage from bytes to MB and compare with resources.memory (which is in MB) - const usageMB = usage / (1024 * 1024); - return (usageMB / resources.memory) * 100; - }; - - const isLoading = metricsData.status === "pending"; - const hasError = metricsData.status === "error"; - const data = metricsData.metrics || {}; - - if (isLoading) { - return ( -
-

Metrics

-
Loading...
-
- ); - } - - if (hasError) { - return ( -
-

Metrics

-
- Error loading metrics -
-
- ); - } - - const memoryPercentage = calculateMemoryPercentage( - data.memory_usage_bytes, - ); - - return ( -
-
-

Container Metrics

- -
- - {/* Main Metrics */} -
-
-
-
CPU Usage
-
- {cpuPercentage} - {metricsData.rawData?.cpu_usage_seconds_total && - metricsData.rawData.cpu_usage_seconds_total.length > 0 ? ( - - ) : null} -
-
-
-
Memory Usage
-
- - {formatBytes(data.memory_usage_bytes)} - {memoryPercentage !== null && ( - - ({memoryPercentage.toFixed(1)}%) - - )} - - {metricsData.rawData?.memory_usage_bytes && - metricsData.rawData.memory_usage_bytes.length > 0 ? ( - - ) : null} -
-
-
-
- - {/* Advanced Metrics */} - {false && ( - - {/* CPU & Performance */} -
-

CPU & Performance

-
-
CPU Load Average (10s)
-
{formatCpuUsage(data.cpu_load_average_10s)}
-
CPU Usage Seconds Total
-
- {formatNumber(data.cpu_usage_seconds_total)} -
-
CPU User Seconds Total
-
{formatNumber(data.cpu_user_seconds_total)}
-
CPU System Seconds Total
-
- {formatNumber(data.cpu_system_seconds_total)} -
-
CPU Schedstat Run Periods
-
- {formatNumber( - data.cpu_schedstat_run_periods_total, - )} -
-
CPU Schedstat Run Seconds
-
- {formatNumber( - data.cpu_schedstat_run_seconds_total, - )} -
-
CPU Schedstat Runqueue Seconds
-
- {formatNumber( - data.cpu_schedstat_runqueue_seconds_total, - )} -
-
-
- - {/* Memory */} -
-

Memory

-
-
Memory Usage
-
{formatBytes(data.memory_usage_bytes)}
-
Memory Working Set
-
- {formatBytes(data.memory_working_set_bytes)} -
-
Memory RSS
-
{formatBytes(data.memory_rss)}
-
Memory Cache
-
{formatBytes(data.memory_cache)}
-
Memory Swap
-
{formatBytes(data.memory_swap)}
-
Memory Max Usage
-
{formatBytes(data.memory_max_usage_bytes)}
-
Memory Mapped File
-
{formatBytes(data.memory_mapped_file)}
-
Memory Failcnt
-
{formatNumber(data.memory_failcnt)}
-
-
- - {/* Memory Failures */} -
-

Memory Failures

-
-
Page Fault (Container)
-
- {formatNumber( - data.memory_failures_pgfault_container, - )} -
-
Page Fault (Hierarchy)
-
- {formatNumber( - data.memory_failures_pgfault_hierarchy, - )} -
-
Major Page Fault (Container)
-
- {formatNumber( - data.memory_failures_pgmajfault_container, - )} -
-
Major Page Fault (Hierarchy)
-
- {formatNumber( - data.memory_failures_pgmajfault_hierarchy, - )} -
-
-
- - {/* Resource Limits */} -
-

Resource Limits

-
-
Memory Limit
-
{resources?.memory ? `${resources.memory} MB` : "n/a"}
-
CPU Limit
-
{resources?.cpu ? `${resources.cpu / 1000} cores` : "n/a"}
-
-
- - {/* Processes & Threads */} -
-

- Processes & Threads -

-
-
Processes
-
{formatNumber(data.processes)}
-
Threads
-
{formatNumber(data.threads)}
-
Max Threads
-
{formatNumber(data.threads_max)}
-
Tasks Running
-
{formatNumber(data.tasks_state_running)}
-
Tasks Sleeping
-
{formatNumber(data.tasks_state_sleeping)}
-
Tasks Stopped
-
{formatNumber(data.tasks_state_stopped)}
-
Tasks IO Waiting
-
{formatNumber(data.tasks_state_iowaiting)}
-
Tasks Uninterruptible
-
- {formatNumber(data.tasks_state_uninterruptible)} -
-
-
- - {/* Filesystem */} -
-

Filesystem

-
-
Reads Bytes Total (sda)
-
- {formatBytes(data.fs_reads_bytes_total_sda)} -
-
Writes Bytes Total (sda)
-
- {formatBytes(data.fs_writes_bytes_total_sda)} -
-
-
- - {/* Network - Receive */} -
-

Network - Receive

-
-
Bytes Total (eth0)
-
- {formatBytes( - data.network_receive_bytes_total_eth0, - )} -
-
Bytes Total (eth1)
-
- {formatBytes( - data.network_receive_bytes_total_eth1, - )} -
-
Errors Total (eth0)
-
- {formatNumber( - data.network_receive_errors_total_eth0, - )} -
-
Errors Total (eth1)
-
- {formatNumber( - data.network_receive_errors_total_eth1, - )} -
-
Packets Dropped (eth0)
-
- {formatNumber( - data.network_receive_packets_dropped_total_eth0, - )} -
-
Packets Dropped (eth1)
-
- {formatNumber( - data.network_receive_packets_dropped_total_eth1, - )} -
-
Packets Total (eth0)
-
- {formatNumber( - data.network_receive_packets_total_eth0, - )} -
-
Packets Total (eth1)
-
- {formatNumber( - data.network_receive_packets_total_eth1, - )} -
-
-
- - {/* Network - Transmit */} -
-

Network - Transmit

-
-
Bytes Total (eth0)
-
- {formatBytes( - data.network_transmit_bytes_total_eth0, - )} -
-
Bytes Total (eth1)
-
- {formatBytes( - data.network_transmit_bytes_total_eth1, - )} -
-
Errors Total (eth0)
-
- {formatNumber( - data.network_transmit_errors_total_eth0, - )} -
-
Errors Total (eth1)
-
- {formatNumber( - data.network_transmit_errors_total_eth1, - )} -
-
Packets Dropped (eth0)
-
- {formatNumber( - data.network_transmit_packets_dropped_total_eth0, - )} -
-
Packets Dropped (eth1)
-
- {formatNumber( - data.network_transmit_packets_dropped_total_eth1, - )} -
-
Packets Total (eth0)
-
- {formatNumber( - data.network_transmit_packets_total_eth0, - )} -
-
Packets Total (eth1)
-
- {formatNumber( - data.network_transmit_packets_total_eth1, - )} -
-
-
- - {/* TCP Connections */} -
-

TCP Connections

-
-
Close
-
- {formatNumber(data.network_tcp_usage_close)} -
-
Close Wait
-
- {formatNumber( - data.network_tcp_usage_closewait, - )} -
-
Closing
-
- {formatNumber(data.network_tcp_usage_closing)} -
-
Established
-
- {formatNumber( - data.network_tcp_usage_established, - )} -
-
Fin Wait 1
-
- {formatNumber(data.network_tcp_usage_finwait1)} -
-
Fin Wait 2
-
- {formatNumber(data.network_tcp_usage_finwait2)} -
-
Last Ack
-
- {formatNumber(data.network_tcp_usage_lastack)} -
-
Listen
-
- {formatNumber(data.network_tcp_usage_listen)} -
-
Syn Recv
-
- {formatNumber(data.network_tcp_usage_synrecv)} -
-
Syn Sent
-
- {formatNumber(data.network_tcp_usage_synsent)} -
-
Time Wait
-
- {formatNumber(data.network_tcp_usage_timewait)} -
-
-
- - {/* TCP6 Connections */} -
-

TCP6 Connections

-
-
Close
-
- {formatNumber(data.network_tcp6_usage_close)} -
-
Close Wait
-
- {formatNumber( - data.network_tcp6_usage_closewait, - )} -
-
Closing
-
- {formatNumber(data.network_tcp6_usage_closing)} -
-
Established
-
- {formatNumber( - data.network_tcp6_usage_established, - )} -
-
Fin Wait 1
-
- {formatNumber(data.network_tcp6_usage_finwait1)} -
-
Fin Wait 2
-
- {formatNumber(data.network_tcp6_usage_finwait2)} -
-
Last Ack
-
- {formatNumber(data.network_tcp6_usage_lastack)} -
-
Listen
-
- {formatNumber(data.network_tcp6_usage_listen)} -
-
Syn Recv
-
- {formatNumber(data.network_tcp6_usage_synrecv)} -
-
Syn Sent
-
- {formatNumber(data.network_tcp6_usage_synsent)} -
-
Time Wait
-
- {formatNumber(data.network_tcp6_usage_timewait)} -
-
-
- - {/* UDP Connections */} -
-

UDP Connections

-
-
Dropped
-
- {formatNumber(data.network_udp_usage_dropped)} -
-
Listen
-
- {formatNumber(data.network_udp_usage_listen)} -
-
RX Queued
-
- {formatNumber(data.network_udp_usage_rxqueued)} -
-
TX Queued
-
- {formatNumber(data.network_udp_usage_txqueued)} -
-
-
- - {/* UDP6 Connections */} -
-

UDP6 Connections

-
-
Dropped
-
- {formatNumber(data.network_udp6_usage_dropped)} -
-
Listen
-
- {formatNumber(data.network_udp6_usage_listen)} -
-
RX Queued
-
- {formatNumber(data.network_udp6_usage_rxqueued)} -
-
TX Queued
-
- {formatNumber(data.network_udp6_usage_txqueued)} -
-
-
- - {/* System */} -
-

System

-
-
File Descriptors
-
{formatNumber(data.file_descriptors)}
-
Sockets
-
{formatNumber(data.sockets)}
-
Last Seen
-
{formatTimestamp(data.last_seen)}
-
Start Time
-
{formatTimestamp(data.start_time_seconds)}
-
-
-
- )} -
- ); -} \ No newline at end of file diff --git a/frontend/packages/components/src/actors/actor-network.tsx b/frontend/packages/components/src/actors/actor-network.tsx deleted file mode 100644 index 3bb466ad99..0000000000 --- a/frontend/packages/components/src/actors/actor-network.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import { - Button, - Dd, - DiscreteCopyButton, - Dl, - DocsSheet, - Dt, - Flex, - cn, -} from "@rivet-gg/components"; -import { Icon, faBooks } from "@rivet-gg/icons"; -import { useAtomValue } from "jotai"; -import { selectAtom } from "jotai/utils"; -import { Fragment } from "react"; -import type { Actor, ActorAtom } from "./actor-context"; -import { ActorObjectInspector } from "./console/actor-inspector"; - -const selector = (a: Actor) => a.network?.ports; - -export interface ActorNetworkProps { - actor: ActorAtom; -} - -export function ActorNetwork({ actor }: ActorNetworkProps) { - const ports = useAtomValue(selectAtom(actor, selector)); - if (!ports) { - return null; - } - - return ( -
-
-

Network

- - - -
-
- -
-
Ports
-
- {Object.entries(ports).map( - ([name, port], index) => ( - - - {name} - -
-
Protocol
-
- - {port.protocol} - -
-
Port
-
- - {port.port} - -
-
Hostname
-
- - - {port.hostname} - - -
- {port.url ? ( - <> -
URL
-
- - - {port.url} - - -
- - ) : null} - - {port.routing?.host ? ( - <> -
Host Routing
-
- - - -
- - ) : null} -
-
- ), - )} -
-
-
-
-
- ); -} diff --git a/frontend/packages/components/src/actors/actor-not-found.tsx b/frontend/packages/components/src/actors/actor-not-found.tsx deleted file mode 100644 index c1beda2464..0000000000 --- a/frontend/packages/components/src/actors/actor-not-found.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { Icon, faQuestionSquare } from "@rivet-gg/icons"; -import { useAtomValue, useSetAtom } from "jotai"; -import { selectAtom } from "jotai/utils"; -import { useCallback } from "react"; -import { Button } from "../ui/button"; -import { FilterOp } from "../ui/filters"; -import { - type ActorFeature, - actorFiltersAtom, - currentActorQueryAtom, -} from "./actor-context"; -import { ActorTabs } from "./actors-actor-details"; -import { useActorsView } from "./actors-view-context-provider"; -import { ShimmerLine } from "../shimmer-line"; - -export function ActorNotFound({ - features = [], -}: { features?: ActorFeature[] }) { - const { copy } = useActorsView(); - - const setFilters = useSetAtom(actorFiltersAtom); - const hasDevMode = useAtomValue( - selectAtom( - actorFiltersAtom, - useCallback((filters) => filters.devMode, []), - ), - ); - - const { isLoading } = useAtomValue(currentActorQueryAtom); - - return ( -
- -
- {!isLoading ? ( - <> - -

- {copy.actorNotFound} -

-

- {copy.actorNotFoundDescription} -

- - ) : null} - - {!hasDevMode && !isLoading ? ( - - ) : null} - {isLoading ? : null} -
-
-
- ); -} diff --git a/frontend/packages/components/src/actors/actor-region.tsx b/frontend/packages/components/src/actors/actor-region.tsx deleted file mode 100644 index 12b7eb3e2e..0000000000 --- a/frontend/packages/components/src/actors/actor-region.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { Flex, WithTooltip } from "@rivet-gg/components"; -import { useAtomValue } from "jotai"; -import { selectAtom } from "jotai/utils"; -import { useCallback } from "react"; -import { - REGION_LABEL, - RegionIcon, - getRegionKey, -} from "../matchmaker/lobby-region"; -import { actorRegionsAtom } from "./actor-context"; - -interface ActorRegionProps { - regionId?: string; - showLabel?: boolean | "abbreviated"; - className?: string; -} - -export function ActorRegion({ - showLabel, - regionId, - className, -}: ActorRegionProps) { - const region = useAtomValue( - selectAtom( - actorRegionsAtom, - useCallback( - (regions) => regions.find((region) => region.id === regionId), - [regionId], - ), - ), - ); - - const regionKey = getRegionKey(region?.id); - - if (showLabel) { - return ( - - - - {showLabel === "abbreviated" - ? regionKey.toUpperCase() - : (REGION_LABEL[regionKey] ?? REGION_LABEL.unknown)} - - - ); - } - - return ( - - - - } - /> - ); -} diff --git a/frontend/packages/components/src/actors/actor-runtime.tsx b/frontend/packages/components/src/actors/actor-runtime.tsx deleted file mode 100644 index e8c0f181a8..0000000000 --- a/frontend/packages/components/src/actors/actor-runtime.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import equal from "fast-deep-equal"; -import { useAtomValue } from "jotai"; -import { selectAtom } from "jotai/utils"; -import { Suspense } from "react"; -import { formatDuration } from "../lib/formatter"; -import { toRecord } from "../lib/utils"; -import { Flex } from "../ui/flex"; -import { Skeleton } from "../ui/skeleton"; -import { Dd, Dl, Dt } from "../ui/typography"; -import { ActorBuild } from "./actor-build"; -import { - type Actor, - type ActorAtom, - ActorFeature, - currentActorFeaturesAtom, -} from "./actor-context"; -import { ACTOR_FRAMEWORK_TAG_VALUE } from "./actor-tags"; -import { ActorObjectInspector } from "./console/actor-inspector"; - -const selector = (a: Actor) => ({ - lifecycle: a.lifecycle, - resources: a.resources, - runtime: a.runtime, - tags: a.tags, -}); - -export interface ActorRuntimeProps { - actor: ActorAtom; -} - -export function ActorRuntime({ actor }: ActorRuntimeProps) { - const { lifecycle, resources, runtime, tags } = useAtomValue( - selectAtom(actor, selector, equal), - ); - - const features = useAtomValue(currentActorFeaturesAtom); - - return ( - <> - {features.includes(ActorFeature.Runtime) && lifecycle && runtime ? ( -
-
-

Runtime

-
- -
-
Kill timeout
-
- {formatDuration(lifecycle.killTimeout || 0, { - show0Min: true, - })} -
- {toRecord(tags).framework !== - ACTOR_FRAMEWORK_TAG_VALUE && resources ? ( - <> -
Resources
-
- {resources.cpu / 1000} CPU cores,{" "} - {resources.memory} MB RAM -
- - ) : null} -
Arguments
-
- -
-
Environment
-
- -
- -
Durable
-
{lifecycle.durable ? "Yes" : "No"}
-
-
-
- ) : null} - - } - > - - - - ); -} diff --git a/frontend/packages/components/src/actors/actor-state-change-indicator.tsx b/frontend/packages/components/src/actors/actor-state-change-indicator.tsx deleted file mode 100644 index b5d1400d85..0000000000 --- a/frontend/packages/components/src/actors/actor-state-change-indicator.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { Badge } from "@rivet-gg/components"; -import { motion } from "framer-motion"; -import { useEffect, useRef } from "react"; - -const EMPTY_OBJECT = {}; - -interface ActorStateChangeIndicatorProps { - state: unknown | undefined; -} - -export function ActorStateChangeIndicator({ - state, -}: ActorStateChangeIndicatorProps) { - const isMounted = useRef(false); - const oldState = useRef(); - - useEffect(() => { - isMounted.current = true; - }, []); - - useEffect(() => { - oldState.current = state || EMPTY_OBJECT; - }, [state]); - - const hasChanged = state !== oldState.current; - const shouldUpdate = hasChanged && isMounted.current; - - return ( - - - State changed - - - ); -} diff --git a/frontend/packages/components/src/actors/actor-state-tab.tsx b/frontend/packages/components/src/actors/actor-state-tab.tsx deleted file mode 100644 index 634596a9b5..0000000000 --- a/frontend/packages/components/src/actors/actor-state-tab.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { useAtomValue } from "jotai"; -import { selectAtom } from "jotai/utils"; -import type { Actor, ActorAtom } from "./actor-context"; -import { ActorEditableState } from "./actor-editable-state"; -import { - useActorState, - useActorWorkerStatus, -} from "./worker/actor-worker-context"; - -const selector = (a: Actor) => a.destroyedAt; - -interface ActorStateTabProps { - actor: ActorAtom; -} - -export function ActorStateTab({ actor }: ActorStateTabProps) { - const destroyedAt = useAtomValue(selectAtom(actor, selector)); - const status = useActorWorkerStatus(); - - const state = useActorState(); - - if (destroyedAt) { - return ( -
- State Preview is unavailable for inactive Actors. -
- ); - } - - if (status.type === "error") { - return ( -
- State Preview is currently unavailable. -
- See console/logs for more details. -
- ); - } - - if (status.type === "unsupported") { - return ( -
- State Preview is not supported for this Actor. -
- ); - } - - if (status.type !== "ready") { - return ( -
- Loading state... -
- ); - } - - return ( -
- -
- ); -} diff --git a/frontend/packages/components/src/actors/actor-status-indicator.tsx b/frontend/packages/components/src/actors/actor-status-indicator.tsx deleted file mode 100644 index 81f9ff34dc..0000000000 --- a/frontend/packages/components/src/actors/actor-status-indicator.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { Ping, cn } from "@rivet-gg/components"; -import { useAtomValue } from "jotai"; -import { selectAtom } from "jotai/utils"; -import type { ComponentPropsWithRef } from "react"; -import type { Actor, ActorAtom } from "./actor-context"; - -export type ActorStatus = - | "starting" - | "running" - | "stopped" - | "crashed" - | "unknown"; - -export function getActorStatus( - actor: Pick, -): ActorStatus { - const { createdAt, startedAt, destroyedAt } = actor; - - if (createdAt && !startedAt && !destroyedAt) { - return "starting"; - } - - if (createdAt && startedAt && !destroyedAt) { - return "running"; - } - - if (createdAt && startedAt && destroyedAt) { - return "stopped"; - } - - if (createdAt && !startedAt && destroyedAt) { - return "crashed"; - } - - return "unknown"; -} - -interface AtomizedActorStatusIndicatorProps - extends ComponentPropsWithRef<"span"> { - actor: ActorAtom; -} - -export const AtomizedActorStatusIndicator = ({ - actor, - ...props -}: AtomizedActorStatusIndicatorProps) => { - const status = useAtomValue(selectAtom(actor, selector)); - return ; -}; - -const selector = ({ status }: Actor) => status; - -interface ActorStatusIndicatorProps extends ComponentPropsWithRef<"span"> { - status: ReturnType; -} - -export const ActorStatusIndicator = ({ - status, - ...props -}: ActorStatusIndicatorProps) => { - if (status === "running") { - return ( - - ); - } - - return ( - - ); -}; diff --git a/frontend/packages/components/src/actors/actor-status-label.tsx b/frontend/packages/components/src/actors/actor-status-label.tsx deleted file mode 100644 index c6a16ddb7d..0000000000 --- a/frontend/packages/components/src/actors/actor-status-label.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { useAtomValue } from "jotai"; -import { selectAtom } from "jotai/utils"; -import type { Actor, ActorAtom } from "./actor-context"; -import type { ActorStatus } from "./actor-status-indicator"; - -export const ACTOR_STATUS_LABEL_MAP = { - unknown: "Unknown", - starting: "Starting", - running: "Running", - stopped: "Stopped", - crashed: "Crashed", -} satisfies Record; - -export const ActorStatusLabel = ({ status }: { status: ActorStatus }) => { - return {ACTOR_STATUS_LABEL_MAP[status]}; -}; - -const selector = (a: Actor) => a.status; - -export const AtomizedActorStatusLabel = ({ - actor, -}: { - actor: ActorAtom; -}) => { - const status = useAtomValue(selectAtom(actor, selector)); - return ; -}; diff --git a/frontend/packages/components/src/actors/actor-status.tsx b/frontend/packages/components/src/actors/actor-status.tsx deleted file mode 100644 index 0f587ed7cb..0000000000 --- a/frontend/packages/components/src/actors/actor-status.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { cn } from "@rivet-gg/components"; -import type { ActorAtom } from "./actor-context"; -import { - ActorStatusIndicator, - type ActorStatus as ActorStatusType, - AtomizedActorStatusIndicator, -} from "./actor-status-indicator"; -import { - ActorStatusLabel, - AtomizedActorStatusLabel, -} from "./actor-status-label"; - -interface ActorStatusProps { - className?: string; - actor: ActorAtom; -} - -export const AtomizedActorStatus = ({ - className, - ...props -}: ActorStatusProps) => { - return ( -
- - -
- ); -}; - -export const ActorStatus = ({ - className, - status, -}: { - className?: string; - status: ActorStatusType; -}) => { - return ( -
- - -
- ); -}; diff --git a/frontend/packages/components/src/actors/actor-stop-button.tsx b/frontend/packages/components/src/actors/actor-stop-button.tsx deleted file mode 100644 index cbc67ea89f..0000000000 --- a/frontend/packages/components/src/actors/actor-stop-button.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { Button, WithTooltip } from "@rivet-gg/components"; -import { Icon, faXmark } from "@rivet-gg/icons"; - -import equal from "fast-deep-equal"; -import { useAtomValue } from "jotai"; -import { selectAtom } from "jotai/utils"; -import type { Actor, ActorAtom, DestroyActorAtom } from "./actor-context"; - -const selector = (a: Actor) => ({ - destroyedAt: a.destroyedAt, - destroy: a.destroy, -}); - -interface ActorStopButtonProps { - actor: ActorAtom; -} - -export function ActorStopButton({ actor }: ActorStopButtonProps) { - const { destroy: destroyAtom, destroyedAt } = useAtomValue( - selectAtom(actor, selector, equal), - ); - - if (destroyedAt || !destroyAtom) { - return null; - } - - return ; -} - -function Content({ destroy: destroyAtom }: { destroy: DestroyActorAtom }) { - const { destroy, isDestroying } = useAtomValue(destroyAtom); - return ( - - - - } - content="Stop Actor" - /> - ); -} diff --git a/frontend/packages/components/src/actors/actor-tags-select.tsx b/frontend/packages/components/src/actors/actor-tags-select.tsx deleted file mode 100644 index 17eedbba3e..0000000000 --- a/frontend/packages/components/src/actors/actor-tags-select.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import { Combobox } from "@rivet-gg/components"; -import { useAtomValue } from "jotai"; -import { useMemo } from "react"; -import { actorTagsAtom } from "./actor-context"; -import { ActorTag } from "./actor-tags"; - -interface ActorTagsSelectProps { - value: Record; - onValueChange: (value: Record) => void; - showSelectedOptions?: number; -} - -export function ActorTagsSelect({ - value, - onValueChange, - showSelectedOptions, -}: ActorTagsSelectProps) { - const data = useAtomValue(actorTagsAtom); - - const valArray = useMemo(() => Object.entries(value), [value]); - const tags = useMemo(() => { - // upsert custom tags to the list of tags - const tags = [...data]; - for (const [key, value] of valArray) { - const found = data.find( - (tag) => tag.key === key && tag.value === value, - ); - - if (!found) { - tags.push({ key, value }); - } - } - return tags; - }, [valArray, data]); - - const val = useMemo( - () => - valArray.map(([key, value]) => { - return [key, value].join("="); - }), - [valArray], - ); - - const options = useMemo( - () => - tags.map((tag) => { - return { - label: ( - - - {tag.key}={tag.value} - - - ), - value: [tag.key, tag.value].join("="), - tag, - }; - }), - [tags], - ); - - const handleValueChange = (value: string[]) => { - onValueChange( - Object.fromEntries( - value.map((v) => { - // its safe to split by "=" because the value is a tag - const [key, value] = v.split("="); - return [key.trim(), value.trim()]; - }), - ), - ); - }; - - const handleCreateOption = (option: string) => { - const parts = option.split("="); - if (parts.length !== 2) return; - - const [key, value] = parts.map((part) => part.trim()); - if (!key || !value) return; - - onValueChange(Object.fromEntries([...valArray, [key, value]])); - }; - - return ( - { - const tagKey = option.tag.key.toLowerCase(); - const tagValue = option.tag.value.toLowerCase(); - - if (search.includes("=")) { - const [key, value] = search.split("="); - return tagKey.includes(key) && tagValue.includes(value); - } - return tagKey.includes(search) || tagValue.includes(search); - }} - className="w-full min-w-[20rem]" - /> - ); -} diff --git a/frontend/packages/components/src/actors/actor-tags.tsx b/frontend/packages/components/src/actors/actor-tags.tsx deleted file mode 100644 index 5a017d5cb0..0000000000 --- a/frontend/packages/components/src/actors/actor-tags.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import { - Button, - DiscreteCopyButton, - Slot, - Slottable, - WithTooltip, - cn, -} from "@rivet-gg/components"; -import { Icon, faTag } from "@rivet-gg/icons"; -import { type ReactNode, forwardRef, useState } from "react"; - -const BUILT_IN_TAGS = { - actors: ["framework", "framework-version"], - builds: ["current"], -}; - -export const ACTOR_FRAMEWORK_TAG_VALUE = "rivetkit"; - -export const ActorTag = forwardRef< - HTMLSpanElement, - { children: ReactNode; className?: string } ->(({ children, className, ...props }, ref) => ( - - - {children} - -)); - -interface ActorTagsProps { - tags?: unknown; - excludeBuiltIn?: keyof typeof BUILT_IN_TAGS; - className?: string; - truncate?: boolean; - copy?: boolean; - max?: number; - hoverable?: boolean; -} - -export function ActorTags({ - tags = {}, - excludeBuiltIn = undefined, - truncate = true, - className, - hoverable = true, - max = Number.POSITIVE_INFINITY, - copy = true, -}: ActorTagsProps) { - const withoutBuiltIn = Object.entries(tags ?? {}).filter(([key]) => - excludeBuiltIn ? !BUILT_IN_TAGS[excludeBuiltIn].includes(key) : true, - ); - - const [isTruncatedList, setTruncatedList] = useState( - withoutBuiltIn.length > max, - ); - - const truncated = withoutBuiltIn.filter((_, index) => - isTruncatedList ? index < max : true, - ); - - const truncatedCount = withoutBuiltIn.length - truncated.length; - - return ( -
- {truncated.length > 0 ? ( - <> - {truncated - .sort(([a], [b]) => a.localeCompare(b)) - .map(([key, value]) => { - let trigger = truncate ? ( - - - {key}={value} - - - ) : ( - - - {key}={value} - - - ); - - trigger = copy ? ( - - - {trigger} - - - ) : ( - trigger - ); - - return truncate && hoverable && !copy ? ( - - ) : ( - trigger - ); - })} - - {truncatedCount > 0 ? ( - - ) : null} - - ) : null} -
- ); -} diff --git a/frontend/packages/components/src/actors/actors-actor-details.tsx b/frontend/packages/components/src/actors/actors-actor-details.tsx deleted file mode 100644 index e45a7bc48e..0000000000 --- a/frontend/packages/components/src/actors/actors-actor-details.tsx +++ /dev/null @@ -1,240 +0,0 @@ -import { - Flex, - Tabs, - TabsContent, - TabsList, - TabsTrigger, - cn, -} from "@rivet-gg/components"; -import { Icon, faQuestionSquare } from "@rivet-gg/icons"; -import { useAtomValue } from "jotai"; -import { type ReactNode, Suspense, memo } from "react"; -import { ActorConfigTab } from "./actor-config-tab"; -import { ActorConnectionsTab } from "./actor-connections-tab"; -import { - type ActorAtom, - ActorFeature, - currentActorFeaturesAtom, -} from "./actor-context"; -import { ActorDetailsSettingsProvider } from "./actor-details-settings"; -import { ActorLogsTab } from "./actor-logs-tab"; -import { ActorMetricsTab } from "./actor-metrics-tab"; -import { ActorStateTab } from "./actor-state-tab"; -import { AtomizedActorStatus } from "./actor-status"; -import { ActorStopButton } from "./actor-stop-button"; -import { ActorsSidebarToggleButton } from "./actors-sidebar-toggle-button"; -import { useActorsView } from "./actors-view-context-provider"; -import { ActorConsole } from "./console/actor-console"; -import { ActorWorkerContextProvider } from "./worker/actor-worker-context"; - -interface ActorsActorDetailsProps { - tab?: string; - actor: ActorAtom; - onTabChange?: (tab: string) => void; - onExportLogs?: ( - actorId: string, - typeFilter?: string, - filter?: string, - ) => Promise; - isExportingLogs?: boolean; -} - -export const ActorsActorDetails = memo( - ({ - tab, - onTabChange, - actor, - onExportLogs, - isExportingLogs, - }: ActorsActorDetailsProps) => { - const actorFeatures = useAtomValue(currentActorFeaturesAtom); - const supportsConsole = actorFeatures?.includes(ActorFeature.Console); - - return ( - - -
- - - {supportsConsole ? : null} -
-
-
- ); - }, -); - -export const ActorsActorEmptyDetails = ({ - features, -}: { - features: ActorFeature[]; -}) => { - const { copy } = useActorsView(); - return ( -
- -
- -

{copy.selectActor}

-
-
-
- ); -}; - -export function ActorTabs({ - tab, - features, - onTabChange, - actor, - className, - disabled, - children, - onExportLogs, - isExportingLogs, -}: { - disabled?: boolean; - tab?: string; - features: ActorFeature[]; - onTabChange?: (tab: string) => void; - actor?: ActorAtom; - className?: string; - children?: ReactNode; - onExportLogs?: ( - actorId: string, - typeFilter?: string, - filter?: string, - ) => Promise; - isExportingLogs?: boolean; -}) { - const supportsState = features?.includes(ActorFeature.State); - const supportsLogs = features?.includes(ActorFeature.Logs); - const supportsConnections = features?.includes(ActorFeature.Connections); - const supportsConfig = features?.includes(ActorFeature.Config); - const supportsMetrics = features?.includes(ActorFeature.Metrics); - - const defaultTab = supportsState ? "state" : "logs"; - const value = disabled ? undefined : tab || defaultTab; - - return ( - -
- -
- - {supportsState ? ( - - State - - ) : null} - {supportsConnections ? ( - - Connections - - ) : null} - {supportsLogs ? ( - - Logs - - ) : null} - {supportsConfig ? ( - - Config - - ) : null} - {supportsMetrics ? ( - - Metrics - - ) : null} - - {actor ? ( - - - - - ) : null} -
-
- {actor ? ( - <> - {supportsLogs ? ( - - }> - - - - ) : null} - {supportsConfig ? ( - - - - ) : null} - {supportsConnections ? ( - - - - ) : null} - {supportsState ? ( - - - - ) : null} - {supportsMetrics ? ( - - - - ) : null} - - ) : null} - {children} -
- ); -} diff --git a/frontend/packages/components/src/actors/actors-actor-not-found.tsx b/frontend/packages/components/src/actors/actors-actor-not-found.tsx deleted file mode 100644 index 96fed3be76..0000000000 --- a/frontend/packages/components/src/actors/actors-actor-not-found.tsx +++ /dev/null @@ -1,33 +0,0 @@ -// import { isRivetError } from "@/lib/utils"; -// import { RivetError } from "@rivet-gg/api"; -import { Icon, faCircleExclamation } from "@rivet-gg/icons"; -import type { ErrorComponentProps } from "@tanstack/react-router"; -import { ActorsSidebarToggleButton } from "./actors-sidebar-toggle-button"; - -export function ActorsActorError({ error }: ErrorComponentProps) { - // if (isRivetError(error) || error instanceof RivetError) { - // return ( - //
- //
- // - //
- //
- // - // Actor not found. - //
- //
- // ); - // } - - return ( -
-
- -
-
- - Error occurred while fetching Actor. -
-
- ); -} diff --git a/frontend/packages/components/src/actors/actors-layout-context.tsx b/frontend/packages/components/src/actors/actors-layout-context.tsx deleted file mode 100644 index a40a84c760..0000000000 --- a/frontend/packages/components/src/actors/actors-layout-context.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import type { ReactNode } from "react"; -import { createContext, useContext, useMemo } from "react"; -import { assertNonNullable } from "../lib/utils"; - -export interface ActorsLayoutContextValue { - isFolded: boolean; - setFolded: (value: boolean) => void; -} - -export const ActorsLayoutContext = - createContext(null); - -interface ActorsLayoutProviderProps extends ActorsLayoutContextValue { - children: ReactNode; -} - -export function ActorsLayoutContextProvider({ - children, - isFolded, - setFolded, -}: ActorsLayoutProviderProps) { - return ( - ({ isFolded, setFolded }), - [isFolded, setFolded], - )} - > - {children} - - ); -} - -export function useActorsLayout() { - const context = useContext(ActorsLayoutContext); - assertNonNullable(context); - return context; -} diff --git a/frontend/packages/components/src/actors/actors-layout.tsx b/frontend/packages/components/src/actors/actors-layout.tsx deleted file mode 100644 index df2a8d4b32..0000000000 --- a/frontend/packages/components/src/actors/actors-layout.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { type ReactNode, memo, useState } from "react"; -import { cn, ls } from "../lib/utils"; -import { ActorsLayoutContextProvider } from "./actors-layout-context"; - -interface ActorsListPreviewProps { - left: ReactNode; - right: ReactNode; - className?: string; -} - -export const ActorsLayout = memo( - ({ left, right, className }: ActorsListPreviewProps) => { - const [folded, setFolded] = useState(() => ls.actorsList.getFolded()); - - return ( - -
- {left} -
- {right} -
-
-
- ); - }, -); diff --git a/frontend/packages/components/src/actors/actors-list-panel.tsx b/frontend/packages/components/src/actors/actors-list-panel.tsx deleted file mode 100644 index aea9d29000..0000000000 --- a/frontend/packages/components/src/actors/actors-list-panel.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { ActorsList } from "./actors-list"; - -export function ActorsListPanel() { - return ; -} diff --git a/frontend/packages/components/src/actors/actors-list-preview.tsx b/frontend/packages/components/src/actors/actors-list-preview.tsx deleted file mode 100644 index d26a22acab..0000000000 --- a/frontend/packages/components/src/actors/actors-list-preview.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import { Icon, faGripDotsVertical } from "@rivet-gg/icons"; -import { - animate, - motion, - useMotionTemplate, - useMotionValue, - useMotionValueEvent, - useTransform, -} from "framer-motion"; -import { - type ReactNode, - Suspense, - memo, - useCallback, - useLayoutEffect, - useState, -} from "react"; -import { cn, ls } from "../lib/utils"; -import { ActorsLayoutContextProvider } from "./actors-layout-context"; -import { ActorsListPanel } from "./actors-list-panel"; - -const RIGHT_PANEL_MIN_WIDTH = 480; - -interface ActorsListPreviewProps { - children: ReactNode; -} - -export const ActorsListPreview = memo( - ({ children }: ActorsListPreviewProps) => { - const outerWidth = useMotionValue(0); - const x = useMotionValue(0); - - const rightWidth = useMotionTemplate`calc(50% - ${x}px)`; - const leftWidth = useMotionTemplate`calc(50% + ${x}px)`; - - const [folded, setFolded] = useState(() => ls.actorsList.getFolded()); - const [isDragging, setIsDragging] = useState(false); - - const [, setInitialized] = useState(false); - - useMotionValueEvent(x, "change", (value) => { - ls.actorsList.set(value / outerWidth.get(), folded); - }); - - // biome-ignore lint/correctness/useExhaustiveDependencies: on first draw - useLayoutEffect(() => { - x.setCurrent((ls.actorsList.getWidth() || 0) * outerWidth.get()); - setInitialized(true); - }, []); - - const relativeOffset = useTransform( - [x, outerWidth], - ([value, outerWidth]: number[]) => { - const center = outerWidth / 2; - const percent = (value + center) / outerWidth; - // 0.5 is the center - // 0 is the left - // 1 is the right - return percent; - }, - ); - - const opacity = useTransform(relativeOffset, [0, 0.1], [0, 1]); - const pointerEvents = useTransform(opacity, () => { - return opacity.get() > 0.5 ? "auto" : "none"; - }); - - const toggle = useCallback( - (newValue: boolean) => { - setFolded(newValue); - if (newValue) { - animate(x, -outerWidth.get() / 2); - } else { - animate(x, 0); - } - }, - [outerWidth, x], - ); - - return ( - -
{ - if (ref) { - const width = ref.getBoundingClientRect().width; - outerWidth.set(width); - } - }} - > - - - - setIsDragging(true)} - onDrag={(e, info) => { - const rightPos = outerWidth.get() - info.point.x; - setFolded(outerWidth.get() - rightPos < 470); - }} - onDoubleClick={() => { - setFolded(!folded); - if (folded) { - animate(x, 0); - } else { - animate(x, -outerWidth.get() / 2); - } - }} - onDragEnd={(e, info) => { - if (folded) { - animate(x, -outerWidth.get() / 2); - } else { - const leftPos = outerWidth.get() - info.point.x; - if (leftPos < RIGHT_PANEL_MIN_WIDTH) { - animate( - x, - outerWidth.get() / 2 - - RIGHT_PANEL_MIN_WIDTH, - ); - } - } - setIsDragging(false); - }} - dragMomentum={false} - className="w-[1px] bg-border cursor-col-resize inset-y-0 z-20 relative flex items-center group" - > - - - - {children} - -
-
- ); - }, -); diff --git a/frontend/packages/components/src/actors/actors-list-row.tsx b/frontend/packages/components/src/actors/actors-list-row.tsx deleted file mode 100644 index 6108b4df0c..0000000000 --- a/frontend/packages/components/src/actors/actors-list-row.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import { - Button, - RelativeTime, - SmallText, - WithTooltip, - cn, - toRecord, -} from "@rivet-gg/components"; -import { Icon, faTag, faTags } from "@rivet-gg/icons"; -import { Link } from "@tanstack/react-router"; -import { useAtomValue } from "jotai"; -import { selectAtom } from "jotai/utils"; -import { memo } from "react"; -import { - type Actor, - type ActorAtom, - isCurrentActorAtom, -} from "./actor-context"; -import { ActorRegion } from "./actor-region"; -import { AtomizedActorStatusIndicator } from "./actor-status-indicator"; -import { AtomizedActorStatusLabel } from "./actor-status-label"; -import { ActorTags } from "./actor-tags"; - -interface ActorsListRowProps { - className?: string; - actor: ActorAtom; -} - -const selector = (actor: Actor) => actor.id; - -export const ActorsListRow = memo( - ({ className, actor }: ActorsListRowProps) => { - const id = useAtomValue(selectAtom(actor, selector)); - const isCurrent = useAtomValue(isCurrentActorAtom(actor)); - - return ( - - ); - }, -); - -const regionSelector = (actor: Actor) => actor.region; - -function Region({ actor }: { actor: ActorAtom }) { - const regionId = useAtomValue(selectAtom(actor, regionSelector)); - - return ( - - ); -} - -const tagsSelector = (actor: Actor) => toRecord(actor.tags); - -function Tags({ actor }: { actor: ActorAtom }) { - const tags = useAtomValue(selectAtom(actor, tagsSelector)); - - const tagCount = Object.keys(tags).length; - - return ( - - -
- - {Object.keys(tags).length}{" "} - {tagCount === 1 ? "tag" : "tags"} -
- - } - content={ - <> -

Tags

- - - } - /> - ); -} - -const createdAtSelector = (actor: Actor) => actor.createdAt; - -function CreatedAt({ actor }: { actor: ActorAtom }) { - const createdAt = useAtomValue(selectAtom(actor, createdAtSelector)); - - return ( - - {createdAt ? ( - } - content={createdAt.toLocaleString()} - /> - ) : ( - - - )} - - ); -} - -const destroyedAtSelector = (actor: Actor) => actor.destroyedAt; -function DestroyedAt({ actor }: { actor: ActorAtom }) { - const destroyedAt = useAtomValue(selectAtom(actor, destroyedAtSelector)); - - return ( - - {destroyedAt ? ( - } - content={new Date(destroyedAt).toLocaleString()} - /> - ) : ( - - - )} - - ); -} diff --git a/frontend/packages/components/src/actors/actors-list.tsx b/frontend/packages/components/src/actors/actors-list.tsx deleted file mode 100644 index 2ca0e8242d..0000000000 --- a/frontend/packages/components/src/actors/actors-list.tsx +++ /dev/null @@ -1,489 +0,0 @@ -import { - Button, - Checkbox, - CommandGroup, - CommandItem, - DocsSheet, - FilterCreator, - type FilterDefinitions, - FilterOp, - type OnFiltersChange, - type OptionsProviderProps, - ScrollArea, - ShimmerLine, - SmallText, - cn, - createFiltersPicker, - createFiltersSchema, -} from "@rivet-gg/components"; -import { - Icon, - faActors, - faCalendarCircleMinus, - faCalendarCirclePlus, - faCalendarMinus, - faCalendarPlus, - faCode, - faGlobe, - faReact, - faSignalBars, - faTag, - faTs, -} from "@rivet-gg/icons"; -import { useNavigate, useSearch } from "@tanstack/react-router"; -import { useAtomValue, useSetAtom } from "jotai"; -import { useCallback, useMemo } from "react"; -import { - actorFiltersAtom, - actorFiltersCountAtom, - actorRegionsAtom, - actorTagsAtom, - actorsAtomsAtom, - actorsPaginationAtom, - actorsQueryAtom, - filteredActorsCountAtom, -} from "./actor-context"; -import { ActorStatus } from "./actor-status"; -import type { ActorStatus as ActorStatusType } from "./actor-status-indicator"; -import { ActorTag } from "./actor-tags"; -import { ActorsListRow } from "./actors-list-row"; -import { useActorsView } from "./actors-view-context-provider"; -import { CreateActorButton } from "./create-actor-button"; -import { GoToActorButton } from "./go-to-actor-button"; - -export function ActorsList() { - return ( - <> - -
-
-
- -
- - -
- -
-
-
-
- - Region - - - - -
-
ID
-
Tags
-
- - Created - - - - -
-
- - Destroyed - - - - -
-
-
- - -
- - - ); -} - -function LoadingIndicator() { - const state = useAtomValue(actorsQueryAtom); - if (state.isLoading) { - return ; - } - return null; -} - -function List() { - const actors = useAtomValue(actorsAtomsAtom); - return ( - <> - {actors.map((actor) => ( - - ))} - - ); -} - -function Pagination() { - const { hasNextPage, isFetchingNextPage, fetchNextPage } = - useAtomValue(actorsPaginationAtom); - - if (hasNextPage) { - return ( -
- -
- ); - } - - return ; -} - -function EmptyState() { - const count = useAtomValue(filteredActorsCountAtom); - const filtersCount = useAtomValue(actorFiltersCountAtom); - const setFilters = useSetAtom(actorFiltersAtom); - const { copy } = useActorsView(); - - return ( -
- {count === 0 ? ( - filtersCount === 0 ? ( -
- - - {copy.noActorsFound} - -
- {" "} - - Use one of the quick start guides to get - started. - -
- - - - - - -
-
-
- ) : ( - <> - - {copy.noActorsMatchFilter} - - - - ) - ) : ( - - {copy.noMoreActors} - - )} -
- ); -} - -const FILTER_DEFINITIONS = { - tags: { - type: "select", - label: "Tags", - icon: faTag, - options: TagsOptions, - operators: { - [FilterOp.EQUAL]: "is one of", - [FilterOp.NOT_EQUAL]: "is not one of", - }, - }, - createdAt: { - type: "date", - label: "Created", - icon: faCalendarCirclePlus, - }, - destroyedAt: { - type: "date", - label: "Destroyed", - icon: faCalendarCircleMinus, - }, - status: { - type: "select", - label: "Status", - icon: faSignalBars, - options: StatusOptions, - display: ({ value }) => { - if (value.length > 1) { - return {value.length} statuses; - } - return ( - - ); - }, - }, - region: { - type: "select", - label: "Region", - icon: faGlobe, - options: RegionOptions, - display: ({ value }) => { - if (value.length > 1) { - return {value.length} regions; - } - const region = useAtomValue(actorRegionsAtom).find( - (region) => region.id === value[0], - ); - return {region?.name}; - }, - operators: { - [FilterOp.EQUAL]: "is one of", - [FilterOp.NOT_EQUAL]: "is not one of", - }, - }, - devMode: { - type: "boolean", - label: "Show hidden actors", - icon: faCode, - }, -} satisfies FilterDefinitions; - -export const ActorsListFiltersSchema = createFiltersSchema(FILTER_DEFINITIONS); - -export const pickActorListFilters = createFiltersPicker(FILTER_DEFINITIONS); - -function Filters() { - const navigate = useNavigate(); - const filters = useSearch({ strict: false }); - - const onFiltersChange: OnFiltersChange = useCallback( - (fnOrValue) => { - if (typeof fnOrValue === "function") { - navigate({ - search: ({ actorId, tab, ...filters }) => ({ - actorId, - tab, - ...Object.fromEntries( - Object.entries(fnOrValue(filters)).filter( - ([, filter]) => filter.value.length > 0, - ), - ), - }), - }); - } else { - navigate({ - search: (value) => ({ - actorId: value.actorId, - tab: value.tab, - ...Object.fromEntries( - Object.entries(fnOrValue).filter( - ([, filter]) => filter.value.length > 0, - ), - ), - }), - }); - } - }, - [navigate], - ); - - const { copy } = useActorsView(); - - const filtersDefs = useMemo(() => { - return { - ...FILTER_DEFINITIONS, - devMode: { - ...FILTER_DEFINITIONS.devMode, - hidden: true, - label: copy.showHiddenActors, - }, - }; - }, [copy.showHiddenActors]); - - return ( - - ); -} - -function TagsOptions({ onSelect, value: filterValue }: OptionsProviderProps) { - const tags = useAtomValue(actorTagsAtom); - - const values = filterValue.map((filter) => filter.split("=")); - - return ( - - {tags.map(({ key, value }) => { - const isSelected = values.some( - ([filterKey, filterValue]) => - filterKey === key && filterValue === value, - ); - return ( - { - if (isSelected) { - onSelect( - values - .filter( - ([filterKey, filterValue]) => - filterKey !== key || - filterValue !== value, - ) - .map((pair) => pair.join("=")), - { closeAfter: true }, - ); - return; - } - onSelect([...filterValue, `${key}=${value}`], { - closeAfter: true, - }); - }} - > - - - - {key}={value} - - - - ); - })} - - ); -} - -function StatusOptions({ onSelect, value: filterValue }: OptionsProviderProps) { - return ( - - {["running", "starting", "crashed", "stopped"].map((key) => { - const isSelected = filterValue.some((val) => val === key); - return ( - { - if (isSelected) { - onSelect( - filterValue.filter( - (filterKey) => filterKey !== key, - ), - { closeAfter: true }, - ); - return; - } - - onSelect([...filterValue, key], { - closeAfter: true, - }); - }} - > - - - - ); - })} - - ); -} - -function RegionOptions({ onSelect, value: filterValue }: OptionsProviderProps) { - const regions = useAtomValue(actorRegionsAtom); - return ( - - {regions.map(({ id, name }) => { - const isSelected = filterValue.some((val) => val === id); - return ( - { - if (isSelected) { - onSelect( - filterValue.filter( - (filterKey) => filterKey !== id, - ), - { closeAfter: true }, - ); - return; - } - - onSelect([...filterValue, id], { - closeAfter: true, - }); - }} - > - - {name} - - ); - })} - - ); -} diff --git a/frontend/packages/components/src/actors/actors-sidebar-toggle-button.tsx b/frontend/packages/components/src/actors/actors-sidebar-toggle-button.tsx deleted file mode 100644 index d210653c7a..0000000000 --- a/frontend/packages/components/src/actors/actors-sidebar-toggle-button.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Button } from "@rivet-gg/components"; -import { Icon, faSidebar } from "@rivet-gg/icons"; -import { useActorsLayout } from "./actors-layout-context"; - -export function ActorsSidebarToggleButton() { - const { setFolded, isFolded } = useActorsLayout(); - - if (!isFolded) { - return null; - } - return ( -
- -
- ); -} diff --git a/frontend/packages/components/src/actors/actors-view-context-provider.tsx b/frontend/packages/components/src/actors/actors-view-context-provider.tsx deleted file mode 100644 index d7a063f1ae..0000000000 --- a/frontend/packages/components/src/actors/actors-view-context-provider.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { createContext, useContext } from "react"; - -const defaultValue = { - copy: { - createActor: "Create Actor", - createActorUsingForm: "Create Actor using simple form", - noActorsFound: "No Actors found", - selectActor: ( - <> - No Actor selected. -
{" "} - - Select an Actor from the list to view its details. - - - ), - goToActor: "Go to Actor", - showActorList: "Show Actor List", - showHiddenActors: "Show hidden Actors", - actorId: "Actor ID", - noActorsMatchFilter: "No Actors match the filters.", - noMoreActors: "No more Actors to load.", - - createActorModal: { - title: "Create Actor", - description: - "Choose a build to create an Actor from. Actor will be created using default settings.", - }, - - actorNotFound: "Actor not found", - actorNotFoundDescription: - "Change your filters to find the Actor you are looking for.", - - gettingStarted: { - title: "Getting Started with Actors", - description: - "Use a quick start guide to start deploying Actors to your environment.", - }, - }, - canCreate: true, -}; - -export const ActorsViewContext = - createContext(defaultValue); - -export const useActorsView = () => { - const context = useContext(ActorsViewContext); - if (!context) { - throw new Error( - "useActorsView must be used within a ActorsViewContextProvider", - ); - } - return context; -}; diff --git a/frontend/packages/components/src/actors/build-select.tsx b/frontend/packages/components/src/actors/build-select.tsx deleted file mode 100644 index ac5ed0eecf..0000000000 --- a/frontend/packages/components/src/actors/build-select.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { Badge, Combobox } from "@rivet-gg/components"; -import { useAtomValue } from "jotai"; -import { useMemo } from "react"; -import { actorBuildsAtom } from "./actor-context"; - -interface BuildSelectProps { - onValueChange: (value: string) => void; - value: string; - onlyCurrent?: boolean; -} - -export function BuildSelect({ - onValueChange, - value, - onlyCurrent, -}: BuildSelectProps) { - const data = useAtomValue(actorBuildsAtom); - - const builds = useMemo(() => { - let sorted = data.toSorted( - (a, b) => b.createdAt.valueOf() - a.createdAt.valueOf(), - ); - - if (onlyCurrent) { - sorted = sorted.filter((build) => build.tags.current); - } - - const findLatest = (name: string) => - sorted.find((build) => build.tags.name === name); - return sorted.map((build, index, array) => { - return { - label: ( -
-
-
- {build.tags.name || build.id.split("-")[0]} - - {findLatest(build.tags.name)?.id === - build.id ? ( - - Latest - - ) : null} -
-
- Created: {build.createdAt.toLocaleString()} -
-
-
- ), - value: build.id, - build, - }; - }); - }, [data, onlyCurrent]); - - return ( - - option.build.name.includes(search) || - option.build.tags.name.includes(search) - } - className="w-full h-14" - /> - ); -} diff --git a/frontend/packages/components/src/actors/console/actor-console-input.tsx b/frontend/packages/components/src/actors/console/actor-console-input.tsx deleted file mode 100644 index 58689575ef..0000000000 --- a/frontend/packages/components/src/actors/console/actor-console-input.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { Button, ScrollArea } from "@rivet-gg/components"; -import { useRef } from "react"; -import { useActorRpcs, useActorWorker } from "../worker/actor-worker-context"; -import { ActorConsoleMessage } from "./actor-console-message"; -import { ReplInput, type ReplInputRef, replaceCode } from "./repl-input"; - -export function ActorConsoleInput() { - const worker = useActorWorker(); - const rpcs = useActorRpcs(); - const ref = useRef(null); - - return ( -
- - - { - worker.run(code); - }} - /> - -
-
- {rpcs.map((rpc) => ( - - ))} -
-
-
-
- ); -} diff --git a/frontend/packages/components/src/actors/console/actor-console-log-formatted.tsx b/frontend/packages/components/src/actors/console/actor-console-log-formatted.tsx deleted file mode 100644 index 11545a63f5..0000000000 --- a/frontend/packages/components/src/actors/console/actor-console-log-formatted.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import type { FormattedCode } from "../worker/actor-worker-schema"; - -export function ActorConsoleLogFormatted({ tokens }: FormattedCode) { - return ( - <> - {tokens.map((tokensLine, index) => ( - - {tokensLine.map((token, index) => ( - - {token.content} - - ))} - - ))} - - ); -} diff --git a/frontend/packages/components/src/actors/console/actor-console-log.tsx b/frontend/packages/components/src/actors/console/actor-console-log.tsx deleted file mode 100644 index 38b97eb0cf..0000000000 --- a/frontend/packages/components/src/actors/console/actor-console-log.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { memo } from "react"; -import type { ReplCommand } from "../worker/actor-worker-container"; -import { ActorConsoleLogFormatted } from "./actor-console-log-formatted"; -import { ActorConsoleMessage } from "./actor-console-message"; -import { ActorObjectInspector } from "./actor-inspector"; - -type ActorConsoleLogProps = ReplCommand & { - showTimestmaps: boolean; -}; - -export const ActorConsoleLog = memo((props: ActorConsoleLogProps) => { - return ( - <> - - {"formatted" in props && props.formatted ? ( - - ) : null} - - {"error" in props ? ( - - {props.error && - typeof props.error === "object" && - "toString" in props.error - ? props.error.toString() - : JSON.stringify(props.error)} - - ) : null} - {props.logs?.map((log, index) => ( - - {log.data?.map((element, key) => { - if (typeof element === "string") { - return ( - - {element} - - ); - } - return ( - - ); - })} - - ))} - {"result" in props ? ( - - {typeof props.result === "string" ? ( - props.result - ) : ( - - )} - - ) : null} - - ); -}); diff --git a/frontend/packages/components/src/actors/console/actor-console-logs.tsx b/frontend/packages/components/src/actors/console/actor-console-logs.tsx deleted file mode 100644 index bd3593a6ba..0000000000 --- a/frontend/packages/components/src/actors/console/actor-console-logs.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { ScrollArea } from "@rivet-gg/components"; -import { useLayoutEffect, useRef } from "react"; -import { useActorDetailsSettings } from "../actor-details-settings"; -import { useActorReplCommands } from "../worker/actor-worker-context"; -import { ActorConsoleLog } from "./actor-console-log"; - -export function ActorConsoleLogs() { - const isScrolledToBottom = useRef(true); - const ref = useRef(null); - const commands = useActorReplCommands(); - - const [settings] = useActorDetailsSettings(); - - // biome-ignore lint/correctness/useExhaustiveDependencies: we want to run this effect on every commands change - useLayoutEffect(() => { - if (ref.current && isScrolledToBottom.current) { - ref.current.scrollTop = ref.current.scrollHeight; - } - }, [commands]); - - return ( - { - if (ref.current) { - isScrolledToBottom.current = - ref.current.scrollTop + ref.current.clientHeight >= - ref.current.scrollHeight - 1; - } - }, - }} - className="w-full flex-1" - > - {commands.map((log) => ( - - ))} - - ); -} diff --git a/frontend/packages/components/src/actors/console/actor-console-message.tsx b/frontend/packages/components/src/actors/console/actor-console-message.tsx deleted file mode 100644 index eaba9b1e3d..0000000000 --- a/frontend/packages/components/src/actors/console/actor-console-message.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { cn } from "@rivet-gg/components"; -import { - Icon, - faAngleLeft, - faAngleRight, - faExclamationCircle, - faSpinnerThird, - faWarning, -} from "@rivet-gg/icons"; -import { format } from "date-fns"; -import { type ReactNode, forwardRef } from "react"; - -interface ActorConsoleMessageProps { - variant: - | "input" - | "input-pending" - | "output" - | "error" - | "log" - | "warn" - | "info" - | "debug"; - timestamp?: Date; - className?: string; - children: ReactNode; -} - -export const ActorConsoleMessage = forwardRef< - HTMLDivElement, - ActorConsoleMessageProps ->(({ children, variant, timestamp, className, ...props }, ref) => { - return ( -
-
- -
-
- {timestamp - ? format(timestamp, "LLL dd HH:mm:ss").toUpperCase() - : null} -
-
- {children} -
-
- ); -}); - -export const ConsoleMessageVariantIcon = ({ - variant, - className, -}: { - variant: string; - className?: string; -}) => { - if (variant === "input") { - return ; - } - if (variant === "input-pending") { - return ( - - ); - } - if (variant === "output") { - return ; - } - if (variant === "error") { - return ( - - ); - } - if (variant === "warn") { - return ; - } - return ; -}; - -export const getConsoleMessageVariant = (variant: string) => - cn({ - "bg-red-950/30 border-red-800/40 text-red-400 z-10": - variant === "error", - "bg-yellow-500/10 border-yellow-800/40 text-yellow-200 z-10": - variant === "warn", - "bg-blue-950/30 border-blue-800/40 text-blue-400 z-10": - variant === "debug", - }); diff --git a/frontend/packages/components/src/actors/console/actor-console.tsx b/frontend/packages/components/src/actors/console/actor-console.tsx deleted file mode 100644 index 681cbbf10d..0000000000 --- a/frontend/packages/components/src/actors/console/actor-console.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { Button } from "@rivet-gg/components"; -import { Icon, faChevronDown } from "@rivet-gg/icons"; -import { AnimatePresence, motion } from "framer-motion"; -import { useState } from "react"; -import { useActorWorkerStatus } from "../worker/actor-worker-context"; -import { ActorWorkerStatus } from "../worker/actor-worker-status"; -import { ActorConsoleInput } from "./actor-console-input"; -import { ActorConsoleLogs } from "./actor-console-logs"; - -export function ActorConsole() { - const [isOpen, setOpen] = useState(false); - - const status = useActorWorkerStatus(); - - const isBlocked = status.type !== "ready"; - - return ( - - - - {isOpen && !isBlocked ? ( - - - - - ) : null} - - - ); -} diff --git a/frontend/packages/components/src/actors/console/actor-inspector.tsx b/frontend/packages/components/src/actors/console/actor-inspector.tsx deleted file mode 100644 index 87949e7a13..0000000000 --- a/frontend/packages/components/src/actors/console/actor-inspector.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { cn } from "@rivet-gg/components"; -import type { ComponentProps } from "react"; -import { Inspector, type ObjectInspector, chromeDark } from "react-inspector"; - -const INSPECTOR_THEME = { - ...chromeDark, - BASE_BACKGROUND_COLOR: "transparent", -}; - -export function ActorObjectInspector( - props: ComponentProps, -) { - return ( -
- -
- ); -} diff --git a/frontend/packages/components/src/actors/console/repl-input.tsx b/frontend/packages/components/src/actors/console/repl-input.tsx deleted file mode 100644 index fc7ef1e372..0000000000 --- a/frontend/packages/components/src/actors/console/repl-input.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { - CodeMirror, - type CodeMirrorRef, - type CompletionContext, - EditorView, - External, - defaultKeymap, - javascript, - javascriptLanguage, - keymap, -} from "@rivet-gg/components/code-mirror"; -import { forwardRef } from "react"; - -export const replaceCode = (editor: EditorView, code: string) => { - return editor.dispatch({ - changes: { - from: 0, - to: editor.state.doc.length, - insert: code, - }, - selection: { anchor: code.length }, - scrollIntoView: true, - annotations: [External.of(true)], - }); -}; - -const deleteBgTheme = EditorView.theme({ - ".cm-content": { padding: 0 }, -}); - -export type ReplInputRef = CodeMirrorRef; - -interface ReplInputProps { - className: string; - rpcs: string[]; - onRun: (code: string) => void; -} - -export const ReplInput = forwardRef( - ({ rpcs, onRun, className }, ref) => { - const rivetKeymap = keymap.of([ - { - key: "Enter", - run: (editor) => { - onRun(editor?.state.doc.toString()); - editor.dispatch({ - changes: { - from: 0, - to: editor.state.doc.length, - insert: "", - }, - annotations: [External.of(true)], - }); - return true; - }, - }, - ...defaultKeymap, - ]); - - const replAutocomplete = javascriptLanguage.data.of({ - autocomplete: (context: CompletionContext) => { - const word = context.matchBefore(/^actor\.\w*/); - if (!word || (word?.from === word?.to && !context.explicit)) - return null; - return { - from: word.from, - to: word.to, - boost: 99, - options: rpcs.map((rpc) => ({ - label: `actor.${rpc}(/* args */)`, - apply: `actor.${rpc}(`, - validFor: /^actor\.\w*$/, - info: `Call "${rpc}" RPC on Actor`, - })), - }; - }, - }); - - return ( - - ); - }, -); diff --git a/frontend/packages/components/src/actors/create-actor-button.tsx b/frontend/packages/components/src/actors/create-actor-button.tsx deleted file mode 100644 index 3748de59bb..0000000000 --- a/frontend/packages/components/src/actors/create-actor-button.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { Button, type ButtonProps, WithTooltip } from "@rivet-gg/components"; -import { Icon, faPlus } from "@rivet-gg/icons"; -import { useNavigate } from "@tanstack/react-router"; -import { useAtomValue } from "jotai"; -import { - actorBuildsCountAtom, - actorManagerEndpointAtom, -} from "./actor-context"; -import { useActorsView } from "./actors-view-context-provider"; - -export function CreateActorButton(props: ButtonProps) { - const navigate = useNavigate(); - const builds = useAtomValue(actorBuildsCountAtom); - const endpoint = useAtomValue(actorManagerEndpointAtom); - - const { copy, canCreate: contextAllowActorsCreation } = useActorsView(); - - const canCreate = builds > 0 && contextAllowActorsCreation && endpoint; - - if (!contextAllowActorsCreation) { - return null; - } - - const content = ( -
- -
- ); - - if (canCreate) { - return content; - } - - return ( - - ); -} diff --git a/frontend/packages/components/src/actors/current-environment-version-title.tsx b/frontend/packages/components/src/actors/current-environment-version-title.tsx deleted file mode 100644 index e38d15afec..0000000000 --- a/frontend/packages/components/src/actors/current-environment-version-title.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { useSuspenseQuery } from "@tanstack/react-query"; -import { - projectEnvironmentQueryOptions, - projectVersionQueryOptions, -} from "../queries"; -import { EnvironmentVersionTitle } from "./environment-version-title"; - -interface CurrentEnvironmentVersionTitleProps { - environmentId: string; - projectId: string; -} - -export function CurrentEnvironmentVersionTitle({ - environmentId, - projectId, -}: CurrentEnvironmentVersionTitleProps) { - const { - data: { namespace: environment }, - } = useSuspenseQuery( - projectEnvironmentQueryOptions({ projectId, environmentId }), - ); - - const { data: version } = useSuspenseQuery( - projectVersionQueryOptions({ - projectId, - versionId: environment.versionId, - }), - ); - - return ( - - ); -} diff --git a/frontend/packages/components/src/actors/dialogs/create-actor-dialog.tsx b/frontend/packages/components/src/actors/dialogs/create-actor-dialog.tsx deleted file mode 100644 index 9dc8b0745f..0000000000 --- a/frontend/packages/components/src/actors/dialogs/create-actor-dialog.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { useAtomValue } from "jotai"; -import { - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from "../../ui/dialog"; -import { Flex } from "../../ui/flex"; -import { createActorAtom } from "../actor-context"; -import { useActorsView } from "../actors-view-context-provider"; -import * as ActorCreateForm from "../form/actor-create-form"; -import type { DialogContentProps } from "../hooks"; - -interface ContentProps extends DialogContentProps {} - -export default function CreateActorDialog({ onClose }: ContentProps) { - const { endpoint, create } = useAtomValue(createActorAtom); - - const { copy } = useActorsView(); - - return ( - <> - { - if (!endpoint) { - throw new Error("No endpoint"); - } - await create({ - endpoint, - id: values.buildId, - tags: Object.fromEntries( - values.tags.map((tag) => [tag.key, tag.value]), - ), - params: values.parameters - ? JSON.parse(values.parameters) - : undefined, - region: values.regionId, - }); - onClose?.(); - }} - defaultValues={{ buildId: "", regionId: "" }} - > - - {copy.createActorModal.title} - - {copy.createActorModal.description} - - - - - - - {/* */} - - - - Create - - - - - ); -} diff --git a/frontend/packages/components/src/actors/dialogs/go-to-actor-dialog.tsx b/frontend/packages/components/src/actors/dialogs/go-to-actor-dialog.tsx deleted file mode 100644 index 3baf32d185..0000000000 --- a/frontend/packages/components/src/actors/dialogs/go-to-actor-dialog.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { Button } from "../../ui/button"; -import { DialogFooter, DialogHeader, DialogTitle } from "../../ui/dialog"; -import { useActorsView } from "../actors-view-context-provider"; -import * as GoToActorForm from "../form/go-to-actor-form"; -import type { DialogContentProps } from "../hooks"; - -interface ContentProps extends DialogContentProps { - onSubmit?: (actorId: string) => void; -} - -export default function GoToActorDialogContent({ - onClose, - onSubmit, -}: ContentProps) { - const { copy } = useActorsView(); - return ( - { - onSubmit?.(actorId); - }} - > - - {copy.goToActor} - - - - - Go - - - ); -} diff --git a/frontend/packages/components/src/actors/dynamic-servers-feature-card.tsx b/frontend/packages/components/src/actors/dynamic-servers-feature-card.tsx deleted file mode 100644 index 7df263034b..0000000000 --- a/frontend/packages/components/src/actors/dynamic-servers-feature-card.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { - Card, - CardContent, - CardHeader, - CardTitle, - Link, - Text, -} from "@rivet-gg/components"; -import { Link as RouterLink } from "@tanstack/react-router"; - -export function DynamicServersFeatureCard() { - return ( - - - Legacy Lobbies and Environments - - - - Dynamic servers and builds are the new way to manage your - project servers. However, if you're looking for lobbies and - namespaces, you can switch back to the old interface in the{" "} - - - User Settings - - - . - - - - ); -} diff --git a/frontend/packages/components/src/actors/environment-select.tsx b/frontend/packages/components/src/actors/environment-select.tsx deleted file mode 100644 index 2261946dbc..0000000000 --- a/frontend/packages/components/src/actors/environment-select.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { projectEnvironmentsQueryOptions } from "@/domains/project/queries"; -import { - Flex, - Select, - SelectContent, - SelectItem, - SelectSeparator, - SelectTrigger, - SelectValue, -} from "@rivet-gg/components"; -import { Icon, faCirclePlus } from "@rivet-gg/icons"; -import { useSuspenseQuery } from "@tanstack/react-query"; -import { type ComponentProps, useCallback } from "react"; - -interface EnvironmentSelectProps extends ComponentProps { - projectId: string; - showCreateEnvironment?: boolean; - onCreateClick?: () => void; - variant?: ComponentProps["variant"]; -} - -export function EnvironmentSelect({ - showCreateEnvironment, - onCreateClick, - onValueChange, - projectId, - variant, - ...props -}: EnvironmentSelectProps) { - const { data } = useSuspenseQuery( - projectEnvironmentsQueryOptions(projectId), - ); - - const handleValueChange = useCallback( - (value: string) => { - if (value === "create") { - onCreateClick?.(); - return; - } - onValueChange?.(value); - }, - [onCreateClick, onValueChange], - ); - - return ( - - ); -} diff --git a/frontend/packages/components/src/actors/environment-version-title.tsx b/frontend/packages/components/src/actors/environment-version-title.tsx deleted file mode 100644 index 0777d81cda..0000000000 --- a/frontend/packages/components/src/actors/environment-version-title.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Badge, Flex } from "@rivet-gg/components"; - -interface EnvironmentVersionTitleProps { - environment: string; - version: string; -} - -export function EnvironmentVersionTitle({ - environment, - version, -}: EnvironmentVersionTitleProps) { - return ( - - {environment} - {version} - - ); -} diff --git a/frontend/packages/components/src/actors/form/actor-create-form.tsx b/frontend/packages/components/src/actors/form/actor-create-form.tsx deleted file mode 100644 index bf92b99b46..0000000000 --- a/frontend/packages/components/src/actors/form/actor-create-form.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import { - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, - Label, - createSchemaForm, -} from "@rivet-gg/components"; -import { JsonCode } from "@rivet-gg/components/code-mirror"; -import { type UseFormReturn, useFormContext } from "react-hook-form"; -import z from "zod"; - -import { useAtomValue, useSetAtom } from "jotai"; -import { - actorCustomTagKeys, - actorCustomTagValues, - actorTagKeysAtom, - actorTagValuesAtom, -} from "../actor-context"; -import { BuildSelect } from "../build-select"; -import { RegionSelect } from "../region-select"; -import { - Tags as TagsInput, - formSchema as tagsFormSchema, -} from "./build-tags-form"; - -const jsonValid = z.custom((value) => { - try { - JSON.parse(value); - return true; - } catch { - return false; - } -}); - -export const formSchema = z.object({ - buildId: z.string().nonempty("Build is required"), - regionId: z.string(), - parameters: jsonValid.optional(), - tags: tagsFormSchema.shape.tags, -}); - -export type FormValues = z.infer; -export type SubmitHandler = ( - values: FormValues, - form: UseFormReturn, -) => Promise; - -const { Form, Submit } = createSchemaForm(formSchema); -export { Form, Submit }; - -export const Build = () => { - const { control } = useFormContext(); - return ( - ( - - Build - - - - - - )} - /> - ); -}; - -export const Region = () => { - const { control } = useFormContext(); - - return ( - ( - - Region - - - - - - )} - /> - ); -}; - -export const Parameters = () => { - const { control } = useFormContext(); - return ( - ( - - Parameters - - - - - - )} - /> - ); -}; - -export const Tags = () => { - const setValues = useSetAtom(actorCustomTagValues); - const setKeys = useSetAtom(actorCustomTagKeys); - - const keys = useAtomValue(actorTagKeysAtom); - const values = useAtomValue(actorTagValuesAtom); - - return ( -
- - ({ - label: key, - value: key, - }))} - values={values.map((value) => ({ - label: value, - value: value, - }))} - onCreateKeyOption={(value) => { - setKeys((old) => - Array.from(new Set([...old, value]).values()), - ); - }} - onCreateValueOption={(value) => { - setValues((old) => - Array.from(new Set([...old, value]).values()), - ); - }} - /> -
- ); -}; diff --git a/frontend/packages/components/src/actors/form/build-tags-form.tsx b/frontend/packages/components/src/actors/form/build-tags-form.tsx deleted file mode 100644 index b431baf7cd..0000000000 --- a/frontend/packages/components/src/actors/form/build-tags-form.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import { Icon, faTrash } from "@rivet-gg/icons"; -import { - type UseFormReturn, - useFieldArray, - useFormContext, -} from "react-hook-form"; -import z from "zod"; -import { createSchemaForm } from "../../lib/create-schema-form"; -import { Button } from "../../ui/button"; -import { Combobox, type ComboboxOption as Option } from "../../ui/combobox"; -import { - FormControl, - FormFieldContext, - FormItem, - FormLabel, - FormMessage, -} from "../../ui/form"; -import { Text } from "../../ui/typography"; - -export const formSchema = z.object({ - tags: z - .array( - z.object({ - key: z.string().min(1), - value: z.string(), - }), - ) - .superRefine((tags, ctx) => { - const tagsSet = new Set(); - for (const [idx, tag] of [...tags].reverse().entries()) { - if (tagsSet.has(tag.key)) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - path: [idx, "key"], - message: "Tag keys must be unique", - }); - } - tagsSet.add(tag.key); - } - }), -}); - -export type FormValues = z.infer; -export type SubmitHandler = ( - values: FormValues, - form: UseFormReturn, -) => Promise; - -const { Form, Submit } = createSchemaForm(formSchema); -export { Form, Submit }; - -export const Tags = ({ - onCreateKeyOption, - onCreateValueOption, - keys, - values, -}: { - onCreateKeyOption: (option: string) => void; - onCreateValueOption: (option: string) => void; - keys: Option[]; - values: Option[]; -}) => { - const { control, setValue, watch } = useFormContext(); - const { fields, append, remove } = useFieldArray({ - name: "tags", - control, - }); - - return ( - <> - {fields.length === 0 ? ( - - There's no tags added. - - ) : null} - {fields.map((field, index) => ( -
- - - Key - - { - setValue(`tags.${index}.key`, value, { - shouldDirty: true, - shouldTouch: true, - shouldValidate: true, - }); - }} - allowCreate - onCreateOption={onCreateKeyOption} - /> - - - - - - - - Value - - { - setValue(`tags.${index}.value`, value, { - shouldDirty: true, - shouldTouch: true, - shouldValidate: true, - }); - }} - allowCreate - onCreateOption={onCreateValueOption} - /> - - - - - -
- ))} - - - ); -}; diff --git a/frontend/packages/components/src/actors/form/go-to-actor-form.tsx b/frontend/packages/components/src/actors/form/go-to-actor-form.tsx deleted file mode 100644 index 4b7acdf0d2..0000000000 --- a/frontend/packages/components/src/actors/form/go-to-actor-form.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { type UseFormReturn, useFormContext } from "react-hook-form"; -import z from "zod"; -import { createSchemaForm } from "../../lib/create-schema-form"; -import { - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "../../ui/form"; -import { Input } from "../../ui/input"; -import { useActorsView } from "../actors-view-context-provider"; - -export const formSchema = z.object({ - actorId: z.string().nonempty("Actor ID is required").uuid(), -}); - -export type FormValues = z.infer; -export type SubmitHandler = ( - values: FormValues, - form: UseFormReturn, -) => Promise; - -const { Form, Submit } = createSchemaForm(formSchema); -export { Form, Submit }; - -export const ActorId = () => { - const { control } = useFormContext(); - const { copy } = useActorsView(); - return ( - ( - - {copy.actorId} - - - - - - )} - /> - ); -}; diff --git a/frontend/packages/components/src/actors/get-started.tsx b/frontend/packages/components/src/actors/get-started.tsx deleted file mode 100644 index 8039cb19f9..0000000000 --- a/frontend/packages/components/src/actors/get-started.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { Icon, faActors, faFunction, faServer } from "@rivet-gg/icons"; -import { motion } from "framer-motion"; -import type { ComponentProps } from "react"; -import { DocsSheet } from "../docs-sheet"; -import { cn } from "../lib/utils"; -import { Button } from "../ui/button"; - -export function ActorsResources() { - return ( - <> -
- - - -
- - ); -} - -const linkVariants = { - hidden: { - opacity: 0, - }, - show: { - opacity: 1, - }, -}; - -interface ExampleLinkProps { - title: string; - description?: string; - icon: ComponentProps["icon"]; - href: string; - size?: "sm" | "md" | "lg"; -} - -function ExampleLink({ - title, - description, - icon, - href, - size = "lg", -}: ExampleLinkProps) { - return ( - - - - ); -} diff --git a/frontend/packages/components/src/actors/getting-started.tsx b/frontend/packages/components/src/actors/getting-started.tsx deleted file mode 100644 index e64f8ac502..0000000000 --- a/frontend/packages/components/src/actors/getting-started.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Icon, faActors } from "@rivet-gg/icons"; -import { useActorsView } from "./actors-view-context-provider"; -import { ActorsResources } from "./get-started"; - -export function GettingStarted() { - const { copy } = useActorsView(); - return ( -
-
- -

- {copy.gettingStarted.title} -

-

- {copy.gettingStarted.description} -

-
- -
- ); -} diff --git a/frontend/packages/components/src/actors/go-to-actor-button.tsx b/frontend/packages/components/src/actors/go-to-actor-button.tsx deleted file mode 100644 index b38d67f676..0000000000 --- a/frontend/packages/components/src/actors/go-to-actor-button.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Button, type ButtonProps } from "@rivet-gg/components"; -import { Icon, faMagnifyingGlass } from "@rivet-gg/icons"; -import { useNavigate } from "@tanstack/react-router"; -import { useActorsView } from "./actors-view-context-provider"; - -export function GoToActorButton(props: ButtonProps) { - const navigate = useNavigate(); - const { copy } = useActorsView(); - return ( - - ); -} diff --git a/frontend/packages/components/src/actors/group-project-select.tsx b/frontend/packages/components/src/actors/group-project-select.tsx deleted file mode 100644 index d1f7fa6c86..0000000000 --- a/frontend/packages/components/src/actors/group-project-select.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { groupProjectsQueryOptions } from "@/domains/project/queries"; -import { - Flex, - Select, - SelectContent, - SelectItem, - SelectSeparator, - SelectTrigger, - SelectValue, -} from "@rivet-gg/components"; -import { Icon, faCirclePlus } from "@rivet-gg/icons"; -import { useSuspenseQuery } from "@tanstack/react-query"; -import { type ComponentProps, useCallback } from "react"; - -interface GroupProjectSelectProps extends ComponentProps { - groupId: string; - showCreateProject?: boolean; - onCreateClick?: () => void; - variant?: ComponentProps["variant"]; -} - -export function GroupProjectSelect({ - groupId, - showCreateProject, - onCreateClick, - onValueChange, - variant, - ...props -}: GroupProjectSelectProps) { - const { data } = useSuspenseQuery(groupProjectsQueryOptions(groupId)); - - const handleValueChange = useCallback( - (value: string) => { - if (value === "create") { - onCreateClick?.(); - return; - } - onValueChange?.(value); - }, - [onCreateClick, onValueChange], - ); - - return ( - - ); -} diff --git a/frontend/packages/components/src/actors/hooks/index.ts b/frontend/packages/components/src/actors/hooks/index.ts deleted file mode 100644 index 14c4d0400f..0000000000 --- a/frontend/packages/components/src/actors/hooks/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./use-dialog"; diff --git a/frontend/packages/components/src/actors/hooks/use-dialog.tsx b/frontend/packages/components/src/actors/hooks/use-dialog.tsx deleted file mode 100644 index 77373da6c4..0000000000 --- a/frontend/packages/components/src/actors/hooks/use-dialog.tsx +++ /dev/null @@ -1,159 +0,0 @@ -"use client"; -import { - type ComponentProps, - type ComponentType, - lazy, - useCallback, - useMemo, - useState, -} from "react"; -import { Dialog, DialogContent, type DialogProps } from "../../ui/dialog"; - -export interface DialogContentProps { - onClose?: () => void; -} - -interface DialogConfig { - autoFocus?: boolean; -} - -export const createDialogHook = < - // biome-ignore lint/suspicious/noExplicitAny: we don't know the type of the component, so we use any - Component extends Promise<{ default: ComponentType }>, ->( - component: Component, - opts: DialogConfig = {}, -) => { - const DialogImpl = ({ - dialogProps, - ...props - }: ComponentProps["default"]> & { - dialogProps?: DialogProps; - }) => { - // biome-ignore lint/correctness/useExhaustiveDependencies: component here is a static value, won't change over time - const Content = useMemo(() => lazy(() => component), []); - - return ( - - { - if (opts.autoFocus === false) { - return e.preventDefault(); - } - }} - > - dialogProps?.onOpenChange?.(false)} - /> - - - ); - }; - - const useHook = (props: ComponentProps["default"]>) => { - const [isOpen, setIsOpen] = useState(() => false); - - const close = useCallback(() => { - setIsOpen(false); - }, []); - - const open = useCallback(() => { - setIsOpen(true); - }, []); - - const handleOpenChange = useCallback((open: boolean) => { - setIsOpen(open); - }, []); - - return { - open, - close, - dialog: ( - - ), - }; - }; - - useHook.Dialog = DialogImpl; - - return useHook; -}; - -export const createDataDialogHook = < - const DataPropKeys extends string[], - // biome-ignore lint/suspicious/noExplicitAny: we don't know the type of the component, so we use any - Component extends Promise<{ default: ComponentType }>, ->( - _: DataPropKeys, - component: Component, - opts: DialogConfig = {}, -) => { - return ( - props: Omit< - ComponentProps["default"]>, - DataPropKeys[number] - >, - ) => { - const [isOpen, setIsOpen] = useState(false); - const [data, setData] = - useState< - Pick< - ComponentProps["default"]>, - DataPropKeys[number] - > - >(); - - const close = useCallback(() => { - setIsOpen(false); - }, []); - - const open = useCallback( - ( - data: Pick< - ComponentProps["default"]>, - DataPropKeys[number] - >, - ) => { - setIsOpen(true); - setData(data); - }, - [], - ); - - // biome-ignore lint/correctness/useExhaustiveDependencies: component here is a static value, won't change over time - const Content = useMemo(() => lazy(() => component), []); - - return { - open, - dialog: ( - - { - if (opts.autoFocus === false) { - return e.preventDefault(); - } - }} - > - - - - ), - }; - }; -}; - -export function useDialog() {} - -useDialog.GoToActor = createDialogHook(import("../dialogs/go-to-actor-dialog")); - -useDialog.Feedback = createDialogHook(import("../../dialogs/feedback-dialog")); -useDialog.CreateActor = createDialogHook( - import("../dialogs/create-actor-dialog"), -); diff --git a/frontend/packages/components/src/actors/index.tsx b/frontend/packages/components/src/actors/index.tsx deleted file mode 100644 index dd5baf45d2..0000000000 --- a/frontend/packages/components/src/actors/index.tsx +++ /dev/null @@ -1,17 +0,0 @@ -export * from "./getting-started"; -export * from "./actors-list-preview"; -export * from "./actor-tags"; -export * from "./actor-context"; -export * from "./actors-actor-details"; -export * from "./hooks/index"; -export { getActorStatus } from "./actor-status-indicator"; -export * from "./actors-layout"; -export * from "./actors-layout-context"; -export * from "./console/actor-console-message"; -export * from "./actor-region"; -export * from "./console/actor-inspector"; -export * from "./actor-status-indicator"; -export * from "./actor-status-label"; -export * from "./actors-view-context-provider"; -export * from "./actor-not-found"; -export { ActorsListFiltersSchema, pickActorListFilters } from "./actors-list"; diff --git a/frontend/packages/components/src/actors/matchmaker-lobby-config-settings-card.tsx b/frontend/packages/components/src/actors/matchmaker-lobby-config-settings-card.tsx deleted file mode 100644 index c6112dea98..0000000000 --- a/frontend/packages/components/src/actors/matchmaker-lobby-config-settings-card.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import * as MatchmakerLobbyConfigForm from "@/domains/project/forms/matchmaker-lobby-config-form"; -import { - Card, - CardContent, - CardFooter, - CardHeader, - CardTitle, - Flex, -} from "@rivet-gg/components"; -import { useSuspenseQuery } from "@tanstack/react-query"; -import { useMatchmakerLobbyConfigFormHandler } from "../hooks/use-matchmaker-lobby-config-form-handler"; -import { projectEnvironmentQueryOptions } from "../queries"; - -interface MatchMakerLobbyConfigSettingsCardProps { - projectId: string; - environmentId: string; -} - -export function MatchMakerLobbyConfigSettingsCard({ - environmentId, - projectId, -}: MatchMakerLobbyConfigSettingsCardProps) { - const { data } = useSuspenseQuery( - projectEnvironmentQueryOptions({ projectId, environmentId }), - ); - - const handleSubmit = useMatchmakerLobbyConfigFormHandler({ - environmentId, - projectId, - }); - - return ( - - - - Config - - - - - - - - - - Save - - - - - ); -} diff --git a/frontend/packages/components/src/actors/project-builds-table-actions.tsx b/frontend/packages/components/src/actors/project-builds-table-actions.tsx deleted file mode 100644 index 47dcea2344..0000000000 --- a/frontend/packages/components/src/actors/project-builds-table-actions.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { - Button, - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@rivet-gg/components"; -import { Icon, faEllipsisH } from "@rivet-gg/icons"; -import { useNavigate } from "@tanstack/react-router"; - -interface ProjectBuildsTableActionsProps { - buildId: string; -} - -export function ProjectBuildsTableActions({ - buildId, -}: ProjectBuildsTableActionsProps) { - const navigate = useNavigate(); - return ( - - - - - - { - navigate({ - to: ".", - search: { modal: "edit-tags", buildId }, - }); - }} - > - Edit tags - - - - ); -} diff --git a/frontend/packages/components/src/actors/project-environments-table-actions.tsx b/frontend/packages/components/src/actors/project-environments-table-actions.tsx deleted file mode 100644 index b6722a6274..0000000000 --- a/frontend/packages/components/src/actors/project-environments-table-actions.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { - Button, - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@rivet-gg/components"; -import { Icon, faEllipsisH } from "@rivet-gg/icons"; - -export function ProjectEnvironmentsTableActions() { - return ( - - - - - - Manage - - - ); -} diff --git a/frontend/packages/components/src/actors/project-logo-settings-card.tsx b/frontend/packages/components/src/actors/project-logo-settings-card.tsx deleted file mode 100644 index e4ede8afde..0000000000 --- a/frontend/packages/components/src/actors/project-logo-settings-card.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import * as GroupImageForm from "@/domains/project/forms/project-logo-form"; -import { - Card, - CardContent, - CardFooter, - CardHeader, - CardTitle, -} from "@rivet-gg/components"; -import { useProjectLogoUploadMutation } from "../queries"; - -interface ProjectLogoSettingsCardProps { - projectId: string; -} - -export function ProjectLogoSettingsCard({ - projectId, -}: ProjectLogoSettingsCardProps) { - const { mutateAsync } = useProjectLogoUploadMutation(projectId); - return ( - { - try { - await mutateAsync({ file: values.logo }); - } catch { - form.setError("logo", { - type: "manual", - message: "An error occurred while uploading the image", - }); - } - }} - defaultValues={{ logo: undefined }} - > - - - Project Logo - - - - - - Save - - - - ); -} diff --git a/frontend/packages/components/src/actors/project-select.tsx b/frontend/packages/components/src/actors/project-select.tsx deleted file mode 100644 index 1438de3313..0000000000 --- a/frontend/packages/components/src/actors/project-select.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { GroupAvatar } from "@/domains/group/components/group-avatar"; -import { projectsByGroupQueryOptions } from "@/domains/project/queries"; -import { - Flex, - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectLabel, - SelectSeparator, - SelectTrigger, - SelectValue, -} from "@rivet-gg/components"; -import { Icon, faCirclePlus } from "@rivet-gg/icons"; -import { useSuspenseQuery } from "@tanstack/react-query"; -import { type ComponentProps, Fragment, useCallback } from "react"; - -interface ProjectSelectProps extends ComponentProps { - showCreateProject?: boolean; - onCreateClick?: () => void; -} - -export function ProjectSelect({ - showCreateProject, - onCreateClick, - onValueChange, - ...props -}: ProjectSelectProps) { - const { data } = useSuspenseQuery(projectsByGroupQueryOptions()); - - const handleValueChange = useCallback( - (value: string) => { - if (value === "create") { - onCreateClick?.(); - return; - } - onValueChange?.(value); - }, - [onCreateClick, onValueChange], - ); - - return ( - - ); -} diff --git a/frontend/packages/components/src/actors/project-table-actions.tsx b/frontend/packages/components/src/actors/project-table-actions.tsx deleted file mode 100644 index bd462c5d44..0000000000 --- a/frontend/packages/components/src/actors/project-table-actions.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { - Button, - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@rivet-gg/components"; -import { Icon, faEllipsisH } from "@rivet-gg/icons"; - -export function ProjectTableActions() { - return ( - - - - - - Manage - - - ); -} diff --git a/frontend/packages/components/src/actors/project-tile.tsx b/frontend/packages/components/src/actors/project-tile.tsx deleted file mode 100644 index 0938f22eaa..0000000000 --- a/frontend/packages/components/src/actors/project-tile.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import type { Rivet } from "@rivet-gg/api"; -import { AssetImage, Flex, Text } from "@rivet-gg/components"; -import { BillingPlanBadge } from "./billing/billing-plan-badge"; - -interface ProjectTileProps - extends Pick< - Rivet.game.GameSummary, - "gameId" | "displayName" | "logoUrl" - > {} - -export function ProjectTile({ - gameId: projectId, - displayName, - logoUrl, -}: ProjectTileProps) { - return ( - -
- -
- {displayName} - -
- ); -} diff --git a/frontend/packages/components/src/actors/region-select.tsx b/frontend/packages/components/src/actors/region-select.tsx deleted file mode 100644 index d88d187105..0000000000 --- a/frontend/packages/components/src/actors/region-select.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { Combobox } from "@rivet-gg/components"; -import { useAtomValue } from "jotai"; -import { actorRegionsAtom } from "./actor-context"; -import { ActorRegion } from "./actor-region"; - -interface RegionSelectProps { - onValueChange: (value: string) => void; - value: string; -} - -export function RegionSelect({ onValueChange, value }: RegionSelectProps) { - const data = useAtomValue(actorRegionsAtom); - - const regions = [ - { - label: "Automatic (Recommended)", - value: "", - region: { id: "", name: "Automatic (Recommended)" }, - }, - ...data.map((region) => { - return { - label: , - value: region.id, - region, - }; - }), - ]; - - return ( - { - const search = searchMixed.toLowerCase(); - return ( - option.region.id.includes(search) || - option.region.name.includes(search) - ); - }} - className="w-full" - /> - ); -} diff --git a/frontend/packages/components/src/actors/worker/actor-repl.worker.ts b/frontend/packages/components/src/actors/worker/actor-repl.worker.ts deleted file mode 100644 index 98753fabf2..0000000000 --- a/frontend/packages/components/src/actors/worker/actor-repl.worker.ts +++ /dev/null @@ -1,354 +0,0 @@ -import { - type InspectData, - type ToClient, - ToClientSchema, - type ToServer, -} from "actor-core/inspector/protocol/actor"; -import type { Request, ResponseOk } from "actor-core/protocol/http"; -import { fromJs } from "esast-util-from-js"; -import { toJs } from "estree-util-to-js"; -import { - createHighlighterCore, - createOnigurumaEngine, - type HighlighterCore, -} from "shiki"; -import { endWithSlash } from "../../lib/utils"; -import { - MessageSchema, - type ReplErrorCode, - type Response, - ResponseSchema, -} from "./actor-worker-schema"; - -class ReplError extends Error { - constructor( - public readonly code: ReplErrorCode, - message: string, - ) { - super(message); - } - - static unsupported() { - return new ReplError("unsupported", "Actor unsupported"); - } -} - -export let highlighter: HighlighterCore | undefined; - -async function formatCode(code: string) { - highlighter ??= await createHighlighterCore({ - themes: [import("shiki/themes/github-dark-default.mjs")], - langs: [import("@shikijs/langs/typescript")], - engine: createOnigurumaEngine(import("shiki/wasm")), - }); - - return highlighter.codeToTokens(code, { - lang: "typescript", - theme: "github-dark-default", - }); -} - -const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); - -async function evaluateCode(code: string, args: Record) { - const argsString = Object.keys(args); - const argValues = Object.values(args); - - let jsCode: ReturnType; - try { - const program = fromJs(code, { - module: true, - allowAwaitOutsideFunction: true, - allowReturnOutsideFunction: true, - }); - - const lastStatement = program.body[program.body.length - 1]; - if (lastStatement.type === "ExpressionStatement") { - program.body[program.body.length - 1] = { - type: "ReturnStatement", - argument: lastStatement.expression, - }; - } - - jsCode = toJs(program); - } catch (e) { - throw new ReplError("syntax", "Syntax error"); - } - - return new Function( - "window", - ...argsString, - `"use strict"; - return (async () => { - ${jsCode.value} - })() - `, - )({}, ...argValues); -} - -const createConsole = (id: string) => { - return new Proxy( - { ...console }, - { - get(target, prop) { - return (...args: unknown[]) => { - respond({ - type: "log", - id, - data: { - method: prop as "log" | "warn" | "error", - data: args, - timestamp: new Date().toISOString(), - }, - }); - return Reflect.get(target, prop)(...args); - }; - }, - }, - ); -}; - -let init: null | ({ ws: WebSocket; url: URL } & InspectData) = null; - -async function connect(endpoint: string, opts?: { token?: string }) { - const url = new URL("inspect", endWithSlash(endpoint)); - - if (opts?.token) { - url.searchParams.set("token", opts.token); - } - - const ws = new WebSocket(url); - - await waitForOpen(ws); - - ws.send( - JSON.stringify({ - type: "info", - } satisfies ToServer), - ); - - const { type: _, ...info } = await waitForMessage(ws, "info"); - init = { ...info, ws, url: new URL(endpoint) }; - - ws.addEventListener("message", (event) => { - try { - const data = ToClientSchema.parse(JSON.parse(event.data)); - - if (data.type === "info") { - return respond({ - type: "inspect", - data: { - ...data, - }, - }); - } - if (data.type === "error") { - return respond({ - type: "error", - data: data.message, - }); - } - } catch (error) { - console.warn("Malformed message", event.data, error); - return; - } - }); - - ws.addEventListener("close", () => { - respond({ - type: "lost-connection", - }); - setTimeout(() => { - connect(endpoint, opts); - }, 500); - }); - - respond({ - type: "ready", - data: { - ...info, - }, - }); -} - -addEventListener("message", async (event) => { - const { success, error, data } = MessageSchema.safeParse(event.data); - - if (!success) { - console.error("Malformed message", event.data, error); - return; - } - - if (data.type === "init") { - if (init) { - respond({ - type: "error", - data: new Error("Actor already initialized"), - }); - return; - } - - try { - await Promise.race([ - connect(data.endpoint, data.token ? { token: data.token } : {}), - wait(5000).then(() => { - throw new Error("Timeout"); - }), - ]); - - return; - } catch (e) { - return respond({ - type: "error", - data: e, - }); - } - } - - if (data.type === "code") { - const actor = init; - if (!actor) { - respond({ - type: "error", - data: new Error("Actor not initialized"), - }); - return; - } - - try { - const formatted = await formatCode(data.data); - respond({ - type: "formatted", - id: data.id, - data: formatted, - }); - - const createRpc = - (rpc: string) => - async (...args: unknown[]) => { - const url = new URL( - `rpc/${rpc}`, - endWithSlash(actor.url.href), - ); - const response = await fetch(url, { - method: "POST", - body: JSON.stringify({ - a: args, - } satisfies Request), - }); - - if (!response.ok) { - throw new Error("RPC failed"); - } - - const data = (await response.json()) as ResponseOk; - return data.o; - }; - - const exposedActor = Object.fromEntries( - init?.rpcs.map((rpc) => [rpc, createRpc(rpc)]) ?? [], - ); - - const evaluated = await evaluateCode(data.data, { - console: createConsole(data.id), - wait, - actor: exposedActor, - }); - return respond({ - type: "result", - id: data.id, - data: evaluated, - }); - } catch (e) { - return respond({ - type: "error", - id: data.id, - data: e, - }); - } - } - - if (data.type === "set-state") { - const actor = init; - if (!actor) { - respond({ - type: "error", - data: new Error("Actor not initialized"), - }); - return; - } - - try { - const state = JSON.parse(data.data); - actor.ws.send( - JSON.stringify({ - type: "setState", - state, - } satisfies ToServer), - ); - } catch (e) { - return respond({ - type: "error", - data: e, - }); - } - } -}); - -function respond(msg: Response) { - return postMessage(ResponseSchema.parse(msg)); -} - -function waitForOpen(ws: WebSocket) { - const { promise, resolve, reject } = Promise.withResolvers(); - ws.addEventListener("open", () => { - resolve(undefined); - }); - ws.addEventListener("error", (event) => { - reject(); - }); - ws.addEventListener("close", (event) => { - reject(); - }); - - return Promise.race([ - promise, - wait(5000).then(() => { - throw new Error("Timeout"); - }), - ]); -} - -function waitForMessage( - ws: WebSocket, - type: T, -): Promise> { - const { promise, resolve, reject } = - Promise.withResolvers>(); - - function onMessage(event: MessageEvent) { - try { - const data = ToClientSchema.parse(JSON.parse(event.data)); - - if (data.type === type) { - resolve(data as Extract); - ws.removeEventListener("message", onMessage); - } - } catch (e) { - console.error(e); - } - } - - ws.addEventListener("message", onMessage); - ws.addEventListener("error", (event) => { - ws.removeEventListener("message", onMessage); - reject(); - }); - - return Promise.race([ - promise, - wait(5000).then(() => { - throw new Error("Timeout"); - }), - ]); -} diff --git a/frontend/packages/components/src/actors/worker/actor-worker-container.ts b/frontend/packages/components/src/actors/worker/actor-worker-container.ts deleted file mode 100644 index 7352e8e6bc..0000000000 --- a/frontend/packages/components/src/actors/worker/actor-worker-container.ts +++ /dev/null @@ -1,330 +0,0 @@ -import { CancelledError } from "@tanstack/react-query"; -import { toast } from "sonner"; -import { ls } from "../../lib/utils"; -import ActorWorker from "./actor-repl.worker?worker"; -import { - type CodeMessage, - type FormattedCode, - type InitMessage, - type InspectData, - type Log, - ResponseSchema, - type SetStateMessage, -} from "./actor-worker-schema"; - -export type ReplCommand = { - logs: Log[]; - code: string; - key: string; - inputTimestamp?: string; - outputTimestamp?: string; -} & ( - | { status: "pending" } - | { status: "formatted"; formatted: FormattedCode } - | { status: "success"; formatted: FormattedCode; result: unknown } - | { status: "error"; formatted: FormattedCode | undefined; error: unknown } -); - -export type ContainerStatus = - | { type: "ready" } - | { type: "error"; error: unknown } - | { type: "pending" } - | { type: "unsupported"; error: unknown } - | { type: "unknown" }; - -export type ContainerState = { - status: ContainerStatus; - commands: ReplCommand[]; -} & InspectData; - -export class ActorWorkerContainer { - #state: ContainerState = { - status: { type: "unknown" }, - commands: [], - rpcs: [], - state: { enabled: false, value: undefined }, - connections: [], - }; - - #meta: { - actorId: string; - } | null = null; - - #opts: { - notifyOnReconnect?: boolean; - } | null = null; - - #listeners: (() => void)[] = []; - #worker: Worker | undefined; - - // - async init({ - actorId, - endpoint, - signal, - notifyOnReconnect, - }: { - actorId: string; - endpoint: string; - signal: AbortSignal; - notifyOnReconnect?: boolean; - }) { - this.terminate(); - - this.#meta = { actorId }; - this.#opts = { notifyOnReconnect }; - this.#state.status = { type: "pending" }; - this.#update(); - try { - signal.throwIfAborted(); - - // FIXME(RVT-4553) - // if (actor.resources.cpu !== 125 || actor.resources.memory !== 128) { - // throw new Error("Unsupported actor resources"); - // } - - // If we reached this point, the actor is supported - // check if we still operate on the same actor - if (this.#meta.actorId !== actorId) { - // if not, we don't need to do anything - return null; - } - - const worker = new ActorWorker({ name: `actor-${actorId}` }); - signal.throwIfAborted(); - // now worker needs to check if the actor is supported - this.#setupWorker(worker, { actorId, endpoint }); - signal.throwIfAborted(); - return worker; - } catch (e) { - console.log(e); - // If we reached this point, the actor is not supported - // check if we still operate on the same actor - if (e instanceof DOMException && e.name === "AbortError") { - return null; - } - - if (e instanceof CancelledError) { - this.#worker?.terminate(); - this.#worker = undefined; - return null; - } - - this.#worker?.terminate(); - this.#worker = undefined; - this.#state.status = { type: "unsupported", error: e }; - this.#update(); - } - return null; - } - - terminate() { - this.#worker?.terminate(); - this.#worker = undefined; - this.#state.commands = []; - this.#state.status = { type: "unknown" }; - this.#state.rpcs = []; - this.#state.state = { - enabled: false, - value: undefined, - }; - this.#meta = null; - this.#opts = null; - this.#state.connections = []; - this.#update(); - } - - #setupWorker(worker: Worker, data: Omit) { - this.#worker = worker; - this.#worker.addEventListener("message", (event) => { - try { - this.#handleMessage(event); - } catch (e) { - console.error(e); - this.#state.status = { type: "error", error: e }; - this.#update(); - } - }); - - this.#worker.addEventListener("error", (error) => { - console.log(error, error.message, error.error); - }); - - this.#worker.postMessage({ - type: "init", - ...data, - token: ls.get("rivet-token")?.token, - } satisfies InitMessage); - } - - run(data: string) { - const key = Date.now().toString(); - this.#state.commands = [ - ...this.#state.commands, - { status: "pending", code: data, key, logs: [] }, - ]; - - this.#worker?.postMessage({ - type: "code", - data, - id: key, - } satisfies CodeMessage); - this.#update(); - } - - setState(data: string) { - this.#worker?.postMessage({ - type: "set-state", - data, - } satisfies SetStateMessage); - this.#state.state = { - ...this.#state.state, - value: JSON.parse(data || "{}"), - }; - this.#update(); - } - - getCommands() { - return this.#state.commands; - } - - getStatus() { - return this.#state.status; - } - - getRpcs() { - return this.#state.rpcs; - } - - getState() { - return this.#state.state; - } - - getConnections() { - return this.#state.connections; - } - - subscribe(cb: () => void) { - this.#listeners.push(cb); - return () => { - this.#listeners = this.#listeners.filter( - (listener) => listener !== cb, - ); - }; - } - - #handleMessage(event: MessageEvent) { - const { success, data: msg } = ResponseSchema.safeParse(event.data); - - if (!success) { - return; - } - - if (msg.type === "formatted") { - const command = this.#state.commands.find( - (command) => command.key === msg.id, - ); - if (command) { - const newCommand = { - inputTimestamp: new Date().toISOString(), - ...command, - status: "formatted", - formatted: msg.data, - } satisfies ReplCommand; - Object.assign(command, newCommand); - this.#state.commands = [...this.#state.commands]; - this.#update(); - } - } - - if (msg.type === "result") { - const command = this.#state.commands.find( - (command) => command.key === msg.id, - ); - if (command) { - const newCommand = { - outputTimestamp: new Date().toISOString(), - ...command, - status: "success", - result: msg.data, - }; - Object.assign(command, newCommand); - this.#state.commands = [...this.#state.commands]; - this.#update(); - } - } - - if (msg.type === "log") { - const command = this.#state.commands.find( - (command) => command.key === msg.id, - ); - if (command) { - const newCommand = { - ...command, - logs: [...command.logs, msg.data], - }; - Object.assign(command, newCommand); - this.#state.commands = [...this.#state.commands]; - this.#update(); - } - } - - if (msg.type === "error") { - if (!msg.id) { - this.#state.status = { type: "error", error: msg.data }; - console.error("Actor Worker Error", msg.data); - this.#update(); - return; - } - - const command = this.#state.commands.find( - (command) => command.key === msg.id, - ); - if (command) { - const newCommand = { - outputTimestamp: new Date().toISOString(), - ...command, - status: "error", - error: msg.data, - }; - Object.assign(command, newCommand); - this.#state.commands = [...this.#state.commands]; - this.#update(); - } - } - - if (msg.type === "ready") { - if (this.#opts?.notifyOnReconnect) { - toast.success("Connected to Actor", { - id: "ac-ws-reconnect", - }); - } - this.#state.status = { type: "ready" }; - } - - if (msg.type === "inspect" || msg.type === "ready") { - this.#state.rpcs = [...msg.data.rpcs]; - this.#state.state = { - ...msg.data.state, - value: msg.data.state.value || {}, - }; - this.#state.connections = [...msg.data.connections]; - this.#update(); - } - - if (msg.type === "lost-connection") { - this.#state.status = { type: "pending" }; - - if (this.#opts?.notifyOnReconnect) { - toast.loading("Reconnecting...", { id: "ac-ws-reconnect" }); - } - this.#update(); - } - } - - #update() { - for (const listener of this.#listeners) { - listener(); - } - } -} diff --git a/frontend/packages/components/src/actors/worker/actor-worker-context.tsx b/frontend/packages/components/src/actors/worker/actor-worker-context.tsx deleted file mode 100644 index 0d731caae6..0000000000 --- a/frontend/packages/components/src/actors/worker/actor-worker-context.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import { useAtomValue } from "jotai"; -import { selectAtom } from "jotai/utils"; -import { - type ReactNode, - createContext, - useCallback, - useContext, - useEffect, - useState, - useSyncExternalStore, -} from "react"; -import { toast } from "sonner"; -import { assertNonNullable } from "../../lib/utils"; -import { type Actor, type ActorAtom, ActorFeature } from "../actor-context"; -import { ActorWorkerContainer } from "./actor-worker-container"; - -export const ActorWorkerContext = createContext( - null, -); - -export const useActorWorker = () => { - const value = useContext(ActorWorkerContext); - assertNonNullable(value); - return value; -}; - -const selector = (a: Actor) => ({ - actorId: a.id, - endpoint: a.endpoint, - enabled: - !a.destroyedAt && - a.endpoint !== null && - a.startedAt !== null && - a.features?.includes(ActorFeature.Console), -}); - -interface ActorWorkerContextProviderProps { - actor: ActorAtom; - children: ReactNode; - notifyOnReconnect?: boolean; -} - -// FIXME: rewrite with jotai -export const ActorWorkerContextProvider = ({ - children, - actor, - notifyOnReconnect, -}: ActorWorkerContextProviderProps) => { - const { actorId, endpoint, enabled } = useAtomValue( - selectAtom(actor, selector), - ); - - const [container] = useState( - () => new ActorWorkerContainer(), - ); - - // biome-ignore lint/correctness/useExhaustiveDependencies: we want to create worker on each of those props change - useEffect(() => { - const ctrl = new AbortController(); - - if (enabled && endpoint) { - container.init({ - actorId, - endpoint, - notifyOnReconnect, - signal: ctrl.signal, - }); - } else { - toast.dismiss("ac-ws-reconnect"); - } - - return () => { - ctrl.abort(); - container.terminate(); - }; - }, [actorId, endpoint, enabled]); - - return ( - - {children} - - ); -}; - -export function useActorReplCommands() { - const container = useActorWorker(); - return useSyncExternalStore( - useCallback( - (cb) => { - return container.subscribe(cb); - }, - [container], - ), - useCallback(() => { - return container.getCommands(); - }, [container]), - ); -} - -export function useActorWorkerStatus() { - const container = useActorWorker(); - return useSyncExternalStore( - useCallback( - (cb) => { - return container.subscribe(cb); - }, - [container], - ), - useCallback(() => { - return container.getStatus(); - }, [container]), - ); -} - -export function useActorRpcs() { - const container = useActorWorker(); - return useSyncExternalStore( - useCallback( - (cb) => { - return container.subscribe(cb); - }, - [container], - ), - useCallback(() => { - return container.getRpcs(); - }, [container]), - ); -} - -export function useActorState() { - const container = useActorWorker(); - return useSyncExternalStore( - useCallback( - (cb) => { - return container.subscribe(cb); - }, - [container], - ), - useCallback(() => { - return container.getState(); - }, [container]), - ); -} - -export function useActorConnections() { - const container = useActorWorker(); - return useSyncExternalStore( - useCallback( - (cb) => { - return container.subscribe(cb); - }, - [container], - ), - useCallback(() => { - return container.getConnections(); - }, [container]), - ); -} diff --git a/frontend/packages/components/src/actors/worker/actor-worker-schema.ts b/frontend/packages/components/src/actors/worker/actor-worker-schema.ts deleted file mode 100644 index 755bb57b59..0000000000 --- a/frontend/packages/components/src/actors/worker/actor-worker-schema.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { InspectDataSchema } from "actor-core/inspector/protocol/actor"; -import { z } from "zod"; - -export type ReplErrorCode = - | "unsupported" - | "runtime_error" - | "timeout" - | "syntax"; - -const CodeMessageSchema = z.object({ - type: z.literal("code"), - data: z.string(), - id: z.string(), -}); -const InitMessageSchema = z.object({ - type: z.literal("init"), - endpoint: z.string(), - actorId: z.string(), - token: z.string().optional(), -}); - -const SetStateMessageSchema = z.object({ - type: z.literal("set-state"), - data: z.string(), -}); - -export const MessageSchema = z.discriminatedUnion("type", [ - CodeMessageSchema, - InitMessageSchema, - SetStateMessageSchema, -]); - -export const FormattedCodeSchema = z - .object({ - fg: z.string().optional(), - tokens: z.array( - z.array( - z.object({ - content: z.string(), - color: z.string().optional(), - }), - ), - ), - }) - .catch((ctx) => ctx.input); - -export const LogSchema = z.object({ - method: z.union([z.literal("log"), z.literal("warn"), z.literal("error")]), - data: z.array(z.any()).optional(), - timestamp: z.string().optional(), -}); - -export const ResponseSchema = z.discriminatedUnion("type", [ - z.object({ - type: z.literal("error"), - id: z.string().optional(), - data: z.any(), - }), - z.object({ - type: z.literal("formatted"), - id: z.string(), - data: FormattedCodeSchema, - }), - z.object({ - type: z.literal("result"), - id: z.string(), - data: z.any().optional(), - }), - z.object({ - type: z.literal("log"), - id: z.string(), - data: LogSchema, - }), - z.object({ - type: z.literal("ready"), - data: InspectDataSchema, - }), - z.object({ - type: z.literal("inspect"), - data: InspectDataSchema, - }), - z.object({ - type: z.literal("lost-connection"), - }), -]); - -export type Response = z.infer; -export type Message = z.infer; -export type FormattedCode = z.infer; -export type Log = z.infer; -export type InitMessage = z.infer; -export type CodeMessage = z.infer; -export type InspectData = z.infer; -export type SetStateMessage = z.infer; diff --git a/frontend/packages/components/src/actors/worker/actor-worker-status.tsx b/frontend/packages/components/src/actors/worker/actor-worker-status.tsx deleted file mode 100644 index 7838351392..0000000000 --- a/frontend/packages/components/src/actors/worker/actor-worker-status.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { Icon, faExclamationTriangle, faSpinner } from "@rivet-gg/icons"; -import { AnimatePresence, motion } from "framer-motion"; -import type { ContainerStatus } from "./actor-worker-container"; - -interface ActorWorkerStatusProps { - status: ContainerStatus["type"]; -} - -export function ActorWorkerStatus({ status }: ActorWorkerStatusProps) { - return ( - - {status === "pending" ? ( - - - Connecting to Actor... - - ) : null} - {status === "error" ? ( - - - Couldn't connect to Actor. - - ) : null} - {status === "unsupported" ? ( - - - Console is not supported for this Actor. - - ) : null} - - ); -} diff --git a/frontend/src/app/actors.tsx b/frontend/src/app/actors.tsx index aae11f8c78..e67302a8dd 100644 --- a/frontend/src/app/actors.tsx +++ b/frontend/src/app/actors.tsx @@ -1,7 +1,6 @@ import { useQuery } from "@tanstack/react-query"; import { useNavigate, useSearch } from "@tanstack/react-router"; import { - ActorFeature, type ActorId, ActorNotFound, ActorsActorDetails, @@ -13,17 +12,7 @@ import { export function Actors({ actorId }: { actorId: string | undefined }) { return ( - {actorId ? ( - - ) : ( - - )} + {actorId ? : } ); } @@ -37,16 +26,7 @@ function Actor() { ); if (!data || isError) { - return ( - - ); + return ; } return ( diff --git a/frontend/src/app/data-providers/default-data-provider.tsx b/frontend/src/app/data-providers/default-data-provider.tsx index 0b3c0a25f5..34cb877ed3 100644 --- a/frontend/src/app/data-providers/default-data-provider.tsx +++ b/frontend/src/app/data-providers/default-data-provider.tsx @@ -1,25 +1,13 @@ +import type { Rivet } from "@rivetkit/engine-api-full"; import { infiniteQueryOptions, type MutationOptions, mutationOptions, type QueryKey, queryOptions, - UseInfiniteQueryOptions, } from "@tanstack/react-query"; -import type { - ActorId, - ActorLogEntry, - CreateActor as InspectorCreateActor, -} from "rivetkit/inspector"; import { z } from "zod"; -import { - type Actor, - type ActorMetrics, - type Build, - type CrashPolicy, - getActorStatus, - type Region, -} from "@/components/actors"; +import { type ActorId, getActorStatus } from "@/components/actors"; import { queryClient } from "@/queries/global"; export const ActorQueryOptionsSchema = z @@ -53,20 +41,7 @@ export type ActorQueryOptions = z.infer; export const RECORDS_PER_PAGE = 10; -type PaginatedResponse = { - pagination: { cursor?: string }; -} & Record; - -type PaginatedActorResponse = PaginatedResponse; -type PaginatedBuildsResponse = PaginatedResponse; -type PaginatedRegionsResponse = PaginatedResponse; - -type CreateActor = Omit & { - runnerNameSelector: string; - key: string; - crashPolicy: CrashPolicy; - datacenter?: string; -}; +type CreateActor = Omit; const defaultContext = { endpoint: "", @@ -82,23 +57,10 @@ const defaultContext = { refetchInterval: 2000, queryFn: async () => { throw new Error("Not implemented"); - return {} as PaginatedActorResponse; - }, - getNextPageParam: (lastPage) => { - if (lastPage.pagination.cursor) { - return lastPage.pagination.cursor; - } - - if ( - !lastPage || - lastPage.actors.length === 0 || - lastPage.actors.length < RECORDS_PER_PAGE - ) { - return undefined; - } - - return lastPage.actors[lastPage.actors.length - 1].id; + // biome-ignore lint/correctness/noUnreachable: stub + return {} as Rivet.ActorsListResponse; }, + getNextPageParam: (lastPage) => lastPage.pagination.cursor, }); }, @@ -110,13 +72,21 @@ const defaultContext = { refetchInterval: 2000, queryFn: async () => { throw new Error("Not implemented"); - return {} as PaginatedBuildsResponse; + // biome-ignore lint/correctness/noUnreachable: stub + return {} as Rivet.ActorsListNamesResponse; }, getNextPageParam: () => { return undefined; }, select: (data) => { - return data.pages.flatMap((page) => page.builds); + return data.pages.flatMap((page) => + Array.from( + Object.entries(page.names).map(([id, name]) => ({ + id, + name, + })), + ), + ); }, }); }, @@ -126,7 +96,7 @@ const defaultContext = { ...this.buildsQueryOptions(), select: (data) => { return data.pages.reduce((acc, page) => { - return acc + page.builds.length; + return acc + Object.keys(page.names).length; }, 0); }, }); @@ -139,7 +109,7 @@ const defaultContext = { refetchInterval: 5000, select: (data) => { return data.pages.flatMap((page) => - page.actors.map((actor) => actor.id), + page.actors.map((actor) => actor.actorId), ); }, }); @@ -150,7 +120,7 @@ const defaultContext = { ...this.actorsQueryOptions(opts), select: (data) => { return data.pages.flatMap((page) => - page.actors.map((actor) => actor.id), + page.actors.map((actor) => actor.actorId), ).length; }, }); @@ -160,24 +130,17 @@ const defaultContext = { actorQueryOptions(actorId: ActorId) { return queryOptions({ queryFn: async () => { - return {} as Actor; + return {} as Rivet.Actor; }, queryKey: ["actor", actorId] as QueryKey, }); }, - actorRegionQueryOptions(actorId: ActorId) { - return queryOptions({ - ...this.actorQueryOptions(actorId), - select: (data) => data.region, - }); - }, - actorDestroyedAtQueryOptions(actorId: ActorId) { return queryOptions({ ...this.actorQueryOptions(actorId), select: (data) => - data.destroyedAt ? new Date(data.destroyedAt) : null, + data.destroyTs ? new Date(data.destroyTs) : null, }); }, @@ -191,58 +154,39 @@ const defaultContext = { actorStatusAdditionalInfoQueryOptions(actorId: ActorId) { return queryOptions({ ...this.actorQueryOptions(actorId), - select: ({ rescheduleAt }) => ({ - rescheduleAt, + select: ({ rescheduleTs }) => ({ + rescheduleTs, }), }); }, - actorFeaturesQueryOptions(actorId: ActorId) { - return queryOptions({ - ...this.actorQueryOptions(actorId), - select: (data) => data.features ?? [], - }); - }, - actorGeneralQueryOptions(actorId: ActorId) { return queryOptions({ ...this.actorQueryOptions(actorId), select: (data) => ({ - tags: data.tags, keys: data.key, - createdAt: data.createdAt ? new Date(data.createdAt) : null, - destroyedAt: data.destroyedAt - ? new Date(data.destroyedAt) - : null, - connectableAt: data.connectableAt - ? new Date(data.connectableAt) + createTs: data.createTs ? new Date(data.createTs) : null, + destroyTs: data.destroyTs ? new Date(data.destroyTs) : null, + connectableTs: data.connectableTs + ? new Date(data.connectableTs) : null, - pendingAllocationAt: data.pendingAllocationAt - ? new Date(data.pendingAllocationAt) + pendingAllocationTs: data.pendingAllocationTs + ? new Date(data.pendingAllocationTs) : null, - sleepingAt: data.sleepingAt ? new Date(data.sleepingAt) : null, - region: data.region, - runner: data.runner, + sleepTs: data.sleepTs ? new Date(data.sleepTs) : null, + datacenter: data.datacenter, + runner: data.runnerNameSelector, crashPolicy: data.crashPolicy, }), }); }, - actorBuildQueryOptions(actorId: ActorId) { - return queryOptions({ - queryKey: ["actor", actorId, "build"] as QueryKey, - queryFn: async () => { - throw new Error("Not implemented"); - return {} as Build; - }, - enabled: false, - }); - }, actorMetricsQueryOptions(actorId: ActorId) { return queryOptions({ queryKey: ["actor", actorId, "metrics"] as QueryKey, queryFn: async () => { throw new Error("Not implemented"); - return {} as ActorMetrics; + // biome-ignore lint/correctness/noUnreachable: stub + return {}; }, enabled: false, }); @@ -283,81 +227,47 @@ const defaultContext = { initialPageParam: null as string | null, queryFn: async () => { throw new Error("Not implemented"); - return [] as ActorLogEntry[]; + // biome-ignore lint/correctness/noUnreachable: stub + return []; }, getNextPageParam: () => null, }); }, - actorNetworkQueryOptions(actorId: ActorId) { - return queryOptions({ - ...this.actorQueryOptions(actorId), - select: (data) => data.network, - }); - }, - actorNetworkPortsQueryOptions(actorId: ActorId) { - return queryOptions({ - ...this.actorNetworkQueryOptions(actorId), - select: (data) => data.network?.ports, - }); - }, - actorRuntimeQueryOptions(actorId: ActorId) { - return queryOptions({ - ...this.actorQueryOptions(actorId), - select: ({ runtime, lifecycle, tags }) => ({ - runtime, - lifecycle, - tags, - }), - }); - }, actorWorkerQueryOptions(actorId: ActorId) { return queryOptions({ ...this.actorQueryOptions(actorId), select: (data) => ({ - features: data.features ?? [], name: data.name ?? null, endpoint: this.endpoint ?? null, - destroyedAt: data.destroyedAt - ? new Date(data.destroyedAt) - : null, - runner: data.runner ?? undefined, - sleepingAt: data.sleepingAt ? new Date(data.sleepingAt) : null, - startedAt: data.startedAt ? new Date(data.startedAt) : null, + destroyedAt: data.destroyTs ? new Date(data.destroyTs) : null, + runner: data.runnerNameSelector ?? undefined, + sleepingAt: data.sleepTs ? new Date(data.sleepTs) : null, + startedAt: data.startTs ? new Date(data.startTs) : null, }), }); }, // #endregion - regionsQueryOptions() { + datacentersQueryOptions() { return infiniteQueryOptions({ queryKey: ["actor", "regions"] as QueryKey, initialPageParam: null as string | null, queryFn: async () => { throw new Error("Not implemented"); - return {} as PaginatedRegionsResponse; + // biome-ignore lint/correctness/noUnreachable: stub + return {} as Rivet.DatacentersListResponse; }, getNextPageParam: () => null, - select: (data) => data.pages.flatMap((page) => page.regions), + select: (data) => data.pages.flatMap((page) => page.datacenters), }); }, - regionQueryOptions(regionId: string | undefined) { + datacenterQueryOptions(regionId: string | undefined) { return queryOptions({ queryKey: ["actor", "region", regionId] as QueryKey, enabled: !!regionId, queryFn: async () => { throw new Error("Not implemented"); - return {} as Region; - }, - }); - }, - statusQueryOptions() { - return queryOptions({ - queryKey: ["status"] as QueryKey, - refetchInterval: 1000, - enabled: false, - retry: 0, - queryFn: async () => { - throw new Error("Not implemented"); - return false as boolean; + // biome-ignore lint/correctness/noUnreachable: stub + return {} as Rivet.Datacenter; }, }); }, @@ -366,6 +276,7 @@ const defaultContext = { mutationKey: ["createActor"] as QueryKey, mutationFn: async (_: CreateActor) => { throw new Error("Not implemented"); + // biome-ignore lint/correctness/noUnreachable: stub return ""; }, onSuccess: () => { diff --git a/frontend/src/app/data-providers/engine-data-provider.tsx b/frontend/src/app/data-providers/engine-data-provider.tsx index 3a651e6fc3..2e8d958c9e 100644 --- a/frontend/src/app/data-providers/engine-data-provider.tsx +++ b/frontend/src/app/data-providers/engine-data-provider.tsx @@ -5,16 +5,10 @@ import { mutationOptions, type QueryKey, queryOptions, - UseQueryOptions, } from "@tanstack/react-query"; import z from "zod"; import { getConfig, ls } from "@/components"; -import { - type Actor, - ActorFeature, - type ActorId, - type CrashPolicy, -} from "@/components/actors"; +import type { ActorId } from "@/components/actors"; import { engineEnv } from "@/lib/env"; import { convertStringToId } from "@/lib/utils"; import { noThrow, shouldRetryAllExpect403 } from "@/queries/utils"; @@ -141,39 +135,17 @@ export const createNamespaceContext = ({ canCreateActors: true, canDeleteActors: true, }, - statusQueryOptions() { - return queryOptions({ - ...def.statusQueryOptions(), - queryKey: [{ namespace }, ...def.statusQueryOptions().queryKey], - enabled: true, - queryFn: async () => { - return true; - }, - retry: shouldRetryAllExpect403, - throwOnError: noThrow, - meta: { - mightRequireAuth, - }, - }); - }, - regionsQueryOptions() { + datacentersQueryOptions() { return infiniteQueryOptions({ - ...def.regionsQueryOptions(), + ...def.datacentersQueryOptions(), enabled: true, queryKey: [ { namespace }, - ...def.regionsQueryOptions().queryKey, + ...def.datacentersQueryOptions().queryKey, ] as QueryKey, queryFn: async () => { const data = await client.datacenters.list(); - return { - regions: data.datacenters.map((dc) => ({ - id: dc.name, - name: dc.name, - url: dc.url, - })), - pagination: data.pagination, - }; + return data; }, retry: shouldRetryAllExpect403, throwOnError: noThrow, @@ -182,27 +154,27 @@ export const createNamespaceContext = ({ }, }); }, - regionQueryOptions(regionId: string | undefined) { + datacenterQueryOptions(name: string | undefined) { return queryOptions({ - ...def.regionQueryOptions(regionId), + ...def.datacenterQueryOptions(name), queryKey: [ { namespace }, - ...def.regionQueryOptions(regionId).queryKey, + ...def.datacenterQueryOptions(name).queryKey, ], queryFn: async ({ client }) => { const regions = await client.ensureInfiniteQueryData( - this.regionsQueryOptions(), + this.datacentersQueryOptions(), ); for (const page of regions.pages) { - for (const region of page.regions) { - if (region.id === regionId) { + for (const region of page.datacenters) { + if (region.name === name) { return region; } } } - throw new Error(`Region not found: ${regionId}`); + throw new Error(`Region not found: ${name}`); }, retry: shouldRetryAllExpect403, throwOnError: noThrow, @@ -328,22 +300,9 @@ export const createNamespaceContext = ({ { abortSignal }, ); - return { - pagination: data.pagination, - builds: Object.keys(data.names) - .sort() - .map((build) => ({ - id: build, - name: build, - })), - }; - }, - getNextPageParam: (lastPage) => { - if (lastPage.builds.length < RECORDS_PER_PAGE) { - return undefined; - } - return lastPage.pagination.cursor; + return data; }, + getNextPageParam: (lastPage) => lastPage.pagination.cursor, retry: shouldRetryAllExpect403, throwOnError: noThrow, meta: { @@ -679,42 +638,27 @@ function transformActor(a: Rivet.Actor): Actor { id: a.actorId as ActorId, name: a.name, key: a.key ? a.key : undefined, - connectableAt: a.connectableTs + connectableTs: a.connectableTs ? new Date(a.connectableTs).toISOString() : undefined, region: a.datacenter, createdAt: new Date(a.createTs).toISOString(), - startedAt: a.startTs ? new Date(a.startTs).toISOString() : undefined, - destroyedAt: a.destroyTs + startTs: a.startTs ? new Date(a.startTs).toISOString() : undefined, + destroyTs: a.destroyTs ? new Date(a.destroyTs).toISOString() : undefined, - sleepingAt: a.sleepTs ? new Date(a.sleepTs).toISOString() : undefined, - pendingAllocationAt: a.pendingAllocationTs + sleepTs: a.sleepTs ? new Date(a.sleepTs).toISOString() : undefined, + pendingAllocationTs: a.pendingAllocationTs ? new Date(a.pendingAllocationTs).toISOString() : undefined, crashPolicy: a.crashPolicy as CrashPolicy, runner: a.runnerNameSelector, - rescheduleAt: a.rescheduleTs + rescheduleTs: a.rescheduleTs ? new Date(a.rescheduleTs).toISOString() : undefined, - features: [ - ActorFeature.Config, - ActorFeature.Connections, - ActorFeature.State, - ActorFeature.Console, - ActorFeature.Database, - ActorFeature.EventsMonitoring, - ], }; } -type RunnerConfig = [ - string, - { - datacenters: Record; - }, -]; - export function hasMetadataProvider( metadata: unknown, ): metadata is { provider?: string } { diff --git a/frontend/src/app/data-providers/inspector-data-provider.tsx b/frontend/src/app/data-providers/inspector-data-provider.tsx index be6afd2d9b..5853c6ddf7 100644 --- a/frontend/src/app/data-providers/inspector-data-provider.tsx +++ b/frontend/src/app/data-providers/inspector-data-provider.tsx @@ -46,9 +46,9 @@ export const createGlobalContext = (opts: { url?: string; token?: string }) => { }, }; }, - regionsQueryOptions() { + datacentersQueryOptions() { return infiniteQueryOptions({ - ...def.regionsQueryOptions(), + ...def.datacentersQueryOptions(), enabled: true, queryFn: async () => { return { diff --git a/frontend/src/app/dialogs/connect-manual-serverfull-frame.tsx b/frontend/src/app/dialogs/connect-manual-serverfull-frame.tsx index eb0065ab32..6990b53e95 100644 --- a/frontend/src/app/dialogs/connect-manual-serverfull-frame.tsx +++ b/frontend/src/app/dialogs/connect-manual-serverfull-frame.tsx @@ -64,11 +64,11 @@ export default function ConnectManualServerlfullFrameContent({ provider, }: ConnectManualServerlfullFrameContentProps) { usePrefetchInfiniteQuery({ - ...useEngineCompatDataProvider().regionsQueryOptions(), + ...useEngineCompatDataProvider().datacentersQueryOptions(), pages: Infinity, }); const { data } = useSuspenseInfiniteQuery( - useEngineCompatDataProvider().regionsQueryOptions(), + useEngineCompatDataProvider().datacentersQueryOptions(), ); const prefferedRegionForRailway = @@ -373,7 +373,9 @@ const useSelectedDatacenter = () => { const datacenter = useWatch({ name: "datacenter" }); const { data } = useQuery( - useEngineCompatDataProvider().regionQueryOptions(datacenter || "auto"), + useEngineCompatDataProvider().datacenterQueryOptions( + datacenter || "auto", + ), ); return data?.url || engineEnv().VITE_APP_API_URL; diff --git a/frontend/src/app/dialogs/connect-manual-serverless-frame.tsx b/frontend/src/app/dialogs/connect-manual-serverless-frame.tsx index 71e32cb2f1..611516bd81 100644 --- a/frontend/src/app/dialogs/connect-manual-serverless-frame.tsx +++ b/frontend/src/app/dialogs/connect-manual-serverless-frame.tsx @@ -66,12 +66,12 @@ export default function ConnectManualServerlessFrameContent({ onClose, }: ConnectManualServerlessFrameContentProps) { usePrefetchInfiniteQuery({ - ...useEngineCompatDataProvider().regionsQueryOptions(), + ...useEngineCompatDataProvider().datacentersQueryOptions(), pages: Infinity, }); const { data: datacenters } = useSuspenseInfiniteQuery( - useEngineCompatDataProvider().regionsQueryOptions(), + useEngineCompatDataProvider().datacentersQueryOptions(), ); return ; diff --git a/frontend/src/app/dialogs/connect-quick-railway-frame.tsx b/frontend/src/app/dialogs/connect-quick-railway-frame.tsx index 5a3bf6631c..1c8561ebf0 100644 --- a/frontend/src/app/dialogs/connect-quick-railway-frame.tsx +++ b/frontend/src/app/dialogs/connect-quick-railway-frame.tsx @@ -52,11 +52,11 @@ export default function ConnectQuickRailwayFrameContent({ onClose, }: ConnectQuickRailwayFrameContentProps) { usePrefetchInfiniteQuery({ - ...useEngineCompatDataProvider().regionsQueryOptions(), + ...useEngineCompatDataProvider().datacentersQueryOptions(), pages: Infinity, }); const { data } = useSuspenseInfiniteQuery( - useEngineCompatDataProvider().regionsQueryOptions(), + useEngineCompatDataProvider().datacentersQueryOptions(), ); const prefferedRegionForRailway = diff --git a/frontend/src/app/dialogs/connect-quick-vercel-frame.tsx b/frontend/src/app/dialogs/connect-quick-vercel-frame.tsx index 2e24a0212e..14e9970ede 100644 --- a/frontend/src/app/dialogs/connect-quick-vercel-frame.tsx +++ b/frontend/src/app/dialogs/connect-quick-vercel-frame.tsx @@ -44,12 +44,12 @@ export default function ConnectQuickVercelFrameContent({ onClose, }: ConnectQuickVercelFrameContentProps) { usePrefetchInfiniteQuery({ - ...useEngineCompatDataProvider().regionsQueryOptions(), + ...useEngineCompatDataProvider().datacentersQueryOptions(), pages: Infinity, }); const { data: datacenters } = useSuspenseInfiniteQuery( - useEngineCompatDataProvider().regionsQueryOptions(), + useEngineCompatDataProvider().datacentersQueryOptions(), ); return ( diff --git a/frontend/src/app/dialogs/connect-railway-frame.tsx b/frontend/src/app/dialogs/connect-railway-frame.tsx index 6b6581cfa0..0b9cfe14ee 100644 --- a/frontend/src/app/dialogs/connect-railway-frame.tsx +++ b/frontend/src/app/dialogs/connect-railway-frame.tsx @@ -66,11 +66,11 @@ export default function ConnectRailwayFrameContent({ onClose, }: ConnectRailwayFrameContentProps) { usePrefetchInfiniteQuery({ - ...useEngineCompatDataProvider().regionsQueryOptions(), + ...useEngineCompatDataProvider().datacentersQueryOptions(), pages: Infinity, }); const { data } = useSuspenseInfiniteQuery( - useEngineCompatDataProvider().regionsQueryOptions(), + useEngineCompatDataProvider().datacentersQueryOptions(), ); const prefferedRegionForRailway = @@ -353,7 +353,9 @@ export const useSelectedDatacenter = () => { const datacenter = useWatch({ name: "datacenter" }); const { data } = useQuery( - useEngineCompatDataProvider().regionQueryOptions(datacenter || "auto"), + useEngineCompatDataProvider().datacenterQueryOptions( + datacenter || "auto", + ), ); return data?.url || engineEnv().VITE_APP_API_URL; diff --git a/frontend/src/app/dialogs/connect-vercel-frame.tsx b/frontend/src/app/dialogs/connect-vercel-frame.tsx index 1b3e661c0a..e51272dd73 100644 --- a/frontend/src/app/dialogs/connect-vercel-frame.tsx +++ b/frontend/src/app/dialogs/connect-vercel-frame.tsx @@ -89,12 +89,12 @@ export default function CreateProjectFrameContent({ onClose, }: CreateProjectFrameContentProps) { usePrefetchInfiniteQuery({ - ...useEngineCompatDataProvider().regionsQueryOptions(), + ...useEngineCompatDataProvider().datacentersQueryOptions(), pages: Infinity, }); const { data: datacenters } = useSuspenseInfiniteQuery( - useEngineCompatDataProvider().regionsQueryOptions(), + useEngineCompatDataProvider().datacentersQueryOptions(), ); return ( diff --git a/frontend/src/app/forms/connect-manual-serverless-form.tsx b/frontend/src/app/forms/connect-manual-serverless-form.tsx index 8ed1cdd5e1..217d9c8901 100644 --- a/frontend/src/app/forms/connect-manual-serverless-form.tsx +++ b/frontend/src/app/forms/connect-manual-serverless-form.tsx @@ -105,7 +105,7 @@ export const RunnerName = function RunnerName() { export const Datacenters = function Datacenter() { const { control } = useFormContext(); const { data, hasNextPage, fetchNextPage } = useInfiniteQuery( - useEngineCompatDataProvider().regionsQueryOptions(), + useEngineCompatDataProvider().datacentersQueryOptions(), ); return ( diff --git a/frontend/src/components/actors/actor-build.tsx b/frontend/src/components/actors/actor-build.tsx deleted file mode 100644 index c19adb9ae1..0000000000 --- a/frontend/src/components/actors/actor-build.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; -import { Dd, DiscreteCopyButton, Dl, Dt, Flex } from "@/components"; -import { useDataProvider } from "./data-provider"; -import type { ActorId } from "./queries"; - -interface ActorBuildProps { - actorId: ActorId; -} - -export function ActorBuild({ actorId }: ActorBuildProps) { - const { data } = useQuery( - useDataProvider().actorBuildQueryOptions(actorId), - ); - - if (!data) { - return null; - } - - return ( -
-
-

Build

-
- -
-
ID
-
- - {data.id} - -
-
-
-
- ); -} diff --git a/frontend/src/components/actors/actor-connections-tab.tsx b/frontend/src/components/actors/actor-connections-tab.tsx index 36b04f402d..03c60d9799 100644 --- a/frontend/src/components/actors/actor-connections-tab.tsx +++ b/frontend/src/components/actors/actor-connections-tab.tsx @@ -1,9 +1,9 @@ import { useQuery } from "@tanstack/react-query"; import { LiveBadge, ScrollArea } from "@/components"; -import { useActor } from "./actor-queries-context"; import { ActorObjectInspector } from "./console/actor-inspector"; import { useDataProvider } from "./data-provider"; -import { type ActorId, useActorConnectionsStream } from "./queries"; +import { useInspector } from "./inspector-context"; +import type { ActorId } from "./queries"; interface ActorConnectionsTabProps { actorId: ActorId; @@ -14,12 +14,10 @@ export function ActorConnectionsTab({ actorId }: ActorConnectionsTabProps) { useDataProvider().actorDestroyedAtQueryOptions(actorId), ); - const actorQueries = useActor(); - const { - data: { connections } = {}, - isError, - isLoading, - } = useQuery(actorQueries.actorConnectionsQueryOptions(actorId)); + const inspector = useInspector(); + const { data: { connections } = {} } = useQuery( + inspector.actorConnectionsQueryOptions(actorId), + ); // useActorConnectionsStream(actorId); @@ -31,24 +29,6 @@ export function ActorConnectionsTab({ actorId }: ActorConnectionsTabProps) { ); } - if (isError) { - return ( -
- Connections Preview is currently unavailable. -
- See console/logs for more details. -
- ); - } - - if (isLoading) { - return ( -
- Loading connections... -
- ); - } - return (
diff --git a/frontend/src/components/actors/actor-context.tsx b/frontend/src/components/actors/actor-context.tsx deleted file mode 100644 index d62efbb389..0000000000 --- a/frontend/src/components/actors/actor-context.tsx +++ /dev/null @@ -1,4 +0,0 @@ -export { - createActorInspectorClient, - createManagerInspectorClient, -} from "rivetkit/inspector"; diff --git a/frontend/src/components/actors/actor-queries-context.tsx b/frontend/src/components/actors/actor-queries-context.tsx deleted file mode 100644 index c9ef62ab70..0000000000 --- a/frontend/src/components/actors/actor-queries-context.tsx +++ /dev/null @@ -1,270 +0,0 @@ -import { queryOptions } from "@tanstack/react-query"; -import { createContext, useContext } from "react"; -import { - createActorInspectorClient, - type RecordedRealtimeEvent, -} from "rivetkit/inspector"; -import type { ActorId } from "./queries"; - -type RequestOptions = Parameters[1]; - -export const createDefaultActorContext = ( - { hash }: { hash: string } = { hash: `${Date.now()}` }, -) => ({ - createActorInspectorFetchConfiguration: async ( - actorId: ActorId | string, - opts: { auth?: boolean } = { auth: true }, - ): Promise => ({ - headers: { - "X-RivetKit-Query": JSON.stringify({ - getForId: { actorId }, - }), - }, - }), - createActorInspectorUrl(actorId: ActorId | string) { - return "http://localhost:6420/registry/actors/inspect"; - }, - async createActorInspector( - actorId: ActorId | string, - opts: { auth?: boolean } = { auth: true }, - ) { - return createActorInspectorClient( - this.createActorInspectorUrl(actorId), - await this.createActorInspectorFetchConfiguration(actorId, opts), - ); - }, - actorPingQueryOptions( - actorId: ActorId, - opts: { enabled?: boolean; refetchInterval?: number | false } = {}, - ) { - return queryOptions({ - enabled: false, - refetchInterval: 1000, - ...opts, - queryKey: [hash, "actor", actorId, "ping"], - queryFn: async ({ queryKey: [, , actorId] }) => { - const client = await this.createActorInspector(actorId); - const response = await client.ping.$get(); - if (!response.ok) { - throw response; - } - return await response.json(); - }, - }); - }, - - actorStateQueryOptions( - actorId: ActorId, - { enabled }: { enabled: boolean } = { enabled: true }, - ) { - return queryOptions({ - enabled, - refetchInterval: 1000, - queryKey: [hash, "actor", actorId, "state"], - queryFn: async ({ queryKey: [, , actorId] }) => { - const client = await this.createActorInspector(actorId); - const response = await client.state.$get(); - - if (!response.ok) { - throw response; - } - return (await response.json()) as { - enabled: boolean; - state: unknown; - }; - }, - }); - }, - - actorConnectionsQueryOptions( - actorId: ActorId, - { enabled }: { enabled: boolean } = { enabled: true }, - ) { - return queryOptions({ - enabled, - refetchInterval: 1000, - queryKey: [hash, "actor", actorId, "connections"], - queryFn: async ({ queryKey: [, , actorId] }) => { - const client = await this.createActorInspector(actorId); - const response = await client.connections.$get(); - - if (!response.ok) { - throw response; - } - return await response.json(); - }, - }); - }, - - actorDatabaseQueryOptions( - actorId: ActorId, - { enabled }: { enabled: boolean } = { enabled: true }, - ) { - return queryOptions({ - enabled, - queryKey: [hash, "actor", actorId, "database"], - queryFn: async ({ queryKey: [, , actorId] }) => { - const client = await this.createActorInspector(actorId); - const response = await client.db.$get(); - - if (!response.ok) { - throw response; - } - return await response.json(); - }, - }); - }, - - actorDatabaseEnabledQueryOptions( - actorId: ActorId, - { enabled }: { enabled: boolean } = { enabled: true }, - ) { - return queryOptions({ - ...this.actorDatabaseQueryOptions(actorId, { enabled }), - select: (data) => data.enabled, - notifyOnChangeProps: ["data", "isError", "isLoading"], - }); - }, - - actorDatabaseTablesQueryOptions( - actorId: ActorId, - { enabled }: { enabled: boolean } = { enabled: true }, - ) { - return queryOptions({ - ...this.actorDatabaseQueryOptions(actorId, { enabled }), - select: (data) => - data.db?.map((table) => ({ - name: table.table.name, - type: table.table.type, - records: table.records, - })) || [], - notifyOnChangeProps: ["data", "isError", "isLoading"], - }); - }, - - actorDatabaseRowsQueryOptions( - actorId: ActorId, - table: string, - { enabled }: { enabled: boolean } = { enabled: true }, - ) { - return queryOptions({ - enabled, - staleTime: 0, - gcTime: 5000, - queryKey: [hash, "actor", actorId, "database", table], - queryFn: async ({ queryKey: [, , actorId, , table] }) => { - const client = await this.createActorInspector(actorId); - const response = await client.db.$post({ - json: { query: `SELECT * FROM ${table} LIMIT 500` }, - }); - if (!response.ok) { - throw response; - } - return await response.json(); - }, - }); - }, - - actorEventsQueryOptions( - actorId: ActorId, - { enabled }: { enabled: boolean } = { enabled: true }, - ) { - return queryOptions({ - enabled, - refetchInterval: 1000, - queryKey: [hash, "actor", actorId, "events"], - queryFn: async ({ queryKey: [, , actorId] }) => { - const client = await this.createActorInspector(actorId); - const response = await client.events.$get(); - - if (!response.ok) { - throw response; - } - return (await response.json()) as { - events: RecordedRealtimeEvent[]; - }; - }, - }); - }, - - actorRpcsQueryOptions( - actorId: ActorId, - { enabled }: { enabled: boolean } = { enabled: true }, - ) { - return queryOptions({ - enabled, - queryKey: [hash, "actor", actorId, "rpcs"], - queryFn: async ({ queryKey: [, , actorId] }) => { - const client = await this.createActorInspector(actorId); - const response = await client.rpcs.$get(); - - if (!response.ok) { - throw response; - } - return await response.json(); - }, - }); - }, - - actorClearEventsMutationOptions(actorId: ActorId) { - return { - mutationKey: [hash, "actor", actorId, "clear-events"], - mutationFn: async () => { - const client = await this.createActorInspector(actorId); - const response = await client.events.clear.$post(); - if (!response.ok) { - throw response; - } - return await response.json(); - }, - }; - }, - - actorWakeUpMutationOptions(actorId: ActorId) { - return { - mutationKey: [hash, "actor", actorId, "wake-up"], - mutationFn: async () => { - const client = await this.createActorInspector(actorId); - try { - await client.ping.$get(); - return true; - } catch { - return false; - } - }, - }; - }, - - actorAutoWakeUpQueryOptions( - actorId: ActorId, - { enabled }: { enabled?: boolean } = {}, - ) { - return queryOptions({ - enabled, - refetchInterval: 1000, - staleTime: 0, - gcTime: 0, - queryKey: [hash, "actor", actorId, "auto-wake-up"], - queryFn: async ({ queryKey: [, , actorId] }) => { - const client = await this.createActorInspector(actorId, { - auth: false, - }); - try { - await client.ping.$get(); - return true; - } catch { - return false; - } - }, - retry: false, - }); - }, -}); - -export type ActorContext = ReturnType; - -const ActorContext = createContext({} as ActorContext); - -export const useActor = () => useContext(ActorContext); - -export const ActorProvider = ActorContext.Provider; diff --git a/frontend/src/components/actors/actor-region.tsx b/frontend/src/components/actors/actor-region.tsx index 9a5f0b9a1c..1921e96a71 100644 --- a/frontend/src/components/actors/actor-region.tsx +++ b/frontend/src/components/actors/actor-region.tsx @@ -20,7 +20,7 @@ export function ActorRegion({ className, }: ActorRegionProps) { const { data: region } = useQuery( - useDataProvider().regionQueryOptions(regionId), + useDataProvider().datacenterQueryOptions(regionId), ); if (!regionId || !region) { diff --git a/frontend/src/components/actors/actor-runtime.tsx b/frontend/src/components/actors/actor-runtime.tsx deleted file mode 100644 index c3803df811..0000000000 --- a/frontend/src/components/actors/actor-runtime.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Suspense } from "react"; -import { Skeleton } from "../ui/skeleton"; -import { ActorBuild } from "./actor-build"; -import type { ActorId } from "./queries"; - -export interface ActorRuntimeProps { - actorId: ActorId; -} - -export function ActorRuntime({ actorId }: ActorRuntimeProps) { - return ( - }> - - - ); -} diff --git a/frontend/src/components/actors/actor-status-label.tsx b/frontend/src/components/actors/actor-status-label.tsx index 2faac42c55..34ed06518a 100644 --- a/frontend/src/components/actors/actor-status-label.tsx +++ b/frontend/src/components/actors/actor-status-label.tsx @@ -48,17 +48,17 @@ export function QueriedActorStatusAdditionalInfo({ }: { actorId: ActorId; }) { - const { data: { rescheduleAt } = {} } = useQuery( + const { data: { rescheduleTs } = {} } = useQuery( useDataProvider().actorStatusAdditionalInfoQueryOptions(actorId), ); - if (rescheduleAt) { + if (rescheduleTs) { return ( Will try to start again{" "} - ( - {formatISO(rescheduleAt)}){" "} + ( + {formatISO(rescheduleTs)}){" "} ); diff --git a/frontend/src/components/actors/actors-actor-details.tsx b/frontend/src/components/actors/actors-actor-details.tsx index 4b782e3dd5..57196a1b28 100644 --- a/frontend/src/components/actors/actors-actor-details.tsx +++ b/frontend/src/components/actors/actors-actor-details.tsx @@ -21,12 +21,11 @@ import { QueriedActorStatus } from "./actor-status"; import { ActorStopButton } from "./actor-stop-button"; import { useActorsView } from "./actors-view-context-provider"; import { ActorConsole } from "./console/actor-console"; -import { useDataProvider } from "./data-provider"; import { GuardConnectableInspector, useInspectorGuard, } from "./guard-connectable-inspector"; -import { ActorFeature, type ActorId } from "./queries"; +import type { ActorId } from "./queries"; import { ActorWorkerContextProvider } from "./worker/actor-worker-context"; interface ActorsActorDetailsProps { @@ -43,24 +42,17 @@ interface ActorsActorDetailsProps { export const ActorsActorDetails = memo( ({ tab, onTabChange, actorId }: ActorsActorDetailsProps) => { - const { data: features = [] } = useQuery( - useDataProvider().actorFeaturesQueryOptions(actorId), - ); - - const supportsConsole = features.includes(ActorFeature.Console); - return (
- {supportsConsole ? : null} +
@@ -80,15 +72,11 @@ function Console({ actorId }: { actorId: ActorId }) { ); } -export const ActorsActorEmptyDetails = ({ - features, -}: { - features: ActorFeature[]; -}) => { +export const ActorsActorEmptyDetails = () => { const { copy } = useActorsView(); return (
- +

{copy.selectActor}

@@ -100,7 +88,6 @@ export const ActorsActorEmptyDetails = ({ export function ActorTabs({ tab, - features, onTabChange, actorId, className, @@ -109,22 +96,12 @@ export function ActorTabs({ }: { disabled?: boolean; tab?: string; - features: ActorFeature[]; onTabChange?: (tab: string) => void; actorId?: ActorId; className?: string; children?: ReactNode; }) { - const supportsState = features?.includes(ActorFeature.State); - const supportsLogs = features?.includes(ActorFeature.Logs); - const supportsConnections = features?.includes(ActorFeature.Connections); - const supportsMetadata = features?.includes(ActorFeature.Config); - const supportsMetrics = features?.includes(ActorFeature.Metrics); - const supportsEvents = features?.includes(ActorFeature.EventsMonitoring); - const supportsDatabase = features?.includes(ActorFeature.Database); - - const defaultTab = supportsState ? "state" : "logs"; - const value = disabled ? undefined : tab || defaultTab; + const value = disabled ? undefined : tab || "state"; const guardContent = useInspectorGuard(); @@ -138,69 +115,59 @@ export function ActorTabs({
- {supportsState ? ( - - State - - ) : null} - {supportsConnections ? ( - - Connections - - ) : null} - {supportsEvents ? ( - - Events - - ) : null} - {supportsDatabase ? ( - - Database - - ) : null} - {supportsLogs ? ( - + State + + + + Connections + + + + Events + + + + Database + + + {/* Logs - - ) : null} - {supportsMetadata ? ( - - Metadata - - ) : null} - {supportsMetrics ? ( - */} + + Metadata + + {/* Metrics - - ) : null} + */} {actorId ? ( {actorId ? ( <> - {supportsLogs ? ( - - }> - {guardContent || ( - - )} - - - ) : null} - {supportsMetadata ? ( - - - - ) : null} - {supportsConnections ? ( - - {guardContent || ( - - )} - - ) : null} - {supportsEvents ? ( - - {guardContent || ( - - )} - - ) : null} - {supportsDatabase ? ( - - {guardContent || ( - - )} - - ) : null} - {supportsState ? ( - - {guardContent || ( - - )} - - ) : null} - {supportsMetrics ? ( + + }> + {guardContent || } + + + + + + + {guardContent || ( + + )} + + + + {guardContent || } + + + {guardContent || } + + + {guardContent || } + + {/* {supportsMetrics ? ( )} - ) : null} + ) : null} */} ) : null} {children} diff --git a/frontend/src/components/actors/index.ts b/frontend/src/components/actors/index.ts index bdf87a9973..4f39184f87 100644 --- a/frontend/src/components/actors/index.ts +++ b/frontend/src/components/actors/index.ts @@ -1,6 +1,4 @@ -export * from "./actor-context"; export * from "./actor-not-found"; -export * from "./actor-queries-context"; export * from "./actor-region"; export * from "./actor-status-indicator"; export * from "./actor-status-label"; diff --git a/frontend/src/components/actors/inspector-context.tsx b/frontend/src/components/actors/inspector-context.tsx new file mode 100644 index 0000000000..fe00180040 --- /dev/null +++ b/frontend/src/components/actors/inspector-context.tsx @@ -0,0 +1 @@ +export const useInspector = () => {}; diff --git a/frontend/src/components/actors/queries/actor.ts b/frontend/src/components/actors/queries/actor.ts deleted file mode 100644 index d372d9a996..0000000000 --- a/frontend/src/components/actors/queries/actor.ts +++ /dev/null @@ -1,226 +0,0 @@ -import { fetchEventSource } from "@microsoft/fetch-event-source"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { compare } from "fast-json-patch"; -import { useCallback, useEffect } from "react"; -import type { ActorId, RecordedRealtimeEvent } from "rivetkit/inspector"; -import { useAsyncMemo } from "@/components/hooks/use-async-memo"; -import { useActor } from "../actor-queries-context"; - -export const useActorClearEventsMutation = ( - actorId: ActorId, - options?: Parameters[1], -) => { - const queryClient = useQueryClient(); - const queries = useActor(); - return useMutation({ - ...queries.actorClearEventsMutationOptions(actorId), - onMutate: async () => { - queryClient.setQueryData( - queries.actorEventsQueryOptions(actorId).queryKey, - () => ({ events: [] }), - ); - }, - ...options, - }); -}; - -export const useActorStatePatchMutation = ( - actorId: ActorId, - options?: Parameters[1], -) => { - const queryClient = useQueryClient(); - const queries = useActor(); - return useMutation({ - mutationFn: async (data: any) => { - const client = await queries.createActorInspector(actorId); - - const oldStateQuery = queryClient.getQueryData( - queries.actorStateQueryOptions(actorId).queryKey, - ); - - const oldState = oldStateQuery?.state; - - let response: Awaited>; - - if (!oldState || !isPatchable(data)) { - response = await client.state.$patch({ - // its okay, we know the type - json: { replace: data }, - }); - } else { - const patches = compare(oldState, data); - response = await client.state.$patch({ - // its okay, we know the type - // @ts-expect-error - json: { patch: patches }, - }); - } - - if (!response.ok) { - throw response; - } - return (await response.json()) as { - state: unknown; - enabled: boolean; - }; - }, - onSuccess: (data) => { - queryClient.setQueryData( - queries.actorStateQueryOptions(actorId).queryKey, - () => ({ enabled: true, state: data.state }), - ); - }, - ...options, - }); -}; - -const getHeaders = ( - v: - | Record - | (() => Record | Promise>), -) => { - if (typeof v === "function") { - return v(); - } - return v; -}; - -function useStream( - actorId: ActorId, - onMessage: (data: T) => void, - url: string | null | undefined, - opts: { enabled: boolean } = { enabled: true }, -) { - const stableOnMessage = useCallback(onMessage, []); - const queries = useActor(); - - useEffect(() => { - const controller = new AbortController(); - if (!opts.enabled || !url) { - controller.abort(); - return () => controller.abort(); - } - - async function establishConnection() { - if (!url) { - return; - } - fetchEventSource(url, { - signal: controller.signal, - headers: await getHeaders( - ( - await queries.createActorInspectorFetchConfiguration( - actorId, - ) - )?.headers || {}, - ), - onmessage: (event) => { - const msg = JSON.parse(event.data); - stableOnMessage(msg); - }, - onclose: async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - controller.signal.throwIfAborted(); - establishConnection(); - }, - }).catch((error) => console.error(error)); - } - - establishConnection(); - return () => { - controller.abort(); - }; - }, [url, actorId, opts.enabled, stableOnMessage]); -} - -export const useActorStateStream = ( - actorId: ActorId, - opts: { enabled: boolean } = { enabled: true }, -) => { - const queryClient = useQueryClient(); - const queries = useActor(); - - useStream( - actorId, - useCallback( - (data: unknown) => { - queryClient.setQueryData( - queries.actorStateQueryOptions(actorId).queryKey, - () => ({ enabled: true, state: data }), - ); - }, - [queryClient, actorId, queries], - ), - useAsyncMemo( - async () => - ( - await queries.createActorInspector(actorId) - ).state.stream.$url().href, - [actorId, queries], - ), - opts, - ); -}; - -export const useActorConnectionsStream = (actorId: ActorId) => { - const queryClient = useQueryClient(); - const queries = useActor(); - - useStream( - actorId, - useCallback( - (data) => { - queryClient.setQueryData( - queries.actorConnectionsQueryOptions(actorId).queryKey, - () => ({ enabled: true, connections: data }), - ); - }, - [queryClient, actorId, queries], - ), - useAsyncMemo( - async () => - ( - await queries.createActorInspector(actorId) - ).connections.stream.$url().href, - [actorId, queries], - ), - ); -}; - -export const useActorEventsStream = ( - actorId: ActorId, - opts: { enabled: boolean }, -) => { - const queryClient = useQueryClient(); - const queries = useActor(); - - useStream( - actorId, - useCallback( - (data: RecordedRealtimeEvent[]) => { - queryClient.setQueryData( - queries.actorEventsQueryOptions(actorId).queryKey, - () => { - return { events: data }; - }, - ); - }, - [queryClient, actorId, queries], - ), - useAsyncMemo( - async () => - ( - await queries.createActorInspector(actorId) - ).events.stream.$url().href, - [actorId, queries], - ), - opts, - ); -}; - -/** - * Check if the object is patchable, i.e. if it is an object and not null. - */ -function isPatchable(data: unknown) { - return typeof data === "object" && data !== null; -} diff --git a/frontend/src/components/actors/queries/index.ts b/frontend/src/components/actors/queries/index.ts index 383c469228..47f1d4f6df 100644 --- a/frontend/src/components/actors/queries/index.ts +++ b/frontend/src/components/actors/queries/index.ts @@ -1,95 +1,6 @@ -import type { Actor as InspectorActor } from "rivetkit/inspector"; +import type { Rivet } from "@rivetkit/engine-api-full"; -export type { ActorLogEntry } from "rivetkit/inspector"; -export { ActorFeature } from "rivetkit/inspector"; - -import type { ActorId } from "rivetkit/inspector"; - -export type { ActorId }; - -export type PortRouting = { - guard?: {}; - host?: {}; -}; - -export type Port = { - protocol: "http" | "https" | "tcp" | "tcp_tls" | "udp"; - internalPort?: number; - hostname?: string; - port?: number; - path?: string; - /** Fully formed connection URL including protocol, hostname, port, and path, if applicable. */ - url?: string; - routing: PortRouting; -}; - -export type Runtime = { - build: string; - arguments?: string[]; - environment?: Record; -}; - -export type Lifecycle = { - /** The duration to wait for in milliseconds before killing the actor. This should be set to a safe default, and can be overridden during a DELETE request if needed. */ - killTimeout?: number; - /** If true, the actor will try to reschedule itself automatically in the event of a crash or a datacenter failover. The actor will not reschedule if it exits successfully. */ - durable?: boolean; -}; - -export type Resources = { - /** - * The number of CPU cores in millicores, or 1/1000 of a core. For example, - * 1/8 of a core would be 125 millicores, and 1 core would be 1000 - * millicores. - */ - cpu: number; - /** The amount of memory in megabytes */ - memory: number; -}; - -export type Actor = Omit & { - network?: { - mode: "bridge" | "host"; - ports: Record; - }; - runtime?: Runtime; - lifecycle?: Lifecycle; - key: string | undefined; - - // engine related - runner?: string; - crashPolicy?: CrashPolicy; - sleepingAt?: string | null; - connectableAt?: string | null; - pendingAllocationAt?: string | null; - datacenter?: string | null; - rescheduleAt?: string | null; -} & { id: ActorId }; - -export enum CrashPolicy { - Restart = "restart", - Sleep = "sleep", - Destroy = "destroy", -} - -export type ActorMetrics = { - metrics: Record; - rawData: Record; - interval: number; -}; - -export type Build = { - id: string; - name: string; -}; - -export type Region = { - id: string; - name: string; - url?: string; -}; - -export * from "./actor"; +export type ActorId = string; export type ActorStatus = | "starting" @@ -103,49 +14,49 @@ export type ActorStatus = export function getActorStatus( actor: Pick< - Actor, - | "createdAt" - | "startedAt" - | "destroyedAt" - | "sleepingAt" - | "pendingAllocationAt" - | "rescheduleAt" + Rivet.Actor, + | "createTs" + | "destroyTs" + | "sleepTs" + | "pendingAllocationTs" + | "rescheduleTs" + | "connectableTs" >, ): ActorStatus { const { - createdAt, - startedAt, - destroyedAt, - sleepingAt, - pendingAllocationAt, - rescheduleAt, + createTs, + connectableTs, + destroyTs, + sleepTs, + pendingAllocationTs, + rescheduleTs, } = actor; - if (rescheduleAt) { + if (rescheduleTs) { return "crash-loop"; } - if (pendingAllocationAt && !startedAt && !destroyedAt) { + if (pendingAllocationTs && !connectableTs && !destroyTs) { return "pending"; } - if (createdAt && sleepingAt && !destroyedAt) { + if (createTs && sleepTs && !destroyTs) { return "sleeping"; } - if (createdAt && !startedAt && !destroyedAt) { + if (createTs && !connectableTs && !destroyTs) { return "starting"; } - if (createdAt && startedAt && !destroyedAt) { + if (createTs && connectableTs && !destroyTs) { return "running"; } - if (createdAt && startedAt && destroyedAt) { + if (createTs && connectableTs && destroyTs) { return "stopped"; } - if (createdAt && !startedAt && destroyedAt) { + if (createTs && !connectableTs && destroyTs) { return "crashed"; } diff --git a/frontend/src/components/actors/region-select.tsx b/frontend/src/components/actors/region-select.tsx index bb8dee1831..de0a6742e5 100644 --- a/frontend/src/components/actors/region-select.tsx +++ b/frontend/src/components/actors/region-select.tsx @@ -19,7 +19,7 @@ export function RegionSelect({ fetchNextPage, isLoading, isFetchingNextPage, - } = useInfiniteQuery(useDataProvider().regionsQueryOptions()); + } = useInfiniteQuery(useDataProvider().datacentersQueryOptions()); const regions = [ ...(showAuto diff --git a/frontend/src/components/actors/worker/actor-worker-context.tsx b/frontend/src/components/actors/worker/actor-worker-context.tsx index 4288f49c8a..2bd88690c3 100644 --- a/frontend/src/components/actors/worker/actor-worker-context.tsx +++ b/frontend/src/components/actors/worker/actor-worker-context.tsx @@ -1,3 +1,4 @@ +/** biome-ignore-all lint/correctness/useHookAtTopLevel: guarded by build constant */ import { useQuery } from "@tanstack/react-query"; import { createContext, @@ -13,7 +14,7 @@ import { useInspectorCredentials } from "@/app/credentials-context"; import { assertNonNullable, ls } from "../../lib/utils"; import { useActor } from "../actor-queries-context"; import { useDataProvider, useEngineCompatDataProvider } from "../data-provider"; -import { ActorFeature, type ActorId } from "../queries"; +import type { ActorId } from "../queries"; import { ActorWorkerContainer } from "./actor-worker-container"; export const ActorWorkerContext = createContext( @@ -70,7 +71,6 @@ export const ActorWorkerContextProvider = ({ const { data: { - features, name, endpoint, destroyedAt, @@ -81,12 +81,7 @@ export const ActorWorkerContextProvider = ({ } = useQuery(dataProvider.actorWorkerQueryOptions(actorId)); const inspectorToken = useInspectorToken(runner || ""); - const enabled = - (features?.includes(ActorFeature.Console) && - !destroyedAt && - !sleepingAt && - !!startedAt) ?? - false; + const enabled = (!destroyedAt && !sleepingAt && !!startedAt) ?? false; const actorQueries = useActor(); const { data: { rpcs } = {} } = useQuery( diff --git a/frontend/src/queries/actor-inspector.ts b/frontend/src/queries/actor-inspector.ts index f37c61ba8a..f3d36e81fd 100644 --- a/frontend/src/queries/actor-inspector.ts +++ b/frontend/src/queries/actor-inspector.ts @@ -1,7 +1,3 @@ -import { - type ActorContext, - createDefaultActorContext, -} from "@/components/actors"; import { ensureTrailingSlash } from "@/lib/utils"; export const createInspectorActorContext = ({ @@ -13,37 +9,39 @@ export const createInspectorActorContext = ({ token: string | (() => string) | (() => Promise); engineToken?: string; }) => { - const def = createDefaultActorContext({ - hash: btoa(url + inspectorToken + (engineToken || "")).slice(0, 8), - }); - const newUrl = new URL(url); - if (!newUrl.pathname.endsWith("inspect")) { - newUrl.pathname = `${ensureTrailingSlash(newUrl.pathname)}inspect`; - } - return { - ...def, - async createActorInspectorFetchConfiguration(actorId, opts) { - return { - headers: { - "x-rivet-actor": actorId, - "x-rivet-target": "actor", - ...(engineToken ? { "x-rivet-token": engineToken } : {}), - ...(opts?.auth - ? { - ...{ - authorization: `Bearer ${ - typeof inspectorToken === "string" - ? inspectorToken - : await inspectorToken() - }`, - }, - } - : {}), - }, - }; - }, - createActorInspectorUrl() { - return new URL(`${url}/inspect`, window.location.origin).href; - }, - } satisfies ActorContext; + // const def = createDefaultActorContext({ + // hash: btoa(url + inspectorToken + (engineToken || "")).slice(0, 8), + // }); + // const newUrl = new URL(url); + // if (!newUrl.pathname.endsWith("inspect")) { + // newUrl.pathname = `${ensureTrailingSlash(newUrl.pathname)}inspect`; + // } + // return { + // ...def, + // async createActorInspectorFetchConfiguration(actorId, opts) { + // return { + // headers: { + // "x-rivet-actor": actorId, + // "x-rivet-target": "actor", + // ...(engineToken ? { "x-rivet-token": engineToken } : {}), + // ...(opts?.auth + // ? { + // ...{ + // authorization: `Bearer ${ + // typeof inspectorToken === "string" + // ? inspectorToken + // : await inspectorToken() + // }`, + // }, + // } + // : {}), + // }, + // }; + // }, + // createActorInspectorUrl() { + // return new URL(`${url}/inspect`, window.location.origin).href; + // }, + // } satisfies ActorContext; + + return {}; }; diff --git a/frontend/src/utils/use-railway-template-link.ts b/frontend/src/utils/use-railway-template-link.ts index 64e2bcc1b3..c652b5dc4a 100644 --- a/frontend/src/utils/use-railway-template-link.ts +++ b/frontend/src/utils/use-railway-template-link.ts @@ -24,7 +24,7 @@ export function useRailwayTemplateLink({ const useDatacenterEndpoint = ({ datacenter }: { datacenter: string }) => { const { data } = useQuery( - useEngineCompatDataProvider().regionQueryOptions(datacenter), + useEngineCompatDataProvider().datacenterQueryOptions(datacenter), ); return data?.url || engineEnv().VITE_APP_API_URL; }; diff --git a/rivetkit-openapi/openapi.json b/rivetkit-openapi/openapi.json index 6f7096cccc..aab9828926 100644 --- a/rivetkit-openapi/openapi.json +++ b/rivetkit-openapi/openapi.json @@ -331,6 +331,60 @@ } } } + }, + "/actors/names": { + "get": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "required": true, + "name": "namespace", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "names": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "metadata": { + "type": "object", + "additionalProperties": { + "nullable": true + } + } + }, + "required": [ + "metadata" + ] + } + } + }, + "required": [ + "names" + ] + } + } + } + }, + "400": { + "description": "User error" + }, + "500": { + "description": "Internal error" + } + } + } } } } \ No newline at end of file diff --git a/rivetkit-typescript/packages/cloudflare-workers/src/manager-driver.ts b/rivetkit-typescript/packages/cloudflare-workers/src/manager-driver.ts index cfdf78ebbe..82da009a3b 100644 --- a/rivetkit-typescript/packages/cloudflare-workers/src/manager-driver.ts +++ b/rivetkit-typescript/packages/cloudflare-workers/src/manager-driver.ts @@ -388,8 +388,4 @@ export class CloudflareActorsManagerDriver implements ManagerDriver { properties: {}, }; } - - getOrCreateInspectorAccessToken() { - return generateRandomString(); - } } diff --git a/rivetkit-typescript/packages/rivetkit/package.json b/rivetkit-typescript/packages/rivetkit/package.json index d9548edc7e..d69c300e19 100644 --- a/rivetkit-typescript/packages/rivetkit/package.json +++ b/rivetkit-typescript/packages/rivetkit/package.json @@ -153,7 +153,7 @@ ], "scripts": { "build": "tsup src/mod.ts src/client/mod.ts src/common/log.ts src/common/websocket.ts src/actor/errors.ts src/topologies/coordinate/mod.ts src/topologies/partition/mod.ts src/utils.ts src/driver-helpers/mod.ts src/driver-test-suite/mod.ts src/test/mod.ts src/inspector/mod.ts", - "build:schema": "./scripts/compile-bare.ts compile schemas/client-protocol/v1.bare -o dist/schemas/client-protocol/v1.ts && ./scripts/compile-bare.ts compile schemas/client-protocol/v2.bare -o dist/schemas/client-protocol/v2.ts && ./scripts/compile-bare.ts compile schemas/file-system-driver/v1.bare -o dist/schemas/file-system-driver/v1.ts && ./scripts/compile-bare.ts compile schemas/file-system-driver/v2.bare -o dist/schemas/file-system-driver/v2.ts && ./scripts/compile-bare.ts compile schemas/actor-persist/v1.bare -o dist/schemas/actor-persist/v1.ts && ./scripts/compile-bare.ts compile schemas/actor-persist/v2.bare -o dist/schemas/actor-persist/v2.ts && ./scripts/compile-bare.ts compile schemas/actor-persist/v3.bare -o dist/schemas/actor-persist/v3.ts", + "build:schema": "./scripts/compile-bare.ts compile schemas/client-protocol/v1.bare -o dist/schemas/client-protocol/v1.ts && ./scripts/compile-bare.ts compile schemas/client-protocol/v2.bare -o dist/schemas/client-protocol/v2.ts && ./scripts/compile-bare.ts compile schemas/file-system-driver/v1.bare -o dist/schemas/file-system-driver/v1.ts && ./scripts/compile-bare.ts compile schemas/file-system-driver/v2.bare -o dist/schemas/file-system-driver/v2.ts && ./scripts/compile-bare.ts compile schemas/actor-persist/v1.bare -o dist/schemas/actor-persist/v1.ts && ./scripts/compile-bare.ts compile schemas/actor-persist/v2.bare -o dist/schemas/actor-persist/v2.ts && ./scripts/compile-bare.ts compile schemas/actor-persist/v3.bare -o dist/schemas/actor-persist/v3.ts && ./scripts/compile-bare.ts compile schemas/actor-inspector/v1.bare -o dist/schemas/actor-inspector/v1.ts", "check-types": "tsc --noEmit", "test": "vitest run", "test:watch": "vitest", diff --git a/rivetkit-typescript/packages/rivetkit/schemas/actor-inspector/v1.bare b/rivetkit-typescript/packages/rivetkit/schemas/actor-inspector/v1.bare new file mode 100644 index 0000000000..c10adcc579 --- /dev/null +++ b/rivetkit-typescript/packages/rivetkit/schemas/actor-inspector/v1.bare @@ -0,0 +1,183 @@ +# MARK: Message To Server + +type OpTest struct { + path: str + value: data +} + +type OpCopy struct { + path: str + from: str +} + +type OpMove struct { + path: str + from: str +} + +type OpReplace struct { + path: str + value: data +} + +type OpAdd struct { + path: str + value: data +} + +type OpRemove struct { + path: str +} + +type PatchOperation union { + OpRemove | + OpAdd | + OpReplace | + OpMove | + OpCopy | + OpTest +} + +type PatchStateRequest struct { + operations: list +} + +type ClearStateRequest void + +type DatabaseQueryRequest struct { + id: uint + query: str + params: data +} + +type ActionRequest struct { + id: uint + name: str + args: data +} + +type ToServerBody union { + PatchStateRequest | + ClearStateRequest | + DatabaseQueryRequest | + ActionRequest +} + +type ToServer struct { + body: ToServerBody +} + +# MARK: Message To Client + +type State data + +type Connection struct { + id: str + details: data +} + +type ActionEvent struct { + name: str + args: data + connId: str +} + +type BroadcastEvent struct { + eventName: str + args: data +} + +type SubscribeEvent struct { + eventName: str + connId: str +} + +type UnSubscribeEvent struct { + eventName: str + connId: str +} + +type FiredEvent struct { + eventName: str + args: data + connId: str +} + +type EventBody union { + ActionEvent | + BroadcastEvent | + SubscribeEvent | + UnSubscribeEvent | + FiredEvent +} + +type Event struct { + id: str + timestamp: uint + body: EventBody +} + +type Init struct { + connections: list + events: list + state: State + rpcs: list +} + +type ConnectionsResponse struct { + connections: list +} + +type StateResponse struct { + state: State +} + +type DatabaseColumn struct { + name: str + type: str +} + +type DatabaseForeignKey struct { + id: str + table: str + from: str + to: str +} + +type DatabaseTable struct { + name: str + schema: data + type: str + columns: list + foreignKeys: list + records: uint +} + +type DatabaseResponse struct { + tables: list +} + +type DatabaseQueryResponse data + +type ActionResponse struct { + id: uint + output: data +} + +type Error struct { + message: str +} + +type ToClientBody union { + StateResponse | + ConnectionsResponse | + DatabaseResponse | + DatabaseQueryResponse | + ActionResponse | + Error | + Init +} + +type ToClient struct { + body: ToClientBody +} \ No newline at end of file diff --git a/rivetkit-typescript/packages/rivetkit/scripts/dump-openapi.ts b/rivetkit-typescript/packages/rivetkit/scripts/dump-openapi.ts index e60a09c436..d1e708cdf7 100644 --- a/rivetkit-typescript/packages/rivetkit/scripts/dump-openapi.ts +++ b/rivetkit-typescript/packages/rivetkit/scripts/dump-openapi.ts @@ -38,7 +38,6 @@ function main() { proxyRequest: unimplemented, proxyWebSocket: unimplemented, displayInformation: unimplemented, - getOrCreateInspectorAccessToken: unimplemented, }; const client = createClientWithDriver( diff --git a/rivetkit-typescript/packages/rivetkit/src/actor/instance/mod.ts b/rivetkit-typescript/packages/rivetkit/src/actor/instance/mod.ts index ccadfcfa38..4aadbe4309 100644 --- a/rivetkit-typescript/packages/rivetkit/src/actor/instance/mod.ts +++ b/rivetkit-typescript/packages/rivetkit/src/actor/instance/mod.ts @@ -142,22 +142,25 @@ export class ActorInstance { return Array.from( this.#connectionManager.connections.entries(), ).map(([id, conn]) => ({ - type: conn[CONN_DRIVER_SYMBOL]?.type, id, - params: conn.params as any, - state: conn[CONN_STATE_ENABLED_SYMBOL] - ? conn.state - : undefined, - subscriptions: conn.subscriptions.size, - lastSeen: conn.lastSeen, - stateEnabled: conn[CONN_STATE_ENABLED_SYMBOL], - isHibernatable: conn.isHibernatable, - hibernatableRequestId: conn[CONN_PERSIST_SYMBOL] - .hibernatableRequestId - ? idToStr( - conn[CONN_PERSIST_SYMBOL].hibernatableRequestId, - ) - : undefined, + details: { + type: conn[CONN_DRIVER_SYMBOL]?.type || null, + params: conn.params as any, + state: conn[CONN_STATE_ENABLED_SYMBOL] + ? conn.state + : null, + subscriptions: conn.subscriptions.size, + lastSeen: conn.lastSeen, + stateEnabled: conn[CONN_STATE_ENABLED_SYMBOL], + isHibernatable: conn.isHibernatable, + hibernatableRequestId: conn[CONN_PERSIST_SYMBOL] + .hibernatableRequestId + ? idToStr( + conn[CONN_PERSIST_SYMBOL] + .hibernatableRequestId, + ) + : null, + }, })); }, setState: async (state: unknown) => { diff --git a/rivetkit-typescript/packages/rivetkit/src/actor/router.ts b/rivetkit-typescript/packages/rivetkit/src/actor/router.ts index d56c980b30..5fffa74a50 100644 --- a/rivetkit-typescript/packages/rivetkit/src/actor/router.ts +++ b/rivetkit-typescript/packages/rivetkit/src/actor/router.ts @@ -1,5 +1,4 @@ import { Hono } from "hono"; -import invariant from "invariant"; import { type ActionOpts, type ActionOutput, @@ -22,11 +21,7 @@ import { loggerMiddleware, } from "@/common/router"; import { noopNext } from "@/common/utils"; -import { - type ActorInspectorRouterEnv, - createActorInspectorRouter, -} from "@/inspector/actor"; -import { isInspectorEnabled, secureInspector } from "@/inspector/utils"; +import { createActorInspectorRouter } from "@/inspector/actor"; import type { RunnerConfig } from "@/registry/run-config"; import { CONN_DRIVER_SYMBOL, generateConnRequestId } from "./conn/mod"; import type { ActorDriver } from "./driver"; @@ -199,25 +194,13 @@ export function createActorRouter( } }); - if (isInspectorEnabled(runConfig, "actor")) { - router.route( + if (runConfig.inspector.enabled) { + router.use( "/inspect", - new Hono< - ActorInspectorRouterEnv & { Bindings: ActorRouterBindings } - >() - .use(secureInspector(runConfig), async (c, next) => { - const inspector = ( - await actorDriver.loadActor(c.env.actorId) - ).inspector; - invariant( - inspector, - "inspector not supported on this platform", - ); - - c.set("inspector", inspector); - return next(); - }) - .route("/", createActorInspectorRouter()), + createActorInspectorRouter({ + actorDriver, + upgradeWebSocket: runConfig.getUpgradeWebSocket, + }), ); } diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/test-inline-client-driver.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/test-inline-client-driver.ts index f25376b2f3..7a5844a2b3 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/test-inline-client-driver.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/test-inline-client-driver.ts @@ -226,8 +226,6 @@ export function createTestInlineClientDriver( displayInformation(): ManagerDisplayInformation { return { name: "Test Inline", properties: {} }; }, - // TODO: - getOrCreateInspectorAccessToken: () => "", // action: async = unknown[], Response = unknown>( // _c: HonoContext | undefined, diff --git a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-inspector.ts b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-inspector.ts index 48bc7f3e7c..9b2a51dede 100644 --- a/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-inspector.ts +++ b/rivetkit-typescript/packages/rivetkit/src/driver-test-suite/tests/actor-inspector.ts @@ -1,681 +1,363 @@ -import { describe, expect, test } from "vitest"; -import { HEADER_ACTOR_QUERY } from "@/driver-helpers/mod"; -import { - createActorInspectorClient, - createManagerInspectorClient, -} from "@/inspector/mod"; -import type { ActorQuery } from "@/mod"; +import { describe } from "vitest"; import type { DriverTestConfig } from "../mod"; -import { setupDriverTest } from "../utils"; export function runActorInspectorTests(driverTestConfig: DriverTestConfig) { // TODO: Add back describe.skip("Actor Inspector Tests", () => { - describe("Manager Inspector", () => { - test("should respond to ping", async (c) => { - const { endpoint } = await setupDriverTest(c, driverTestConfig); - - const http = createManagerInspectorClient( - `${endpoint}/inspect`, - { - headers: { - Authorization: `Bearer token`, - }, - }, - ); - - const response = await http.ping.$get(); - expect(response.status).toBe(200); - - const data = await response.json(); - expect(data).toEqual({ message: "pong" }); - }); - - test("should get actors with pagination", async (c) => { - const { client, endpoint } = await setupDriverTest( - c, - driverTestConfig, - ); - - // Create some actors first - await client.counter.create(["test-actor-1"]); - await client.counter.create(["test-actor-2"]); - - const http = createManagerInspectorClient( - `${endpoint}/inspect`, - { - headers: { - Authorization: `Bearer token`, - }, - }, - ); - - const response = await http.actors.$get({ - query: { limit: "1" }, - }); - expect(response.status).toBe(200); - - const data = await response.json(); - expect(data).toEqual( - expect.arrayContaining([ - expect.objectContaining({ key: ["test-actor-1"] }), - ]), - ); - expect(data.length).toBe(1); - }); - - test("should get all actors with pagination", async (c) => { - const { client, endpoint } = await setupDriverTest( - c, - driverTestConfig, - ); - - const actorKey1 = ["test-cursor-1"]; - const actorKey2 = ["test-cursor-2"]; - - // Create some actors first - await client.counter.create(actorKey1); - await client.counter.create(actorKey2); - - const http = createManagerInspectorClient( - `${endpoint}/inspect`, - { - headers: { - Authorization: `Bearer token`, - }, - }, - ); - - const response = await http.actors.$get({ - query: { limit: "5" }, - }); - expect(response.status).toBe(200); - - const data = await response.json(); - expect(data).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - id: expect.any(String), - key: actorKey1, - }), - expect.objectContaining({ - id: expect.any(String), - key: actorKey2, - }), - ]), - ); - }); - - test("should handle invalid limit parameter", async (c) => { - const { endpoint } = await setupDriverTest(c, driverTestConfig); - - const http = createManagerInspectorClient( - `${endpoint}/inspect`, - { - headers: { - Authorization: `Bearer token`, - }, - }, - ); - - const response = await http.actors.$get({ - query: { limit: "0" }, - }); - expect(response.status).toBe(400); - }); - - test("should create a new actor", async (c) => { - const { endpoint } = await setupDriverTest(c, driverTestConfig); - - const http = createManagerInspectorClient( - `${endpoint}/inspect`, - { - headers: { - Authorization: `Bearer token`, - }, - }, - ); - - const response = await http.actors.$post({ - json: { - name: "default", - key: ["test-create-actor"], - input: {}, - }, - }); - - expect(response.status).toBe(201); - const data = await response.json(); - expect(data).toEqual( - expect.objectContaining({ - id: expect.any(String), - name: "default", - key: ["test-create-actor"], - }), - ); - }); - - test("should get builds", async (c) => { - const { endpoint } = await setupDriverTest(c, driverTestConfig); - - const http = createManagerInspectorClient( - `${endpoint}/inspect`, - { - headers: { - Authorization: `Bearer token`, - }, - }, - ); - - const response = await http.builds.$get(); - expect(response.status).toBe(200); - - const data = await response.json(); - expect(data).toEqual( - expect.arrayContaining([ - expect.objectContaining({ name: expect.any(String) }), - ]), - ); - }); - - test("should get actor by id", async (c) => { - const { client, endpoint } = await setupDriverTest( - c, - driverTestConfig, - ); - - // Create an actor and get its ID - const handle = await client.counter.create(["test-get-by-id"]); - const actorId = await handle.resolve(); - - const http = createManagerInspectorClient( - `${endpoint}/inspect`, - { - headers: { - Authorization: `Bearer token`, - }, - }, - ); - - const response = await http.actor[":id"].$get({ - param: { id: actorId }, - }); - expect(response.status).toBe(200); - - const data = await response.json(); - expect(data).toHaveProperty("id", actorId); - }); - - test("should return 404 for non-existent actor", async (c) => { - const { endpoint } = await setupDriverTest(c, driverTestConfig); - - const http = createManagerInspectorClient( - `${endpoint}/inspect`, - { - headers: { - Authorization: `Bearer token`, - }, - }, - ); - - const response = await http.actor[":id"].$get({ - param: { id: "non-existent-id" }, - }); - expect(response.status).toBe(404); - - const data = await response.json(); - expect(data).toEqual({ error: "Actor not found" }); - }); - - test("should get bootstrap data", async (c) => { - const { client, endpoint } = await setupDriverTest( - c, - driverTestConfig, - ); - - // Create at least one actor to ensure bootstrap has data - // Create an actor and get its ID - const handle = await client.counter.create(["test-bootstrap"]); - await handle.resolve(); - - const http = createManagerInspectorClient( - `${endpoint}/inspect`, - { - headers: { - Authorization: `Bearer token`, - }, - }, - ); - - const response = await http.bootstrap.$get(); - expect(response.status).toBe(200); - - const data = await response.json(); - expect(data.actors).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - key: ["test-bootstrap"], - name: "counter", - }), - ]), - ); - }); - }); - - describe("Actor Inspector", () => { - test("should handle actor not found", async (c) => { - const { endpoint } = await setupDriverTest(c, driverTestConfig); - - const actorId = "non-existing"; - - const http = createActorInspectorClient( - `${endpoint}/actors/inspect`, - { - headers: { - Authorization: `Bearer token`, - [HEADER_ACTOR_QUERY]: JSON.stringify({ - getForId: { name: "counter", actorId }, - } satisfies ActorQuery), - }, - }, - ); - - const response = await http.ping.$get(); - expect(response.ok).toBe(false); - }); - test("should respond to ping", async (c) => { - const { client, endpoint } = await setupDriverTest( - c, - driverTestConfig, - ); - - const handle = await client.counter.create(["test-ping"]); - const actorId = await handle.resolve(); - - const http = createActorInspectorClient( - `${endpoint}/actors/inspect`, - { - headers: { - Authorization: `Bearer token`, - [HEADER_ACTOR_QUERY]: JSON.stringify({ - getForId: { name: "counter", actorId }, - } satisfies ActorQuery), - }, - }, - ); - - const response = await http.ping.$get(); - expect(response.status).toBe(200); - - const data = await response.json(); - expect(data).toEqual({ message: "pong" }); - }); - - test("should get actor state", async (c) => { - const { client, endpoint } = await setupDriverTest( - c, - driverTestConfig, - ); - - const handle = await client.counter.create(["test-state"]); - const actorId = await handle.resolve(); - - // Increment the counter to set some state - await handle.increment(5); - - const http = createActorInspectorClient( - `${endpoint}/actors/inspect`, - { - headers: { - Authorization: `Bearer token`, - [HEADER_ACTOR_QUERY]: JSON.stringify({ - getForId: { name: "counter", actorId }, - } satisfies ActorQuery), - }, - }, - ); - - const response = await http.state.$get(); - expect(response.status).toBe(200); - - const data = await response.json(); - expect(data).toEqual({ - enabled: true, - state: expect.objectContaining({ - count: 5, - }), - }); - }); - - test("should update actor state with replace", async (c) => { - const { client, endpoint } = await setupDriverTest( - c, - driverTestConfig, - ); - - const handle = await client.counter.create([ - "test-state-replace", - ]); - const actorId = await handle.resolve(); - - const http = createActorInspectorClient( - `${endpoint}/actors/inspect`, - { - headers: { - Authorization: `Bearer token`, - [HEADER_ACTOR_QUERY]: JSON.stringify({ - getForId: { name: "counter", actorId }, - } satisfies ActorQuery), - }, - }, - ); - - // Replace the entire state - const response = await http.state.$patch({ - json: { - replace: { count: 10 }, - }, - }); - expect(response.status).toBe(200); - - const data = await response.json(); - expect(data).toEqual({ - enabled: true, - state: { count: 10 }, - }); - }); - - test("should update actor state with patch", async (c) => { - const { client, endpoint } = await setupDriverTest( - c, - driverTestConfig, - ); - - const handle = await client.counter.create([ - "test-state-patch", - ]); - const actorId = await handle.resolve(); - - // Set initial state - await handle.increment(3); - - const http = createActorInspectorClient( - `${endpoint}/actors/inspect`, - { - headers: { - Authorization: `Bearer token`, - [HEADER_ACTOR_QUERY]: JSON.stringify({ - getForId: { name: "counter", actorId }, - } satisfies ActorQuery), - }, - }, - ); - - // Patch the state - const response = await http.state.$patch({ - json: { - patch: [ - { - op: "replace", - path: "/count", - value: 7, - }, - ], - }, - }); - expect(response.status).toBe(200); - - const data = await response.json(); - expect(data).toEqual({ - enabled: true, - state: expect.objectContaining({ - count: 7, - }), - }); - }); - - test("should get actor connections", async (c) => { - const { client, endpoint } = await setupDriverTest( - c, - driverTestConfig, - ); - - const handle = await client.counter.create([ - "test-connections", - ]); - const actorId = await handle.resolve(); - handle.connect(); - await handle.increment(10); - - const http = createActorInspectorClient( - `${endpoint}/actors/inspect`, - { - headers: { - Authorization: `Bearer token`, - [HEADER_ACTOR_QUERY]: JSON.stringify({ - getForId: { name: "counter", actorId }, - } satisfies ActorQuery), - }, - }, - ); - - const response = await http.connections.$get(); - expect(response.status).toBe(200); - - const data = await response.json(); - expect(data.connections).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - id: expect.any(String), - }), - ]), - ); - }); - - test("should get actor events", async (c) => { - const { client, endpoint } = await setupDriverTest( - c, - driverTestConfig, - ); - - const handle = await client.counter.create(["test-events"]); - const actorId = await handle.resolve(); - - handle.connect(); - await handle.increment(10); - - const http = createActorInspectorClient( - `${endpoint}/actors/inspect`, - { - headers: { - Authorization: `Bearer token`, - [HEADER_ACTOR_QUERY]: JSON.stringify({ - getForId: { name: "counter", actorId }, - } satisfies ActorQuery), - }, - }, - ); - - const response = await http.events.$get(); - expect(response.status).toBe(200); - - const data = await response.json(); - expect(data.events).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - type: "broadcast", - id: expect.any(String), - }), - ]), - ); - }); - - test("should clear actor events", async (c) => { - const { client, endpoint } = await setupDriverTest( - c, - driverTestConfig, - ); - - const handle = await client.counter.create([ - "test-events-clear", - ]); - const actorId = await handle.resolve(); - - handle.connect(); - await handle.increment(10); - - const http = createActorInspectorClient( - `${endpoint}/actors/inspect`, - { - headers: { - Authorization: `Bearer token`, - [HEADER_ACTOR_QUERY]: JSON.stringify({ - getForId: { name: "counter", actorId }, - } satisfies ActorQuery), - }, - }, - ); - - { - const response = await http.events.$get(); - expect(response.status).toBe(200); - - const data = await response.json(); - expect(data.events).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - type: "broadcast", - id: expect.any(String), - }), - ]), - ); - } - - const response = await http.events.clear.$post(); - expect(response.status).toBe(200); - }); - - test("should get actor rpcs", async (c) => { - const { client, endpoint } = await setupDriverTest( - c, - driverTestConfig, - ); - - const handle = await client.counter.create(["test-rpcs"]); - const actorId = await handle.resolve(); - - const http = createActorInspectorClient( - `${endpoint}/actors/inspect`, - { - headers: { - Authorization: `Bearer token`, - [HEADER_ACTOR_QUERY]: JSON.stringify({ - getForId: { name: "counter", actorId }, - } satisfies ActorQuery), - }, - }, - ); - - const response = await http.rpcs.$get(); - expect(response.status).toBe(200); - - const data = await response.json(); - expect(data).toEqual( - expect.objectContaining({ - rpcs: expect.arrayContaining(["increment", "getCount"]), - }), - ); - }); - - // database is not officially supported yet - test.skip("should get actor database info", async (c) => { - const { client, endpoint } = await setupDriverTest( - c, - driverTestConfig, - ); - - const handle = await client.counter.create(["test-db"]); - const actorId = await handle.resolve(); - - const http = createActorInspectorClient( - `${endpoint}/actors/inspect`, - { - headers: { - Authorization: `Bearer token`, - [HEADER_ACTOR_QUERY]: JSON.stringify({ - getForId: { name: "counter", actorId }, - } satisfies ActorQuery), - }, - }, - ); - - const response = await http.db.$get(); - expect(response.status).toBe(200); - - const data = await response.json(); - // Database might be enabled or disabled depending on actor configuration - expect(data).toHaveProperty("enabled"); - expect(typeof data.enabled).toBe("boolean"); - - if (data.enabled) { - expect(data).toHaveProperty("db"); - expect(Array.isArray(data.db)).toBe(true); - } else { - expect(data.db).toBe(null); - } - }); - - test.skip("should execute database query when database is enabled", async (c) => { - const { client, endpoint } = await setupDriverTest( - c, - driverTestConfig, - ); - - const handle = await client.counter.create(["test-db-query"]); - const actorId = await handle.resolve(); - - const http = createActorInspectorClient( - `${endpoint}/actors/inspect`, - { - headers: { - Authorization: `Bearer token`, - [HEADER_ACTOR_QUERY]: JSON.stringify({ - getForId: { name: "counter", actorId }, - } satisfies ActorQuery), - }, - }, - ); - - // First check if database is enabled - const dbInfoResponse = await http.db.$get(); - const dbInfo = await dbInfoResponse.json(); - - if (dbInfo.enabled) { - // Execute a simple query - const queryResponse = await http.db.$post({ - json: { - query: "SELECT 1 as test", - params: [], - }, - }); - expect(queryResponse.status).toBe(200); - - const queryData = await queryResponse.json(); - expect(queryData).toHaveProperty("result"); - } else { - // If database is not enabled, the POST should return enabled: false - const queryResponse = await http.db.$post({ - json: { - query: "SELECT 1 as test", - params: [], - }, - }); - expect(queryResponse.status).toBe(200); - - const queryData = await queryResponse.json(); - expect(queryData).toEqual({ enabled: false }); - } - }); - }); + // describe("Actor Inspector", () => { + // test("should handle actor not found", async (c) => { + // const { endpoint } = await setupDriverTest(c, driverTestConfig); + // const actorId = "non-existing"; + // const http = createActorInspectorClient( + // `${endpoint}/actors/inspect`, + // { + // headers: { + // Authorization: `Bearer token`, + // [HEADER_ACTOR_QUERY]: JSON.stringify({ + // getForId: { name: "counter", actorId }, + // } satisfies ActorQuery), + // }, + // }, + // ); + // const response = await http.ping.$get(); + // expect(response.ok).toBe(false); + // }); + // test("should respond to ping", async (c) => { + // const { client, endpoint } = await setupDriverTest( + // c, + // driverTestConfig, + // ); + // const handle = await client.counter.create(["test-ping"]); + // const actorId = await handle.resolve(); + // const http = createActorInspectorClient( + // `${endpoint}/actors/inspect`, + // { + // headers: { + // Authorization: `Bearer token`, + // [HEADER_ACTOR_QUERY]: JSON.stringify({ + // getForId: { name: "counter", actorId }, + // } satisfies ActorQuery), + // }, + // }, + // ); + // const response = await http.ping.$get(); + // expect(response.status).toBe(200); + // const data = await response.json(); + // expect(data).toEqual({ message: "pong" }); + // }); + // test("should get actor state", async (c) => { + // const { client, endpoint } = await setupDriverTest( + // c, + // driverTestConfig, + // ); + // const handle = await client.counter.create(["test-state"]); + // const actorId = await handle.resolve(); + // // Increment the counter to set some state + // await handle.increment(5); + // const http = createActorInspectorClient( + // `${endpoint}/actors/inspect`, + // { + // headers: { + // Authorization: `Bearer token`, + // [HEADER_ACTOR_QUERY]: JSON.stringify({ + // getForId: { name: "counter", actorId }, + // } satisfies ActorQuery), + // }, + // }, + // ); + // const response = await http.state.$get(); + // expect(response.status).toBe(200); + // const data = await response.json(); + // expect(data).toEqual({ + // enabled: true, + // state: expect.objectContaining({ + // count: 5, + // }), + // }); + // }); + // test("should update actor state with replace", async (c) => { + // const { client, endpoint } = await setupDriverTest( + // c, + // driverTestConfig, + // ); + // const handle = await client.counter.create([ + // "test-state-replace", + // ]); + // const actorId = await handle.resolve(); + // const http = createActorInspectorClient( + // `${endpoint}/actors/inspect`, + // { + // headers: { + // Authorization: `Bearer token`, + // [HEADER_ACTOR_QUERY]: JSON.stringify({ + // getForId: { name: "counter", actorId }, + // } satisfies ActorQuery), + // }, + // }, + // ); + // // Replace the entire state + // const response = await http.state.$patch({ + // json: { + // replace: { count: 10 }, + // }, + // }); + // expect(response.status).toBe(200); + // const data = await response.json(); + // expect(data).toEqual({ + // enabled: true, + // state: { count: 10 }, + // }); + // }); + // test("should update actor state with patch", async (c) => { + // const { client, endpoint } = await setupDriverTest( + // c, + // driverTestConfig, + // ); + // const handle = await client.counter.create([ + // "test-state-patch", + // ]); + // const actorId = await handle.resolve(); + // // Set initial state + // await handle.increment(3); + // const http = createActorInspectorClient( + // `${endpoint}/actors/inspect`, + // { + // headers: { + // Authorization: `Bearer token`, + // [HEADER_ACTOR_QUERY]: JSON.stringify({ + // getForId: { name: "counter", actorId }, + // } satisfies ActorQuery), + // }, + // }, + // ); + // // Patch the state + // const response = await http.state.$patch({ + // json: { + // patch: [ + // { + // op: "replace", + // path: "/count", + // value: 7, + // }, + // ], + // }, + // }); + // expect(response.status).toBe(200); + // const data = await response.json(); + // expect(data).toEqual({ + // enabled: true, + // state: expect.objectContaining({ + // count: 7, + // }), + // }); + // }); + // test("should get actor connections", async (c) => { + // const { client, endpoint } = await setupDriverTest( + // c, + // driverTestConfig, + // ); + // const handle = await client.counter.create([ + // "test-connections", + // ]); + // const actorId = await handle.resolve(); + // handle.connect(); + // await handle.increment(10); + // const http = createActorInspectorClient( + // `${endpoint}/actors/inspect`, + // { + // headers: { + // Authorization: `Bearer token`, + // [HEADER_ACTOR_QUERY]: JSON.stringify({ + // getForId: { name: "counter", actorId }, + // } satisfies ActorQuery), + // }, + // }, + // ); + // const response = await http.connections.$get(); + // expect(response.status).toBe(200); + // const data = await response.json(); + // expect(data.connections).toEqual( + // expect.arrayContaining([ + // expect.objectContaining({ + // id: expect.any(String), + // }), + // ]), + // ); + // }); + // test("should get actor events", async (c) => { + // const { client, endpoint } = await setupDriverTest( + // c, + // driverTestConfig, + // ); + // const handle = await client.counter.create(["test-events"]); + // const actorId = await handle.resolve(); + // handle.connect(); + // await handle.increment(10); + // const http = createActorInspectorClient( + // `${endpoint}/actors/inspect`, + // { + // headers: { + // Authorization: `Bearer token`, + // [HEADER_ACTOR_QUERY]: JSON.stringify({ + // getForId: { name: "counter", actorId }, + // } satisfies ActorQuery), + // }, + // }, + // ); + // const response = await http.events.$get(); + // expect(response.status).toBe(200); + // const data = await response.json(); + // expect(data.events).toEqual( + // expect.arrayContaining([ + // expect.objectContaining({ + // type: "broadcast", + // id: expect.any(String), + // }), + // ]), + // ); + // }); + // test("should clear actor events", async (c) => { + // const { client, endpoint } = await setupDriverTest( + // c, + // driverTestConfig, + // ); + // const handle = await client.counter.create([ + // "test-events-clear", + // ]); + // const actorId = await handle.resolve(); + // handle.connect(); + // await handle.increment(10); + // const http = createActorInspectorClient( + // `${endpoint}/actors/inspect`, + // { + // headers: { + // Authorization: `Bearer token`, + // [HEADER_ACTOR_QUERY]: JSON.stringify({ + // getForId: { name: "counter", actorId }, + // } satisfies ActorQuery), + // }, + // }, + // ); + // { + // const response = await http.events.$get(); + // expect(response.status).toBe(200); + // const data = await response.json(); + // expect(data.events).toEqual( + // expect.arrayContaining([ + // expect.objectContaining({ + // type: "broadcast", + // id: expect.any(String), + // }), + // ]), + // ); + // } + // const response = await http.events.clear.$post(); + // expect(response.status).toBe(200); + // }); + // test("should get actor rpcs", async (c) => { + // const { client, endpoint } = await setupDriverTest( + // c, + // driverTestConfig, + // ); + // const handle = await client.counter.create(["test-rpcs"]); + // const actorId = await handle.resolve(); + // const http = createActorInspectorClient( + // `${endpoint}/actors/inspect`, + // { + // headers: { + // Authorization: `Bearer token`, + // [HEADER_ACTOR_QUERY]: JSON.stringify({ + // getForId: { name: "counter", actorId }, + // } satisfies ActorQuery), + // }, + // }, + // ); + // const response = await http.rpcs.$get(); + // expect(response.status).toBe(200); + // const data = await response.json(); + // expect(data).toEqual( + // expect.objectContaining({ + // rpcs: expect.arrayContaining(["increment", "getCount"]), + // }), + // ); + // }); + // // database is not officially supported yet + // test.skip("should get actor database info", async (c) => { + // const { client, endpoint } = await setupDriverTest( + // c, + // driverTestConfig, + // ); + // const handle = await client.counter.create(["test-db"]); + // const actorId = await handle.resolve(); + // const http = createActorInspectorClient( + // `${endpoint}/actors/inspect`, + // { + // headers: { + // Authorization: `Bearer token`, + // [HEADER_ACTOR_QUERY]: JSON.stringify({ + // getForId: { name: "counter", actorId }, + // } satisfies ActorQuery), + // }, + // }, + // ); + // const response = await http.db.$get(); + // expect(response.status).toBe(200); + // const data = await response.json(); + // // Database might be enabled or disabled depending on actor configuration + // expect(data).toHaveProperty("enabled"); + // expect(typeof data.enabled).toBe("boolean"); + // if (data.enabled) { + // expect(data).toHaveProperty("db"); + // expect(Array.isArray(data.db)).toBe(true); + // } else { + // expect(data.db).toBe(null); + // } + // }); + // test.skip("should execute database query when database is enabled", async (c) => { + // const { client, endpoint } = await setupDriverTest( + // c, + // driverTestConfig, + // ); + // const handle = await client.counter.create(["test-db-query"]); + // const actorId = await handle.resolve(); + // const http = createActorInspectorClient( + // `${endpoint}/actors/inspect`, + // { + // headers: { + // Authorization: `Bearer token`, + // [HEADER_ACTOR_QUERY]: JSON.stringify({ + // getForId: { name: "counter", actorId }, + // } satisfies ActorQuery), + // }, + // }, + // ); + // // First check if database is enabled + // const dbInfoResponse = await http.db.$get(); + // const dbInfo = await dbInfoResponse.json(); + // if (dbInfo.enabled) { + // // Execute a simple query + // const queryResponse = await http.db.$post({ + // json: { + // query: "SELECT 1 as test", + // params: [], + // }, + // }); + // expect(queryResponse.status).toBe(200); + // const queryData = await queryResponse.json(); + // expect(queryData).toHaveProperty("result"); + // } else { + // // If database is not enabled, the POST should return enabled: false + // const queryResponse = await http.db.$post({ + // json: { + // query: "SELECT 1 as test", + // params: [], + // }, + // }); + // expect(queryResponse.status).toBe(200); + // const queryData = await queryResponse.json(); + // expect(queryData).toEqual({ enabled: false }); + // } + // }); + // }); }); } diff --git a/rivetkit-typescript/packages/rivetkit/src/drivers/file-system/global-state.ts b/rivetkit-typescript/packages/rivetkit/src/drivers/file-system/global-state.ts index 9bc76f87a8..41cfad34ee 100644 --- a/rivetkit-typescript/packages/rivetkit/src/drivers/file-system/global-state.ts +++ b/rivetkit-typescript/packages/rivetkit/src/drivers/file-system/global-state.ts @@ -7,7 +7,6 @@ import { lookupInRegistry } from "@/actor/definition"; import { ActorAlreadyExists } from "@/actor/errors"; import type { AnyActorInstance } from "@/actor/instance/mod"; import type { ActorKey } from "@/actor/mod"; -import { generateRandomString } from "@/actor/utils"; import type { AnyClient } from "@/client/client"; import { type ActorDriver, @@ -683,17 +682,6 @@ export class FileSystemGlobalState { }, delay); } - getOrCreateInspectorAccessToken(): string { - const tokenPath = path.join(this.#storagePath, "inspector-token"); - if (fsSync.existsSync(tokenPath)) { - return fsSync.readFileSync(tokenPath, "utf-8"); - } - - const newToken = generateRandomString(); - fsSync.writeFileSync(tokenPath, newToken); - return newToken; - } - /** * Cleanup stale temp files on startup (synchronous) */ diff --git a/rivetkit-typescript/packages/rivetkit/src/drivers/file-system/manager.ts b/rivetkit-typescript/packages/rivetkit/src/drivers/file-system/manager.ts index d080642900..31d3d1f283 100644 --- a/rivetkit-typescript/packages/rivetkit/src/drivers/file-system/manager.ts +++ b/rivetkit-typescript/packages/rivetkit/src/drivers/file-system/manager.ts @@ -20,8 +20,6 @@ import type { ListActorsInput, ManagerDriver, } from "@/driver-helpers/mod"; -import { ManagerInspector } from "@/inspector/manager"; -import { type Actor, ActorFeature, type ActorId } from "@/inspector/mod"; import type { ManagerDisplayInformation } from "@/manager/driver"; import { type DriverConfig, @@ -46,8 +44,6 @@ export class FileSystemManagerDriver implements ManagerDriver { #actorDriver: ActorDriver; #actorRouter: ActorRouter; - inspector?: ManagerInspector; - constructor( registryConfig: RegistryConfig, runConfig: RunConfig, @@ -59,73 +55,6 @@ export class FileSystemManagerDriver implements ManagerDriver { this.#state = state; this.#driverConfig = driverConfig; - if (runConfig.inspector.enabled) { - const startedAt = new Date().toISOString(); - function transformActor(actorState: schema.ActorState): Actor { - return { - id: actorState.actorId as ActorId, - name: actorState.name, - key: actorState.key as string[], - startedAt: startedAt, - createdAt: new Date( - Number(actorState.createdAt), - ).toISOString(), - features: [ - ActorFeature.State, - ActorFeature.Connections, - ActorFeature.Console, - ActorFeature.EventsMonitoring, - ActorFeature.Database, - ], - }; - } - - this.inspector = new ManagerInspector(() => { - return { - getAllActors: async ({ cursor, limit }) => { - const itr = this.#state.getActorsIterator({ cursor }); - const actors: Actor[] = []; - - for await (const actor of itr) { - actors.push(transformActor(actor)); - if (limit && actors.length >= limit) { - break; - } - } - return actors; - }, - getActorById: async (id) => { - try { - const result = - await this.#state.loadActorStateOrError(id); - return transformActor(result); - } catch { - return null; - } - }, - getBuilds: async () => { - return Object.keys(this.#registryConfig.use).map( - (name) => ({ - name, - }), - ); - }, - createActor: async (input) => { - const { actorId } = await this.createActor(input); - try { - const result = - await this.#state.loadActorStateOrError( - actorId, - ); - return transformActor(result); - } catch { - return null; - } - }, - }; - }); - } - // Actors run on the same node as the manager, so we create a dummy actor router that we route requests to const inlineClient = createClientWithDriver( this, @@ -377,8 +306,4 @@ export class FileSystemManagerDriver implements ManagerDriver { data: this.#state.storagePath, }; } - - getOrCreateInspectorAccessToken() { - return this.#state.getOrCreateInspectorAccessToken(); - } } diff --git a/rivetkit-typescript/packages/rivetkit/src/inspector/actor.ts b/rivetkit-typescript/packages/rivetkit/src/inspector/actor.ts index fc080bcc74..157b963817 100644 --- a/rivetkit-typescript/packages/rivetkit/src/inspector/actor.ts +++ b/rivetkit-typescript/packages/rivetkit/src/inspector/actor.ts @@ -1,23 +1,17 @@ -import { sValidator } from "@hono/standard-validator"; -import jsonPatch from "@rivetkit/fast-json-patch"; -import { Hono } from "hono"; -import { streamSSE } from "hono/streaming"; -import { createNanoEvents, type Unsubscribe } from "nanoevents"; -import z from "zod/v4"; +import type { WSContext } from "hono/ws"; +import { createNanoEvents } from "nanoevents"; import type { AnyDatabaseProvider, InferDatabaseClient, } from "@/actor/database"; -import { promiseWithResolvers } from "@/utils"; +import type { ActorDriver } from "@/actor/driver"; +import type { UpgradeWebSocketArgs } from "@/mod"; +import type * as schema from "@/schemas/actor-inspector/mod"; import { - ColumnsSchema, - type Connection, - ForeignKeysSchema, - PatchSchema, - type RealtimeEvent, - type RecordedRealtimeEvent, - TablesSchema, -} from "./protocol/common"; + TO_CLIENT_VERSIONED, + TO_SERVER_VERSIONED, +} from "@/schemas/actor-inspector/versioned"; +import type { GetUpgradeWebSocket } from "@/utils"; export type ActorInspectorRouterEnv = { Variables: { @@ -29,8 +23,45 @@ export type ActorInspectorRouterEnv = { * Create a router for the Actor Inspector. * @internal */ -export function createActorInspectorRouter() { - return new Hono() +export function createActorInspectorRouter(config: { + upgradeWebSocket?: GetUpgradeWebSocket; + actorDriver: ActorDriver; +}) { + const upgradeWebSocket = config.upgradeWebSocket?.(); + + if (!upgradeWebSocket) { + throw new Error("WebSocket upgrade function is not provided."); + // return c.text( + // "WebSockets are not enabled for this driver, enable them to use the Actor Inspector.", + // 400, + // ); + } + + return upgradeWebSocket(async (c) => { + const inspector = (await config.actorDriver.loadActor(c.env.actorId)) + .inspector; + + if (!inspector) { + return c.text("Inspector is not enabled for this actor.", 400); + } + + return { + onOpen: async (event, ws) => { + console.log("onOpen", event, ws); + }, + onMessage: (event: any, ws: WSContext): void => { + console.log("onMsg", event, ws); + }, + onClose: (event: any, ws: WSContext): void => { + console.log("onClose", event, ws); + }, + onError: (error: any, ws: WSContext): void => { + console.log("onError", error, ws); + }, + } satisfies UpgradeWebSocketArgs; + }); +} +/* .get("/ping", (c) => { return c.json({ message: "pong" }, 200); }) @@ -289,8 +320,7 @@ export function createActorInspectorRouter() { ); return c.json({ result }, 200); }, - ); -} + );*/ interface ActorInspectorAccessors { isStateEnabled: () => Promise; @@ -306,9 +336,39 @@ interface ActorInspectorAccessors { interface ActorInspectorEmitterEvents { stateUpdated: (state: unknown) => void; connectionUpdated: () => void; - eventFired: (event: RealtimeEvent) => void; + eventFired: (event: EventDetails) => void; } +type Connection = Omit & { + details: unknown; +}; + +type EventDetails = + | { + type: "action"; + name: string; + args: unknown[]; + connId: string; + } + | { + type: "subscribe"; + eventName: string; + connId: string; + } + | { + type: "unsubscribe"; + eventName: string; + connId: string; + } + | { + type: "event"; + eventName: string; + args: unknown[]; + connId: string; + }; + +type Event = { id: string; timestamp: number } & EventDetails; + /** * Provides a unified interface for inspecting actor external and internal state. */ @@ -316,23 +376,23 @@ export class ActorInspector { public readonly accessors: ActorInspectorAccessors; public readonly emitter = createNanoEvents(); - #lastRealtimeEvents: RecordedRealtimeEvent[] = []; + #lastEvents: Event[] = []; - get lastRealtimeEvents() { - return this.#lastRealtimeEvents; + get lastEvents() { + return this.#lastEvents; } constructor(accessors: () => ActorInspectorAccessors) { this.accessors = accessors(); this.emitter.on("eventFired", (event) => { - this.#lastRealtimeEvents.push({ + this.#lastEvents.push({ id: crypto.randomUUID(), timestamp: Date.now(), ...event, }); // keep the last 100 events - if (this.#lastRealtimeEvents.length > 100) { - this.#lastRealtimeEvents = this.#lastRealtimeEvents.slice(-100); + if (this.#lastEvents.length > 100) { + this.#lastEvents = this.#lastEvents.slice(-100); } }); } diff --git a/rivetkit-typescript/packages/rivetkit/src/inspector/config.ts b/rivetkit-typescript/packages/rivetkit/src/inspector/config.ts index 69688f3c8e..945f12c114 100644 --- a/rivetkit-typescript/packages/rivetkit/src/inspector/config.ts +++ b/rivetkit-typescript/packages/rivetkit/src/inspector/config.ts @@ -20,16 +20,7 @@ const defaultEnabled = () => { export const InspectorConfigSchema = z .object({ - enabled: z - .boolean() - .or( - z.object({ - actor: z.boolean().optional().default(true), - manager: z.boolean().optional().default(true), - }), - ) - .optional() - .default(defaultEnabled), + enabled: z.boolean().default(defaultEnabled), /** * Token used to access the Inspector. diff --git a/rivetkit-typescript/packages/rivetkit/src/inspector/manager.ts b/rivetkit-typescript/packages/rivetkit/src/inspector/manager.ts deleted file mode 100644 index 3c131db1b0..0000000000 --- a/rivetkit-typescript/packages/rivetkit/src/inspector/manager.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { sValidator } from "@hono/standard-validator"; -import { Hono } from "hono"; -import invariant from "invariant"; -import type { CreateInput } from "@/manager/driver"; -import { inspectorLogger } from "./log"; -import { type Actor, type Builds, CreateActorSchema } from "./protocol/common"; - -export type ManagerInspectorRouterEnv = { - Variables: { - inspector: ManagerInspector; - }; -}; - -/** - * Create a router for the Manager Inspector. - * @internal - */ -export function createManagerInspectorRouter() { - return new Hono() - .get("/ping", (c) => { - return c.json({ message: "pong" }, 200); - }) - .get("/actors", async (c) => { - const limit = - Number.parseInt(c.req.query("limit") ?? "") || undefined; - const cursor = c.req.query("cursor") || undefined; - - if (!limit || (limit && limit <= 0)) { - return c.json("Invalid limit", 400); - } - - try { - const actors = await c.var.inspector.accessors.getAllActors({ - limit, - cursor, - }); - return c.json(actors, 200); - } catch (error) { - inspectorLogger().error({ - msg: "Failed to fetch actors", - error, - }); - return c.json("Failed to fetch actors", 500); - } - }) - - .post("/actors", sValidator("json", CreateActorSchema), async (c) => { - const actor = await c.var.inspector.accessors.createActor( - c.req.valid("json"), - ); - return c.json(actor, 201); - }) - .get("/builds", async (c) => { - const builds = await c.var.inspector.accessors.getBuilds(); - return c.json(builds, 200); - }) - .get("/actor/:id", async (c) => { - const id = c.req.param("id"); - const actor = await c.var.inspector.accessors.getActorById(id); - if (!actor) { - return c.json({ error: "Actor not found" }, 404); - } - return c.json(actor, 200); - }) - .get("/bootstrap", async (c) => { - const actors = await c.var.inspector.accessors.getAllActors({ - limit: 10, - }); - return c.json({ actors }, 200); - }); -} - -interface ManagerInspectorAccessors { - getAllActors: (param: { - cursor?: string; - limit: number; - }) => Promise; - getActorById: (id: string) => Promise; - getBuilds: () => Promise; - createActor: (input: CreateInput) => Promise; -} - -/** - * Provides a unified interface for inspecting actor external and internal state. - */ -export class ManagerInspector { - public readonly accessors: ManagerInspectorAccessors; - - constructor(accessors: () => ManagerInspectorAccessors) { - this.accessors = accessors(); - inspectorLogger().debug({ msg: "Manager Inspector enabled and ready" }); - } -} diff --git a/rivetkit-typescript/packages/rivetkit/src/inspector/mod.ts b/rivetkit-typescript/packages/rivetkit/src/inspector/mod.ts index da4942a6eb..1ee3127821 100644 --- a/rivetkit-typescript/packages/rivetkit/src/inspector/mod.ts +++ b/rivetkit-typescript/packages/rivetkit/src/inspector/mod.ts @@ -1,3 +1,2 @@ -export * from "./protocol/common"; -export * from "./protocol/mod"; -export * from "./utils"; +export * from "../schemas/actor-inspector/mod"; +export * from "../schemas/actor-inspector/versioned"; diff --git a/rivetkit-typescript/packages/rivetkit/src/inspector/protocol/actor.ts b/rivetkit-typescript/packages/rivetkit/src/inspector/protocol/actor.ts deleted file mode 100644 index a625044233..0000000000 --- a/rivetkit-typescript/packages/rivetkit/src/inspector/protocol/actor.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { hc } from "hono/client"; -import type { createActorInspectorRouter } from "../actor"; - -type ActorInspectorRouter = ReturnType; -const client = hc(""); -export type ActorInspectorClient = typeof client; - -export const createActorInspectorClient = ( - ...args: Parameters -): ActorInspectorClient => hc(...args); diff --git a/rivetkit-typescript/packages/rivetkit/src/inspector/protocol/common.ts b/rivetkit-typescript/packages/rivetkit/src/inspector/protocol/common.ts deleted file mode 100644 index 26410ac73b..0000000000 --- a/rivetkit-typescript/packages/rivetkit/src/inspector/protocol/common.ts +++ /dev/null @@ -1,196 +0,0 @@ -import z from "zod/v4"; -import { ActorKeySchema, MAX_ACTOR_KEY_SIZE } from "@/manager/protocol/query"; - -export const ActorId = z.string().brand("ActorId"); -export type ActorId = z.infer; - -export enum ActorFeature { - Logs = "logs", - Config = "config", - Connections = "connections", - State = "state", - Console = "console", - Runtime = "runtime", - Metrics = "metrics", - EventsMonitoring = "events-monitoring", - Database = "database", -} - -export const ActorLogEntry = z.object({ - level: z.string(), - message: z.string(), - timestamp: z.string(), - metadata: z.record(z.string(), z.any()).optional(), -}); - -export const ActorSchema = z.object({ - id: ActorId, - name: z.string(), - key: z.array(z.string()), - tags: z.record(z.string(), z.string()).optional(), - region: z.string().optional(), - createdAt: z.string().optional(), - startedAt: z.string().optional(), - destroyedAt: z.string().optional(), - features: z.array(z.enum(ActorFeature)).optional(), -}); - -export type Actor = z.infer; -export type ActorLogEntry = z.infer; - -// MARK: State - -export const OperationSchema = z.discriminatedUnion("op", [ - z.object({ - op: z.literal("remove"), - path: z.string(), - }), - z.object({ - op: z.literal("add"), - path: z.string(), - value: z.unknown(), - }), - z.object({ - op: z.literal("replace"), - path: z.string(), - value: z.unknown(), - }), - z.object({ - op: z.literal("move"), - path: z.string(), - from: z.string(), - }), - z.object({ - op: z.literal("copy"), - path: z.string(), - from: z.string(), - }), - z.object({ - op: z.literal("test"), - path: z.string(), - value: z.unknown(), - }), -]); -export type Operation = z.infer; - -export const PatchSchema = z.array(OperationSchema); -export type Patch = z.infer; - -// MARK: Connections - -export const ConnectionSchema = z.object({ - params: z.record(z.string(), z.any()).optional(), - id: z.string(), - stateEnabled: z.boolean().optional(), - state: z.any().optional(), - auth: z.record(z.string(), z.any()).optional(), -}); -export type Connection = z.infer; - -// MARK: Realtime Events - -export const RealtimeEventSchema = z.discriminatedUnion("type", [ - z.object({ - type: z.literal("action"), - name: z.string(), - args: z.array(z.any()), - connId: z.string(), - }), - z.object({ - type: z.literal("broadcast"), - eventName: z.string(), - args: z.array(z.any()), - }), - z.object({ - type: z.literal("subscribe"), - eventName: z.string(), - connId: z.string(), - }), - z.object({ - type: z.literal("unsubscribe"), - eventName: z.string(), - connId: z.string(), - }), - z.object({ - type: z.literal("event"), - eventName: z.string(), - args: z.array(z.any()), - connId: z.string(), - }), -]); -export type RealtimeEvent = z.infer; -export const RecordedRealtimeEventSchema = RealtimeEventSchema.and( - z.object({ - id: z.string(), - timestamp: z.number(), - }), -); -export type RecordedRealtimeEvent = z.infer; - -// MARK: Database - -export const DatabaseQuerySchema = z.object({ - sql: z.string(), - args: z.array(z.string().or(z.number())), -}); -export type DatabaseQuery = z.infer; - -export const TableSchema = z.object({ - schema: z.string(), - name: z.string(), - type: z.enum(["table", "view"]), -}); -export type Table = z.infer; - -export const TablesSchema = z.array(TableSchema); -export type Tables = z.infer; - -export const ColumnSchema = z.object({ - cid: z.number(), - name: z.string(), - type: z - .string() - .toLowerCase() - .transform((val) => { - return z - .enum(["integer", "text", "real", "blob", "numeric", "serial"]) - .parse(val); - }), - notnull: z.coerce.boolean(), - dflt_value: z.string().nullable(), - pk: z.coerce.boolean().nullable(), -}); -export type Column = z.infer; - -export const ColumnsSchema = z.array(ColumnSchema); -export type Columns = z.infer; - -export const ForeignKeySchema = z.object({ - id: z.number(), - table: z.string(), - from: z.string(), - to: z.string(), -}); -export type ForeignKey = z.infer; - -export const ForeignKeysSchema = z.array(ForeignKeySchema); -export type ForeignKeys = z.infer; - -// MARK: Builds - -export const BuildSchema = z.object({ - name: z.string(), - createdAt: z.string().optional(), - tags: z.record(z.string(), z.string()).optional(), -}); -export type Build = z.infer; -export const BuildsSchema = z.array(BuildSchema); -export type Builds = z.infer; - -export const CreateActorSchema = z.object({ - name: z.string(), - // FIXME: Replace with ActorKeySchema when ready - key: z.array(z.string().max(MAX_ACTOR_KEY_SIZE)), - input: z.any(), -}); -export type CreateActor = z.infer; diff --git a/rivetkit-typescript/packages/rivetkit/src/inspector/protocol/manager.ts b/rivetkit-typescript/packages/rivetkit/src/inspector/protocol/manager.ts deleted file mode 100644 index ac3e1aecb7..0000000000 --- a/rivetkit-typescript/packages/rivetkit/src/inspector/protocol/manager.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { hc } from "hono/client"; -import type { createManagerInspectorRouter } from "../manager"; - -type ManagerInspectorRouter = ReturnType; -const client = hc(""); -export type ManagerInspectorClient = typeof client; - -export const createManagerInspectorClient = ( - ...args: Parameters -): ManagerInspectorClient => hc(...args); diff --git a/rivetkit-typescript/packages/rivetkit/src/inspector/protocol/mod.ts b/rivetkit-typescript/packages/rivetkit/src/inspector/protocol/mod.ts deleted file mode 100644 index a4f6f07f14..0000000000 --- a/rivetkit-typescript/packages/rivetkit/src/inspector/protocol/mod.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./actor"; -export * from "./manager"; diff --git a/rivetkit-typescript/packages/rivetkit/src/inspector/utils.ts b/rivetkit-typescript/packages/rivetkit/src/inspector/utils.ts index edb2b78daa..b753fdccda 100644 --- a/rivetkit-typescript/packages/rivetkit/src/inspector/utils.ts +++ b/rivetkit-typescript/packages/rivetkit/src/inspector/utils.ts @@ -75,19 +75,6 @@ export function getInspectorUrl(runConfig: RunnerConfigInput | undefined) { return url.href; } -export const isInspectorEnabled = ( - runConfig: RunConfig, - // TODO(kacper): Remove context in favor of using the gateway, so only context is the actor - context: "actor" | "manager", -) => { - if (typeof runConfig.inspector?.enabled === "boolean") { - return runConfig.inspector.enabled; - } else if (typeof runConfig.inspector?.enabled === "object") { - return runConfig.inspector.enabled[context]; - } - return false; -}; - export const configureInspectorAccessToken = ( runConfig: RunConfig, managerDriver: ManagerDriver, diff --git a/rivetkit-typescript/packages/rivetkit/src/manager/driver.ts b/rivetkit-typescript/packages/rivetkit/src/manager/driver.ts index 22dcabd1ed..8c4eb01334 100644 --- a/rivetkit-typescript/packages/rivetkit/src/manager/driver.ts +++ b/rivetkit-typescript/packages/rivetkit/src/manager/driver.ts @@ -1,6 +1,5 @@ import type { Env, Hono, Context as HonoContext } from "hono"; import type { ActorKey, Encoding, UniversalWebSocket } from "@/actor/mod"; -import type { ManagerInspector } from "@/inspector/manager"; import type { RegistryConfig } from "@/registry/config"; import type { RunnerConfig } from "@/registry/run-config"; @@ -44,20 +43,6 @@ export interface ManagerDriver { registryConfig: RegistryConfig, router: Hono, ) => void; - - // TODO(kacper): Remove this in favor of standard manager API - /** - * @internal - */ - readonly inspector?: ManagerInspector; - - // TODO(kacper): Remove this in favor of ActorDriver.getinspectorToken - /** - * Get or create the inspector access token. - * @experimental - * @returns creates or returns existing inspector access token - */ - getOrCreateInspectorAccessToken: () => string; } export interface ManagerDisplayInformation { diff --git a/rivetkit-typescript/packages/rivetkit/src/manager/router.ts b/rivetkit-typescript/packages/rivetkit/src/manager/router.ts index b54523335e..a751f60934 100644 --- a/rivetkit-typescript/packages/rivetkit/src/manager/router.ts +++ b/rivetkit-typescript/packages/rivetkit/src/manager/router.ts @@ -1,10 +1,10 @@ import { createRoute, OpenAPIHono } from "@hono/zod-openapi"; import * as cbor from "cbor-x"; -import { +import type { Hono, - type Context as HonoContext, - type MiddlewareHandler, - type Next, + Context as HonoContext, + MiddlewareHandler, + Next, } from "hono"; import { createMiddleware } from "hono/factory"; import invariant from "invariant"; @@ -220,24 +220,6 @@ function addManagerRoutes( managerDriver: ManagerDriver, router: OpenAPIHono, ) { - // TODO(kacper): Remove this in favor of standard manager API - // Inspector - if (isInspectorEnabled(runConfig, "manager")) { - if (!managerDriver.inspector) { - throw new Unsupported("inspector"); - } - router.route( - "/inspect", - new Hono<{ Variables: { inspector: any } }>() - .use(secureInspector(runConfig)) - .use((c, next) => { - c.set("inspector", managerDriver.inspector!); - return next(); - }) - .route("/", createManagerInspectorRouter()), - ); - } - // Actor gateway router.use("*", actorGateway.bind(undefined, runConfig, managerDriver)); diff --git a/rivetkit-typescript/packages/rivetkit/src/registry/mod.ts b/rivetkit-typescript/packages/rivetkit/src/registry/mod.ts index 78fb798778..708b084a2e 100644 --- a/rivetkit-typescript/packages/rivetkit/src/registry/mod.ts +++ b/rivetkit-typescript/packages/rivetkit/src/registry/mod.ts @@ -8,7 +8,6 @@ import { ENGINE_ENDPOINT, ensureEngineProcess } from "@/engine-process/mod"; import { configureInspectorAccessToken, getInspectorUrl, - isInspectorEnabled, } from "@/inspector/utils"; import { createManagerRouter } from "@/manager/router"; import { @@ -139,15 +138,12 @@ export class Registry { // Set defaults based on the driver if (driver.name === "engine") { - config.inspector.enabled = { manager: false, actor: true }; - // We need to leave the default server enabled for dev if (config.runnerKind !== "serverless") { config.disableDefaultServer = true; } } if (driver.name === "cloudflare-workers") { - config.inspector.enabled = { manager: false, actor: true }; config.disableDefaultServer = true; config.disableActorDriver = true; config.noWelcome = true; @@ -173,7 +169,7 @@ export class Registry { definitions: Object.keys(this.#config.use).length, ...driverLog, }); - if (isInspectorEnabled(config, "manager") && managerDriver.inspector) { + if (config.inspector.enabled) { logger().info({ msg: "inspector ready", url: getInspectorUrl(config), @@ -200,10 +196,7 @@ export class Registry { const padding = " ".repeat(Math.max(0, 13 - k.length)); console.log(` - ${k}:${padding}${v}`); } - if ( - isInspectorEnabled(config, "manager") && - managerDriver.inspector - ) { + if (config.inspector.enabled) { console.log(` - Inspector: ${getInspectorUrl(config)}`); } console.log(); diff --git a/rivetkit-typescript/packages/rivetkit/src/remote-manager-driver/mod.ts b/rivetkit-typescript/packages/rivetkit/src/remote-manager-driver/mod.ts index e9eecef6e3..e81da48ef1 100644 --- a/rivetkit-typescript/packages/rivetkit/src/remote-manager-driver/mod.ts +++ b/rivetkit-typescript/packages/rivetkit/src/remote-manager-driver/mod.ts @@ -381,8 +381,4 @@ export class RemoteManagerDriver implements ManagerDriver { displayInformation(): ManagerDisplayInformation { return { name: "Remote", properties: {} }; } - - getOrCreateInspectorAccessToken() { - return generateRandomString(); - } } diff --git a/rivetkit-typescript/packages/rivetkit/src/schemas/actor-inspector/mod.ts b/rivetkit-typescript/packages/rivetkit/src/schemas/actor-inspector/mod.ts new file mode 100644 index 0000000000..f5c9cb363e --- /dev/null +++ b/rivetkit-typescript/packages/rivetkit/src/schemas/actor-inspector/mod.ts @@ -0,0 +1 @@ +export * from "../../../dist/schemas/actor-inspector/v1"; diff --git a/rivetkit-typescript/packages/rivetkit/src/schemas/actor-inspector/versioned.ts b/rivetkit-typescript/packages/rivetkit/src/schemas/actor-inspector/versioned.ts new file mode 100644 index 0000000000..849025a44b --- /dev/null +++ b/rivetkit-typescript/packages/rivetkit/src/schemas/actor-inspector/versioned.ts @@ -0,0 +1,23 @@ +import { + createVersionedDataHandler, + type MigrationFn, +} from "@/common/versioned-data"; +import * as v1 from "../../../dist/schemas/actor-inspector/v1"; + +export const CURRENT_VERSION = 1; + +const migrations = new Map>([]); + +export const TO_SERVER_VERSIONED = createVersionedDataHandler({ + currentVersion: CURRENT_VERSION, + migrations, + serializeVersion: (data) => v1.encodeToServer(data), + deserializeVersion: (bytes) => v1.decodeToServer(bytes), +}); + +export const TO_CLIENT_VERSIONED = createVersionedDataHandler({ + currentVersion: CURRENT_VERSION, + migrations, + serializeVersion: (data) => v1.encodeToClient(data), + deserializeVersion: (bytes) => v1.decodeToClient(bytes), +});