From 519b43e9f839ce602f4651c94a54d1ccfff515ef Mon Sep 17 00:00:00 2001 From: Niki Wix Skaarup Date: Sun, 6 Apr 2025 02:08:53 +0200 Subject: [PATCH] init --- .editorconfig | 30 +++ .env.example | 6 + .gitignore | 47 +++++ .npmrc | 3 + .prettierignore | 19 ++ .prettierrc | 8 + Dockerfile | 38 ++++ README.md | 15 ++ bun.lock | 311 +++++++++++++++++++++++++++++++ bunfig.toml | 5 + data/.gitkeep | 0 drizzle.config.ts | 7 + drizzle/0000_neat_marvel_boy.sql | 10 + drizzle/meta/0000_snapshot.json | 81 ++++++++ drizzle/meta/_journal.json | 13 ++ package.json | 36 ++++ src/db/index.ts | 47 +++++ src/db/migrate.ts | 10 + src/db/schema.ts | 25 +++ src/index.css | 23 +++ src/index.d.ts | 9 + src/index.html | 21 +++ src/server.ts | 153 +++++++++++++++ src/static/favicon.png | Bin 0 -> 5348 bytes src/static/manifest.json | 10 + src/static/robots.txt | 4 + src/static/sitemap.txt | 1 + src/wrapped-timer.ts | 78 ++++++++ tsconfig.json | 33 ++++ 29 files changed, 1043 insertions(+) create mode 100644 .editorconfig create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 bun.lock create mode 100644 bunfig.toml create mode 100644 data/.gitkeep create mode 100644 drizzle.config.ts create mode 100644 drizzle/0000_neat_marvel_boy.sql create mode 100644 drizzle/meta/0000_snapshot.json create mode 100644 drizzle/meta/_journal.json create mode 100644 package.json create mode 100644 src/db/index.ts create mode 100644 src/db/migrate.ts create mode 100644 src/db/schema.ts create mode 100644 src/index.css create mode 100644 src/index.d.ts create mode 100644 src/index.html create mode 100644 src/server.ts create mode 100644 src/static/favicon.png create mode 100644 src/static/manifest.json create mode 100644 src/static/robots.txt create mode 100644 src/static/sitemap.txt create mode 100644 src/wrapped-timer.ts create mode 100644 tsconfig.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..fcc0e13 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,30 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +indent_size = 2 +trim_trailing_whitespace = false + +[*.js] +indent_style = tab + +[*.ts] +indent_style = tab + +[*.yml] +indent_style = space +indent_size = 2 + +[*.json] +indent_style = space +indent_size = 2 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..623d0ef --- /dev/null +++ b/.env.example @@ -0,0 +1,6 @@ +SQLITE_DB_PATH="./data" +SQLITE_DB_NAME="db.sqlite3" + +BEARER_TOKEN="REPLACE_ME" + +PORT=3000 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e5cf772 --- /dev/null +++ b/.gitignore @@ -0,0 +1,47 @@ + +.bun +.DS_Store +.env +.env.* +.env.development.local +.env.local +.env.production.local +.env.test.local +!.env.example +.netlify +.output +.pnp.js +.vercel +.vercel +*.log +*.pem +**/*.bun +**/*.log +**/*.tar.gz +**/*.tgz +**/*.trace +**/*.zip +/.next/ +/.pnp +/.svelte-kit +/~ +/build +/coverage +/node_modules +/out/ +/package +node_modules +npm-debug.log* +package-lock.json +yarn-debug.log* +yarn-error.log* + +**/*.sqlite3 +**/*.sqlite3-shm +**/*.sqlite3-wal +**/*.sqlite +**/*.sqlite-shm +**/*.sqlite-wal +**/*.db +**/*.db-shm +**/*.db-wal diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..82eeeed --- /dev/null +++ b/.npmrc @@ -0,0 +1,3 @@ +engine-strict=true +resolution-mode=highest +@jsr:registry=https://npm.jsr.io diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..b8cee66 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,19 @@ +.DS_Store +node_modules +/build +/.svelte-kit +/package +.env +.env.* +!.env.example +vite.config.js.timestamp-* +vite.config.ts.timestamp-* + +# Ignore files for PNPM, NPM and YARN +pnpm-lock.yaml +package-lock.json +yarn.lock + +*.db +*.sqlite +*.sqlite3 diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..93e9d6a --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "useTabs": false, + "singleQuote": true, + "trailingComma": "es5", + "printWidth": 100, + "plugins": [], + "overrides": [] +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..237f052 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,38 @@ +# use the official Bun image +# see all versions at https://hub.docker.com/r/oven/bun/tags +FROM oven/bun:latest AS base +WORKDIR /app + +ENV NODE_ENV=production + +# install dependencies into temp directory +# this will cache them and speed up future builds +FROM base AS install +RUN mkdir -p /temp/dev +COPY package.json bun.lock /temp/dev/ +RUN cd /temp/dev && bun install --frozen-lockfile + +# install with --production (exclude devDependencies) +RUN mkdir -p /temp/prod +COPY package.json bun.lock /temp/prod/ +RUN cd /temp/prod && bun install --frozen-lockfile --production + +# copy node_modules from temp directory +# then copy all (non-ignored) project files into the image +FROM base AS prerelease +COPY --from=install /temp/dev/node_modules node_modules +COPY . . + +# [optional] tests & build +# RUN bun test +# RUN bun run build + +# copy production dependencies and source code into final image +FROM base AS release +WORKDIR /app + +COPY --from=install /temp/prod/node_modules node_modules +COPY --from=prerelease /app/ . + +# run the app +ENTRYPOINT [ "bun", "run", "start" ] diff --git a/README.md b/README.md new file mode 100644 index 0000000..688c87e --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# Elysia with Bun runtime + +## Getting Started +To get started with this template, simply paste this command into your terminal: +```bash +bun create elysia ./elysia-example +``` + +## Development +To start the development server run: +```bash +bun run dev +``` + +Open http://localhost:3000/ with your browser to see the result. \ No newline at end of file diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..4e8f051 --- /dev/null +++ b/bun.lock @@ -0,0 +1,311 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "progress-tracker-api", + "dependencies": { + "bun-plugin-tailwind": "^0.0.15", + "drizzle-kit": "^0.30.6", + "drizzle-orm": "^0.41.0", + "tailwindcss": "^4.1.3", + }, + "devDependencies": { + "@types/bun": "latest", + "oxlint": "latest", + "prettier": "^4.0.0-alpha.12", + "prettier-plugin-tailwindcss": "^0.6.11", + }, + "peerDependencies": { + "typescript": "^5.8.2", + }, + }, + }, + "trustedDependencies": [ + "esbuild", + ], + "packages": { + "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], + + "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="], + + "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.19.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.19.12", "", { "os": "android", "cpu": "arm" }, "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.19.12", "", { "os": "android", "cpu": "arm64" }, "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.19.12", "", { "os": "android", "cpu": "x64" }, "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.19.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.19.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.19.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.19.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.19.12", "", { "os": "linux", "cpu": "arm" }, "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.19.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.19.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.19.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.19.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.19.12", "", { "os": "linux", "cpu": "x64" }, "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.19.12", "", { "os": "none", "cpu": "x64" }, "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.19.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.19.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.19.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.19.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.19.12", "", { "os": "win32", "cpu": "x64" }, "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA=="], + + "@oxlint/darwin-arm64": ["@oxlint/darwin-arm64@0.16.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-lxwzCWM8zkSsv+JMEAvhHoS5X+5ICq/Vupb4RAalozX2V39Y/JXNol6RBxIGD7EEmitxWBghMIMdmqW9Y0wChA=="], + + "@oxlint/darwin-x64": ["@oxlint/darwin-x64@0.16.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-wRUxqisY7X1N88xoAFRcR3K7bPw6xqtNhaxVOaRhu5F3NIt85D80WBayoPl7TZKWTACUX55SGvAJBnOYmE7+zg=="], + + "@oxlint/linux-arm64-gnu": ["@oxlint/linux-arm64-gnu@0.16.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-veM/ve9kqTU7Qei3qjvGzXPZLT8L1vQ2nE8U78DbjrmP9i5MC1QB5ynBB9oQDJ1yce9ExmjTpIor2SF3phfXXQ=="], + + "@oxlint/linux-arm64-musl": ["@oxlint/linux-arm64-musl@0.16.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-QojvKDSA43gbt3pjua+zgX0jfpyWLU6xXEBgA/Ww7HPR7Ec1pbs8lhL1Or7BQl8jywkMdzWiicPtd65+DBw/IQ=="], + + "@oxlint/linux-x64-gnu": ["@oxlint/linux-x64-gnu@0.16.4", "", { "os": "linux", "cpu": "x64" }, "sha512-rVZhsnzQzfhQSEVc+aGoLlWdzhyuXrKxrwv0h1R9rJSB7vzBx2+D2k9LQSW1WkuhUd3Bn4CIDPakT8XUKYxvXQ=="], + + "@oxlint/linux-x64-musl": ["@oxlint/linux-x64-musl@0.16.4", "", { "os": "linux", "cpu": "x64" }, "sha512-ZLc+jh8+HF5ftnvVfWA3jl1/Rq45naakC96MlmRIxHhDNsClo41ssSeANJ15shk1MbCOdEnJh7foHPvU6QmGpQ=="], + + "@oxlint/win32-arm64": ["@oxlint/win32-arm64@0.16.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-/KvI2fLK5V9cPuR7rfomchRJmHBJDH7PFZINNm5o3ivmjstUVsIT2HoArjitwoVdionKKJ0Ac8Vc0xqS/Vvbcg=="], + + "@oxlint/win32-x64": ["@oxlint/win32-x64@0.16.4", "", { "os": "win32", "cpu": "x64" }, "sha512-KS2ztiU039dooBi82i24QL6aa/4PROwg9fEcZc4o3N2BueO01a0s4nKlz/AJL+1Twa6LZJgGsQ87EcAzlj3DUA=="], + + "@petamoriken/float16": ["@petamoriken/float16@3.9.2", "", {}, "sha512-VgffxawQde93xKxT3qap3OH+meZf7VaSB5Sqd4Rqc+FP5alWbpOyan/7tRbOAvynjpG3GpdtAuGU/NdhQpmrog=="], + + "@prettier/cli": ["@prettier/cli@0.7.1", "", { "dependencies": { "atomically": "^2.0.3", "fast-ignore": "^1.1.3", "find-up-json": "^2.0.4", "function-once": "^3.0.0", "import-meta-resolve": "^4.1.0", "is-binary-path": "^2.1.0", "js-yaml": "^4.1.0", "json-sorted-stringify": "^1.0.0", "json5": "^2.2.3", "kasi": "^1.1.0", "lomemo": "^1.0.0", "pioppo": "^1.2.0", "promise-resolve-timeout": "^2.0.0", "smol-toml": "^1.3.1", "specialist": "^1.4.5", "tiny-editorconfig": "^1.0.0", "tiny-jsonc": "^1.0.1", "tiny-readdir": "^2.7.4", "tiny-readdir-glob": "^1.23.1", "tiny-spinner": "^2.0.4", "worktank": "^2.7.3", "zeptomatch": "^2.0.0", "zeptomatch-escape": "^1.0.0", "zeptomatch-is-static": "^1.0.0" }, "peerDependencies": { "prettier": "^3.1.0 || ^4.0.0-alpha" }, "bin": { "prettier-next": "dist/bin.js" } }, "sha512-YoXPLOLmEEHP4MKgzcEilzaUtlo80Qm5Pb+59QbgDeOsIExGBkRqZmC2+iwzSwEhlhTEpGqDwqb3+nP/dPay9A=="], + + "@types/bun": ["@types/bun@1.2.8", "", { "dependencies": { "bun-types": "1.2.7" } }, "sha512-t8L1RvJVUghW5V+M/fL3Thbxcs0HwNsXsnTEBEfEVqGteiJToOlZ/fyOEaR1kZsNqnu+3XA4RI/qmnX4w6+S+w=="], + + "@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="], + + "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="], + + "ansi-purge": ["ansi-purge@1.0.1", "", {}, "sha512-5NNMT7rljQ24DKHnIYG1qFXs8eUv5mZcT6kOPf5NopQUzpURBh/T4tbQw3TX//q3Zpw3JwVvsVHHsRKJesQHZQ=="], + + "ansi-truncate": ["ansi-truncate@1.2.0", "", { "dependencies": { "fast-string-truncated-width": "^1.2.0" } }, "sha512-/SLVrxNIP8o8iRHjdK3K9s2hDqdvb86NEjZOAB6ecWFsOo+9obaby97prnvAPn6j7ExXCpbvtlJFYPkkspg4BQ=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "atomically": ["atomically@2.0.3", "", { "dependencies": { "stubborn-fs": "^1.2.5", "when-exit": "^2.1.1" } }, "sha512-kU6FmrwZ3Lx7/7y3hPS5QnbJfaohcIul5fGqf7ok+4KklIEk9tJ0C2IQPdacSbVUWv6zVHXEBWoWd6NrVMT7Cw=="], + + "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], + + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + + "bun-plugin-tailwind": ["bun-plugin-tailwind@0.0.15", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-qtAXMNGG4R0UGGI8zWrqm2B7BdXqx48vunJXBPzfDOHPA5WkRUZdTSbE7TFwO4jLhYqSE23YMWsM9NhE6ovobw=="], + + "bun-types": ["bun-types@1.2.7", "", { "dependencies": { "@types/node": "*", "@types/ws": "*" } }, "sha512-P4hHhk7kjF99acXqKvltyuMQ2kf/rzIw3ylEDpCxDS9Xa0X0Yp/gJu/vDCucmWpiur5qJ0lwB2bWzOXa2GlHqA=="], + + "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + + "dettle": ["dettle@1.0.5", "", {}, "sha512-ZVyjhAJ7sCe1PNXEGveObOH9AC8QvMga3HJIghHawtG7mE4K5pW9nz/vDGAr/U7a3LWgdOzEE7ac9MURnyfaTA=="], + + "drizzle-kit": ["drizzle-kit@0.30.6", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.19.7", "esbuild-register": "^3.5.0", "gel": "^2.0.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-U4wWit0fyZuGuP7iNmRleQyK2V8wCuv57vf5l3MnG4z4fzNTjY/U13M8owyQ5RavqvqxBifWORaR3wIUzlN64g=="], + + "drizzle-orm": ["drizzle-orm@0.41.0", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-7A4ZxhHk9gdlXmTdPj/lREtP+3u8KvZ4yEN6MYVxBzZGex5Wtdc+CWSbu7btgF6TB0N+MNPrvW7RKBbxJchs/Q=="], + + "env-paths": ["env-paths@3.0.0", "", {}, "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="], + + "esbuild": ["esbuild@0.19.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.19.12", "@esbuild/android-arm": "0.19.12", "@esbuild/android-arm64": "0.19.12", "@esbuild/android-x64": "0.19.12", "@esbuild/darwin-arm64": "0.19.12", "@esbuild/darwin-x64": "0.19.12", "@esbuild/freebsd-arm64": "0.19.12", "@esbuild/freebsd-x64": "0.19.12", "@esbuild/linux-arm": "0.19.12", "@esbuild/linux-arm64": "0.19.12", "@esbuild/linux-ia32": "0.19.12", "@esbuild/linux-loong64": "0.19.12", "@esbuild/linux-mips64el": "0.19.12", "@esbuild/linux-ppc64": "0.19.12", "@esbuild/linux-riscv64": "0.19.12", "@esbuild/linux-s390x": "0.19.12", "@esbuild/linux-x64": "0.19.12", "@esbuild/netbsd-x64": "0.19.12", "@esbuild/openbsd-x64": "0.19.12", "@esbuild/sunos-x64": "0.19.12", "@esbuild/win32-arm64": "0.19.12", "@esbuild/win32-ia32": "0.19.12", "@esbuild/win32-x64": "0.19.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg=="], + + "esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="], + + "fast-ignore": ["fast-ignore@1.1.3", "", { "dependencies": { "grammex": "^3.1.2", "string-escape-regex": "^1.0.0" } }, "sha512-xTo4UbrOKfEQgOFlPaqFScodTV/Wf3KATEqCZZSMh6OP4bcez0lTsqww3n3/Fve1q9u0jmfDP0q0nOhH4POZEg=="], + + "fast-string-truncated-width": ["fast-string-truncated-width@1.2.1", "", {}, "sha512-Q9acT/+Uu3GwGj+5w/zsGuQjh9O1TyywhIwAxHudtWrgF09nHOPrvTLhQevPbttcxjr/SNN7mJmfOw/B1bXgow=="], + + "fast-string-width": ["fast-string-width@1.1.0", "", { "dependencies": { "fast-string-truncated-width": "^1.2.0" } }, "sha512-O3fwIVIH5gKB38QNbdg+3760ZmGz0SZMgvwJbA1b2TGXceKE6A2cOlfogh1iw8lr049zPyd7YADHy+B7U4W9bQ=="], + + "find-up-json": ["find-up-json@2.0.5", "", { "dependencies": { "find-up-path": "^1.0.1" } }, "sha512-1zZZUfD1GOOEEd1AqwbRmCkCCv1O9t0vOpCYgmzfJqKty8WKaKlDyxWej8Aew+vI5lvDiTviaQuaVuu6GzlHzQ=="], + + "find-up-path": ["find-up-path@1.0.1", "", {}, "sha512-cl4Sfxufq9WK848L887b4r+NVZoBjMeB4QydPZ+pXbp6Jt2nUVspTo2svNOm48stIIeSxtuCsULa9+e+LMTzwA=="], + + "function-once": ["function-once@3.0.1", "", {}, "sha512-bE3E8REk4jANDot3l0sLFkXgywBwzFKsmbwdnVHLJUnt/3kV6dNG0oJJqoRBuS1Z9Lr4ZoQgwV0ZNLDgWDbv7Q=="], + + "gel": ["gel@2.0.1", "", { "dependencies": { "@petamoriken/float16": "^3.8.7", "debug": "^4.3.4", "env-paths": "^3.0.0", "semver": "^7.6.2", "shell-quote": "^1.8.1", "which": "^4.0.0" }, "bin": { "gel": "dist/cli.mjs" } }, "sha512-gfem3IGvqKqXwEq7XseBogyaRwGsQGuE7Cw/yQsjLGdgiyqX92G1xENPCE0ltunPGcsJIa6XBOTx/PK169mOqw=="], + + "get-current-package": ["get-current-package@1.0.1", "", { "dependencies": { "find-up-json": "^2.0.5" } }, "sha512-c/Rw5ByDQ+zg+Lh/emBWv0bDpugEFdmXPR6/srIemVtIvol0XbT0JAr8Db0cX+Jj/xY9wj1wdjeq2qNB35Tayg=="], + + "get-tsconfig": ["get-tsconfig@4.10.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A=="], + + "grammex": ["grammex@3.1.10", "", {}, "sha512-UCfMsV/sfqk4TN1+m5ehSOXuADyLUgSuwMI2vCVlbN/REoSmTl4eagswC9DzzVxtsKv7Yp2CmIJNn4fMk8PaQA=="], + + "import-meta-resolve": ["import-meta-resolve@4.1.0", "", {}, "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw=="], + + "ini-simple-parser": ["ini-simple-parser@1.0.1", "", {}, "sha512-myU5nhF2miBQP3tO/giUi+8BI9QhfM/XRZd0RD7G0p+40K6KPAwxMDtH3UEtJ2XJZbd+ZiQOoGh432DTYfzNVQ=="], + + "ionstore": ["ionstore@1.0.1", "", {}, "sha512-g+99vyka3EiNFJCnbq3NxegjV211RzGtkDUMbZGB01Con8ZqUmMx/FpWMeqgDXOqgM7QoVeDhe+CfYCWznaDVA=="], + + "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="], + + "isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="], + + "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], + + "json-sorted-stringify": ["json-sorted-stringify@1.0.1", "", {}, "sha512-pWv9hqWho37EpwpBgqDYVPKPCgT/ytuvqtlBvb6M44BrnvooTk/5D/aSeohsGDLp+g8waP5dUUGODR+Ley+Idg=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "kasi": ["kasi@1.1.1", "", {}, "sha512-pzBwGWFIjf84T/8aD0XzMli1T3Ckr/jVLh6v0Jskwiv5ehmcgDM+vpYFSk8WzGn4ed4HqgaifTgQUHzzZHa+Qw=="], + + "lomemo": ["lomemo@1.0.1", "", {}, "sha512-g8CnVp7UYypeQKpXpMzyrJoDzhOoqVQYSJApoq/cFI3vGxXoHQ+6lH5cApW9XwzVy5SL9/Owil7/JxbKckw0Lg=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "oxlint": ["oxlint@0.16.4", "", { "optionalDependencies": { "@oxlint/darwin-arm64": "0.16.4", "@oxlint/darwin-x64": "0.16.4", "@oxlint/linux-arm64-gnu": "0.16.4", "@oxlint/linux-arm64-musl": "0.16.4", "@oxlint/linux-x64-gnu": "0.16.4", "@oxlint/linux-x64-musl": "0.16.4", "@oxlint/win32-arm64": "0.16.4", "@oxlint/win32-x64": "0.16.4" }, "bin": { "oxlint": "bin/oxlint", "oxc_language_server": "bin/oxc_language_server" } }, "sha512-zi/a8i2R3OJcAUx1KHGy4B8UWWCN3YLDYzlYSNX9uknfX0PJSzv632Xrw9V0Ty5Y1biD6ZuLyO+5Yy2yeG2ySA=="], + + "pioppo": ["pioppo@1.2.1", "", { "dependencies": { "dettle": "^1.0.5", "when-exit": "^2.1.4" } }, "sha512-1oErGVWD6wFDPmrJWEY1Cj2p829UGT6Fw9OItYFxLkWtBjCvQSMC8wA5IcAR5ms/6gqiY8pnJvIV/+/Imyobew=="], + + "prettier": ["prettier@4.0.0-alpha.12", "", { "dependencies": { "@prettier/cli": "^0.7.1" }, "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-wQ8RK48Io6nRr39OQFXZu+EALwTygXnstPgN9UplY+mqkg6P52ceGifo5gylIwX1X9lOuXxreUFrLxXsCbA+sg=="], + + "prettier-plugin-tailwindcss": ["prettier-plugin-tailwindcss@0.6.11", "", { "peerDependencies": { "@ianvs/prettier-plugin-sort-imports": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", "@trivago/prettier-plugin-sort-imports": "*", "@zackad/prettier-plugin-twig": "*", "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", "prettier-plugin-import-sort": "*", "prettier-plugin-jsdoc": "*", "prettier-plugin-marko": "*", "prettier-plugin-multiline-arrays": "*", "prettier-plugin-organize-attributes": "*", "prettier-plugin-organize-imports": "*", "prettier-plugin-sort-imports": "*", "prettier-plugin-style-order": "*", "prettier-plugin-svelte": "*" }, "optionalPeers": ["@ianvs/prettier-plugin-sort-imports", "@prettier/plugin-pug", "@shopify/prettier-plugin-liquid", "@trivago/prettier-plugin-sort-imports", "@zackad/prettier-plugin-twig", "prettier-plugin-astro", "prettier-plugin-css-order", "prettier-plugin-import-sort", "prettier-plugin-jsdoc", "prettier-plugin-marko", "prettier-plugin-multiline-arrays", "prettier-plugin-organize-attributes", "prettier-plugin-organize-imports", "prettier-plugin-sort-imports", "prettier-plugin-style-order", "prettier-plugin-svelte"] }, "sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA=="], + + "promise-make-counter": ["promise-make-counter@1.0.2", "", { "dependencies": { "promise-make-naked": "^3.0.2" } }, "sha512-FJAxTBWQuQoAs4ZOYuKX1FHXxEgKLEzBxUvwr4RoOglkTpOjWuM+RXsK3M9q5lMa8kjqctUrhwYeZFT4ygsnag=="], + + "promise-make-naked": ["promise-make-naked@2.1.2", "", {}, "sha512-y7s8ZuHIG56JYspB24be9GFkXA1zXL85Ur9u1DKrW/tvyUoPxWgBjnalK6Nc6l7wHBcAW0c3PO07+XOsWTRuhg=="], + + "promise-resolve-timeout": ["promise-resolve-timeout@2.0.1", "", {}, "sha512-90Qzzu5SmR+ksmTPsc79121NZGtEiPvKACQLCl6yofknRx5xJI9kNj3oDVSX6dVTneF8Ju6+xpVFdDSzb7cNcg=="], + + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + + "semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], + + "shell-quote": ["shell-quote@1.8.2", "", {}, "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA=="], + + "smol-toml": ["smol-toml@1.3.1", "", {}, "sha512-tEYNll18pPKHroYSmLLrksq233j021G0giwW7P3D24jC54pQ5W5BXMsQ/Mvw1OJCmEYDgY+lrzT+3nNUtoNfXQ=="], + + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + + "specialist": ["specialist@1.4.5", "", { "dependencies": { "tiny-bin": "^1.10.3", "tiny-colors": "^2.2.2", "tiny-parse-argv": "^2.8.1", "tiny-updater": "^3.5.3" } }, "sha512-4mPQEREzBUW2hzlXX/dWFbQdUWzpkqvMFVpUAdRlo1lUlhKMObDHiAo09oZ94x4cS3uWMJebPOTn+GaQYLfv3Q=="], + + "stdin-blocker": ["stdin-blocker@2.0.1", "", {}, "sha512-NEcAEpag+gE/Iivx1prq1AFPwnmgmcyHNvGZLUqGBoOE/7DZtmhtP9iYqJt8ymueFL+kknhfEebAMWbrWp3FJw=="], + + "string-escape-regex": ["string-escape-regex@1.0.1", "", {}, "sha512-cdSXOHSJ32K/T2dbj9t7rJwonujaOkaINpa1zsXT+PNFIv1zuPjtr0tXanCvUhN2bIu2IB0z/C7ksl+Qsy44nA=="], + + "stubborn-fs": ["stubborn-fs@1.2.5", "", {}, "sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g=="], + + "tailwindcss": ["tailwindcss@4.1.3", "", {}, "sha512-2Q+rw9vy1WFXu5cIxlvsabCwhU2qUwodGq03ODhLJ0jW4ek5BUtoCsnLB0qG+m8AHgEsSJcJGDSDe06FXlP74g=="], + + "tiny-bin": ["tiny-bin@1.11.1", "", { "dependencies": { "ansi-purge": "^1.0.1", "fast-string-width": "^1.1.0", "get-current-package": "^1.0.1", "tiny-colors": "^2.2.2", "tiny-levenshtein": "^1.0.1", "tiny-parse-argv": "^2.8.2", "tiny-updater": "^3.5.3" } }, "sha512-UFC5EwtmCkFshKOBgXZzNFJsHpZrtbWZ/jQj+pwoIGUUbmenlQGGVDOwVqVOuG1nTxICSd+GLp3b+j7dUKZr2Q=="], + + "tiny-colors": ["tiny-colors@2.2.2", "", {}, "sha512-Elmv7JL+dX0c78caKEelH1nHHBskHzJkaqBRgVvQuxsvVA/Z9Fa2R3ZZtfmkkajcd18e96RLMwJvtFqC8jsZWA=="], + + "tiny-cursor": ["tiny-cursor@2.0.1", "", { "dependencies": { "when-exit": "^2.1.4" } }, "sha512-28ytGEfb7m/8Gdflv+wSo5qRM01fROo2CjJVYon6yYbzPsc3ap3Ps5CZXuS19pIROwswSvZMGbEQ7kWnokdUGA=="], + + "tiny-editorconfig": ["tiny-editorconfig@1.0.0", "", { "dependencies": { "ini-simple-parser": "^1.0.0", "zeptomatch": "^1.1.3" } }, "sha512-rxpWaSurnvPUkL2/qydRH10llK7MD1XfE38zoWTsp/ZWWYnnwPBzGUePBOcXFaNA3cJQm8ItqrofGeRJ6AVaew=="], + + "tiny-jsonc": ["tiny-jsonc@1.0.2", "", {}, "sha512-f5QDAfLq6zIVSyCZQZhhyl0QS6MvAyTxgz4X4x3+EoCktNWEYJ6PeoEA97fyb98njpBNNi88ybpD7m+BDFXaCw=="], + + "tiny-levenshtein": ["tiny-levenshtein@1.0.1", "", {}, "sha512-Q4rRa0pxGIbYoXQDejEDnonHt+QUTFrejaAxdv7h352/PWQBJ2eKsbzw1khvbIXKrpG1n2ZABX0A34oBGZXB2w=="], + + "tiny-parse-argv": ["tiny-parse-argv@2.8.2", "", {}, "sha512-RnIDHQ+r9zMuslQWVoRxfKVOumteeheQqbwNYJyQxzM2vzx/vdN5xAeL64F3rQOpfbVdxFkhM4zPDyfq7SxsBQ=="], + + "tiny-readdir": ["tiny-readdir@2.7.4", "", { "dependencies": { "promise-make-counter": "^1.0.2" } }, "sha512-721U+zsYwDirjr8IM6jqpesD/McpZooeFi3Zc6mcjy1pse2C+v19eHPFRqz4chGXZFw7C3KITDjAtHETc2wj7Q=="], + + "tiny-readdir-glob": ["tiny-readdir-glob@1.23.1", "", { "dependencies": { "tiny-readdir": "^2.7.0", "zeptomatch": "^1.2.2", "zeptomatch-explode": "^1.0.0", "zeptomatch-is-static": "^1.0.0", "zeptomatch-unescape": "^1.0.0" } }, "sha512-UJB7alIB+lSmIfVGKLxNyErlo5mto1luldvXW+s+NSO5gyleU/Ut9P4QKghPEenCHqVZhW0j6OggDkmattVhKw=="], + + "tiny-spinner": ["tiny-spinner@2.0.5", "", { "dependencies": { "stdin-blocker": "^2.0.1", "tiny-colors": "^2.2.2", "tiny-cursor": "^2.0.1", "tiny-truncate": "^1.0.3" } }, "sha512-OIGogtfEbA2IQdCBgF0zI3EjpFyiUEd6Uj5j0q5jhIPPq8pgNR83D0t9WIckbD2FzPann8lH/uLf1vX0YIu04w=="], + + "tiny-truncate": ["tiny-truncate@1.0.3", "", { "dependencies": { "ansi-truncate": "^1.2.0" } }, "sha512-ZdCMtUg6N5VgYAInid90lnA4R720w5iU7raqPspAoYxOSMyzp132b8DeKZGrO2yC3tvoJMUDaymY3XFN3Zr5sQ=="], + + "tiny-updater": ["tiny-updater@3.5.3", "", { "dependencies": { "ionstore": "^1.0.1", "tiny-colors": "^2.2.2", "when-exit": "^2.1.4" } }, "sha512-wEUssfOOkVLg2raSaRbyZDHpVCDj6fnp7UjynpNE4XGuF+Gkj8GRRMoHdfk73VzLQs/AHKsbY8fCxXNz8Hx4Qg=="], + + "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + + "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], + + "webworker-shim": ["webworker-shim@1.1.1", "", {}, "sha512-XCWuBjJH3Xn/7SbyUF1WrrCbe6ZEsgaD7kxlFhxIwdkljGYX3BqP/dhG6ge0NBT+V7ZPjR4/BXq5BvbdaxrpKg=="], + + "when-exit": ["when-exit@2.1.4", "", {}, "sha512-4rnvd3A1t16PWzrBUcSDZqcAmsUIy4minDXT/CZ8F2mVDgd65i4Aalimgz1aQkRGU0iH5eT5+6Rx2TK8o443Pg=="], + + "which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="], + + "worktank": ["worktank@2.7.3", "", { "dependencies": { "promise-make-naked": "^2.0.0", "webworker-shim": "^1.1.0" } }, "sha512-M0fesnpttBPdvNYBdzRvLDsacN0na9RYWFxwmM/x1+/6mufjduv9/9vBObK8EXDqxRMX/SOYJabpo0UCYYBUdQ=="], + + "zeptomatch": ["zeptomatch@2.0.1", "", { "dependencies": { "grammex": "^3.1.10" } }, "sha512-nbnIYF2n3o3EqV36HkIhEMLIDFbG3M6RUjhkdKIn6qqIJkdkL7bgVSfTTCEXBJpk1T45tLfEYfStndJc2lUEnA=="], + + "zeptomatch-escape": ["zeptomatch-escape@1.0.1", "", {}, "sha512-kAc5HzvnF66djCYDqpsS46Y/FKi+4pe/KJRmTmm/hwmoaNYzmm6bBY07cdkxmJCdY018S5UeQn4yP+9X2x1MbQ=="], + + "zeptomatch-explode": ["zeptomatch-explode@1.0.1", "", {}, "sha512-7cUQASLLRGZ20+zEQcEgQ9z/gH1+jSfrNg4KfRJSxF1QU2fpymAwWvnAxl69GD5pr3IV0V9vo3ke2np//Nh4tQ=="], + + "zeptomatch-is-static": ["zeptomatch-is-static@1.0.1", "", {}, "sha512-bN9q7H/UdXhkub01WE7b7Grg07jLldNnIWG2T1IpBq5NtvcQ4DwFbNiGGapnbKHUdWiCNjg/bIvixV88nj9gog=="], + + "zeptomatch-unescape": ["zeptomatch-unescape@1.0.1", "", {}, "sha512-xhSFkKV0aQ03e/eiN4VhOTwJhcqfH7SMiGHrWKw9gXi+0EVJAxJ8Gt4ehozYsYLhUXL1JFbP1g3EE6ZmkStB0g=="], + + "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], + + "promise-make-counter/promise-make-naked": ["promise-make-naked@3.0.2", "", {}, "sha512-B+b+kQ1YrYS7zO7P7bQcoqqMUizP06BOyNSBEnB5VJKDSWo8fsVuDkfSmwdjF0JsRtaNh83so5MMFJ95soH5jg=="], + + "tiny-editorconfig/zeptomatch": ["zeptomatch@1.2.2", "", { "dependencies": { "grammex": "^3.1.1" } }, "sha512-0ETdzEO0hdYmT8aXHHf5aMjpX+FHFE61sG4qKFAoJD2Umt3TWdCmH7ADxn2oUiWTlqBGC+SGr8sYMfr+37J8pQ=="], + + "tiny-readdir-glob/zeptomatch": ["zeptomatch@1.2.2", "", { "dependencies": { "grammex": "^3.1.1" } }, "sha512-0ETdzEO0hdYmT8aXHHf5aMjpX+FHFE61sG4qKFAoJD2Umt3TWdCmH7ADxn2oUiWTlqBGC+SGr8sYMfr+37J8pQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], + } +} diff --git a/bunfig.toml b/bunfig.toml new file mode 100644 index 0000000..331faab --- /dev/null +++ b/bunfig.toml @@ -0,0 +1,5 @@ +[install.scopes] +"@jsr" = "https://npm.jsr.io" + +[serve.static] +plugins = ["bun-plugin-tailwind"] diff --git a/data/.gitkeep b/data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/drizzle.config.ts b/drizzle.config.ts new file mode 100644 index 0000000..a81f0f7 --- /dev/null +++ b/drizzle.config.ts @@ -0,0 +1,7 @@ +import type { Config } from "drizzle-kit"; + +export default { + schema: 'src/db/schema.ts', + out: './drizzle', + dialect: 'sqlite', +} satisfies Config; diff --git a/drizzle/0000_neat_marvel_boy.sql b/drizzle/0000_neat_marvel_boy.sql new file mode 100644 index 0000000..b87c2f2 --- /dev/null +++ b/drizzle/0000_neat_marvel_boy.sql @@ -0,0 +1,10 @@ +CREATE TABLE `entry` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `name` text NOT NULL, + `href` text NOT NULL, + `finished` integer DEFAULT false NOT NULL, + `created_at` integer DEFAULT (unixepoch('subsec') NOT NULL, + `updated_at` integer DEFAULT (unixepoch('subsec') NOT NULL +); +--> statement-breakpoint +CREATE UNIQUE INDEX `entry_name_unique` ON `entry` (`name`); \ No newline at end of file diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json new file mode 100644 index 0000000..bead0bc --- /dev/null +++ b/drizzle/meta/0000_snapshot.json @@ -0,0 +1,81 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "5b7c56dc-4c5d-444a-9c66-9da5c7e06977", + "prevId": "00000000-0000-0000-0000-000000000000", + "tables": { + "entry": { + "name": "entry", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "href": { + "name": "href", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "finished": { + "name": "finished", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch('subsec')" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch('subsec')" + } + }, + "indexes": { + "entry_name_unique": { + "name": "entry_name_unique", + "columns": [ + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json new file mode 100644 index 0000000..07bebd2 --- /dev/null +++ b/drizzle/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "sqlite", + "entries": [ + { + "idx": 0, + "version": "6", + "when": 1743898098259, + "tag": "0000_neat_marvel_boy", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..06587c8 --- /dev/null +++ b/package.json @@ -0,0 +1,36 @@ +{ + "name": "progress-tracker-api", + "version": "1.0.50", + "private": true, + "type": "module", + "module": "src/server.ts", + "scripts": { + "migrate": "bun --bun run ./src/db/migrate.ts", + "generate": "bunx --bun drizzle-kit generate --config=./drizzle.config.ts", + "studio": "bunx --bun drizzle-kit studio --config=./drizzle.config.ts", + "up": "drizzle-kit up --config=./drizzle.config.ts", + "start": "bun run src/server.ts", + "dev": "bun run --watch src/server.ts", + "lint": "oxlint .", + "format": "prettier --write ." + }, + "dependencies": { + "bun-plugin-tailwind": "^0.0.15", + "drizzle-kit": "^0.30.6", + "drizzle-orm": "^0.41.0", + "tailwindcss": "^4.1.3" + }, + "devDependencies": { + "@types/bun": "latest", + "oxlint": "latest", + "prettier": "^4.0.0-alpha.12", + "prettier-plugin-tailwindcss": "^0.6.11" + }, + "peerDependencies": { + "typescript": "^5.8.2" + }, + "trustedDependencies": [ + "esbuild", + "es5-ext" + ] +} diff --git a/src/db/index.ts b/src/db/index.ts new file mode 100644 index 0000000..5740a53 --- /dev/null +++ b/src/db/index.ts @@ -0,0 +1,47 @@ +import { Database } from 'bun:sqlite'; +import { env } from 'bun'; +import { drizzle } from 'drizzle-orm/bun-sqlite'; +import { migrate } from 'drizzle-orm/bun-sqlite/migrator'; +import { createWrappedTimer } from '../wrapped-timer'; + +function initDb() { + global.db.exec('PRAGMA journal_mode = delete'); + global.db.exec('PRAGMA journal_mode = WAL'); + global.db.exec('PRAGMA synchronous = NORMAL'); + global.db.exec('PRAGMA auto_vacuum = INCREMENTAL'); + global.db.exec('PRAGMA wal_autocheckpoint = 1000'); +} + +function incrementalVacuumDb() { + global.db.exec('PRAGMA incremental_vacuum'); +} + +function optimizeDb() { + global.db.exec('PRAGMA optimize'); +} + +function vacuumDb() { + global.db.exec('vacuum'); +} + +if (global.db === undefined || global.drizzleDB === undefined) { + global.db = new Database(`${env.SQLITE_DB_PATH}/${env.SQLITE_DB_NAME}`, { create: true }); + initDb(); + global.drizzleDB = drizzle(global.db); + migrate(global.drizzleDB, { migrationsFolder: './drizzle' }); +} + +const incrementalVacuumInterval = 1000 * 30; // 30 seconds +const incrementalVacuumRunnable = createWrappedTimer('databaseIncrementalVacuum', incrementalVacuumDb, incrementalVacuumInterval); + +setTimeout(incrementalVacuumRunnable.callback, 0); +const optimizeInterval = 1000 * 60 * 30; // 30 minutes +const optimizeRunnable = createWrappedTimer('databaseOptimize', optimizeDb, optimizeInterval); +setTimeout(optimizeRunnable.callback, 0); + +const vacuumInterval = 1000 * 60 * 60 * 24; // 24 hours +const vacuumRunnable = createWrappedTimer('databaseVacuum', vacuumDb, vacuumInterval); +setTimeout(vacuumRunnable.callback, 0); + +export const db = global.db; +export const drizzleDB = global.drizzleDB; diff --git a/src/db/migrate.ts b/src/db/migrate.ts new file mode 100644 index 0000000..295a1ee --- /dev/null +++ b/src/db/migrate.ts @@ -0,0 +1,10 @@ +import { Database } from 'bun:sqlite'; +import { env } from 'bun'; +import { drizzle } from 'drizzle-orm/bun-sqlite'; +import { migrate } from 'drizzle-orm/bun-sqlite/migrator'; + +const sqlite = new Database(`${env.SQLITE_DB_PATH}/${env.SQLITE_DB_NAME}`, { create: true }); + +const db = drizzle(sqlite); + +migrate(db, { migrationsFolder: './drizzle' }); diff --git a/src/db/schema.ts b/src/db/schema.ts new file mode 100644 index 0000000..d3d4ca0 --- /dev/null +++ b/src/db/schema.ts @@ -0,0 +1,25 @@ +import { sql, type InferSelectModel } from 'drizzle-orm'; +import { index, integer, primaryKey, sqliteTable, text } from 'drizzle-orm/sqlite-core'; + +const createdAt = integer('created_at', { mode: 'timestamp_ms' }) + .notNull() + .default(sql`(unixepoch('subsec')`) + .$defaultFn(() => new Date()); +const updatedAt = integer('updated_at', { mode: 'timestamp_ms' }) + .notNull() + .default(sql`(unixepoch('subsec')`) + .$onUpdateFn(() => new Date()); + +export const entry = sqliteTable( + 'entry', + { + id: integer('id', { mode: 'number' }).primaryKey({ autoIncrement: true }), + name: text('name').notNull().unique(), + href: text('href').notNull(), + finished: integer('finished', { mode: 'boolean' }).notNull().default(false), + createdAt, + updatedAt, + } +); + +type SelectEntry = InferSelectModel; diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..cbc6c3e --- /dev/null +++ b/src/index.css @@ -0,0 +1,23 @@ +@import 'tailwindcss' source('.'); + +@theme { + --breakout-size: calc((var(--breakpoint-xl) - var(--breakpoint-lg)) / 2); + --ultrawide-val: minmax(calc(var(--spacing) * 4), 1fr); + --breakout-val: minmax(0, var(--breakout-size)); + --content-val: min(100% - calc(var(--spacing) * 8), var(--breakpoint-lg)); +} + +@layer base { + * { + min-width: 0; + } + + .content { + grid-template-columns: + [ultrawide-start] var(--ultrawide-val) [breakout-start] var(--breakout-val) [content-start] var(--content-val) [content-end] var(--breakout-val) [breakout-end] var(--ultrawide-val) [ultrawide-end]; + } + + .content>* { + grid-column: content; + } +} diff --git a/src/index.d.ts b/src/index.d.ts new file mode 100644 index 0000000..8cff349 --- /dev/null +++ b/src/index.d.ts @@ -0,0 +1,9 @@ +import type { Database } from 'bun:sqlite'; +import type { BunSQLiteDatabase } from 'drizzle-orm/bun-sqlite'; + +declare global { + var db: Database; + var drizzleDB: BunSQLiteDatabase; + var performanceObserver: PerformanceObserver; + var wrappedTimers: Map; +} diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..19e405d --- /dev/null +++ b/src/index.html @@ -0,0 +1,21 @@ + + + + Progress tracker API + + + + + + + +
+

progress tracker api

+
+ + diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..cf27861 --- /dev/null +++ b/src/server.ts @@ -0,0 +1,153 @@ +import { env } from "bun"; +import homepage from './index.html'; +import robotsTxt from './static/robots.txt'; +import sitemapTxt from './static/sitemap.txt'; +// @ts-ignore ts2307 +import icon from './static/favicon.png' with { type: 'file' }; +import { entry } from "./db/schema"; +import { desc, eq } from "drizzle-orm"; +const favicon = await Bun.file(icon).bytes(); + +const development = env.NODE_ENV !== 'production'; + +Bun.serve({ + routes: { + '/': homepage, + '/robots.txt': new Response(robotsTxt, { + headers: { 'Content-Type': 'text/plain' }, + }), + '/sitemap.txt': new Response(sitemapTxt, { + headers: { 'Content-Type': 'text/plain' }, + }), + '/favicon.ico': new Response(favicon, { + headers: { 'Content-Type': 'image/png' }, + }), + '/health': new Response('OK'), + '/api/entries': { + async GET(req) { + if (!isAuthenticated(req)) return unauthorizedResp(); + + const entries = await drizzleDB.select().from(entry).orderBy(desc(entry.updatedAt)); + + return new Response(JSON.stringify(entries), { + headers: { 'Content-Type': 'application/json' } + }); + }, + async POST(req) { + if (!isAuthenticated(req)) return unauthorizedResp(); + + const body = await getEntryFromReq(req); + if (!body) return new Response('Invalid data', { status: 400 }); + + const result = await drizzleDB.select({ id: entry.id }).from(entry).where(eq(entry.name, body.name)); + + let created = true; + + if (result.length === 0) { + await drizzleDB.insert(entry).values(body).execute(); + } else if (result.length === 1) { + const row = result[0]; + created = false; + await drizzleDB.update(entry).set(body).where(eq(entry.id, row.id)).execute(); + } else { + return new Response('Invalid data, multiple matches?', { status: 400 }); + } + + return new Response(JSON.stringify({ created }), { + headers: { 'Content-Type': 'application/json' } + }); + }, + }, + '/api/entries/:id': { + async GET(req) { + if (!isAuthenticated(req)) return unauthorizedResp(); + + const id = Number.parseInt(req.params.id, 10); + if (Number.isNaN(id)) return new Response('Invalid id', { status: 400 }); + + const result = await drizzleDB.select().from(entry).where(eq(entry.id, id)); + + return new Response(JSON.stringify(result), { + headers: { 'Content-Type': 'application/json' } + }); + }, + async PUT(req) { + if (!isAuthenticated(req)) return unauthorizedResp(); + + const id = Number.parseInt(req.params.id, 10); + if (Number.isNaN(id)) return new Response('Invalid id', { status: 400 }); + + const body = await getEntryFromReq(req); + if (!body) return new Response('Invalid data', { status: 400 }); + + await drizzleDB.update(entry).set(body).where(eq(entry.id, id)).execute(); + + let created = false; + return new Response(JSON.stringify({ created: false }), { + headers: { 'Content-Type': 'application/json' } + }); + }, + async DELETE(req) { + if (!isAuthenticated(req)) return unauthorizedResp(); + + const id = Number.parseInt(req.params.id, 10); + if (Number.isNaN(id)) return new Response('Invalid id', { status: 400 }); + + await drizzleDB.delete(entry).where(eq(entry.id, id)).execute(); + + return new Response('OK'); + }, + }, + '/api/entries/:id/check/:finished': { + async PUT(req) { + if (!isAuthenticated(req)) return unauthorizedResp(); + + const id = Number.parseInt(req.params.id, 10); + if (Number.isNaN(id)) return new Response('Invalid id', { status: 400 }); + const finished = req.params.finished === 'true'; + + await drizzleDB + .update(entry) + .set({ finished: finished }) + .where(eq(entry.id, id)) + .execute(); + + return new Response('OK'); + }, + }, + }, + development, + reusePort: true, + port: env.PORT || 3000, + // async fetch(req, server) { + // return new Response("Not found", { status: 404 }); + // }, +}); + +async function getEntryFromReq(req: Request) { + const json = await req.json(); + const body = json as { name: string, href: string } + if (!body.name || !body.href || typeof body.name !== 'string' || typeof body.href !== 'string') { + return null; + } + + // Trim and lowercase the name to ensure consistency and uniqueness + body.name = body.name.trim().toLocaleLowerCase(); + + return body; +} + +function isAuthenticated(req: Request) { + const bearer = req.headers.get('Bearer'); + if (!bearer) return false; + + return bearer === env.BEARER_TOKEN; +} + +const unauthorizedHeaders = new Headers({ + 'WWW-Authenticate': `Bearer realm='sign', error="invalid_request"` +}); + +function unauthorizedResp() { + return new Response('Unauthorized', { status: 401, headers: unauthorizedHeaders }); +} diff --git a/src/static/favicon.png b/src/static/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..dfa8b458c7bebb013ccbb22e75ca9648bf2fe207 GIT binary patch literal 5348 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 C$>o~> literal 0 HcmV?d00001 diff --git a/src/static/manifest.json b/src/static/manifest.json new file mode 100644 index 0000000..67bc2b1 --- /dev/null +++ b/src/static/manifest.json @@ -0,0 +1,10 @@ +{ + "name": "Progress tracker api by skaarup.dev", + "short_name": "progress-tracker-api", + "display": "browser", + "background_color": "#141141", + "theme_color": "#371D85", + "description": "Progress tracker api by skaarup.dev", + "icons": [], + "related_applications": [] +} diff --git a/src/static/robots.txt b/src/static/robots.txt new file mode 100644 index 0000000..c1579f8 --- /dev/null +++ b/src/static/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://pt-api.skaarup.dev/sitemap.txt diff --git a/src/static/sitemap.txt b/src/static/sitemap.txt new file mode 100644 index 0000000..b772eb7 --- /dev/null +++ b/src/static/sitemap.txt @@ -0,0 +1 @@ +https://pt-api.skaarup.dev/ diff --git a/src/wrapped-timer.ts b/src/wrapped-timer.ts new file mode 100644 index 0000000..c9b6a03 --- /dev/null +++ b/src/wrapped-timer.ts @@ -0,0 +1,78 @@ +if (global.wrappedTimers === undefined) { + global.wrappedTimers = new Map(); +} + +type WrappedTimer = { timer: Timer | undefined; running: boolean; }; +type WrappedTimerResult = { callback: ReturnType; wrappedTimer: WrappedTimer; }; + +/** + * Create a callback handler for a wrapped timer that prevents the callback from running if it is already running + * and prevents the callback from running if it is already running. + * @template TArgs + * @param {string} key unique identifier for the timer + * @param {(...args: Array) => (Promise | void)} callback function to run + * @param {Array} args arguments to pass to the callback + * @returns {() => Promise} + */ +function getCallbackHandler(key: string, callback: (...args: Array) => (Promise | void), ...args: Array): () => Promise { + return async () => { + const thisTimer = global.wrappedTimers.get(key); + if (thisTimer === undefined) { + console.debug(`Wrapped timer ${key} does not exist`); + return; + } + if (thisTimer.running) { + console.debug(`Wrapped timer ${key} is already running`); + return; + } + + try { + thisTimer.running = true; + await callback(...args); + } catch (e) { + console.error(e); + } finally { + thisTimer.running = false; + } + }; +} + +/** + * Create a wrapped timer aka interval + * @template TArgs + * @param {string} key unique identifier for the timer + * @param {number} interval in milliseconds + * @param {(...args: Array) => (Promise | void)} callback function to run + * @param {Array} args arguments to pass to the callback + * @returns {WrappedTimerResult} + */ +export function createWrappedTimer(key: string, callback: (...args: Array) => (Promise | void), interval: number, ...args: Array): WrappedTimerResult { + const thisTimer = global.wrappedTimers.get(key); + const handler = getCallbackHandler(key, callback, ...args); + + if (thisTimer !== undefined) { + console.debug(`Wrapped timer ${key} already exists, clearing timer`); + clearInterval(thisTimer.timer); + console.debug(`Wrapped timer ${key} set with interval ${interval}ms`); + thisTimer.timer = setInterval(handler, interval); + + return { + callback: handler, + wrappedTimer: thisTimer, + }; + } + + console.debug(`Wrapped timer ${key} created with interval ${interval}ms`); + + const wrappedTimer: WrappedTimer = { + timer: setInterval(handler, interval), + running: false, + }; + + global.wrappedTimers.set(key, wrappedTimer); + + return { + callback: handler, + wrappedTimer, + }; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..d47c860 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "sourceMap": true, + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } + // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias + // + // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes + // from the referenced tsconfig.json - TypeScript does not merge them in +}