De Runtime SDK gebruiken
Doel
De Runtime SDK begrijpen — de ene interface waarmee een aangepaste React-app of een aangepast besturingselement het Parsware-platform bereikt — en elk onderdeel ervan goed gebruiken.
Vereisten
- Een project dat is opgezet zoals in
Een aangepaste React-app bouwen of
Een aangepast besturingselement bouwen, met
@parsware/runtime-sdkgeïnstalleerd vanafhttps://docs.parsware.com/sdk/parsware-runtime-sdk-1.0.0.tgz. @parsware/runtime-sdkals gedeelde singleton gedeclareerd in de Module Federation-configuratie van het project. Zonder dat krijgt de app een eigen, lege kopie van de SDK in plaats van die van de shell.
De ene regel
Een gehost artefact bereikt het platform alleen via de Runtime SDK. De shell koppelt de runtime aan de omgeving waarin zij de app opende en aan de persoon die haar gebruikt. Er is geen omgevings-id om door te geven, geen token om te bewaren en geen adres om te configureren, dus dezelfde build werkt in elke omgeving waarin ze is geïnstalleerd. Een app die zelf de HTTP-API's van het platform aanroept, moet alle drie zelf oplossen en doet het fout zodra ze ergens anders wordt geïnstalleerd.
De runtime krijgen
Een aangepaste React-app leest hem uit een hook:
import { useParswareRuntime } from '@parsware/runtime-sdk';
export default function App() {
const runtime = useParswareRuntime();
// …
}
Een aangepast besturingselement krijgt hetzelfde oppervlak als zijn prop context:
export default function Control({ value, onChange, fieldMetadata, context }: ParswareControlProps) {
context.log('debug', 'mounted');
}
Buiten de shell, in een unittest, omsluit je de component met ParswareRuntimeProvider en een
stub: <ParswareRuntimeProvider runtime={stub}>.
Wat de runtime bevat
| Onderdeel | Wat het is |
|---|---|
environmentId, environmentName | De omgeving waarin de app werd geopend. De naam mag je tonen. |
artifactId | De logische naam van de app of het besturingselement. |
user | De persoon die het gebruikt: id, name, email, tenantId, roles. |
scopes | Wat de app vroeg, ingeperkt tot wat de sessie van de persoon bevat. |
data | Tabellen, records, query's, opgeslagen query's, aangepaste API's, rapporten en recordbestanden, gekoppeld aan de omgeving. |
storage | Objectopslag: buckets en de bestanden erin. |
log | Gestructureerd logboek, dat met app en omgeving naar het platform gaat. |
Wie haar gebruikt, en wat die mag
runtime.user is om te tonen en te begroeten: een naam, een rol om een indeling op te kiezen. Het
is geen controle van rechten.
Elke aanroep gebeurt als de persoon die de app gebruikt. Hun beveiligingsrol bepaalt wat elke aanroep mag lezen en wijzigen, precies zoals in een modelgestuurde app, dus een app kan nooit meer dan de persoon die haar opende, en twee mensen die dezelfde app gebruiken zien elk wat zij mogen zien.
runtime.scopes zijn de requestedScopes van de app, ingeperkt tot de sessie van de persoon. Lees
ze om weg te laten wat niet zal werken:
const canSave = runtime.scopes.includes('storage.write');
Records lezen
Een pagina van één tabel
import { FilterConnector, RecordFilterOperator } from '@parsware/runtime-sdk';
const page = await runtime.data.queryRecords('par_order', {
pageNumber: 1,
pageSize: 25,
sorts: [{ field: 'par_createdon', descending: true }],
filterGroup: {
connector: FilterConnector.And,
conditions: [{ field: 'par_status', operator: RecordFilterOperator.Equals, value: 1 }],
},
});
// page.items, page.totalCount, page.hasNextPage …
Een pagina, nooit de hele tabel. { pageSize: 1 } is de goedkope manier om alleen een aantal te
lezen.
Een query: kolommen, koppelingen en totalen
runQuery neemt hetzelfde querydocument dat de rasters van het platform zelf versturen, en kan dus
kolommen kiezen, zoekvelden volgen en optellen:
const result = await runtime.data.runQuery('nw_visitor', {
select: ['nw_name', 'nw_company', 'new_purpose'],
order: [{ field: 'nw_name', descending: false }],
page: { number: 1, size: 50 },
});
for (const record of result.records?.items ?? []) {
record.values.nw_name; // 'Ada Lovelace'
record.values.new_purpose; // 270150000 — what is stored
record.formattedValues?.new_purpose; // 'Meeting' — what a person reads
}
Met een aggregate-blok antwoordt ze met groepen in plaats van rijen (result.kind === 'Groups').
Vraag de server om totalen in plaats van elke rij op te halen om ze in de browser op te tellen.
Een opgeslagen query, op naam
Hoort de vraag bij de oplossing en niet bij de code van de app, sla haar dan op in de oplossing en voer haar uit op naam:
const totals = await runtime.data.runSavedQuery('nw_visitorsbycompany');
for (const group of totals.groups?.groups ?? []) {
group.groups.nw_company; // 'Analytical Engines'
group.measures.visitors; // 4
}
De definitie reist dan mee met de oplossing en kan worden verbeterd zonder een nieuwe build uit te
brengen. De opties kunnen haar alleen beperken: een extra filter wordt met EN gecombineerd met
de opgeslagen filter, en page of top begrenzen haar. Wat ze leest, groepeert en telt, blijft
dat van de opgeslagen query.
Zoekvelden en keuzes
searchLookup(table, { search })vindt kandidaten voor een zoekveld, met de kolommen die de zoekweergave van de doeltabel toont.resolveLookup(table, id)maakt van een opgeslagen id weer de tekst om te tonen.- Een keuzekolom komt als het nummer van de optie, met het label ernaast.
valuesbevat het opgeslagen nummer enformattedValueshet label dat iemand leest, zoals de naam van een zoekveld meekomt. Gegroepeerde resultaten dragen het informattedGroups.listOptionSets()geeft de hele lijst, voor een keuzelijst.
Records wijzigen
const created = await runtime.data.createRecord('par_order', { par_name: 'ORD-1001' });
await runtime.data.updateRecord('par_order', created.id, { par_status: 2 });
await runtime.data.deleteRecord('par_order', created.id);
updateRecord neemt alleen de kolommen die veranderen. Een hele record terugsturen overschrijft
waarden die iemand anders sinds jouw lezen kan hebben gewijzigd. Plug-instappen op de server draaien
bij deze schrijfacties zoals bij elke andere; de bedrijfsregels aan clientzijde van een formulier
niet, want er is geen formulier.
Een aangepaste API aanroepen
const overview = await runtime.data.callApi('GET', 'psl/v1/overview', { season: '2026' });
Het pad is het adres van de API onder de omgeving: voorvoegsel van de uitgever, versie, route. Bij
GET en DELETE gaan de invoerwaarden in de querystring, anders in de JSON-body. Een weigering
geeft een fout met de eigen melding van de API. Een app en een extern systeem die dezelfde API
aanroepen, lezen hetzelfde antwoord.
Objectopslag
Voor bestanden die niet bij één record horen: een export die de app maakte, een document dat iemand haar gaf, een batch die ze zo gaat verwerken.
await runtime.storage.createBucket('nw-frontdesk'); // or returns the existing one
const saved = await runtime.storage.put('nw-frontdesk', 'visitors/2026-10-14.csv',
new Blob([csv], { type: 'text/csv' }), '2026-10-14.csv');
const files = await runtime.storage.list('nw-frontdesk', 'visitors/');
const blob = await runtime.storage.get('nw-frontdesk', saved.key);
await runtime.storage.remove('nw-frontdesk', saved.key);
- Een bucket hoort bij de tenant, niet bij de omgeving. Twee omgevingen van één tenant delen hem, dus zet de omgeving in de sleutel als hun bestanden gescheiden moeten blijven.
- Bucketnamen worden over alle tenants gedeeld, zoals in S3. Kies er een die echt van jou is.
- Vraag
storage.readenstorage.writeaan in derequestedScopesvan de app. - Een bestand dat bij één record hoort, gaat in plaats daarvan op de record (volgende sectie).
Bestanden en afbeeldingen van een record
Een kolom van het type File of Image bevat een klein bestand dat bij die record hoort:
const info = await runtime.data.getRecordFile('par_contact', id, 'par_photo'); // null when empty
await runtime.data.uploadRecordFile('par_contact', id, 'par_photo', file);
const blob = await runtime.data.downloadRecordFile('par_contact', id, 'par_photo');
await runtime.data.deleteRecordFile('par_contact', id, 'par_photo');
- Het uploaden vult de kolom zelf. Zet het veld niet in
createRecordofupdateRecord. - De record moet eerst bestaan. Maak hem aan, upload daarna.
- Er is geen link voor
<img src>. Het bestand wordt geautoriseerd als zijn record, dus toon de Blob viaURL.createObjectURL(blob).
Tabellen en hun definities
listEntities(), listFields(entityId), listViews(entityId), getViewDefinition(…),
listForms(entityId), getFormDefinition(…), listRelationships(), listOptionSets() en
getEntityStatus(entityId) beschrijven de omgeving, voor een app die haar eigen raster of
formulier tekent. getRecordPermissions(table) zegt of de persoon in een tabel mag aanmaken,
lezen, bijwerken of verwijderen, zodat de app een knop kan weglaten voordat die wordt geweigerd.
canImport zegt of de persoon er een spreadsheet in mag laden; daarvoor is naast aanmaken ook het
recht Gegevens importeren nodig.
Logboek
runtime.log('info', 'Visitor list saved', { rows: 8 });
runtime.log('error', 'Could not load visitors', { message });
De niveaus zijn debug, info, warn en error. App en omgeving worden voor je toegevoegd.
Log nooit recordwaarden, persoonsgegevens of iets uit een token. De browserconsole is
onzichtbaar voor wie de app ondersteunt; dit niet.
Fouten afhandelen
Elke aanroep geeft een promise terug die kan worden afgewezen: geen recht, een record die intussen is verwijderd, een netwerk dat plat ligt. Toon de melding, en wijzig geen status meer zodra de app is gesloten:
useEffect(() => {
let cancelled = false;
runtime.data.runQuery('nw_visitor', { page: { number: 1, size: 50 } })
.then((r) => { if (!cancelled) setRows(r.records?.items ?? []); })
.catch((e: unknown) => { if (!cancelled) setError(e instanceof Error ? e.message : String(e)); });
return () => { cancelled = true; };
}, [runtime]);
Wat de SDK niet doet
- Ze verstuurt geen meldingen. Er is geen meldingsoppervlak; doe er geen na met een directe HTTP-aanroep.
- Ze slaat de waarde van een aangepast besturingselement niet op. Een besturingselement legt
vast via
onChange, en het formulier slaat op. - Ze geeft geen tokens. Er is niets te lezen en niets te bewaren.
- Ze bereikt geen andere omgevingen. Werk tussen omgevingen verplaatsen is een oplossing exporteren en importeren.
Problemen oplossen
useParswareRuntime()geeft een fout — de component is buiten een runtime-provider gerenderd. In de shell kan dat niet; in een test omsluit je haar metParswareRuntimeProvider.- De runtime is er maar is leeg, of hooks falen —
@parsware/runtime-sdkof React is in hetshared-blok van Module Federation nietsingleton: true, dus de app heeft een eigen kopie gebundeld. - Een aanroep wordt geweigerd — de persoon die de app gebruikt, mag dat niet; hun
beveiligingsrol beslist. Controleer
runtime.scopesengetRecordPermissions, en laat de actie weg. - Een keuzekolom toont een getal — lees
formattedValues, nietvalues, voor het label. Cannot find module …/dist/typesonder Vitest — de SDK is geïnstalleerd uit een oudere download. Installeer de huidige.
Gerelateerde scenario's
- Een aangepaste React-app bouwen.
- Een aangepast besturingselement bouwen.
- Een toegangstoken verkrijgen.