sichere-express-actions
v2.6.6
Published
Deklaratives Fullstack-Framework fuer sichere Node.js-Anwendungen
Readme
sichere-express-actions
Generisches Fullstack-Framework fuer sichere Mehrbenutzer-Anwendungen. Eine fachliche Datei erzeugt Datenbank, Express-API, React-Frontend, Tests und Dokumentation.
Kann
| Bereich | Automatisch enthalten |
|---|---|
| Datenbank | SQLite/WAL oder MySQL/InnoDB, Tabellen, Referenzen, Indizes, partielles Unique, Seeds und Migrationen |
| Backend | CRUD, eigene Fachactions, Transaktionen, Validierung, CORS, anpassbares Antwortformat und einheitliche 4xx/5xx-Fehler |
| Mehrbenutzer | Mutex, Optimistic Locking, 409-Konflikte, 423-Sperren, Vormerkungen und Ablaufzeiten |
| Rechte | Rollen, Status, Besitzer, Nicht-Besitzer und Vier-Augen-Regeln; standardmaessig geschlossen konfigurierbar |
| Prozesse | Workflows, bedingte Statuswechsel und resource-uebergreifende Effekte innerhalb der Transaktion |
| Read-Models | berechnete Felder wie frei, belegt oder anzahlOffen, ohne redundante Datenbankspalte |
| Frontend | Login, Navigation, Adminansichten, CRUD, Actions, Historie, Konfliktdialog, Sperren und SSE/Polling |
| Schnittstellen | freie Tabellen-, Feld- und API-Namen sowie eigene Express-Routen als Escape Hatch |
| Nachweise | ausfuehrbare HTTP-, Parallel-, SQL- und Rollback-Szenarien mit echten Ergebnisdateien |
| Dokumentation | Markdown, HTML, PDF, gerendertes ERD, Diagramme, API, Rechte, Tests, automatische UI-Screenshots und Validierung |
Es gibt keine eingebauten Auto- oder Vermietungsbegriffe. Dasselbe Framework passt fuer Lager, Termine, Tickets, Geraete, Fischtanks, Freigaben oder andere Fachgebiete.
Start
npx sichere-express-actions create meine-app
cd meine-app
npm install
npx playwright install chromium
npm run devFrontend: http://localhost:5173
Eine fachliche Datei
import { fileURLToPath } from 'node:url';
import { defineApp } from 'sichere-express-actions';
import { SQLiteAuditAdapter, sqlite } from 'sichere-express-actions/sqlite';
const database = sqlite(fileURLToPath(new URL('./data.db', import.meta.url)));
export default defineApp({
name: 'Geraeteverleih',
database,
audit: new SQLiteAuditAdapter(database),
api: { prefix: '/api', eventsPath: '/ereignisse' },
auth: { resource: 'account', username: 'name', password: 'passwort', role: 'rolle' },
resources: {
account: {
fields: {
name: { type: 'string', required: true, unique: true },
passwort: { type: 'string', required: true, sensitive: true },
rolle: ['mitarbeiter', 'admin'],
},
},
geraet: {
fields: {
bezeichnung: 'string!',
status: { enum: ['frei', 'verliehen'], default: 'frei', readonly: true },
},
optimisticLock: true,
audit: true,
live: { mode: 'both', intervalMs: 5000 },
computed: {
frei: { type: 'boolean', query: { resource: 'ausleihe', where: { geraetId: '$record.id', ende: null }, aggregate: 'notExists' } },
},
},
ausleihe: {
fields: {
geraetId: 'ref:geraet!',
ende: { type: 'datetime', optional: true, nullable: true, readonly: true },
accountId: 'ref:account!',
},
optimisticLock: true,
tracking: { actorResource: 'account' },
audit: true,
editingLock: { expiresIn: '10m', actorResource: 'account' },
workflow: {
initial: 'aktiv',
endpoint: { path: '/status', field: 'status' },
transitions: {
zuruecknehmen: {
from: 'aktiv', to: 'abgeschlossen', roles: ['mitarbeiter', 'admin'],
effects: [{ resource: 'geraet', id: '$record.geraetId', values: { status: 'frei' } }],
},
},
},
},
},
actions: {
ausleihen: {
input: { geraetId: { ref: 'geraet', required: true, lock: true } },
roles: ['mitarbeiter', 'admin'],
transaction: true,
create: { resource: 'ausleihe', values: { geraetId: '$input.geraetId', accountId: '$user.id', ende: null, status: 'aktiv' } },
effects: [{ resource: 'geraet', id: '$input.geraetId', values: { status: 'verliehen' } }],
},
},
seed: {
account: [{ name: 'admin', passwort: '1234', rolle: 'admin' }],
geraet: [{ bezeichnung: 'G-001', status: 'frei' }],
},
});Kurztypen: string!, int!, number, boolean, email, date, datetime, ref:name! und ['a', 'b'].
api.prefix legt den API-Basispfad fest. api.eventsPath legt den SSE-Pfad relativ dazu fest; ohne Angabe bleibt er bei /events.
cors: true erlaubt jedem Frontend den Zugriff auf die API - das ist fuer eine getrennt laufende Entwicklungsoberflaeche gedacht. Fuer den Betrieb gehoert eine konkrete Herkunft hin:
cors: { origin: ['https://app.example'], credentials: false, maxAge: 600 },origin akzeptiert '*', eine feste Herkunft, eine Liste, einen regulaeren Ausdruck oder eine eigene Pruefung; credentials: true ist mit '*' nicht erlaubt und wird bereits beim Start abgelehnt. Preflights (OPTIONS) werden vor allen Routen mit 204 beantwortet.
api.response formt fachliche Antworten um, zum Beispiel fuer einen aelteren Client, der andere Felder erwartet:
api: {
response: ({ kind, resource, operation, action, transition, result, status, req }) => (
kind === 'resource' && operation === 'update' ? { ok: true, ...result } : undefined
),
},undefined laesst die Standardantwort unveraendert. /meta und der SSE-Stream bleiben unberuehrt, damit die generierte Oberflaeche weiterarbeitet.
Eine Action meldet ihr Ergebnis mit publish - ein Event oder eine Liste. Aendert eine eigene execute-Funktion nebenbei eine weitere Resource, meldet emit das an:
execute: async ({ input, repositories, emit }) => {
const auto = await repositories.auto.updateWhere(input.autoId, { status: 'frei' }, { status: 'vermietet' });
emit({ topic: 'auto.changed', data: { operation: 'rueckgabe', resource: 'auto', value: auto } });
return ...;
},Beide Wege veroeffentlichen erst nach dem Commit: Ein Rollback verschickt keine Meldung ueber eine Aenderung, die es nie gab.
Ein Feld mit writeOnly: true geht durch das generierte Formular hinein, kommt aber nie aus der API zurueck - fuer Passwoerter und andere Geheimnisse. Beim Aendern darf es fehlen und bleibt dann unveraendert; Tabellen und Dokumentation zeigen es nie:
passwort: { type: 'string', required: true, writeOnly: true, min: 8 },Wirkungen auf eine zweite Resource stehen bei effects - nicht nur bei Workflow-Uebergaengen, sondern auch bei create, update und delete. Sie laufen in derselben Transaktion, ihre Live-Meldung erst nach dem Commit:
effects: {
create: [{ resource: 'geraet', id: '$record.geraetId', values: { status: 'verliehen' } }],
update: [{
resource: 'geraet', id: '$previous.geraetId', values: { status: 'frei' },
when: { ne: ['$previous.geraetId', '$record.geraetId'] },
}],
},$previous ist der Stand vor der Aenderung, $record der danach. Wer aus eigenem Code melden will, sobald wirklich gespeichert ist, verwendet database.afterCommit(() => events.publish(...)); ausserhalb einer Transaktion laeuft der Rueckruf sofort. defaultDirection: 'desc' dreht die Standardsortierung einer Liste um.
Ein Speichern ohne fachliche Aenderung schreibt keinen Protokolleintrag. Version, Zeitstempel, letzte Person und Sperrfelder zaehlen dabei nicht als Aenderung; wer trotzdem jedes Speichern festhalten will, setzt audit: { skipUnchanged: false }.
Fehler nehmen immer denselben Weg: Validierungsfehler (400), kaputtes JSON (400), zu grosse Bodies (413), Rechte-, Konflikt- und Sperrfehler landen als HttpError in der Fehlerkette. Eine eigene Fehler-Middleware aus configure({ app }) sieht sie deshalb alle und kann sie in einem einzigen Format beantworten.
Befehle
npm run dev # Backend und generiertes Frontend
npm run seed # definierter Anfangszustand
npm test # Smoke- und Szenariotests mit echten Nachweisen
npm run build # auslieferbares Frontend
npm run docs # Diagramme, API und Projektdokumentation
npm run sql # SQLite-Konsole
npm run db:update # sichere additive Migration
npx sichere-actions browser # Mehrbenutzer-UI-Nachweise mit echten BrowserfensternMehrbenutzer-UI-Nachweise (sichere-actions browser)
Fuer Ablaeufe, die sich nicht als HTTP-Szenario, sondern nur als sichtbares
Zusammenspiel mehrerer angemeldeter Personen zeigen lassen (zwei Personen
bearbeiten gleichzeitig denselben Datensatz, eine Sperre wird uebernommen
oder abgewiesen, ...): tests.browserScenarios in der Konfiguration, dann
npm run dev # die Anwendung muss laufen
npx sichere-actions browserJedes Szenario oeffnet pro Akteur ein eigenes, echtes Browserfenster
(nebeneinander angeordnet), meldet sich mit dessen Zugangsdaten an und
arbeitet die steps der Reihe nach ab. Jeder Schritt erzeugt automatisch
einen Screenshot als Nachweis:
tests: {
browserScenarios: [{
id: 'B-01', title: 'Eine fremde aktive Sperre laesst sich nicht uebernehmen',
actors: { hans: { username: 'hans', password: '1234' }, admin: { username: 'admin', password: '1234' } },
steps: [
{ actor: 'hans', do: 'login' },
{ actor: 'hans', do: 'click', selector: '[data-nav-resource="vermietung"]' },
{ actor: 'hans', do: 'click', selector: 'tr[data-record-id="1"] button:has-text("Bearbeiten")' },
{ actor: 'hans', label: 'formular-offen', selector: '[data-resource-form="vermietung"][data-form-mode="edit"]', expect: 'visible' },
{ actor: 'admin', do: 'login' },
{ actor: 'admin', do: 'click', selector: '[data-nav-resource="vermietung"]' },
{ actor: 'admin', label: 'bearbeiten-deaktiviert', selector: 'tr[data-record-id="1"] button:has-text("Bearbeiten")', expect: 'disabled' },
],
}],
},Schritt-Aktionen (do): login, goto, click, fill, check, uncheck,
select (per value oder index), waitFor, wait, capture (Attribut
oder Text in eine Variable merken), screenshot. Erwartungen (expect):
visible, hidden, disabled, enabled, text (mit contains), count.
Selektoren duerfen volle Playwright-Syntax nutzen (:has-text(...), >>,
...) - so frei wie eine direkte Playwright-Automatisierung, nur deklarativ in
der Konfiguration. {{name}} in selector/value/text wird durch zuvor
mit capture gemerkte Werte ersetzt, akteur- und szenarioweit geteilt.
Ergebnis: docs/generated/browser-tests/bericht.md mit allen Screenshots.
npm run docs bindet diesen Bericht automatisch als eigenes Kapitel
"UI-Mehrbenutzertests" in dokumentation.pdf, .docx, .html und .md ein
- die Erkenntnisse aus einem manuell entdeckten Fall werden so Teil der Abgabe, mit demselben Nachweis, den ein Mensch beim Zusehen haette.
sichere-actions test verwendet eine eigene temporaere SQLite-Datei und entfernt sie auch nach Fehlern. Bei eigenen DB-Closures (zum Beispiel Custom-Audit oder Repository-Patches) muss die Config als Factory exportiert werden, die den uebergebenen database-Adapter verwendet; dann wird die Produktivdatei beim Testlauf nicht geoeffnet.
Szenario-Erwartungen fuer HTTP-Antworten, SQL-Ergebnisse und SSE-Nutzdaten duerfen als Liste kombiniert werden. { path: 'feld', absent: true } verlangt, dass ein Feld fehlt. expect.headers prueft Antwort-Header: 'name': 'wert' vergleicht exakt, { includes: 'teil' } prueft eine Teilzeichenkette und null verlangt, dass der Header fehlt. Parallelschritte koennen Benutzer zyklisch verteilen; cleanup-Schritte laufen auch nach Fehlern. Ein SSE-Schritt verbindet sich zuerst, begrenzt Verbindung und Trigger zeitlich und loest dann seinen HTTP-Request aus.
Der generierte Editor versucht Bearbeitungssperren beim Speichern, Abbrechen, Datensatzwechsel, Navigieren, Abmelden und Seitenende freizugeben; bei einem Netzfehler bleibt der konfigurierte TTL die Rueckfallebene. Bei HTTP 409 uebernimmt „Neu laden“ die aktuellen Werte samt Version ins offene Formular. acquireOverrideRoles und releaseOverrideRoles koennen bei Bearbeitungssperren und Vormerkungen die Uebernahme und das administrative Freigeben getrennt steuern; ohne Angabe gilt weiterhin overrideRoles.
Dokumentation
Ein Befehl erzeugt die technische Abgabe aus derselben fachlichen Konfiguration:
npm run docsFertig ist insbesondere:
docs/generated/dokumentation.pdfZusaetzlich bleiben dokumentation.md, dokumentation.html, das PlantUML-ERD, ein gerendertes SVG, Resource-, API-, Workflow-, Action-, Rechte- und Testdokumente erhalten. reports/generation.md nennt echte Anzahlen, Warnungen und alles, was noch manuell fehlt.
Screenshots
Das Framework startet Backend und generiertes Frontend selbst auf freien Ports. Es nimmt nur wirklich vorhandene Ansichten auf: Anmeldung, Anwendungsuebersicht, Resource-Listen, erreichbare Create-/Edit-/History-Ansichten und Action-Formulare. Screenshots werden fachlich beim passenden Kapitel eingebunden. Alte generierte Screenshots entfernt der naechste Lauf ueber sein Manifest.
Chromium wird einmal pro Rechner installiert:
npx playwright install chromiumDokumentationsdaten und Login
Die Aufnahme laeuft mit einer eigenen SQLite-Datenbank. Standardmaessig ist das eine In-Memory-Datenbank in der Reihenfolge documentation.fixtures, vorhandene Seeds, danach sicher ableitbare Beispieldaten.
Wer den aktuellen Datenbestand dokumentieren will, stellt auf documentation: { data: 'live' } um oder ruft sichere-actions docs --data live auf. Dann arbeitet der Lauf auf einer frischen Kopie der konfigurierten Datenbankdatei (VACUUM INTO), sodass Screenshots und Listen genau den Stand zeigen, der beim Generieren vorhanden ist. Angemeldet wird mit dem Konto aus dem Seed, bei echten Daten bevorzugt mit einem Konto der Rolle admin, damit auch geschuetzte Ansichten aufgenommen werden. documentation: { account: 'benutzername' } waehlt das Konto ausdruecklich. Werte aus sensitive-Feldern der echten Datenbank fliessen zusaetzlich in die Validierung ein, damit kein Passwort im Dokument landet. Die echte Projekt- oder Produktionsdatenbank wird in beiden Faellen nur gelesen, nie beschrieben. Ist tests.createConfig vorhanden, baut die Dokumentation die Konfiguration damit fuer die isolierte Datenbank neu - eigene Audit-Adapter, Repository-Erweiterungen und Ereignisbusse sind so auch im Dokumentationslauf aktiv.
Die automatischen Tests des Dokumentationslaufs starten auf tests.fixtures beziehungsweise seed, also demselben Bestand wie sichere-actions test; die Screenshots verwenden weiterhin documentation.fixtures. Das Testprotokoll wird nach diesem Lauf geschrieben und enthaelt dessen Ergebnisse.
Wichtig fuer eigene Adapter: Ein Audit-Adapter, ein Repository-Wrapper oder ein Ereignisbus, den die Konfiguration in einer Closure festhaelt, schreibt weiter in die Datenbank, mit der er gebaut wurde. sichere-actions test und sichere-actions docs koennen das nur ueber tests.createConfig aufloesen - ohne diese Factory landen solche Schreibzugriffe in der Produktionsdatenbank. Die Config deshalb als Factory exportieren, die den uebergebenen database-Adapter durchgaengig verwendet. Standard-Auth wird mit einem isolierten Seed-Konto automatisch bedient; Passwoerter, Tokens, Cookies und Storage-State werden nie unter docs/ gespeichert.
Nur bei einer vollstaendig eigenen Anmeldung ist ein Hook noetig:
documentation: {
screenshots: {
async setup(page) {
// individuelle Anmeldung
},
},
}Ausgabe und Sicherheit
npm run docs erzeugt und prueft:
docs/generated/
dokumentation.md
dokumentation.html
dokumentation.pdf
dokumentation.docx
architecture/erd.puml
architecture/erd.svg
diagrams/*.svg
diagrams/*.png
api/endpoints.md
security/rights.md
resources/*.md
workflows/*.md
actions/*.md
screenshots/**
reports/tests.md
reports/generation.mddokumentation.pdf, dokumentation.docx, dokumentation.html und dokumentation.md enthalten denselben vollstaendigen Inhalt: Uebersicht, Resources, API, Rechte, Workflows, Actions, Audit, Events, Teststatus, alle Diagramme als Bild, alle Screenshots und alle Einzeldokumente (Datenbank, Konflikte, Mehrbenutzeranalyse, Transaktionen, Sperrstrategie, Live-Updates, Architekturentscheide, W-Fragen, Nachweise, Testspezifikation, Testprotokoll). Damit ist jede der vier Dateien allein abgabefaehig.
Quelltext bleibt bewusst draussen: PlantUML-Quellen, OpenAPI und die Postman-Collection liegen als Dateien daneben, statt Seiten im Dokument zu fuellen. Wer sie doch abdrucken will, setzt documentation: { includeSources: true }.
Eigene Dokumente einbinden
Antworten, die niemand automatisch fuer dich erzeugen kann - eine Konfliktanalyse, Begruendungen, Beobachtungen aus einem Testlauf - schreibst du selbst als Markdown-Datei irgendwo im Projekt. documentation.extraDocuments bindet sie als eigenes Kapitel ein:
documentation: {
extraDocuments: [
{ file: 'werkstatt.md', title: 'Werkstatt' },
'weitere-notiz.md', // Titel wird aus dem Dateinamen abgeleitet
],
},Lokale Bilder darin () werden mitkopiert; die Datei bleibt dabei auch fuer sich allein gueltig (z. B. direkt in einem Editor geoeffnet). Eine fehlende Datei bricht den Lauf nicht ab, sondern erscheint als Warnung im Generation Report.
Jede PlantUML-Quelle wird ohne Java als SVG gezeichnet - Sequenz-, Zustands-, Klassen-, Entity- und Komponentendiagramme. Fuer Word werden dieselben Diagramme zusaetzlich als PNG gerastert, weil Word SVG nicht zuverlaessig einbettet. Die Word-Datei entsteht ohne zusaetzliche Abhaengigkeit.
Die Validierung kontrolliert Bilder, relative Links, PDF/SVG/DOCX, unsichere absolute Pfade und bekannte sensitive Werte. Am Schluss zeigt die CLI kompakt, was automatisch erstellt wurde und welche menschlichen Angaben wie Autor oder Projektbeschreibung noch fehlen.
Pruefungsworkflow
Modell und individuelle Fachlogik definieren
-> npm test
-> npm run docs
-> docs/generated/dokumentation.pdf oder dokumentation.docx abgebenFrei erweiterbar
Vollstaendige field(), resource() und action()-Definitionen, eigene Funktionen, extend(), configure(), eigene Express-Routen und eigene React-Komponenten bleiben moeglich. Die Kurzform vereinfacht Standardfaelle, sie begrenzt die Anwendung nicht.
Installation ohne Starter:
npm install sichere-express-actions express zodLizenz: MIT
