Tovább a tartalomhoz

Plugin fejlesztés

A leggyorsabb módja egy új plugin projekt indításának a @racona/cli CLI:

Terminál
bunx @racona/cli

A wizard végigvezet a beállításokon:

  1. App ID — kebab-case azonosító (pl. my-app)
  2. Display Name — megjelenítendő név a Racona-ben
  3. Description — rövid leírás
  4. Author — neved és email-ed
  5. Features — válaszd ki a szükséges funkciókat
  6. Install dependencies? — automatikusan futtatja a bun install-t
FeatureMit ad hozzá
sidebarOldalsáv navigáció (menu.json, AppLayout mód, több oldal komponens)
databaseSQL migrációk, sdk.data.query() támogatás, lokális dev adatbázis Docker-rel
remote_functionsserver/functions.ts, sdk.remote.call(), lokális dev szerver
notificationssdk.notifications.send() támogatás
i18nlocales/hu.json + locales/en.json, sdk.i18n.t() támogatás
datatableDataTable komponens insert formmal, sor akciókkal (duplikálás/törlés), teljes i18n

A struktúra a kiválasztott feature-öktől függ. Teljes példa (minden feature engedélyezve):

my-app/
├── manifest.json # App metaadatok és jogosultságok
├── package.json
├── vite.config.ts
├── tsconfig.json
├── menu.json # (ha sidebar)
├── build-all.js # (ha sidebar)
├── dev-server.ts # (ha remote_functions)
├── docker-compose.dev.yml # (ha database)
├── .env.example # (ha database)
├── src/
│ ├── App.svelte
│ ├── main.ts
│ ├── plugin.ts
│ └── components/ # (ha sidebar)
│ ├── Overview.svelte
│ ├── Settings.svelte
│ ├── Datatable.svelte # (ha datatable)
│ ├── Notifications.svelte # (ha notifications)
│ └── Remote.svelte # (ha remote_functions)
├── server/ # (ha remote_functions)
│ └── functions.ts
├── migrations/ # (ha database)
│ ├── 001_init.sql
│ └── dev/
│ └── 000_auth_seed.sql
├── locales/ # (ha i18n)
│ ├── hu.json
│ └── en.json
└── assets/
└── icon.svg

A generált projekt scriptkészlete a kiválasztott feature-öktől függ.

Terminál
bun dev # Vite dev szerver (standalone, Mock SDK) — http://localhost:5174
bun run build # IIFE bundle elkészítése (dist/index.iife.js)
bun run build:watch # Build figyelő módban
bun run package # .elyospkg csomag elkészítése
Terminál
bun run dev:server # Dev szerver indítása — http://localhost:5175

A dev:server egy Bun HTTP szervert indít, amely:

  • Statikus fájlokat szolgál ki a dist/ mappából és a projekt gyökeréből (CORS fejlécekkel)
  • POST /api/remote/:functionName endpointot biztosít a server/functions.ts függvényeinek hívásához
Terminál
bun db:up # Docker Postgres konténer indítása
bun db:down # Docker Postgres konténer leállítása
bun run dev:full # dev:server + dev párhuzamosan (egy terminálban)

A dev:full egyszerre indítja a Vite dev szervert (5174) és a dev szervert (5175), így nem kell két terminál.

Terminál
cp .env.example .env # Környezeti változók beállítása
bun db:up # Postgres konténer indítása (Docker szükséges)
bun run dev:full # Dev szerver + Vite egyszerre

A .env.example tartalmazza az alapértelmezett kapcsolati URL-t a Docker Compose által indított adatbázishoz:

DATABASE_URL=postgresql://postgres:postgres@localhost:5433/{plugin_id}_dev
PORT=5175
DEV_USER_ID=dev-user

Az alkalmazás fejleszthető futó Racona példány nélkül is. A @racona/sdk/dev csomag egy Mock SDK-t biztosít, amely szimulálja az összes SDK szolgáltatást:

SDK szolgáltatásMock viselkedés
ui.toast()console.log-ba ír
ui.dialog()window.confirm / window.prompt
data.set/get/delete()localStorage-t használ (devapp:{appId}: kulcs prefix alatt)
data.query()Üres tömböt ad vissza
remote.call()Konfigurálható mock handler
i18n.t()A megadott fordítási mapből olvas
notifications.send()console.log-ba ír
Terminál
bun dev

A Vite dev szerver elindul a http://localhost:5174 címen. A hot reload automatikusan frissíti a böngészőt minden mentéskor.

Ha remote_functions is engedélyezve van, a bun dev mellé párhuzamosan szükséges a bun run dev:server is (vagy használd a bun run dev:full parancsot, ha database is engedélyezve van).

A src/main.ts fájlban a Mock SDK inicializálása automatikusan megtörténik:

src/main.ts
import { MockWebOSSDK } from '@racona/sdk/dev';
import App from './App.svelte';
import { mount } from 'svelte';
// Csak akkor fut le, ha NEM Racona-ben vagyunk
if (typeof window !== 'undefined' && !window.webOS) {
MockWebOSSDK.initialize({
i18n: {
locale: 'en',
translations: {
en: { title: 'My App', welcome: 'Welcome!' },
hu: { title: 'Alkalmazás', welcome: 'Üdvözöljük!' }
}
},
context: {
pluginId: 'my-app',
user: {
id: 'dev-user',
name: 'Developer',
email: 'dev@localhost',
roles: ['admin'],
groups: []
},
permissions: ['database', 'notifications', 'remote_functions']
}
});
}
const target = document.getElementById('app');
if (target) mount(App, { target });

Az initialize() összes konfigurációs lehetősége:

OpcióTípusLeírás
i18n.localestringAlapértelmezett nyelv (pl. 'hu')
i18n.translationsRecord<string, Record<string, string>>Fordítási kulcsok nyelvenkénti mapje
context.pluginIdstringSzimulált alkalmazás ID
context.userUserInfoSzimulált bejelentkezett felhasználó
context.permissionsstring[]Szimulált jogosultságok
data.initialDataRecord<string, unknown>Előre feltöltött localStorage adatok
remote.handlersRecord<string, Function>Mock szerver függvény handlerek
assets.baseUrlstringAsset URL prefix

Amikor a Racona betölti az alkalmazást élesben, a window.webOS már létezik, ezért az if (!window.webOS) feltétel miatt a Mock SDK nem fut le.

Ha szerver függvényeket is tesztelsz standalone módban:

MockWebOSSDK.initialize({
remote: {
handlers: {
getServerTime: async () => ({
iso: new Date().toISOString(),
locale: new Date().toLocaleString('hu-HU')
}),
calculate: async ({ a, b, operation }) => {
if (operation === 'add') return { result: a + b };
throw new Error('Unsupported operation');
}
}
}
});

A dev:server alapértelmezetten az 5175-ös portot használja (a Vite dev szerver a 5174-est). Ha egyszerre több alkalmazást fejlesztesz, a port a PORT környezeti változóval felülírható a .env fájlban vagy közvetlenül:

Terminál
PORT=5176 bun run dev:server

A Racona Dev Alkalmazások betöltőjében az URL-t ennek megfelelően add meg: http://localhost:5176.


A standalone dev mód (Mock SDK) csak a UI-t teszteli. Ha valódi SDK hívásokat, adatbázist vagy szerver függvényeket is tesztelni szeretnél, az alkalmazást be kell tölteni egy futó Racona példányba.

A folyamat lényege: buildeld le az alkalmazást, indíts egy statikus HTTP szervert (dev:server), majd töltsd be a Racona-be URL alapján. Nincs automatikus hot reload — ha változtattál a kódon, újra kell buildelni és újra megnyitni az alkalmazás ablakát.

Az elyos-core monorepo gyökerében:

Terminál
# .env.local fájlban engedélyezd a dev alkalmazás betöltést:
# DEV_MODE=true
bun app:dev

A Racona alapértelmezetten a http://localhost:5173 címen érhető el. Jelentkezz be admin fiókkal.

Az alkalmazás projekt mappájában:

Terminál
bun run build

Ez létrehozza a dist/index.iife.js fájlt — ezt tölti be a Racona.

3. lépés — Plugin dev szerver indítása

Szekció neve “3. lépés — Plugin dev szerver indítása”
Terminál
bun run dev:server

Ez elindítja a dev-server.ts Bun HTTP szervert a http://localhost:5175 címen. A szerver a dist/ mappából és a projekt gyökeréből szolgálja ki a fájlokat CORS fejlécekkel.

Ha database is engedélyezve van, a szerver induláskor automatikusan futtatja a migrációkat, és a POST /api/remote/:functionName endpointon keresztül elérhetők a server/functions.ts függvényei.

4. lépés — Alkalmazás betöltése a Racona-be

Szekció neve “4. lépés — Alkalmazás betöltése a Racona-be”
  1. Nyisd meg a Racona-t a böngészőben
  2. Start menü → Alkalmazás Manager
  3. A bal oldalsávban kattints a “Dev Alkalmazások” menüpontra
  4. Megjelenik egy URL beviteli mező http://localhost:5175 alapértelmezett értékkel
  5. Kattints a “Load” gombra

A Racona lekéri a manifest.json-t a dev szerverről, majd betölti az IIFE bundle-t és Web Component-ként regisztrálja az alkalmazást.

Terminál
# 1. Újrabuildelés
bun run build
# 2. A Racona-ben: zárd be az alkalmazás ablakát, majd nyisd meg újra
# (a "Load" gombot nem kell újra megnyomni — az alkalmazás már a listában van)

Alap (remote_functions nélkül):

Terminál
# Terminál 1 — Racona core
cd elyos-core && bun app:dev
# Terminál 2 — Alkalmazás build + szerver
cd my-app
bun run build # IIFE bundle elkészítése
bun run dev:server # statikus szerver indítása (http://localhost:5175)
# Racona-ben: Alkalmazás Manager → Dev Alkalmazások → Load → http://localhost:5175

Adatbázissal (database + remote_functions):

Terminál
# Terminál 1 — Racona core
cd elyos-core && bun app:dev
# Terminál 2 — Alkalmazás (első alkalommal)
cd my-app
cp .env.example .env # DATABASE_URL és PORT beállítása
bun db:up # Postgres konténer indítása
# Terminál 2 — Alkalmazás (minden alkalommal)
bun run build # IIFE bundle elkészítése
bun run dev:server # dev szerver + migrációk + remote endpoint (http://localhost:5175)
# Racona-ben: Alkalmazás Manager → Dev Alkalmazások → Load → http://localhost:5175

Ha az alkalmazás fejlesztése kész, csomagold be és telepítsd a Racona-be.

Terminál
bun run build # IIFE bundle elkészítése
bun run package # .elyospkg fájl létrehozása

Ez létrehozza a {id}-{version}.elyospkg fájlt (pl. my-app-1.0.0.elyospkg). A csomag egy ZIP archívum, amely tartalmazza:

  • manifest.json
  • dist/ — build output (IIFE bundle)
  • locales/ — fordítások (ha van)
  • assets/ — statikus fájlok (ha van)
  • menu.json — oldalsáv konfiguráció (ha van)
  • server/ — szerver oldali függvények (ha van)
  • migrations/ — adatbázis migrációk (ha van, dev seed fájlok nélkül)
  1. Start menü → Alkalmazás Manager → Plugin Feltöltés
  2. Húzd rá a .elyospkg fájlt, vagy kattints a böngészés gombra
  3. A Racona validálja a csomagot, majd megmutatja az előnézetet
  4. Kattints a Telepítés gombra

A telepítés során a Racona:

  • Kicsomagolja a fájlokat a plugin tárolóba
  • Regisztrálja az alkalmazást az app registry-ben
  • Importálja a fordításokat (ha van locales/)
  • Létrehozza a plugin adatbázis sémát (ha database jogosultság van)
  • Regisztrálja az email template-eket (ha notifications jogosultság van)

A manifest.json az alkalmazás metaadatait tartalmazza. Kötelező és opcionális mezők:

{
"id": "my-app",
"name": { "hu": "Alkalmazásom", "en": "My App" },
"version": "1.0.0",
"description": { "hu": "Rövid leírás", "en": "Short description" },
"author": "Szerző Neve <email@example.com>",
"entry": "dist/index.iife.js",
"icon": "assets/icon.svg",
"iconStyle": "cover",
"category": "utilities",
"permissions": ["database", "notifications", "remote_functions"],
"multiInstance": false,
"defaultSize": { "width": 800, "height": 600 },
"minSize": { "width": 400, "height": 300 },
"maxSize": { "width": 1920, "height": 1080 },
"keywords": ["example", "demo"],
"isPublic": false,
"sortOrder": 100,
"dependencies": {
"svelte": "^5.0.0",
"@lucide/svelte": "^1.0.0"
},
"minWebOSVersion": "2.0.0",
"locales": ["hu", "en"]
}
JogosultságLeírásSDK funkciók
databaseAdatbázis hozzáférésdata.set(), data.get(), data.query()
notificationsÉrtesítések küldésenotifications.send()
remote_functionsSzerver oldali függvényekremote.call()
file_accessFájl hozzáférés(tervezett)
user_dataFelhasználói adatok(tervezett)
  • Csak kisbetűk, számok és kötőjel (kebab-case)
  • Minimum 3, maximum 50 karakter
  • Regex: ^[a-z0-9-]+$
"id": "my-app" // ✅ Helyes
"id": "MyApp" // ❌ Hibás
"id": "my_app" // ❌ Hibás

Az SDK a window.webOS globális objektumon keresztül érhető el:

const sdk = window.webOS!;
// Toast értesítés
sdk.ui.toast('Üzenet', 'success');
// type: 'info' | 'success' | 'warning' | 'error'
// Dialógus
const result = await sdk.ui.dialog({
title: 'Cím',
message: 'Üzenet',
type: 'confirm' // 'info' | 'confirm' | 'prompt'
});
// Szerver függvény hívása
const result = await sdk.remote.call('functionName', { param: 'value' });
// Generikus visszatérési típussal
const result = await sdk.remote.call<MyResult>('functionName', params);
// Kulcs-érték tárolás
await sdk.data.set('key', { value: 123 });
const value = await sdk.data.get('key');
await sdk.data.delete('key');
// SQL lekérdezés (csak a plugin saját sémájában!)
const rows = await sdk.data.query('SELECT * FROM my_table WHERE id = $1', [123]);
// Tranzakció
await sdk.data.transaction(async (tx) => {
await tx.query('INSERT INTO ...');
await tx.query('UPDATE ...');
await tx.commit();
});
// Fordítás
const text = sdk.i18n.t('key');
// Paraméterekkel
const text = sdk.i18n.t('welcome', { name: 'John' });
// Aktuális nyelv
const locale = sdk.i18n.locale; // 'hu' | 'en'
// Nyelv váltás
await sdk.i18n.setLocale('en');
await sdk.notifications.send({
userId: 'user-123',
title: 'Cím',
message: 'Üzenet',
type: 'info' // 'info' | 'success' | 'warning' | 'error'
});
const pluginId = sdk.context.pluginId;
const user = sdk.context.user;
const permissions = sdk.context.permissions;
// Ablak vezérlők
sdk.context.window.close();
sdk.context.window.setTitle('Új cím');
const iconUrl = sdk.assets.getUrl('icon.svg');
const imageUrl = sdk.assets.getUrl('images/logo.png');

Az @racona/sdk teljes TypeScript típusdefiníciókat tartalmaz. A window.webOS típusa automatikusan elérhető:

// Automatikus típus — nincs szükség importra
const sdk = window.webOS!;
sdk.ui.toast('Hello!', 'success'); // ✅ autocomplete
sdk.data.set('key', { value: 123 }); // ✅ típusellenőrzés
sdk.remote.call<MyResult>('fn', params); // ✅ generikus visszatérési típus

Explicit típusimport szükség esetén:

import type { WebOSSDKInterface, UserInfo } from '@racona/sdk/types';
const user: UserInfo = sdk.context.user;

A plugin Svelte 5 runes-alapú reaktivitást használ. A vite.config.ts-ben a runes: true compiler opció be van kapcsolva:

<script lang="ts">
const sdk = window.webOS!;
let count = $state(0);
let doubled = $derived(count * 2);
$effect(() => {
sdk.ui.toast(`Count: ${count}`, 'info');
});
</script>
<button onclick={() => count++}>
{count} (doubled: {doubled})
</button>

Ha a remote_functions feature engedélyezve van, a server/functions.ts fájlban definiálhatók szerver oldali függvények:

server/functions.ts
import type { PluginFunctionContext } from '@racona/sdk/types';
export async function getItems(
params: { page: number; pageSize: number },
context: PluginFunctionContext
) {
const { db, pluginId } = context;
const schema = `app__${pluginId.replace(/-/g, '_')}`;
const rows = await db.query(
`SELECT * FROM ${schema}.items LIMIT $1 OFFSET $2`,
[params.pageSize, (params.page - 1) * params.pageSize]
);
return { success: true, data: rows };
}

Hívás a kliensről:

const result = await sdk.remote.call('getItems', { page: 1, pageSize: 20 });

Ha a database feature engedélyezve van, a migrations/ mappában SQL fájlok definiálják a plugin saját adatbázis sémáját. A fájlok névsorrendben futnak le (pl. 001_init.sql, 002_add_column.sql).

-- migrations/001_init.sql
CREATE TABLE items (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
value JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

A migrations/dev/ mappában lévő fájlok csak fejlesztési célra szolgálnak (pl. seed adatok) — a .elyospkg csomagba nem kerülnek bele.


A plugin CSS-e az IIFE build során a vite-plugin-css-injected-by-js plugin segítségével automatikusan a JS bundle-be kerül. Ez a plugin a @racona/cli által generált vite.config.ts-ben már benne van — nem kell kézzel hozzáadni.

A core app Tailwind stílusai (base layer resetok) felülírhatják a plugin stílusait. A Svelte scoped CSS button.svelte-xxxx selectorokat generál, de a Tailwind button { ... } resetje magasabb specificitással töltődik be.

A megoldás: mindig egy saját konténer osztályon belül definiáld a stílusokat:

<!-- ❌ Rossz — a core stílusai felülírják -->
<style>
button { border: 1px solid #ccc; }
</style>
<!-- ✅ Helyes — konténer osztályon belül scopelve -->
<style>
.my-plugin button { border: 1px solid #ccc; }
</style>

Ha a core stílusai felülírnak egy elemet, az all: revert visszaállítja a böngésző natív stílusát:

<style>
.my-plugin button {
all: revert;
cursor: pointer;
border: 1px solid #ccc;
border-radius: 0.25rem;
padding: 0.5rem 1rem;
}
</style>
SzabályMiért
Konténer osztályon belül scopelj (.my-plugin button)A core Tailwind stílusai felülírják a nyers tag selectorokat
Szükség esetén használj all: revert-etVisszaállítja a böngésző natív stílusát
Adj egyedi osztálynevet a gyökér konténernekElkerüli az ütközést más pluginok stílusaival

  • eval() és Function() konstruktor
  • innerHTML és document.write()
  • Külső domain-ekre fetch/XHR
  • Dinamikus import külső URL-ről
  • Más plugin sémák elérése
  • System sémák elérése (platform, auth, public)

A manifest dependencies mezőjében csak fehérlistán lévő package-ek szerepelhetnek:

  • svelte (^5.x.x)
  • @lucide/svelte / lucide-svelte
  • phosphor-svelte
  • @elyos/* és @elyos-dev/* (minden verzió) — deprecated, használd helyette @racona/*
  • @racona/* (minden verzió)

HibaMegoldás
"Invalid plugin ID format"Használj kebab-case-t: my-plugin
"Permission denied"Add hozzá a jogosultságot a manifest.json-ban
"Module not found"Futtasd le: bun run build
"Plugin already exists"Az adott ID-vel már telepítve van egy plugin — távolítsd el előbb
"Plugin is inactive"A plugin inaktív állapotban van — aktiváld az Alkalmazás Managerben
Dev alkalmazás nem jelenik megEllenőrizd, hogy DEV_MODE=true van-e a Racona .env.local-ban