Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/content/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export default defineConfig({
items: [
{ text: 'HTTP', link: '/docs/adapters/http' },
{ text: 'Websocket', link: '/docs/adapters/websocket' },
{ text: 'Message Port', link: '/docs/adapters/message-port' },
],
},
{
Expand All @@ -105,6 +106,9 @@ export default defineConfig({
{ text: 'SolidStart', link: '/docs/integrations/solid-start' },
{ text: 'Astro', link: '/docs/integrations/astro' },
{ text: 'React Native', link: '/docs/integrations/react-native' },
{ text: 'Electron', link: '/docs/integrations/electron' },
{ text: 'Browser Extension', link: '/docs/integrations/browser-extension' },
{ text: 'Worker Threads', link: '/docs/integrations/worker-threads' },
],
},
{
Expand Down
46 changes: 46 additions & 0 deletions apps/content/docs/adapters/message-port.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
title: Message Port
description: Using oRPC with Message Ports
---

# Message Port

oRPC offers built-in support for common Message Port implementations, enabling easy internal communication between different processes.

| Environment | Documentation |
| --------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
| [Electron Message Port](https://www.electronjs.org/docs/latest/tutorial/message-ports) | [Integration Guide](/docs/integrations/electron) |
| [Browser Extension Long-lived Connections](https://developer.chrome.com/docs/extensions/develop/concepts/messaging#connect) | [Integration Guide](/docs/integrations/browser-extension) |
| [Node.js Worker Threads Port](https://nodejs.org/api/worker_threads.html#workerparentport) | [Integration Guide](/docs/integrations/worker-threads) |

## Basic Usage

Message Ports work by establishing two endpoints that can communicate with each other:

```ts [bridge]
const channel = new MessageChannel()
const serverPort = channel.port1
const clientPort = channel.port2
```

```ts [server]
import { experimental_RPCHandler as RPCHandler } from '@orpc/server/message-port'

const handler = new RPCHandler(router)

handler.upgrade(serverPort, {
context: {}, // Optionally provide an initial context
})
```

```ts [client]
import { experimental_RPCLink as RPCLink } from '@orpc/client/message-port'

const link = new RPCLink({
port: clientPort,
})
```

:::info
This only shows how to configure the link. For full client examples, see [Client-Side Clients](/docs/client/client-side).
:::
51 changes: 51 additions & 0 deletions apps/content/docs/integrations/browser-extension.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
title: Browser Extension Integration
description: Integrate oRPC with Browser Extensions
---

# Browser Extension Integration

Easily set up type-safe communication between scripts in your browser extension using [Message Port Adapter](/docs/adapters/message-port). Before you begin, it’s recommended to read the [Message Passing Docs](https://developer.chrome.com/docs/extensions/develop/concepts/messaging#connect)

::: warning
The browser [Message Passing API](https://developer.chrome.com/docs/extensions/develop/concepts/messaging) does not support transferring binary data, which means oRPC features like `File` and `Blob` cannot be used natively. However, you can temporarily work around this limitation by extending the [RPC JSON Serializer](/docs/advanced/rpc-json-serializer#extending-native-data-types) to encode `File` and `Blob` as Base64.
:::

## Server

To listen for connections on a port and upgrade the handler:

```ts
import { experimental_RPCHandler as RPCHandler } from '@orpc/server/message-port'
import { router } from './router'

const handler = new RPCHandler(router)

browser.runtime.onConnect.addListener((port) => {
handler.upgrade(port, {
context: {}, // provide initial context if needed
})
})
```

:::info
Both `browser` and `chrome` namespaces work similarly in this case. You can use whichever one you prefer.
:::

## Client

To connect to the port and create an oRPC link on the client side:

```ts
import { experimental_RPCLink as RPCLink } from '@orpc/client/message-port'

const port = browser.runtime.connect()

const link = new RPCLink({
port,
})
```

:::info
This only shows how to configure the link. For full client examples, see [Client-Side Clients](/docs/client/client-side).
:::
65 changes: 65 additions & 0 deletions apps/content/docs/integrations/electron.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
title: Electron Integration
description: Integrate oRPC with Electron
---

# Electron Integration

Establish type-safe communication between processes in [Electron](https://www.electronjs.org/) using the [Message Port Adapter](/docs/adapters/message-port). Before you start, we recommend reading the [MessagePorts in Electron](https://www.electronjs.org/docs/latest/tutorial/message-ports) guide.

## Main Process

Listen for a port sent from the renderer, then upgrade it:

```ts
import { experimental_RPCHandler as RPCHandler } from '@orpc/server/message-port'
import { router } from './router'

const handler = new RPCHandler(router)

app.whenReady().then(() => {
ipcMain.on('start-orpc-server', async (event) => {
const [serverPort] = event.ports
handler.upgrade(serverPort)
serverPort.start()
})
})
```

:::info
Channel `start-orpc-server` is arbitrary. you can use any name that fits your needs.
:::

## Preload Process

Receive the port from the renderer and forward it to the main process:

```ts
window.addEventListener('message', (event) => {
if (event.data === 'start-orpc-client') {
const [serverPort] = event.ports

ipcRenderer.postMessage('start-orpc-server', null, [serverPort])
}
})
```

## Renderer Process

Create a `MessageChannel`, send one port to the preload script, and use the other to initialize the client link:

```ts
const { port1: clientPort, port2: serverPort } = new MessageChannel()

window.postMessage('start-orpc-client', '*', [serverPort])

const link = new RPCLink({
port: clientPort,
})

clientPort.start()
```

:::info
This only shows how to configure the link. For full client examples, see [Client-Side Clients](/docs/client/client-side).
:::
48 changes: 48 additions & 0 deletions apps/content/docs/integrations/worker-threads.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
title: Worker Threads Integration
description: Enable type-safe communication between Node.js Worker Threads using oRPC.
---

# Worker Threads Integration

Use [Node.js Worker Threads](https://nodejs.org/api/worker_threads.html) with oRPC for type-safe inter-thread communication via the [Message Port Adapter](/docs/adapters/message-port). Before proceeding, we recommend reviewing the [Node.js Worker Thread API](https://nodejs.org/api/worker_threads.html).

## Worker Thread

Listen for a `MessagePort` sent from the main thread and upgrade it:

```ts
import { parentPort } from 'node:worker_threads'
import { experimental_RPCHandler as RPCHandler } from '@orpc/server/message-port'

const handler = new RPCHandler(router)

parentPort.on('message', (message) => {
if (message instanceof MessagePort) {
handler.upgrade(message)
}
})
```

## Main Thread

Create a `MessageChannel`, send one port to the thread worker, and use the other to initialize the client link:

```ts
import { MessageChannel, Worker } from 'node:worker_threads'
import { experimental_RPCLink as RPCLink } from '@orpc/client/message-port'

const { port1: clientPort, port2: serverPort } = new MessageChannel()

const worker = new Worker('some-worker.js')

worker.postMessage(serverPort, [serverPort])

const link = new RPCLink({
port: clientPort
})
```

:::info
This only shows how to configure the link. For full client examples, see [Client-Side Clients](/docs/client/client-side).
:::
24 changes: 14 additions & 10 deletions apps/content/docs/playgrounds.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,18 @@ featuring pre-configured examples accessible instantly via StackBlitz or local s

## Available Playgrounds

| Environment | StackBlitz | GitHub Source |
| ------------------------- | --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| Next.js Playground | [Open in StackBlitz](https://stackblitz.com/github/unnoq/orpc/tree/main/playgrounds/next) | [View Source](https://github.com/unnoq/orpc/tree/main/playgrounds/next) |
| TanStack Start Playground | [Open in StackBlitz](https://stackblitz.com/github/unnoq/orpc/tree/main/playgrounds/tanstack-start) | [View Source](https://github.com/unnoq/orpc/tree/main/playgrounds/tanstack-start) |
| Nuxt.js Playground | [Open in StackBlitz](https://stackblitz.com/github/unnoq/orpc/tree/main/playgrounds/nuxt) | [View Source](https://github.com/unnoq/orpc/tree/main/playgrounds/nuxt) |
| Solid Start Playground | [Open in StackBlitz](https://stackblitz.com/github/unnoq/orpc/tree/main/playgrounds/solid-start) | [View Source](https://github.com/unnoq/orpc/tree/main/playgrounds/solid-start) |
| Svelte Kit Playground | [Open in StackBlitz](https://stackblitz.com/github/unnoq/orpc/tree/main/playgrounds/svelte-kit) | [View Source](https://github.com/unnoq/orpc/tree/main/playgrounds/svelte-kit) |
| Astro Playground | [Open in StackBlitz](https://stackblitz.com/github/unnoq/orpc/tree/main/playgrounds/astro) | [View Source](https://github.com/unnoq/orpc/tree/main/playgrounds/astro) |
| Contract-First Playground | [Open in StackBlitz](https://stackblitz.com/github/unnoq/orpc/tree/main/playgrounds/contract-first) | [View Source](https://github.com/unnoq/orpc/tree/main/playgrounds/contract-first) |
| NestJS Playground | [Open in StackBlitz](https://stackblitz.com/github/unnoq/orpc/tree/main/playgrounds/nest) | [View Source](https://github.com/unnoq/orpc/tree/main/playgrounds/nest) |
| Environment | StackBlitz | GitHub Source |
| ---------------------------- | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| Next.js Playground | [Open in StackBlitz](https://stackblitz.com/github/unnoq/orpc/tree/main/playgrounds/next) | [View Source](https://github.com/unnoq/orpc/tree/main/playgrounds/next) |
| TanStack Start Playground | [Open in StackBlitz](https://stackblitz.com/github/unnoq/orpc/tree/main/playgrounds/tanstack-start) | [View Source](https://github.com/unnoq/orpc/tree/main/playgrounds/tanstack-start) |
| Nuxt.js Playground | [Open in StackBlitz](https://stackblitz.com/github/unnoq/orpc/tree/main/playgrounds/nuxt) | [View Source](https://github.com/unnoq/orpc/tree/main/playgrounds/nuxt) |
| Solid Start Playground | [Open in StackBlitz](https://stackblitz.com/github/unnoq/orpc/tree/main/playgrounds/solid-start) | [View Source](https://github.com/unnoq/orpc/tree/main/playgrounds/solid-start) |
| Svelte Kit Playground | [Open in StackBlitz](https://stackblitz.com/github/unnoq/orpc/tree/main/playgrounds/svelte-kit) | [View Source](https://github.com/unnoq/orpc/tree/main/playgrounds/svelte-kit) |
| Astro Playground | [Open in StackBlitz](https://stackblitz.com/github/unnoq/orpc/tree/main/playgrounds/astro) | [View Source](https://github.com/unnoq/orpc/tree/main/playgrounds/astro) |
| Contract-First Playground | [Open in StackBlitz](https://stackblitz.com/github/unnoq/orpc/tree/main/playgrounds/contract-first) | [View Source](https://github.com/unnoq/orpc/tree/main/playgrounds/contract-first) |
| NestJS Playground | [Open in StackBlitz](https://stackblitz.com/github/unnoq/orpc/tree/main/playgrounds/nest) | [View Source](https://github.com/unnoq/orpc/tree/main/playgrounds/nest) |
| Electron Playground | | [View Source](https://github.com/unnoq/orpc/tree/main/playgrounds/electron) |
| Browser Extension Playground | | [View Source](https://github.com/unnoq/orpc/tree/main/playgrounds/browser-extension) |

:::warning
StackBlitz has own limitations, so some features may not work as expected.
Expand All @@ -38,6 +40,8 @@ npx degit unnoq/orpc/playgrounds/svelte-kit orpc-svelte-kit-playground
npx degit unnoq/orpc/playgrounds/astro orpc-astro-playground
npx degit unnoq/orpc/playgrounds/contract-first orpc-contract-first-playground
npx degit unnoq/orpc/playgrounds/nest orpc-nest-playground
npx degit unnoq/orpc/playgrounds/electron orpc-electron-playground
npx degit unnoq/orpc/playgrounds/browser-extension orpc-browser-extension-playground
```

For each project, set up the development environment:
Expand Down
2 changes: 1 addition & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@ export default antfu({
rules: {
'no-alert': 'off',
'eslint-comments/no-unlimited-disable': 'off',
'node/prefer-global/process': 'off',
},
}, {
files: ['playgrounds/nest/**'],
rules: {
'node/prefer-global/process': 'off',
'@typescript-eslint/consistent-type-imports': 'off',
},
})
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@
"vite-plugin-solid": "^2.11.6",
"vitest": "^3.0.4"
},
"pnpm": {
"onlyBuiltDependencies": [
"electron",
"esbuild"
]
},
Comment thread
dinwwwh marked this conversation as resolved.
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged"
},
Expand Down
8 changes: 7 additions & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@
"types": "./dist/adapters/websocket/index.d.mts",
"import": "./dist/adapters/websocket/index.mjs",
"default": "./dist/adapters/websocket/index.mjs"
},
"./message-port": {
"types": "./dist/adapters/message-port/index.d.mts",
"import": "./dist/adapters/message-port/index.mjs",
"default": "./dist/adapters/message-port/index.mjs"
}
}
},
Expand All @@ -47,7 +52,8 @@
"./plugins": "./src/plugins/index.ts",
"./standard": "./src/adapters/standard/index.ts",
"./fetch": "./src/adapters/fetch/index.ts",
"./websocket": "./src/adapters/websocket/index.ts"
"./websocket": "./src/adapters/websocket/index.ts",
"./message-port": "./src/adapters/message-port/index.ts"
},
"files": [
"dist"
Expand Down
3 changes: 3 additions & 0 deletions packages/client/src/adapters/message-port/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './link-client'
export * from './message-port'
export * from './rpc-link'
33 changes: 33 additions & 0 deletions packages/client/src/adapters/message-port/link-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { StandardLazyResponse, StandardRequest } from '@orpc/standard-server'
import type { ClientContext, ClientOptions } from '../../types'
import type { StandardLinkClient } from '../standard'
import type { SupportedMessagePort } from './message-port'
import { ClientPeer } from '@orpc/standard-server-peer'
import { onMessagePortClose, onMessagePortMessage, postMessagePortMessage } from './message-port'

export interface experimental_LinkMessagePortClientOptions {
port: SupportedMessagePort
}

export class experimental_LinkMessagePortClient<T extends ClientContext> implements StandardLinkClient<T> {
private readonly peer: ClientPeer

constructor(options: experimental_LinkMessagePortClientOptions) {
this.peer = new ClientPeer(async (message) => {
postMessagePortMessage(options.port, message instanceof Blob ? await message.arrayBuffer() : message)
})

onMessagePortMessage(options.port, async (message) => {
await this.peer.message(message)
})

onMessagePortClose(options.port, () => {
this.peer.close()
})
}

async call(request: StandardRequest, _options: ClientOptions<T>, _path: readonly string[], _input: unknown): Promise<StandardLazyResponse> {
const response = await this.peer.request(request)
return { ...response, body: () => Promise.resolve(response.body) }
}
}
Loading