tinyjs Desktop
tinyjs combines a txiki.js backend with a native system WebView. @zeltjs/adapter-tinyjs connects the page to Zelt's HTTP controllers over native IPC, without opening a TCP port.
npm install @zeltjs/core @zeltjs/adapter-tinyjs
Backend entry:
import { Controller, createApp, Get, http } from '@zeltjs/core';
import { onTinyjs, tinyjsBridge } from '@zeltjs/adapter-tinyjs';
@Controller('/api')
class ApiController {
@Get('/status')
status() { return { status: 'ok' }; }
}
const desktop = await onTinyjs(createApp([http({ controllers: [ApiController] }), tinyjsBridge()]));
export const { api, init } = desktop.tinyjsBridge.create(desktop.http);
Renderer:
import { tinyFetch } from '@zeltjs/adapter-tinyjs/renderer';
const response = await tinyFetch('/api/status');
const status = await response.json();
The renderer receives a standard Response, including status, headers, JSON and binary bodies. The single-target factory exposes the supplied HTTP capability as http. To expose several targets explicitly, use desktop.tinyjsBridge.create(new Map([['http', desktop.http], ['admin', desktop.admin]])), then select a map key in the renderer with createTinyFetch({ feature: 'admin' }). HTTP features are never exposed automatically.
Native app and shutdown
Export init to attach the native app and ensure Zelt shutdown finishes before tinyjs exits. Inject TinyjsAppAdaptor for native setTitle, push and quit operations. Access before init or after shutdown fails explicitly.
The backend shares DI services across HTTP features and uses synchronous context. Capture helpers at entry and keep the returned references across await.
Build and package
The complete task-list example includes backend and renderer bundles plus tinyjs.json:
pnpm --filter @zeltjs/adapter-tinyjs... --filter @zeltjs/hono-client... build
pnpm --filter @examples/tinyjs-desktop dev
pnpm --filter @examples/tinyjs-desktop package
The build uses Node; the packaged application runs on txiki.js and the OS WebView. Build on the OS you target. Linux was verified with tinyjs 0.50.1, including a real WebView; macOS and Windows require separate verification.
IPC responses are buffered and must finish. Declare httpServer() and use app.httpServer.listen(app.http, options) for streaming/SSE endpoints. Browser cookies are not automatically managed by this bridge. Aborting fetch rejects renderer-side waiting; an already-running backend handler can still finish.