Compare commits
22 Commits
7e27640a26
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 393ff406a3 | |||
| 9073e38238 | |||
| 27e5f3c76f | |||
| 4a78ba2882 | |||
| fafe02bdbd | |||
| 815ee31f95 | |||
| f8af18cff5 | |||
| 34d2cbb1cd | |||
| ce39faf778 | |||
| cc3a5dc5ad | |||
| d206a3315f | |||
| c6027590a7 | |||
| dd1facd240 | |||
| 5174cff3c5 | |||
| 2209ab0b27 | |||
| 7f4cfc8973 | |||
| afc8b0b077 | |||
| d9210b3b0b | |||
| 4f414014a8 | |||
| ed257fe4e6 | |||
| 695e826dcf | |||
| f520b54ca5 |
@@ -1 +1,6 @@
|
||||
DATABASE_URL=postgres://postgres:postgres@localhost:5432/postgres
|
||||
|
||||
# Optional logging knobs (defaults are usually fine):
|
||||
# LOG_LEVEL=info # trace|debug|info|warning|error|fatal
|
||||
# LOG_FORMAT=pretty # pretty|json — defaults to TTY ? pretty : json
|
||||
# LOG_DB=false # true to log every Drizzle SQL query
|
||||
|
||||
+14
-145
@@ -1,154 +1,23 @@
|
||||
### Custom ###
|
||||
|
||||
# TanStack
|
||||
.tanstack/
|
||||
|
||||
# Nitro
|
||||
.output/
|
||||
|
||||
# Bun build
|
||||
*.bun-build
|
||||
|
||||
### Node ###
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
# Dependencies
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
# Build output
|
||||
.output/
|
||||
.tanstack/
|
||||
.vite/
|
||||
out/
|
||||
*.bun-build
|
||||
*.tsbuildinfo
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
# Env
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
.output
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Sveltekit cache directory
|
||||
.svelte-kit/
|
||||
|
||||
# vitepress build output
|
||||
**/.vitepress/dist
|
||||
|
||||
# vitepress cache directory
|
||||
**/.vitepress/cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# Firebase cache directory
|
||||
.firebase/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v3
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
|
||||
# Vite files
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
.vite/
|
||||
# OS
|
||||
.DS_Store
|
||||
|
||||
Vendored
+3
-1
@@ -1,9 +1,11 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"biomejs.biome",
|
||||
"codezombiech.gitignore",
|
||||
"hverlin.mise-vscode",
|
||||
"oven.bun-vscode",
|
||||
"redhat.vscode-yaml",
|
||||
"tamasfe.even-better-toml"
|
||||
"tamasfe.even-better-toml",
|
||||
"unional.vscode-sort-package-json"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,28 +5,31 @@ Compact, repo-specific notes for AI agents. Generic language/framework knowledge
|
||||
## Stack & runtime
|
||||
|
||||
- **Bun-only** (`mise.toml` pins `bun = 1.3.13`). Never invoke `npm`/`npx`/`node`/`yarn`/`pnpm`. Use `bun run <script>` (bare `bun <script>` can collide with Bun built-in subcommands).
|
||||
- TanStack Start (React 19 SSR, file-routed) + Vite 8 + Nitro (nightly, preset `bun`). Vite dev port is **strict 3000**.
|
||||
- PostgreSQL + **Drizzle ORM `0.45.2` (0.x, NOT 1.0 beta)** — see "Drizzle" section, this matters a lot.
|
||||
- **Prefer Bun-native APIs over external packages and `node:*` polyfills.** UUIDv7 in app code → `Bun.randomUUIDv7()` (not the `uuid` package); DB primary keys are a separate matter — those go through PG18's `uuidv7()`, see "Drizzle" section. SHA-256 → `Bun.CryptoHasher.hash('sha256', s, 'hex')` (not `node:crypto.createHash`); short sleeps → `Bun.sleep(ms)` (not raw `setTimeout` with promise wrapping); file I/O in build scripts → `Bun.file` / `Bun.write` are fine. The runtime is Bun, the deployment target is Bun, the test runner is Bun — there is no "portability" concern that would justify dragging in npm packages or Node compat shims for things Bun ships natively.
|
||||
- TanStack Start (React 19 SSR, file-routed) + Vite 8 + Nitro (nightly, preset `bun`). Dev server defaults to Vite's port (3000); not pinned, override via `vite dev --port <n>` if you need to.
|
||||
- **PostgreSQL 18+ only** (`compose.yaml` pins `postgres:18-alpine`). The starter relies on PG18's built-in `uuidv7()` function for primary-key generation — see "Drizzle" section. Do not soften this to support older PG; if you need PG <18 compatibility, fork and reintroduce app-side UUIDv7 (e.g. `Bun.randomUUIDv7()` or the `uuid` package) yourself.
|
||||
- **Drizzle ORM `0.45.2` (0.x, NOT 1.0 beta)** — see "Drizzle" section, this matters a lot.
|
||||
- ORPC (contract-first), TanStack Query v5, Tailwind v4.
|
||||
- **Logging via [LogTape](https://logtape.org/)** (zero-dep, runtime-agnostic) — see "Logging" section. `console.*` is forbidden in business code.
|
||||
|
||||
## Scripts
|
||||
|
||||
```bash
|
||||
bun run dev # bunx --bun vite dev (localhost:3000)
|
||||
bun run build # bunx --bun vite build → .output/
|
||||
bun run compile # bun compile.ts → out/server-<target> (standalone CLI binary)
|
||||
bun run cli <cmd> # bun bin.ts <cmd> — run a CLI subcommand in source (dev)
|
||||
bun run compile # bun scripts/compile.ts → out/server-<target> (standalone CLI binary)
|
||||
bun run cli <cmd> # bun src/bin.ts <cmd> — run a CLI subcommand in source (dev)
|
||||
bun run typecheck # tsc --noEmit
|
||||
bun run test # bun test — runs all *.test.ts files (colocated with source)
|
||||
bun run fix # biome check --write (lint + format + organize imports)
|
||||
bun run db:push # dev only — push schema to DB, no migration file
|
||||
bun run db:generate # drizzle-kit generate && embed-migrations.ts (regenerates migrations.gen.ts)
|
||||
bun run db:embed # embed-migrations.ts only — regenerate migrations.gen.ts from ./drizzle/
|
||||
bun run db:generate # drizzle-kit generate && scripts/embed-migrations.ts (regenerates migrations.gen.ts)
|
||||
bun run db:embed # scripts/embed-migrations.ts only — regenerate migrations.gen.ts from ./drizzle/
|
||||
bun run db:migrate # apply migrations via drizzle-kit (local dev convenience; prod uses ./server migrate)
|
||||
bun run db:studio # Drizzle Studio
|
||||
```
|
||||
|
||||
Cross-compile targets live under `compile:{linux,darwin,windows}[:arch]`. `compile.ts` accepts `--target bun-<os>-<arch>`; default derives from host.
|
||||
Cross-compile targets live under `compile:{linux,darwin,windows}[:arch]`. `scripts/compile.ts` accepts `--target bun-<os>-<arch>`; default derives from host.
|
||||
|
||||
Before committing: `bun run fix && bun run typecheck && bun run test`. No CI, no pre-commit hooks, no lint-staged — so these are on you.
|
||||
|
||||
@@ -45,36 +48,36 @@ Before committing: `bun run fix && bun run typecheck && bun run test`. No CI, no
|
||||
```
|
||||
Do NOT use the v2 object form (`orderBy: { createdAt: 'desc' }`, `where: { id }`) — it won't type-check.
|
||||
- To add relations later: declare per-table with `relations()` from `drizzle-orm` and export them from the same file as the table; they get picked up automatically because `index.ts` does `drizzle({ schema })` via `import *`.
|
||||
- Every table must spread `...generatedFields` from `src/server/db/fields.ts` (`id` UUIDv7 via `$defaultFn(uuidv7)`, `createdAt`, `updatedAt` with `$onUpdateFn`). `generatedFieldKeys` (hand-written `as const`) feeds `createInsertSchema(...).omit(...)`.
|
||||
- Every table must spread `...generatedFields` from `src/server/db/fields.ts` (`id uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL` — **Postgres-side generation**, requires PG18+; `createdAt`, `updatedAt` with `$onUpdateFn`). The DB is the single source of UUIDv7 truth: monotonic per cluster, uses DB clock, no app-side round-trip. **Do not reintroduce `$defaultFn(() => Bun.randomUUIDv7())`** — the SQL default is what the migration emits and what `drizzle-zod` reads as "optional in insert schema". `generatedFieldKeys` is hand-written and uses `satisfies Record<keyof typeof generatedFields, true>` so any field-key drift fails typecheck; it feeds `createInsertSchema(...).omit(...)` / `createUpdateSchema(...).omit(...)`.
|
||||
- `src/server/db/index.ts` exports a module-level `const db = drizzle(...)` — not a lazy singleton. On Bun this is a long-lived process, so top-level side effects are fine and requested. Don't reintroduce `getDB/closeDB` ceremony; the Nitro shutdown plugin calls `db.$client.end()` directly. (Cloudflare Workers would need per-request init — we don't support that deployment target.)
|
||||
- `drizzle.config.ts` runs outside Vite — `@/*` path aliases do NOT resolve there. It currently does `import { env } from './src/env'` (relative). Preserve that.
|
||||
- **Migrations are embedded in the binary, not read from disk.** `bun run db:generate` chains `drizzle-kit generate && bun embed-migrations.ts`, which regenerates `src/server/db/migrations.gen.ts` (committed, AUTO-GENERATED header) by `import sql_<idx> from '../../../drizzle/<tag>.sql' with { type: 'text' }`. `src/cli/migrate.ts` reads `embeddedMigrations`, computes SHA-256 hashes at runtime, and applies pending entries via `db.execute(sql\`...\`)` + `db.transaction(...)` against the `drizzle.__drizzle_migrations` book-keeping table — public APIs only, no `db.dialect`/`db.session` (those are `@internal`). Dev helpers `db:push` / `drizzle-kit migrate` still read `./drizzle/`.
|
||||
- **Migrations are embedded in the binary, not read from disk.** `bun run db:generate` chains `drizzle-kit generate && bun scripts/embed-migrations.ts`, which regenerates `src/server/db/migrations.gen.ts` (committed, AUTO-GENERATED header) by `import sql_<idx> from '#drizzle/<tag>.sql' with { type: 'text' }`. `src/cli/migrate.ts` reads `embeddedMigrations`, **validates SHA-256 hash of every already-applied migration against the embedded SQL** (rejects schema drift if anyone edited an applied migration), then applies pending entries via `db.execute(sql\`...\`)` + `db.transaction(...)` against the `drizzle.__drizzle_migrations` book-keeping table — public APIs only, no `db.dialect`/`db.session` (those are `@internal`). Each migration is split on `--> statement-breakpoint`; empty fragments are trimmed and skipped. Dev helpers `db:push` / `drizzle-kit migrate` still read `./drizzle/`.
|
||||
|
||||
## CLI & single-binary deploy
|
||||
|
||||
`bun run compile` produces a single executable that dispatches subcommands via [citty](https://github.com/unjs/citty). Entry is `bin.ts` at repo root, subcommands live in `src/cli/`.
|
||||
`bun run compile` produces a single executable that dispatches subcommands via [citty](https://github.com/unjs/citty). Entry is `src/bin.ts`; subcommands live in `src/cli/`.
|
||||
|
||||
```
|
||||
./server [serve] # default — start the HTTP server
|
||||
./server migrate # apply migrations from ./drizzle
|
||||
./server migrate # apply embedded migrations
|
||||
./server --help
|
||||
```
|
||||
|
||||
**Nitro side-effect pitfall (important).** Under the `bun` preset, `.output/server/index.mjs` has a top-level `serve(...)` call — merely importing it starts the HTTP server. `bin.ts` therefore must not eager-import any subcommand module, and `src/cli/serve.ts` reaches `.output/server/index.mjs` through the `src/cli/_serve-nitro.mjs` bridge (with `_serve-nitro.d.mts` for types, since `.output/` doesn't exist at typecheck time). Citty's `subCommands: { x: () => import('...') }` lazy-loader is what keeps `--help` and `migrate` from booting the server.
|
||||
**Nitro side-effect pitfall (important).** Under the `bun` preset, `.output/server/index.mjs` has a top-level `serve(...)` call — merely importing it starts the HTTP server. `src/bin.ts` therefore must not eager-import any subcommand module, and `src/cli/serve.ts` reaches `.output/server/index.mjs` through the `src/cli/_serve-nitro.mjs` bridge (with `_serve-nitro.d.mts` for types, since `.output/` doesn't exist at typecheck time). Citty's `subCommands: { x: () => import('...') }` lazy-loader is what keeps `--help` and `migrate` from booting the server.
|
||||
|
||||
**Citty eager-loads subcommand modules for `--help`** to read each subcommand's `meta`. So every `src/cli/*.ts` module body must be side-effect-free: do NOT static-import `@/env`, `@/server/db/*`, or anything that reads env at module-load time. Use `await import('@/env')` inside `run()`. Otherwise `./server --help` (or any subcommand's help) will fail with env validation errors before printing.
|
||||
|
||||
Add a subcommand: drop a file in `src/cli/` that default-exports `defineCommand({...})`, then register it in `bin.ts`'s `subCommands` with a `() => import(...)` thunk. Keep top-level imports limited to `citty` + Node built-ins; pull env / db / etc. via `await import(...)` inside `run()`.
|
||||
Add a subcommand: drop a file in `src/cli/` that default-exports `defineCommand({...})`, then register it in `src/bin.ts`'s `subCommands` with a `() => import(...)` thunk. Keep top-level imports limited to `citty` + Node built-ins; pull env / db / etc. via `await import(...)` inside `run()`.
|
||||
|
||||
**Deploy flow is always migrate-then-serve.** Migrations are embedded in the binary (see "Drizzle" section), so the binary is the only artifact — no `./drizzle/` directory at runtime. Dockerfile copies just `./server`. `compose.yaml` models the pattern with a one-shot `migrate` service that `app` `depends_on: service_completed_successfully`. On k8s, run `./server migrate` as an initContainer or a Helm `pre-upgrade` Job; run `./server` (= `./server serve`) as the main container.
|
||||
|
||||
## Compile flags
|
||||
|
||||
`compile.ts` builds with `--minify --bytecode --sourcemap=inline`:
|
||||
`scripts/compile.ts` builds with `--minify --bytecode --sourcemap=inline`:
|
||||
|
||||
- **`bytecode`** — pre-compiles JS to bytecode and embeds it in the binary; ~2x startup on app-sized binaries (Bun docs benchmark). Requires `--compile`; top-level `await` must live inside `async` functions (it already does in this repo).
|
||||
- **`minify`** — shrinks the binary and the bytecode it derives from.
|
||||
- **`sourcemap: 'inline'`** — embeds the source map in the binary so error stack traces stay decodable. Bun also writes a residual `out/bin.js.map` next to the output; `compile.ts` removes it so the binary is the only artifact.
|
||||
- **`sourcemap: 'inline'`** — embeds the source map in the binary so error stack traces stay decodable. Bun also writes a residual `out/bin.js.map` next to the output; `scripts/compile.ts` removes it so the binary is the only artifact.
|
||||
|
||||
## ORPC
|
||||
|
||||
@@ -87,7 +90,7 @@ Contract → Router → Handler → Client, all type-safe from a single contract
|
||||
```
|
||||
Barrel-aggregated in `contracts/index.ts` as `export const contract = { todo }`.
|
||||
- Routers (`src/server/api/routers/*.router.ts`) import `db` directly from `@/server/db` in their handlers. There is **no `middlewares/` directory** by default — `db` doesn't need one (module-level const). When you actually need per-request context (auth, tenant, rate-limit), create `src/server/api/middlewares/<name>.middleware.ts` with `os.middleware(...)` and extend `BaseContext` in `context.ts`.
|
||||
- **Interceptors are attached at the handler level, not in `server.ts` and not on `os`.** Both `src/routes/api/rpc.$.ts` (`RPCHandler`) and `src/routes/api/$.ts` (`OpenAPIHandler`) register `[onError(logError)]` (server) and `[onError(handleValidationError)]` (client). The validation interceptor rewrites `BAD_REQUEST + ValidationError` into `INPUT_VALIDATION_FAILED` (422) and output validation errors into `OUTPUT_VALIDATION_FAILED`. `logError` calls `logger.error` from `@/server/logger` — never `console.*` directly.
|
||||
- **Interceptors are attached at the handler level, not in `server.ts` and not on `os`.** Both `src/routes/api/rpc.$.ts` (`RPCHandler`) and `src/routes/api/$.ts` (`OpenAPIHandler`) register `[onError(logError)]` (server) and `[onError(handleValidationError)]` (client). The validation interceptor rewrites `BAD_REQUEST + ValidationError` into `INPUT_VALIDATION_FAILED` (422) and output validation errors into `OUTPUT_VALIDATION_FAILED`. `logError` resolves a `getLogger(['api'])` LogTape category — never `console.*` directly. See "Logging" section.
|
||||
- OpenAPI/Scalar: docs at `/api/docs`, spec at `/api/spec.json` (handler prefix `/api`, plugin paths `/docs` and `/spec.json`).
|
||||
- **SSR isomorphism** (`src/client/orpc.ts`): `createIsomorphicFn().server(createRouterClient(...)).client(new RPCLink(...))`. Server branch reads `getRequestHeaders()` for context; client branch POSTs to `${origin}/api/rpc`.
|
||||
- **Mutation invalidation is colocated at the call site** via `mutationOptions({ onSuccess })`, not in `src/client/orpc.ts`. `orpc` in `src/client/orpc.ts` is a plain `createTanstackQueryUtils(client)` — no `experimental_defaults`. Per-feature query helpers live in `src/client/queries/<feature>.ts` (e.g. `useInvalidateTodos`); routes/components compose those hooks rather than holding query keys inline. See `src/client/queries/todo.ts` + `src/routes/index.tsx` for the canonical shape.
|
||||
@@ -96,6 +99,7 @@ Contract → Router → Handler → Client, all type-safe from a single contract
|
||||
## Code style (Biome)
|
||||
|
||||
- 2-space, LF, single quotes, **semicolons as-needed** (omitted unless required), 120-col, arrow parens always, `useArrowFunction: "error"` (covers function *expressions* only). Also `noReactPropAssignments: "error"`.
|
||||
- Lint domains enabled (Biome 2.4): `types: "all"` (catches `noFloatingPromises`, `noMisusedPromises`, `useAwaitThenable`, `noUnnecessaryConditions` — TS-aware async/promise traps), `drizzle: "recommended"`, `react: "recommended"`. `noImportCycles` is on under `suspicious`.
|
||||
- **Route components use `function Foo()` declarations**, placed below the `Route` config so the file reads top-down (route on top, component below). This is the official TanStack Router/Start pattern and relies on hoisting — `const Foo = () => {}` would TDZ-error when referenced from `createFileRoute({ component: Foo })` above it. Inline arrows are fine for trivial leaf components (e.g. plain redirect routes). Non-route components (UI primitives in `src/components/`) use `const Foo = () => {}`.
|
||||
- Imports are auto-organized into two groups (external, then `@/*`), each alphabetical, with `import type` interleaved (NOT a separate group). `bun run fix` handles this; don't hand-sort.
|
||||
- Files: utils `kebab-case.ts`, components `PascalCase.tsx`.
|
||||
@@ -120,11 +124,29 @@ Path alias: `@/* → src/*`. For files outside `src/` use `@/../<file>` (example
|
||||
|
||||
## Env
|
||||
|
||||
`src/env.ts` via `@t3-oss/env-core`. Server: `DATABASE_URL` (required, `z.url()`). `client: {}` is empty by default — any client-side env must be `VITE_`-prefixed. Never commit `.env`.
|
||||
`src/env.ts` via `@t3-oss/env-core`. Server: `DATABASE_URL` (required, `z.url()`), `LOG_LEVEL` (`trace|debug|info|warning|error|fatal`, default `info`), `LOG_FORMAT` (`pretty|json`, default = TTY ? `pretty` : `json`), `LOG_DB` (`stringbool`, default `false` — flips on Drizzle SQL query logging). `client: {}` is empty by default — any client-side env must be `VITE_`-prefixed. Never commit `.env`.
|
||||
|
||||
## Logging
|
||||
|
||||
All server-side logging goes through `src/server/logger.ts`, a thin wrapper over [LogTape](https://logtape.org/). The module configures LogTape on import (via `configureSync`, no top-level await — works under `--bytecode`) and re-exports `getLogger`.
|
||||
|
||||
```ts
|
||||
import { getLogger } from '@/server/logger'
|
||||
const logger = getLogger(['feature', 'subsystem'])
|
||||
logger.info('Created todo {id}', { id })
|
||||
logger.error('DB write failed', { error })
|
||||
```
|
||||
|
||||
- Categories are hierarchical arrays — they show up as dot-paths in JSON output (`"logger":"feature.subsystem"`) and let you filter by prefix when shipping logs.
|
||||
- The `{name}` placeholders are for **primitive** values you want rendered inline (numbers, short strings, IDs). For objects, errors, and anything multi-field, omit the placeholder and just pass the value in properties — `logger.error('Auth failed', { error, userId })` keeps the message clean while properties stay structured. Never string-concatenate or template-literal — that defeats structured logging.
|
||||
- Format is `pretty` (icons + ANSI) on TTY, `json` (one-line JSON) when piped — perfect for Loki/Datadog/CloudWatch ingestion. Override with `LOG_FORMAT`.
|
||||
- Drizzle SQL queries are logged at `info` under category `['db']` when `LOG_DB=true`, via `@logtape/drizzle-orm`'s `DrizzleLogger` adapter (constructed in `src/server/db/index.ts`). The `info` level is intentional: flipping `LOG_DB=true` alone is enough — no need to also lower `LOG_LEVEL`.
|
||||
- `src/server/api/interceptors.ts` calls `getLogger(['api']).error(...)` from `logError`. CLI subcommands lazy-import the logger inside `run()` — they are still required to be side-effect-free at module top (citty eager-loads for `--help`).
|
||||
- Bun-specific: `process.env.NODE_ENV` is **inlined at build time** by `bun build --minify` — do NOT branch on it for logger config (use `process.stdout.isTTY` or `LOG_FORMAT` instead). pino is unusable here because its worker-thread transports crash inside the `/$bunfs/` virtual filesystem of compiled binaries; LogTape has zero workers and zero dynamic require, so it ships cleanly into the single binary.
|
||||
|
||||
## Docker / deploy
|
||||
|
||||
- Multi-stage: `oven/bun:1.3.13` builds and runs `bun compile.ts`, then `gcr.io/distroless/cc-debian13:nonroot` runs the single `./server` binary. The `cc` (glibc) distroless variant is required because Bun's compiled binary links glibc.
|
||||
- Multi-stage: `oven/bun:1.3.13` builds and runs `bun scripts/compile.ts`, then `gcr.io/distroless/cc-debian13:nonroot` runs the single `./server` binary. The `cc` (glibc) distroless variant is required because Bun's compiled binary links glibc.
|
||||
- `compose.yaml`: one-shot `migrate` service runs `./server migrate` with `restart: "no"`, then `app` starts (`depends_on: migrate: service_completed_successfully`). `DATABASE_URL=postgres://postgres:postgres@db:5432/postgres` for both.
|
||||
- Distroless has no shell, so any init-then-serve pattern must use exec-form `command: [...]`, not `sh -c`.
|
||||
|
||||
@@ -132,13 +154,14 @@ Path alias: `@/* → src/*`. For files outside `src/` use `@/../<file>` (example
|
||||
|
||||
```
|
||||
src/
|
||||
├── bin.ts # citty entry — keep imports minimal (see "CLI" section)
|
||||
├── client/
|
||||
│ ├── orpc.ts # isomorphic ORPC client + TanStack Query utils (no global invalidation defaults)
|
||||
│ └── queries/ # per-feature query hooks: keys, options, `useInvalidate<Feature>` helpers
|
||||
├── cli/ # CLI subcommands (loaded lazily by bin.ts via citty)
|
||||
├── cli/ # CLI subcommands (loaded lazily by src/bin.ts via citty)
|
||||
│ ├── serve.ts # `./server serve` — imports the Nitro bridge on demand
|
||||
│ ├── migrate.ts # `./server migrate` — drizzle migrate against ./drizzle
|
||||
│ ├── _serve-nitro.mjs # bridge: `import('../../.output/server/index.mjs')`
|
||||
│ ├── migrate.ts # `./server migrate` — applies embedded migrations via public `db.execute(sql)` + `db.transaction()`
|
||||
│ ├── _serve-nitro.mjs # bridge: `import('#server')` (subpath import → .output/server/index.mjs)
|
||||
│ └── _serve-nitro.d.mts # types for the bridge (build output has no .d.ts)
|
||||
├── routes/
|
||||
│ ├── __root.tsx # root route + RootDocument shell
|
||||
@@ -148,7 +171,7 @@ src/
|
||||
│ ├── $.ts # OpenAPI + Scalar; interceptors registered here
|
||||
│ └── rpc.$.ts # RPC; interceptors registered here
|
||||
├── server/
|
||||
│ ├── logger.ts # the only log entrypoint — wrap before swapping to pino/otel
|
||||
│ ├── logger.ts # LogTape `configureSync` + `getLogger` re-export — the only log entrypoint
|
||||
│ ├── api/
|
||||
│ │ ├── server.ts # the ONLY place to build `os`
|
||||
│ │ ├── context.ts # BaseContext (add per-request fields when you add middlewares)
|
||||
@@ -160,6 +183,7 @@ src/
|
||||
│ │ ├── index.ts # module-level `export const db = drizzle({...})`
|
||||
│ │ ├── fields.ts # generatedFields (id/createdAt/updatedAt) + generatedFieldKeys
|
||||
│ │ ├── migrations.gen.ts # AUTO-GENERATED by `bun run db:embed`; embeds ./drizzle/*.sql via `with { type: 'text' }`
|
||||
│ │ ├── sql.d.ts # ambient `declare module '*.sql'` — load-bearing for `with { type: 'text' }` imports in migrations.gen.ts
|
||||
│ │ └── schema/ # pgTable definitions; also put `relations()` here when adding
|
||||
│ └── plugins/
|
||||
│ └── shutdown.ts # SIGINT/SIGTERM → db.$client.end() with 500ms delay (prod only)
|
||||
@@ -168,9 +192,9 @@ src/
|
||||
├── router.tsx # QueryClient + setupRouterSsrQueryIntegration
|
||||
├── styles.css # Tailwind v4 entry
|
||||
└── routeTree.gen.ts # auto-generated, do not edit
|
||||
bin.ts # citty entry (root) — keep imports minimal (see "CLI" section)
|
||||
compile.ts # `bun build --compile` driver; resolves --target; sets minify/bytecode/sourcemap
|
||||
embed-migrations.ts # codegen: scans ./drizzle/meta/_journal.json → src/server/db/migrations.gen.ts
|
||||
scripts/
|
||||
├── compile.ts # `bun build --compile` driver; resolves --target; sets minify/bytecode/sourcemap
|
||||
└── embed-migrations.ts # codegen: scans ./drizzle/meta/_journal.json → src/server/db/migrations.gen.ts
|
||||
drizzle/ # SQL migrations (source of truth for `db:generate`; not shipped in binary)
|
||||
```
|
||||
|
||||
@@ -178,8 +202,9 @@ Nitro plugins are wired in `vite.config.ts` (`nitro({ plugins: [...] })`), not v
|
||||
|
||||
## Don'ts (specific, non-obvious)
|
||||
|
||||
- **Don't run `bun run db:generate` (or `drizzle-kit generate`) as an AI agent.** Migration generation is reserved for the human. Make schema changes in `src/server/db/schema/*` + `src/server/db/fields.ts`, push the code changes, and stop — the human will run `bun run db:generate` and commit the resulting `drizzle/*.sql` + `src/server/db/migrations.gen.ts` themselves. (`bun run db:embed` is also off-limits because it's the codegen tail of `db:generate`.)
|
||||
- Don't edit `routeTree.gen.ts` or `src/server/db/migrations.gen.ts`.
|
||||
- Don't eager-import anything from `.output/` in `bin.ts` or any module it statically imports — it starts the HTTP server as a side effect. Subcommands must be lazy via citty's `() => import(...)` thunks.
|
||||
- Don't eager-import anything from `.output/` in `src/bin.ts` or any module it statically imports — it starts the HTTP server as a side effect. Subcommands must be lazy via citty's `() => import(...)` thunks.
|
||||
- Don't re-add an auto-migrate Nitro plugin. Migrations are an explicit deploy step via `./server migrate`.
|
||||
- Don't reach into `db.dialect`/`db.session` from `migrate.ts` — they're `@internal`. The current implementation uses public `db.execute(sql)` + `db.transaction(...)` against the documented `drizzle.__drizzle_migrations` schema.
|
||||
- Don't add `./drizzle/` back to the runtime image — migrations are embedded into the binary.
|
||||
@@ -202,6 +227,6 @@ These keep the starter from setting bad precedents as it grows. Append, don't re
|
||||
5. Interceptors (`src/server/api/interceptors.ts`) do cross-cutting error logging, transport normalization, and validation rewrites. They do NOT read business data.
|
||||
6. One file per Drizzle table. Relations live in the same file and are exported as `<entity>Relations`. No global `relations.ts`.
|
||||
7. One router file per feature (`routers/<feature>.router.ts`). Only introduce `routers/<domain>/index.ts` when a domain grows past ~5 router files or needs shared domain helpers.
|
||||
8. All server-side logging goes through `src/server/logger.ts`. Do not call `console.error/info/warn` directly from business code.
|
||||
9. CLI subcommand modules keep top-level imports to `citty` + Node built-ins. Env, db, and server code are `await import(...)`-ed inside `run()` (see `bin.ts` comment for why).
|
||||
8. All server-side logging goes through `getLogger([...])` from `@/server/logger`. Use a hierarchical category (`['api']`, `['db']`, `['cli', 'migrate']`, etc.) — these become dot-paths in JSON output and let you filter by prefix. Use the `{name}` placeholder + properties form, not string interpolation. `console.*` is forbidden in business code.
|
||||
9. CLI subcommand modules keep top-level imports to `citty` + Node built-ins. Env, db, and server code are `await import(...)`-ed inside `run()` (see `src/bin.ts` comment for why).
|
||||
10. Every new business feature ships with at least one `bun test` covering a contract schema, a pure helper, or a router behavior.
|
||||
|
||||
@@ -3,7 +3,6 @@ FROM oven/bun:1.3.13 AS build
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json bun.lock ./
|
||||
COPY patches ./patches
|
||||
RUN bun install --frozen-lockfile
|
||||
|
||||
COPY . .
|
||||
|
||||
@@ -1,64 +1,112 @@
|
||||
# fullstack-starter
|
||||
|
||||
Opinionated single-binary fullstack starter. Bun + TanStack Start (React 19 SSR) + ORPC (contract-first) + Drizzle + PostgreSQL, deployed as one compiled executable.
|
||||
一个**单二进制**的全栈应用 starter——`bun run compile` 出来的 `./server` 文件就是你要部署的全部产物,自带 HTTP 服务、SSR、API、嵌入式 SQL 迁移,运行时不依赖 Node、不依赖源码、不依赖外部 migration 目录。
|
||||
|
||||
> Agent notes and non-obvious invariants live in [AGENTS.md](./AGENTS.md). Read it before making structural changes.
|
||||
技术栈:Bun · TanStack Start (React 19 SSR) · ORPC(契约优先 API)· Drizzle ORM · PostgreSQL 18+ · Tailwind v4 · Biome。
|
||||
|
||||
## Quick start
|
||||
## 为什么用这个
|
||||
|
||||
- **部署最简**:发布只拷一个二进制文件。先 `./server migrate` 再 `./server`,完事。
|
||||
- **契约优先**:在 `*.contract.ts` 用 Zod 定义一次,前端、后端、OpenAPI 文档自动同步。
|
||||
- **类型严格**:TypeScript strict,杜绝 `any` / `@ts-ignore` / `as any` 等类型逃逸。
|
||||
- **开箱可跑**:路径别名、文件路由、ORPC 接线、Tailwind、热重载、错误页全部预接好。
|
||||
|
||||
## 快速开始
|
||||
|
||||
> **需要 PostgreSQL 18+**——schema 用 PG 原生的 `uuidv7()` 生成主键(`compose.yaml` 已锁 `postgres:18-alpine`)。要兼容更老的 PG,把 `src/server/db/fields.ts` 里的 `default(sql\`uuidv7()\`)` 换成 `$defaultFn(() => Bun.randomUUIDv7())`,再跑 `bun run db:generate`。
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
cp .env.example .env # 把里面的 DATABASE_URL 改成你的 Postgres
|
||||
bun install
|
||||
bun run db:push # dev: sync schema without migration files
|
||||
bun run dev # http://localhost:3000
|
||||
bun run db:push # 开发期:把 schema 直接同步到 DB(不写 migration 文件)
|
||||
bun run dev # http://localhost:3000
|
||||
```
|
||||
|
||||
RPC endpoint: `/api/rpc` · OpenAPI docs: `/api/docs` · Spec: `/api/spec.json` · Liveness: `/health`.
|
||||
打开浏览器:
|
||||
|
||||
## Scripts
|
||||
- `http://localhost:3000/` — Todo 示例页
|
||||
- `http://localhost:3000/api/docs` — Scalar 渲染的 API 文档
|
||||
|
||||
| Command | What it does |
|
||||
| --- | --- |
|
||||
| `bun run dev` | Vite dev server on port 3000 (strict) |
|
||||
| `bun run build` | Build to `.output/` |
|
||||
| `bun run compile` | Single-binary `out/server-<target>` via `bun build --compile` |
|
||||
| `bun run cli <cmd>` | Run a CLI subcommand in source (`serve`, `migrate`) |
|
||||
| `bun run typecheck` | `tsc --noEmit` |
|
||||
| `bun run test` | `bun test` (colocated `*.test.ts`) |
|
||||
| `bun run fix` | Biome lint + format + organize imports |
|
||||
| `bun run db:push` | Dev-only schema sync (no migration files) |
|
||||
| `bun run db:generate` | Write SQL migrations to `./drizzle` and regenerate `migrations.gen.ts` |
|
||||
| `bun run db:embed` | Regenerate `src/server/db/migrations.gen.ts` from `./drizzle` (run if you hand-edit migrations) |
|
||||
| `bun run db:migrate` | Apply migrations locally via drizzle-kit (dev convenience) |
|
||||
| `bun run db:studio` | Drizzle Studio |
|
||||
## 目录结构(你需要关心的部分)
|
||||
|
||||
Cross-compile: `bun run compile:{linux,darwin,windows}[:arch]`.
|
||||
```
|
||||
src/
|
||||
├── routes/ # 文件路由:页面 + API 端点
|
||||
├── server/
|
||||
│ ├── api/
|
||||
│ │ ├── contracts/ # Zod 契约(client / server 共享)
|
||||
│ │ └── routers/ # 业务实现
|
||||
│ └── db/ # Drizzle schema + 嵌入式 migrations
|
||||
├── client/ # 前端 hooks、ORPC 客户端
|
||||
└── components/ # UI 组件
|
||||
```
|
||||
|
||||
## Add a feature (e.g. `post`)
|
||||
## 加一个功能(以 `post` 为例)
|
||||
|
||||
Contract-first, additive. Create or touch files in this order:
|
||||
每一步都很短,按顺序填即可:
|
||||
|
||||
1. `src/server/db/schema/post.ts` — define `postTable`, spread `...generatedFields`.
|
||||
2. `src/server/db/schema/index.ts` — `export * from './post'`.
|
||||
3. `src/server/api/contracts/post.contract.ts` — derive Zod from the table via `drizzle-zod`.
|
||||
4. `src/server/api/contracts/index.ts` — add `post` to the `contract` object.
|
||||
5. `src/server/api/routers/post.router.ts` — implement `os.post.*.handler(...)`.
|
||||
6. `src/server/api/routers/index.ts` — add `post` to the `router` object.
|
||||
7. `src/client/queries/post.ts` — export `useInvalidatePosts` (or finer-grained helpers) for affected list keys.
|
||||
8. `src/routes/<page>.tsx` — UI with `useSuspenseQuery` + loader `ensureQueryData`; call the helper from mutation `onSuccess`.
|
||||
9. `bun run db:generate` — emit SQL migrations to `./drizzle` and embed them into `src/server/db/migrations.gen.ts`.
|
||||
1. **建表**:`src/server/db/schema/post.ts` 定义 `postTable`,记得展开 `...generatedFields`(自动注入 `id` / `createdAt` / `updatedAt`)。
|
||||
2. **导出表**:在 `src/server/db/schema/index.ts` 加 `export * from './post'`。
|
||||
3. **写契约**:`src/server/api/contracts/post.contract.ts` 用 `drizzle-zod` 从表派生 Zod schema。
|
||||
4. **挂契约**:在 `src/server/api/contracts/index.ts` 把 `post` 加进 `contract` 对象。
|
||||
5. **写实现**:`src/server/api/routers/post.router.ts` 实现 `os.post.*.handler(...)`。
|
||||
6. **挂路由**:在 `src/server/api/routers/index.ts` 把 `post` 加进 `router` 对象。
|
||||
7. **写前端 hook**:`src/client/queries/post.ts` 导出 `useInvalidatePosts` 等失效辅助。
|
||||
8. **写页面**:`src/routes/<page>.tsx` 用 `useSuspenseQuery` 读、`mutate` 写;mutation 的 `onSuccess` 调用第 7 步的 helper。
|
||||
9. **生成 migration**:`bun run db:generate` 把 SQL 写到 `./drizzle/` 并嵌入二进制。
|
||||
|
||||
## Deploy
|
||||
完工。`bun run dev` 已自动热重载。
|
||||
|
||||
Always **migrate-then-serve**. Migrations are embedded in the binary; the binary is the only artifact you ship.
|
||||
## 部署
|
||||
|
||||
**永远先 migrate 再 serve**。Migration 已嵌入二进制;部署只发一个 `./server` 文件。
|
||||
|
||||
```bash
|
||||
./server migrate # applies embedded migrations against $DATABASE_URL
|
||||
./server # starts HTTP server (default subcommand)
|
||||
./server migrate # 应用嵌入式 migration(用 $DATABASE_URL)
|
||||
./server # 启动 HTTP 服务(默认子命令)
|
||||
./server --help # 列出所有子命令
|
||||
```
|
||||
|
||||
`compose.yaml` models the pattern with a one-shot `migrate` service that `app` depends on (`service_completed_successfully`). On Kubernetes: run `./server migrate` as an initContainer or Helm `pre-upgrade` Job.
|
||||
仓库自带 `compose.yaml`(一次性 `migrate` 服务先跑完,再启动 `app`):
|
||||
|
||||
```bash
|
||||
docker compose up --build
|
||||
```
|
||||
|
||||
Kubernetes 上:把 `./server migrate` 放进 initContainer 或 Helm `pre-upgrade` Job,主容器跑 `./server`。
|
||||
|
||||
## 脚本一览
|
||||
|
||||
| 命令 | 作用 |
|
||||
| --- | --- |
|
||||
| `bun run dev` | Vite 开发服务器(默认端口 3000) |
|
||||
| `bun run build` | 构建到 `.output/`(`bun run compile` 会用到) |
|
||||
| `bun run compile` | 生成单二进制 `out/server-<target>` |
|
||||
| `bun run typecheck` | TypeScript 类型检查 |
|
||||
| `bun run test` | 运行所有 `*.test.ts` |
|
||||
| `bun run fix` | Biome 格式化 + lint + 整理 imports |
|
||||
| `bun run db:push` | 开发期:直接同步 schema 到 DB(不写 migration 文件) |
|
||||
| `bun run db:generate` | 写 SQL migration 到 `./drizzle/` 并嵌入二进制 |
|
||||
| `bun run db:embed` | 仅重生 `migrations.gen.ts`(手改了 `./drizzle/*.sql` 后用) |
|
||||
| `bun run db:migrate` | 通过 drizzle-kit 在本地应用 migration(开发便利) |
|
||||
| `bun run db:studio` | Drizzle Studio(可视化 DB) |
|
||||
|
||||
跨平台编译:`bun run compile:{linux,darwin,windows}[:arch]`。
|
||||
|
||||
## 端点
|
||||
|
||||
| 路径 | 用途 |
|
||||
| --- | --- |
|
||||
| `/` | Todo 示例 UI |
|
||||
| `/health` | 存活探针(不查 DB,纯文本 `ok`) |
|
||||
| `/api/rpc` | ORPC RPC 端点(client 直连) |
|
||||
| `/api/docs` | Scalar 渲染的 API 文档 |
|
||||
| `/api/spec.json` | OpenAPI spec |
|
||||
|
||||
## 提交前
|
||||
|
||||
```bash
|
||||
bun run fix && bun run typecheck && bun run test
|
||||
```
|
||||
|
||||
没有 CI、没有 pre-commit hook——上面三条由你自觉跑。
|
||||
|
||||
+13
@@ -17,6 +17,11 @@
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"domains": {
|
||||
"drizzle": "recommended",
|
||||
"react": "recommended",
|
||||
"types": "all"
|
||||
},
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"complexity": {
|
||||
@@ -24,6 +29,14 @@
|
||||
},
|
||||
"correctness": {
|
||||
"noReactPropAssignments": "error"
|
||||
},
|
||||
"style": {
|
||||
"noNonNullAssertion": "error"
|
||||
},
|
||||
"suspicious": {
|
||||
"noExplicitAny": "error",
|
||||
"noImportCycles": "error",
|
||||
"noTsIgnore": "error"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
"": {
|
||||
"name": "fullstack-starter",
|
||||
"dependencies": {
|
||||
"@logtape/drizzle-orm": "^2.0.5",
|
||||
"@logtape/logtape": "^2.0.5",
|
||||
"@logtape/pretty": "^2.0.5",
|
||||
"@orpc/client": "^1.14.0",
|
||||
"@orpc/contract": "^1.14.0",
|
||||
"@orpc/openapi": "^1.14.0",
|
||||
@@ -13,16 +16,15 @@
|
||||
"@orpc/zod": "^1.14.0",
|
||||
"@t3-oss/env-core": "^0.13.11",
|
||||
"@tanstack/react-query": "^5.100.1",
|
||||
"@tanstack/react-router": "^1.168.23",
|
||||
"@tanstack/react-router": "^1.168.24",
|
||||
"@tanstack/react-router-ssr-query": "^1.166.11",
|
||||
"@tanstack/react-start": "^1.167.43",
|
||||
"@tanstack/react-start": "^1.167.48",
|
||||
"citty": "^0.2.2",
|
||||
"drizzle-orm": "0.45.2",
|
||||
"drizzle-zod": "^0.8.3",
|
||||
"postgres": "^3.4.9",
|
||||
"react": "^19.2.5",
|
||||
"react-dom": "^19.2.5",
|
||||
"uuid": "^14.0.0",
|
||||
"zod": "^4.3.6",
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -35,16 +37,13 @@
|
||||
"@types/bun": "^1.3.13",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"drizzle-kit": "0.31.10",
|
||||
"nitro": "npm:nitro-nightly@3.0.1-20260423-183501-f92fb7b7",
|
||||
"nitro": "npm:nitro-nightly@3.0.1-20260424-182106-f8cf6ccc",
|
||||
"tailwindcss": "^4.2.4",
|
||||
"typescript": "^6.0.3",
|
||||
"vite": "^8.0.10",
|
||||
},
|
||||
},
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"@tanstack/start-plugin-core@1.168.0": "patches/@tanstack%2Fstart-plugin-core@1.168.0.patch",
|
||||
},
|
||||
"packages": {
|
||||
"@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
|
||||
|
||||
@@ -176,6 +175,12 @@
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||
|
||||
"@logtape/drizzle-orm": ["@logtape/drizzle-orm@2.0.5", "", { "peerDependencies": { "@logtape/logtape": "^2.0.5" } }, "sha512-woM4x9J7B4XzoQTY1bzR6uJVlqezn6oY6flV+rDD6lsuwP2llNVvptgCOGjRutDye+6WZldSUIGe7UFK/faBzA=="],
|
||||
|
||||
"@logtape/logtape": ["@logtape/logtape@2.0.5", "", {}, "sha512-UizDkh20ZPJVOddRxG1F77WhHdlNl/sbQgoO8T534R7XvUBMAJ9En9f35u+meW2tRsNLvjz6R87Zanwf53tspQ=="],
|
||||
|
||||
"@logtape/pretty": ["@logtape/pretty@2.0.5", "", { "peerDependencies": { "@logtape/logtape": "^2.0.5" } }, "sha512-jU5pYL0CW0tFmxBS5umMF5VEMq1vXLvkqKrj7KRHnSlb5SrBOSCYl0w4q7FmPPFVADmgTmzVVr6IcIWwK/2Nig=="],
|
||||
|
||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="],
|
||||
|
||||
"@oozcitak/dom": ["@oozcitak/dom@2.0.2", "", { "dependencies": { "@oozcitak/infra": "^2.0.2", "@oozcitak/url": "^3.0.0", "@oozcitak/util": "^10.0.0" } }, "sha512-GjpKhkSYC3Mj4+lfwEyI1dqnsKTgwGy48ytZEhm4A/xnH/8z9M3ZVXKr/YGQi3uCLs1AEBS+x5T2JPiueEDW8w=="],
|
||||
@@ -322,19 +327,19 @@
|
||||
|
||||
"@tanstack/react-query-devtools": ["@tanstack/react-query-devtools@5.100.1", "", { "dependencies": { "@tanstack/query-devtools": "5.100.1" }, "peerDependencies": { "@tanstack/react-query": "^5.100.1", "react": "^18 || ^19" } }, "sha512-JuLinBUl/BlZhm0WVX83fJgE2a3YSbuEdxf3fgP+THg92hX7YfwuH5DzT35a6sL/rifZsPr0yJ9itB6jDOcdRg=="],
|
||||
|
||||
"@tanstack/react-router": ["@tanstack/react-router@1.168.23", "", { "dependencies": { "@tanstack/history": "1.161.6", "@tanstack/react-store": "^0.9.3", "@tanstack/router-core": "1.168.15", "isbot": "^5.1.22" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-+GblieDnutG6oipJJPNtRJjrWF8QTZEG/l0532+BngFkVK48oHNOcvIkSoAFYftK1egAwM7KBxXsb0Ou+X6/MQ=="],
|
||||
"@tanstack/react-router": ["@tanstack/react-router@1.168.24", "", { "dependencies": { "@tanstack/history": "1.161.6", "@tanstack/react-store": "^0.9.3", "@tanstack/router-core": "1.168.16", "isbot": "^5.1.22" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-CQWd9ywDZU6icG65SrjJMzWcNg/ehBKFxfxYssKSuELk0eM9SzJxJ3JBI760kdLXIsXewOX9PHpJDknxC/R5Ag=="],
|
||||
|
||||
"@tanstack/react-router-devtools": ["@tanstack/react-router-devtools@1.166.13", "", { "dependencies": { "@tanstack/router-devtools-core": "1.167.3" }, "peerDependencies": { "@tanstack/react-router": "^1.168.15", "@tanstack/router-core": "^1.168.11", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" }, "optionalPeers": ["@tanstack/router-core"] }, "sha512-6yKRFFJrEEOiGp5RAAuGCYsl81M4XAhJmLcu9PKj+HZle4A3dsP60lwHoqQYWHMK9nKKFkdXR+D8qxzxqtQbEA=="],
|
||||
|
||||
"@tanstack/react-router-ssr-query": ["@tanstack/react-router-ssr-query@1.166.11", "", { "dependencies": { "@tanstack/router-ssr-query-core": "1.167.1" }, "peerDependencies": { "@tanstack/query-core": ">=5.90.0", "@tanstack/react-query": ">=5.90.0", "@tanstack/react-router": ">=1.127.0", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-i81a5avRWgTjSKH5VYttbQ/Y86Il8GIkdcrIlyYUys0Lt1zMCxkTGHH9lBN5ZmhBe3mzwQ+9jOlx9xSxj8Kx0w=="],
|
||||
|
||||
"@tanstack/react-start": ["@tanstack/react-start@1.167.43", "", { "dependencies": { "@tanstack/react-router": "1.168.23", "@tanstack/react-start-client": "1.166.40", "@tanstack/react-start-rsc": "0.0.22", "@tanstack/react-start-server": "1.166.41", "@tanstack/router-utils": "1.161.7", "@tanstack/start-client-core": "1.167.17", "@tanstack/start-plugin-core": "1.168.0", "@tanstack/start-server-core": "1.167.19", "pathe": "^2.0.3" }, "peerDependencies": { "@rsbuild/core": "^2.0.0", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0", "vite": ">=7.0.0" }, "optionalPeers": ["@rsbuild/core"], "bin": { "intent": "bin/intent.js" } }, "sha512-qlrdPuh8nywuN06ypBYm6LahsvQqyZYgP2F4u9XyntXTNfmdEgzjUL6BFSLj8Xq/DQBxfah/chDVtG3lpMuxeQ=="],
|
||||
"@tanstack/react-start": ["@tanstack/react-start@1.167.48", "", { "dependencies": { "@tanstack/react-router": "1.168.24", "@tanstack/react-start-client": "1.166.42", "@tanstack/react-start-rsc": "0.0.27", "@tanstack/react-start-server": "1.166.43", "@tanstack/router-utils": "1.161.7", "@tanstack/start-client-core": "1.167.19", "@tanstack/start-plugin-core": "1.169.4", "@tanstack/start-server-core": "1.167.21", "pathe": "^2.0.3" }, "peerDependencies": { "@rsbuild/core": "^2.0.0", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0", "vite": ">=7.0.0" }, "optionalPeers": ["@rsbuild/core", "vite"], "bin": { "intent": "bin/intent.js" } }, "sha512-DsvzxSywOkdCpmvljT8HPOT9GFGh4p9Uz64yJ+9ixYUyJFQZbvtL/SkUbCyPzp+I0z6SEDd4W60fJPRZ11fk9A=="],
|
||||
|
||||
"@tanstack/react-start-client": ["@tanstack/react-start-client@1.166.40", "", { "dependencies": { "@tanstack/react-router": "1.168.23", "@tanstack/router-core": "1.168.15", "@tanstack/start-client-core": "1.167.17" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-ynjRe8YjaPfcQNEaQ3nE2/zIZNCdyVGew0pHK5lCorqEy3z/YuiKlj5ZXPmel7XGw0XoKsDIH2eXnUtTbIwpjg=="],
|
||||
"@tanstack/react-start-client": ["@tanstack/react-start-client@1.166.42", "", { "dependencies": { "@tanstack/react-router": "1.168.24", "@tanstack/router-core": "1.168.16", "@tanstack/start-client-core": "1.167.19" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-p8i9HNvVz3QnzBMoVqx7qp1HaMzqiNStVpD+cOGlVi93cxx9CVMDZnOECwe5cIXVuAC3WW4+eB5+5vK8VEWdbg=="],
|
||||
|
||||
"@tanstack/react-start-rsc": ["@tanstack/react-start-rsc@0.0.22", "", { "dependencies": { "@tanstack/react-router": "1.168.23", "@tanstack/react-start-server": "1.166.41", "@tanstack/router-core": "1.168.15", "@tanstack/router-utils": "1.161.7", "@tanstack/start-client-core": "1.167.17", "@tanstack/start-fn-stubs": "1.161.6", "@tanstack/start-plugin-core": "1.168.0", "@tanstack/start-server-core": "1.167.19", "@tanstack/start-storage-context": "1.166.29", "pathe": "^2.0.3" }, "peerDependencies": { "@rspack/core": ">=2.0.0-0", "@vitejs/plugin-rsc": ">=0.5.20", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0", "react-server-dom-rspack": ">=0.0.2" }, "optionalPeers": ["@rspack/core", "@vitejs/plugin-rsc", "react-server-dom-rspack"] }, "sha512-+VeicPF2jdErTqSjamDQ+8S2jPqT/hUszLPXdPBqlZSzoUYE5TbXgKlbJK71YUjgpwy7LTLWTIy9B8c+bpBbDg=="],
|
||||
"@tanstack/react-start-rsc": ["@tanstack/react-start-rsc@0.0.27", "", { "dependencies": { "@tanstack/react-router": "1.168.24", "@tanstack/react-start-server": "1.166.43", "@tanstack/router-core": "1.168.16", "@tanstack/router-utils": "1.161.7", "@tanstack/start-client-core": "1.167.19", "@tanstack/start-fn-stubs": "1.161.6", "@tanstack/start-plugin-core": "1.169.4", "@tanstack/start-server-core": "1.167.21", "@tanstack/start-storage-context": "1.166.30", "pathe": "^2.0.3" }, "peerDependencies": { "@rspack/core": ">=2.0.0-0", "@vitejs/plugin-rsc": ">=0.5.20", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0", "react-server-dom-rspack": ">=0.0.2" }, "optionalPeers": ["@rspack/core", "@vitejs/plugin-rsc", "react-server-dom-rspack"] }, "sha512-fSdgoHP5BdxpYknvpz2qOulCowwCSk5MhNpHWRsBJmvQtN4m0bOx8cSg9zbaDduhBDE0Qr6dGu4u4h2X4PJ32Q=="],
|
||||
|
||||
"@tanstack/react-start-server": ["@tanstack/react-start-server@1.166.41", "", { "dependencies": { "@tanstack/history": "1.161.6", "@tanstack/react-router": "1.168.23", "@tanstack/router-core": "1.168.15", "@tanstack/start-client-core": "1.167.17", "@tanstack/start-server-core": "1.167.19" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-Z0kyOeraz5nHE7DYh4brYetYoXvh3wjNNI3fJZ0+OzGODfNUgZtEQg/f1g1f1kj64irgWIuWTVPi3rOwiPSzYw=="],
|
||||
"@tanstack/react-start-server": ["@tanstack/react-start-server@1.166.43", "", { "dependencies": { "@tanstack/history": "1.161.6", "@tanstack/react-router": "1.168.24", "@tanstack/router-core": "1.168.16", "@tanstack/start-client-core": "1.167.19", "@tanstack/start-server-core": "1.167.21" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-tfKFhDCiH8HNCwGFxkiuZsXIi68SHa+5/0i4bO7JCpPzy3FprVdcCJX0Vs0izaNcP8FVKs/TzRAzDilL42Tdlw=="],
|
||||
|
||||
"@tanstack/react-store": ["@tanstack/react-store@0.9.3", "", { "dependencies": { "@tanstack/store": "0.9.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg=="],
|
||||
|
||||
@@ -342,23 +347,23 @@
|
||||
|
||||
"@tanstack/router-devtools-core": ["@tanstack/router-devtools-core@1.167.3", "", { "dependencies": { "clsx": "^2.1.1", "goober": "^2.1.16" }, "peerDependencies": { "@tanstack/router-core": "^1.168.11", "csstype": "^3.0.10" }, "optionalPeers": ["csstype"] }, "sha512-fJ1VMhyQgnoashTrP763c2HRc9kofgF61L7Jb3F6eTHAmCKtGVx8BRtiFt37sr3U0P0jmaaiiSPGP6nT5JtVNg=="],
|
||||
|
||||
"@tanstack/router-generator": ["@tanstack/router-generator@1.166.33", "", { "dependencies": { "@babel/types": "^7.28.5", "@tanstack/router-core": "1.168.15", "@tanstack/router-utils": "1.161.7", "@tanstack/virtual-file-routes": "1.161.7", "magic-string": "^0.30.21", "prettier": "^3.5.0", "tsx": "^4.19.2", "zod": "^3.24.2" } }, "sha512-MXP1WrEaZ13tlO5iJoXC+ZIFHNj5CtcvWuqlAZ3zXY70Musuq+mfUcKWMVdcIstnNqrZl5M2hfqLh5Zf5t4NVw=="],
|
||||
"@tanstack/router-generator": ["@tanstack/router-generator@1.166.34", "", { "dependencies": { "@babel/types": "^7.28.5", "@tanstack/router-core": "1.168.16", "@tanstack/router-utils": "1.161.7", "@tanstack/virtual-file-routes": "1.161.7", "magic-string": "^0.30.21", "prettier": "^3.5.0", "tsx": "^4.19.2", "zod": "^3.24.2" } }, "sha512-VamHGCFqOntz9Ds6oOHtpzXLQg2yhOsXsCXSznLjoDzme3Rk69HvOzXkx9IIpDxo4v64QdvNjeV2IyfXuz5RFw=="],
|
||||
|
||||
"@tanstack/router-plugin": ["@tanstack/router-plugin@1.167.23", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@tanstack/router-core": "1.168.15", "@tanstack/router-generator": "1.166.33", "@tanstack/router-utils": "1.161.7", "@tanstack/virtual-file-routes": "1.161.7", "chokidar": "^3.6.0", "unplugin": "^2.1.2", "zod": "^3.24.2" }, "peerDependencies": { "@rsbuild/core": ">=1.0.2 || ^2.0.0", "@tanstack/react-router": "^1.168.23", "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0", "vite-plugin-solid": "^2.11.10 || ^3.0.0-0", "webpack": ">=5.92.0" }, "optionalPeers": ["@rsbuild/core", "@tanstack/react-router", "vite", "vite-plugin-solid", "webpack"], "bin": { "intent": "bin/intent.js" } }, "sha512-dqfCd8gsZThbVQ8bcYMO62/hW5GCkUoPLnnjOd3fCWoEi+Ei5oWa/GnlgHCpG7bdeGr/K8isnYUmI9Ysq5vLrg=="],
|
||||
"@tanstack/router-plugin": ["@tanstack/router-plugin@1.167.26", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@tanstack/router-core": "1.168.16", "@tanstack/router-generator": "1.166.34", "@tanstack/router-utils": "1.161.7", "@tanstack/virtual-file-routes": "1.161.7", "chokidar": "^3.6.0", "unplugin": "^3.0.0", "zod": "^3.24.2" }, "peerDependencies": { "@rsbuild/core": ">=1.0.2 || ^2.0.0", "@tanstack/react-router": "^1.168.24", "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0", "vite-plugin-solid": "^2.11.10 || ^3.0.0-0", "webpack": ">=5.92.0" }, "optionalPeers": ["@rsbuild/core", "@tanstack/react-router", "vite", "vite-plugin-solid", "webpack"], "bin": { "intent": "bin/intent.js" } }, "sha512-NUbZriBBZDAoTO9ymcnuABbJdl0jMQYpPIa0JVKQn32S1xGWkVhJ8V9xmTPJU3rVdHZYedcmTHFTTadClG389w=="],
|
||||
|
||||
"@tanstack/router-ssr-query-core": ["@tanstack/router-ssr-query-core@1.167.1", "", { "peerDependencies": { "@tanstack/query-core": ">=5.90.0", "@tanstack/router-core": ">=1.127.0" } }, "sha512-sJNRHa36lfuHw04akO9C6KU1P1Ncam2Azsk5XlgdQHMFgOtSlFAsuwqAHpyYSwu5Jyxj6P3PmyKYMIm4u8dI7Q=="],
|
||||
|
||||
"@tanstack/router-utils": ["@tanstack/router-utils@1.161.7", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/generator": "^7.28.5", "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "ansis": "^4.1.0", "babel-dead-code-elimination": "^1.0.12", "diff": "^8.0.2", "pathe": "^2.0.3", "tinyglobby": "^0.2.15" } }, "sha512-VkY0u7ax/GD0qU6ZLLnfPC+UMxVzxRbvZp4yV4iUSXjgJZ/siAT5/QlLm9FEDJ9QDoC0VD9W7f00tKKreUI7Ng=="],
|
||||
|
||||
"@tanstack/start-client-core": ["@tanstack/start-client-core@1.167.17", "", { "dependencies": { "@tanstack/router-core": "1.168.15", "@tanstack/start-fn-stubs": "1.161.6", "@tanstack/start-storage-context": "1.166.29", "seroval": "^1.5.0" }, "bin": { "intent": "bin/intent.js" } }, "sha512-3ZnpQ0LPnhrm/GX+HT7XfRxTcqnmBE1KJd7LtaJNuN13NH0C4ZOWchKLPEed2/gluhgsT6UgWm+Ec0kEFtxSaw=="],
|
||||
"@tanstack/start-client-core": ["@tanstack/start-client-core@1.167.19", "", { "dependencies": { "@tanstack/router-core": "1.168.16", "@tanstack/start-fn-stubs": "1.161.6", "@tanstack/start-storage-context": "1.166.30", "seroval": "^1.5.0" }, "bin": { "intent": "bin/intent.js" } }, "sha512-lyzA0hzSRKN+vl6093kX8GXnI4XVPDX5x5siWn0CGfmw4/+8l5dRU4Q7QVeWy7xu6JAZCV/gzdDs+nIm50YK7Q=="],
|
||||
|
||||
"@tanstack/start-fn-stubs": ["@tanstack/start-fn-stubs@1.161.6", "", {}, "sha512-Y6QSlGiLga8cHfvxGGaonXIlt2bIUTVdH6AMjmpMp7+ANNCp+N96GQbjjhLye3JkaxDfP68x5iZA8NK4imgRig=="],
|
||||
|
||||
"@tanstack/start-plugin-core": ["@tanstack/start-plugin-core@1.168.0", "", { "dependencies": { "@babel/code-frame": "7.27.1", "@babel/core": "^7.28.5", "@babel/types": "^7.28.5", "@rolldown/pluginutils": "1.0.0-beta.40", "@tanstack/router-core": "1.168.15", "@tanstack/router-generator": "1.166.33", "@tanstack/router-plugin": "1.167.23", "@tanstack/router-utils": "1.161.7", "@tanstack/start-client-core": "1.167.17", "@tanstack/start-server-core": "1.167.19", "cheerio": "^1.0.0", "exsolve": "^1.0.7", "pathe": "^2.0.3", "picomatch": "^4.0.3", "seroval": "^1.5.0", "source-map": "^0.7.6", "srvx": "^0.11.9", "tinyglobby": "^0.2.15", "ufo": "^1.5.4", "vitefu": "^1.1.1", "xmlbuilder2": "^4.0.3", "zod": "^3.24.2" }, "peerDependencies": { "@rsbuild/core": "^2.0.0", "vite": ">=7.0.0" }, "optionalPeers": ["@rsbuild/core"] }, "sha512-+pxvYYD6othWDuIKqXgi+yHx1i3iQYD2wWa0m0gKmuOmDQecA9laMJZGOjETREmQrHvUzXixwIbEnXlLv8Nlzg=="],
|
||||
"@tanstack/start-plugin-core": ["@tanstack/start-plugin-core@1.169.4", "", { "dependencies": { "@babel/code-frame": "7.27.1", "@babel/core": "^7.28.5", "@babel/types": "^7.28.5", "@rolldown/pluginutils": "1.0.0-beta.40", "@tanstack/router-core": "1.168.16", "@tanstack/router-generator": "1.166.34", "@tanstack/router-plugin": "1.167.26", "@tanstack/router-utils": "1.161.7", "@tanstack/start-client-core": "1.167.19", "@tanstack/start-server-core": "1.167.21", "cheerio": "^1.0.0", "exsolve": "^1.0.7", "lightningcss": "^1.32.0", "pathe": "^2.0.3", "picomatch": "^4.0.3", "seroval": "^1.5.0", "source-map": "^0.7.6", "srvx": "^0.11.9", "tinyglobby": "^0.2.15", "ufo": "^1.5.4", "vitefu": "^1.1.1", "xmlbuilder2": "^4.0.3", "zod": "^3.24.2" }, "peerDependencies": { "@rsbuild/core": "^2.0.0", "vite": ">=7.0.0" }, "optionalPeers": ["@rsbuild/core", "vite"] }, "sha512-HjMhhC5V96DD8TcM2aQ5VNGom2mVKZSWN4WGC0/UWMIpnLTiVdvd88XDg2MwsZxIh8Zlye5u/OMa6bC6mHgd7A=="],
|
||||
|
||||
"@tanstack/start-server-core": ["@tanstack/start-server-core@1.167.19", "", { "dependencies": { "@tanstack/history": "1.161.6", "@tanstack/router-core": "1.168.15", "@tanstack/start-client-core": "1.167.17", "@tanstack/start-storage-context": "1.166.29", "h3-v2": "npm:h3@2.0.1-rc.20", "seroval": "^1.5.0" }, "bin": { "intent": "bin/intent.js" } }, "sha512-wzOdfzLsK91CnjoywnEjXSlVlaRVK99HJhyVijNU1TECBI2JEKvW9S6d14YfS4gD4fFH4V86tFYhkcLPe6nzWg=="],
|
||||
"@tanstack/start-server-core": ["@tanstack/start-server-core@1.167.21", "", { "dependencies": { "@tanstack/history": "1.161.6", "@tanstack/router-core": "1.168.16", "@tanstack/start-client-core": "1.167.19", "@tanstack/start-storage-context": "1.166.30", "h3-v2": "npm:h3@2.0.1-rc.20", "seroval": "^1.5.0" }, "bin": { "intent": "bin/intent.js" } }, "sha512-G1tWw4lCCjPhyzUT+kMf2NyBS9i5mTlHoElvQBRYu2TvYbUStqnDhEha5apKWkuQWSucw2lM9V61JHucMFsZwg=="],
|
||||
|
||||
"@tanstack/start-storage-context": ["@tanstack/start-storage-context@1.166.29", "", { "dependencies": { "@tanstack/router-core": "1.168.15" } }, "sha512-KrJYudc1nbnTY43jdN+hQFMYkhz7+3T+hkgBoGnIP1OspSe6vGQaYGDB4EUXYnkLfyQp+iUuKubgS8hSKeJ0ng=="],
|
||||
"@tanstack/start-storage-context": ["@tanstack/start-storage-context@1.166.30", "", { "dependencies": { "@tanstack/router-core": "1.168.16" } }, "sha512-2hnpn1n2ls6NbsviNSLl/iD5y+WK45XGFgHDMUnzsUMEf5jXKUoQNSmmcjh+Lhtg+Ag7XOwOs4utXvm0OkevMQ=="],
|
||||
|
||||
"@tanstack/store": ["@tanstack/store@0.9.3", "", {}, "sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw=="],
|
||||
|
||||
@@ -376,8 +381,6 @@
|
||||
|
||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.1", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.7" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ=="],
|
||||
|
||||
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
|
||||
|
||||
"ansis": ["ansis@4.2.0", "", {}, "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig=="],
|
||||
|
||||
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
|
||||
@@ -558,7 +561,7 @@
|
||||
|
||||
"nf3": ["nf3@0.3.16", "", {}, "sha512-Gs0xRPpUm2nDkqbi40NJ9g7qDIcjcJzgExiydnq6LAyqhI2jfno8wG3NKTL+IiJsx799UHOb1CnSd4Wg4SG4Pw=="],
|
||||
|
||||
"nitro": ["nitro-nightly@3.0.1-20260423-183501-f92fb7b7", "", { "dependencies": { "consola": "^3.4.2", "crossws": "^0.4.5", "db0": "^0.3.4", "env-runner": "^0.1.7", "h3": "^2.0.1-rc.20", "hookable": "^6.1.1", "nf3": "^0.3.16", "ocache": "^0.1.4", "ofetch": "^2.0.0-alpha.3", "ohash": "^2.0.11", "rolldown": "^1.0.0-rc.17", "srvx": "^0.11.15", "unenv": "^2.0.0-rc.24", "unstorage": "^2.0.0-alpha.7" }, "peerDependencies": { "@vercel/queue": "^0.1.6", "dotenv": "*", "giget": "*", "jiti": "^2.6.1", "rollup": "^4.60.2", "vite": "^7 || ^8", "xml2js": "^0.6.2", "zephyr-agent": "^0.2.0" }, "optionalPeers": ["@vercel/queue", "dotenv", "giget", "jiti", "rollup", "vite", "xml2js", "zephyr-agent"], "bin": { "nitro": "dist/cli/index.mjs" } }, "sha512-KAR36bmbCyR7EMBbAaJJLtOSTGzpBB0sW3custX1tH8VtTz4q8xiRop+lMkmL2c+KDSAfkUdEECUwotVLQlKMw=="],
|
||||
"nitro": ["nitro-nightly@3.0.1-20260424-182106-f8cf6ccc", "", { "dependencies": { "consola": "^3.4.2", "crossws": "^0.4.5", "db0": "^0.3.4", "env-runner": "^0.1.7", "h3": "^2.0.1-rc.20", "hookable": "^6.1.1", "nf3": "^0.3.16", "ocache": "^0.1.4", "ofetch": "^2.0.0-alpha.3", "ohash": "^2.0.11", "rolldown": "^1.0.0-rc.17", "srvx": "^0.11.15", "unenv": "^2.0.0-rc.24", "unstorage": "^2.0.0-alpha.7" }, "peerDependencies": { "@vercel/queue": "^0.1.6", "dotenv": "*", "giget": "*", "jiti": "^2.6.1", "rollup": "^4.60.2", "vite": "^7 || ^8", "xml2js": "^0.6.2", "zephyr-agent": "^0.2.0" }, "optionalPeers": ["@vercel/queue", "dotenv", "giget", "jiti", "rollup", "vite", "xml2js", "zephyr-agent"], "bin": { "nitro": "dist/cli/index.mjs" } }, "sha512-NngKuHxMQ7CApL2QwKBLNvnmYEtL0ItmXORa1sUPb6XGrIXUhW28gLX9PwXsymLUBhNS10VTJ/NweAnycJSmGQ=="],
|
||||
|
||||
"node-releases": ["node-releases@2.0.38", "", {}, "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw=="],
|
||||
|
||||
@@ -654,7 +657,7 @@
|
||||
|
||||
"unenv": ["unenv@2.0.0-rc.24", "", { "dependencies": { "pathe": "^2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="],
|
||||
|
||||
"unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="],
|
||||
"unplugin": ["unplugin@3.0.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg=="],
|
||||
|
||||
"unstorage": ["unstorage@2.0.0-alpha.7", "", { "peerDependencies": { "@azure/app-configuration": "^1.11.0", "@azure/cosmos": "^4.9.1", "@azure/data-tables": "^13.3.2", "@azure/identity": "^4.13.0", "@azure/keyvault-secrets": "^4.10.0", "@azure/storage-blob": "^12.31.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.13.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.36.2", "@vercel/blob": ">=0.27.3", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1.0.1", "aws4fetch": "^1.0.20", "chokidar": "^4 || ^5", "db0": ">=0.3.4", "idb-keyval": "^6.2.2", "ioredis": "^5.9.3", "lru-cache": "^11.2.6", "mongodb": "^6 || ^7", "ofetch": "*", "uploadthing": "^7.7.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "chokidar", "db0", "idb-keyval", "ioredis", "lru-cache", "mongodb", "ofetch", "uploadthing"] }, "sha512-ELPztchk2zgFJnakyodVY3vJWGW9jy//keJ32IOJVGUMyaPydwcA1FtVvWqT0TNRch9H+cMNEGllfVFfScImog=="],
|
||||
|
||||
@@ -662,8 +665,6 @@
|
||||
|
||||
"use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
|
||||
|
||||
"uuid": ["uuid@14.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg=="],
|
||||
|
||||
"vite": ["vite@8.0.10", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.10", "rolldown": "1.0.0-rc.17", "tinyglobby": "^0.2.16" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw=="],
|
||||
|
||||
"vitefu": ["vitefu@1.1.3", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["vite"] }, "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg=="],
|
||||
@@ -698,16 +699,36 @@
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"@tanstack/react-router/@tanstack/router-core": ["@tanstack/router-core@1.168.16", "", { "dependencies": { "@tanstack/history": "1.161.6", "cookie-es": "^3.0.0", "seroval": "^1.5.0", "seroval-plugins": "^1.5.0" }, "bin": { "intent": "bin/intent.js" } }, "sha512-2lkWNMzDWWxVqTf9Y54DH1ceZOrGrOGzx9CFVMq8gQSOxhr3x3+otjVei1Rlx4xmsCc4yCqccqjun5wDtE9MZA=="],
|
||||
|
||||
"@tanstack/react-start-client/@tanstack/router-core": ["@tanstack/router-core@1.168.16", "", { "dependencies": { "@tanstack/history": "1.161.6", "cookie-es": "^3.0.0", "seroval": "^1.5.0", "seroval-plugins": "^1.5.0" }, "bin": { "intent": "bin/intent.js" } }, "sha512-2lkWNMzDWWxVqTf9Y54DH1ceZOrGrOGzx9CFVMq8gQSOxhr3x3+otjVei1Rlx4xmsCc4yCqccqjun5wDtE9MZA=="],
|
||||
|
||||
"@tanstack/react-start-rsc/@tanstack/router-core": ["@tanstack/router-core@1.168.16", "", { "dependencies": { "@tanstack/history": "1.161.6", "cookie-es": "^3.0.0", "seroval": "^1.5.0", "seroval-plugins": "^1.5.0" }, "bin": { "intent": "bin/intent.js" } }, "sha512-2lkWNMzDWWxVqTf9Y54DH1ceZOrGrOGzx9CFVMq8gQSOxhr3x3+otjVei1Rlx4xmsCc4yCqccqjun5wDtE9MZA=="],
|
||||
|
||||
"@tanstack/react-start-server/@tanstack/router-core": ["@tanstack/router-core@1.168.16", "", { "dependencies": { "@tanstack/history": "1.161.6", "cookie-es": "^3.0.0", "seroval": "^1.5.0", "seroval-plugins": "^1.5.0" }, "bin": { "intent": "bin/intent.js" } }, "sha512-2lkWNMzDWWxVqTf9Y54DH1ceZOrGrOGzx9CFVMq8gQSOxhr3x3+otjVei1Rlx4xmsCc4yCqccqjun5wDtE9MZA=="],
|
||||
|
||||
"@tanstack/router-generator/@tanstack/router-core": ["@tanstack/router-core@1.168.16", "", { "dependencies": { "@tanstack/history": "1.161.6", "cookie-es": "^3.0.0", "seroval": "^1.5.0", "seroval-plugins": "^1.5.0" }, "bin": { "intent": "bin/intent.js" } }, "sha512-2lkWNMzDWWxVqTf9Y54DH1ceZOrGrOGzx9CFVMq8gQSOxhr3x3+otjVei1Rlx4xmsCc4yCqccqjun5wDtE9MZA=="],
|
||||
|
||||
"@tanstack/router-generator/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"@tanstack/router-plugin/@tanstack/router-core": ["@tanstack/router-core@1.168.16", "", { "dependencies": { "@tanstack/history": "1.161.6", "cookie-es": "^3.0.0", "seroval": "^1.5.0", "seroval-plugins": "^1.5.0" }, "bin": { "intent": "bin/intent.js" } }, "sha512-2lkWNMzDWWxVqTf9Y54DH1ceZOrGrOGzx9CFVMq8gQSOxhr3x3+otjVei1Rlx4xmsCc4yCqccqjun5wDtE9MZA=="],
|
||||
|
||||
"@tanstack/router-plugin/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"@tanstack/start-client-core/@tanstack/router-core": ["@tanstack/router-core@1.168.16", "", { "dependencies": { "@tanstack/history": "1.161.6", "cookie-es": "^3.0.0", "seroval": "^1.5.0", "seroval-plugins": "^1.5.0" }, "bin": { "intent": "bin/intent.js" } }, "sha512-2lkWNMzDWWxVqTf9Y54DH1ceZOrGrOGzx9CFVMq8gQSOxhr3x3+otjVei1Rlx4xmsCc4yCqccqjun5wDtE9MZA=="],
|
||||
|
||||
"@tanstack/start-plugin-core/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
|
||||
|
||||
"@tanstack/start-plugin-core/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.40", "", {}, "sha512-s3GeJKSQOwBlzdUrj4ISjJj5SfSh+aqn0wjOar4Bx95iV1ETI7F6S/5hLcfAxZ9kXDcyrAkxPlqmd1ZITttf+w=="],
|
||||
|
||||
"@tanstack/start-plugin-core/@tanstack/router-core": ["@tanstack/router-core@1.168.16", "", { "dependencies": { "@tanstack/history": "1.161.6", "cookie-es": "^3.0.0", "seroval": "^1.5.0", "seroval-plugins": "^1.5.0" }, "bin": { "intent": "bin/intent.js" } }, "sha512-2lkWNMzDWWxVqTf9Y54DH1ceZOrGrOGzx9CFVMq8gQSOxhr3x3+otjVei1Rlx4xmsCc4yCqccqjun5wDtE9MZA=="],
|
||||
|
||||
"@tanstack/start-plugin-core/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"@tanstack/start-server-core/@tanstack/router-core": ["@tanstack/router-core@1.168.16", "", { "dependencies": { "@tanstack/history": "1.161.6", "cookie-es": "^3.0.0", "seroval": "^1.5.0", "seroval-plugins": "^1.5.0" }, "bin": { "intent": "bin/intent.js" } }, "sha512-2lkWNMzDWWxVqTf9Y54DH1ceZOrGrOGzx9CFVMq8gQSOxhr3x3+otjVei1Rlx4xmsCc4yCqccqjun5wDtE9MZA=="],
|
||||
|
||||
"@tanstack/start-storage-context/@tanstack/router-core": ["@tanstack/router-core@1.168.16", "", { "dependencies": { "@tanstack/history": "1.161.6", "cookie-es": "^3.0.0", "seroval": "^1.5.0", "seroval-plugins": "^1.5.0" }, "bin": { "intent": "bin/intent.js" } }, "sha512-2lkWNMzDWWxVqTf9Y54DH1ceZOrGrOGzx9CFVMq8gQSOxhr3x3+otjVei1Rlx4xmsCc4yCqccqjun5wDtE9MZA=="],
|
||||
|
||||
"anymatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
|
||||
|
||||
"h3/rou3": ["rou3@0.8.1", "", {}, "sha512-ePa+XGk00/3HuCqrEnK3LxJW7I0SdNg6EFzKUJG73hMAdDcOUC/i/aSz7LSDwLrGr33kal/rqOGydzwl6U7zBA=="],
|
||||
|
||||
+20
-16
@@ -3,20 +3,25 @@
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"imports": {
|
||||
"#drizzle/*.sql": "./drizzle/*.sql",
|
||||
"#package": "./package.json",
|
||||
"#server": "./.output/server/index.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "bunx --bun vite build",
|
||||
"cli": "bun bin.ts",
|
||||
"compile": "bun compile.ts",
|
||||
"cli": "bun src/bin.ts",
|
||||
"compile": "bun scripts/compile.ts",
|
||||
"compile:darwin": "bun run compile:darwin:arm64 && bun run compile:darwin:x64",
|
||||
"compile:darwin:arm64": "bun compile.ts --target bun-darwin-arm64",
|
||||
"compile:darwin:x64": "bun compile.ts --target bun-darwin-x64",
|
||||
"compile:darwin:arm64": "bun scripts/compile.ts --target bun-darwin-arm64",
|
||||
"compile:darwin:x64": "bun scripts/compile.ts --target bun-darwin-x64",
|
||||
"compile:linux": "bun run compile:linux:x64 && bun run compile:linux:arm64",
|
||||
"compile:linux:arm64": "bun compile.ts --target bun-linux-arm64",
|
||||
"compile:linux:x64": "bun compile.ts --target bun-linux-x64",
|
||||
"compile:linux:arm64": "bun scripts/compile.ts --target bun-linux-arm64",
|
||||
"compile:linux:x64": "bun scripts/compile.ts --target bun-linux-x64",
|
||||
"compile:windows": "bun run compile:windows:x64",
|
||||
"compile:windows:x64": "bun compile.ts --target bun-windows-x64",
|
||||
"db:generate": "drizzle-kit generate && bun embed-migrations.ts",
|
||||
"db:embed": "bun embed-migrations.ts",
|
||||
"compile:windows:x64": "bun scripts/compile.ts --target bun-windows-x64",
|
||||
"db:embed": "bun scripts/embed-migrations.ts",
|
||||
"db:generate": "drizzle-kit generate && bun scripts/embed-migrations.ts",
|
||||
"db:migrate": "drizzle-kit migrate",
|
||||
"db:push": "drizzle-kit push",
|
||||
"db:studio": "drizzle-kit studio",
|
||||
@@ -26,6 +31,9 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@logtape/drizzle-orm": "^2.0.5",
|
||||
"@logtape/logtape": "^2.0.5",
|
||||
"@logtape/pretty": "^2.0.5",
|
||||
"@orpc/client": "^1.14.0",
|
||||
"@orpc/contract": "^1.14.0",
|
||||
"@orpc/openapi": "^1.14.0",
|
||||
@@ -34,16 +42,15 @@
|
||||
"@orpc/zod": "^1.14.0",
|
||||
"@t3-oss/env-core": "^0.13.11",
|
||||
"@tanstack/react-query": "^5.100.1",
|
||||
"@tanstack/react-router": "^1.168.23",
|
||||
"@tanstack/react-router": "^1.168.24",
|
||||
"@tanstack/react-router-ssr-query": "^1.166.11",
|
||||
"@tanstack/react-start": "^1.167.43",
|
||||
"@tanstack/react-start": "^1.167.48",
|
||||
"citty": "^0.2.2",
|
||||
"drizzle-orm": "0.45.2",
|
||||
"drizzle-zod": "^0.8.3",
|
||||
"postgres": "^3.4.9",
|
||||
"react": "^19.2.5",
|
||||
"react-dom": "^19.2.5",
|
||||
"uuid": "^14.0.0",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -56,12 +63,9 @@
|
||||
"@types/bun": "^1.3.13",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"drizzle-kit": "0.31.10",
|
||||
"nitro": "npm:nitro-nightly@3.0.1-20260423-183501-f92fb7b7",
|
||||
"nitro": "npm:nitro-nightly@3.0.1-20260424-182106-f8cf6ccc",
|
||||
"tailwindcss": "^4.2.4",
|
||||
"typescript": "^6.0.3",
|
||||
"vite": "^8.0.10"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"@tanstack/start-plugin-core@1.168.0": "patches/@tanstack%2Fstart-plugin-core@1.168.0.patch"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
diff --git a/dist/esm/index.js b/dist/esm/index.js
|
||||
index 5a5e23586d76ffac58ca95cafa46759d39be68d0..32db5e8d38c11bb430326ed6c1b4fd9a944b49f2 100644
|
||||
--- a/dist/esm/index.js
|
||||
+++ b/dist/esm/index.js
|
||||
@@ -1,6 +1,4 @@
|
||||
import { START_ENVIRONMENT_NAMES, VITE_ENVIRONMENT_NAMES } from "./constants.js";
|
||||
import { createVirtualModule } from "./vite/createVirtualModule.js";
|
||||
import { tanStackStartVite } from "./vite/plugin.js";
|
||||
-import { RSBUILD_ENVIRONMENT_NAMES } from "./rsbuild/planning.js";
|
||||
-import { tanStackStartRsbuild } from "./rsbuild/plugin.js";
|
||||
-export { RSBUILD_ENVIRONMENT_NAMES, START_ENVIRONMENT_NAMES, VITE_ENVIRONMENT_NAMES, createVirtualModule, tanStackStartRsbuild, tanStackStartVite };
|
||||
+export { START_ENVIRONMENT_NAMES, VITE_ENVIRONMENT_NAMES, createVirtualModule, tanStackStartVite };
|
||||
@@ -1,7 +1,8 @@
|
||||
import { mkdir, rm } from 'node:fs/promises'
|
||||
import { basename } from 'node:path'
|
||||
import { parseArgs } from 'node:util'
|
||||
|
||||
const ENTRYPOINT = 'bin.ts'
|
||||
const ENTRYPOINT = 'src/bin.ts'
|
||||
const OUTDIR = 'out'
|
||||
|
||||
const SUPPORTED_TARGETS: readonly Bun.Build.CompileTarget[] = [
|
||||
@@ -12,8 +13,9 @@ const SUPPORTED_TARGETS: readonly Bun.Build.CompileTarget[] = [
|
||||
'bun-linux-arm64',
|
||||
]
|
||||
|
||||
const isSupportedTarget = (value: string): value is Bun.Build.CompileTarget =>
|
||||
(SUPPORTED_TARGETS as readonly string[]).includes(value)
|
||||
const SUPPORTED_TARGET_SET: ReadonlySet<string> = new Set(SUPPORTED_TARGETS)
|
||||
|
||||
const isSupportedTarget = (value: string): value is Bun.Build.CompileTarget => SUPPORTED_TARGET_SET.has(value)
|
||||
|
||||
const { values } = parseArgs({
|
||||
options: { target: { type: 'string' } },
|
||||
@@ -48,7 +50,8 @@ const main = async () => {
|
||||
const result = await Bun.build({
|
||||
entrypoints: [ENTRYPOINT],
|
||||
outdir: OUTDIR,
|
||||
compile: { outfile, target },
|
||||
// autoloadDotenv: false — produce a deterministic binary; it must not silently consume a .env from cwd.
|
||||
compile: { outfile, target, autoloadDotenv: false },
|
||||
minify: true,
|
||||
bytecode: true,
|
||||
sourcemap: 'inline',
|
||||
@@ -59,7 +62,7 @@ const main = async () => {
|
||||
}
|
||||
|
||||
// Bun bundler still writes *.js.map next to the binary even with inline sourcemap.
|
||||
await rm(`${OUTDIR}/${ENTRYPOINT.replace(/\.ts$/, '')}.js.map`, { force: true })
|
||||
await rm(`${OUTDIR}/${basename(ENTRYPOINT, '.ts')}.js.map`, { force: true })
|
||||
|
||||
console.log(`✓ ${target} → ${OUTDIR}/${outfile}`)
|
||||
}
|
||||
@@ -1,20 +1,33 @@
|
||||
import { existsSync } from 'node:fs'
|
||||
import { readFile, writeFile } from 'node:fs/promises'
|
||||
import { z } from 'zod'
|
||||
|
||||
const JOURNAL = './drizzle/meta/_journal.json'
|
||||
const OUTPUT = './src/server/db/migrations.gen.ts'
|
||||
const SQL_RELATIVE_FROM_OUTPUT = '../../../drizzle'
|
||||
|
||||
type JournalEntry = { idx: number; tag: string; when: number; breakpoints: boolean }
|
||||
type Journal = { entries: JournalEntry[] }
|
||||
const journalEntrySchema = z.object({
|
||||
idx: z.number().int().nonnegative(),
|
||||
tag: z.string().regex(/^\d{4}_[a-z0-9_]+$/),
|
||||
when: z.number().int().nonnegative(),
|
||||
breakpoints: z.boolean(),
|
||||
})
|
||||
const journalSchema = z.object({ entries: z.array(journalEntrySchema).default([]) })
|
||||
|
||||
type JournalEntry = z.infer<typeof journalEntrySchema>
|
||||
|
||||
const readJournalEntries = async (): Promise<JournalEntry[]> => {
|
||||
if (!existsSync(JOURNAL)) {
|
||||
return []
|
||||
}
|
||||
const raw: unknown = JSON.parse(await readFile(JOURNAL, 'utf-8'))
|
||||
return journalSchema.parse(raw).entries.sort((a, b) => a.idx - b.idx)
|
||||
}
|
||||
|
||||
const main = async () => {
|
||||
const entries: JournalEntry[] = existsSync(JOURNAL)
|
||||
? ((JSON.parse(await readFile(JOURNAL, 'utf-8')) as Journal).entries ?? []).sort((a, b) => a.idx - b.idx)
|
||||
: []
|
||||
const entries = await readJournalEntries()
|
||||
|
||||
const imports = entries
|
||||
.map((e) => `import sql_${e.idx} from '${SQL_RELATIVE_FROM_OUTPUT}/${e.tag}.sql' with { type: 'text' }`)
|
||||
.map((e) => `import sql_${e.idx} from '#drizzle/${e.tag}.sql' with { type: 'text' }`)
|
||||
.join('\n')
|
||||
|
||||
const arrayBody = entries.length
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
import { defineCommand, runMain } from 'citty'
|
||||
import { name, version } from './package.json' with { type: 'json' }
|
||||
import { name, version } from '#package'
|
||||
|
||||
// IMPORTANT: keep this file's static imports minimal. Nitro's bun preset
|
||||
// emits `.output/server/index.mjs` with a top-level `serve(...)` call, so any
|
||||
@@ -13,8 +13,8 @@ const main = defineCommand({
|
||||
},
|
||||
default: 'serve',
|
||||
subCommands: {
|
||||
serve: () => import('./src/cli/serve').then((m) => m.default),
|
||||
migrate: () => import('./src/cli/migrate').then((m) => m.default),
|
||||
serve: () => import('@/cli/serve').then((m) => m.default),
|
||||
migrate: () => import('@/cli/migrate').then((m) => m.default),
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export default async function startNitroServer() {
|
||||
await import('../../.output/server/index.mjs')
|
||||
await import('#server')
|
||||
}
|
||||
|
||||
+39
-15
@@ -6,20 +6,30 @@ export default defineCommand({
|
||||
description: 'Apply pending database migrations',
|
||||
},
|
||||
async run() {
|
||||
const [{ env }, { drizzle }, { sql }, { embeddedMigrations }, { createHash }] = await Promise.all([
|
||||
const [{ env }, { drizzle }, { sql }, { embeddedMigrations }, { getLogger }] = await Promise.all([
|
||||
import('@/env'),
|
||||
import('drizzle-orm/postgres-js'),
|
||||
import('drizzle-orm'),
|
||||
import('@/server/db/migrations.gen'),
|
||||
import('node:crypto'),
|
||||
import('@/server/logger'),
|
||||
])
|
||||
|
||||
const logger = getLogger(['cli', 'migrate'])
|
||||
|
||||
if (embeddedMigrations.length === 0) {
|
||||
console.log('No migrations bundled into this binary.')
|
||||
logger.info('No migrations bundled into this binary.')
|
||||
return
|
||||
}
|
||||
|
||||
const db = drizzle({ connection: { url: env.DATABASE_URL, max: 1, onnotice: () => {} } })
|
||||
const sha256 = (s: string) => Bun.CryptoHasher.hash('sha256', s, 'hex')
|
||||
|
||||
const db = drizzle({
|
||||
connection: {
|
||||
url: env.DATABASE_URL,
|
||||
max: 1,
|
||||
onnotice: (n) => logger.debug('pg notice', { notice: n.message }),
|
||||
},
|
||||
})
|
||||
try {
|
||||
await db.execute(sql`CREATE SCHEMA IF NOT EXISTS "drizzle"`)
|
||||
await db.execute(sql`
|
||||
@@ -29,30 +39,44 @@ export default defineCommand({
|
||||
created_at bigint
|
||||
)
|
||||
`)
|
||||
const last = await db.execute<{ created_at: string | null }>(
|
||||
sql`SELECT created_at FROM "drizzle"."__drizzle_migrations" ORDER BY created_at DESC LIMIT 1`,
|
||||
)
|
||||
const lastMillis = Number(last[0]?.created_at ?? 0)
|
||||
|
||||
const pending = embeddedMigrations.filter((m) => m.when > lastMillis)
|
||||
const applied = await db.execute<{ hash: string; created_at: string | null }>(
|
||||
sql`SELECT hash, created_at FROM "drizzle"."__drizzle_migrations" ORDER BY created_at ASC`,
|
||||
)
|
||||
|
||||
// Reject schema drift: any applied migration whose embedded SQL has changed (or is missing) is fatal.
|
||||
for (const row of applied) {
|
||||
const when = Number(row.created_at)
|
||||
const m = embeddedMigrations.find((e) => e.when === when)
|
||||
if (!m) {
|
||||
throw new Error(`Applied migration when=${when} is not in this binary; do not roll back applied migrations.`)
|
||||
}
|
||||
if (sha256(m.sql) !== row.hash) {
|
||||
throw new Error(`Migration hash mismatch at when=${when}; do not edit migrations after they are applied.`)
|
||||
}
|
||||
}
|
||||
|
||||
const appliedWhens = new Set(applied.map((r) => Number(r.created_at)))
|
||||
const pending = embeddedMigrations.filter((m) => !appliedWhens.has(m.when))
|
||||
if (pending.length === 0) {
|
||||
console.log('Database is up to date.')
|
||||
logger.info('Database is up to date.')
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`Applying ${pending.length} migration(s)...`)
|
||||
logger.info('Applying {count} migration(s)...', { count: pending.length })
|
||||
await db.transaction(async (tx) => {
|
||||
for (const m of pending) {
|
||||
for (const stmt of m.sql.split('--> statement-breakpoint')) {
|
||||
for (const rawStmt of m.sql.split('--> statement-breakpoint')) {
|
||||
const stmt = rawStmt.trim()
|
||||
if (!stmt) continue
|
||||
await tx.execute(sql.raw(stmt))
|
||||
}
|
||||
const hash = createHash('sha256').update(m.sql).digest('hex')
|
||||
await tx.execute(
|
||||
sql`INSERT INTO "drizzle"."__drizzle_migrations" ("hash", "created_at") VALUES (${hash}, ${m.when})`,
|
||||
sql`INSERT INTO "drizzle"."__drizzle_migrations" ("hash", "created_at") VALUES (${sha256(m.sql)}, ${m.when})`,
|
||||
)
|
||||
}
|
||||
})
|
||||
console.log('Migrations applied.')
|
||||
logger.info('Migrations applied.')
|
||||
} finally {
|
||||
await db.$client.end()
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export const ErrorComponent = ({ error, reset }: { error: Error; reset: () => vo
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-slate-900">出错了</h1>
|
||||
<p className="text-slate-500 mt-2">{error.message}</p>
|
||||
<p className="text-slate-500 mt-2">{import.meta.env.DEV ? error.message : '请求失败,请稍后重试'}</p>
|
||||
</div>
|
||||
<div className="flex items-center justify-center gap-4">
|
||||
<button
|
||||
|
||||
+4
-1
@@ -3,7 +3,10 @@ import { z } from 'zod'
|
||||
|
||||
export const env = createEnv({
|
||||
server: {
|
||||
DATABASE_URL: z.url(),
|
||||
DATABASE_URL: z.url({ protocol: /^postgres(ql)?$/ }),
|
||||
LOG_DB: z.stringbool().default(false),
|
||||
LOG_FORMAT: z.enum(['pretty', 'json']).optional(),
|
||||
LOG_LEVEL: z.enum(['trace', 'debug', 'info', 'warning', 'error', 'fatal']).default('info'),
|
||||
},
|
||||
clientPrefix: 'VITE_',
|
||||
client: {},
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools'
|
||||
import { createRootRouteWithContext, HeadContent, Scripts } from '@tanstack/react-router'
|
||||
import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'
|
||||
import type { ReactNode } from 'react'
|
||||
import { name } from '@/../package.json'
|
||||
import { name } from '#package'
|
||||
import { ErrorComponent } from '@/components/Error'
|
||||
import { NotFoundComponent } from '@/components/NotFound'
|
||||
import appCss from '@/styles.css?url'
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { OpenAPIReferencePlugin } from '@orpc/openapi/plugins'
|
||||
import { onError } from '@orpc/server'
|
||||
import { ZodToJsonSchemaConverter } from '@orpc/zod/zod4'
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { name, version } from '@/../package.json'
|
||||
import { name, version } from '#package'
|
||||
import { handleValidationError, logError } from '@/server/api/interceptors'
|
||||
import { router } from '@/server/api/routers'
|
||||
|
||||
|
||||
@@ -8,7 +8,9 @@ const selectSchema = createSelectSchema(todoTable)
|
||||
|
||||
const insertSchema = createInsertSchema(todoTable).omit(generatedFieldKeys)
|
||||
|
||||
const updateSchema = createUpdateSchema(todoTable).omit(generatedFieldKeys)
|
||||
const updateSchema = createUpdateSchema(todoTable)
|
||||
.omit(generatedFieldKeys)
|
||||
.refine((data) => Object.keys(data).length > 0, { message: 'At least one field is required' })
|
||||
|
||||
export const list = oc.input(z.void()).output(z.array(selectSchema))
|
||||
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import { ORPCError, ValidationError } from '@orpc/server'
|
||||
import { z } from 'zod'
|
||||
import { logger } from '@/server/logger'
|
||||
import { getLogger } from '@/server/logger'
|
||||
|
||||
const logger = getLogger(['api'])
|
||||
|
||||
export const logError = (error: unknown) => {
|
||||
logger.error(error)
|
||||
logger.error('Unhandled error in ORPC handler', { error })
|
||||
}
|
||||
|
||||
export const handleValidationError = (error: unknown) => {
|
||||
if (error instanceof ORPCError && error.code === 'BAD_REQUEST' && error.cause instanceof ValidationError) {
|
||||
// ORPC ValidationError.issues are Zod issues in this app.
|
||||
if (!(error instanceof ORPCError) || !(error.cause instanceof ValidationError)) return
|
||||
|
||||
if (error.code === 'BAD_REQUEST') {
|
||||
// ORPC widens issues to the Standard Schema shape; every contract here is built from Zod/drizzle-zod,
|
||||
// so the runtime objects are Zod issues. Rehydrate to reuse z.prettifyError / z.flattenError.
|
||||
const zodError = new z.ZodError(error.cause.issues as z.core.$ZodIssue[])
|
||||
|
||||
throw new ORPCError('INPUT_VALIDATION_FAILED', {
|
||||
@@ -19,7 +24,7 @@ export const handleValidationError = (error: unknown) => {
|
||||
})
|
||||
}
|
||||
|
||||
if (error instanceof ORPCError && error.code === 'INTERNAL_SERVER_ERROR' && error.cause instanceof ValidationError) {
|
||||
if (error.code === 'INTERNAL_SERVER_ERROR') {
|
||||
throw new ORPCError('OUTPUT_VALIDATION_FAILED', {
|
||||
cause: error.cause,
|
||||
})
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { ContractRouterClient, InferContractRouterInputs, InferContractRouterOutputs } from '@orpc/contract'
|
||||
import type { ContractRouterClient, InferContractRouterOutputs } from '@orpc/contract'
|
||||
import type { Contract } from './contracts'
|
||||
|
||||
export type RouterClient = ContractRouterClient<Contract>
|
||||
export type RouterInputs = InferContractRouterInputs<Contract>
|
||||
export type RouterOutputs = InferContractRouterOutputs<Contract>
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { sql } from 'drizzle-orm'
|
||||
import { timestamp, uuid } from 'drizzle-orm/pg-core'
|
||||
import { v7 as uuidv7 } from 'uuid'
|
||||
|
||||
export const generatedFields = {
|
||||
id: uuid('id')
|
||||
.primaryKey()
|
||||
.$defaultFn(() => uuidv7()),
|
||||
id: uuid('id').primaryKey().default(sql`uuidv7()`),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true })
|
||||
.notNull()
|
||||
@@ -12,4 +10,10 @@ export const generatedFields = {
|
||||
.$onUpdateFn(() => new Date()),
|
||||
}
|
||||
|
||||
export const generatedFieldKeys = { id: true, createdAt: true, updatedAt: true } as const
|
||||
type GeneratedFieldKey = keyof typeof generatedFields
|
||||
|
||||
export const generatedFieldKeys = {
|
||||
id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
} satisfies Record<GeneratedFieldKey, true>
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { DrizzleLogger } from '@logtape/drizzle-orm'
|
||||
import { drizzle } from 'drizzle-orm/postgres-js'
|
||||
import { env } from '@/env'
|
||||
import * as schema from '@/server/db/schema'
|
||||
import { getLogger } from '@/server/logger'
|
||||
|
||||
export const db = drizzle({
|
||||
connection: env.DATABASE_URL,
|
||||
schema,
|
||||
logger: env.LOG_DB ? new DrizzleLogger(getLogger(['db']), 'info') : false,
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// AUTO-GENERATED by `bun run db:embed`. Do not edit.
|
||||
import sql_0 from '../../../drizzle/0000_loving_thunderbird.sql' with { type: 'text' }
|
||||
import sql_0 from '#drizzle/0000_loving_thunderbird.sql' with { type: 'text' }
|
||||
|
||||
export type EmbeddedMigration = { tag: string; sql: string; when: number; breakpoints: boolean }
|
||||
|
||||
|
||||
+18
-4
@@ -1,5 +1,19 @@
|
||||
export const logger = {
|
||||
error: (error: unknown) => console.error(error),
|
||||
warn: (...args: unknown[]) => console.warn(...args),
|
||||
info: (...args: unknown[]) => console.info(...args),
|
||||
import { configureSync, getConfig, getConsoleSink, getJsonLinesFormatter } from '@logtape/logtape'
|
||||
import { prettyFormatter } from '@logtape/pretty'
|
||||
import { env } from '@/env'
|
||||
|
||||
if (getConfig() === null) {
|
||||
const format = env.LOG_FORMAT ?? (process.stdout.isTTY ? 'pretty' : 'json')
|
||||
|
||||
configureSync({
|
||||
sinks: {
|
||||
console: getConsoleSink({ formatter: format === 'pretty' ? prettyFormatter : getJsonLinesFormatter() }),
|
||||
},
|
||||
loggers: [
|
||||
{ category: [], lowestLevel: env.LOG_LEVEL, sinks: ['console'] },
|
||||
{ category: ['logtape', 'meta'], lowestLevel: 'warning', sinks: ['console'], parentSinks: 'override' },
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
export { getLogger } from '@logtape/logtape'
|
||||
|
||||
@@ -1,21 +1,30 @@
|
||||
import { db } from '@/server/db'
|
||||
import { getLogger } from '@/server/logger'
|
||||
|
||||
export default () => {
|
||||
if (import.meta.dev) return
|
||||
|
||||
const logger = getLogger(['shutdown'])
|
||||
let exiting = false
|
||||
|
||||
const shutdown = () => {
|
||||
const shutdown = async (signal: NodeJS.Signals) => {
|
||||
if (exiting) {
|
||||
logger.warn('Forcing exit on repeated signal', { signal })
|
||||
process.exit(0)
|
||||
}
|
||||
exiting = true
|
||||
logger.info('Draining for shutdown', { signal, graceMs: 500 })
|
||||
|
||||
setTimeout(() => {
|
||||
db.$client.end().finally(() => process.exit(0))
|
||||
}, 500)
|
||||
await Bun.sleep(500)
|
||||
try {
|
||||
await db.$client.end()
|
||||
logger.info('DB pool closed, exiting')
|
||||
} catch (error) {
|
||||
logger.error('DB pool close failed during shutdown', { error })
|
||||
}
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
process.on('SIGINT', shutdown)
|
||||
process.on('SIGTERM', shutdown)
|
||||
process.on('SIGINT', () => void shutdown('SIGINT'))
|
||||
process.on('SIGTERM', () => void shutdown('SIGTERM'))
|
||||
}
|
||||
|
||||
@@ -21,8 +21,4 @@ export default defineConfig({
|
||||
resolve: {
|
||||
tsconfigPaths: true,
|
||||
},
|
||||
server: {
|
||||
port: 3000,
|
||||
strictPort: true,
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user