mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-24 10:12:44 +00:00
Keep plugin execution scoped to its daemon while allowing one local TypeScript entry point to contribute validated RPCs and native client surfaces. Paseo owns host selection, navigation chrome, and per-installation query caches.
90 lines
2.7 KiB
Markdown
90 lines
2.7 KiB
Markdown
# Local plugins
|
|
|
|
Local plugins contribute daemon RPCs and native app surfaces from one `index.tsx`. Paseo executes
|
|
the server contribution in a subprocess and evaluates the client contribution in the app runtime.
|
|
Plugin code is trusted code; this first slice does not sandbox it.
|
|
|
|
## Configure a directory source
|
|
|
|
Add the plugin to the root `plugins` object in `$PASEO_HOME/config.json`:
|
|
|
|
```json
|
|
{
|
|
"plugins": {
|
|
"my-plugin": {
|
|
"source": "directory",
|
|
"path": "/absolute/path/to/my-plugin"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
The directory contains an identity-only manifest and one entry point:
|
|
|
|
```text
|
|
my-plugin/
|
|
paseo-plugin.json
|
|
index.tsx
|
|
```
|
|
|
|
```json
|
|
{
|
|
"id": "my-plugin"
|
|
}
|
|
```
|
|
|
|
The manifest ID must match the config key. Restart the development daemon after changing plugin
|
|
source or configuration; directory watching is outside the first slice.
|
|
|
|
## Contribute behavior and UI
|
|
|
|
Default export one contribution function. Paseo calls it with a plugin-scoped context. The compiler
|
|
removes UI registrations from the server bundle and RPC registrations from the client bundle before
|
|
resolving dependencies.
|
|
|
|
```tsx
|
|
import { Text } from "react-native";
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import { z } from "zod";
|
|
import { defineRpc, type PluginContext, useRpc } from "@paseo/plugin";
|
|
|
|
const greetRpc = defineRpc({
|
|
name: "greet",
|
|
input: z.object({ name: z.string() }),
|
|
output: z.object({ message: z.string() }),
|
|
});
|
|
|
|
function Greeting() {
|
|
const greet = useRpc(greetRpc);
|
|
const query = useQuery({
|
|
queryKey: ["greeting", "Paseo"],
|
|
queryFn: () => greet({ name: "Paseo" }),
|
|
});
|
|
return <Text>{query.data?.message}</Text>;
|
|
}
|
|
|
|
export default function contribute(plugin: PluginContext) {
|
|
plugin.handle(greetRpc, async ({ name }) => ({ message: `Hello, ${name}` }));
|
|
plugin.addSurface("main", Greeting);
|
|
plugin.addSidebarItem({
|
|
id: "main",
|
|
title: "Greeting",
|
|
icon: "MessageCircle",
|
|
surface: "main",
|
|
});
|
|
}
|
|
```
|
|
|
|
Paseo owns the route, screen header, Lucide icon validation, close action, theme DTO, layout facts,
|
|
and render error boundary. The contributed component owns the complete body below the header.
|
|
|
|
RPC contracts validate inputs and outputs in both the app and plugin subprocess. `useRpc` returns a
|
|
typed async function. Use the host-provided `@tanstack/react-query` for request state and caching;
|
|
Paseo gives each plugin installation its own query client.
|
|
|
|
When the same plugin contribution exists on multiple hosts, Paseo shows it once in the sidebar and
|
|
adds a host picker to the screen header. The selected host supplies the bundle, RPC transport, and
|
|
query cache. Plugin code cannot address another host.
|
|
|
|
See `examples/local-plugin` for a runnable plugin.
|