npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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 dev

Frontend: 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 Browserfenstern

Mehrbenutzer-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 browser

Jedes 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 docs

Fertig ist insbesondere:

docs/generated/dokumentation.pdf

Zusaetzlich 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 chromium

Dokumentationsdaten 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.md

dokumentation.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 (![Konfliktmeldung](bilder/konflikt.png)) 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 abgeben

Frei 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 zod

Lizenz: MIT