feat(jts-02): groups.service.ts binden, Transaktionen tragfaehig machen, Absicherung sehend machen
prisma-tenant.extension.ts bekommt withTenantTransaction(prisma, tenantId, fn) — die interaktive Transaktion auf dem UNgebundenen Client mit set_config als erster Anweisung direkt auf tx, die in Aufgabe 1 als einzige der drei gemessenen Formen sowohl die Einzelmessung als auch eine Lastprobe unter echter Nebenlaeufigkeit bestand (die Array-Form auf dem gebundenen Client verteilt jede Operation auf eine eigene Teiltransaktion; die interaktive Form auf dem gebundenen Client brach unter 40 parallelen Aufrufen mit P2028 ab). rls-access-inventory.spec.ts bekommt eine dritte Erkennung fuer Modellzugriffe ueber den Rueckgabeparameter einer interaktiven Transaktion (zwei Formen: direkter Empfaenger.$transaction(async...) und das neue Hilfsmittel withTenantTransaction(...)) — macht das Paar (groups.service.ts, tenantModuleActivation) erstmals sichtbar, das bislang keine Pruefung dieses Projekts je gesehen hat. groups.service.ts: alle zwoelf Methoden inklusive der drei Transaktionen (update() isDefault:true, reassignDefaultBeforeDelete(), ensureDefaultGroup()) laufen jetzt ueber den Mandantenkontext. Zaehler und Transaktion in ensureDefaultGroup() sind gemeinsam gebunden (T-JTS-05). addUserToDefaultGroup() prueft neu, dass der Zielbenutzer zum Mandanten gehoert (Befund E, T-JTS-02) — die Regel auf GroupMembership prueft nachweislich nur die Gruppenseite. groups.service.spec.ts bekommt zwei unterscheidbare Clients ueber demselben Speicher-Fake (Muster aus 260909-ipc, auf die interaktive Form uebertragen) und 13 neue Bindungsnachweise; alle 42 Bestandstests bleiben gruen. Klassifikationsdokument nachgezogen. 737 Tests und die Typpruefung gruen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
This commit is contained in:
@@ -55,6 +55,51 @@ import { PrismaClient } from '@prisma/client';
|
||||
* Parametrisiert ueber ein getaggtes `$executeRaw`-Template (kein
|
||||
* `$executeRawUnsafe` mit zusammengebautem Text mehr) — die
|
||||
* Injektionsfestigkeit aus T-02-05 bleibt beim Umbau erhalten.
|
||||
*
|
||||
* ERNEUTE PRUEFUNG FUER ETAPPE 2, BEREICH `groups` (260909-jts, gemessen
|
||||
* 2026-09-09 gegen die echte Datenbank, siehe Aufgabe 1 in
|
||||
* `.planning/quick/260909-jts-.../260909-jts-PLAN.md` und den Abschnitt
|
||||
* "Bereich groups" in `docs/mandantentrennung-etappe2-fehlerrichtung.md`):
|
||||
* `groups.service.ts` ist die einzige Datei im gesamten API-Quelltext mit
|
||||
* einer interaktiven Callback-Transaktion (`ensureDefaultGroup`), dazu zwei
|
||||
* Array-Transaktionen (`update`, `reassignDefaultBeforeDelete`) — genau der
|
||||
* im Absatz oben benannte neue Fall. Ergebnis der Messung, mit
|
||||
* `pg_backend_pid()` und `current_tenant_id()` je Teilschritt:
|
||||
*
|
||||
* Form (i) — Array-Form auf dem gebundenen Client: FAELLT DURCH. Zwei
|
||||
* Teilschritte liefen auf ZWEI verschiedenen Verbindungen
|
||||
* (`step1.pid=276749`, `step2.pid=276750`) — exakt die im
|
||||
* Absatz oben beschriebene Aufspaltung in mehrere
|
||||
* Teiltransaktionen. Jeder Teilschritt sah zwar noch den
|
||||
* richtigen Kontext (kein Datenleck), aber die Atomaritaet
|
||||
* der aeusseren Transaktion ist nicht mehr gegeben.
|
||||
* Form (ii) — interaktive Callback-Form auf dem gebundenen Client
|
||||
* (`forTenant(prisma, tenantId).$transaction(async (tx) => ...)`):
|
||||
* bestand die Einzelmessung (gleiche Verbindung, richtiger
|
||||
* Kontext, richtige Zeilenzahl), brach aber unter einer
|
||||
* zusaetzlichen Lastprobe (40 parallele Aufrufe,
|
||||
* alternierend TENANT-A/TENANT-B) mit
|
||||
* `PrismaClientKnownRequestError: Transaction API error:
|
||||
* Unable to start a transaction in the given time.` (P2028)
|
||||
* ab — jeder `tx.$queryRaw`-Aufruf innerhalb der interaktiven
|
||||
* Transaktion loest selbst wieder eine VERSCHACHTELTE
|
||||
* Array-Transaktion aus (weil `$allOperations` bei jedem
|
||||
* Aufruf erneut feuert), und die aeussere plus jede innere
|
||||
* Verschachtelung belegen gleichzeitig eine Verbindung aus
|
||||
* demselben, endlichen Pool.
|
||||
* Form (iii) — interaktive Callback-Form auf dem UNGEBUNDENEN Client,
|
||||
* `set_config` als ERSTE Anweisung direkt auf `tx` (nicht auf
|
||||
* dem aeusseren Client): bestand sowohl die Einzelmessung als
|
||||
* auch die Lastprobe (0 Verletzungen unter 40 parallelen
|
||||
* Aufrufen) — sie belegt pro Aufruf genau EINE Verbindung,
|
||||
* ohne Verschachtelung.
|
||||
*
|
||||
* Entscheidung: `withTenantTransaction()` unten baut Form (iii) nach und
|
||||
* ist das Hilfsmittel fuer alle mehrschrittigen, mandantengebundenen
|
||||
* Aenderungen dieses Bereichs. Fuer den naechsten Bereich mit einer eigenen
|
||||
* Transaktion gilt weiterhin: vor jedem neuen Fall erneut pruefen, nicht
|
||||
* von hier abschreiben — eine andere Lastform oder ein anderer Pool koennte
|
||||
* ein anderes Ergebnis liefern.
|
||||
*/
|
||||
export function forTenant(prisma: PrismaClient, tenantId: string) {
|
||||
return prisma.$extends({
|
||||
@@ -70,3 +115,32 @@ export function forTenant(prisma: PrismaClient, tenantId: string) {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fuehrt `fn` als EINE mehrschrittige, mandantengebundene Transaktion aus
|
||||
* (260909-jts, Befund A/Aufgabe 1). Anders als `forTenant()` bindet diese
|
||||
* Funktion NICHT ueber `$extends`/`$allOperations`, sondern oeffnet direkt
|
||||
* eine interaktive Transaktion auf dem UNGEBUNDENEN Basisclient und setzt
|
||||
* `app.current_tenant` als allererste Anweisung ueber ein getaggtes
|
||||
* Roh-Template DIREKT AUF `tx` — nicht auf `prisma`. Jede weitere Anweisung
|
||||
* innerhalb von `fn` bekommt denselben `tx`-Parameter uebergeben und laeuft
|
||||
* dadurch auf DERSELBEN Verbindung wie das `set_config` davor.
|
||||
*
|
||||
* Parametrisiert wie `forTenant()` (getaggtes Template, kein
|
||||
* zusammengebauter Text — T-02-05 bleibt erhalten).
|
||||
*
|
||||
* Fuer Einzeloperationen bleibt `forTenant()` das richtige Werkzeug; dieses
|
||||
* Hilfsmittel ist ausschliesslich fuer Aufrufstellen gedacht, die mehrere
|
||||
* Schritte als EINE Transaktion brauchen (Array- oder interaktive
|
||||
* Callback-Form).
|
||||
*/
|
||||
export function withTenantTransaction<T>(
|
||||
prisma: PrismaClient,
|
||||
tenantId: string,
|
||||
fn: (tx: any) => Promise<T>,
|
||||
): Promise<T> {
|
||||
return (prisma as any).$transaction(async (tx: any) => {
|
||||
await tx.$executeRaw`SELECT set_config('app.current_tenant', ${tenantId}, true)`;
|
||||
return fn(tx);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user