Direkt zum Hauptinhalt

Das Runtime SDK verwenden

Ziel​

Das Runtime SDK verstehen – die eine Schnittstelle, über die eine benutzerdefinierte React-App oder ein benutzerdefiniertes Steuerelement die Parsware-Plattform erreicht – und jeden Teil davon richtig verwenden.

Voraussetzungen​

Die eine Regel​

Ein gehostetes Artefakt erreicht die Plattform nur über das Runtime SDK. Die Shell bindet die Runtime an die Umgebung, in der sie die App geöffnet hat, und an die Person, die sie benutzt. Es gibt keine Umgebungs-ID zu übergeben, kein Token zu speichern und keine Adresse zu konfigurieren, sodass derselbe Build in jeder Umgebung funktioniert, in der er installiert ist. Eine App, die die HTTP-APIs der Plattform selbst aufruft, muss alle drei Dinge selbst lösen und macht sie falsch, sobald sie zum ersten Mal woanders installiert wird.

Die Runtime bekommen​

Eine benutzerdefinierte React-App liest sie aus einem Hook:

import { useParswareRuntime } from '@parsware/runtime-sdk';

export default function App() {
const runtime = useParswareRuntime();
// …
}

Ein benutzerdefiniertes Steuerelement bekommt dieselbe Oberfläche als Prop context:

export default function Control({ value, onChange, fieldMetadata, context }: ParswareControlProps) {
context.log('debug', 'mounted');
}

Außerhalb der Shell, in einem Unit-Test, umschließen Sie die Komponente mit ParswareRuntimeProvider und einem Stub: <ParswareRuntimeProvider runtime={stub}>.

Was die Runtime enthält​

ElementWas es ist
environmentId, environmentNameDie Umgebung, in der die App geöffnet wurde. Den Namen kann man anzeigen.
artifactIdDer logische Name der App oder des Steuerelements.
userDie Person, die es benutzt: id, name, email, tenantId, roles.
scopesWas die App angefordert hat, beschnitten auf das, was die Sitzung der Person enthält.
dataTabellen, Datensätze, Abfragen, gespeicherte Abfragen, benutzerdefinierte APIs, Berichte und Datensatzdateien, an die Umgebung gebunden.
storageObjektspeicher: Buckets und die Dateien darin.
logStrukturierte Protokollierung, die mit App und Umgebung an die Plattform geht.

Wer sie benutzt und was er darf​

runtime.user ist zum Anzeigen und Begrüßen da: ein Name, eine Rolle, nach der man ein Layout wählt. Es ist keine Berechtigungsprüfung.

Jeder Aufruf erfolgt als die Person, die die App benutzt. Ihre Sicherheitsrolle entscheidet, was jeder Aufruf lesen und ändern darf, genau wie in einer modellgesteuerten App. Eine App kann also nie mehr tun als die Person, die sie geöffnet hat, und zwei Personen, die dieselbe App benutzen, sehen jeweils, was sie sehen dürfen.

runtime.scopes sind die requestedScopes der App, beschnitten auf die Sitzung der Person. Lesen Sie sie, um wegzulassen, was nicht funktionieren wird:

const canSave = runtime.scopes.includes('storage.write');

Datensätze lesen​

Eine Seite einer Tabelle​

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 …

Eine Seite, nie die ganze Tabelle. { pageSize: 1 } ist der günstige Weg, nur eine Anzahl zu lesen.

Eine Abfrage: Spalten, Verknüpfungen und Summen​

runQuery nimmt dasselbe Abfragedokument, das die Raster der Plattform selbst senden, und kann deshalb Spalten wählen, Nachschlagefeldern folgen und aufsummieren:

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
}

Mit einem aggregate-Block antwortet sie mit Gruppen statt Zeilen (result.kind === 'Groups'). Bitten Sie den Server um Summen, statt jede Zeile zu holen und sie im Browser zu addieren.

Eine gespeicherte Abfrage, nach Namen​

Gehört die Frage zur Lösung und nicht zum Code der App, speichern Sie sie in der Lösung und führen Sie sie über ihren Namen aus:

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
}

Die Definition reist dann mit der Lösung und lässt sich verbessern, ohne einen neuen Build auszuliefern. Die Optionen können sie nur einschränken: Ein zusätzlicher filter wird mit dem gespeicherten UND-verknüpft, und page oder top begrenzen sie. Was sie liest, gruppiert und zählt, bleibt das der gespeicherten Abfrage.

Nachschlagefelder und Optionen​

  • searchLookup(table, { search }) findet Kandidaten für ein Nachschlagefeld, mit den Spalten, die die Nachschlageansicht der Zieltabelle zeigt. resolveLookup(table, id) macht aus einer gespeicherten ID wieder den anzuzeigenden Text.
  • Eine Optionsspalte kommt als Nummer der Option, mit ihrer Beschriftung daneben. values enthält die gespeicherte Nummer und formattedValues die Beschriftung, die ein Mensch liest – so, wie der Name eines Nachschlagefelds mitkommt. Gruppierte Ergebnisse tragen sie in formattedGroups. listOptionSets() liefert die ganze Liste, für eine Auswahl.

Datensätze ändern​

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 nimmt nur die Spalten, die sich ändern. Einen ganzen Datensatz zurückzuschicken überschreibt Werte, die jemand anderes inzwischen geändert haben kann. Server-Plug-in-Schritte laufen bei diesen Schreibvorgängen wie bei jedem anderen; die clientseitigen Geschäftsregeln eines Formulars nicht, denn es gibt kein Formular.

Eine benutzerdefinierte API aufrufen​

const overview = await runtime.data.callApi('GET', 'psl/v1/overview', { season: '2026' });

Der Pfad ist die Adresse der API unterhalb der Umgebung: Herausgeberpräfix, Version, Route. Bei GET und DELETE gehen die Eingaben in die Abfragezeichenfolge, sonst in den JSON-Body. Eine Ablehnung wird mit der eigenen Meldung der API ausgelöst. Eine App und ein externes System, die dieselbe API aufrufen, lesen dieselbe Antwort.

Objektspeicher​

Für Dateien, die keinem einzelnen Datensatz gehören: ein Export, den die App erstellt hat, ein Dokument, das ihr jemand gegeben hat, ein Stapel, den sie gleich verarbeitet.

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);
  • Ein Bucket gehört dem Mandanten, nicht der Umgebung. Zwei Umgebungen desselben Mandanten teilen ihn; nehmen Sie also die Umgebung in den Schlüssel auf, wenn ihre Dateien getrennt bleiben müssen.
  • Bucket-Namen werden über alle Mandanten hinweg geteilt, wie bei S3. Wählen Sie einen, der eindeutig Ihrer ist.
  • Fordern Sie storage.read und storage.write in den requestedScopes der App an.
  • Eine Datei, die einem einzelnen Datensatz gehört, kommt stattdessen an den Datensatz (nächster Abschnitt).

Dateien und Bilder eines Datensatzes​

Eine Spalte vom Typ File oder Image enthält eine kleine Datei, die diesem Datensatz gehört:

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');
  • Das Hochladen setzt die Spalte selbst. Setzen Sie das Feld nicht in createRecord oder updateRecord.
  • Der Datensatz muss zuerst existieren. Legen Sie ihn an, dann laden Sie hoch.
  • Es gibt keinen Link für <img src>. Die Datei ist wie ihr Datensatz berechtigt; zeigen Sie den Blob also über URL.createObjectURL(blob) an.

Tabellen und ihre Definitionen​

listEntities(), listFields(entityId), listViews(entityId), getViewDefinition(…), listForms(entityId), getFormDefinition(…), listRelationships(), listOptionSets() und getEntityStatus(entityId) beschreiben die Umgebung, für eine App, die ihr eigenes Raster oder Formular zeichnet. getRecordPermissions(table) sagt, ob die Person in einer Tabelle anlegen, lesen, aktualisieren oder löschen darf, sodass die App eine Schaltfläche weglassen kann, bevor sie abgelehnt wird. canImport sagt, ob sie eine Tabellenkalkulation hineinladen darf; dafür braucht es neben dem Anlegen auch das Recht „Daten importieren“.

Protokollierung​

runtime.log('info', 'Visitor list saved', { rows: 8 });
runtime.log('error', 'Could not load visitors', { message });

Die Stufen sind debug, info, warn und error. App und Umgebung werden für Sie angehängt. Protokollieren Sie nie Datensatzwerte, personenbezogene Daten oder etwas aus einem Token. Die Browserkonsole ist für den, der die App betreut, unsichtbar; das hier nicht.

Fehler behandeln​

Jeder Aufruf gibt ein Promise zurück, das abgelehnt werden kann: keine Berechtigung, ein inzwischen gelöschter Datensatz, ein ausgefallenes Netzwerk. Zeigen Sie die Meldung an, und setzen Sie keinen Zustand mehr, sobald die App geschlossen ist:

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]);

Was das SDK nicht tut​

  • Es versendet keine Benachrichtigungen. Es gibt keine Benachrichtigungsoberfläche; täuschen Sie keine mit einem direkten HTTP-Aufruf vor.
  • Es speichert nicht den Wert eines benutzerdefinierten Steuerelements. Ein Steuerelement übergibt über onChange, und das Formular speichert.
  • Es gibt keine Token heraus. Es gibt nichts zu lesen und nichts zu speichern.
  • Es erreicht keine anderen Umgebungen. Arbeit zwischen Umgebungen zu bewegen ist ein Export und Import einer Lösung.

Fehlerbehebung​

  • useParswareRuntime() wirft einen Fehler – die Komponente wurde außerhalb eines Runtime-Providers gerendert. In der Shell kann das nicht passieren; in einem Test umschließen Sie sie mit ParswareRuntimeProvider.
  • Die Runtime ist da, aber leer, oder Hooks schlagen fehl – @parsware/runtime-sdk oder React ist im shared-Block von Module Federation nicht singleton: true, sodass die App ihre eigene Kopie gebündelt hat.
  • Ein Aufruf wird abgelehnt – die Person, die die App benutzt, darf das nicht; ihre Sicherheitsrolle entscheidet. Prüfen Sie runtime.scopes und getRecordPermissions und lassen Sie die Aktion weg.
  • Eine Optionsspalte zeigt eine Zahl – lesen Sie für die Beschriftung formattedValues, nicht values.
  • Cannot find module …/dist/types unter Vitest – das SDK wurde aus einem älteren Download installiert. Installieren Sie den aktuellen.

Verwandte Szenarien​

  • Eine benutzerdefinierte React-App erstellen.
  • Ein benutzerdefiniertes Steuerelement erstellen.
  • Ein Zugriffstoken abrufen.