diff --git a/Ringstats_Dokumentation.tex b/Ringstats_Dokumentation.tex index 9d79058f..6d52232a 100644 --- a/Ringstats_Dokumentation.tex +++ b/Ringstats_Dokumentation.tex @@ -323,12 +323,12 @@ Ein klarer Vorteil ist, dass das Modell extrem leserlich und einfach zu modellie Ein klarer Nachteil ist bei uns, dass das meiste über ids referenziert wird und nur wenig wirklich verschachtelt ist. Zwar bringt eine Weapon ihren item\_type und item\_effect direkt mit, aber Player, Item, Region und Enemy zeigen größtenteils nur über ids aufeinander. Dadurch nutzen wir die eigentliche Stärke des Modells kaum und haben am Ende vor allem viele einzelne Dokumente, die sich gegenseitig referenzieren. \subsection{Vor- und Nachteile des Graph-Modells} -\todo[inline]{Wir koennten Regionen Traversierung Pathfinding einbauen} +% \todo[inline]{Wir koennten Regionen Traversierung Pathfinding einbauen} Ein klarer Vorteil ist, dass sich Beziehungen sehr leicht abfragen lassen. Wir können zum Beispiel direkt verfolgen, welcher Player welche Weapon trägt oder welches Item ein Enemy fallen lässt, ohne die Verbindungen umständlich selbst zusammenzubauen. -\todo[inline]{Wir haben keine Beziehungen, die ueber mehrere Knoten hinweg gehen. Keine komlexen beziehungen wo der graph einen hohen nutzen mit sich bringt} +% \todo[inline]{Wir haben keine Beziehungen, die ueber mehrere Knoten hinweg gehen. Keine komlexen beziehungen wo der graph einen hohen nutzen mit sich bringt} Ein klarer Nachteil ist, dass Neo4j für unseren Fall überdimensioniert ist. Bei uns geht es vor allem darum, die Daten des Player schnell zu schreiben und wieder auszulesen, und nicht darum, ständig tiefe Beziehungen abzufragen. Für dieses einfache Schreiben und Lesen bringt der Graph mehr Aufwand mit, als er uns am Ende Vorteil bringt. diff --git a/build/Ringstats_Dokumentation.aux b/build/Ringstats_Dokumentation.aux index fd87e117..ff88a506 100644 --- a/build/Ringstats_Dokumentation.aux +++ b/build/Ringstats_Dokumentation.aux @@ -37,10 +37,6 @@ \@writefile{toc}{\contentsline {subsection}{\numberline {3.1}Vor- und Nachteile des Key-Value-Modells}{8}{subsection.3.1}\protected@file@percent } \@writefile{toc}{\contentsline {subsection}{\numberline {3.2}Vor- und Nachteile des dokumentenorientierten Modells}{8}{subsection.3.2}\protected@file@percent } \@writefile{toc}{\contentsline {subsection}{\numberline {3.3}Vor- und Nachteile des Graph-Modells}{8}{subsection.3.3}\protected@file@percent } -\@writefile{tdo}{\contentsline {todo}{Wir koennten Regionen Traversierung Pathfinding einbauen}{8}{section*.1}\protected@file@percent } -\pgfsyspdfmark {pgfid1}{19562756}{23935922} -\@writefile{tdo}{\contentsline {todo}{Wir haben keine Beziehungen, die ueber mehrere Knoten hinweg gehen. Keine komlexen beziehungen wo der graph einen hohen nutzen mit sich bringt}{8}{section*.2}\protected@file@percent } -\pgfsyspdfmark {pgfid2}{19562756}{18172368} \BKM@entry{id=13,dest={73656374696F6E2E34},srcline={340}}{5C3337365C3337375C303030465C303030615C3030307A5C303030695C30303074} \@writefile{toc}{\contentsline {section}{\numberline {4}Fazit}{9}{section.4}\protected@file@percent } \global\@namedef{scr@dte@section@lastmaxnumwidth}{12.14694pt} diff --git a/build/Ringstats_Dokumentation.fdb_latexmk b/build/Ringstats_Dokumentation.fdb_latexmk index dc97449f..9ae3462d 100644 --- a/build/Ringstats_Dokumentation.fdb_latexmk +++ b/build/Ringstats_Dokumentation.fdb_latexmk @@ -1,5 +1,5 @@ # Fdb version 4 -["pdflatex"] 1780313072.59105 "Ringstats_Dokumentation.tex" "build/Ringstats_Dokumentation.pdf" "Ringstats_Dokumentation" 1780313073.65504 0 +["pdflatex"] 1780687793.04109 "Ringstats_Dokumentation.tex" "build/Ringstats_Dokumentation.pdf" "Ringstats_Dokumentation" 1780687794.00438 0 "/usr/share/texmf-dist/fonts/enc/dvips/cm-super/cm-super-t1.enc" 1775415801 2971 def0b6c1f0b107b3b936def894055589 "" "/usr/share/texmf-dist/fonts/map/fontname/texfonts.map" 1775415801 3524 cb3e574dea2d1052e39280babc910dc8 "" "/usr/share/texmf-dist/fonts/tfm/jknappen/ec/ecbx1200.tfm" 1775415801 3584 402da0b29eafbad07963b1224b222f18 "" @@ -159,12 +159,12 @@ "/usr/share/texmf-dist/tex/latex/xcolor/xcolor.sty" 1775415801 55384 b454dec21c2d9f45ec0b793f0995b992 "" "/usr/share/texmf-dist/tex/latex/xkeyval/xkeyval.sty" 1775415801 5006 d7e899bb5d9717c7f214e9bc0284de7b "" "/usr/share/texmf-dist/web2c/texmf.cnf" 1775415801 43569 fd570f2fa160877d211e859f687312ba "" - "/var/lib/texmf/fonts/map/pdftex/updmap/pdftex.map" 1777228096 5385779 47c0c393a77fd4a098bacfd57d66b88a "" - "/var/lib/texmf/web2c/pdftex/pdflatex.fmt" 1778762763 3734533 c370cc050c4a9823a1fe1ab265afc57b "" - "Ringstats_Dokumentation.tex" 1780313071.55337 16584 03bd9bb6b9242fada2b5fd161766d7ed "" + "/var/lib/texmf/fonts/map/pdftex/updmap/pdftex.map" 1780436122 5385779 47c0c393a77fd4a098bacfd57d66b88a "" + "/var/lib/texmf/web2c/pdftex/pdflatex.fmt" 1780436120 3734532 282dcc45f1c728134b92f993ed6db952 "" + "Ringstats_Dokumentation.tex" 1780687790.47947 16588 b04ea6d6404d252977bf34dce650e1c0 "" "assets/er-modell.png" 1778764597.46144 805989 0b7323ca4b38e6518c24fd9e5684655b "" - "build/Ringstats_Dokumentation.aux" 1780313073.61548 8525 dc2f45dff6b0575fd760f48e262e0d7d "pdflatex" - "build/Ringstats_Dokumentation.toc" 1780313073.61648 1305 1401ae3dd1fbee665ef3e10a49daf4b1 "pdflatex" + "build/Ringstats_Dokumentation.aux" 1780687793.96743 8079 e14d3fbb21e9c686a9967800d978e8e7 "pdflatex" + "build/Ringstats_Dokumentation.toc" 1780687793.96843 1305 1401ae3dd1fbee665ef3e10a49daf4b1 "pdflatex" (generated) "build/Ringstats_Dokumentation.aux" "build/Ringstats_Dokumentation.log" diff --git a/build/Ringstats_Dokumentation.log b/build/Ringstats_Dokumentation.log index 28e41afc..7990a5bf 100644 --- a/build/Ringstats_Dokumentation.log +++ b/build/Ringstats_Dokumentation.log @@ -1,4 +1,4 @@ -This is pdfTeX, Version 3.141592653-2.6-1.40.29 (TeX Live 2026/Arch Linux) (preloaded format=pdflatex 2026.5.14) 1 JUN 2026 13:24 +This is pdfTeX, Version 3.141592653-2.6-1.40.29 (TeX Live 2026/Arch Linux) (preloaded format=pdflatex 2026.6.2) 5 JUN 2026 21:29 entering extended mode restricted \write18 enabled. %&-line parsing enabled. @@ -737,33 +737,20 @@ Overfull \hbox (9.63942pt too wide) in paragraph at lines 296--308 [][] [] -[7] -Package hyperref Info: bookmark level for unknown todo defaults to 0 on input l -ine 326. - -Overfull \hbox (11.24713pt too wide) in paragraph at lines 326--326 -[] - [] - - -Overfull \hbox (11.24713pt too wide) in paragraph at lines 331--331 -[] - [] - -[8] [9] (build/Ringstats_Dokumentation.aux) +[7] [8] [9] (build/Ringstats_Dokumentation.aux) *********** LaTeX2e <2025-11-01> L3 programming layer <2026-01-19> *********** ) Here is how much of TeX's memory you used: - 24546 strings out of 469462 - 479786 string characters out of 5468614 + 24473 strings out of 469462 + 477938 string characters out of 5468615 1009471 words of memory out of 5000000 - 52828 multiletter control sequences out of 15000+600000 + 52757 multiletter control sequences out of 15000+600000 638939 words of font info for 63 fonts, out of 8000000 for 9000 14 hyphenation exceptions out of 8191 - 108i,8n,108p,11253b,869s stack positions out of 10000i,1000n,20000p,200000b,200000s + 108i,8n,108p,11253b,461s stack positions out of 10000i,1000n,20000p,200000b,200000s -Output written on build/Ringstats_Dokumentation.pdf (9 pages, 608048 bytes). +Output written on build/Ringstats_Dokumentation.pdf (9 pages, 607609 bytes). PDF statistics: - 172 PDF objects out of 1000 (max. 8388607) - 141 compressed objects within 2 object streams - 29 named destinations out of 1000 (max. 500000) + 170 PDF objects out of 1000 (max. 8388607) + 139 compressed objects within 2 object streams + 27 named destinations out of 1000 (max. 500000) 122 words of extra memory for PDF output out of 10000 (max. 10000000) diff --git a/build/Ringstats_Dokumentation.pdf b/build/Ringstats_Dokumentation.pdf index da320823..01777e67 100644 Binary files a/build/Ringstats_Dokumentation.pdf and b/build/Ringstats_Dokumentation.pdf differ diff --git a/endpraesi/demo/Ringstats_Dokumentation.pdf b/endpraesi/demo/Ringstats_Dokumentation.pdf new file mode 100644 index 00000000..1b8c0b32 Binary files /dev/null and b/endpraesi/demo/Ringstats_Dokumentation.pdf differ diff --git a/endpraesi/demo/client-redit/.gitignore b/endpraesi/demo/client-redit/.gitignore new file mode 100644 index 00000000..a14702c4 --- /dev/null +++ b/endpraesi/demo/client-redit/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/endpraesi/demo/client-redit/CLAUDE.md b/endpraesi/demo/client-redit/CLAUDE.md new file mode 100644 index 00000000..28c00b9e --- /dev/null +++ b/endpraesi/demo/client-redit/CLAUDE.md @@ -0,0 +1,110 @@ + +Default to using Bun instead of Node.js. + +- Use `bun ` instead of `node ` or `ts-node ` +- Use `bun test` instead of `jest` or `vitest` +- Use `bun build ` instead of `webpack` or `esbuild` +- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install` +- Use `bun run + + +``` + +With the following `frontend.tsx`: + +```tsx#frontend.tsx +import React from "react"; + +// import .css files directly and it works +import './index.css'; + +import { createRoot } from "react-dom/client"; + +const root = createRoot(document.body); + +export default function Frontend() { + return

Hello, world!

; +} + +root.render(); +``` + +Then, run index.ts + +```sh +bun --hot ./index.ts +``` + +For more information, read the Bun API docs in `node_modules/bun-types/docs/**.md`. + +## Coding Style Preferences + +- Write everything WITHOUT comments. diff --git a/endpraesi/demo/client-redit/Doku.md b/endpraesi/demo/client-redit/Doku.md new file mode 100644 index 00000000..3897341a --- /dev/null +++ b/endpraesi/demo/client-redit/Doku.md @@ -0,0 +1,8 @@ +# Projekt-Dokumentation: Entwicklungsreihenfolge + +1. Projekt init +2. Docker compose mit redis +3. Einfache verbindung +4. Redis getestet (mit set, get und incr) +5. Init-Skript (db/init.ts) mit ~100 statischen Entitäten erstellt (für reproduzierbare Tests) +6. Client (index.ts) aktualisiert Player-Daten live und liest statische Gegner-Daten diff --git a/endpraesi/demo/client-redit/README.md b/endpraesi/demo/client-redit/README.md new file mode 100644 index 00000000..cd8c3af3 --- /dev/null +++ b/endpraesi/demo/client-redit/README.md @@ -0,0 +1,15 @@ +# client-redit + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.3.3. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime. diff --git a/endpraesi/demo/client-redit/bun.lock b/endpraesi/demo/client-redit/bun.lock new file mode 100644 index 00000000..ea13b0f8 --- /dev/null +++ b/endpraesi/demo/client-redit/bun.lock @@ -0,0 +1,26 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "client-redit", + "devDependencies": { + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5", + }, + }, + }, + "packages": { + "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], + + "@types/node": ["@types/node@26.1.0", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw=="], + + "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], + } +} diff --git a/endpraesi/demo/client-redit/db/init.ts b/endpraesi/demo/client-redit/db/init.ts new file mode 100644 index 00000000..1303033f --- /dev/null +++ b/endpraesi/demo/client-redit/db/init.ts @@ -0,0 +1,210 @@ +import { RedisClient } from "bun"; +const redis = new RedisClient("redis://localhost:40379"); + +console.log("Populating database with ~100 static Ringstats entries..."); + +const effects = [ + { id: 1, name: "Stärke", value: 5 }, + { id: 2, name: "Geschick", value: 5 }, + { id: 3, name: "Blutung", value: 50 }, + { id: 4, name: "Gift", value: 45 }, + { id: 5, name: "Scharlachfäule", value: 50 }, + { id: 6, name: "Frost", value: 55 }, + { id: 7, name: "Heilung", value: 100 }, + { id: 8, name: "Magieschaden", value: 20 }, + { id: 9, name: "Feuerschaden", value: 30 }, + { id: 10, name: "Blitzschaden", value: 25 }, +]; + +const types = [ + { id: 1, item_id: 101, name: "Dolch" }, + { id: 2, item_id: 102, name: "Gerades Schwert" }, + { id: 3, item_id: 103, name: "Großschwert" }, + { id: 4, item_id: 104, name: "Katana" }, + { id: 5, item_id: 105, name: "Helm" }, + { id: 6, item_id: 106, name: "Brustpanzer" }, + { id: 7, item_id: 107, name: "Stulpen" }, + { id: 8, item_id: 108, name: "Beinschutz" }, + { id: 9, item_id: 109, name: "Mittlerer Schild" }, + { id: 10, item_id: 110, name: "Talisman-Amulett" }, +]; + +const weapons = [ + { id: 1, item_effect_id: 3, item_type_id: 4, weight: 5.5, damage: 115 }, // Uchigatana + { id: 2, item_effect_id: 3, item_type_id: 4, weight: 6.5, damage: 118 }, // Rivers of Blood + { id: 3, item_effect_id: 8, item_type_id: 3, weight: 10.0, damage: 160 }, // Dark Moon Greatsword + { id: 4, item_effect_id: 1, item_type_id: 3, weight: 23.0, damage: 210 }, // Guts Greatsword + { id: 5, item_effect_id: 3, item_type_id: 1, weight: 1.5, damage: 74 }, // Reduvia + { id: 6, item_effect_id: 9, item_type_id: 2, weight: 4.0, damage: 105 }, // Blasphemous Blade + { id: 7, item_effect_id: 10, item_type_id: 4, weight: 6.0, damage: 110 }, // Dragonscale Blade + { id: 8, item_effect_id: 1, item_type_id: 3, weight: 15.0, damage: 175 }, // Claymore + { id: 9, item_effect_id: 2, item_type_id: 1, weight: 2.0, damage: 80 }, // Misericorde + { id: 10, item_effect_id: 6, item_type_id: 2, weight: 4.5, damage: 100 }, // Icerind Hatchet (simulated as straight sword for simplicity) +]; + +const armors = [ + { id: 1, item_effect_id: 1, item_type_id: 5, weight: 4.6, defense: 14 }, + { id: 2, item_effect_id: 1, item_type_id: 6, weight: 10.6, defense: 35 }, + { id: 3, item_effect_id: 1, item_type_id: 7, weight: 3.5, defense: 11 }, + { id: 4, item_effect_id: 1, item_type_id: 8, weight: 6.6, defense: 20 }, + { id: 5, item_effect_id: 8, item_type_id: 5, weight: 2.2, defense: 8 }, + { id: 6, item_effect_id: 8, item_type_id: 6, weight: 5.1, defense: 15 }, + { id: 7, item_effect_id: 9, item_type_id: 5, weight: 5.5, defense: 18 }, + { id: 8, item_effect_id: 9, item_type_id: 6, weight: 11.8, defense: 42 }, + { id: 9, item_effect_id: 10, item_type_id: 5, weight: 3.0, defense: 10 }, + { id: 10, item_effect_id: 10, item_type_id: 6, weight: 7.0, defense: 22 }, +]; + +const shields = [ + { id: 1, item_effect_id: 1, item_type_id: 9, weight: 3.5, damage: 45, defense: 100 }, + { id: 2, item_effect_id: 8, item_type_id: 9, weight: 4.5, damage: 55, defense: 100 }, + { id: 3, item_effect_id: 9, item_type_id: 9, weight: 5.5, damage: 65, defense: 100 }, + { id: 4, item_effect_id: 10, item_type_id: 9, weight: 4.0, damage: 50, defense: 100 }, + { id: 5, item_effect_id: 6, item_type_id: 9, weight: 3.0, damage: 40, defense: 95 }, +]; + +const talismans = [ + { id: 1, item_effect_id: 1 }, + { id: 2, item_effect_id: 2 }, + { id: 3, item_effect_id: 7 }, + { id: 4, item_effect_id: 8 }, + { id: 5, item_effect_id: 9 }, +]; + +const items = [ + // Weapons (id 1-10) + { id: 1, name: "Uchigatana", gear_type: "weapon", gear_id: 1 }, + { id: 2, name: "Rivers of Blood", gear_type: "weapon", gear_id: 2 }, + { id: 3, name: "Dark Moon Greatsword", gear_type: "weapon", gear_id: 3 }, + { id: 4, name: "Guts Greatsword", gear_type: "weapon", gear_id: 4 }, + { id: 5, name: "Reduvia", gear_type: "weapon", gear_id: 5 }, + { id: 6, name: "Blasphemous Blade", gear_type: "weapon", gear_id: 6 }, + { id: 7, name: "Dragonscale Blade", gear_type: "weapon", gear_id: 7 }, + { id: 8, name: "Claymore", gear_type: "weapon", gear_id: 8 }, + { id: 9, name: "Misericorde", gear_type: "weapon", gear_id: 9 }, + { id: 10, name: "Icerind Hatchet", gear_type: "weapon", gear_id: 10 }, + // Armors (id 11-20) + { id: 11, name: "Knight Helm", gear_type: "armor", gear_id: 1 }, + { id: 12, name: "Knight Armor", gear_type: "armor", gear_id: 2 }, + { id: 13, name: "Knight Gauntlets", gear_type: "armor", gear_id: 3 }, + { id: 14, name: "Knight Greaves", gear_type: "armor", gear_id: 4 }, + { id: 15, name: "Astrologer Hood", gear_type: "armor", gear_id: 5 }, + { id: 16, name: "Astrologer Robe", gear_type: "armor", gear_id: 6 }, + { id: 17, name: "Radahn Helm", gear_type: "armor", gear_id: 7 }, + { id: 18, name: "Radahn Lion Armor", gear_type: "armor", gear_id: 8 }, + { id: 19, name: "Carian Knight Helm", gear_type: "armor", gear_id: 9 }, + { id: 20, name: "Carian Knight Armor", gear_type: "armor", gear_id: 10 }, + // Shields (id 21-25) + { id: 21, name: "Brass Shield", gear_type: "shield", gear_id: 1 }, + { id: 22, name: "Carian Knight Shield", gear_type: "shield", gear_id: 2 }, + { id: 23, name: "Kite Shield", gear_type: "shield", gear_id: 3 }, + { id: 24, name: "Banished Knight Shield", gear_type: "shield", gear_id: 4 }, + { id: 25, name: "Heater Shield", gear_type: "shield", gear_id: 5 }, + // Talismans (id 26-30) + { id: 26, name: "Erdtree's Favor", gear_type: "talisman", gear_id: 1 }, + { id: 27, name: "Radagon's Soreseal", gear_type: "talisman", gear_id: 2 }, + { id: 28, name: "Crimson Amber Medallion", gear_type: "talisman", gear_id: 3 }, + { id: 29, name: "Graven-Mass Talisman", gear_type: "talisman", gear_id: 4 }, + { id: 30, name: "Fire Scorpion Charm", gear_type: "talisman", gear_id: 5 }, + { id: 31, name: "Crimson Flask", gear_type: "consumable" }, + { id: 32, name: "Cerulean Flask", gear_type: "consumable" }, + { id: 33, name: "Rune Arc", gear_type: "consumable" }, + { id: 34, name: "Golden Rune [1]", gear_type: "consumable" }, + { id: 35, name: "Golden Rune [5]", gear_type: "consumable" }, + { id: 36, name: "Golden Rune [10]", gear_type: "consumable" }, + { id: 37, name: "Hero's Rune [1]", gear_type: "consumable" }, + { id: 38, name: "Lord's Rune", gear_type: "consumable" }, + { id: 39, name: "Remembrance of the Grafted", gear_type: "consumable" }, + { id: 40, name: "Remembrance of the Full Moon Queen", gear_type: "consumable" }, + { id: 41, name: "Remembrance of the Starscourge", gear_type: "consumable" }, + { id: 42, name: "Remembrance of the Omen King", gear_type: "consumable" }, + { id: 43, name: "Remembrance of the Fire Giant", gear_type: "consumable" }, + { id: 44, name: "Remembrance of the Black Blade", gear_type: "consumable" }, + { id: 45, name: "Smithing Stone [1]", gear_type: "material" }, + { id: 46, name: "Smithing Stone [4]", gear_type: "material" }, + { id: 47, name: "Somber Smithing Stone [1]", gear_type: "material" }, + { id: 48, name: "Somber Smithing Stone [7]", gear_type: "material" }, + { id: 49, name: "Glovewort [1]", gear_type: "material" }, + { id: 50, name: "Ghost Glovewort [9]", gear_type: "material" }, +]; + +const regions = [ + { id: 1, name: "Limgrave", items: ["1", "8", "21", "26"], enemies: ["1", "2", "3", "11", "12", "13", "14"] }, + { id: 2, name: "Liurnia of the Lakes", items: ["3", "15", "16", "19", "20", "22", "29"], enemies: ["4", "5", "15", "16", "17", "18"] }, + { id: 3, name: "Caelid", items: ["4", "17", "18"], enemies: ["6", "7", "19", "20", "21", "22"] }, + { id: 4, name: "Altus Plateau", items: ["2", "23", "24"], enemies: ["8", "23", "24", "25"] }, + { id: 5, name: "Mountaintops of the Giants", items: ["10"], enemies: ["9", "26", "27", "28"] }, + { id: 6, name: "Farum Azula", items: ["6", "25", "30"], enemies: ["10", "29", "30"] }, + { id: 7, name: "Weeping Peninsula", items: [], enemies: [] }, + { id: 8, name: "Siofra River", items: [], enemies: [] }, + { id: 9, name: "Ainsel River", items: [], enemies: [] }, + { id: 10, name: "Deeproot Depths", items: [], enemies: [] }, + { id: 11, name: "Lake of Rot", items: [], enemies: [] }, + { id: 12, name: "Consecrated Snowfield", items: [], enemies: [] }, + { id: 13, name: "Miquella's Haligtree", items: [], enemies: [] }, + { id: 14, name: "Mohgwyn Palace", items: [], enemies: [] }, + { id: 15, name: "Leyndell, Royal Capital", items: [], enemies: [] }, + { id: 16, name: "Leyndell, Ashen Capital", items: [], enemies: [] }, +]; + +const enemies = [ + { id: 1, region_id: 1, name: "Tree Sentinel", health: 3000, level: 20, damage: 70, items: ["1", "47", "34"] }, + { id: 2, region_id: 1, name: "Margit, the Fell Omen", health: 4100, level: 25, damage: 100, items: ["26", "33", "34"] }, + { id: 3, region_id: 1, name: "Godrick the Grafted", health: 6000, level: 30, damage: 150, items: ["33", "39"] }, + { id: 4, region_id: 2, name: "Rennala, Queen of the Full Moon", health: 5000, level: 40, damage: 200, items: ["15", "16", "33", "40"] }, + { id: 5, region_id: 2, name: "Red Wolf of Radagon", health: 3000, level: 35, damage: 120, items: ["29", "35", "46"] }, + { id: 6, region_id: 3, name: "Starscourge Radahn", health: 10000, level: 60, damage: 400, items: ["17", "18", "33", "41"] }, + { id: 7, region_id: 3, name: "Commander O'Neil", health: 7000, level: 50, damage: 250, items: ["33", "35"] }, + { id: 8, region_id: 4, name: "Morgott, the Omen King", health: 12000, level: 70, damage: 500, items: ["33", "42"] }, + { id: 9, region_id: 5, name: "Fire Giant", health: 25000, level: 90, damage: 800, items: ["30", "33", "43"] }, + { id: 10, region_id: 6, name: "Maliketh, the Black Blade", health: 18000, level: 110, damage: 1000, items: ["33", "44"] }, + { id: 11, region_id: 1, name: "Godrick Soldier", health: 150, level: 5, damage: 20, items: ["21", "34", "45"] }, + { id: 12, region_id: 1, name: "Wandering Noble", health: 80, level: 2, damage: 10, items: ["31", "34"] }, + { id: 13, region_id: 1, name: "Limgrave Knight", health: 350, level: 12, damage: 40, items: ["11", "12", "13", "14", "23", "45"] }, + { id: 14, region_id: 1, name: "Demi-Human", health: 120, level: 4, damage: 15, items: ["34", "45"] }, + { id: 15, region_id: 2, name: "Raya Lucaria Soldier", health: 200, level: 15, damage: 30, items: ["22", "34", "46"] }, + { id: 16, region_id: 2, name: "Marionette Soldier", health: 180, level: 16, damage: 25, items: ["45", "49"] }, + { id: 17, region_id: 2, name: "Albinauric", health: 250, level: 18, damage: 35, items: ["34", "35"] }, + { id: 18, region_id: 2, name: "Carian Knight", health: 600, level: 28, damage: 80, items: ["19", "20", "22", "47"] }, + { id: 19, region_id: 3, name: "Radahn Soldier", health: 300, level: 25, damage: 45, items: ["35", "46"] }, + { id: 20, region_id: 3, name: "Monstrous Dog", health: 800, level: 32, damage: 110, items: ["35", "49"] }, + { id: 21, region_id: 3, name: "Monstrous Crow", health: 750, level: 33, damage: 120, items: ["35", "49"] }, + { id: 22, region_id: 3, name: "Cleanrot Knight", health: 900, level: 38, damage: 100, items: ["36", "48"] }, + { id: 23, region_id: 4, name: "Leyndell Soldier", health: 400, level: 40, damage: 60, items: ["36", "46"] }, + { id: 24, region_id: 4, name: "Leyndell Knight", health: 1200, level: 48, damage: 140, items: ["24", "36", "48"] }, + { id: 25, region_id: 4, name: "Omen", health: 1500, level: 50, damage: 180, items: ["36", "48", "33"] }, + { id: 26, region_id: 5, name: "Fire Monk", health: 800, level: 55, damage: 120, items: ["36", "48"] }, + { id: 27, region_id: 5, name: "Giant Dog", health: 1100, level: 60, damage: 160, items: ["36"] }, + { id: 28, region_id: 5, name: "Troll", health: 2200, level: 65, damage: 220, items: ["37", "48"] }, + { id: 29, region_id: 6, name: "Beastman of Farum Azula", health: 1000, level: 80, damage: 200, items: ["37", "48"] }, + { id: 30, region_id: 6, name: "Banished Knight", health: 2000, level: 85, damage: 280, items: ["24", "37", "48", "50"] }, +]; + +// Helper for hset +const setHash = async (prefix: string, list: any[]) => { + for (const obj of list) { + // extract items/enemies sets if they exist + const { items, enemies, ...hashData } = obj; + await redis.hset(`${prefix}:${obj.id}`, hashData); + if (items && items.length > 0) { + await redis.sadd(`${prefix}:${obj.id}:items`, ...items); + } + if (enemies && enemies.length > 0) { + await redis.sadd(`${prefix}:${obj.id}:enemies`, ...enemies); + } + } +}; + +await setHash("item_effect", effects); +await setHash("item_type", types); +await setHash("weapon", weapons); +await setHash("armor", armors); +await setHash("shield", shields); +await setHash("talisman", talismans); +await setHash("item", items); +await setHash("region", regions); +await setHash("enemy", enemies); + +console.log(`Inserted ${effects.length + types.length + weapons.length + armors.length + shields.length + talismans.length + items.length + regions.length + enemies.length} primary entities into Redis.`); +console.log("Database successfully seeded."); +process.exit(0); diff --git a/endpraesi/demo/client-redit/docker-compose.yml b/endpraesi/demo/client-redit/docker-compose.yml new file mode 100644 index 00000000..ea0f761e --- /dev/null +++ b/endpraesi/demo/client-redit/docker-compose.yml @@ -0,0 +1,6 @@ +services: + redis: + image: redis:alpine + ports: + - "40379:6379" + restart: always diff --git a/endpraesi/demo/client-redit/index.ts b/endpraesi/demo/client-redit/index.ts new file mode 100644 index 00000000..c5f4d423 --- /dev/null +++ b/endpraesi/demo/client-redit/index.ts @@ -0,0 +1,144 @@ +import { RedisClient } from "bun"; +const redis = new RedisClient("redis://localhost:40379"); + +const colors = { + reset: "\x1b[0m", + cyan: "\x1b[36m", + green: "\x1b[32m", + yellow: "\x1b[33m", + magenta: "\x1b[35m", + red: "\x1b[31m", + gray: "\x1b[90m", + bold: "\x1b[1m", +}; + +let totalRequests = 0; + +function log(msg: string, color: string = colors.reset) { + const timestamp = new Date().toISOString().split('T')[1].slice(0, -1); + console.log(`${colors.gray}[${timestamp}]${colors.reset} ${color}${msg}${colors.reset}`); +} + +const introMessage = "Starting Ringstats Desktop Application..."; +log(introMessage, colors.cyan); + +const pingStart = performance.now(); +await redis.ping(); +totalRequests++; +log(`Connected to Ringstats Database successfully. (Reqs: 1 | Total: ${totalRequests} | Time: ${(performance.now() - pingStart).toFixed(2)}ms)`, colors.green); + +log("Looking for Elden Ring process...", colors.gray); +await Bun.sleep(1500); +log("Found Elden Ring process!", colors.green); + +log("Equipping initial gear...", colors.gray); +await redis.del("player:1:weapon", "player:1:armor", "player:1:talisman"); +await redis.sadd("player:1:weapon", "1"); +await redis.sadd("player:1:armor", "11"); +await redis.sadd("player:1:talisman", "26"); +totalRequests += 4; + +let playerHealth = 100; +let playerStamina = 100; +let playerLevel = 1; +let currentRegionId = 1; + +log("Entering live sync loop (press Ctrl+C to exit)...", colors.gray); + +while (true) { + playerHealth = Math.floor(Math.max(10, playerHealth - (Math.random() * 10) + 5)); + playerStamina = Math.floor(Math.max(10, playerStamina - (Math.random() * 20) + 10)); + + if (Math.random() > 0.9) { + playerLevel += 1; + } + + if (Math.random() > 0.85) { + currentRegionId = Math.floor(Math.random() * 16) + 1; + + const startTravel = performance.now(); + const regionName = await redis.hget(`region:${currentRegionId}`, "name"); + const regionEnemiesIds = await redis.smembers(`region:${currentRegionId}:enemies`); + const regionItemsIds = await redis.smembers(`region:${currentRegionId}:items`); + + const enemyNames = await Promise.all(regionEnemiesIds.map(id => redis.hget(`enemy:${id}`, "name"))); + const itemNames = await Promise.all(regionItemsIds.map(id => redis.hget(`item:${id}`, "name"))); + + const queries = 3 + regionEnemiesIds.length + regionItemsIds.length; + totalRequests += queries; + const travelTime = (performance.now() - startTravel).toFixed(2); + + console.log(); + log(`>>> Fast Traveled to: [ID: ${currentRegionId}] ${regionName} <<<`, colors.magenta + colors.bold); + log(`[Region Data] Enemies nearby: ${enemyNames.join(", ") || "None"}`, colors.magenta); + log(`[Region Data] Items nearby: ${itemNames.join(", ") || "None"}`, colors.magenta); + log(`[Stats] Query Time: ${travelTime}ms | Reqs in Block: ${queries} | Total Reqs: ${totalRequests}`, colors.gray); + console.log(); + } + + if (Math.random() > 0.90) { // 10% chance to swap gear + const newWeaponId = Math.floor(Math.random() * 10) + 1; // 1 to 10 + const newArmorId = Math.floor(Math.random() * 10) + 11; // 11 to 20 + const newTalismanId = Math.floor(Math.random() * 5) + 26; // 26 to 30 + + const startSwap = performance.now(); + await redis.del("player:1:weapon", "player:1:armor", "player:1:talisman"); + await redis.sadd("player:1:weapon", newWeaponId.toString()); + await redis.sadd("player:1:armor", newArmorId.toString()); + await redis.sadd("player:1:talisman", newTalismanId.toString()); + + const weaponName = await redis.hget(`item:${newWeaponId}`, "name"); + const armorName = await redis.hget(`item:${newArmorId}`, "name"); + const talismanName = await redis.hget(`item:${newTalismanId}`, "name"); + + totalRequests += 7; // 1 del + 3 sadd + 3 hget + const swapTime = (performance.now() - startSwap).toFixed(2); + + console.log(); + log(`>>> Gear Swapped <<<`, colors.cyan + colors.bold); + log(`Equipped: ${weaponName} | ${armorName} | ${talismanName}`, colors.cyan); + log(`[Stats] Query Time: ${swapTime}ms | Reqs in Block: 7 | Total Reqs: ${totalRequests}`, colors.gray); + console.log(); + } + + const startSync = performance.now(); + await redis.hset("player:1", { + id: 1, + name: "Jonas", + health: playerHealth, + stamina: playerStamina, + level: playerLevel, + region_id: currentRegionId + }); + totalRequests += 1; + const syncTime = (performance.now() - startSync).toFixed(2); + + log(`[Live Sync] [ID: 1] Jonas - Lvl: ${playerLevel} | HP: ${playerHealth} | Stamina: ${playerStamina}`, colors.green); + log(`[Stats] Query Time: ${syncTime}ms | Reqs in Block: 1 | Total Reqs: ${totalRequests}`, colors.gray); + + if (Math.random() > 0.7) { + const startLock = performance.now(); + const randomEnemyId = await redis.srandmember(`region:${currentRegionId}:enemies`); + totalRequests += 1; + + if (randomEnemyId) { + console.log(); + log("--- Target Lock Detected ---", colors.red + colors.bold); + const enemyInfo = await redis.hgetall(`enemy:${randomEnemyId}`); + + const dropItemIds = await redis.smembers(`enemy:${randomEnemyId}:items`); + const dropItemNames = await Promise.all(dropItemIds.map(id => redis.hget(`item:${id}`, "name"))); + + const queries = 2 + dropItemIds.length; + totalRequests += queries; + const lockTime = (performance.now() - startLock).toFixed(2); + + log(`Overlay -> [ID: ${enemyInfo.id}] ${enemyInfo.name} | Lvl: ${enemyInfo.level} | HP: ${enemyInfo.health} | DMG: ${enemyInfo.damage}`, colors.yellow); + log(`Drops -> ${dropItemNames.length > 0 ? dropItemNames.join(", ") : "None"}`, colors.yellow); + log(`[Stats] Query Time: ${lockTime}ms | Reqs in Block: ${queries + 1} | Total Reqs: ${totalRequests}`, colors.gray); + console.log(); + } + } + + await Bun.sleep(2000); +} diff --git a/endpraesi/demo/client-redit/package.json b/endpraesi/demo/client-redit/package.json new file mode 100644 index 00000000..7238bfb0 --- /dev/null +++ b/endpraesi/demo/client-redit/package.json @@ -0,0 +1,12 @@ +{ + "name": "client-redit", + "module": "index.ts", + "type": "module", + "private": true, + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5" + } +} diff --git a/endpraesi/demo/client-redit/tsconfig.json b/endpraesi/demo/client-redit/tsconfig.json new file mode 100644 index 00000000..bfa0fead --- /dev/null +++ b/endpraesi/demo/client-redit/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} diff --git a/endpraesi/demo/website/.gitignore b/endpraesi/demo/website/.gitignore new file mode 100644 index 00000000..a14702c4 --- /dev/null +++ b/endpraesi/demo/website/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/endpraesi/demo/website/CLAUDE.md b/endpraesi/demo/website/CLAUDE.md new file mode 100644 index 00000000..1ee68904 --- /dev/null +++ b/endpraesi/demo/website/CLAUDE.md @@ -0,0 +1,106 @@ + +Default to using Bun instead of Node.js. + +- Use `bun ` instead of `node ` or `ts-node ` +- Use `bun test` instead of `jest` or `vitest` +- Use `bun build ` instead of `webpack` or `esbuild` +- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install` +- Use `bun run + + +``` + +With the following `frontend.tsx`: + +```tsx#frontend.tsx +import React from "react"; + +// import .css files directly and it works +import './index.css'; + +import { createRoot } from "react-dom/client"; + +const root = createRoot(document.body); + +export default function Frontend() { + return

Hello, world!

; +} + +root.render(); +``` + +Then, run index.ts + +```sh +bun --hot ./index.ts +``` + +For more information, read the Bun API docs in `node_modules/bun-types/docs/**.md`. diff --git a/endpraesi/demo/website/README.md b/endpraesi/demo/website/README.md new file mode 100644 index 00000000..1fe6af37 --- /dev/null +++ b/endpraesi/demo/website/README.md @@ -0,0 +1,21 @@ +# bun-react-tailwind-template + +To install dependencies: + +```bash +bun install +``` + +To start a development server: + +```bash +bun dev +``` + +To run for production: + +```bash +bun start +``` + +This project was created using `bun init` in bun v1.3.3. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime. diff --git a/endpraesi/demo/website/build.ts b/endpraesi/demo/website/build.ts new file mode 100644 index 00000000..f3c5cd45 --- /dev/null +++ b/endpraesi/demo/website/build.ts @@ -0,0 +1,149 @@ +#!/usr/bin/env bun +import plugin from "bun-plugin-tailwind"; +import { existsSync } from "fs"; +import { rm } from "fs/promises"; +import path from "path"; + +if (process.argv.includes("--help") || process.argv.includes("-h")) { + console.log(` +🏗️ Bun Build Script + +Usage: bun run build.ts [options] + +Common Options: + --outdir Output directory (default: "dist") + --minify Enable minification (or --minify.whitespace, --minify.syntax, etc) + --sourcemap Sourcemap type: none|linked|inline|external + --target Build target: browser|bun|node + --format Output format: esm|cjs|iife + --splitting Enable code splitting + --packages Package handling: bundle|external + --public-path Public path for assets + --env Environment handling: inline|disable|prefix* + --conditions Package.json export conditions (comma separated) + --external External packages (comma separated) + --banner Add banner text to output + --footer Add footer text to output + --define Define global constants (e.g. --define.VERSION=1.0.0) + --help, -h Show this help message + +Example: + bun run build.ts --outdir=dist --minify --sourcemap=linked --external=react,react-dom +`); + process.exit(0); +} + +const toCamelCase = (str: string): string => str.replace(/-([a-z])/g, g => g[1].toUpperCase()); + +const parseValue = (value: string): any => { + if (value === "true") return true; + if (value === "false") return false; + + if (/^\d+$/.test(value)) return parseInt(value, 10); + if (/^\d*\.\d+$/.test(value)) return parseFloat(value); + + if (value.includes(",")) return value.split(",").map(v => v.trim()); + + return value; +}; + +function parseArgs(): Partial { + const config: Partial = {}; + const args = process.argv.slice(2); + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + if (arg === undefined) continue; + if (!arg.startsWith("--")) continue; + + if (arg.startsWith("--no-")) { + const key = toCamelCase(arg.slice(5)); + config[key] = false; + continue; + } + + if (!arg.includes("=") && (i === args.length - 1 || args[i + 1]?.startsWith("--"))) { + const key = toCamelCase(arg.slice(2)); + config[key] = true; + continue; + } + + let key: string; + let value: string; + + if (arg.includes("=")) { + [key, value] = arg.slice(2).split("=", 2) as [string, string]; + } else { + key = arg.slice(2); + value = args[++i] ?? ""; + } + + key = toCamelCase(key); + + if (key.includes(".")) { + const [parentKey, childKey] = key.split("."); + config[parentKey] = config[parentKey] || {}; + config[parentKey][childKey] = parseValue(value); + } else { + config[key] = parseValue(value); + } + } + + return config; +} + +const formatFileSize = (bytes: number): string => { + const units = ["B", "KB", "MB", "GB"]; + let size = bytes; + let unitIndex = 0; + + while (size >= 1024 && unitIndex < units.length - 1) { + size /= 1024; + unitIndex++; + } + + return `${size.toFixed(2)} ${units[unitIndex]}`; +}; + +console.log("\n🚀 Starting build process...\n"); + +const cliConfig = parseArgs(); +const outdir = cliConfig.outdir || path.join(process.cwd(), "dist"); + +if (existsSync(outdir)) { + console.log(`🗑️ Cleaning previous build at ${outdir}`); + await rm(outdir, { recursive: true, force: true }); +} + +const start = performance.now(); + +const entrypoints = [...new Bun.Glob("**.html").scanSync("src")] + .map(a => path.resolve("src", a)) + .filter(dir => !dir.includes("node_modules")); +console.log(`📄 Found ${entrypoints.length} HTML ${entrypoints.length === 1 ? "file" : "files"} to process\n`); + +const result = await Bun.build({ + entrypoints, + outdir, + plugins: [plugin], + minify: true, + target: "browser", + sourcemap: "linked", + define: { + "process.env.NODE_ENV": JSON.stringify("production"), + }, + ...cliConfig, +}); + +const end = performance.now(); + +const outputTable = result.outputs.map(output => ({ + File: path.relative(process.cwd(), output.path), + Type: output.kind, + Size: formatFileSize(output.size), +})); + +console.table(outputTable); +const buildTime = (end - start).toFixed(2); + +console.log(`\n✅ Build completed in ${buildTime}ms\n`); diff --git a/endpraesi/demo/website/bun-env.d.ts b/endpraesi/demo/website/bun-env.d.ts new file mode 100644 index 00000000..72f1c26e --- /dev/null +++ b/endpraesi/demo/website/bun-env.d.ts @@ -0,0 +1,17 @@ +// Generated by `bun init` + +declare module "*.svg" { + /** + * A path to the SVG file + */ + const path: `${string}.svg`; + export = path; +} + +declare module "*.module.css" { + /** + * A record of class names to their corresponding CSS module classes + */ + const classes: { readonly [key: string]: string }; + export = classes; +} diff --git a/endpraesi/demo/website/bun.lock b/endpraesi/demo/website/bun.lock new file mode 100644 index 00000000..4571f6af --- /dev/null +++ b/endpraesi/demo/website/bun.lock @@ -0,0 +1,97 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "bun-react-template", + "dependencies": { + "bun-plugin-tailwind": "^0.1.2", + "clsx": "^2.1.1", + "framer-motion": "^12.42.2", + "lucide-react": "^1.23.0", + "react": "^19", + "react-dom": "^19", + "tailwind-merge": "^3.6.0", + "tailwindcss": "^4.1.11", + }, + "devDependencies": { + "@types/bun": "latest", + "@types/react": "^19", + "@types/react-dom": "^19", + }, + }, + }, + "packages": { + "@oven/bun-darwin-aarch64": ["@oven/bun-darwin-aarch64@1.3.14", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Omj20SuiHBOUjUBIyqtkNjSUIjOtEOJwmbix/ZyFH4BaQ6OZTaaRWIR4TjHVz0yadHgli6lLTiAh1uarnvD49A=="], + + "@oven/bun-darwin-x64": ["@oven/bun-darwin-x64@1.3.14", "", { "os": "darwin", "cpu": "x64" }, "sha512-FFj3QdU/OhlDyZOJ8CWfN5eWLpRlT4qjZg7lMQi7jA6GuoY5ajlO1zWLP/MuHYRSbXQUvV52RejNi8DVnAp13w=="], + + "@oven/bun-darwin-x64-baseline": ["@oven/bun-darwin-x64-baseline@1.3.14", "", { "os": "darwin", "cpu": "x64" }, "sha512-OSfsTZstc898HHElhU4NccaBGOSSDn5VfahiVTnidZ9B/+wb7WTyfZJaBeJcfjwJ9H2W9uTh2TGtl3UfcXgV9g=="], + + "@oven/bun-freebsd-aarch64": ["@oven/bun-freebsd-aarch64@1.3.14", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-LIKrXaFxAHybVO5Pf+9XP2FHUj/5APvXTUKk9dqHm5iFz4oH+W24cmhjkJirNujh9hKeTyrpWSe3no9JZKowIw=="], + + "@oven/bun-freebsd-x64": ["@oven/bun-freebsd-x64@1.3.14", "", { "os": "freebsd", "cpu": "x64" }, "sha512-uwD+fGUH1ADpIF3B1U2jWzzb20QwRLZfj5QZ28GUCGrAJ/nTmWrD6YYGsblCY1wuhldRez3lU40AyuvSCyLYmw=="], + + "@oven/bun-linux-aarch64": ["@oven/bun-linux-aarch64@1.3.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-X5SsPZHs+iYO8R/efIcRtc7gT2Q2DgPfliCxEkx4cXBumwkw0c/EsHMNwH3EgGpCDaZ7IYVPhpCG/xBOQHEwZw=="], + + "@oven/bun-linux-aarch64-android": ["@oven/bun-linux-aarch64-android@1.3.14", "", { "os": "android", "cpu": "arm64" }, "sha512-y4kq5b85lsrmFb9Xvi4w9mA5IEFJkLMrSmYn06q24KjL9rUWDWO3VFZEtteZxUN5+ec3Zm5S8OnJw1umaCbVjA=="], + + "@oven/bun-linux-aarch64-musl": ["@oven/bun-linux-aarch64-musl@1.3.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-jmqOA92Cd1NL/1XBd4bFkJLxQ86K0RW7ohxS2qzzAvuitO4JiIxjjTeCspoU44zCozH72HpfZfUE2On31OjnWA=="], + + "@oven/bun-linux-x64": ["@oven/bun-linux-x64@1.3.14", "", { "os": "linux", "cpu": "x64" }, "sha512-7OVTAKvwfPmSbIV1HpdOoVVx5VRc427GuPPne93N6vk4eQBPId9nXmZDh9/zGaKPdbVjVtQSZafWQoUjx38Utw=="], + + "@oven/bun-linux-x64-android": ["@oven/bun-linux-x64-android@1.3.14", "", { "os": "android", "cpu": "x64" }, "sha512-qe9e1d+3VAEU7nAA2ol9Jvmy/o99PVMSgZhHn7Q/9O3YcDrfEqyQ8zm4zoe5qTEo8HZH0dN03Le0Ys2eQPs7eg=="], + + "@oven/bun-linux-x64-baseline": ["@oven/bun-linux-x64-baseline@1.3.14", "", { "os": "linux", "cpu": "x64" }, "sha512-q/8EdOC0yUE8FPeoOVq8/Pw5I9/tJaYmUfO/uDUAREx8IUnOJH1RJ5A3BjFqre8pvJoiZA9AovPJq5FnNNjSxA=="], + + "@oven/bun-linux-x64-musl": ["@oven/bun-linux-x64-musl@1.3.14", "", { "os": "linux", "cpu": "x64" }, "sha512-GBCB/k/sIqcr06eTNgg7g46qiUv35Jasx4XiccJ/n7RGqrE4RWUD/XJBbWFprVPjvqd59+QtSnS99XGqvftHfg=="], + + "@oven/bun-linux-x64-musl-baseline": ["@oven/bun-linux-x64-musl-baseline@1.3.14", "", { "os": "linux", "cpu": "x64" }, "sha512-n6iE71G4lQE4XkrZhQQcL5YUlxDbnq6nqV7zeQi33PMsLT/0kYE+RvHOtBWZ3w0wMdXZfINmp63hIb9ijUBGtw=="], + + "@oven/bun-windows-aarch64": ["@oven/bun-windows-aarch64@1.3.14", "", { "os": "win32", "cpu": "arm64" }, "sha512-T7s3x/BsVKQObGU6QDkZeI6wKynzqGbBH1yI77jrrj5siElclxr3DQrDIk8CV4G5/SJq2HHq4kpLyYY2DKCSmA=="], + + "@oven/bun-windows-x64": ["@oven/bun-windows-x64@1.3.14", "", { "os": "win32", "cpu": "x64" }, "sha512-mUFWL3BoYkNpjd8e9PqROiFF/1Xeotq20mABJsiQH62jM1g5zqWh4khw1RZ6bX8Q8fWvlPaxG1PjofkmjUi3vg=="], + + "@oven/bun-windows-x64-baseline": ["@oven/bun-windows-x64-baseline@1.3.14", "", { "os": "win32", "cpu": "x64" }, "sha512-uIjLUC1S9DWgICzuoMba7vurBJnBruE4S5CxnvmZkdqWVXRzx1Rgu636HoH+k0qeaQCFh3jeG3JQ1y6fRHv0sw=="], + + "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], + + "@types/node": ["@types/node@26.1.0", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw=="], + + "@types/react": ["@types/react@19.2.17", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw=="], + + "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + + "bun": ["bun@1.3.14", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.3.14", "@oven/bun-darwin-x64": "1.3.14", "@oven/bun-darwin-x64-baseline": "1.3.14", "@oven/bun-freebsd-aarch64": "1.3.14", "@oven/bun-freebsd-x64": "1.3.14", "@oven/bun-linux-aarch64": "1.3.14", "@oven/bun-linux-aarch64-android": "1.3.14", "@oven/bun-linux-aarch64-musl": "1.3.14", "@oven/bun-linux-x64": "1.3.14", "@oven/bun-linux-x64-android": "1.3.14", "@oven/bun-linux-x64-baseline": "1.3.14", "@oven/bun-linux-x64-musl": "1.3.14", "@oven/bun-linux-x64-musl-baseline": "1.3.14", "@oven/bun-windows-aarch64": "1.3.14", "@oven/bun-windows-x64": "1.3.14", "@oven/bun-windows-x64-baseline": "1.3.14" }, "os": [ "!aix", "!sunos", "!openbsd", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bunx.exe" } }, "sha512-aB6GVd42x1Y5ie1K16SF+oLGtgSkwX9hgoDdIW88pjvfTccU8F1vfpoOt34QLv0dZ1v3XimtaxPlZUG81Gx9Zg=="], + + "bun-plugin-tailwind": ["bun-plugin-tailwind@0.1.2", "", { "peerDependencies": { "bun": ">=1.0.0" } }, "sha512-41jNC1tZRSK3s1o7pTNrLuQG8kL/0vR/JgiTmZAJ1eHwe0w5j6HFPKeqEk0WAD13jfrUC7+ULuewFBBCoADPpg=="], + + "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], + + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "framer-motion": ["framer-motion@12.42.2", "", { "dependencies": { "motion-dom": "^12.42.2", "motion-utils": "^12.39.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-5XY9luDiu0oHfHBjpDthFMh0ES+122w6p/papSJBweMkO8Sn+PW2QaEgRblQBpWFnuvZS5qvarpt/hO2pjGmnw=="], + + "lucide-react": ["lucide-react@1.23.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-38BpJcD0JhFosxHApP/BYsBetLpQFRoTRzEzstM/XCc3jsAG7wqaY1lgVwxiUe3xqYE+lNxo2PkCmYwXWrwwIw=="], + + "motion-dom": ["motion-dom@12.42.2", "", { "dependencies": { "motion-utils": "^12.39.0" } }, "sha512-5gIMWLp/PycBtJRJWRgjxke5n8dlvkSn2DrYW+tr3XcqAZY1xZh6BJyooJXCM8wdfM7wfMjkBJNLge1CKPUIRA=="], + + "motion-utils": ["motion-utils@12.39.0", "", {}, "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ=="], + + "react": ["react@19.2.7", "", {}, "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ=="], + + "react-dom": ["react-dom@19.2.7", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.7" } }, "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ=="], + + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], + + "tailwind-merge": ["tailwind-merge@3.6.0", "", {}, "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w=="], + + "tailwindcss": ["tailwindcss@4.3.2", "", {}, "sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], + } +} diff --git a/endpraesi/demo/website/bunfig.toml b/endpraesi/demo/website/bunfig.toml new file mode 100644 index 00000000..8877354c --- /dev/null +++ b/endpraesi/demo/website/bunfig.toml @@ -0,0 +1,4 @@ + +[serve.static] +plugins = ["bun-plugin-tailwind"] +env = "BUN_PUBLIC_*" \ No newline at end of file diff --git a/endpraesi/demo/website/package.json b/endpraesi/demo/website/package.json new file mode 100644 index 00000000..1e4ecda8 --- /dev/null +++ b/endpraesi/demo/website/package.json @@ -0,0 +1,26 @@ +{ + "name": "bun-react-template", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "bun --hot src/index.ts", + "start": "NODE_ENV=production bun src/index.ts", + "build": "bun run build.ts" + }, + "dependencies": { + "bun-plugin-tailwind": "^0.1.2", + "clsx": "^2.1.1", + "framer-motion": "^12.42.2", + "lucide-react": "^1.23.0", + "react": "^19", + "react-dom": "^19", + "tailwind-merge": "^3.6.0", + "tailwindcss": "^4.1.11" + }, + "devDependencies": { + "@types/react": "^19", + "@types/react-dom": "^19", + "@types/bun": "latest" + } +} diff --git a/endpraesi/demo/website/src/APITester.tsx b/endpraesi/demo/website/src/APITester.tsx new file mode 100644 index 00000000..0f378ae4 --- /dev/null +++ b/endpraesi/demo/website/src/APITester.tsx @@ -0,0 +1,63 @@ +import { useRef, type FormEvent } from "react"; + +export function APITester() { + const responseInputRef = useRef(null); + + const testEndpoint = async (e: FormEvent) => { + e.preventDefault(); + + try { + const form = e.currentTarget; + const formData = new FormData(form); + const endpoint = formData.get("endpoint") as string; + const url = new URL(endpoint, location.href); + const method = formData.get("method") as string; + const res = await fetch(url, { method }); + + const data = await res.json(); + responseInputRef.current!.value = JSON.stringify(data, null, 2); + } catch (error) { + responseInputRef.current!.value = String(error); + } + }; + + return ( +
+
+ + + +
+