Init: RoggioApp Architecture, Prisma Schema, API MVP
This commit is contained in:
Executable
BIN
Binary file not shown.
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
+9
@@ -0,0 +1,9 @@
|
|||||||
|
# RoggioApp - Vision & Kontext
|
||||||
|
|
||||||
|
## Die Grundidee (Ganz von vorne)
|
||||||
|
* **Use-Case:** Verwaltung einer Ferienwohnungs-Vermietung.
|
||||||
|
* **Betreiber-Struktur:** Das System wird nicht von einer klassischen "Firma" oder einem "Hotel-Manager" bedient, sondern von einem **Kollektiv**.
|
||||||
|
|
||||||
|
## Wichtige Implikationen (Claras Notizen)
|
||||||
|
* *Rechte & Rollen:* Kollektiv bedeutet oft: flachere Hierarchien, geteilte Verantwortung, Transparenz. Ein starrer Admin/User-Schnitt (wie im alten System) passt hier vielleicht nicht. Jeder im Kollektiv muss vermutlich sehen, wer wann da ist und welche Aufgaben anstehen (z.B. Putzen, Check-In).
|
||||||
|
* *Gäste-Kommunikation:* Die Interaktion mit den Gästen ist wahrscheinlich persönlicher oder kollaborativer als in einem Standard-Hotel.
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
# RoggioApp - Real World & Ökosystem
|
||||||
|
|
||||||
|
## 1. Die physische Welt (Real World / RW)
|
||||||
|
Das System muss eine flexible, organisch wachsende Infrastruktur abbilden können:
|
||||||
|
* **Gelände/Grundstück:** Viel Fläche, die sich entwickelt.
|
||||||
|
* **Bestand:**
|
||||||
|
* 2 feste Häuser.
|
||||||
|
* 1 alter Zirkuswagen.
|
||||||
|
* **Zukunft / Flexibilität:**
|
||||||
|
* Camping-Möglichkeiten.
|
||||||
|
* Platz für zukünftige Unterkünfte.
|
||||||
|
* **Nutzungseinheiten (Granularität):**
|
||||||
|
* In den Häusern gibt es verschiedene Wohnungen.
|
||||||
|
* Es gibt Funktionsräume und Gemeinschaftsräume/Flächen.
|
||||||
|
* *Kernanforderung:* Diese Einheiten müssen in verschiedenen Kombinationen vermietet oder belegt werden können (z.B. Wohnung A + Gemeinschaftsraum, oder nur Zirkuswagen).
|
||||||
|
|
||||||
|
## 2. Die digitale Welt (Ecosystem)
|
||||||
|
Die neue Applikation existiert nicht im luftleeren Raum, sondern ist Teil einer Dreifaltigkeit:
|
||||||
|
1. **Werbung & Frontend (Wordpress):** Externe Website für Marketing, Gästeinformationen und als Einstiegspunkt für Buchungsanfragen.
|
||||||
|
2. **Dateien & Kommunikation (Nextcloud):** Bestehende Instanz für interne Datenverwaltung und Team-Kommunikation.
|
||||||
|
3. **Verwaltung (RoggioApp):** Die neu zu bauende Kern-Applikation für Buchungen, Aufgaben (Tasks/Cleaning) und Finanzen.
|
||||||
|
|
||||||
|
## 3. Architektur-Ideen & Schnittstellen
|
||||||
|
* **Single Sign-On (SSO):** Nextcloud als zentrales Auth-Backend (OIDC / SAML) für alle Tools, um die Benutzerverwaltung für das Kollektiv zentral an einem Ort zu halten.
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
# RoggioApp - Datenarchitektur & Normalisierung
|
||||||
|
|
||||||
|
## 1. Die Grundphilosophie (Single Source of Truth)
|
||||||
|
* **Keine Redundanz:** Keine doppelte Datenhaltung und (noch wichtiger) keine doppelte Dateneingabe.
|
||||||
|
* **Ableitung statt Hardcoding (Computed Values):**
|
||||||
|
* Die Fläche einer Wohnung wird *nicht* als eigenes Feld in der Datenbank eingetragen. Sie wird berechnet (Summe der Flächen aller Räume, die zur Wohnung gehören).
|
||||||
|
* Die Schlafplatzkapazität einer Einheit wird *nicht* als Zahl eingetragen. Sie ergibt sich aus: `Einheit -> hat Räume (Kategorie: Schlafzimmer) -> hat Betten -> Bett hat Schlafplätze (1 oder 2) = Summe X`.
|
||||||
|
|
||||||
|
## 2. Granularität & Hierarchie
|
||||||
|
Alles im physischen Raum ist eine "Unit" (Einheit), die zueinander in Beziehung steht und strikt kategorisiert sein muss.
|
||||||
|
|
||||||
|
**Beispiel-Hierarchie für Kapazitäten:**
|
||||||
|
1. **Unit: Wohnung** (z.B. Haus A, Wohnung 1)
|
||||||
|
2. --> besteht aus **Unit: Raum** (z.B. Zimmer 1, Kategorie: Schlafzimmer)
|
||||||
|
3. ----> enthält **Inventar/Unit: Bett** (Kategorie: Doppelbett, Kapazität: 2 Personen)
|
||||||
|
4. --> besteht aus **Unit: Raum** (z.B. Zimmer 2, Kategorie: Schlafzimmer)
|
||||||
|
5. ----> enthält **Inventar/Unit: Bett** (Kategorie: Einzelbett, Kapazität: 1 Person)
|
||||||
|
|
||||||
|
*Ergebnis:* Das System weiß automatisch, dass die Wohnung 3 Schlafplätze hat. Wenn ein Einzelbett ins Zimmer gestellt wird, erhöht sich die Kapazität der Wohnung automatisch auf 4, ohne dass jemand das Datenfeld "Wohnung_MaxPax" ändern muss.
|
||||||
|
|
||||||
|
## Claras Architektonische Ableitung
|
||||||
|
Diese Anforderung schreit nach einem strikten, relationalen Baum-Modell (Tree/Graph) in PostgreSQL.
|
||||||
|
* *Vorteil:* Absolute Konsistenz. Es gibt keine Widersprüche (z.B. "Wohnung hat 5 Betten, aber im System steht Kapazität 2").
|
||||||
|
* *Herausforderung (auf die wir achten müssen):* Solche "Bottom-Up"-Berechnungen (Summen aus der Tiefe der Datenbank holen) können in SQL sehr komplex (viele JOINs) werden, wenn man z.B. nur schnell filtern will: "Zeig mir alle Wohnungen für 4 Personen".
|
||||||
|
* *Lösungansatz:* Wir nutzen das normalisierte Modell für die *Struktur*, aber arbeiten beim Abfragen vielleicht mit Datenbank-Views (Materialized Views) oder Prisma-Computed-Fields, damit das Frontend nicht endlos rechnen muss.
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
# RoggioApp - Die Ultimative Abstraktion (Einheiten & Eigenschaften)
|
||||||
|
|
||||||
|
## 1. Die physische Welt ist ein Graph aus "Einheiten" (Units)
|
||||||
|
Es gibt keine festen, harten Tabellen wie `Wohnungen`, `Räume` oder `Parkplätze`.
|
||||||
|
Alles ist eine grundlegende **Unit** (Einheit) auf dem Gelände.
|
||||||
|
|
||||||
|
### Was definiert eine Unit?
|
||||||
|
Eine Unit ist lediglich ein Knotenpunkt (Node), der über seine Eigenschaften (Properties/Traits) und seine Verbindungen (Parent/Child) definiert wird.
|
||||||
|
|
||||||
|
**Beispiele für Einheiten (Units):**
|
||||||
|
* Das Grundstück selbst (Unit)
|
||||||
|
* Das Haus A (Unit, liegt auf dem Grundstück)
|
||||||
|
* Ein Zirkuswagen (Unit)
|
||||||
|
* Ein Badezimmer (Unit, liegt in Haus A)
|
||||||
|
* Ein Stück Rasen (Unit)
|
||||||
|
|
||||||
|
### Die Eigenschaften (Traits / Properties)
|
||||||
|
Was die Unit *ist* oder was man mit ihr *tun* kann, wird über zugewiesene Eigenschaften bestimmt:
|
||||||
|
* `is_rentable` (RentalUnit): Die Einheit kann gebucht werden (z.B. Wohnung, Zirkuswagen, Veranstaltungsraum, Parkplatz).
|
||||||
|
* `needs_service` (Serviceable): Die Einheit erfordert Reinigung oder Instandhaltung (gilt für Mietobjekte, aber eben auch für Flure oder den privaten Putzraum).
|
||||||
|
* `has_capacity` (Capacity): Definiert, wie viele Schlafplätze (oder Sitzplätze beim Veranstaltungsraum) sich durch die Untereinheiten ergeben.
|
||||||
|
|
||||||
|
## 2. Die Akteure (Personen & Gruppen)
|
||||||
|
Auch hier wird stark normalisiert, um Doppelungen bei den Personendaten zu vermeiden (z.B. wenn Peter dieses Jahr mit Familie A kommt und nächstes Jahr mit Verein B).
|
||||||
|
|
||||||
|
* **Person (Die Basis):**
|
||||||
|
* *Eindeutig / Fest:* Vorname, Nachname, Geburtsdatum, Ausweisnr., Mail, Mobil, Foto.
|
||||||
|
* *Kategorisierung (Eigenschaft):* Ist diese Person Kollektiv-Mitglied, Support-Gang, Personal oder Gast/Kunde? (Kann sich überlappen!).
|
||||||
|
* **Adresse (Die Location):**
|
||||||
|
* *Nicht eindeutig / Shared:* Straße, Hausnummer, PLZ, Stadt, Land. (Mehrere Personen können dieselbe Adresse referenzieren).
|
||||||
|
* **Gruppe (Die Entität, die bucht):**
|
||||||
|
* Besteht aus $N$ Personen.
|
||||||
|
* *Variabel:* Person X kann Mitglied in Gruppe A (Familienurlaub) und Gruppe B (Yoga-Retreat) sein.
|
||||||
|
|
||||||
|
## 3. Die Transaktionen (Buchungen & Aktionen)
|
||||||
|
* **Buchungen (Bookings):**
|
||||||
|
* Verbinden eine **Gruppe** mit einer **RentalUnit** (Unit mit `is_rentable=true`) über einen bestimmten Zeitraum.
|
||||||
|
* **Aktionen (Tasks / Operations):**
|
||||||
|
* Putzen, Einkaufen, Abholen.
|
||||||
|
* Können an eine Buchung geknüpft sein (Endreinigung nach Gruppe A).
|
||||||
|
* Oder an eine Unit (Grundreinigung des Flurs, der `needs_service` ist, aber nicht `is_rentable`).
|
||||||
|
|
||||||
|
## Claras architektonische Ableitung
|
||||||
|
Das ist ein Entity-Component-System (ECS) oder ein Node-Graph, wie man ihn aus Game-Engines oder extrem skalierbaren ERP-Systemen kennt!
|
||||||
|
Statt 20 verschiedener Tabellen (`flats`, `rooms`, `parking_spaces`) bauen wir einen großen `units`-Baum. Was die Unit kann, bestimmt eine Verknüpfungstabelle `unit_traits`.
|
||||||
|
Das macht das System zukunftssicher: Wenn ihr morgen beschließt, Surfbretter zu verleihen, legt ihr einfach neue Units an, gebt ihnen den Trait `is_rentable` (RentalUnit) und sie tauchen sofort im Buchungskalender auf, ohne dass das Datenbankschema geändert werden muss.
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
# RoggioApp - Events, Aufgaben & Relationen
|
||||||
|
|
||||||
|
## 1. Die Universalität der Entitäten
|
||||||
|
Nicht nur die physische Welt (Räume, Zirkuswagen), sondern **alles** ist eine grundlegende Entität, die über Eigenschaften (Traits) und Verknüpfungen definiert wird.
|
||||||
|
* **Personen als Entitäten:** Können Rollen in Gruppen übernehmen.
|
||||||
|
* **Dynamische Relationen (Verknüpfungen mit Eigenschaften):** Eine Person ist nicht einfach "in" einer Gruppe. Die Verbindung selbst hat eine Eigenschaft!
|
||||||
|
* *Person A* ist über die Eigenschaft `is_comm_contact` mit *Gruppe X* verbunden.
|
||||||
|
* *Person B* ist über die Eigenschaft `is_finance_contact` mit *Gruppe X* verbunden.
|
||||||
|
|
||||||
|
## 2. Buchungen und Aufgaben = "Events"
|
||||||
|
Die höchste Form der Abstraktion betrifft die Zeit und die Aktionen. Es gibt keine starren Tabellen für "Buchungen" und "Putzpläne". Beide sind im Kern **Events**.
|
||||||
|
|
||||||
|
### Was ist ein Event?
|
||||||
|
Ein Event ist ein Knotenpunkt in der Zeit, der andere Entitäten miteinander verbindet und einen Zustand hat.
|
||||||
|
|
||||||
|
* **Beispiel A: Die Buchung (Event-Typ: Rental)**
|
||||||
|
* *Eigenschaften des Events:* Zeitraum (14.08 - 21.08), Status (Bestätigt).
|
||||||
|
* *Relation 1:* Verknüpft mit Entität "Gruppe Familie Rossi" (Wer?).
|
||||||
|
* *Relation 2:* Verknüpft mit Entität "Haus A" + "Zirkuswagen" (Wo?).
|
||||||
|
* *Relation 3:* Finanzen/Rechnung (Geld).
|
||||||
|
|
||||||
|
* **Beispiel B: Das Putzen (Event-Typ: Task/Maintenance)**
|
||||||
|
* *Eigenschaften des Events:* Fälligkeitsfenster (21.08 10:00 - 15:00), Status (Offen).
|
||||||
|
* *Relation 1:* Verknüpft mit Entität "Haus A" (Was muss geputzt werden?).
|
||||||
|
* *Relation 2:* Verknüpft mit Entität "Kollektiv-Mitglied Sarah" (Wer putzt?).
|
||||||
|
* *Spezial-Relation (Event -> Event):* Getriggert durch / Abhängig von "Event A (Buchung Ende)".
|
||||||
|
|
||||||
|
## Claras architektonische Ableitung
|
||||||
|
Das gesamte System besteht eigentlich nur aus drei riesigen Säulen:
|
||||||
|
1. **Nodes (Entitäten):** Die Bausteine der Welt (Personen, Einheiten, etc.).
|
||||||
|
2. **Edges (Relationen/Traits):** Wie die Nodes verbunden sind und was sie können.
|
||||||
|
3. **Events (Zeitliche Knoten):** Das Klebeband, das Nodes auf einer Zeitachse zusammenführt (Buchungen, Aufgaben, Wartung).
|
||||||
|
|
||||||
|
*Herausforderung für die Datenbank:* So etwas baut man idealerweise mit einer Graphendatenbank (wie Neo4j) oder in PostgreSQL mit JSONB-Feldern für die Traits und extrem klugen Pivot-Tabellen (Edge-Tables) für die Beziehungen, damit man nicht den Verstand beim Queries-Schreiben verliert. Wir müssen einen Weg finden, das in Prisma sauber und typsicher zu modellieren, ohne die Performance zu töten.
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
# RoggioApp - Architektur & Schnittstellen-Strategie
|
||||||
|
|
||||||
|
## 1. Das Kern-System (Die absolute Source of Truth)
|
||||||
|
Das Ziel ist eine unantastbare, zukunftssichere und hochflexible Datenstruktur, die Schnittstellen (Frontends, CMS, Apps) als austauschbare Plugins betrachtet.
|
||||||
|
|
||||||
|
* **Der Datenbank-Core:**
|
||||||
|
* *Relationales Fundament (SQL):* Für absolute Konsistenz bei Transaktionen, Buchungen, Finanzen und den Kern-Entitäten (Nodes/Edges). Postgres ist hierfür ideal.
|
||||||
|
* *NoSQL-Einflüsse / Flexibilität:* Eigenschaften (Traits/Properties), die sich ständig ändern können, werden als JSONB-Felder (in Postgres) oder in einer kooperierenden NoSQL-Struktur abgelegt.
|
||||||
|
* *Caching/Performance:* Redis oder Memory-Caches vor den Berechnungen, damit der komplexe Graph nicht bei jedem Aufruf die SQL-DB auslastet.
|
||||||
|
* *Die API:* Eine zentrale REST- oder GraphQL-API. *Nichts* redet direkt mit der Datenbank, alles geht durch diese API.
|
||||||
|
|
||||||
|
## 2. Die Satelliten (Schnittstellen & Konsumenten)
|
||||||
|
Um dieses Kern-System kreisen verschiedene spezialisierte Werkzeuge, die über die API kommunizieren:
|
||||||
|
|
||||||
|
### A. Nextcloud (Das interne Werkzeug)
|
||||||
|
* **Auth (RBAC):** Nextcloud fungiert als Identity Provider (OIDC). Rollen und Rechte des Kollektivs werden dort gepflegt.
|
||||||
|
* **Ablage:** Dokumente, Rechnungs-PDFs und Verträge werden in Nextcloud gespeichert (das Roggio-Backend speichert nur den Link/die WebDAV-Referenz).
|
||||||
|
* **Organisation:** Tasks (Putzen/Support) und interne Kalender-Syncs (CalDAV) für das Kollektiv.
|
||||||
|
|
||||||
|
### B. WordPress (Das Schaufenster)
|
||||||
|
* **Aufgabe:** Marketing, Landing Pages, SEO.
|
||||||
|
* **Integration:** Ein schlankes Plugin oder Widget greift per API auf das Roggio-Backend zu, liest die Verfügbarkeiten aus (Belegungskalender) und schickt Buchungsanfragen zurück ins Kern-System.
|
||||||
|
|
||||||
|
### C. WebApp / MobileApp (Das Cockpit)
|
||||||
|
Eine (oder zwei getrennte) maßgeschneiderte Frontend-Applikationen (vermutlich React/React Native), die über die API mit dem Kern sprechen:
|
||||||
|
* **Für das Kollektiv / Personal:** Das "Backend-Frontend". Ansicht von Rechnungen, Zuweisen von Tasks, Managen der Graphen (Einheiten erstellen).
|
||||||
|
* **Für die Gäste (Kunden):** Eine Art "Guest-Portal" (Self-Service). Rechnungen einsehen, Services hinzubuchen (z.B. Sauna), Chat/Nachrichten, Check-in-Infos.
|
||||||
Executable
+44
@@ -0,0 +1,44 @@
|
|||||||
|
# RoggioApp - Tech-Stack Empfehlung (Hetzner, Schmal, API-First)
|
||||||
|
|
||||||
|
## Prämissen
|
||||||
|
1. **Hetzner-Ökosystem:** Performant, günstig, viel Bandbreite. Eigene VMs via Hetzner Cloud (Console) sind in Sekunden da.
|
||||||
|
2. **So schmal wie möglich (Lean):** Keine Over-Engineering-Monster (wie Kubernetes), wenn Docker Compose reicht. Wenig Wartungsaufwand für das Kollektiv.
|
||||||
|
3. **Zukunftssicher & Flexibel:** API-First, Graph-Datenmodell (ECS-Ansatz), Integration mit Nextcloud (WebDAV/OIDC) & WordPress.
|
||||||
|
|
||||||
|
## Der empfohlene Tech-Stack
|
||||||
|
|
||||||
|
### 1. Infrastruktur & Hosting (Die Basis)
|
||||||
|
* **Server:** Hetzner Cloud (z.B. CX22 oder CPX21). Reicht völlig für den Anfang, skaliert in Sekunden.
|
||||||
|
* **Orchestrierung:** **Docker Compose**. Kein K8s, kein Swarm. Eine einfache `docker-compose.yml`, die alle Core-Services hochzieht. Das ist extrem wartungsarm und portabel.
|
||||||
|
* **Reverse Proxy:** **Nginx Proxy Manager** (oder Caddy). Extrem schmal, kümmert sich automatisch um Let's Encrypt SSL-Zertifikate und leitet Traffic auf die Docker-Container (wie wir es lokal schon erfolgreich getestet haben!).
|
||||||
|
|
||||||
|
### 2. Die Datenhaltung (Das Herzstück)
|
||||||
|
* **Datenbank:** **PostgreSQL**.
|
||||||
|
* *Warum:* Absoluter Industrie-Standard, bombensicher für Finanz-Transaktionen und durch die exzellente `JSONB`-Unterstützung perfekt für unser flexibles "Traits/Eigenschaften"-Modell.
|
||||||
|
* **Caching / Message Broker (Optional, erst später zuschalten):** **Redis**.
|
||||||
|
* *Warum:* Wenn später WordPress alle 2 Sekunden den Kalender anfragt, puffert Redis das, damit Postgres nicht schwitzt. Für den Start (MVP) können wir das aber noch weglassen ("so schmal wie möglich").
|
||||||
|
|
||||||
|
### 3. Das Backend (Die API)
|
||||||
|
* **Sprache:** **Node.js mit TypeScript**.
|
||||||
|
* *Warum:* Enorme Community, sehr schnell entwickelt, und (das ist der Hauptgrund) wir können Code und Typen zwischen Frontend und Backend teilen!
|
||||||
|
* **ORM (Datenbank-Mapper):** **Prisma**.
|
||||||
|
* *Warum:* Es gibt nichts Besseres für TypeScript und Postgres. Es generiert uns typsichere Abfragen, verwaltet die Datenbank-Migrationen und macht das Jonglieren mit unserem Graph/Node-Modell deutlich angenehmer als rohes SQL.
|
||||||
|
* **Framework:** **NestJS** (oder leichtgewichtig: **Hono / Express**).
|
||||||
|
* *Empfehlung für euch:* Wenn es sauber und "Enterprise-like" strukturiert sein soll: *NestJS*. Wenn es absolut schmal und pfeilschnell (Edge-ready) sein soll: *Hono*.
|
||||||
|
|
||||||
|
### 4. Die Frontends (Das Gesicht)
|
||||||
|
* **Core-App (Kollektiv & Gäste Web-Portal):** **React** (gebaut mit **Vite** oder **Next.js**).
|
||||||
|
* *Warum:* Absoluter Standard. Riesiges Ökosystem an Kalender- und UI-Bibliotheken (z.B. Tailwind CSS, shadcn/ui).
|
||||||
|
* *Architektur:* Eine Single-Page-Application (SPA) oder statisch exportierte Seite, die direkt mit dem Node-Backend redet.
|
||||||
|
* **Marketing (bestehend):** WordPress (bei Hetzner Webhosting).
|
||||||
|
* *Warum:* Ihr habt es schon, jeder kann Inhalte einpflegen. Zieht die Buchungsdaten über eine simple REST-Schnittstelle von unserem Node-Backend.
|
||||||
|
* **Dateien / Auth (bestehend):** Nextcloud (bei Hetzner Storage Share).
|
||||||
|
* *Integration:* Das Node-Backend authentifiziert sich per OAuth2/OIDC an Nextcloud und speichert Rechnungen per WebDAV dort ab.
|
||||||
|
|
||||||
|
## Zusammenfassung des MVP-Setups (Schmalster Weg)
|
||||||
|
1. **Eine Hetzner Cloud VM**
|
||||||
|
2. Darauf läuft **Docker Compose**
|
||||||
|
3. Darin laufen genau 3 Container:
|
||||||
|
- **Nginx Proxy Manager** (für SSL und Domains)
|
||||||
|
- **PostgreSQL** (Die Datenbank)
|
||||||
|
- **Node.js (Hono/Prisma)** (Das Backend + liefert das kompilierte React-Frontend aus)
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# RoggioApp - Langzeit-Vision & Hardware-Schnittstellen
|
||||||
|
|
||||||
|
## 1. Lokale Resilienz (Datenbank-Mirroring)
|
||||||
|
* **Ziel:** Die Cloud (Hetzner) darf ausfallen oder das Internet auf dem Gelände streiken, ohne dass der Betrieb komplett lahmgelegt wird.
|
||||||
|
* **Ansatz:** Ein lokales Mirror-System für die Datenbank.
|
||||||
|
* *Architektur-Implication:* PostgreSQL erlaubt logische Replikation (Master-Slave oder Master-Master Konzepte). Wir sollten das Backend so bauen, dass es (später) Offline-First / Sync-Fähigkeiten bekommt oder zumindest ein lokaler Read-Replica Server auf dem Gelände läuft. (Für den Start bauen wir Single-Node, halten uns aber die Multi-Node Replikation in Postgres offen).
|
||||||
|
|
||||||
|
## 2. Zentrale User-Verwaltung (Identity & Access Management - IAM)
|
||||||
|
* **Ziel:** Das Auth-System (Nextcloud / OIDC) soll in Zukunft der einzige Schlüssel für *alles* sein.
|
||||||
|
* **Geplante Erweiterungen für User:**
|
||||||
|
* WireGuard VPN-Profile
|
||||||
|
* VoIP / SIP-Accounts (Telefonie)
|
||||||
|
* E-Mail Accounts
|
||||||
|
* NFC / RFID Zugangskontrolle (Türen)
|
||||||
|
* *Architektur-Implication:* Nextcloud als OIDC-Provider ist hier bereits ein sehr guter Startpunkt. Wir dürfen in der RoggioApp keine eigenen, isolierten "User-Passwort"-Tabellen bauen. Die RoggioApp referenziert nur eine globale `user_id` (z.B. aus LDAP oder Nextcloud), an der die Hardware-Schlüssel hängen.
|
||||||
|
|
||||||
|
## 3. Home Automation & IoT (Der Campus)
|
||||||
|
* **Ziel:** Smarte Steuerung und Überwachung der physischen Infrastruktur vor Ort.
|
||||||
|
* **Core-Technologien:** HomeAssistant, MQTT, LoRaWAN.
|
||||||
|
* **Kritische Systeme:**
|
||||||
|
* PV / Solar Management (Strom)
|
||||||
|
* Wasserspeicher (Infrastruktur)
|
||||||
|
* Zugangskontrolle (Smart Locks)
|
||||||
|
* *Architektur-Implication:* Unser Konzept der "Units" und "Traits" greift hier perfekt!
|
||||||
|
* Eine Unit (z.B. Zirkuswagen) bekommt in unserer Datenbank den Trait `has_smart_lock`.
|
||||||
|
* In diesem Trait speichern wir die MQTT-Topic-ID (`/house/zirkuswagen/lock`).
|
||||||
|
* Wenn im Event "Buchung" der Status auf "Check-in" wechselt, feuert unser Node-Backend einen MQTT-Befehl an den lokalen HomeAssistant, um den Code des Gastes auf das Schloss zu spielen.
|
||||||
|
|
||||||
|
## Claras Fazit
|
||||||
|
Die Weichenstellung ist klar: Das System ist kein reines "Software-Tool", sondern das Betriebssystem für ein physisches Gelände. Die strikte Trennung von Core-Logik, externem Auth-Provider und MQTT-fähigen Hardware-Layern sichert uns ab. Keine dieser Anforderungen sprengt unseren API-First / Node.js / Postgres Stack – im Gegenteil, MQTT und OIDC lassen sich in Node.js hervorragend nativ integrieren.
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# RoggioApp - Nächster Schritt: Das Prisma Datenmodell (MVP)
|
||||||
|
|
||||||
|
Um vom Konzept in die Praxis zu kommen, sollten wir als Nächstes ein `schema.prisma` entwerfen, das unseren "Graph aus Units und Events" abbildet.
|
||||||
|
|
||||||
|
*Zu klären:*
|
||||||
|
1. Wie bilden wir die flache "Unit"-Tabelle ab?
|
||||||
|
2. Wie speichern wir die flexiblen "Traits" (als JSONB oder eigene Tabellen)?
|
||||||
|
3. Wie verknüpfen wir Events (Buchungen/Aufgaben) typsicher?
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
# RoggioApp - System-Status & Paranoia-Checkpoint
|
||||||
|
|
||||||
|
## 1. Warum wir sicher sind (Keine Angst vor Token-Limits)
|
||||||
|
* **Festplatten-Gedächtnis:** Alles, was wir bisher konzipiert haben (Die Node/Graph-Abstraktion, die Architektur-Entscheidungen), habe ich bereits als Markdown-Dateien in diesem Ordner abgelegt (`01_...` bis `09_...`).
|
||||||
|
* **Infrastruktur existiert wirklich:** Die Docker-VM, der SMB-Share, das Let's Encrypt Zertifikat, die Prisma-Konfiguration und die laufende PostgreSQL-Datenbank sind **echt**. Sie laufen unabhängig von diesem Chatfenster.
|
||||||
|
* **Der Code liegt auf der Platte:** Die `schema.prisma` und die Nginx-Konfigurationen sind nicht flüchtig im Chat-Verlauf, sondern fest gespeichert.
|
||||||
|
|
||||||
|
## 2. Wie wir nach einem Neustart (oder morgen) weitermachen
|
||||||
|
Sollten wir morgen neu anfangen oder das Kontextfenster platzen, sagst du einfach:
|
||||||
|
*"Clara, schau in den Ordner `/home/clara/.openclaw/workspace/projects/RoggioApp/Brainstorm/260426/`, lies dir die Architektur-Entscheidungen durch und lass uns am Prisma-Backend weiterarbeiten."*
|
||||||
|
|
||||||
|
Ich werde dann genau dort ansetzen, wo wir aufgehört haben, weil mein "Gedächtnis" auf SSD-Basis (in diesen Dateien) liegt, nicht nur im flüchtigen Chat-Protokoll.
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
# RoggioApp - Personen Entitäten & Nextcloud Sync (OIDC)
|
||||||
|
|
||||||
|
## Das Problem
|
||||||
|
Wir wollen Personen (`Persons`) als zentrale Knoten im Graph (Datenbank) modellieren.
|
||||||
|
Aber die "Wahrheit" über den Login (Passwort, Rollen) soll in der Nextcloud (als Identity Provider) liegen.
|
||||||
|
Stellen wir uns ein Bein, wenn wir in der Datenbank jetzt Personen anlegen, ohne dass die Nextcloud steht?
|
||||||
|
|
||||||
|
## Die Lösung: Entkopplung von "Identity" und "Profile"
|
||||||
|
Wir trennen das Konzept "Wer bist du technisch?" von "Wer bist du in der realen Welt?".
|
||||||
|
|
||||||
|
### 1. Das Roggio-Profil (Die Person-Entität)
|
||||||
|
In unserer Postgres-DB (Prisma) liegt das Modell `Person`.
|
||||||
|
Dieses Modell enthält **nur Profil- und Geschäftsdaten**:
|
||||||
|
* `firstName`, `lastName`, `phone`
|
||||||
|
* `traits` (JSONB) für alles andere (Körpergröße für Leihräder, Essensvorlieben für Gruppen).
|
||||||
|
* **Der Anker:** Ein Feld `ssoId` (Single-Sign-On ID).
|
||||||
|
|
||||||
|
### 2. Die Nextcloud-Identität (Der Identity Provider)
|
||||||
|
In der Nextcloud liegen die sensiblen Login-Daten:
|
||||||
|
* `username` (z.B. "sev")
|
||||||
|
* `password` (gehasht)
|
||||||
|
* `email`
|
||||||
|
* `groups` (z.B. "admin", "kollektiv")
|
||||||
|
|
||||||
|
### Der Workflow (Wie beides zusammenspielt)
|
||||||
|
Wir können jetzt sofort Personen in der Datenbank anlegen (z.B. Gäste, die sowieso keinen Nextcloud-Account bekommen).
|
||||||
|
Wenn sich später ein Kollektiv-Mitglied über Nextcloud einloggt (OIDC), passiert folgendes:
|
||||||
|
1. Nextcloud sagt: *"Hier ist User 'sev' mit der OIDC-Sub-ID '12345' und der Email 'sev@...'."*
|
||||||
|
2. Unser Backend schaut in der `Person`-Tabelle nach: *"Habe ich eine Person mit `ssoId = '12345'` oder `email = 'sev@...'`?"*
|
||||||
|
3. Wenn Ja: Die Person ist authentifiziert und das Roggio-Profil wird mit der Sitzung verknüpft.
|
||||||
|
4. Wenn Nein (erster Login): Das Backend erstellt automatisch eine neue `Person` mit der `ssoId='12345'`.
|
||||||
|
|
||||||
|
## Fazit für die Entwicklung
|
||||||
|
Wir können das Datenmodell jetzt zu 100% fertigbauen und verfeinern!
|
||||||
|
Gäste existieren ohnehin nur in Roggio. Für das Kollektiv lassen wir einfach das Feld `ssoId` vorerst leer. Wenn wir Nextcloud anbinden, füllen wir die ID nach oder lassen sie beim ersten Login auto-matchen.
|
||||||
|
Kein Bein gestellt!
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
# RoggioApp - Das Kollektiv als eigene Entität
|
||||||
|
|
||||||
|
## Die Erkenntnis: Das Kollektiv ist kein abstrakter "Gott", sondern nur eine Gruppe
|
||||||
|
Wenn man konsequent abstrahiert, ist die Betreiber-Struktur (das Kollektiv) nichts anderes als eine ganz normale Entität im Graphen.
|
||||||
|
|
||||||
|
### 1. Das Kollektiv ist eine `Group`
|
||||||
|
In unserer Datenbank-Tabelle `Group` existieren Gruppen für "Familie Rossi" (Gäste).
|
||||||
|
Genau dort existiert auch ein Datensatz für "Das Kollektiv".
|
||||||
|
Unterschieden wird dies nur durch das JSONB-Feld `traits`:
|
||||||
|
* *Familie Rossi:* `{"group_type": "guest", "preferences": "quiet"}`
|
||||||
|
* *Das Kollektiv:* `{"group_type": "operator", "bank_account": "DE123...", "tax_id": "DE999..."}`
|
||||||
|
|
||||||
|
### 2. Die Mitglieder sind `Person`-Entitäten mit Relationen
|
||||||
|
Die Menschen, die das System betreiben (Sev, Sarah, etc.), sind normale `Person`-Einträge.
|
||||||
|
Ihre Zugehörigkeit zum Kollektiv wird durch die reguläre Pivot-Tabelle `GroupMembership` definiert:
|
||||||
|
* *Person ID (Sev)* + *Group ID (Das Kollektiv)* + *Rolle (`finance_admin`)*
|
||||||
|
* *Person ID (Sarah)* + *Group ID (Das Kollektiv)* + *Rolle (`cleaning_crew`)*
|
||||||
|
|
||||||
|
### 3. Warum das absolut genial ist: Mandantenfähigkeit ("Multi-Tenancy")
|
||||||
|
Wenn wir das Kollektiv als einfache Gruppe speichern, ist das System sofort **mandantenfähig**.
|
||||||
|
Beispiel: In 5 Jahren wollt ihr neben dem Haupt-Gelände noch ein zweites Haus im Nachbardorf verwalten, das aber von einer anderen Genossenschaft (oder GmbH) betrieben wird.
|
||||||
|
Wir müssen *keinen einzigen* Datenbankumbau vornehmen! Wir legen einfach eine zweite Operator-Gruppe an, ordnen dieser Gruppe ein paar Personen und die neuen "Units" (Räume) zu, und fertig. Die Rechnungen ziehen sich dann automatisch die Bankdaten von Gruppe B statt von Gruppe A.
|
||||||
|
|
||||||
|
## Fazit
|
||||||
|
Das System kennt keinen "Super-Admin-Hardcode". Es kennt nur den Graphen. Das Kollektiv ist ein normaler Knotenpunkt, der sich über seine Beziehungen definiert.
|
||||||
|
|
||||||
|
### 4. B2B / Firmen als Kunden (Organisationen)
|
||||||
|
Genau wie "Das Kollektiv" eine Operator-Gruppe ist, sind auch B2B-Kunden (Firmen, NGOs, Vereine) einfache Einträge in der Tabelle `Group`.
|
||||||
|
* *Verein e.V.:* `{"group_type": "b2b_guest", "tax_id": "DE123...", "invoice_notes": "Vereinsrabatt"}`
|
||||||
|
* Die Verknüpfung der tatsächlichen Gäste erfolgt wieder über `GroupMembership`. (Z.B. ist Peter der `invoice_contact` für den Verein, und Sarah, Tim und Lisa sind `guest`).
|
||||||
|
* Auch Rechnungen können somit direkt an die Entität `Group` ausgestellt werden, da die Firmen-/Vereinsadresse in deren Traits liegt, anstatt an einer einzelnen Person zu kleben.
|
||||||
+58
@@ -0,0 +1,58 @@
|
|||||||
|
# RoggioApp - Analyse: Die Person-Entität (Hard vs. Flexible)
|
||||||
|
|
||||||
|
## Die Herausforderung
|
||||||
|
Was gehört zwingend als harte, durchsuchbare SQL-Spalte in die Tabelle `Person`, und was lagern wir als flexible Eigenschaft (Trait) in das JSONB-Feld oder in Relationen aus?
|
||||||
|
|
||||||
|
## Sevs Entwurf & Claras Analyse
|
||||||
|
|
||||||
|
### 1. Originär (Die Person selbst)
|
||||||
|
* **Vorname & Nachname:** ✅ Absolut notwendig. Harte SQL-Spalten. (Ggf. um `nickname` als Spalte erweitern).
|
||||||
|
* **Geburtsdatum:** ✅ Harte Spalte (für Altersberechnungen, Kurtaxe, Meldescheine extrem wichtig).
|
||||||
|
* **Mobile-Nr & Private E-Mail:** ⚠️ *Diskussion:* Sind die eindeutig?
|
||||||
|
* *Realität:* Oft buchen Ehepaare oder Familien mit nur *einer* gemeinsamen Mailadresse oder Handynummer.
|
||||||
|
* *Lösung:* Wir dürfen `email` und `phone` **nicht** auf `UNIQUE` (Einzigartig) in der Datenbank setzen! Sie bleiben harte SQL-Spalten für die schnelle Suche, aber Doppelungen sind erlaubt.
|
||||||
|
|
||||||
|
### 2. Relational / Flexibel (Die Eigenschaften)
|
||||||
|
* **Anrede (Pronouns / Title):** -> JSONB (`traits`). (Ändert sich gesellschaftlich, JSONB ist hier flexibler als ein harter SQL-Enum).
|
||||||
|
* **Identity-Dokument (Perso/Passport):** -> JSONB (`traits`).
|
||||||
|
* *Warum:* Die Felder variieren stark je nach Land (Ausgestellt in, Gültig bis, Behörde). Ein JSONB-Objekt `identity_document: { type: "passport", number: "...", valid_until: "..." }` ist hier perfekt.
|
||||||
|
* **Geburtsort:** -> JSONB (`traits`).
|
||||||
|
* **Adresse (Wohnort):** -> ⚠️ *Eigene Tabelle!*
|
||||||
|
* *Warum:* Eine Adresse (Straße, Stadt, Land) kann von 5 Familienmitgliedern oder einer Firma geteilt werden. Wir bauen eine Tabelle `Address` und verknüpfen die Person(en) per ID damit. Das spart immense Redundanz.
|
||||||
|
* **Finanz-Infos (IBAN etc.):** -> JSONB (`traits`).
|
||||||
|
|
||||||
|
### 3. Logisch durch den Graphen gelöst (Architektur-Bonus)
|
||||||
|
* **Buchungsverlauf:** Wird *nicht* in der Person gespeichert. Ergibt sich zu 100% aus den `Event`-Knoten, die mit der Person (oder ihrer Gruppe) verknüpft sind.
|
||||||
|
* **Loginrights:** Werden nicht gespeichert. Das übernimmt die Nextcloud (SSO). Wir speichern nur die `ssoId`.
|
||||||
|
* **Type (Kollektiv / Client / Personal):** Wird *nicht* als hartes Feld gespeichert! Ergibt sich aus den `GroupMembership`-Verknüpfungen (z.B. wenn Person A Mitglied in der Operator-Gruppe ist, ist sie Personal).
|
||||||
|
* **Groups & Groupfunction:** Wird 1:1 durch unsere Pivot-Tabelle `GroupMembership` (inkl. `role`) abgebildet.
|
||||||
|
|
||||||
|
## Claras Vorschlag für die finale Prisma-Tabelle `Person`
|
||||||
|
```prisma
|
||||||
|
model Person {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
firstName String @db.VarChar(100)
|
||||||
|
lastName String @db.VarChar(100)
|
||||||
|
nickname String? @db.VarChar(100)
|
||||||
|
birthDate DateTime? @db.Date
|
||||||
|
|
||||||
|
// Kontaktdaten (NICHT UNIQUE!)
|
||||||
|
email String? @db.VarChar(255)
|
||||||
|
phone String? @db.VarChar(50)
|
||||||
|
|
||||||
|
// Verknüpfung zur Authentifizierung
|
||||||
|
ssoId String? @unique @db.VarChar(255)
|
||||||
|
|
||||||
|
// Die Wohnadresse (Relational)
|
||||||
|
addressId String?
|
||||||
|
address Address? @relation(fields: [addressId], references: [id])
|
||||||
|
|
||||||
|
// Alles andere (Pass, Finanzen, Anrede, Geburtsort, Allergien)
|
||||||
|
traits Json @default("{}")
|
||||||
|
|
||||||
|
groupMemberships GroupMembership[]
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
```
|
||||||
+82
@@ -0,0 +1,82 @@
|
|||||||
|
# RoggioApp - Units & Hierarchie (Beispiel: Wohnung im 1. Stock)
|
||||||
|
|
||||||
|
## 1. Die Hierarchie (Der Tree)
|
||||||
|
Die Macht unseres Node-Graphen zeigt sich in der Hierarchie. Wir legen nicht eine fette Zeile in einer Tabelle "Wohnung" an, sondern stapeln Units ineinander (`parentId`).
|
||||||
|
|
||||||
|
**Beispiel-Baum:**
|
||||||
|
* **Unit 1:** Haus A (Parent: Null)
|
||||||
|
* **Unit 2:** 1. Stock (Parent: Unit 1)
|
||||||
|
* **Unit 3:** Wohnung Sonnenaufgang (Parent: Unit 2, `is_rentable: true`)
|
||||||
|
* **Unit 4:** Küche (Parent: Unit 3)
|
||||||
|
* **Unit 5:** Bad (Parent: Unit 3)
|
||||||
|
* **Unit 6:** Wohnzimmer (Parent: Unit 3)
|
||||||
|
* **Unit 7:** Terrasse (Parent: Unit 3)
|
||||||
|
* **Unit 8:** Schlafzimmer 1 (Parent: Unit 3)
|
||||||
|
* **Unit 9:** Doppelbett (Parent: Unit 8, `capacity: 2`)
|
||||||
|
* **Unit 10:** Schlafzimmer 2 (Parent: Unit 3)
|
||||||
|
* **Unit 11:** Einzelbett A (Parent: Unit 10, `capacity: 1`)
|
||||||
|
* **Unit 12:** Einzelbett B (Parent: Unit 10, `capacity: 1`)
|
||||||
|
* **Unit 13:** Schlafzimmer 3 (Parent: Unit 3)
|
||||||
|
* **Unit 14:** Einzelbett (Parent: Unit 13, `capacity: 1`)
|
||||||
|
|
||||||
|
## 2. Die Eigenschaften (Traits im JSONB)
|
||||||
|
Wie unterscheiden wir nun ein Schlafzimmer von einem Bad, ohne für jedes eine eigene Datenbanktabelle zu bauen? Über das JSONB-Feld `traits`.
|
||||||
|
|
||||||
|
**Beispiele für das `traits`-Feld (JSON):**
|
||||||
|
|
||||||
|
* **Bei Unit 3 (Wohnung Sonnenaufgang):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "apartment",
|
||||||
|
"is_rentable": true,
|
||||||
|
"base_price": 85.00,
|
||||||
|
"cleaning_fee": 40.00
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*(Hinweis: Fläche und Kapazität stehen hier absichtlich NICHT drin. Sie werden später live aus den Kindern berechnet!)*
|
||||||
|
|
||||||
|
* **Bei Unit 8 (Schlafzimmer 1):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "room",
|
||||||
|
"room_category": "bedroom",
|
||||||
|
"area_sqm": 18.5,
|
||||||
|
"features": ["Meerblick", "Verdunkelungsrollos"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* **Bei Unit 9 (Doppelbett):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "inventory",
|
||||||
|
"inventory_category": "bed_double",
|
||||||
|
"sleep_capacity": 2,
|
||||||
|
"width_cm": 180
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* **Bei Unit 5 (Bad):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "room",
|
||||||
|
"room_category": "bathroom",
|
||||||
|
"area_sqm": 8.0,
|
||||||
|
"features": ["Dusche", "Badewanne", "Bidet", "Fenster"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* **Bei Unit 4 (Küche):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "room",
|
||||||
|
"room_category": "kitchen",
|
||||||
|
"area_sqm": 12.0,
|
||||||
|
"inventory": ["Kühlschrank", "Herd", "Kaffeemaschine", "Töpfe"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Claras Fazit zur Flexibilität
|
||||||
|
Wir müssen in Postgres nur EINE einzige Tabelle `Unit` abfragen, um das gesamte Gebäude zu kennen.
|
||||||
|
Wenn jemand wissen will: *"Wie groß ist die Wohnung und wie viele Leute können dort schlafen?"*
|
||||||
|
Dann fragt unser Backend: *"Summiere alle `area_sqm` von allen Units, deren direkter oder indirekter Parent die 'Wohnung Sonnenaufgang' ist. Und summiere alle `sleep_capacity` der Kinder."*
|
||||||
|
Ergebnis: Fläche = 38.5qm, Schlafplätze = 5.
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
# RoggioApp - Inventar & Zustand (Soll vs. Ist)
|
||||||
|
|
||||||
|
## Das Problem
|
||||||
|
Inventar (Löffel, Gasflaschen) ist nicht statisch. Es verbraucht sich, geht kaputt oder verschwindet. Wir müssen nicht nur wissen, *was* da sein sollte, sondern *wie viel* davon tatsächlich da ist, um Aktionen (Nachbestellen) zu triggern.
|
||||||
|
|
||||||
|
## Sevs Ansatz: Soll- vs. Ist-Anzahl
|
||||||
|
Beispiele:
|
||||||
|
* Küche: Löffel (Soll: 10, Ist: 5)
|
||||||
|
* Butanlager: 5kg Flaschen (Soll: 10, Ist: 7)
|
||||||
|
|
||||||
|
## Claras architektonische Einordnung im Unit-Graphen
|
||||||
|
Wir können Inventar nicht einfach als dumme Text-Strings in ein Array (`inventory: ["Löffel"]`) packen, wenn wir Soll/Ist-Zahlen erfassen wollen. Wir müssen dem Inventar eine kleine Struktur geben.
|
||||||
|
|
||||||
|
Da wir JSONB (`traits`) verwenden, ist das ein Kinderspiel und erfordert **keine** Änderung am Prisma-Schema!
|
||||||
|
|
||||||
|
### Lösung A: Inventar als strukturiertes Objekt im Raum-Trait
|
||||||
|
Anstatt eines einfachen Arrays speichern wir ein Array aus Objekten im JSONB der Unit (z.B. der Küche oder dem Butanlager).
|
||||||
|
|
||||||
|
*Trait für die Unit "Butanlager":*
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "storage_room",
|
||||||
|
"inventory_state": [
|
||||||
|
{
|
||||||
|
"item_id": "item_butan_5kg",
|
||||||
|
"name": "5kg Butangas",
|
||||||
|
"target_qty": 10,
|
||||||
|
"actual_qty": 7,
|
||||||
|
"alert_threshold": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*Vorteil:* Sehr schnell abfragbar.
|
||||||
|
*Nachteil:* Die Endreinigungskräfte müssten theoretisch die Unit "Butanlager" bearbeiten, um die Zahl anzupassen.
|
||||||
|
|
||||||
|
### Lösung B: Inventar als eigene Sub-Units (Der reinrassige Graph-Weg)
|
||||||
|
Anstatt die Gabeln als Text in die Küche zu schreiben, machen wir aus "Bestand: Löffel" eine eigene Unit, die als Child *unter* der Küche hängt.
|
||||||
|
|
||||||
|
*Unit "Löffel-Bestand" (Parent: Küche):*
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "inventory_item",
|
||||||
|
"name": "Kaffeelöffel",
|
||||||
|
"target_qty": 10,
|
||||||
|
"actual_qty": 5
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*Vorteil:* Absolut sauber. Wenn eine Putzkraft nach der Reinigung einen "Schwund" meldet, erstellt das System ein `Event` ("Inventar-Korrektur") und verknüpft es direkt mit der Unit "Löffel-Bestand". Die Historie bleibt erhalten (Wer hat wann gemeldet, dass 5 Löffel fehlen?).
|
||||||
|
|
||||||
|
## Die Konsequenz für Aufgaben (Events)
|
||||||
|
Wenn `actual_qty` kleiner wird als ein definierter Schwellenwert (z.B. bei Gasflaschen), feuert unser System automatisch ein neues `Event` vom Typ `TASK_BUY` (Einkaufen). Dieses Event taucht dann in Nextcloud auf der Einkaufsliste auf.
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
# RoggioApp - Beschluss: Weg 2 (Alles ist eine Unit)
|
||||||
|
|
||||||
|
## Der Entschluss
|
||||||
|
Wir nutzen **Weg 2** für *jedes* Inventar, bei dem Bestände, Schwund oder Nachbestellungen (Soll/Ist) relevant sind.
|
||||||
|
Das bedeutet: Wir verpacken Inventar nicht in unübersichtliche JSON-Arrays innerhalb eines Raums, sondern machen **jedes relevante Inventar zu einer eigenen Child-Unit**.
|
||||||
|
|
||||||
|
## Warum das die mächtigste Entscheidung ist
|
||||||
|
1. **Historie (Audit-Trail):** Wenn "5 Löffel fehlen" als JSON in der Küche steht, weiß niemand, wann das passiert ist. Wenn der Löffel-Bestand eine eigene Unit ist, kann ein `Event` ("Inventur nach Abreise von Rossi") mit der Löffel-Unit verknüpft werden. Man sieht exakt: Am 26.04. hat Sarah 5 fehlende Löffel eingetragen.
|
||||||
|
2. **Modularität:** Ein Grill (Unit) kann heute zur Wohnung 1 (Parent) gehören und morgen auf dem Zirkuswagen-Platz (neuer Parent) stehen. Hätten wir den Grill als JSON in Wohnung 1 geschrieben, müssten wir JSON manipulieren. So verschieben wir einfach die `parentId`.
|
||||||
|
3. **Wartung & Defekte:** Eine Spülmaschine (Unit) kann den Trait `status: "broken"` bekommen. Das generiert automatisch ein `Event` (Task: Reparatur) für die Support-Gang.
|
||||||
|
|
||||||
|
## Das Datenmodell bleibt unberührt
|
||||||
|
Es beweist die Stärke des Modells: Obwohl wir gerade die Verwaltung von Besteck, Gasflaschen und Spülmaschinen beschlossen haben, müssen wir in `schema.prisma` **nichts** ändern. Die `Unit`-Tabelle fängt das komplett ab.
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
# RoggioApp - Zeitachsen (Buchungen, Anwesenheit, Schichten)
|
||||||
|
|
||||||
|
## Die große Vereinheitlichung
|
||||||
|
Genau wie bei den Units gibt es auch bei der Zeit keine harten Trennungen.
|
||||||
|
**Eine Buchung für einen Gast, eine Schicht für Personal und die private Anwesenheit eines Kollektiv-Mitglieds sind technisch absolut identisch.**
|
||||||
|
|
||||||
|
Alles sind `Event`-Knoten auf einer Zeitachse.
|
||||||
|
|
||||||
|
### Der Aufbau eines Zeitachsen-Events
|
||||||
|
Jedes dieser Ereignisse nutzt exakt dieselbe Tabelle (`Event`) in der Datenbank. Die Magie entsteht durch die Verknüpfung (Relationen) und das Etikett (Type).
|
||||||
|
|
||||||
|
**Beispiel 1: Die klassische Gast-Buchung**
|
||||||
|
* `type`: "RENTAL"
|
||||||
|
* `startTime`: 15.08.2026 15:00
|
||||||
|
* `endTime`: 22.08.2026 10:00
|
||||||
|
* `targetUnitId`: Haus A (Was ist belegt?)
|
||||||
|
* `targetGroupId`: Familie Rossi (Wer belegt es?)
|
||||||
|
|
||||||
|
**Beispiel 2: Die Personal-Schicht (Putzen)**
|
||||||
|
* `type`: "SHIFT_WORK"
|
||||||
|
* `startTime`: 22.08.2026 10:00
|
||||||
|
* `endTime`: 22.08.2026 14:00
|
||||||
|
* `targetUnitId`: Haus A (Wo wird gearbeitet?)
|
||||||
|
* `targetPersonId`: Putzkraft Maria (Wer arbeitet?)
|
||||||
|
* *Abhängigkeit (`parentEventId`):* Verweist auf Beispiel 1 (Wird automatisch getriggert, wenn Rossi abreist).
|
||||||
|
|
||||||
|
**Beispiel 3: Anwesenheit Kollektiv (Workation/Urlaub)**
|
||||||
|
* `type`: "PRESENCE_COLLECTIVE"
|
||||||
|
* `startTime`: 01.09.2026
|
||||||
|
* `endTime`: 14.09.2026
|
||||||
|
* `targetUnitId`: Zirkuswagen (Wo schläft Sev?)
|
||||||
|
* `targetPersonId`: Sev (Wer ist da?)
|
||||||
|
|
||||||
|
## Warum das Architektur in Perfektion ist
|
||||||
|
Da alle drei Dinge in *derselben* Tabelle als Event mit Start- und Endzeit liegen, ist das Verhindern von Doppelbelegungen ("Clash-Detection") extrem einfach.
|
||||||
|
Wenn ein Gast "Haus A" buchen will, fragt die Datenbank: *"Gibt es irgendein Event (egal ob RENTAL, SHIFT oder PRESENCE), das sich mit diesem Datum auf 'Haus A' überschneidet?"*
|
||||||
|
Wenn Sev also für seine Workation das Haus A als "Anwesenheit" blockt, kann kein Gast es buchen. Man muss nicht drei verschiedene Tabellen (`Urlaub`, `Buchungen`, `Sperrzeiten`) abfragen!
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# RoggioApp - Replikation & Hardcore-Datenschutz (Online vs. On-Premise)
|
||||||
|
|
||||||
|
## Die Anforderung
|
||||||
|
Zwei aktive Datenbanken (Cloud bei Hetzner + On-Premise auf dem Gelände).
|
||||||
|
**Das Ziel:** Sensible Daten (Ausweisnummern, IBANs, Kreditkarten) dürfen **niemals** in der Cloud-Datenbank liegen. Die Cloud hat nur Pointer (Referenzen) auf diese Daten. Die echte Entschlüsselung und Speicherung findet nur im Safe (On-Premise) statt.
|
||||||
|
|
||||||
|
## Die Lösungsarchitektur (Der Hybrid-Ansatz)
|
||||||
|
|
||||||
|
Wir können nicht einfach eine blinde PostgreSQL-Replikation (Master-Slave) einschalten. Wenn die Cloud der Master ist, hat sie alle Daten. Wenn wir das verhindern wollen, müssen wir die Applikations-Architektur anpassen.
|
||||||
|
|
||||||
|
### 1. Tokenisierung (Das Pointer-Konzept)
|
||||||
|
Die Cloud-Datenbank (Hetzner) speichert keine JSONB-Traits mit sensiblen Daten.
|
||||||
|
Anstatt: `traits: { iban: "DE1234...", passport: "X123" }`
|
||||||
|
Speichert die Cloud: `traits: { secure_vault_id: "vault_abc123" }`
|
||||||
|
|
||||||
|
### 2. Der Vault (On-Premise)
|
||||||
|
Auf dem Server im Gelände läuft ein isolierter "Vault-Service" (z.B. HashiCorp Vault oder eine eigene hochsichere Micro-Postgres-Instanz).
|
||||||
|
Dort liegt die Tabelle `Vault_Data` mit den Inhalten für `vault_abc123`.
|
||||||
|
|
||||||
|
### 3. Wie die API damit umgeht
|
||||||
|
* **Der Cloud-API-Server (Hetzner):** Handelt nur Termine, Namen (ohne Ausweise), Unit-Kapazitäten und Buchungs-Slots. Wenn ein Gast via WordPress bucht und seine IBAN eintippt, schickt die Cloud-API diese IBAN *direkt* durch einen verschlüsselten Tunnel (WireGuard) an den Vault On-Premise, holt sich eine generierte `vault_id` zurück und speichert *nur diese ID* in der Cloud-Datenbank.
|
||||||
|
* **Der On-Premise-API-Server (Das Kollektiv-Cockpit):** Wenn du zu Hause im Netzwerk die Buchung aufmachst, lädt das Frontend die Buchung (aus der Cloud-DB) PLUS die sensiblen Daten (aus dem lokalen Vault) und fügt sie in deinem Browser zusammen.
|
||||||
|
|
||||||
|
### 4. Was passiert bei Internet-Ausfall?
|
||||||
|
Wenn das Gelände offline ist, kann die Cloud immer noch fröhlich neue Buchungen über WordPress annehmen. Sie speichert die sensiblen Daten (z.B. Ausweisfotos) temporär hochverschlüsselt (asymmetrisch, z.B. mit PGP) zwischen. Sobald die On-Premise-Kiste wieder online geht, zieht sie sich die verschlüsselten Pakete, entschlüsselt sie mit ihrem privaten Offline-Key, speichert sie im Vault und sagt der Cloud: "Hab ich, du kannst das Original jetzt löschen."
|
||||||
|
|
||||||
|
## Claras Einschätzung
|
||||||
|
Das ist Enterprise-Level Security (Zero Trust für die Cloud).
|
||||||
|
Für Prisma bedeutet das: Wir trennen die sensiblen Traits strikt ab. Wir können das relativ elegant bauen, indem wir für sensible Traits eine eigene Prisma-Logik verwenden, die immer mit einem On-Prem-Service redet, statt sie ins normale Cloud-JSONB-Feld zu kippen.
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
# RoggioApp - Vault Implementierung (MVP Step 1)
|
||||||
|
|
||||||
|
## Die MVP-Strategie für den Datenschutz-Split
|
||||||
|
Wir bauen nicht sofort eine zweite physische Datenbank und einen VPN-Tunnel auf. Das würde uns am Anfang massiv ausbremsen. Wir simulieren die Architektur zuerst *logisch*, bevor wir sie *physisch* trennen.
|
||||||
|
|
||||||
|
### Schritt 1 (Jetzt): Die logische Trennung (Separate Table)
|
||||||
|
In unserer aktuellen Prisma-Datenbank (Hetzner/Cloud) legen wir eine **eigenständige Tabelle `Vault`** an.
|
||||||
|
* **Warum:** So zwingen wir unseren Backend-Code vom ersten Tag an dazu, so zu tun, als lägen die Daten auf einem anderen Server. Die Logik "Hole Token -> Lade Daten aus Vault" wird in Code gegossen.
|
||||||
|
* **Der Vorteil:** Wenn wir später den echten On-Premise-Server hinstellen, müssen wir den Backend-Code nicht mehr anfassen. Wir ändern nur die Datenbank-URL für die `Vault`-Tabelle auf deine lokale IP um.
|
||||||
|
|
||||||
|
### Schritt 2 (Später): Die physische Trennung
|
||||||
|
Sobald der On-Premise-Server steht, lösen wir die Tabelle `Vault` aus Prisma heraus und schieben sie in eine eigene, kleine SQLite/Postgres-Instanz auf deinem HomeAssistant-Server.
|
||||||
|
|
||||||
|
## Anpassung für Prisma (Mock-Vault)
|
||||||
|
```prisma
|
||||||
|
// ============================================================================
|
||||||
|
// DER VAULT (Logisch getrennt - Später On-Premise)
|
||||||
|
// ============================================================================
|
||||||
|
model VaultData {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
entityType String @db.VarChar(50) // e.g. "Person", "Booking"
|
||||||
|
entityId String // Die ID der Person oder Buchung in der Cloud
|
||||||
|
|
||||||
|
// Die sensiblen Daten (verschlüsselt!)
|
||||||
|
secureData String @db.Text
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
}
|
||||||
|
```
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
# RoggioApp - Nächster Schritt: Das Seed/CRUD Script
|
||||||
|
|
||||||
|
## Das Ziel
|
||||||
|
Die graue Theorie der Graphen-Architektur (`Units`, `Traits`, `Events`) mit echtem Leben füllen.
|
||||||
|
Wir brauchen einen visuellen / greifbaren Beweis, dass unser Prisma-Modell einen komplexen Baum (Wohnung im 1. Stock mit Inventar) abbilden kann und dass wir die Flächen/Kapazitäten rekursiv abfragen können.
|
||||||
|
|
||||||
|
## Der Plan: Ein Node.js Playground-Script
|
||||||
|
Bevor wir ein riesiges Frontend bauen, schreiben wir ein simples `seed.ts` oder `playground.ts` Skript, das die Prisma-API nutzt.
|
||||||
|
|
||||||
|
### Was das Script tun soll:
|
||||||
|
1. **Baum aufbauen:** Es erstellt das Grundstück, ein Haus, den 1. Stock und eine "Wohnung Sonnenaufgang" mit Küche, Bad und Schlafzimmern inklusive Betten und Besteck (als Child-Units).
|
||||||
|
2. **Personen anlegen:** Es erstellt eine Test-Person (z.B. "Sev") und speichert die "Ausweisnummer" testweise in unserer neuen `VaultData`-Tabelle, um die Trennung zu simulieren.
|
||||||
|
3. **Ein Event (Buchung) triggern:** Es verknüpft Sev mit der "Wohnung Sonnenaufgang" für ein Datum.
|
||||||
|
4. **Die Graph-Abfrage (Der Test):** Das Script führt die "Magic-Query" aus, die beweist, dass unser Ansatz funktioniert: Es fragt die Datenbank nach der Wohnung und rechnet rekursiv die Flächen der untergeordneten Räume und die Schlafplätze der Betten zusammen.
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
# RoggioApp - Trennung von "Gehirn" und "Muskeln"
|
||||||
|
|
||||||
|
## Die Philosophie
|
||||||
|
Die OpenClaw-Instanz (`192.168.20.199`) ist Claras Safe-Space. Hier läuft ihr Gedächtnis, ihre Konfiguration und die interne Steuerung.
|
||||||
|
Projekte (wie der API-Server, Datenbanken) dürfen hier *nicht* ausgeführt werden.
|
||||||
|
|
||||||
|
## Zukünftiger Standard-Workflow
|
||||||
|
1. Code wird von Clara in `/home/clara/.openclaw/workspace/projects` (was ein SMB-Mount auf die Docker-VM ist) geschrieben.
|
||||||
|
2. Anstatt Code mit `npx tsx` auf Claras Instanz auszuführen, nutzt Clara *ausschließlich* SSH, um den Code auf der Docker-VM (`192.168.20.252`) auszuführen.
|
||||||
|
3. Der Nginx Proxy Manager leitet Traffic immer auf die Docker-VM weiter.
|
||||||
|
|
||||||
|
*Folge:* Wenn ein API-Prototyp eine Endlosschleife hat oder RAM frisst, stürzt die Docker-VM ab. Clara bleibt aber zu 100% online und kann den Fehler beheben.
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
# RoggioApp - Infrastruktur: Gitea & Node.js Docker-Setup
|
||||||
|
|
||||||
|
## Die Notwendigkeit
|
||||||
|
Projekte wie RoggioApp oder unser API-Server sollten nicht einfach per `nohup` oder in `tmux`-Sessions auf der Docker-VM laufen. Das ist fehleranfällig und schlecht wartbar.
|
||||||
|
Zudem fehlt uns eine Versionskontrolle (Git) auf dem Server, um Code-Änderungen sicher nachverfolgen zu können, ohne Abhängigkeiten zu externen Anbietern (wie GitHub/GitLab) zu schaffen.
|
||||||
|
|
||||||
|
## Die Lösung: Das Infra-Compose-File
|
||||||
|
Wir erweitern unsere Sandkiste (`192.168.20.252`) um ein sauberes Infrastruktur-Compose-Setup:
|
||||||
|
|
||||||
|
### 1. Gitea (Lokales GitHub)
|
||||||
|
* *Warum:* Gitea ist extrem leichtgewichtig (geschrieben in Go), verbraucht kaum RAM und gibt uns eine professionelle Git-Oberfläche (Issues, Pull Requests, Code-Hosting) direkt im lokalen Netzwerk.
|
||||||
|
* *Setup:* Ein Container in der Docker-VM. Wir erstellen einen NPM-Proxy (z.B. `git.h80.11112222.net`).
|
||||||
|
|
||||||
|
### 2. Node.js Production-Serving (PM2 im Docker)
|
||||||
|
* *Warum:* Statt Node.js "nackt" auf dem Host (VM) laufen zu lassen, packen wir die Node.js-Applikationen in eigene Docker-Container.
|
||||||
|
* *Wie:* Für Test-API-Server (wie `bookme`) schreiben wir kleine `Dockerfile`s, die den Code bauen und per Node starten. Diese Container hängen wir dann in das Netzwerk des Nginx Proxy Managers.
|
||||||
|
|
||||||
|
## Nächste Schritte
|
||||||
|
1. Eine `docker-compose.yml` für Gitea anlegen und starten.
|
||||||
|
2. Die Subdomain `git.h80.11112222.net` im Nginx Proxy Manager eintragen.
|
||||||
|
3. Das RoggioApp-Projekt als erstes Repo initialisieren.
|
||||||
Executable
+146
@@ -0,0 +1,146 @@
|
|||||||
|
import { PrismaClient } from '@prisma/client'
|
||||||
|
|
||||||
|
const prisma = new PrismaClient()
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log('🧹 Lösche alte Daten für sauberen Testlauf...')
|
||||||
|
await prisma.event.deleteMany()
|
||||||
|
await prisma.groupMembership.deleteMany()
|
||||||
|
await prisma.group.deleteMany()
|
||||||
|
await prisma.person.deleteMany()
|
||||||
|
await prisma.unit.deleteMany()
|
||||||
|
await prisma.vaultData.deleteMany()
|
||||||
|
|
||||||
|
console.log('🏗️ Baue den Unit-Graphen (Grundstück -> Haus -> Wohnung -> Räume -> Inventar)...')
|
||||||
|
|
||||||
|
// 1. Grundstück
|
||||||
|
const campus = await prisma.unit.create({
|
||||||
|
data: {
|
||||||
|
name: 'Campus Hauptgelände',
|
||||||
|
traits: { type: 'campus' }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 2. Haus A
|
||||||
|
const houseA = await prisma.unit.create({
|
||||||
|
data: {
|
||||||
|
name: 'Haus A',
|
||||||
|
parentId: campus.id,
|
||||||
|
traits: { type: 'building' }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 3. Wohnung Sonnenaufgang (Rental Unit)
|
||||||
|
const apartment = await prisma.unit.create({
|
||||||
|
data: {
|
||||||
|
name: 'Wohnung Sonnenaufgang (1. Stock)',
|
||||||
|
parentId: houseA.id,
|
||||||
|
traits: {
|
||||||
|
type: 'apartment',
|
||||||
|
is_rentable: true,
|
||||||
|
base_price: 85.00
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 4. Räume der Wohnung
|
||||||
|
const kitchen = await prisma.unit.create({
|
||||||
|
data: { name: 'Küche', parentId: apartment.id, traits: { type: 'room', area_sqm: 12.0 } }
|
||||||
|
})
|
||||||
|
|
||||||
|
const bedroom1 = await prisma.unit.create({
|
||||||
|
data: { name: 'Schlafzimmer Groß', parentId: apartment.id, traits: { type: 'room', area_sqm: 18.5 } }
|
||||||
|
})
|
||||||
|
|
||||||
|
// 5. Inventar als Units (Sub-Units der Räume)
|
||||||
|
await prisma.unit.create({
|
||||||
|
data: { name: 'Doppelbett', parentId: bedroom1.id, traits: { type: 'inventory', inventory_category: 'bed', sleep_capacity: 2 } }
|
||||||
|
})
|
||||||
|
|
||||||
|
// 6. Test: Gasflaschen im Butanlager (außerhalb der Wohnung, direkt im Haus)
|
||||||
|
const storage = await prisma.unit.create({
|
||||||
|
data: { name: 'Butanlager', parentId: houseA.id, traits: { type: 'room', needs_maintenance: true } }
|
||||||
|
})
|
||||||
|
|
||||||
|
await prisma.unit.create({
|
||||||
|
data: { name: '5kg Butangas Vorrat', parentId: storage.id, traits: { type: 'stock', soll: 10, ist: 7, alert_limit: 3 } }
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('👤 Erstelle Person (Sev) und simuliere Vault...')
|
||||||
|
// Vault Data Simulation
|
||||||
|
const secureVaultEntry = await prisma.vaultData.create({
|
||||||
|
data: {
|
||||||
|
entityType: 'Person',
|
||||||
|
entityId: 'pending', // Wird nach Person-Erstellung aktualisiert
|
||||||
|
secureData: JSON.stringify({ passport: 'X123456789', iban: 'DE1234...' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const sev = await prisma.person.create({
|
||||||
|
data: {
|
||||||
|
firstName: 'Sev',
|
||||||
|
lastName: 'Atzinger',
|
||||||
|
email: 'sev@example.com',
|
||||||
|
traits: {
|
||||||
|
pronouns: 'er/ihm',
|
||||||
|
vault_id: secureVaultEntry.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await prisma.vaultData.update({ where: { id: secureVaultEntry.id }, data: { entityId: sev.id } })
|
||||||
|
|
||||||
|
console.log('📅 Simuliere Event: Sev bucht die Wohnung Sonnenaufgang...')
|
||||||
|
await prisma.event.create({
|
||||||
|
data: {
|
||||||
|
type: 'RENTAL',
|
||||||
|
status: 'CONFIRMED',
|
||||||
|
startTime: new Date('2026-08-14T15:00:00Z'),
|
||||||
|
endTime: new Date('2026-08-21T10:00:00Z'),
|
||||||
|
targetUnitId: apartment.id,
|
||||||
|
targetPersonId: sev.id,
|
||||||
|
payload: { guests: 2, total_price: 595.00 }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('\n--- 🔍 TEST: DYNAMISCHE ABFRAGE DER WOHNUNG ---')
|
||||||
|
// Wir holen die Wohnung und generieren die Kapazität aus den Kindern (Tiefen-Abfrage)
|
||||||
|
const apartmentWithChildren = await prisma.unit.findUnique({
|
||||||
|
where: { id: apartment.id },
|
||||||
|
include: {
|
||||||
|
children: {
|
||||||
|
include: { children: true } // Wir holen Räume UND deren Inventar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let totalArea = 0;
|
||||||
|
let totalSleepCapacity = 0;
|
||||||
|
|
||||||
|
apartmentWithChildren?.children.forEach(room => {
|
||||||
|
if (room.traits && typeof room.traits === 'object' && 'area_sqm' in room.traits) {
|
||||||
|
totalArea += (room.traits as any).area_sqm;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check Inventar im Raum
|
||||||
|
room.children.forEach(inventory => {
|
||||||
|
if (inventory.traits && typeof inventory.traits === 'object' && 'sleep_capacity' in inventory.traits) {
|
||||||
|
totalSleepCapacity += (inventory.traits as any).sleep_capacity;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`Wohnung: ${apartmentWithChildren?.name}`)
|
||||||
|
console.log(`Gesamtfläche: ${totalArea} qm (Berechnet aus den Räumen)`)
|
||||||
|
console.log(`Schlafplätze: ${totalSleepCapacity} (Berechnet aus den Betten)`)
|
||||||
|
console.log('------------------------------------------------\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
.catch(e => {
|
||||||
|
console.error(e)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
|
.finally(async () => {
|
||||||
|
await prisma.$disconnect()
|
||||||
|
})
|
||||||
Executable
+78
@@ -0,0 +1,78 @@
|
|||||||
|
import { serve } from '@hono/node-server'
|
||||||
|
import { Hono } from 'hono'
|
||||||
|
import { PrismaClient } from '@prisma/client'
|
||||||
|
|
||||||
|
const app = new Hono()
|
||||||
|
const prisma = new PrismaClient()
|
||||||
|
|
||||||
|
app.get('/', (c) => {
|
||||||
|
return c.html(`
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>BookMe - Roggio API</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: monospace; background: #282a36; color: #f8f8f2; padding: 2rem; }
|
||||||
|
h1 { color: #50fa7b; }
|
||||||
|
.unit { border: 1px solid #6272a4; padding: 1rem; margin-bottom: 1rem; border-radius: 8px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>BookMe API (RoggioApp MVP)</h1>
|
||||||
|
<p>Willkommen an der API-Schnittstelle. Test-Endpunkte:</p>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/api/apartments" style="color: #ff79c6;">/api/apartments</a> - Zeigt alle mietbaren Einheiten (dynamisch berechnet)</li>
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
app.get('/api/apartments', async (c) => {
|
||||||
|
const units = await prisma.unit.findMany({
|
||||||
|
include: {
|
||||||
|
children: {
|
||||||
|
include: { children: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const apartments = units.filter(u => {
|
||||||
|
const traits = u.traits as any
|
||||||
|
return traits && traits.is_rentable === true
|
||||||
|
}).map(apartment => {
|
||||||
|
let totalArea = 0;
|
||||||
|
let totalSleepCapacity = 0;
|
||||||
|
|
||||||
|
apartment.children.forEach(room => {
|
||||||
|
const rTraits = room.traits as any;
|
||||||
|
if (rTraits?.area_sqm) totalArea += rTraits.area_sqm;
|
||||||
|
|
||||||
|
room.children.forEach(inv => {
|
||||||
|
const iTraits = inv.traits as any;
|
||||||
|
if (iTraits?.sleep_capacity) totalSleepCapacity += iTraits.sleep_capacity;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: apartment.id,
|
||||||
|
name: apartment.name,
|
||||||
|
base_price: (apartment.traits as any).base_price,
|
||||||
|
calculated_stats: {
|
||||||
|
area_sqm: totalArea,
|
||||||
|
sleep_capacity: totalSleepCapacity
|
||||||
|
},
|
||||||
|
rooms: apartment.children.map(r => r.name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return c.json(apartments)
|
||||||
|
})
|
||||||
|
|
||||||
|
const port = 3000
|
||||||
|
console.log(`Server is running on port ${port}`)
|
||||||
|
|
||||||
|
serve({
|
||||||
|
fetch: app.fetch,
|
||||||
|
port,
|
||||||
|
hostname: '0.0.0.0'
|
||||||
|
})
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.default = void 0;var _config = await jitiImport("@prisma/config");var _default = exports.default =
|
||||||
|
|
||||||
|
(0, _config.defineConfig)({
|
||||||
|
earlyAccess: true,
|
||||||
|
schema: {
|
||||||
|
url: 'postgresql://roggio:roggio_secret@192.168.20.252:5432/roggiodb'
|
||||||
|
},
|
||||||
|
migrations: {
|
||||||
|
url: 'postgresql://roggio:roggio_secret@192.168.20.252:5432/roggiodb'
|
||||||
|
}
|
||||||
|
}); /* v9-fa9df8f6b9f1b8a7 */
|
||||||
+552
@@ -0,0 +1,552 @@
|
|||||||
|
{
|
||||||
|
"name": "roggioapp",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"node_modules/@esbuild/linux-x64": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@hono/node-server": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-n3GfHwwCvHCkGmOwKfxUPOlbfzuO64Sbc5XC4NGPIXxkuOnJrdgExdRKmHfF924r914WRJPT397GdqLvdYTeyQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"hono": "^4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/client": {
|
||||||
|
"version": "6.19.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.19.3.tgz",
|
||||||
|
"integrity": "sha512-mKq3jQFhjvko5LTJFHGilsuQs+W+T3Gm451NzuTDGQxwCzwXHYnIu2zGkRoW+Exq3Rob7yp2MfzSrdIiZVhrBg==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"prisma": "*",
|
||||||
|
"typescript": ">=5.1.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"prisma": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/config": {
|
||||||
|
"version": "7.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.8.0.tgz",
|
||||||
|
"integrity": "sha512-HFESzd9rx2ZQxlK+TL7tu1HPvCqrHiL6LCxYykI2c34mvaUuIVVl3lYuicJD/MNnzgPnyeBEMlK4WTomJCV5jw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"c12": "3.3.4",
|
||||||
|
"deepmerge-ts": "7.1.5",
|
||||||
|
"effect": "3.20.0",
|
||||||
|
"empathic": "2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/debug": {
|
||||||
|
"version": "6.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.4.1.tgz",
|
||||||
|
"integrity": "sha512-Q9xk6yjEGIThjSD8zZegxd5tBRNHYd13GOIG0nLsanbTXATiPXCLyvlYEfvbR2ft6dlRsziQXfQGxAgv7zcMUA==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/engines": {
|
||||||
|
"version": "6.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.4.1.tgz",
|
||||||
|
"integrity": "sha512-KldENzMHtKYwsOSLThghOIdXOBEsfDuGSrxAZjMnimBiDKd3AE4JQ+Kv+gBD/x77WoV9xIPf25GXMWffXZ17BA==",
|
||||||
|
"devOptional": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/debug": "6.4.1",
|
||||||
|
"@prisma/engines-version": "6.4.0-29.a9055b89e58b4b5bfb59600785423b1db3d0e75d",
|
||||||
|
"@prisma/fetch-engine": "6.4.1",
|
||||||
|
"@prisma/get-platform": "6.4.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/engines-version": {
|
||||||
|
"version": "6.4.0-29.a9055b89e58b4b5bfb59600785423b1db3d0e75d",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.4.0-29.a9055b89e58b4b5bfb59600785423b1db3d0e75d.tgz",
|
||||||
|
"integrity": "sha512-Xq54qw55vaCGrGgIJqyDwOq0TtjZPJEWsbQAHugk99hpDf2jcEeQhUcF+yzEsSqegBaDNLA4IC8Nn34sXmkiTQ==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/fetch-engine": {
|
||||||
|
"version": "6.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.4.1.tgz",
|
||||||
|
"integrity": "sha512-uZ5hVeTmDspx7KcaRCNoXmcReOD+84nwlO2oFvQPRQh9xiFYnnUKDz7l9bLxp8t4+25CsaNlgrgilXKSQwrIGQ==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/debug": "6.4.1",
|
||||||
|
"@prisma/engines-version": "6.4.0-29.a9055b89e58b4b5bfb59600785423b1db3d0e75d",
|
||||||
|
"@prisma/get-platform": "6.4.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/get-platform": {
|
||||||
|
"version": "6.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.4.1.tgz",
|
||||||
|
"integrity": "sha512-gXqZaDI5scDkBF8oza7fOD3Q3QMD0e0rBynlzDDZdTWbWmzjuW58PRZtj+jkvKje2+ZigCWkH8SsWZAsH6q1Yw==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/debug": "6.4.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@standard-schema/spec": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "25.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz",
|
||||||
|
"integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~7.19.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/c12": {
|
||||||
|
"version": "3.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz",
|
||||||
|
"integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"chokidar": "^5.0.0",
|
||||||
|
"confbox": "^0.2.4",
|
||||||
|
"defu": "^6.1.6",
|
||||||
|
"dotenv": "^17.3.1",
|
||||||
|
"exsolve": "^1.0.8",
|
||||||
|
"giget": "^3.2.0",
|
||||||
|
"jiti": "^2.6.1",
|
||||||
|
"ohash": "^2.0.11",
|
||||||
|
"pathe": "^2.0.3",
|
||||||
|
"perfect-debounce": "^2.1.0",
|
||||||
|
"pkg-types": "^2.3.0",
|
||||||
|
"rc9": "^3.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"magicast": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"magicast": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/chokidar": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"readdirp": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20.19.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/confbox": {
|
||||||
|
"version": "0.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz",
|
||||||
|
"integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/debug": {
|
||||||
|
"version": "4.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||||
|
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/deepmerge-ts": {
|
||||||
|
"version": "7.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz",
|
||||||
|
"integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/defu": {
|
||||||
|
"version": "6.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz",
|
||||||
|
"integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/destr": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/dotenv": {
|
||||||
|
"version": "17.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz",
|
||||||
|
"integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://dotenvx.com"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/effect": {
|
||||||
|
"version": "3.20.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/effect/-/effect-3.20.0.tgz",
|
||||||
|
"integrity": "sha512-qMLfDJscrNG8p/aw+IkT9W7fgj50Z4wG5bLBy0Txsxz8iUHjDIkOgO3SV0WZfnQbNG2VJYb0b+rDLMrhM4+Krw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@standard-schema/spec": "^1.0.0",
|
||||||
|
"fast-check": "^3.23.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/empathic": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild": {
|
||||||
|
"version": "0.27.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
|
||||||
|
"integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==",
|
||||||
|
"devOptional": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"esbuild": "bin/esbuild"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@esbuild/aix-ppc64": "0.27.7",
|
||||||
|
"@esbuild/android-arm": "0.27.7",
|
||||||
|
"@esbuild/android-arm64": "0.27.7",
|
||||||
|
"@esbuild/android-x64": "0.27.7",
|
||||||
|
"@esbuild/darwin-arm64": "0.27.7",
|
||||||
|
"@esbuild/darwin-x64": "0.27.7",
|
||||||
|
"@esbuild/freebsd-arm64": "0.27.7",
|
||||||
|
"@esbuild/freebsd-x64": "0.27.7",
|
||||||
|
"@esbuild/linux-arm": "0.27.7",
|
||||||
|
"@esbuild/linux-arm64": "0.27.7",
|
||||||
|
"@esbuild/linux-ia32": "0.27.7",
|
||||||
|
"@esbuild/linux-loong64": "0.27.7",
|
||||||
|
"@esbuild/linux-mips64el": "0.27.7",
|
||||||
|
"@esbuild/linux-ppc64": "0.27.7",
|
||||||
|
"@esbuild/linux-riscv64": "0.27.7",
|
||||||
|
"@esbuild/linux-s390x": "0.27.7",
|
||||||
|
"@esbuild/linux-x64": "0.27.7",
|
||||||
|
"@esbuild/netbsd-arm64": "0.27.7",
|
||||||
|
"@esbuild/netbsd-x64": "0.27.7",
|
||||||
|
"@esbuild/openbsd-arm64": "0.27.7",
|
||||||
|
"@esbuild/openbsd-x64": "0.27.7",
|
||||||
|
"@esbuild/openharmony-arm64": "0.27.7",
|
||||||
|
"@esbuild/sunos-x64": "0.27.7",
|
||||||
|
"@esbuild/win32-arm64": "0.27.7",
|
||||||
|
"@esbuild/win32-ia32": "0.27.7",
|
||||||
|
"@esbuild/win32-x64": "0.27.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-register": {
|
||||||
|
"version": "3.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz",
|
||||||
|
"integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "^4.3.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"esbuild": ">=0.12 <1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/exsolve": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/fast-check": {
|
||||||
|
"version": "3.23.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz",
|
||||||
|
"integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/dubzzz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/fast-check"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"pure-rand": "^6.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/get-tsconfig": {
|
||||||
|
"version": "4.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz",
|
||||||
|
"integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"resolve-pkg-maps": "^1.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/giget": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/giget/-/giget-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-GvHTWcykIR/fP8cj8dMpuMMkvaeJfPvYnhq0oW+chSeIr+ldX21ifU2Ms6KBoyKZQZmVaUAAhQ2EZ68KJF8a7A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"giget": "dist/cli.mjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/hono": {
|
||||||
|
"version": "4.12.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.15.tgz",
|
||||||
|
"integrity": "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jiti": {
|
||||||
|
"version": "2.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
|
||||||
|
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"jiti": "lib/jiti-cli.mjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ms": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/ohash": {
|
||||||
|
"version": "2.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz",
|
||||||
|
"integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/pathe": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/perfect-debounce": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/pkg-types": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"confbox": "^0.2.2",
|
||||||
|
"exsolve": "^1.0.7",
|
||||||
|
"pathe": "^2.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/prisma": {
|
||||||
|
"version": "6.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.4.1.tgz",
|
||||||
|
"integrity": "sha512-q2uJkgXnua/jj66mk6P9bX/zgYJFI/jn4Yp0aS6SPRrjH/n6VyOV7RDe1vHD0DX8Aanx4MvgmUPPoYnR6MJnPg==",
|
||||||
|
"devOptional": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/engines": "6.4.1",
|
||||||
|
"esbuild": ">=0.12 <1",
|
||||||
|
"esbuild-register": "3.6.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"prisma": "build/index.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.18"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "2.3.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": ">=5.1.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pure-rand": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/dubzzz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/fast-check"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/rc9": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/rc9/-/rc9-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"defu": "^6.1.6",
|
||||||
|
"destr": "^2.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/readdirp": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20.19.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/resolve-pkg-maps": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsx": {
|
||||||
|
"version": "4.21.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
|
||||||
|
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"esbuild": "~0.27.0",
|
||||||
|
"get-tsconfig": "^4.7.5"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"tsx": "dist/cli.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "~2.3.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "6.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
|
||||||
|
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
||||||
|
"devOptional": true,
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "7.19.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz",
|
||||||
|
"integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./index"
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
module.exports = { ...require('.') }
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./index"
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
module.exports = { ...require('#main-entry-point') }
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./default"
|
||||||
+356
File diff suppressed because one or more lines are too long
+346
@@ -0,0 +1,346 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
|
||||||
|
const {
|
||||||
|
Decimal,
|
||||||
|
objectEnumValues,
|
||||||
|
makeStrictEnum,
|
||||||
|
Public,
|
||||||
|
getRuntime,
|
||||||
|
skip
|
||||||
|
} = require('@prisma/client/runtime/index-browser.js')
|
||||||
|
|
||||||
|
|
||||||
|
const Prisma = {}
|
||||||
|
|
||||||
|
exports.Prisma = Prisma
|
||||||
|
exports.$Enums = {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prisma Client JS version: 6.19.3
|
||||||
|
* Query Engine version: a9055b89e58b4b5bfb59600785423b1db3d0e75d
|
||||||
|
*/
|
||||||
|
Prisma.prismaVersion = {
|
||||||
|
client: "6.19.3",
|
||||||
|
engine: "a9055b89e58b4b5bfb59600785423b1db3d0e75d"
|
||||||
|
}
|
||||||
|
|
||||||
|
Prisma.PrismaClientKnownRequestError = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`PrismaClientKnownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)};
|
||||||
|
Prisma.PrismaClientUnknownRequestError = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`PrismaClientUnknownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.PrismaClientRustPanicError = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`PrismaClientRustPanicError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.PrismaClientInitializationError = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`PrismaClientInitializationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.PrismaClientValidationError = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`PrismaClientValidationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.Decimal = Decimal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-export of sql-template-tag
|
||||||
|
*/
|
||||||
|
Prisma.sql = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`sqltag is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.empty = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`empty is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.join = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`join is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.raw = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`raw is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.validator = Public.validator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extensions
|
||||||
|
*/
|
||||||
|
Prisma.getExtensionContext = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`Extensions.getExtensionContext is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.defineExtension = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`Extensions.defineExtension is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shorthand utilities for JSON filtering
|
||||||
|
*/
|
||||||
|
Prisma.DbNull = objectEnumValues.instances.DbNull
|
||||||
|
Prisma.JsonNull = objectEnumValues.instances.JsonNull
|
||||||
|
Prisma.AnyNull = objectEnumValues.instances.AnyNull
|
||||||
|
|
||||||
|
Prisma.NullTypes = {
|
||||||
|
DbNull: objectEnumValues.classes.DbNull,
|
||||||
|
JsonNull: objectEnumValues.classes.JsonNull,
|
||||||
|
AnyNull: objectEnumValues.classes.AnyNull
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enums
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
|
||||||
|
ReadUncommitted: 'ReadUncommitted',
|
||||||
|
ReadCommitted: 'ReadCommitted',
|
||||||
|
RepeatableRead: 'RepeatableRead',
|
||||||
|
Serializable: 'Serializable'
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.Prisma.UnitScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
name: 'name',
|
||||||
|
description: 'description',
|
||||||
|
parentId: 'parentId',
|
||||||
|
traits: 'traits',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.PersonScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
firstName: 'firstName',
|
||||||
|
lastName: 'lastName',
|
||||||
|
email: 'email',
|
||||||
|
phone: 'phone',
|
||||||
|
ssoId: 'ssoId',
|
||||||
|
traits: 'traits',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.GroupScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
name: 'name',
|
||||||
|
traits: 'traits',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.GroupMembershipScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
personId: 'personId',
|
||||||
|
groupId: 'groupId',
|
||||||
|
role: 'role'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.EventScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
type: 'type',
|
||||||
|
status: 'status',
|
||||||
|
startTime: 'startTime',
|
||||||
|
endTime: 'endTime',
|
||||||
|
targetUnitId: 'targetUnitId',
|
||||||
|
targetGroupId: 'targetGroupId',
|
||||||
|
targetPersonId: 'targetPersonId',
|
||||||
|
payload: 'payload',
|
||||||
|
parentEventId: 'parentEventId',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.CitiesScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
name: 'name',
|
||||||
|
state_id: 'state_id',
|
||||||
|
state_code: 'state_code',
|
||||||
|
country_id: 'country_id',
|
||||||
|
country_code: 'country_code',
|
||||||
|
type: 'type',
|
||||||
|
level: 'level',
|
||||||
|
parent_id: 'parent_id',
|
||||||
|
latitude: 'latitude',
|
||||||
|
longitude: 'longitude',
|
||||||
|
native: 'native',
|
||||||
|
population: 'population',
|
||||||
|
timezone: 'timezone',
|
||||||
|
translations: 'translations',
|
||||||
|
created_at: 'created_at',
|
||||||
|
updated_at: 'updated_at',
|
||||||
|
flag: 'flag',
|
||||||
|
wikiDataId: 'wikiDataId'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.CountriesScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
name: 'name',
|
||||||
|
iso3: 'iso3',
|
||||||
|
numeric_code: 'numeric_code',
|
||||||
|
iso2: 'iso2',
|
||||||
|
phonecode: 'phonecode',
|
||||||
|
capital: 'capital',
|
||||||
|
currency: 'currency',
|
||||||
|
currency_name: 'currency_name',
|
||||||
|
currency_symbol: 'currency_symbol',
|
||||||
|
tld: 'tld',
|
||||||
|
native: 'native',
|
||||||
|
population: 'population',
|
||||||
|
gdp: 'gdp',
|
||||||
|
region: 'region',
|
||||||
|
region_id: 'region_id',
|
||||||
|
subregion: 'subregion',
|
||||||
|
subregion_id: 'subregion_id',
|
||||||
|
nationality: 'nationality',
|
||||||
|
area_sq_km: 'area_sq_km',
|
||||||
|
postal_code_format: 'postal_code_format',
|
||||||
|
postal_code_regex: 'postal_code_regex',
|
||||||
|
timezones: 'timezones',
|
||||||
|
translations: 'translations',
|
||||||
|
latitude: 'latitude',
|
||||||
|
longitude: 'longitude',
|
||||||
|
emoji: 'emoji',
|
||||||
|
emojiU: 'emojiU',
|
||||||
|
created_at: 'created_at',
|
||||||
|
updated_at: 'updated_at',
|
||||||
|
flag: 'flag',
|
||||||
|
wikiDataId: 'wikiDataId'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.StatesScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
name: 'name',
|
||||||
|
country_id: 'country_id',
|
||||||
|
country_code: 'country_code',
|
||||||
|
fips_code: 'fips_code',
|
||||||
|
iso2: 'iso2',
|
||||||
|
iso3166_2: 'iso3166_2',
|
||||||
|
type: 'type',
|
||||||
|
level: 'level',
|
||||||
|
parent_id: 'parent_id',
|
||||||
|
native: 'native',
|
||||||
|
latitude: 'latitude',
|
||||||
|
longitude: 'longitude',
|
||||||
|
timezone: 'timezone',
|
||||||
|
translations: 'translations',
|
||||||
|
created_at: 'created_at',
|
||||||
|
updated_at: 'updated_at',
|
||||||
|
flag: 'flag',
|
||||||
|
wikiDataId: 'wikiDataId',
|
||||||
|
population: 'population'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.AddressScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
street: 'street',
|
||||||
|
streetNo: 'streetNo',
|
||||||
|
postalCode: 'postalCode',
|
||||||
|
cityId: 'cityId',
|
||||||
|
stateId: 'stateId',
|
||||||
|
countryId: 'countryId',
|
||||||
|
rawCity: 'rawCity',
|
||||||
|
rawCountry: 'rawCountry',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.VaultDataScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
entityType: 'entityType',
|
||||||
|
entityId: 'entityId',
|
||||||
|
secureData: 'secureData',
|
||||||
|
createdAt: 'createdAt'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.SortOrder = {
|
||||||
|
asc: 'asc',
|
||||||
|
desc: 'desc'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.JsonNullValueInput = {
|
||||||
|
JsonNull: Prisma.JsonNull
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.QueryMode = {
|
||||||
|
default: 'default',
|
||||||
|
insensitive: 'insensitive'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.JsonNullValueFilter = {
|
||||||
|
DbNull: Prisma.DbNull,
|
||||||
|
JsonNull: Prisma.JsonNull,
|
||||||
|
AnyNull: Prisma.AnyNull
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.NullsOrder = {
|
||||||
|
first: 'first',
|
||||||
|
last: 'last'
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
exports.Prisma.ModelName = {
|
||||||
|
Unit: 'Unit',
|
||||||
|
Person: 'Person',
|
||||||
|
Group: 'Group',
|
||||||
|
GroupMembership: 'GroupMembership',
|
||||||
|
Event: 'Event',
|
||||||
|
cities: 'cities',
|
||||||
|
countries: 'countries',
|
||||||
|
states: 'states',
|
||||||
|
Address: 'Address',
|
||||||
|
VaultData: 'VaultData'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a stub Prisma Client that will error at runtime if called.
|
||||||
|
*/
|
||||||
|
class PrismaClient {
|
||||||
|
constructor() {
|
||||||
|
return new Proxy(this, {
|
||||||
|
get(target, prop) {
|
||||||
|
let message
|
||||||
|
const runtime = getRuntime()
|
||||||
|
if (runtime.isEdge) {
|
||||||
|
message = `PrismaClient is not configured to run in ${runtime.prettyName}. In order to run Prisma Client on edge runtime, either:
|
||||||
|
- Use Prisma Accelerate: https://pris.ly/d/accelerate
|
||||||
|
- Use Driver Adapters: https://pris.ly/d/driver-adapters
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
message = 'PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in `' + runtime.prettyName + '`).'
|
||||||
|
}
|
||||||
|
|
||||||
|
message += `
|
||||||
|
If this is unexpected, please open an issue: https://pris.ly/prisma-prisma-bug-report`
|
||||||
|
|
||||||
|
throw new Error(message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.PrismaClient = PrismaClient
|
||||||
|
|
||||||
|
Object.assign(exports, Prisma)
|
||||||
+19016
File diff suppressed because it is too large
Load Diff
+379
File diff suppressed because one or more lines are too long
Generated
Vendored
Executable
BIN
Binary file not shown.
+183
@@ -0,0 +1,183 @@
|
|||||||
|
{
|
||||||
|
"name": "prisma-client-d8a198d17d4a3c96c5647b2d68e00f04d7d64ee8576c926d0e499bf11ed9b4a9",
|
||||||
|
"main": "index.js",
|
||||||
|
"types": "index.d.ts",
|
||||||
|
"browser": "default.js",
|
||||||
|
"exports": {
|
||||||
|
"./client": {
|
||||||
|
"require": {
|
||||||
|
"node": "./index.js",
|
||||||
|
"edge-light": "./wasm.js",
|
||||||
|
"workerd": "./wasm.js",
|
||||||
|
"worker": "./wasm.js",
|
||||||
|
"browser": "./index-browser.js",
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"import": {
|
||||||
|
"node": "./index.js",
|
||||||
|
"edge-light": "./wasm.js",
|
||||||
|
"workerd": "./wasm.js",
|
||||||
|
"worker": "./wasm.js",
|
||||||
|
"browser": "./index-browser.js",
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"./package.json": "./package.json",
|
||||||
|
".": {
|
||||||
|
"require": {
|
||||||
|
"node": "./index.js",
|
||||||
|
"edge-light": "./wasm.js",
|
||||||
|
"workerd": "./wasm.js",
|
||||||
|
"worker": "./wasm.js",
|
||||||
|
"browser": "./index-browser.js",
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"import": {
|
||||||
|
"node": "./index.js",
|
||||||
|
"edge-light": "./wasm.js",
|
||||||
|
"workerd": "./wasm.js",
|
||||||
|
"worker": "./wasm.js",
|
||||||
|
"browser": "./index-browser.js",
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"./edge": {
|
||||||
|
"types": "./edge.d.ts",
|
||||||
|
"require": "./edge.js",
|
||||||
|
"import": "./edge.js",
|
||||||
|
"default": "./edge.js"
|
||||||
|
},
|
||||||
|
"./react-native": {
|
||||||
|
"types": "./react-native.d.ts",
|
||||||
|
"require": "./react-native.js",
|
||||||
|
"import": "./react-native.js",
|
||||||
|
"default": "./react-native.js"
|
||||||
|
},
|
||||||
|
"./extension": {
|
||||||
|
"types": "./extension.d.ts",
|
||||||
|
"require": "./extension.js",
|
||||||
|
"import": "./extension.js",
|
||||||
|
"default": "./extension.js"
|
||||||
|
},
|
||||||
|
"./index-browser": {
|
||||||
|
"types": "./index.d.ts",
|
||||||
|
"require": "./index-browser.js",
|
||||||
|
"import": "./index-browser.js",
|
||||||
|
"default": "./index-browser.js"
|
||||||
|
},
|
||||||
|
"./index": {
|
||||||
|
"types": "./index.d.ts",
|
||||||
|
"require": "./index.js",
|
||||||
|
"import": "./index.js",
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"./wasm": {
|
||||||
|
"types": "./wasm.d.ts",
|
||||||
|
"require": "./wasm.js",
|
||||||
|
"import": "./wasm.mjs",
|
||||||
|
"default": "./wasm.mjs"
|
||||||
|
},
|
||||||
|
"./runtime/client": {
|
||||||
|
"types": "./runtime/client.d.ts",
|
||||||
|
"node": {
|
||||||
|
"require": "./runtime/client.js",
|
||||||
|
"default": "./runtime/client.js"
|
||||||
|
},
|
||||||
|
"require": "./runtime/client.js",
|
||||||
|
"import": "./runtime/client.mjs",
|
||||||
|
"default": "./runtime/client.mjs"
|
||||||
|
},
|
||||||
|
"./runtime/library": {
|
||||||
|
"types": "./runtime/library.d.ts",
|
||||||
|
"require": "./runtime/library.js",
|
||||||
|
"import": "./runtime/library.mjs",
|
||||||
|
"default": "./runtime/library.mjs"
|
||||||
|
},
|
||||||
|
"./runtime/binary": {
|
||||||
|
"types": "./runtime/binary.d.ts",
|
||||||
|
"require": "./runtime/binary.js",
|
||||||
|
"import": "./runtime/binary.mjs",
|
||||||
|
"default": "./runtime/binary.mjs"
|
||||||
|
},
|
||||||
|
"./runtime/wasm-engine-edge": {
|
||||||
|
"types": "./runtime/wasm-engine-edge.d.ts",
|
||||||
|
"require": "./runtime/wasm-engine-edge.js",
|
||||||
|
"import": "./runtime/wasm-engine-edge.mjs",
|
||||||
|
"default": "./runtime/wasm-engine-edge.mjs"
|
||||||
|
},
|
||||||
|
"./runtime/wasm-compiler-edge": {
|
||||||
|
"types": "./runtime/wasm-compiler-edge.d.ts",
|
||||||
|
"require": "./runtime/wasm-compiler-edge.js",
|
||||||
|
"import": "./runtime/wasm-compiler-edge.mjs",
|
||||||
|
"default": "./runtime/wasm-compiler-edge.mjs"
|
||||||
|
},
|
||||||
|
"./runtime/edge": {
|
||||||
|
"types": "./runtime/edge.d.ts",
|
||||||
|
"require": "./runtime/edge.js",
|
||||||
|
"import": "./runtime/edge-esm.js",
|
||||||
|
"default": "./runtime/edge-esm.js"
|
||||||
|
},
|
||||||
|
"./runtime/react-native": {
|
||||||
|
"types": "./runtime/react-native.d.ts",
|
||||||
|
"require": "./runtime/react-native.js",
|
||||||
|
"import": "./runtime/react-native.js",
|
||||||
|
"default": "./runtime/react-native.js"
|
||||||
|
},
|
||||||
|
"./runtime/index-browser": {
|
||||||
|
"types": "./runtime/index-browser.d.ts",
|
||||||
|
"require": "./runtime/index-browser.js",
|
||||||
|
"import": "./runtime/index-browser.mjs",
|
||||||
|
"default": "./runtime/index-browser.mjs"
|
||||||
|
},
|
||||||
|
"./generator-build": {
|
||||||
|
"require": "./generator-build/index.js",
|
||||||
|
"import": "./generator-build/index.js",
|
||||||
|
"default": "./generator-build/index.js"
|
||||||
|
},
|
||||||
|
"./sql": {
|
||||||
|
"require": {
|
||||||
|
"types": "./sql.d.ts",
|
||||||
|
"node": "./sql.js",
|
||||||
|
"default": "./sql.js"
|
||||||
|
},
|
||||||
|
"import": {
|
||||||
|
"types": "./sql.d.ts",
|
||||||
|
"node": "./sql.mjs",
|
||||||
|
"default": "./sql.mjs"
|
||||||
|
},
|
||||||
|
"default": "./sql.js"
|
||||||
|
},
|
||||||
|
"./*": "./*"
|
||||||
|
},
|
||||||
|
"version": "6.19.3",
|
||||||
|
"sideEffects": false,
|
||||||
|
"imports": {
|
||||||
|
"#wasm-engine-loader": {
|
||||||
|
"edge-light": "./wasm-edge-light-loader.mjs",
|
||||||
|
"workerd": "./wasm-worker-loader.mjs",
|
||||||
|
"worker": "./wasm-worker-loader.mjs",
|
||||||
|
"default": "./wasm-worker-loader.mjs"
|
||||||
|
},
|
||||||
|
"#main-entry-point": {
|
||||||
|
"require": {
|
||||||
|
"node": "./index.js",
|
||||||
|
"edge-light": "./wasm.js",
|
||||||
|
"workerd": "./wasm.js",
|
||||||
|
"worker": "./wasm.js",
|
||||||
|
"browser": "./index-browser.js",
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"import": {
|
||||||
|
"node": "./index.js",
|
||||||
|
"edge-light": "./wasm.js",
|
||||||
|
"workerd": "./wasm.js",
|
||||||
|
"worker": "./wasm.js",
|
||||||
|
"browser": "./index-browser.js",
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"default": "./index.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
File diff suppressed because one or more lines are too long
BIN
Binary file not shown.
+199
@@ -0,0 +1,199 @@
|
|||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
url = "postgresql://roggio:roggio_secret@192.168.20.252:5432/roggiodb"
|
||||||
|
}
|
||||||
|
|
||||||
|
model Unit {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
name String @db.VarChar(255)
|
||||||
|
description String?
|
||||||
|
parentId String?
|
||||||
|
traits Json @default("{}")
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
parent Unit? @relation("UnitHierarchy", fields: [parentId], references: [id])
|
||||||
|
children Unit[] @relation("UnitHierarchy")
|
||||||
|
}
|
||||||
|
|
||||||
|
model Person {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
firstName String @db.VarChar(100)
|
||||||
|
lastName String @db.VarChar(100)
|
||||||
|
email String? @unique @db.VarChar(255)
|
||||||
|
phone String? @db.VarChar(50)
|
||||||
|
ssoId String? @unique @db.VarChar(255)
|
||||||
|
traits Json @default("{}")
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
groupMemberships GroupMembership[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model Group {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
name String @db.VarChar(255)
|
||||||
|
traits Json @default("{}")
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
members GroupMembership[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model GroupMembership {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
personId String
|
||||||
|
groupId String
|
||||||
|
role String @db.VarChar(50)
|
||||||
|
group Group @relation(fields: [groupId], references: [id])
|
||||||
|
person Person @relation(fields: [personId], references: [id])
|
||||||
|
|
||||||
|
@@unique([personId, groupId, role])
|
||||||
|
}
|
||||||
|
|
||||||
|
model Event {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
type String @db.VarChar(50)
|
||||||
|
status String @db.VarChar(50)
|
||||||
|
startTime DateTime
|
||||||
|
endTime DateTime?
|
||||||
|
targetUnitId String?
|
||||||
|
targetGroupId String?
|
||||||
|
targetPersonId String?
|
||||||
|
payload Json @default("{}")
|
||||||
|
parentEventId String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments
|
||||||
|
model cities {
|
||||||
|
id BigInt @id @default(autoincrement())
|
||||||
|
name String @db.VarChar(255)
|
||||||
|
state_id BigInt
|
||||||
|
state_code String @db.VarChar(255)
|
||||||
|
country_id BigInt
|
||||||
|
country_code String @db.Char(2)
|
||||||
|
type String? @db.VarChar(191)
|
||||||
|
level Int?
|
||||||
|
parent_id BigInt?
|
||||||
|
latitude Decimal @db.Decimal(10, 8)
|
||||||
|
longitude Decimal @db.Decimal(11, 8)
|
||||||
|
native String? @db.VarChar(255)
|
||||||
|
population BigInt?
|
||||||
|
timezone String? @db.VarChar(255)
|
||||||
|
translations String?
|
||||||
|
created_at DateTime @default(dbgenerated("'2014-01-01 12:01:01'::timestamp without time zone")) @db.Timestamp(6)
|
||||||
|
updated_at DateTime @default(now()) @db.Timestamp(6)
|
||||||
|
flag Int @default(1) @db.SmallInt
|
||||||
|
wikiDataId String? @db.VarChar(255)
|
||||||
|
countries countries @relation(fields: [country_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||||
|
states states @relation(fields: [state_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||||
|
|
||||||
|
@@index([country_id])
|
||||||
|
@@index([state_id])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments
|
||||||
|
model countries {
|
||||||
|
id BigInt @id @default(autoincrement())
|
||||||
|
name String @db.VarChar(100)
|
||||||
|
iso3 String? @db.Char(3)
|
||||||
|
numeric_code String? @db.Char(3)
|
||||||
|
iso2 String? @db.Char(2)
|
||||||
|
phonecode String? @db.VarChar(255)
|
||||||
|
capital String? @db.VarChar(255)
|
||||||
|
currency String? @db.VarChar(255)
|
||||||
|
currency_name String? @db.VarChar(255)
|
||||||
|
currency_symbol String? @db.VarChar(255)
|
||||||
|
tld String? @db.VarChar(255)
|
||||||
|
native String? @db.VarChar(255)
|
||||||
|
population BigInt?
|
||||||
|
gdp BigInt?
|
||||||
|
region String? @db.VarChar(255)
|
||||||
|
region_id BigInt?
|
||||||
|
subregion String? @db.VarChar(255)
|
||||||
|
subregion_id BigInt?
|
||||||
|
nationality String? @db.VarChar(255)
|
||||||
|
area_sq_km Float?
|
||||||
|
postal_code_format String? @db.VarChar(255)
|
||||||
|
postal_code_regex String? @db.VarChar(255)
|
||||||
|
timezones String?
|
||||||
|
translations String?
|
||||||
|
latitude Decimal? @db.Decimal(10, 8)
|
||||||
|
longitude Decimal? @db.Decimal(11, 8)
|
||||||
|
emoji String? @db.VarChar(191)
|
||||||
|
emojiU String? @db.VarChar(191)
|
||||||
|
created_at DateTime? @db.Timestamp(6)
|
||||||
|
updated_at DateTime @default(now()) @db.Timestamp(6)
|
||||||
|
flag Int @default(1) @db.SmallInt
|
||||||
|
wikiDataId String? @db.VarChar(255)
|
||||||
|
cities cities[]
|
||||||
|
states states[]
|
||||||
|
|
||||||
|
@@index([region_id])
|
||||||
|
@@index([subregion_id])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments
|
||||||
|
model states {
|
||||||
|
id BigInt @id @default(autoincrement())
|
||||||
|
name String @db.VarChar(255)
|
||||||
|
country_id BigInt
|
||||||
|
country_code String @db.Char(2)
|
||||||
|
fips_code String? @db.VarChar(255)
|
||||||
|
iso2 String? @db.VarChar(255)
|
||||||
|
iso3166_2 String? @db.VarChar(10)
|
||||||
|
type String? @db.VarChar(191)
|
||||||
|
level Int?
|
||||||
|
parent_id BigInt?
|
||||||
|
native String? @db.VarChar(255)
|
||||||
|
latitude Decimal? @db.Decimal(10, 8)
|
||||||
|
longitude Decimal? @db.Decimal(11, 8)
|
||||||
|
timezone String? @db.VarChar(255)
|
||||||
|
translations String?
|
||||||
|
created_at DateTime? @db.Timestamp(6)
|
||||||
|
updated_at DateTime @default(now()) @db.Timestamp(6)
|
||||||
|
flag Int @default(1) @db.SmallInt
|
||||||
|
wikiDataId String? @db.VarChar(255)
|
||||||
|
population String? @db.VarChar(255)
|
||||||
|
cities cities[]
|
||||||
|
countries countries @relation(fields: [country_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||||
|
|
||||||
|
@@index([country_id])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// ADRESSEN (Shared Locations)
|
||||||
|
// ============================================================================
|
||||||
|
model Address {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
street String @db.VarChar(255)
|
||||||
|
streetNo String? @db.VarChar(50)
|
||||||
|
postalCode String? @db.VarChar(50)
|
||||||
|
|
||||||
|
cityId Int?
|
||||||
|
stateId Int?
|
||||||
|
countryId Int?
|
||||||
|
|
||||||
|
rawCity String? @db.VarChar(255)
|
||||||
|
rawCountry String? @db.VarChar(255)
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// DER VAULT (Logisch getrennt - Später On-Premise)
|
||||||
|
// ============================================================================
|
||||||
|
model VaultData {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
entityType String @db.VarChar(50)
|
||||||
|
entityId String
|
||||||
|
|
||||||
|
// Die sensiblen Daten (sollten auf App-Ebene verschlüsselt werden)
|
||||||
|
secureData String @db.Text
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
export default import('./query_engine_bg.wasm?module')
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
export default import('./query_engine_bg.wasm')
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./default"
|
||||||
+363
File diff suppressed because one or more lines are too long
+3
@@ -0,0 +1,3 @@
|
|||||||
|
# esbuild
|
||||||
|
|
||||||
|
This is the Linux 64-bit binary for esbuild, a JavaScript bundler and minifier. See https://github.com/evanw/esbuild for details.
|
||||||
BIN
Binary file not shown.
+20
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "@esbuild/linux-x64",
|
||||||
|
"version": "0.27.7",
|
||||||
|
"description": "The Linux 64-bit binary for esbuild, a JavaScript bundler.",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/evanw/esbuild.git"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"preferUnplugged": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
]
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 - present, Yusuke Wada and Hono contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
+396
@@ -0,0 +1,396 @@
|
|||||||
|
# Node.js Adapter for Hono
|
||||||
|
|
||||||
|
This adapter `@hono/node-server` allows you to run your Hono application on Node.js.
|
||||||
|
Initially, Hono wasn't designed for Node.js, but with this adapter, you can now use Hono on Node.js. It utilizes web standard APIs implemented in Node.js.
|
||||||
|
|
||||||
|
## Benchmarks
|
||||||
|
|
||||||
|
Hono is 4.1 times faster than Express.
|
||||||
|
|
||||||
|
Express:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
$ bombardier -d 10s --fasthttp http://localhost:3000/
|
||||||
|
|
||||||
|
Statistics Avg Stdev Max
|
||||||
|
Reqs/sec 20803.37 1713.06 24910.85
|
||||||
|
Latency 6.01ms 5.21ms 451.37ms
|
||||||
|
HTTP codes:
|
||||||
|
1xx - 0, 2xx - 208131, 3xx - 0, 4xx - 0, 5xx - 0
|
||||||
|
others - 0
|
||||||
|
Throughput: 5.75MB/s
|
||||||
|
```
|
||||||
|
|
||||||
|
Hono + `@hono/node-server`:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
$ bombardier -d 10s --fasthttp http://localhost:3000/
|
||||||
|
|
||||||
|
Statistics Avg Stdev Max
|
||||||
|
Reqs/sec 85405.51 7250.65 102658.51
|
||||||
|
Latency 1.46ms 1.00ms 149.95ms
|
||||||
|
HTTP codes:
|
||||||
|
1xx - 0, 2xx - 854120, 3xx - 0, 4xx - 0, 5xx - 0
|
||||||
|
others - 0
|
||||||
|
Throughput: 18.49MB/s
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
It works on Node.js versions greater than 20.x.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
You can install it from the npm registry with `npm` command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install @hono/node-server
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use `yarn`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yarn add @hono/node-server
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Just import `@hono/node-server` at the top and write the code as usual.
|
||||||
|
The same code that runs on Cloudflare Workers, Deno, and Bun will work.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { serve } from '@hono/node-server'
|
||||||
|
import { Hono } from 'hono'
|
||||||
|
|
||||||
|
const app = new Hono()
|
||||||
|
app.get('/', (c) => c.text('Hono meets Node.js'))
|
||||||
|
|
||||||
|
serve(app, (info) => {
|
||||||
|
console.log(`Listening on http://localhost:${info.port}`) // Listening on http://localhost:3000
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## WebSocket
|
||||||
|
|
||||||
|
You can upgrade WebSocket connections with `upgradeWebSocket` from `@hono/node-server`.
|
||||||
|
To enable this, install `ws` (and `@types/ws`) in your project, then create and provide a `WebSocketServer` as shown in the example below.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { serve, upgradeWebSocket } from '@hono/node-server'
|
||||||
|
import { WebSocketServer } from 'ws'
|
||||||
|
import { Hono } from 'hono'
|
||||||
|
|
||||||
|
const app = new Hono()
|
||||||
|
|
||||||
|
app.get(
|
||||||
|
'/ws',
|
||||||
|
upgradeWebSocket(() => ({
|
||||||
|
onMessage(event, ws) {
|
||||||
|
ws.send(event.data)
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
const wss = new WebSocketServer({ noServer: true }) // important to create with `noServer: true`
|
||||||
|
serve({
|
||||||
|
fetch: app.fetch,
|
||||||
|
websocket: { server: wss },
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
For example, run it using `ts-node`. Then an HTTP server will be launched. The default port is `3000`.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ts-node ./index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Open `http://localhost:3000` with your browser.
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
### `port`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
serve({
|
||||||
|
fetch: app.fetch,
|
||||||
|
port: 8787, // Port number, default is 3000
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### `createServer`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { createServer } from 'node:https'
|
||||||
|
import fs from 'node:fs'
|
||||||
|
|
||||||
|
//...
|
||||||
|
|
||||||
|
serve({
|
||||||
|
fetch: app.fetch,
|
||||||
|
createServer: createServer,
|
||||||
|
serverOptions: {
|
||||||
|
key: fs.readFileSync('test/fixtures/keys/agent1-key.pem'),
|
||||||
|
cert: fs.readFileSync('test/fixtures/keys/agent1-cert.pem'),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### `overrideGlobalObjects`
|
||||||
|
|
||||||
|
The default value is `true`. The Node.js Adapter rewrites the global Request/Response and uses a lightweight Request/Response to improve performance. If you don't want to do that, set `false`.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
serve({
|
||||||
|
fetch: app.fetch,
|
||||||
|
overrideGlobalObjects: false,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### `autoCleanupIncoming`
|
||||||
|
|
||||||
|
The default value is `true`. The Node.js Adapter automatically cleans up (explicitly call `destroy()` method) if application is not finished to consume the incoming request. If you don't want to do that, set `false`.
|
||||||
|
|
||||||
|
If the application accepts connections from arbitrary clients, this cleanup must be done otherwise incomplete requests from clients may cause the application to stop responding. If your application only accepts connections from trusted clients, such as in a reverse proxy environment and there is no process that returns a response without reading the body of the POST request all the way through, you can improve performance by setting it to `false`.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
serve({
|
||||||
|
fetch: app.fetch,
|
||||||
|
autoCleanupIncoming: false,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### `websocket`
|
||||||
|
|
||||||
|
provide a websocket server to enable websocket support.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { serve, upgradeWebSocket } from '@hono/node-server'
|
||||||
|
import { WebSocketServer } from 'ws'
|
||||||
|
|
||||||
|
// ...
|
||||||
|
const wss = new WebSocketServer({ noServer: true })
|
||||||
|
|
||||||
|
serve({
|
||||||
|
fetch: app.fetch,
|
||||||
|
websocket: { server: wss },
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Middleware
|
||||||
|
|
||||||
|
Most built-in middleware also works with Node.js.
|
||||||
|
Read [the documentation](https://hono.dev/middleware/builtin/basic-auth) and use the Middleware of your liking.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { serve } from '@hono/node-server'
|
||||||
|
import { Hono } from 'hono'
|
||||||
|
import { prettyJSON } from 'hono/pretty-json'
|
||||||
|
|
||||||
|
const app = new Hono()
|
||||||
|
|
||||||
|
app.get('*', prettyJSON())
|
||||||
|
app.get('/', (c) => c.json({ 'Hono meets': 'Node.js' }))
|
||||||
|
|
||||||
|
serve(app)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Serve Static Middleware
|
||||||
|
|
||||||
|
Use Serve Static Middleware that has been created for Node.js.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { serveStatic } from '@hono/node-server/serve-static'
|
||||||
|
|
||||||
|
//...
|
||||||
|
|
||||||
|
app.use('/static/*', serveStatic({ root: './' }))
|
||||||
|
```
|
||||||
|
|
||||||
|
If using a relative path, `root` will be relative to the current working directory from which the app was started.
|
||||||
|
|
||||||
|
This can cause confusion when running your application locally.
|
||||||
|
|
||||||
|
Imagine your project structure is:
|
||||||
|
|
||||||
|
```
|
||||||
|
my-hono-project/
|
||||||
|
src/
|
||||||
|
index.ts
|
||||||
|
static/
|
||||||
|
index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
Typically, you would run your app from the project's root directory (`my-hono-project`),
|
||||||
|
so you would need the following code to serve the `static` folder:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
app.use('/static/*', serveStatic({ root: './static' }))
|
||||||
|
```
|
||||||
|
|
||||||
|
Notice that `root` here is not relative to `src/index.ts`, rather to `my-hono-project`.
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
#### `rewriteRequestPath`
|
||||||
|
|
||||||
|
If you want to serve files in `./.foojs` with the request path `/__foo/*`, you can write like the following.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
app.use(
|
||||||
|
'/__foo/*',
|
||||||
|
serveStatic({
|
||||||
|
root: './.foojs/',
|
||||||
|
rewriteRequestPath: (path: string) => path.replace(/^\/__foo/, ''),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `onFound`
|
||||||
|
|
||||||
|
You can specify handling when the requested file is found with `onFound`.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
app.use(
|
||||||
|
'/static/*',
|
||||||
|
serveStatic({
|
||||||
|
// ...
|
||||||
|
onFound: (_path, c) => {
|
||||||
|
c.header('Cache-Control', `public, immutable, max-age=31536000`)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `onNotFound`
|
||||||
|
|
||||||
|
The `onNotFound` is useful for debugging. You can write a handle for when a file is not found.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
app.use(
|
||||||
|
'/static/*',
|
||||||
|
serveStatic({
|
||||||
|
root: './non-existent-dir',
|
||||||
|
onNotFound: (path, c) => {
|
||||||
|
console.log(`${path} is not found, request to ${c.req.path}`)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `precompressed`
|
||||||
|
|
||||||
|
The `precompressed` option checks if files with extensions like `.br` or `.gz` are available and serves them based on the `Accept-Encoding` header. It prioritizes Brotli, then Zstd, and Gzip. If none are available, it serves the original file.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
app.use(
|
||||||
|
'/static/*',
|
||||||
|
serveStatic({
|
||||||
|
precompressed: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## ConnInfo Helper
|
||||||
|
|
||||||
|
You can use the [ConnInfo Helper](https://hono.dev/docs/helpers/conninfo) by importing `getConnInfo` from `@hono/node-server/conninfo`.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { getConnInfo } from '@hono/node-server/conninfo'
|
||||||
|
|
||||||
|
app.get('/', (c) => {
|
||||||
|
const info = getConnInfo(c) // info is `ConnInfo`
|
||||||
|
return c.text(`Your remote address is ${info.remote.address}`)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Accessing Node.js API
|
||||||
|
|
||||||
|
You can access the Node.js API from `c.env` in Node.js. For example, if you want to specify a type, you can write the following.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { serve } from '@hono/node-server'
|
||||||
|
import type { HttpBindings } from '@hono/node-server'
|
||||||
|
import { Hono } from 'hono'
|
||||||
|
|
||||||
|
const app = new Hono<{ Bindings: HttpBindings }>()
|
||||||
|
|
||||||
|
app.get('/', (c) => {
|
||||||
|
return c.json({
|
||||||
|
remoteAddress: c.env.incoming.socket.remoteAddress,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
serve(app)
|
||||||
|
```
|
||||||
|
|
||||||
|
The APIs that you can get from `c.env` are as follows.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type HttpBindings = {
|
||||||
|
incoming: IncomingMessage
|
||||||
|
outgoing: ServerResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
type Http2Bindings = {
|
||||||
|
incoming: Http2ServerRequest
|
||||||
|
outgoing: Http2ServerResponse
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Direct response from Node.js API
|
||||||
|
|
||||||
|
You can directly respond to the client from the Node.js API.
|
||||||
|
In that case, the response from Hono should be ignored, so return `RESPONSE_ALREADY_SENT`.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> This feature can be used when migrating existing Node.js applications to Hono, but we recommend using Hono's API for new applications.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { serve } from '@hono/node-server'
|
||||||
|
import type { HttpBindings } from '@hono/node-server'
|
||||||
|
import { RESPONSE_ALREADY_SENT } from '@hono/node-server/utils/response'
|
||||||
|
import { Hono } from 'hono'
|
||||||
|
|
||||||
|
const app = new Hono<{ Bindings: HttpBindings }>()
|
||||||
|
|
||||||
|
app.get('/', (c) => {
|
||||||
|
const { outgoing } = c.env
|
||||||
|
outgoing.writeHead(200, { 'Content-Type': 'text/plain' })
|
||||||
|
outgoing.end('Hello World\n')
|
||||||
|
|
||||||
|
return RESPONSE_ALREADY_SENT
|
||||||
|
})
|
||||||
|
|
||||||
|
serve(app)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Listen to a UNIX domain socket
|
||||||
|
|
||||||
|
You can configure the HTTP server to listen to a UNIX domain socket instead of a TCP port.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { createAdaptorServer } from '@hono/node-server'
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
const socketPath = '/tmp/example.sock'
|
||||||
|
|
||||||
|
const server = createAdaptorServer(app)
|
||||||
|
server.listen(socketPath, () => {
|
||||||
|
console.log(`Listening on ${socketPath}`)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Related projects
|
||||||
|
|
||||||
|
- Hono - <https://hono.dev>
|
||||||
|
- Hono GitHub repository - <https://github.com/honojs/hono>
|
||||||
|
|
||||||
|
## Authors
|
||||||
|
|
||||||
|
- Yusuke Wada <https://github.com/yusukebe>
|
||||||
|
- Taku Amano <https://github.com/usualoma>
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||||||
|
|
||||||
|
//#region src/conninfo.ts
|
||||||
|
/**
|
||||||
|
* ConnInfo Helper for Node.js
|
||||||
|
* @param c Context
|
||||||
|
* @returns ConnInfo
|
||||||
|
*/
|
||||||
|
const getConnInfo = (c) => {
|
||||||
|
const bindings = c.env.server ? c.env.server : c.env;
|
||||||
|
const address = bindings.incoming.socket.remoteAddress;
|
||||||
|
const port = bindings.incoming.socket.remotePort;
|
||||||
|
const family = bindings.incoming.socket.remoteFamily;
|
||||||
|
return { remote: {
|
||||||
|
address,
|
||||||
|
port,
|
||||||
|
addressType: family === "IPv4" ? "IPv4" : family === "IPv6" ? "IPv6" : void 0
|
||||||
|
} };
|
||||||
|
};
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
exports.getConnInfo = getConnInfo;
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
import { GetConnInfo } from "hono/conninfo";
|
||||||
|
|
||||||
|
//#region src/conninfo.d.ts
|
||||||
|
/**
|
||||||
|
* ConnInfo Helper for Node.js
|
||||||
|
* @param c Context
|
||||||
|
* @returns ConnInfo
|
||||||
|
*/
|
||||||
|
declare const getConnInfo: GetConnInfo;
|
||||||
|
//#endregion
|
||||||
|
export { getConnInfo };
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
import { GetConnInfo } from "hono/conninfo";
|
||||||
|
|
||||||
|
//#region src/conninfo.d.ts
|
||||||
|
/**
|
||||||
|
* ConnInfo Helper for Node.js
|
||||||
|
* @param c Context
|
||||||
|
* @returns ConnInfo
|
||||||
|
*/
|
||||||
|
declare const getConnInfo: GetConnInfo;
|
||||||
|
//#endregion
|
||||||
|
export { getConnInfo };
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
//#region src/conninfo.ts
|
||||||
|
/**
|
||||||
|
* ConnInfo Helper for Node.js
|
||||||
|
* @param c Context
|
||||||
|
* @returns ConnInfo
|
||||||
|
*/
|
||||||
|
const getConnInfo = (c) => {
|
||||||
|
const bindings = c.env.server ? c.env.server : c.env;
|
||||||
|
const address = bindings.incoming.socket.remoteAddress;
|
||||||
|
const port = bindings.incoming.socket.remotePort;
|
||||||
|
const family = bindings.incoming.socket.remoteFamily;
|
||||||
|
return { remote: {
|
||||||
|
address,
|
||||||
|
port,
|
||||||
|
addressType: family === "IPv4" ? "IPv4" : family === "IPv6" ? "IPv6" : void 0
|
||||||
|
} };
|
||||||
|
};
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
export { getConnInfo };
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
//#region src/utils/response/constants.ts
|
||||||
|
const X_ALREADY_SENT = "x-hono-already-sent";
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
export { X_ALREADY_SENT as t };
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
//#region src/utils/response/constants.ts
|
||||||
|
const X_ALREADY_SENT = "x-hono-already-sent";
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
Object.defineProperty(exports, 'X_ALREADY_SENT', {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return X_ALREADY_SENT;
|
||||||
|
}
|
||||||
|
});
|
||||||
+1006
File diff suppressed because it is too large
Load Diff
+73
@@ -0,0 +1,73 @@
|
|||||||
|
import { AddressInfo } from "node:net";
|
||||||
|
import { WebSocket, WebSocketServer } from "ws";
|
||||||
|
import { IncomingMessage, Server, ServerOptions, ServerResponse, createServer } from "node:http";
|
||||||
|
import { Http2SecureServer, Http2Server, Http2ServerRequest, Http2ServerResponse, SecureServerOptions, ServerOptions as ServerOptions$1, createSecureServer, createServer as createServer$1 } from "node:http2";
|
||||||
|
import { ServerOptions as ServerOptions$2, createServer as createServer$2 } from "node:https";
|
||||||
|
import { UpgradeWebSocket } from "hono/ws";
|
||||||
|
|
||||||
|
//#region src/types.d.ts
|
||||||
|
type HttpBindings = {
|
||||||
|
incoming: IncomingMessage;
|
||||||
|
outgoing: ServerResponse;
|
||||||
|
};
|
||||||
|
type Http2Bindings = {
|
||||||
|
incoming: Http2ServerRequest;
|
||||||
|
outgoing: Http2ServerResponse;
|
||||||
|
};
|
||||||
|
type FetchCallback = (request: Request, env: HttpBindings | Http2Bindings) => Promise<unknown> | unknown;
|
||||||
|
type ServerType = Server | Http2Server | Http2SecureServer;
|
||||||
|
type createHttpOptions = {
|
||||||
|
serverOptions?: ServerOptions;
|
||||||
|
createServer?: typeof createServer;
|
||||||
|
};
|
||||||
|
type createHttpsOptions = {
|
||||||
|
serverOptions?: ServerOptions$2;
|
||||||
|
createServer?: typeof createServer$2;
|
||||||
|
};
|
||||||
|
type createHttp2Options = {
|
||||||
|
serverOptions?: ServerOptions$1;
|
||||||
|
createServer?: typeof createServer$1;
|
||||||
|
};
|
||||||
|
type createSecureHttp2Options = {
|
||||||
|
serverOptions?: SecureServerOptions;
|
||||||
|
createServer?: typeof createSecureServer;
|
||||||
|
};
|
||||||
|
type ServerOptions$3 = createHttpOptions | createHttpsOptions | createHttp2Options | createSecureHttp2Options;
|
||||||
|
type Options = {
|
||||||
|
fetch: FetchCallback;
|
||||||
|
overrideGlobalObjects?: boolean;
|
||||||
|
autoCleanupIncoming?: boolean;
|
||||||
|
port?: number;
|
||||||
|
hostname?: string;
|
||||||
|
websocket?: {
|
||||||
|
server: WebSocketServer;
|
||||||
|
};
|
||||||
|
} & ServerOptions$3;
|
||||||
|
type CustomErrorHandler = (err: unknown) => void | Response | Promise<void | Response>;
|
||||||
|
//#endregion
|
||||||
|
//#region src/server.d.ts
|
||||||
|
declare const createAdaptorServer: (options: Options) => ServerType;
|
||||||
|
declare const serve: (options: Options, listeningListener?: (info: AddressInfo) => void) => ServerType;
|
||||||
|
//#endregion
|
||||||
|
//#region src/websocket.d.ts
|
||||||
|
type UpgradeWebSocketOptions = {
|
||||||
|
onError: (err: unknown) => void;
|
||||||
|
};
|
||||||
|
declare const upgradeWebSocket: UpgradeWebSocket<WebSocket, UpgradeWebSocketOptions>;
|
||||||
|
//#endregion
|
||||||
|
//#region src/listener.d.ts
|
||||||
|
declare const getRequestListener: (fetchCallback: FetchCallback, options?: {
|
||||||
|
hostname?: string;
|
||||||
|
errorHandler?: CustomErrorHandler;
|
||||||
|
overrideGlobalObjects?: boolean;
|
||||||
|
autoCleanupIncoming?: boolean;
|
||||||
|
}) => (incoming: IncomingMessage | Http2ServerRequest, outgoing: ServerResponse | Http2ServerResponse) => Promise<void>;
|
||||||
|
//#endregion
|
||||||
|
//#region src/error.d.ts
|
||||||
|
declare class RequestError extends Error {
|
||||||
|
constructor(message: string, options?: {
|
||||||
|
cause?: unknown;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
export { type Http2Bindings, type HttpBindings, RequestError, type ServerType, createAdaptorServer, getRequestListener, serve, upgradeWebSocket };
|
||||||
+73
@@ -0,0 +1,73 @@
|
|||||||
|
import { IncomingMessage, Server, ServerOptions, ServerResponse, createServer } from "node:http";
|
||||||
|
import { Http2SecureServer, Http2Server, Http2ServerRequest, Http2ServerResponse, SecureServerOptions, ServerOptions as ServerOptions$1, createSecureServer, createServer as createServer$1 } from "node:http2";
|
||||||
|
import { UpgradeWebSocket } from "hono/ws";
|
||||||
|
import { AddressInfo } from "node:net";
|
||||||
|
import { WebSocket, WebSocketServer } from "ws";
|
||||||
|
import { ServerOptions as ServerOptions$2, createServer as createServer$2 } from "node:https";
|
||||||
|
|
||||||
|
//#region src/types.d.ts
|
||||||
|
type HttpBindings = {
|
||||||
|
incoming: IncomingMessage;
|
||||||
|
outgoing: ServerResponse;
|
||||||
|
};
|
||||||
|
type Http2Bindings = {
|
||||||
|
incoming: Http2ServerRequest;
|
||||||
|
outgoing: Http2ServerResponse;
|
||||||
|
};
|
||||||
|
type FetchCallback = (request: Request, env: HttpBindings | Http2Bindings) => Promise<unknown> | unknown;
|
||||||
|
type ServerType = Server | Http2Server | Http2SecureServer;
|
||||||
|
type createHttpOptions = {
|
||||||
|
serverOptions?: ServerOptions;
|
||||||
|
createServer?: typeof createServer;
|
||||||
|
};
|
||||||
|
type createHttpsOptions = {
|
||||||
|
serverOptions?: ServerOptions$2;
|
||||||
|
createServer?: typeof createServer$2;
|
||||||
|
};
|
||||||
|
type createHttp2Options = {
|
||||||
|
serverOptions?: ServerOptions$1;
|
||||||
|
createServer?: typeof createServer$1;
|
||||||
|
};
|
||||||
|
type createSecureHttp2Options = {
|
||||||
|
serverOptions?: SecureServerOptions;
|
||||||
|
createServer?: typeof createSecureServer;
|
||||||
|
};
|
||||||
|
type ServerOptions$3 = createHttpOptions | createHttpsOptions | createHttp2Options | createSecureHttp2Options;
|
||||||
|
type Options = {
|
||||||
|
fetch: FetchCallback;
|
||||||
|
overrideGlobalObjects?: boolean;
|
||||||
|
autoCleanupIncoming?: boolean;
|
||||||
|
port?: number;
|
||||||
|
hostname?: string;
|
||||||
|
websocket?: {
|
||||||
|
server: WebSocketServer;
|
||||||
|
};
|
||||||
|
} & ServerOptions$3;
|
||||||
|
type CustomErrorHandler = (err: unknown) => void | Response | Promise<void | Response>;
|
||||||
|
//#endregion
|
||||||
|
//#region src/server.d.ts
|
||||||
|
declare const createAdaptorServer: (options: Options) => ServerType;
|
||||||
|
declare const serve: (options: Options, listeningListener?: (info: AddressInfo) => void) => ServerType;
|
||||||
|
//#endregion
|
||||||
|
//#region src/websocket.d.ts
|
||||||
|
type UpgradeWebSocketOptions = {
|
||||||
|
onError: (err: unknown) => void;
|
||||||
|
};
|
||||||
|
declare const upgradeWebSocket: UpgradeWebSocket<WebSocket, UpgradeWebSocketOptions>;
|
||||||
|
//#endregion
|
||||||
|
//#region src/listener.d.ts
|
||||||
|
declare const getRequestListener: (fetchCallback: FetchCallback, options?: {
|
||||||
|
hostname?: string;
|
||||||
|
errorHandler?: CustomErrorHandler;
|
||||||
|
overrideGlobalObjects?: boolean;
|
||||||
|
autoCleanupIncoming?: boolean;
|
||||||
|
}) => (incoming: IncomingMessage | Http2ServerRequest, outgoing: ServerResponse | Http2ServerResponse) => Promise<void>;
|
||||||
|
//#endregion
|
||||||
|
//#region src/error.d.ts
|
||||||
|
declare class RequestError extends Error {
|
||||||
|
constructor(message: string, options?: {
|
||||||
|
cause?: unknown;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
export { type Http2Bindings, type HttpBindings, RequestError, type ServerType, createAdaptorServer, getRequestListener, serve, upgradeWebSocket };
|
||||||
+1001
File diff suppressed because it is too large
Load Diff
+135
@@ -0,0 +1,135 @@
|
|||||||
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||||||
|
let node_stream = require("node:stream");
|
||||||
|
let hono_utils_mime = require("hono/utils/mime");
|
||||||
|
let node_fs = require("node:fs");
|
||||||
|
let node_path = require("node:path");
|
||||||
|
let node_process = require("node:process");
|
||||||
|
|
||||||
|
//#region src/serve-static.ts
|
||||||
|
const COMPRESSIBLE_CONTENT_TYPE_REGEX = /^\s*(?:text\/[^;\s]+|application\/(?:javascript|json|xml|xml-dtd|ecmascript|dart|postscript|rtf|tar|toml|vnd\.dart|vnd\.ms-fontobject|vnd\.ms-opentype|wasm|x-httpd-php|x-javascript|x-ns-proxy-autoconfig|x-sh|x-tar|x-virtualbox-hdd|x-virtualbox-ova|x-virtualbox-ovf|x-virtualbox-vbox|x-virtualbox-vdi|x-virtualbox-vhd|x-virtualbox-vmdk|x-www-form-urlencoded)|font\/(?:otf|ttf)|image\/(?:bmp|vnd\.adobe\.photoshop|vnd\.microsoft\.icon|vnd\.ms-dds|x-icon|x-ms-bmp)|message\/rfc822|model\/gltf-binary|x-shader\/x-fragment|x-shader\/x-vertex|[^;\s]+?\+(?:json|text|xml|yaml))(?:[;\s]|$)/i;
|
||||||
|
const ENCODINGS = {
|
||||||
|
br: ".br",
|
||||||
|
zstd: ".zst",
|
||||||
|
gzip: ".gz"
|
||||||
|
};
|
||||||
|
const ENCODINGS_ORDERED_KEYS = Object.keys(ENCODINGS);
|
||||||
|
const pr54206Applied = () => {
|
||||||
|
const [major, minor] = node_process.versions.node.split(".").map((component) => parseInt(component));
|
||||||
|
return major >= 23 || major === 22 && minor >= 7 || major === 20 && minor >= 18;
|
||||||
|
};
|
||||||
|
const useReadableToWeb = pr54206Applied();
|
||||||
|
const createStreamBody = (stream) => {
|
||||||
|
if (useReadableToWeb) return node_stream.Readable.toWeb(stream);
|
||||||
|
return new ReadableStream({
|
||||||
|
start(controller) {
|
||||||
|
stream.on("data", (chunk) => {
|
||||||
|
controller.enqueue(chunk);
|
||||||
|
});
|
||||||
|
stream.on("error", (err) => {
|
||||||
|
controller.error(err);
|
||||||
|
});
|
||||||
|
stream.on("end", () => {
|
||||||
|
controller.close();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
cancel() {
|
||||||
|
stream.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const getStats = (path) => {
|
||||||
|
let stats;
|
||||||
|
try {
|
||||||
|
stats = (0, node_fs.statSync)(path);
|
||||||
|
} catch {}
|
||||||
|
return stats;
|
||||||
|
};
|
||||||
|
const tryDecode = (str, decoder) => {
|
||||||
|
try {
|
||||||
|
return decoder(str);
|
||||||
|
} catch {
|
||||||
|
return str.replace(/(?:%[0-9A-Fa-f]{2})+/g, (match) => {
|
||||||
|
try {
|
||||||
|
return decoder(match);
|
||||||
|
} catch {
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const tryDecodeURI = (str) => tryDecode(str, decodeURI);
|
||||||
|
const serveStatic = (options = { root: "" }) => {
|
||||||
|
const root = options.root || "";
|
||||||
|
const optionPath = options.path;
|
||||||
|
if (root !== "" && !(0, node_fs.existsSync)(root)) console.error(`serveStatic: root path '${root}' is not found, are you sure it's correct?`);
|
||||||
|
return async (c, next) => {
|
||||||
|
if (c.finalized) return next();
|
||||||
|
let filename;
|
||||||
|
if (optionPath) filename = optionPath;
|
||||||
|
else try {
|
||||||
|
filename = tryDecodeURI(c.req.path);
|
||||||
|
if (/(?:^|[\/\\])\.{1,2}(?:$|[\/\\])|[\/\\]{2,}/.test(filename)) throw new Error();
|
||||||
|
} catch {
|
||||||
|
await options.onNotFound?.(c.req.path, c);
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
let path = (0, node_path.join)(root, !optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename, c) : filename);
|
||||||
|
let stats = getStats(path);
|
||||||
|
if (stats && stats.isDirectory()) {
|
||||||
|
const indexFile = options.index ?? "index.html";
|
||||||
|
path = (0, node_path.join)(path, indexFile);
|
||||||
|
stats = getStats(path);
|
||||||
|
}
|
||||||
|
if (!stats) {
|
||||||
|
await options.onNotFound?.(path, c);
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
const mimeType = (0, hono_utils_mime.getMimeType)(path);
|
||||||
|
c.header("Content-Type", mimeType || "application/octet-stream");
|
||||||
|
if (options.precompressed && (!mimeType || COMPRESSIBLE_CONTENT_TYPE_REGEX.test(mimeType))) {
|
||||||
|
const acceptEncodingSet = new Set(c.req.header("Accept-Encoding")?.split(",").map((encoding) => encoding.trim()));
|
||||||
|
for (const encoding of ENCODINGS_ORDERED_KEYS) {
|
||||||
|
if (!acceptEncodingSet.has(encoding)) continue;
|
||||||
|
const precompressedStats = getStats(path + ENCODINGS[encoding]);
|
||||||
|
if (precompressedStats) {
|
||||||
|
c.header("Content-Encoding", encoding);
|
||||||
|
c.header("Vary", "Accept-Encoding", { append: true });
|
||||||
|
stats = precompressedStats;
|
||||||
|
path = path + ENCODINGS[encoding];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let result;
|
||||||
|
const size = stats.size;
|
||||||
|
const range = c.req.header("range") || "";
|
||||||
|
if (c.req.method == "HEAD" || c.req.method == "OPTIONS") {
|
||||||
|
c.header("Content-Length", size.toString());
|
||||||
|
c.status(200);
|
||||||
|
result = c.body(null);
|
||||||
|
} else if (!range) {
|
||||||
|
c.header("Content-Length", size.toString());
|
||||||
|
result = c.body(createStreamBody((0, node_fs.createReadStream)(path)), 200);
|
||||||
|
} else {
|
||||||
|
c.header("Accept-Ranges", "bytes");
|
||||||
|
c.header("Date", stats.birthtime.toUTCString());
|
||||||
|
const parts = range.replace(/bytes=/, "").split("-", 2);
|
||||||
|
const start = parseInt(parts[0], 10) || 0;
|
||||||
|
let end = parseInt(parts[1], 10) || size - 1;
|
||||||
|
if (size < end - start + 1) end = size - 1;
|
||||||
|
const chunksize = end - start + 1;
|
||||||
|
const stream = (0, node_fs.createReadStream)(path, {
|
||||||
|
start,
|
||||||
|
end
|
||||||
|
});
|
||||||
|
c.header("Content-Length", chunksize.toString());
|
||||||
|
c.header("Content-Range", `bytes ${start}-${end}/${stats.size}`);
|
||||||
|
result = c.body(createStreamBody(stream), 206);
|
||||||
|
}
|
||||||
|
await options.onFound?.(path, c);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
exports.serveStatic = serveStatic;
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
import { Context, Env, MiddlewareHandler } from "hono";
|
||||||
|
|
||||||
|
//#region src/serve-static.d.ts
|
||||||
|
type ServeStaticOptions<E extends Env = Env> = {
|
||||||
|
/**
|
||||||
|
* Root path, relative to current working directory from which the app was started. Absolute paths are not supported.
|
||||||
|
*/
|
||||||
|
root?: string;
|
||||||
|
path?: string;
|
||||||
|
index?: string;
|
||||||
|
precompressed?: boolean;
|
||||||
|
rewriteRequestPath?: (path: string, c: Context<E>) => string;
|
||||||
|
onFound?: (path: string, c: Context<E>) => void | Promise<void>;
|
||||||
|
onNotFound?: (path: string, c: Context<E>) => void | Promise<void>;
|
||||||
|
};
|
||||||
|
declare const serveStatic: <E extends Env = any>(options?: ServeStaticOptions<E>) => MiddlewareHandler<E>;
|
||||||
|
//#endregion
|
||||||
|
export { ServeStaticOptions, serveStatic };
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
import { Context, Env, MiddlewareHandler } from "hono";
|
||||||
|
|
||||||
|
//#region src/serve-static.d.ts
|
||||||
|
type ServeStaticOptions<E extends Env = Env> = {
|
||||||
|
/**
|
||||||
|
* Root path, relative to current working directory from which the app was started. Absolute paths are not supported.
|
||||||
|
*/
|
||||||
|
root?: string;
|
||||||
|
path?: string;
|
||||||
|
index?: string;
|
||||||
|
precompressed?: boolean;
|
||||||
|
rewriteRequestPath?: (path: string, c: Context<E>) => string;
|
||||||
|
onFound?: (path: string, c: Context<E>) => void | Promise<void>;
|
||||||
|
onNotFound?: (path: string, c: Context<E>) => void | Promise<void>;
|
||||||
|
};
|
||||||
|
declare const serveStatic: <E extends Env = any>(options?: ServeStaticOptions<E>) => MiddlewareHandler<E>;
|
||||||
|
//#endregion
|
||||||
|
export { ServeStaticOptions, serveStatic };
|
||||||
+134
@@ -0,0 +1,134 @@
|
|||||||
|
import { Readable } from "node:stream";
|
||||||
|
import { getMimeType } from "hono/utils/mime";
|
||||||
|
import { createReadStream, existsSync, statSync } from "node:fs";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import { versions } from "node:process";
|
||||||
|
|
||||||
|
//#region src/serve-static.ts
|
||||||
|
const COMPRESSIBLE_CONTENT_TYPE_REGEX = /^\s*(?:text\/[^;\s]+|application\/(?:javascript|json|xml|xml-dtd|ecmascript|dart|postscript|rtf|tar|toml|vnd\.dart|vnd\.ms-fontobject|vnd\.ms-opentype|wasm|x-httpd-php|x-javascript|x-ns-proxy-autoconfig|x-sh|x-tar|x-virtualbox-hdd|x-virtualbox-ova|x-virtualbox-ovf|x-virtualbox-vbox|x-virtualbox-vdi|x-virtualbox-vhd|x-virtualbox-vmdk|x-www-form-urlencoded)|font\/(?:otf|ttf)|image\/(?:bmp|vnd\.adobe\.photoshop|vnd\.microsoft\.icon|vnd\.ms-dds|x-icon|x-ms-bmp)|message\/rfc822|model\/gltf-binary|x-shader\/x-fragment|x-shader\/x-vertex|[^;\s]+?\+(?:json|text|xml|yaml))(?:[;\s]|$)/i;
|
||||||
|
const ENCODINGS = {
|
||||||
|
br: ".br",
|
||||||
|
zstd: ".zst",
|
||||||
|
gzip: ".gz"
|
||||||
|
};
|
||||||
|
const ENCODINGS_ORDERED_KEYS = Object.keys(ENCODINGS);
|
||||||
|
const pr54206Applied = () => {
|
||||||
|
const [major, minor] = versions.node.split(".").map((component) => parseInt(component));
|
||||||
|
return major >= 23 || major === 22 && minor >= 7 || major === 20 && minor >= 18;
|
||||||
|
};
|
||||||
|
const useReadableToWeb = pr54206Applied();
|
||||||
|
const createStreamBody = (stream) => {
|
||||||
|
if (useReadableToWeb) return Readable.toWeb(stream);
|
||||||
|
return new ReadableStream({
|
||||||
|
start(controller) {
|
||||||
|
stream.on("data", (chunk) => {
|
||||||
|
controller.enqueue(chunk);
|
||||||
|
});
|
||||||
|
stream.on("error", (err) => {
|
||||||
|
controller.error(err);
|
||||||
|
});
|
||||||
|
stream.on("end", () => {
|
||||||
|
controller.close();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
cancel() {
|
||||||
|
stream.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const getStats = (path) => {
|
||||||
|
let stats;
|
||||||
|
try {
|
||||||
|
stats = statSync(path);
|
||||||
|
} catch {}
|
||||||
|
return stats;
|
||||||
|
};
|
||||||
|
const tryDecode = (str, decoder) => {
|
||||||
|
try {
|
||||||
|
return decoder(str);
|
||||||
|
} catch {
|
||||||
|
return str.replace(/(?:%[0-9A-Fa-f]{2})+/g, (match) => {
|
||||||
|
try {
|
||||||
|
return decoder(match);
|
||||||
|
} catch {
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const tryDecodeURI = (str) => tryDecode(str, decodeURI);
|
||||||
|
const serveStatic = (options = { root: "" }) => {
|
||||||
|
const root = options.root || "";
|
||||||
|
const optionPath = options.path;
|
||||||
|
if (root !== "" && !existsSync(root)) console.error(`serveStatic: root path '${root}' is not found, are you sure it's correct?`);
|
||||||
|
return async (c, next) => {
|
||||||
|
if (c.finalized) return next();
|
||||||
|
let filename;
|
||||||
|
if (optionPath) filename = optionPath;
|
||||||
|
else try {
|
||||||
|
filename = tryDecodeURI(c.req.path);
|
||||||
|
if (/(?:^|[\/\\])\.{1,2}(?:$|[\/\\])|[\/\\]{2,}/.test(filename)) throw new Error();
|
||||||
|
} catch {
|
||||||
|
await options.onNotFound?.(c.req.path, c);
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
let path = join(root, !optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename, c) : filename);
|
||||||
|
let stats = getStats(path);
|
||||||
|
if (stats && stats.isDirectory()) {
|
||||||
|
const indexFile = options.index ?? "index.html";
|
||||||
|
path = join(path, indexFile);
|
||||||
|
stats = getStats(path);
|
||||||
|
}
|
||||||
|
if (!stats) {
|
||||||
|
await options.onNotFound?.(path, c);
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
const mimeType = getMimeType(path);
|
||||||
|
c.header("Content-Type", mimeType || "application/octet-stream");
|
||||||
|
if (options.precompressed && (!mimeType || COMPRESSIBLE_CONTENT_TYPE_REGEX.test(mimeType))) {
|
||||||
|
const acceptEncodingSet = new Set(c.req.header("Accept-Encoding")?.split(",").map((encoding) => encoding.trim()));
|
||||||
|
for (const encoding of ENCODINGS_ORDERED_KEYS) {
|
||||||
|
if (!acceptEncodingSet.has(encoding)) continue;
|
||||||
|
const precompressedStats = getStats(path + ENCODINGS[encoding]);
|
||||||
|
if (precompressedStats) {
|
||||||
|
c.header("Content-Encoding", encoding);
|
||||||
|
c.header("Vary", "Accept-Encoding", { append: true });
|
||||||
|
stats = precompressedStats;
|
||||||
|
path = path + ENCODINGS[encoding];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let result;
|
||||||
|
const size = stats.size;
|
||||||
|
const range = c.req.header("range") || "";
|
||||||
|
if (c.req.method == "HEAD" || c.req.method == "OPTIONS") {
|
||||||
|
c.header("Content-Length", size.toString());
|
||||||
|
c.status(200);
|
||||||
|
result = c.body(null);
|
||||||
|
} else if (!range) {
|
||||||
|
c.header("Content-Length", size.toString());
|
||||||
|
result = c.body(createStreamBody(createReadStream(path)), 200);
|
||||||
|
} else {
|
||||||
|
c.header("Accept-Ranges", "bytes");
|
||||||
|
c.header("Date", stats.birthtime.toUTCString());
|
||||||
|
const parts = range.replace(/bytes=/, "").split("-", 2);
|
||||||
|
const start = parseInt(parts[0], 10) || 0;
|
||||||
|
let end = parseInt(parts[1], 10) || size - 1;
|
||||||
|
if (size < end - start + 1) end = size - 1;
|
||||||
|
const chunksize = end - start + 1;
|
||||||
|
const stream = createReadStream(path, {
|
||||||
|
start,
|
||||||
|
end
|
||||||
|
});
|
||||||
|
c.header("Content-Length", chunksize.toString());
|
||||||
|
c.header("Content-Range", `bytes ${start}-${end}/${stats.size}`);
|
||||||
|
result = c.body(createStreamBody(stream), 206);
|
||||||
|
}
|
||||||
|
await options.onFound?.(path, c);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
export { serveStatic };
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||||||
|
const require_constants = require('../constants-BXAKTxRC.cjs');
|
||||||
|
|
||||||
|
//#region src/utils/response.ts
|
||||||
|
const RESPONSE_ALREADY_SENT = new Response(null, { headers: { [require_constants.X_ALREADY_SENT]: "true" } });
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
exports.RESPONSE_ALREADY_SENT = RESPONSE_ALREADY_SENT;
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
//#region src/utils/response.d.ts
|
||||||
|
declare const RESPONSE_ALREADY_SENT: Response;
|
||||||
|
//#endregion
|
||||||
|
export { RESPONSE_ALREADY_SENT };
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
//#region src/utils/response.d.ts
|
||||||
|
declare const RESPONSE_ALREADY_SENT: Response;
|
||||||
|
//#endregion
|
||||||
|
export { RESPONSE_ALREADY_SENT };
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
import { t as X_ALREADY_SENT } from "../constants-BLSFu_RU.mjs";
|
||||||
|
|
||||||
|
//#region src/utils/response.ts
|
||||||
|
const RESPONSE_ALREADY_SENT = new Response(null, { headers: { [X_ALREADY_SENT]: "true" } });
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
export { RESPONSE_ALREADY_SENT };
|
||||||
+116
@@ -0,0 +1,116 @@
|
|||||||
|
{
|
||||||
|
"name": "@hono/node-server",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"description": "Node.js Adapter for Hono",
|
||||||
|
"main": "dist/index.mjs",
|
||||||
|
"type": "module",
|
||||||
|
"types": "dist/index.d.mts",
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": {
|
||||||
|
"types": "./dist/index.d.mts",
|
||||||
|
"default": "./dist/index.mjs"
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"types": "./dist/index.d.cts",
|
||||||
|
"default": "./dist/index.cjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"./serve-static": {
|
||||||
|
"import": {
|
||||||
|
"types": "./dist/serve-static.d.mts",
|
||||||
|
"default": "./dist/serve-static.mjs"
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"types": "./dist/serve-static.d.cts",
|
||||||
|
"default": "./dist/serve-static.cjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"./utils/*": {
|
||||||
|
"import": {
|
||||||
|
"types": "./dist/utils/*.d.mts",
|
||||||
|
"default": "./dist/utils/*.mjs"
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"types": "./dist/utils/*.d.cts",
|
||||||
|
"default": "./dist/utils/*.cjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"./conninfo": {
|
||||||
|
"import": {
|
||||||
|
"types": "./dist/conninfo.d.mts",
|
||||||
|
"default": "./dist/conninfo.mjs"
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"types": "./dist/conninfo.d.cts",
|
||||||
|
"default": "./dist/conninfo.cjs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"typesVersions": {
|
||||||
|
"*": {
|
||||||
|
".": [
|
||||||
|
"./dist/index.d.mts"
|
||||||
|
],
|
||||||
|
"serve-static": [
|
||||||
|
"./dist/serve-static.d.mts"
|
||||||
|
],
|
||||||
|
"utils/*": [
|
||||||
|
"./dist/utils/*.d.mts"
|
||||||
|
],
|
||||||
|
"conninfo": [
|
||||||
|
"./dist/conninfo.d.mts"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "vitest",
|
||||||
|
"build": "tsdown --external hono",
|
||||||
|
"watch": "tsdown --watch",
|
||||||
|
"postbuild": "publint",
|
||||||
|
"prerelease": "bun run build && bun run test --run",
|
||||||
|
"release": "np",
|
||||||
|
"lint": "eslint src test",
|
||||||
|
"lint:fix": "eslint src test --fix",
|
||||||
|
"format": "prettier --check \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\"",
|
||||||
|
"format:fix": "prettier --write \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\""
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/honojs/node-server.git"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/honojs/node-server",
|
||||||
|
"author": "Yusuke Wada <yusuke@kamawada.com> (https://github.com/yusukebe)",
|
||||||
|
"publishConfig": {
|
||||||
|
"registry": "https://registry.npmjs.org",
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@hono/eslint-config": "^1.0.1",
|
||||||
|
"@types/node": "^20.10.0",
|
||||||
|
"@types/supertest": "^2.0.12",
|
||||||
|
"@types/ws": "^8.18.1",
|
||||||
|
"@whatwg-node/fetch": "^0.9.14",
|
||||||
|
"eslint": "^9.10.0",
|
||||||
|
"hono": "^4.12.8",
|
||||||
|
"np": "^7.7.0",
|
||||||
|
"prettier": "^3.2.4",
|
||||||
|
"publint": "^0.3.18",
|
||||||
|
"supertest": "^6.3.3",
|
||||||
|
"tsdown": "^0.20.3",
|
||||||
|
"typescript": "^5.3.2",
|
||||||
|
"vitest": "^4.0.18",
|
||||||
|
"ws": "^8.19.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"hono": "^4"
|
||||||
|
},
|
||||||
|
"packageManager": "bun@1.2.20"
|
||||||
|
}
|
||||||
+201
@@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
# Prisma Client · [](https://www.npmjs.com/package/@prisma/client) [](https://github.com/prisma/prisma/blob/main/CONTRIBUTING.md) [](https://github.com/prisma/prisma/blob/main/LICENSE) [](https://pris.ly/discord)
|
||||||
|
|
||||||
|
Prisma Client JS is an **auto-generated query builder** that enables **type-safe** database access and **reduces boilerplate**. You can use it as an alternative to traditional ORMs such as Sequelize, TypeORM or SQL query builders like knex.js.
|
||||||
|
|
||||||
|
It is part of the [Prisma](https://www.prisma.io/) ecosystem. Prisma provides database tools for data access, declarative data modeling, schema migrations and visual data management. Learn more in the main [`prisma`](https://github.com/prisma/prisma/) repository or read the [documentation](https://www.prisma.io/docs/).
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
Follow one of these guides to get started with Prisma Client JS:
|
||||||
|
|
||||||
|
- [Quickstart](https://www.prisma.io/docs/getting-started/quickstart) (5 min)
|
||||||
|
- [Set up a new project with Prisma (SQL migrations)](https://www.prisma.io/docs/getting-started/setup-prisma/start-from-scratch-sql) (15 min)
|
||||||
|
- [Set up a new project with Prisma (Prisma Migrate)](https://www.prisma.io/docs/getting-started/setup-prisma/start-from-scratch-prisma-migrate) (15 min)
|
||||||
|
- [Add Prisma to an existing project](https://www.prisma.io/docs/getting-started/setup-prisma/add-to-existing-project) (15 min)
|
||||||
|
|
||||||
|
Alternatively you can explore the ready-to-run [examples](https://github.com/prisma/prisma-examples/) (REST, GraphQL, gRPC, plain JavaScript and TypeScript demos, ...) or watch the [demo videos](https://www.youtube.com/watch?v=0RhtQgIs-TE&list=PLn2e1F9Rfr6k9PnR_figWOcSHgc_erDr5&index=1) (1-2 min per video).
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Refer to our [contribution guidelines](https://github.com/prisma/prisma/blob/main/CONTRIBUTING.md) and [Code of Conduct for contributors](https://github.com/prisma/prisma/blob/main/CODE_OF_CONDUCT.md).
|
||||||
|
|
||||||
|
## Tests Status
|
||||||
|
|
||||||
|
- Prisma Tests Status:
|
||||||
|
[](https://github.com/prisma/prisma/actions/workflows/test.yml)
|
||||||
|
- Ecosystem Tests Status:
|
||||||
|
[](https://github.com/prisma/ecosystem-tests/actions)
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from '.prisma/client/default'
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
...require('.prisma/client/default'),
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from '.prisma/client/edge'
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
module.exports = {
|
||||||
|
// https://github.com/prisma/prisma/pull/12907
|
||||||
|
...require('.prisma/client/edge'),
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from './scripts/default-index'
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
module.exports = {
|
||||||
|
// https://github.com/prisma/prisma/pull/12907
|
||||||
|
...require('./scripts/default-index'),
|
||||||
|
}
|
||||||
+18861
File diff suppressed because it is too large
Load Diff
+3
@@ -0,0 +1,3 @@
|
|||||||
|
const prisma = require('.prisma/client/index-browser')
|
||||||
|
|
||||||
|
module.exports = prisma
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from '.prisma/client/default'
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
module.exports = {
|
||||||
|
// https://github.com/prisma/prisma/pull/12907
|
||||||
|
...require('.prisma/client/default'),
|
||||||
|
}
|
||||||
+327
@@ -0,0 +1,327 @@
|
|||||||
|
{
|
||||||
|
"name": "@prisma/client",
|
||||||
|
"version": "6.19.3",
|
||||||
|
"description": "Prisma Client is an auto-generated, type-safe and modern JavaScript/TypeScript ORM for Node.js that's tailored to your data. Supports PostgreSQL, CockroachDB, MySQL, MariaDB, SQL Server, SQLite & MongoDB databases.",
|
||||||
|
"keywords": [
|
||||||
|
"ORM",
|
||||||
|
"Prisma",
|
||||||
|
"prisma2",
|
||||||
|
"Prisma Client",
|
||||||
|
"client",
|
||||||
|
"query",
|
||||||
|
"query-builder",
|
||||||
|
"database",
|
||||||
|
"db",
|
||||||
|
"JavaScript",
|
||||||
|
"JS",
|
||||||
|
"TypeScript",
|
||||||
|
"TS",
|
||||||
|
"SQL",
|
||||||
|
"SQLite",
|
||||||
|
"pg",
|
||||||
|
"Postgres",
|
||||||
|
"PostgreSQL",
|
||||||
|
"CockroachDB",
|
||||||
|
"MySQL",
|
||||||
|
"MariaDB",
|
||||||
|
"MSSQL",
|
||||||
|
"SQL Server",
|
||||||
|
"SQLServer",
|
||||||
|
"MongoDB",
|
||||||
|
"react-native"
|
||||||
|
],
|
||||||
|
"main": "default.js",
|
||||||
|
"types": "default.d.ts",
|
||||||
|
"browser": "index-browser.js",
|
||||||
|
"exports": {
|
||||||
|
"./package.json": "./package.json",
|
||||||
|
".": {
|
||||||
|
"require": {
|
||||||
|
"types": "./default.d.ts",
|
||||||
|
"node": "./default.js",
|
||||||
|
"edge-light": "./default.js",
|
||||||
|
"workerd": "./default.js",
|
||||||
|
"worker": "./default.js",
|
||||||
|
"browser": "./index-browser.js"
|
||||||
|
},
|
||||||
|
"import": {
|
||||||
|
"types": "./default.d.ts",
|
||||||
|
"node": "./default.js",
|
||||||
|
"edge-light": "./default.js",
|
||||||
|
"workerd": "./default.js",
|
||||||
|
"worker": "./default.js",
|
||||||
|
"browser": "./index-browser.js"
|
||||||
|
},
|
||||||
|
"default": "./default.js"
|
||||||
|
},
|
||||||
|
"./edge": {
|
||||||
|
"types": "./edge.d.ts",
|
||||||
|
"require": "./edge.js",
|
||||||
|
"import": "./edge.js",
|
||||||
|
"default": "./edge.js"
|
||||||
|
},
|
||||||
|
"./react-native": {
|
||||||
|
"types": "./react-native.d.ts",
|
||||||
|
"require": "./react-native.js",
|
||||||
|
"import": "./react-native.js",
|
||||||
|
"default": "./react-native.js"
|
||||||
|
},
|
||||||
|
"./extension": {
|
||||||
|
"types": "./extension.d.ts",
|
||||||
|
"require": "./extension.js",
|
||||||
|
"import": "./extension.js",
|
||||||
|
"default": "./extension.js"
|
||||||
|
},
|
||||||
|
"./index-browser": {
|
||||||
|
"types": "./index.d.ts",
|
||||||
|
"require": "./index-browser.js",
|
||||||
|
"import": "./index-browser.js",
|
||||||
|
"default": "./index-browser.js"
|
||||||
|
},
|
||||||
|
"./index": {
|
||||||
|
"types": "./index.d.ts",
|
||||||
|
"require": "./index.js",
|
||||||
|
"import": "./index.js",
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"./wasm": {
|
||||||
|
"types": "./wasm.d.ts",
|
||||||
|
"require": "./wasm.js",
|
||||||
|
"import": "./wasm.mjs",
|
||||||
|
"default": "./wasm.mjs"
|
||||||
|
},
|
||||||
|
"./runtime/client": {
|
||||||
|
"types": "./runtime/client.d.ts",
|
||||||
|
"node": {
|
||||||
|
"require": "./runtime/client.js",
|
||||||
|
"default": "./runtime/client.js"
|
||||||
|
},
|
||||||
|
"require": "./runtime/client.js",
|
||||||
|
"import": "./runtime/client.mjs",
|
||||||
|
"default": "./runtime/client.mjs"
|
||||||
|
},
|
||||||
|
"./runtime/library": {
|
||||||
|
"types": "./runtime/library.d.ts",
|
||||||
|
"require": "./runtime/library.js",
|
||||||
|
"import": "./runtime/library.mjs",
|
||||||
|
"default": "./runtime/library.mjs"
|
||||||
|
},
|
||||||
|
"./runtime/binary": {
|
||||||
|
"types": "./runtime/binary.d.ts",
|
||||||
|
"require": "./runtime/binary.js",
|
||||||
|
"import": "./runtime/binary.mjs",
|
||||||
|
"default": "./runtime/binary.mjs"
|
||||||
|
},
|
||||||
|
"./runtime/wasm-engine-edge": {
|
||||||
|
"types": "./runtime/wasm-engine-edge.d.ts",
|
||||||
|
"require": "./runtime/wasm-engine-edge.js",
|
||||||
|
"import": "./runtime/wasm-engine-edge.mjs",
|
||||||
|
"default": "./runtime/wasm-engine-edge.mjs"
|
||||||
|
},
|
||||||
|
"./runtime/wasm-compiler-edge": {
|
||||||
|
"types": "./runtime/wasm-compiler-edge.d.ts",
|
||||||
|
"require": "./runtime/wasm-compiler-edge.js",
|
||||||
|
"import": "./runtime/wasm-compiler-edge.mjs",
|
||||||
|
"default": "./runtime/wasm-compiler-edge.mjs"
|
||||||
|
},
|
||||||
|
"./runtime/edge": {
|
||||||
|
"types": "./runtime/edge.d.ts",
|
||||||
|
"require": "./runtime/edge.js",
|
||||||
|
"import": "./runtime/edge-esm.js",
|
||||||
|
"default": "./runtime/edge-esm.js"
|
||||||
|
},
|
||||||
|
"./runtime/react-native": {
|
||||||
|
"types": "./runtime/react-native.d.ts",
|
||||||
|
"require": "./runtime/react-native.js",
|
||||||
|
"import": "./runtime/react-native.js",
|
||||||
|
"default": "./runtime/react-native.js"
|
||||||
|
},
|
||||||
|
"./runtime/index-browser": {
|
||||||
|
"types": "./runtime/index-browser.d.ts",
|
||||||
|
"require": "./runtime/index-browser.js",
|
||||||
|
"import": "./runtime/index-browser.mjs",
|
||||||
|
"default": "./runtime/index-browser.mjs"
|
||||||
|
},
|
||||||
|
"./generator-build": {
|
||||||
|
"require": "./generator-build/index.js",
|
||||||
|
"import": "./generator-build/index.js",
|
||||||
|
"default": "./generator-build/index.js"
|
||||||
|
},
|
||||||
|
"./sql": {
|
||||||
|
"require": {
|
||||||
|
"types": "./sql.d.ts",
|
||||||
|
"node": "./sql.js",
|
||||||
|
"default": "./sql.js"
|
||||||
|
},
|
||||||
|
"import": {
|
||||||
|
"types": "./sql.d.ts",
|
||||||
|
"node": "./sql.mjs",
|
||||||
|
"default": "./sql.mjs"
|
||||||
|
},
|
||||||
|
"default": "./sql.js"
|
||||||
|
},
|
||||||
|
"./*": "./*"
|
||||||
|
},
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.18"
|
||||||
|
},
|
||||||
|
"homepage": "https://www.prisma.io",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/prisma/prisma.git",
|
||||||
|
"directory": "packages/client"
|
||||||
|
},
|
||||||
|
"author": "Tim Suchanek <suchanek@prisma.io>",
|
||||||
|
"bugs": "https://github.com/prisma/prisma/issues",
|
||||||
|
"files": [
|
||||||
|
"README.md",
|
||||||
|
"runtime",
|
||||||
|
"scripts",
|
||||||
|
"generator-build",
|
||||||
|
"edge.js",
|
||||||
|
"edge.d.ts",
|
||||||
|
"wasm.js",
|
||||||
|
"wasm.d.ts",
|
||||||
|
"index.js",
|
||||||
|
"index.d.ts",
|
||||||
|
"react-native.js",
|
||||||
|
"react-native.d.ts",
|
||||||
|
"default.js",
|
||||||
|
"default.d.ts",
|
||||||
|
"index-browser.js",
|
||||||
|
"extension.js",
|
||||||
|
"extension.d.ts",
|
||||||
|
"sql.d.ts",
|
||||||
|
"sql.js",
|
||||||
|
"sql.mjs"
|
||||||
|
],
|
||||||
|
"devDependencies": {
|
||||||
|
"@cloudflare/workers-types": "^4.20250408.0",
|
||||||
|
"@codspeed/benchmark.js-plugin": "4.0.0",
|
||||||
|
"@faker-js/faker": "9.6.0",
|
||||||
|
"@fast-check/jest": "2.0.3",
|
||||||
|
"@hono/node-server": "1.19.0",
|
||||||
|
"@inquirer/prompts": "7.3.3",
|
||||||
|
"@jest/create-cache-key-function": "29.7.0",
|
||||||
|
"@jest/globals": "29.7.0",
|
||||||
|
"@jest/test-sequencer": "29.7.0",
|
||||||
|
"@libsql/client": "0.8.1",
|
||||||
|
"@neondatabase/serverless": "0.10.2",
|
||||||
|
"@opentelemetry/api": "1.9.0",
|
||||||
|
"@opentelemetry/context-async-hooks": "2.1.0",
|
||||||
|
"@opentelemetry/instrumentation": "0.206.0",
|
||||||
|
"@opentelemetry/resources": "2.1.0",
|
||||||
|
"@opentelemetry/sdk-trace-base": "2.1.0",
|
||||||
|
"@opentelemetry/semantic-conventions": "1.37.0",
|
||||||
|
"@planetscale/database": "1.19.0",
|
||||||
|
"@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7",
|
||||||
|
"@prisma/mini-proxy": "0.9.5",
|
||||||
|
"@prisma/query-compiler-wasm": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7",
|
||||||
|
"@prisma/query-engine-wasm": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773",
|
||||||
|
"@snaplet/copycat": "6.0.0",
|
||||||
|
"@swc-node/register": "1.10.9",
|
||||||
|
"@swc/core": "1.11.5",
|
||||||
|
"@swc/jest": "0.2.37",
|
||||||
|
"@timsuchanek/copy": "1.4.5",
|
||||||
|
"@types/debug": "4.1.12",
|
||||||
|
"@types/fs-extra": "11.0.4",
|
||||||
|
"@types/jest": "29.5.14",
|
||||||
|
"@types/js-levenshtein": "1.1.3",
|
||||||
|
"@types/mssql": "9.1.8",
|
||||||
|
"@types/node": "18.19.76",
|
||||||
|
"@types/pg": "8.11.11",
|
||||||
|
"arg": "5.0.2",
|
||||||
|
"benchmark": "2.1.4",
|
||||||
|
"cookie-es": "2.0.0",
|
||||||
|
"decimal.js": "10.5.0",
|
||||||
|
"execa": "8.0.1",
|
||||||
|
"expect-type": "1.2.2",
|
||||||
|
"fs-extra": "11.3.0",
|
||||||
|
"get-stream": "6.0.1",
|
||||||
|
"globby": "11.1.0",
|
||||||
|
"indent-string": "4.0.0",
|
||||||
|
"jest": "29.7.0",
|
||||||
|
"jest-extended": "4.0.2",
|
||||||
|
"jest-junit": "16.0.0",
|
||||||
|
"jest-serializer-ansi-escapes": "4.0.0",
|
||||||
|
"jest-snapshot": "29.7.0",
|
||||||
|
"js-levenshtein": "1.1.6",
|
||||||
|
"kleur": "4.1.5",
|
||||||
|
"klona": "2.0.6",
|
||||||
|
"mariadb": "3.4.5",
|
||||||
|
"memfs": "4.17.2",
|
||||||
|
"mssql": "11.0.1",
|
||||||
|
"new-github-issue-url": "0.2.1",
|
||||||
|
"p-retry": "4.6.2",
|
||||||
|
"pg": "8.14.1",
|
||||||
|
"resolve": "1.22.10",
|
||||||
|
"simple-statistics": "7.8.8",
|
||||||
|
"sort-keys": "5.1.0",
|
||||||
|
"source-map-support": "0.5.21",
|
||||||
|
"sql-template-tag": "5.2.1",
|
||||||
|
"stacktrace-parser": "0.1.11",
|
||||||
|
"strip-ansi": "7.1.0",
|
||||||
|
"strip-indent": "4.0.0",
|
||||||
|
"tempy": "3.0.0",
|
||||||
|
"ts-pattern": "5.6.2",
|
||||||
|
"tsd": "0.31.2",
|
||||||
|
"typescript": "5.4.5",
|
||||||
|
"undici": "7.4.0",
|
||||||
|
"zx": "8.4.1",
|
||||||
|
"@prisma/adapter-better-sqlite3": "6.19.3",
|
||||||
|
"@prisma/adapter-d1": "6.19.3",
|
||||||
|
"@prisma/adapter-libsql": "6.19.3",
|
||||||
|
"@prisma/adapter-mariadb": "6.19.3",
|
||||||
|
"@prisma/adapter-mssql": "6.19.3",
|
||||||
|
"@prisma/adapter-pg": "6.19.3",
|
||||||
|
"@prisma/adapter-planetscale": "6.19.3",
|
||||||
|
"@prisma/adapter-neon": "6.19.3",
|
||||||
|
"@prisma/client-common": "6.19.3",
|
||||||
|
"@prisma/client-engine-runtime": "6.19.3",
|
||||||
|
"@prisma/client-generator-ts": "6.19.3",
|
||||||
|
"@prisma/client-generator-js": "6.19.3",
|
||||||
|
"@prisma/config": "6.19.3",
|
||||||
|
"@prisma/dmmf": "6.19.3",
|
||||||
|
"@prisma/debug": "6.19.3",
|
||||||
|
"@prisma/engines": "6.19.3",
|
||||||
|
"@prisma/fetch-engine": "6.19.3",
|
||||||
|
"@prisma/driver-adapter-utils": "6.19.3",
|
||||||
|
"@prisma/generator": "6.19.3",
|
||||||
|
"@prisma/instrumentation": "6.19.3",
|
||||||
|
"@prisma/get-platform": "6.19.3",
|
||||||
|
"@prisma/internals": "6.19.3",
|
||||||
|
"@prisma/query-plan-executor": "6.19.3",
|
||||||
|
"@prisma/ts-builders": "6.19.3",
|
||||||
|
"@prisma/generator-helper": "6.19.3",
|
||||||
|
"@prisma/migrate": "6.19.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"prisma": "*",
|
||||||
|
"typescript": ">=5.1.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"prisma": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sideEffects": false,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "DEV=true tsx helpers/build.ts",
|
||||||
|
"build": "tsx helpers/build.ts",
|
||||||
|
"test": "dotenv -e ../../.db.env -- jest --silent",
|
||||||
|
"test:e2e": "dotenv -e ../../.db.env -- tsx tests/e2e/_utils/run.ts",
|
||||||
|
"test:functional": "dotenv -e ../../.db.env -- tsx helpers/functional-test/run-tests.ts",
|
||||||
|
"test:functional:client": "pnpm run test:functional --client-runtime client --engine-type client",
|
||||||
|
"test:memory": "dotenv -e ../../.db.env -- tsx helpers/memory-tests.ts",
|
||||||
|
"test:functional:code": "dotenv -e ../../.db.env -- tsx helpers/functional-test/run-tests.ts --no-types",
|
||||||
|
"test:functional:types": "dotenv -e ../../.db.env -- tsx helpers/functional-test/run-tests.ts --types-only",
|
||||||
|
"test-notypes": "dotenv -e ../../.db.env -- jest --testPathIgnorePatterns src/__tests__/types/types.test.ts",
|
||||||
|
"generate": "node scripts/postinstall.js",
|
||||||
|
"postinstall": "node scripts/postinstall.js",
|
||||||
|
"new-test": "tsx ./helpers/new-test/new-test.ts"
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from '.prisma/client/react-native'
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
...require('.prisma/client/react-native'),
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./library"
|
||||||
+286
File diff suppressed because one or more lines are too long
+7
File diff suppressed because one or more lines are too long
+293
File diff suppressed because one or more lines are too long
+7
File diff suppressed because one or more lines are too long
+3982
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user