diff --git a/.agents/skills/daoyou-backend-api-security/SKILL.md b/.agents/skills/daoyou-backend-api-security/SKILL.md index aa9d6646..ed2ec17d 100644 --- a/.agents/skills/daoyou-backend-api-security/SKILL.md +++ b/.agents/skills/daoyou-backend-api-security/SKILL.md @@ -1,71 +1,80 @@ --- name: daoyou-backend-api-security -description: Daoyou Hono API、认证、授权、Better Auth、ALTCHA、admin、internal cron、LLM header 安全和服务端输入校验指南。Use when adding or modifying src/server routes, middleware, auth, admin endpoints, cron/internal endpoints, shared contracts, LLM provider handling, API validation, or security-relevant frontend auth/admin loaders in this repo. Does not cover ordinary page styling or navigation. +description: Daoyou NestJS API、认证、授权、Better Auth、ALTCHA、admin、internal cron、LLM header 安全和服务端输入校验指南。Use when adding or modifying apps/api/src controllers, guards, middleware, auth, admin endpoints, cron/internal endpoints, shared contracts, LLM provider handling, API validation, or security-relevant frontend auth/admin loaders in this repo. Does not cover ordinary page styling or navigation. --- # Daoyou Backend API Security ## Read First -- `src/server/app.ts` -- `src/server/routes/api/index.ts` -- `src/server/routes/internal/cron.router.ts` -- `src/server/lib/hono/middleware.ts` -- `src/server/lib/auth/auth.ts` -- `src/server/lib/auth/hono.ts` -- `src/server/utils/aiClient.ts` -- `src/shared/config/llm.ts` -- `src/shared/config/llmRouting.ts` -- `src/shared/contracts` +- `apps/api/src/main.ts` and `app.module.ts` +- `apps/api/src/config/configuration.module.ts`, `runtime.config.ts`, and `lib/config/environment.ts` +- `apps/api/src/http/configure-http.ts` +- `apps/api/src/runtime/internal-cron.controller.ts` and `internal-cron.guard.ts` +- `apps/api/src/auth/access.guard.ts` and `session.service.ts` +- `apps/api/src/lib/auth/auth.ts` +- `apps/api/src/lib/auth/handler.ts` +- `apps/api/src/utils/aiClient.ts` +- `packages/shared/src/config/llm.ts` +- `packages/shared/src/config/llmRouting.ts` +- `packages/shared/src/contracts` + +## Configuration and Workspace Boundary + +- API builds with `nest build` (Nest CLI 12 ESM Rspack); Web builds with Vite. API may import `@daoyou/shared/*`, never Web; shared cannot import either host. LLM generators and prompt rendering belong in `apps/api/src/lib/generation`. +- Nest services inject `runtimeConfig`; independent libraries read `getRuntimeEnvironment()`. The snapshot is validated once with Zod; dotenv discovery is disabled. Never log credential values on validation failure. +- `DatabaseModule` exports the existing Drizzle client, preserving one pool and transaction propagation. Runtime closes it after request/message drain. ## API Boundary Facts -- API server is Hono. -- `/api/auth/*` is handled by Better Auth through `src/server/lib/auth/hono.ts`. -- `jsonError()` handles Zod errors and uncaught API errors for `/api/*` and `/internal/*`, but auth is registered before that middleware. +- API server is NestJS 12 on Node.js 24 with Express and native ws. Legacy Hono entrypoints, routes and dependencies have been removed; do not reintroduce a compatibility router. +- `/api/auth/*` is handled by Better Auth through `apps/api/src/lib/auth/handler.ts`. +- `ApiExceptionFilter` handles uncaught API errors; route-specific filters and Zod pipes preserve existing response contracts. Better Auth uses its raw Node handler before business body parsing. - Frontend route loaders are UX guards only. Backend handlers are the security boundary. -- Existing auth middleware: - - `requireUser()` for logged-in users. - - `requireActiveCultivatorRef()` resolves identity and sets `user` / `activeCultivatorRef`; it does not hydrate a full cultivator or inject a DB executor. - - `requireAdmin()` uses `adminAccess.ts` (`ADMIN_USER_IDS` or legacy `ADMIN_EMAILS`). `requireBetterAuthAdmin()` requires a configured user ID for account administration. - - `validateJson()` and `validateQuery()` for Zod parsing. -- Inspect each admin subroute for middleware registration; do not assume a filename or frontend loader supplies authorization. +- Existing auth boundary: + - Global `AccessGuard` requires login unless `@Access('public')` is declared. + - `@Access('active')` resolves `user` / `activeCultivatorRef` through `SessionService`; it does not hydrate a full cultivator or inject a DB executor. + - `@Access('admin')` uses `adminAccess.ts` (`ADMIN_USER_IDS` or legacy `ADMIN_EMAILS`); `account-admin` requires configured user ID authorization. + - `JsonBody` reads raw bytes in its first Pipe after guards, then passes parsed JSON to validation pipes. Keep its parameter factory synchronous: Nest 12 does not await a factory's Promise before the first Pipe. The reader does not inflate content-encoding; the business ceiling is 128 MiB and the webhook's independent limit is 256 KiB. `FirstQuery` reads the original URL without a 1,000-key truncation: query objects keep the first decoded key, named reads prefer a literal key over encoded aliases, and malformed UTF-8 escapes remain intact. Express query parsing is disabled; use `FirstQuery` for business queries. Zod pipes retain the intended 400 versus legacy-unhandled 500 behavior. +- Inspect each admin controller's access metadata; do not assume a filename or frontend loader supplies authorization. Use explicit constructor `@Inject` because the Nest CLI Rspack build disables implicit design-type metadata. +- Keep `NotFoundModule` last in `AppModule.imports`: its fallback controllers preserve authorization for unknown paths in previously protected namespaces, after every concrete feature route. Do not broaden these fallbacks to all API or admin paths. - `/internal/cron/*` uses `Authorization: Bearer ${CRON_SECRET}`, not user sessions. In production, missing `CRON_SECRET` returns 500. ## LLM Security Facts -- The browser patches `window.fetch` in `src/react-app/main.tsx` to add `x-llm-provider`, `x-llm-api-key` and `x-llm-model` headers for `/api/` requests. -- Server LLM calls should use `src/server/utils/aiClient.ts` (`generateAiText`, `streamAiText`, `generateAiObject`, `generateAiArray`) so provider resolution, metrics, structured output, and retry behavior stay in one path. -- Server-side `LLM_PROVIDER` is a route table: `provider[/model][:weight],...`. It covers one or many providers and one or many models. Multiple routes are sticky by user id hash on the full `provider + model`. BYOK request config still wins and does not enter the split. Read/parse in `src/shared/config/llmRouting.ts`; `aiClient.ts` only maps env and picks. -- Server accepts request-level BYOK only when provider, API key, and model pass `src/shared/config/llm.ts`; partial or invalid configuration returns 400 without falling back to the server key. -- Request provider IDs are allowlisted in `src/shared/config/llm.ts`; adapters/endpoints are owned by `src/server/lib/llm/providers.ts`. Do not accept arbitrary request Base URLs. +- The browser patches `window.fetch` in `apps/web/src/main.tsx` to add `x-llm-provider`, `x-llm-api-key` and `x-llm-model` headers for `/api/` requests. +- Server LLM calls should use `apps/api/src/utils/aiClient.ts` (`generateAiText`, `streamAiText`, `generateAiObject`, `generateAiArray`) so provider resolution, metrics, structured output, and retry behavior stay in one path. +- Server-side `LLM_PROVIDER` is a route table: `provider[/model][:weight],...`. It covers one or many providers and one or many models. Multiple routes are sticky by user id hash on the full `provider + model`. BYOK request config still wins and does not enter the split. Read/parse in `packages/shared/src/config/llmRouting.ts`; `aiClient.ts` only maps env and picks. +- Server accepts request-level BYOK only when provider, API key, and model pass `packages/shared/src/config/llm.ts`; partial or invalid configuration returns 400 without falling back to the server key. +- Request provider IDs are allowlisted in `packages/shared/src/config/llm.ts`; adapters/endpoints are owned by `apps/api/src/lib/llm/providers.ts`. Do not accept arbitrary request Base URLs. - LLM metrics use in-memory fallback plus Redis key `admin:llm-metrics:events:v1`; do not add a parallel metrics store. -- Prompt files under `src/server/prompts/*.md` have `id:` headers. New prompt scenes usually also need `LlmSceneId`, caller `sceneId`, and schema/constraint updates. +- Prompt files under `apps/api/src/prompts/*.md` have `id:` headers. New prompt scenes usually also need `LlmSceneId`, caller `sceneId`, and schema/constraint updates. - Treat LLM output as untrusted input. Numeric state changes need Zod bounds and service/resource-layer guards. ## V6 Authority and Mutation Boundaries -- Start with `src/server/routes/api/combat-v6.router.ts` and mode-specific routers, `src/shared/contracts/combatV6*.ts`, and `src/server/lib/services/combat-v6`. +- Start with `apps/api/src/combat` and mode-specific feature modules, `packages/shared/src/contracts/combatV6*.ts`, and `apps/api/src/lib/services/combat-v6`. - Resolve the actor from `activeCultivatorRef`; derive combat attributes, equipment, manuals and beasts server-side through `CombatV6BuildService.ts`. Client commands do not authorize client-supplied combat units, results or rewards. - Preserve session ownership/participant checks, `expectedRevision` validation, legal-command queries and Redis CAS. Spectator and replay views must retain their existing visibility checks. - State changes use the owning service's mutation/occupancy guards, transaction and resource response path (`CommandExecutors.ts`, `ResourceMutationResponse.ts`, `InventoryService.ts`). Check mode-specific exceptions such as dungeon recovery before reusing a blanket combat lock. -- Terminal settlement and replay archival run through `src/server/lib/mq/combatV6Messaging.ts` and V6 projectors. Retain retry/idempotency semantics; do not introduce direct route settlement alongside consumers. +- Terminal settlement and replay archival run through `apps/api/src/lib/mq/combatV6Messaging.ts` and V6 projectors. Retain retry/idempotency semantics; do not introduce direct route settlement alongside consumers. - `/api/battle-records/*` is a 410 retirement endpoint; do not restore V5 battle handlers for new V6 history features. ## External Service Facts -- Redis access must go through `src/server/lib/redis`; do not instantiate `new Redis()` in feature code. +- Redis access must go through `apps/api/src/lib/redis`; do not instantiate `new Redis()` in feature code. - Redis is not optional for many runtime paths even though health-check reports `disabled` when `REDIS_URL` is absent. -- NATS access uses `src/server/lib/nats`; shared messaging lifecycle is registered from `src/index.ts`. Health checks include Redis, NATS and message infrastructure. -- SMTP mail uses `src/server/lib/admin/smtp.ts`; required env includes `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS`, and `MAIL_FROM`. +- NATS access uses `apps/api/src/lib/nats`; Nest `RuntimeService` owns shared messaging startup and shutdown. Drain handlers and pending publications before closing database/Redis. Health checks include Redis, NATS and message infrastructure. +- Native WS upgrades bypass Express: `RealtimeAdapter` owns API admission, response headers and pending-handshake tracking; feature services own identity and connection quotas. Preserve individual Set-Cookie headers on both successful and rejected handshakes. +- SMTP mail uses `apps/api/src/lib/admin/smtp.ts`; required env includes `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS`, and `MAIL_FROM`. ## Workflow 1. Classify the endpoint: public, logged-in, active-cultivator, admin, or internal cron. -2. Reuse the existing middleware. Do not hand-roll session parsing. +2. Reuse the existing guards and `SessionService`. Do not hand-roll session parsing. 3. Add or reuse Zod schemas for request bodies and query strings. -4. Register new API routes in `src/server/routes/api/index.ts`; register internal jobs in `src/server/routes/internal`. -5. If the route changes shared request/response shape, update `src/shared/contracts` or `src/shared/types`. +4. Register controllers in feature modules imported by `apps/api/src/app.module.ts`; internal jobs belong to the runtime module and shared job services. Keep repositories and pure domain logic independent of Nest. +5. If the route changes shared request/response shape, update `packages/shared/src/contracts` or `packages/shared/src/types`. 6. If the route calls LLM or consumes LLM output, check prompt/schema bounds and service-layer guards. 7. If adding a prompt scene, update prompt id, `LlmSceneId`, caller `sceneId`, and schema/constraint together. 8. Verify server behavior with lint/build, code inspection, and focused manual/runtime checks; do not add route/service/provider tests. @@ -73,11 +82,11 @@ description: Daoyou Hono API、认证、授权、Better Auth、ALTCHA、admin、 ## Do Not - Do not rely on React loaders for authorization. -- Do not bypass `src/server/lib/auth/hono.ts` for login, signup, reset, OTP, or ALTCHA-protected flows. +- Do not bypass `apps/api/src/lib/auth/handler.ts` for login, signup, reset, OTP, or ALTCHA-protected flows. - Do not add admin files under `/api/admin` without explicit admin authorization. -- Use the validated `llmConfig` from Hono context and configured provider adapters; do not bypass the provider allowlist or introduce a client Base URL. +- Use the validated `llmConfig` from request context / framework-independent AsyncLocalStorage and configured provider adapters; do not bypass the provider allowlist or introduce a client Base URL. Singleton services must not store request identity or BYOK credentials in fields. - Do not make public list/ranking/community endpoints private without checking frontend/product usage. -- Do not assume `src/shared/api` exists; shared contracts live under `src/shared/contracts`. +- Do not assume `packages/shared/src/api` exists; shared contracts live under `packages/shared/src/contracts`. - Do not bypass `aiClient.ts` for LLM calls. - Do not create feature-local Redis clients or SMTP transports. diff --git a/.agents/skills/daoyou-backend-api-security/agents/openai.yaml b/.agents/skills/daoyou-backend-api-security/agents/openai.yaml index dfd91f09..2413f15b 100644 --- a/.agents/skills/daoyou-backend-api-security/agents/openai.yaml +++ b/.agents/skills/daoyou-backend-api-security/agents/openai.yaml @@ -1,4 +1,4 @@ interface: display_name: "Daoyou Backend API Security" - short_description: "Daoyou Hono API、认证、权限与 LLM 安全" + short_description: "Daoyou NestJS API、认证、权限与 LLM 安全" default_prompt: "Use $daoyou-backend-api-security to add or review Daoyou API routes, auth, admin, cron, or LLM security changes." diff --git a/.agents/skills/daoyou-beast-design/SKILL.md b/.agents/skills/daoyou-beast-design/SKILL.md index ef2f2aed..d7cac997 100644 --- a/.agents/skills/daoyou-beast-design/SKILL.md +++ b/.agents/skills/daoyou-beast-design/SKILL.md @@ -14,7 +14,7 @@ description: 万界道友灵兽物种设计与审查规范。用于新增或调 - 设计、扩充或审查物种时,读取 [设计确认基准](references/first-release.md),对照完整名单检查形态重复、数值跨度和天赋组合;具体名单、境界分布、区间、出生概率及 ID 以基准为准。 - 后续用户明确修改设计时,以新要求为准,同步更新相应基准并说明改动;不要把本技能变成额外审批流程。未被要求改变的已确认规则继续沿用。 - 仅要求设计或整理规范时,交付设计或文档,不顺带修改游戏逻辑。配置实施同时使用 [daoyou-game-core-domain](../daoyou-game-core-domain/SKILL.md),持久化改动使用项目数据层技能。 -- 实施前核对 `src/shared/engine/combat-v6/beasts/` 的内容、生成、投影与培养调用,以及 `wild/` 的野外身份和捕捉入口。旧代码中的定位、四技能模板或捕捉加技能规则不能反过来覆盖已确认设计;列明差异后在任务范围内实现。 +- 实施前核对 `packages/shared/src/engine/combat-v6/beasts/` 的内容、生成、投影与培养调用,以及 `wild/` 的野外身份和捕捉入口。旧代码中的定位、四技能模板或捕捉加技能规则不能反过来覆盖已确认设计;列明差异后在任务范围内实现。 - 可用技能与真实效果以 `beasts/data/skills.json`、`beasts/skill-compiler.ts` 和运行时为准,适配差异参照 `docs/combat-v6-beast-classic-skills.md`。不要仅凭梦幻西游原技能名称推断本游戏机制。 ## 妖灵传承与技能文案 diff --git a/.agents/skills/daoyou-data-layer/SKILL.md b/.agents/skills/daoyou-data-layer/SKILL.md index a8dac5ce..26f6244a 100644 --- a/.agents/skills/daoyou-data-layer/SKILL.md +++ b/.agents/skills/daoyou-data-layer/SKILL.md @@ -7,11 +7,12 @@ description: Daoyou Drizzle/PostgreSQL、事务、V6 角色与宗门归属、统 ## Locate the Current Write Path -Read `drizzle.config.ts`, `drizzle.auth.config.ts`, `src/server/lib/drizzle/db.ts` and the relevant definitions in `src/server/lib/drizzle/schema.ts`. Follow the route → service → repository/SQL path before treating a table or DTO as authoritative. Historical migration/design files describe intent; they do not prove a deployed database has applied it. +Read `drizzle.config.ts`, `drizzle.auth.config.ts`, `apps/api/src/lib/drizzle/db.ts` and the relevant definitions in `apps/api/src/lib/drizzle/schema.ts`. Follow the route → service → repository/SQL path before treating a table or DTO as authoritative. Historical migration/design files describe intent; they do not prove a deployed database has applied it. ## Database and Transactions -- Business tables use the main `wanjiedaoyou_*` migration stream in `drizzle/`. Better Auth uses `src/server/lib/auth/schema.ts`, `drizzle.auth.config.ts` and `drizzle-auth/`, with fixed `better_auth` schema and independent history. +- Business tables use the main `wanjiedaoyou_*` migration stream in `drizzle/`. Better Auth uses `apps/api/src/lib/auth/schema.ts`, `drizzle.auth.config.ts` and `drizzle-auth/`, with fixed `better_auth` schema and independent history. +- Nest `apps/api/src/database/database.module.ts` exports the same client via `DRIZZLE_DATABASE` and `DatabaseService`. Runtime drains requests/messages before closing it. Connection settings come from validated `getRuntimeEnvironment()`; do not create feature-local pools. - Runtime uses a module-level `pg.Pool` and `drizzle-orm/node-postgres`, not Bun SQL. `DATABASE_URL` supplies the connection; `DB_MAX_CONNECTIONS` controls pool size. Session settings live in `db.ts`. - Reuse `db`, `getExecutor(tx?)`, `DbExecutor` and `DbTransaction`; do not create parallel DB layers or feature-local pools. - Pass the executor through every nested write. `runDbTasks(executor, tasks)` serializes work on a transaction's single connection and permits parallel pool reads; do not replace it with unconditional `Promise.all`. @@ -26,13 +27,13 @@ Table names below omit the `wanjiedaoyou_` prefix. The exact names and constrain | --- | --- | --- | | Character identity, permanent six attributes, condition | `cultivators` | `cultivatorRepository.ts`, `services/cultivator`, V6 condition services | | Personal manuals and active slots | `cultivator_manual_states`, `cultivator_manual_slots` | `characterLoadoutRepository.ts`, `CombatV6ManualService.ts` | -| Bag/storage item instances | `inventory_items` | `InventoryService.ts`, `src/shared/inventory`, `src/shared/items` | +| Bag/storage item instances | `inventory_items` | `InventoryService.ts`, `packages/shared/src/inventory`, `packages/shared/src/items` | | Equipped V6 equipment | `cultivator_equipment_slots` | `characterLoadoutRepository.ts`, `InventoryService.ts` | | Beasts and lineups | `cultivator_beasts`, `cultivator_beast_lineups` | `combatV6BeastRepository.ts`, `CombatV6BeastService.ts` | | Sect progression | `sect_combat_states`, `sect_method_progress`, `sect_meridian_loadouts`, `sect_meridian_nodes` | `sectCombatRepository.ts` | | V6 history/replays | `combat_replay_archives`, `combat_replay_participants` | `combatV6ReplayRepository.ts` | -Repository names resolve under `src/server/lib/repositories`; V6 service names resolve under `src/server/lib/services/combat-v6`. +Repository names resolve under `apps/api/src/lib/repositories`; V6 service names resolve under `apps/api/src/lib/services/combat-v6`. ### Ownership and JSON @@ -47,14 +48,14 @@ Repository names resolve under `src/server/lib/repositories`; V6 service names r ### Redis, Messages and Replays - Redis is authoritative for active V6 battle state, commands and RNG. Inspect `CombatV6RuntimeStore.ts` and the mode-specific stores for CAS revisions, occupancy, expiry and outboxes; do not substitute process-local sessions. -- Access Redis through `src/server/lib/redis`; NATS through `src/server/lib/nats`. `src/server/lib/mq/combatV6Messaging.ts` coordinates terminal/replay publication and archival. +- Access Redis through `apps/api/src/lib/redis`; NATS through `apps/api/src/lib/nats`. `apps/api/src/lib/mq/combatV6Messaging.ts` coordinates terminal/replay publication and archival. - PostgreSQL V6 archives enforce source/idempotency uniqueness. Replay participants intentionally do not cascade from character deletion; the battle archive owns their lifecycle. - Keep settlement, resource events and replay delivery idempotent across retries. Read `docs/nats-domain-events.md` together with the relevant consumer before changing message boundaries. ## Legacy Boundary - `creation_products`, `materials` and `consumables` still have residual code paths. Inspect their actual callers before modifying or deleting them; they are not fallback sources for V6 equipment/manuals or the unified bag. -- Legacy product views use `src/shared/legacy/products.ts`. Do not restore creation-v2 rehydration / `battleProjection` or treat `creation_products.is_equipped` as current V6 equipment state. +- Legacy product views use `packages/shared/src/legacy/products.ts`. Do not restore creation-v2 rehydration / `battleProjection` or treat `creation_products.is_equipped` as current V6 equipment state. - `/api/battle-records/*` returns 410. V6 history uses the combat replay repository, not `battle_records_v2`. - `battle_records_v3`, `battle_replay_archives` and `bet_battles` are deprecated historical schema, with deletion deferred by release policy. Read `docs/combat-v6-legacy-table-retirement.md` for that policy, but verify exact table names against schema. Do not generate DROP migrations simply while cleaning up skills or legacy references. - Do not infer that every older table is still present, or already physically deleted, from a DTO, directory or migration file alone. diff --git a/.agents/skills/daoyou-game-core-domain/SKILL.md b/.agents/skills/daoyou-game-core-domain/SKILL.md index a89d78e3..bd6f7129 100644 --- a/.agents/skills/daoyou-game-core-domain/SKILL.md +++ b/.agents/skills/daoyou-game-core-domain/SKILL.md @@ -7,19 +7,19 @@ description: Daoyou combat-v6 战斗内核、规则、人物投影、宗门、 ## Locate the Owning Layer -Current combat is `src/shared/engine/combat-v6`. Read the affected module and its callers first; phase plans and the engine README contain historical milestones, not a reliable inventory of current runtime behavior. +Current combat is `packages/shared/src/engine/combat-v6`. Read the affected module and its callers first; phase plans and the engine README contain historical milestones, not a reliable inventory of current runtime behavior. | Task | Code entrypoints | | --- | --- | -| Turn pipeline, commands, effects, RNG, sessions | `src/shared/engine/combat-v6/core` | -| Daoyou combat formulas | `src/shared/engine/combat-v6/rules-daoyou` | -| Character panel and assembly | `src/shared/engine/combat-v6/projection`, `src/shared/lib/cultivatorDisplay.ts` | -| Sect methods, skills, meridians | `src/shared/engine/combat-v6/content`, `src/shared/engine/combat-v6/sect-progression` | -| Equipment compilation and generation | `src/shared/engine/combat-v6/equipment`, `src/shared/forging` | -| Manual definitions and progression | `src/shared/engine/combat-v6/manuals`, `src/shared/manuals` | -| Beasts, encounter orchestration, wild encounters | `src/shared/engine/combat-v6/beasts`, `src/shared/engine/combat-v6/encounter`, `src/shared/engine/combat-v6/wild` | -| Inventory, item definitions, drops, rewards | `src/shared/inventory`, `src/shared/items`, `src/shared/drops`, `src/shared/rewards` | -| Battle presentation, auto commands and replay contracts | `src/shared/combat-v6`, `src/shared/contracts/combatV6Runtime.ts` | +| Turn pipeline, commands, effects, RNG, sessions | `packages/shared/src/engine/combat-v6/core` | +| Daoyou combat formulas | `packages/shared/src/engine/combat-v6/rules-daoyou` | +| Character panel and assembly | `packages/shared/src/engine/combat-v6/projection`, `packages/shared/src/lib/cultivatorDisplay.ts` | +| Sect methods, skills, meridians | `packages/shared/src/engine/combat-v6/content`, `packages/shared/src/engine/combat-v6/sect-progression` | +| Equipment compilation and generation | `packages/shared/src/engine/combat-v6/equipment`, `packages/shared/src/forging` | +| Manual definitions and progression | `packages/shared/src/engine/combat-v6/manuals`, `packages/shared/src/manuals` | +| Beasts, encounter orchestration, wild encounters | `packages/shared/src/engine/combat-v6/beasts`, `packages/shared/src/engine/combat-v6/encounter`, `packages/shared/src/engine/combat-v6/wild` | +| Inventory, item definitions, drops, rewards | `packages/shared/src/inventory`, `packages/shared/src/items`, `packages/shared/src/drops`, `packages/shared/src/rewards` | +| Battle presentation, auto commands and replay contracts | `packages/shared/src/combat-v6`, `packages/shared/src/contracts/combatV6Runtime.ts` | ## Engine Boundary @@ -32,9 +32,9 @@ Current combat is `src/shared/engine/combat-v6`. Read the affected module and it ## Character and Build Boundary -- Current `Cultivator.attributes` still stores six permanent attributes: vitality, strength, spirit, endurance, speed, willpower. Read `src/shared/types/cultivator.ts` and `projection/character-panel-v1.ts` before changing formulas. Future numerical design does not establish an implemented five-attribute model. +- Current `Cultivator.attributes` still stores six permanent attributes: vitality, strength, spirit, endurance, speed, willpower. Read `packages/shared/src/types/cultivator.ts` and `projection/character-panel-v1.ts` before changing formulas. Future numerical design does not establish an implemented five-attribute model. - Current complete projection is `projectCharacterToCombatV6(CharacterCombatInput)`. Historical `projectCultivator*` phase entrypoints are adapters, not the default for new business code. -- `CharacterCombatInput.sect` is optional. Personal display applies equipment and manuals without sect membership. Reuse `projectCharacterDisplay` and resource helpers in `src/shared/lib/cultivatorDisplay.ts`. +- `CharacterCombatInput.sect` is optional. Personal display applies equipment and manuals without sect membership. Reuse `projectCharacterDisplay` and resource helpers in `packages/shared/src/lib/cultivatorDisplay.ts`. - Battle admission is a separate server rule: `CombatV6BuildService.ts` assembles authoritative inputs and checks membership / selected path. Do not remove admission checks merely because pure projection permits a sectless character. - Personal manuals, equipment and beasts belong to the character; sect methods and meridians belong to membership. Keep their revisions separate, and derive readiness from current membership/path. See `docs/combat-domain-ownership.md`. - Respect projection diagnostics and `full` / `persistent` resource policies. Changing maximum HP/MP must not implicitly heal existing characters; recovery and rebasing use the shared display/condition helpers and server V6 condition authority. @@ -44,13 +44,13 @@ Current combat is `src/shared/engine/combat-v6`. Read the affected module and it - For beast species design or content changes involving aptitudes, growth or birth skills, also read [daoyou-beast-design](../daoyou-beast-design/SKILL.md). Its confirmed design baseline is distinct from current runtime behavior; do not restore fixed species roles or complete four-skill templates from older content. - New equipment uses V6 equipment instances; equipped slots are separate from inventory. Manual progression and active slots are separate from consumable manual jades. -- Use `src/shared/items/registry.ts` and definition schemas for item facts, and `src/shared/inventory` for stack/capacity/action rules. Do not translate new items into legacy creation product models. +- Use `packages/shared/src/items/registry.ts` and definition schemas for item facts, and `packages/shared/src/inventory` for stack/capacity/action rules. Do not translate new items into legacy creation product models. - Forge, manual, beast and reward rules have dedicated shared modules; reuse them instead of reproducing costs, eligibility or generation logic in routes/UI. - Existing condition, alchemy and spirit-field code remains outside the battle core. Follow its live callers; removal of the old combat engine does not imply removal of all noncombat systems. ## Verify - For pure deterministic shared logic, add or update a focused reproducing/contract test beside the affected module. -- Examples: `bun run test src/shared/engine/combat-v6/projection`, `bun run test src/shared/engine/combat-v6/rules-daoyou`, or `bun run test src/shared/inventory` depending on scope. -- Broaden to `bun run test` for changes spanning core, content and projection; run lint/build when imports or shared contracts change. +- Examples: `pnpm run test packages/shared/src/engine/combat-v6/projection`, `pnpm run test packages/shared/src/engine/combat-v6/rules-daoyou`, or `pnpm run test packages/shared/src/inventory` depending on scope. +- Broaden to `pnpm run test` for changes spanning core, content and projection; run lint/build when imports or shared contracts change. - Server persistence and browser behavior use code inspection and focused runtime checks per `docs/testing.md`; do not add server, database or UI unit tests. diff --git a/.agents/skills/daoyou-game-ui/SKILL.md b/.agents/skills/daoyou-game-ui/SKILL.md index 7ef87f68..eaee59bf 100644 --- a/.agents/skills/daoyou-game-ui/SKILL.md +++ b/.agents/skills/daoyou-game-ui/SKILL.md @@ -29,10 +29,10 @@ description: 为本项目的主流程游戏 UI 提供抽象规范与审查方法 ## 当前接入边界 -- 先看 `src/react-app/router.tsx` 的真实布局。V6 战斗使用 `src/react-app/layouts/combat-v6-layout.tsx` 和 `src/react-app/components/feature/combat-v6`,不套主流程身份/任务/导航三层审查。 +- 先看 `apps/web/src/router.tsx` 的真实布局。V6 战斗使用 `apps/web/src/layouts/combat-v6-layout.tsx` 和 `apps/web/src/components/feature/combat-v6`,不套主流程身份/任务/导航三层审查。 - 战斗展示与回放核对 `docs/combat-v6-battle-ui.md` 和现有 session/presentation 代码;客户端消费权威状态,不为展示重新计算战斗结果。 -- 角色和物品状态优先复用 `src/react-app/lib/resources` 的 hooks/store/mutations。旧 `useCultivatorBundle` 已不在当前代码中。 -- 角色面板使用 `src/shared/lib/cultivatorDisplay.ts` 的 V6 展示契约;物品预览与道具接入遵循 [daoyou-item-preview](../daoyou-item-preview/SKILL.md) 的固定规范,`docs/item-presentation-ui.md`、`docs/inventory-equipment-ui.md` 用于查实现脉络,不要从旧 creation product 推导新道装属性。 +- 角色和物品状态优先复用 `apps/web/src/lib/resources` 的 hooks/store/mutations。旧 `useCultivatorBundle` 已不在当前代码中。 +- 角色面板使用 `packages/shared/src/lib/cultivatorDisplay.ts` 的 V6 展示契约;物品预览与道具接入遵循 [daoyou-item-preview](../daoyou-item-preview/SKILL.md) 的固定规范,`docs/item-presentation-ui.md`、`docs/inventory-equipment-ui.md` 用于查实现脉络,不要从旧 creation product 推导新道装属性。 ## Mental Model @@ -163,22 +163,22 @@ description: 为本项目的主流程游戏 UI 提供抽象规范与审查方法 在这个仓库里,先检查当前主流程壳组件与共享原语,再决定改动位置。优先关注: -- `src/react-app/components/game-shell` -- `src/react-app/components/feature/world-chat` -- `src/react-app/routes/game` -- `src/react-app/router.tsx` -- `src/react-app/components/game-shell/gameNavigation.ts` -- `src/react-app/lib/router/gameShellRegistry.ts` +- `apps/web/src/components/game-shell` +- `apps/web/src/components/feature/world-chat` +- `apps/web/src/routes/game` +- `apps/web/src/router.tsx` +- `apps/web/src/components/game-shell/gameNavigation.ts` +- `apps/web/src/lib/router/gameShellRegistry.ts` - `docs/game-layout-ownership.md` 优先复用现有主流程原语,而不是重新发明一套页面局部样式约定。只有当现有原语无法表达当前规则时,才新增一个职责单一的组件。 新增或迁移主流程场景时,不要只添加 route 文件。同步核对: -- `src/react-app/router.tsx` 中的 route、`handle={scene(...)}` 和 document title -- `src/react-app/components/game-shell/gameNavigation.ts` 中对应 scene metadata、dock label、href +- `apps/web/src/router.tsx` 中的 route、`handle={scene(...)}` 和 document title +- `apps/web/src/components/game-shell/gameNavigation.ts` 中对应 scene metadata、dock label、href - 需要 immersive chrome 时的特殊返回 descriptor -- `src/react-app/lib/router/gameShellRegistry.ts`、`src/react-app/lib/router/routeTitle.ts` 的注册与标题行为 +- `apps/web/src/lib/router/gameShellRegistry.ts`、`apps/web/src/lib/router/routeTitle.ts` 的注册与标题行为 - `docs/game-layout-ownership.md` 是否仍准确 ## Review Questions diff --git a/.agents/skills/daoyou-game-ui/references/viewport-checklist.md b/.agents/skills/daoyou-game-ui/references/viewport-checklist.md index aa0ce2cb..4133ffda 100644 --- a/.agents/skills/daoyou-game-ui/references/viewport-checklist.md +++ b/.agents/skills/daoyou-game-ui/references/viewport-checklist.md @@ -5,10 +5,10 @@ 先做静态搜索,确认没有明显回退: ```bash -rg -n "font-heading|font-ma-shan-zheng" src/react-app/routes/game src/react-app/components/game-shell src/react-app/components/func -rg -n "InkSection" src/react-app/routes/game src/react-app/components -rg -n "actionBar|sceneSurfaceClassName|sceneBodyClassName|scene[A-Z][A-Za-z]*ClassName" src/react-app -rg -n "variant=\\\"drawer\\\"|onOpenCultivator|WorldChatHost|CultivatorOverviewOverlay" src/react-app +rg -n "font-heading|font-ma-shan-zheng" apps/web/src/routes/game apps/web/src/components/game-shell apps/web/src/components/func +rg -n "InkSection" apps/web/src/routes/game apps/web/src/components +rg -n "actionBar|sceneSurfaceClassName|sceneBodyClassName|scene[A-Z][A-Za-z]*ClassName" apps/web/src +rg -n "variant=\\\"drawer\\\"|onOpenCultivator|WorldChatHost|CultivatorOverviewOverlay" apps/web/src ``` 如果当前任务只改了某个 feature,也要顺着引用检查它进入主流程正文时依赖的共享组件。 diff --git a/.agents/skills/daoyou-ink-portraits/SKILL.md b/.agents/skills/daoyou-ink-portraits/SKILL.md index 65158c59..1c3780e5 100644 --- a/.agents/skills/daoyou-ink-portraits/SKILL.md +++ b/.agents/skills/daoyou-ink-portraits/SKILL.md @@ -48,7 +48,7 @@ description: 为万界道友生成、修改或评审写意墨像立绘,统一 美术语言固定,解剖、动作、神态、取景与色相由物种决定。头像不等于必须画头部;以目标尺寸下最有效的身份表达选择展示范围。 -- **从设定提取身份**:读取当前物种概念,区分不可丢失的身体结构、比例与标志性特征,以及可以探索的姿态和表现方式。栖息环境与习性用于推导行为,不自动变成背景景物或附加装饰。项目灵兽以 `src/shared/engine/combat-v6/beasts/data/species.json` 为事实来源,必要时对照 [物种设计基准](../daoyou-beast-design/references/first-release.md);只做美术时不改玩法设定。 +- **从设定提取身份**:读取当前物种概念,区分不可丢失的身体结构、比例与标志性特征,以及可以探索的姿态和表现方式。栖息环境与习性用于推导行为,不自动变成背景景物或附加装饰。项目灵兽以 `packages/shared/src/engine/combat-v6/beasts/data/species.json` 为事实来源,必要时对照 [物种设计基准](../daoyou-beast-design/references/first-release.md);只做美术时不改玩法设定。 - **以行为确定姿态和神态**:根据身体的运动方式、习性和气质选择一个能成立的状态,说明朝向、重心与主要结构关系。无需全员凶猛、沉静、侧脸或蓄势,也不以人类表情作为有生命感的前提。新设计从物种概念重新构思,不以旧版头像反推造型;仅在用户要求保留或编辑旧稿时把它作为编辑依据。 - **以识别决定构图**:先判断最能区分物种的轮廓和结构,再选择局部或全身、观看角度及画面重心。保留必要的结构联系和负形空间;不用固定头身比例、对称形式或统一动作约束不同体态。头像的紧凑程度以缩小后可读、关键轮廓完整为准。 - **把结构转译为笔墨**:用主墨面组织体量和运动方向,以少量实笔建立识别支点,细节按对物种辨识的贡献取舍。留白与断笔服务结构,不能抹去必要特征;复杂生灵也不靠均匀密集的纹理表现复杂度。 diff --git a/.agents/skills/daoyou-ink-portraits/references/beast-avatar-delivery.md b/.agents/skills/daoyou-ink-portraits/references/beast-avatar-delivery.md index 7afa69a3..30e468a0 100644 --- a/.agents/skills/daoyou-ink-portraits/references/beast-avatar-delivery.md +++ b/.agents/skills/daoyou-ink-portraits/references/beast-avatar-delivery.md @@ -5,7 +5,7 @@ - 默认导出 **256×256、真实透明的 WebP**。从高分辨率原稿裁掉透明外边距,再等比缩放,使有效图幅的最长边为 256px,另一边居中;不拉伸、不裁断主体、不额外添加安全边距。保留用户选稿的姿态、内部留白和半透明笔触。 - 裁切边界按可见墨迹确定,避免近乎透明的孤立噪点撑大边界;当前以 alpha ≥ 4/255 定位边界,裁切范围内保留原始 alpha,不将笔触二值化。生成阶段为防止截断而预留的外圈空白,不保留到最终头像。优先从原稿导出,避免重复放大小尺寸有损图片。 - WebP 质量 90 为起点,按边缘和缩小效果调整。导出后检查至少一边的可见墨迹接近画布两端,另一边允许因物种比例自然留白;不要用 CSS 放大补偿各图片不一致的外边距。 -- 按 [统一图标规范](../../../../docs/game-icons.md),素材放在 `public/assets/icons/`,由 `src/react-app/components/ui/icons/registry.ts` 注册稳定的 `icon:名称`,`BeastIcon` 取值后交给 `GameIcon` 渲染。不新增页面分支或图标协议。 +- 按 [统一图标规范](../../../../docs/game-icons.md),素材放在 `apps/web/public/assets/icons/`,由 `apps/web/src/components/ui/icons/registry.ts` 注册稳定的 `icon:名称`,`BeastIcon` 取值后交给 `GameIcon` 渲染。不新增页面分支或图标协议。 - 检查独立导出图、列表、移动详情和桌面详情,显示尺寸以当前组件为准。缩小后主要轮廓和身份特征仍可辨认,正常 opacity 下与其他头像大小协调、无意外裁切或挤压。 - 检查 alpha、纸色与深底上的透明边缘,以及注册路径、图片加载、WebP 尺寸和体积。原稿与未选候选不进入生产目录;记录采用的提示词、参考和最终素材。 - 涉及接入代码时运行 lint/build 和针对性浏览器检查;纯素材或文档变更检查相关文件与显示效果,不机械运行全量游戏测试。只做头像时不修改物种、数值、技能或玩法能力,不自动启动全物种重绘。 diff --git a/.agents/skills/daoyou-item-preview/SKILL.md b/.agents/skills/daoyou-item-preview/SKILL.md index e75f6fcd..b2475443 100644 --- a/.agents/skills/daoyou-item-preview/SKILL.md +++ b/.agents/skills/daoyou-item-preview/SKILL.md @@ -38,7 +38,7 @@ description: 万界道友物品预览的固定展示与接入规范。新增道 ## 组件职责:数据决定内容,视图只渲染 -以下路径均相对 `src/react-app/components/feature/items/`: +以下路径均相对 `apps/web/src/components/feature/items/`: | 层 | 固定职责 | | --- | --- | @@ -70,7 +70,7 @@ description: 万界道友物品预览的固定展示与接入规范。新增道 - 对照分类基线逐项核对名称、头部顺序、数量、默认折叠、直接消耗、描述。不得恢复双加合计、归元后消耗、重复类型、三层传承标题或移动/合并。 - 检查公共模型组装与渲染是否新增 kind/ID 特判;摘要入口是否开始调用详细预览;正文是否出现任意 JSX 或从文案反解析数据。 - 改公共展示时覆盖装备、图纸、传承灵印、归元灵露、功法玉简、丹药、灵果,以及材料、灵种、符箓;同一物品在背包和受影响的其他入口命名、内容一致。 -- UI 改动运行 `bun run lint`、`bun run build`,按 [测试规范](../../../docs/testing.md) 做本地浏览器检查:桌面/360px、长名称、长描述、空分组、键盘、触屏点击、Esc、展开后浮层仍在;涉及批量服用时检查数量上下限与快捷操作。不得用“编译通过”代替交互验收。 +- UI 改动运行 `pnpm run lint`、`pnpm run build`,按 [测试规范](../../../docs/testing.md) 做本地浏览器检查:桌面/360px、长名称、长描述、空分组、键盘、触屏点击、Esc、展开后浮层仍在;涉及批量服用时检查数量上下限与快捷操作。不得用“编译通过”代替交互验收。 - 不在前端/服务端添加单元测试。改动纯共享领域逻辑时遵循领域 skill 和仓库测试规则;只改本规范时验证 skill、链接和 diff 即可。报告已执行与未执行的检查。 ## 关联规范 diff --git a/.agents/skills/daoyou-map-art/SKILL.md b/.agents/skills/daoyou-map-art/SKILL.md index 29a07702..e62b0cbc 100644 --- a/.agents/skills/daoyou-map-art/SKILL.md +++ b/.agents/skills/daoyou-map-art/SKILL.md @@ -34,7 +34,7 @@ description: 为万界道友设计、生成、修改或评审国画水墨地图 - 基于该地区的地理、人文与修仙特色独立构图,不能裁切或放大世界图局部后添加细节。 - 保持世界归属和重要地理关系,但不直接继承总览的海岸轮廓、镜头比例或坐标。允许适合区域阅读的俯视与散点透视,地形关系仍须清楚。 - 从真实设定选择地域识别点,用地貌、聚落分布、修行场所及险地之间的关系形成特色。不要把天南的某种河谷、城池或林色布局推广为所有区域的模板。 -- 涉及节点布局时,读取当前 `src/shared/data/map.json` 与 `src/shared/lib/game/mapSystem.ts`。未获新要求时保留节点 ID、名称、类型、归属和玩法;展示锚点可依构图重新确定,不把某次节点数量或坐标固化进规范。 +- 涉及节点布局时,读取当前 `packages/shared/src/data/map.json` 与 `packages/shared/src/lib/game/mapSystem.ts`。未获新要求时保留节点 ID、名称、类型、归属和玩法;展示锚点可依构图重新确定,不把某次节点数量或坐标固化进规范。 ## 设计与制作 @@ -46,7 +46,7 @@ description: 为万界道友设计、生成、修改或评审国画水墨地图 ## 交付与边界 - 场景底画与地名、区域入口、节点、命中区、连线、选择状态及界面控件分离。生产底画不烘焙文字、旗标、题签、印章、水印或编辑标注;画中的建筑可以是景物,不代替交互节点。 -- 正式地图素材保存到 `public/assets/maps/`,供 Phaser 作为场景纹理加载,不通过 UI 的 `GameIcon` 图标协议。仅制作美术时不自动接入 HTML、修改路由、发布页面或调整玩法。 +- 正式地图素材保存到 `apps/web/public/assets/maps/`,供 Phaser 作为场景纹理加载,不通过 UI 的 `GameIcon` 图标协议。仅制作美术时不自动接入 HTML、修改路由、发布页面或调整玩法。 - 根据实际画幅、缩放范围、设备纹理限制与包体选择分辨率和压缩参数,不固定像素尺寸、画幅比例或压缩质量;有实测需要再分块。检查解码、尺寸、边缘与压缩后笔墨,文件体积不等于解码后的显存占用。 - 只保留约定的生产素材与必要的交付清单。项目内不归档母稿、历史提示词、生成过程记录、固定参考图包或中间版本;交付后清理本任务产生的临时文件,不删除用户原始输入或其他功能的资产。 - 交付清单只记录路径、用途、规格和接入状态,不保存构图配方。现有素材可用于具体任务的效果对照或编辑,但不要求后续每次生成都附带它们。 diff --git a/.dockerignore b/.dockerignore index 624219a1..b8761d06 100644 --- a/.dockerignore +++ b/.dockerignore @@ -10,3 +10,5 @@ yarn-debug.log* .env* /env/ + +.turbo diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index a5a4123f..6187065c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -7,7 +7,6 @@ on: env: APP_IMAGE_NAME: ${{ secrets.DOCKERHUB_USERNAME }}/daoyou-app - IMAGE_TAG: latest jobs: build-and-push: @@ -20,6 +19,16 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + - name: Image tags and revision + id: metadata + uses: docker/metadata-action@v5 + with: + images: ${{ env.APP_IMAGE_NAME }} + tags: | + type=ref,event=tag + type=sha,format=long + type=raw,value=latest + # 登录 Docker Hub - name: Login to Docker Hub uses: docker/login-action@v3 @@ -33,4 +42,5 @@ jobs: context: . file: docker/Dockerfile.app push: true - tags: ${{ env.APP_IMAGE_NAME }}:${{ env.IMAGE_TAG }} + tags: ${{ steps.metadata.outputs.tags }} + labels: ${{ steps.metadata.outputs.labels }} diff --git a/.gitignore b/.gitignore index c55c7bab..314164db 100644 --- a/.gitignore +++ b/.gitignore @@ -196,6 +196,7 @@ dist .env* /env/* !/env/example.env +!/env/local.example.env .dev.vars* !.dev.vars.example @@ -209,3 +210,6 @@ sponsorkit/.cache*.json sponsorkit/sponsors.json .venv* + +# Turborepo task cache +.turbo/ diff --git a/.prettierignore b/.prettierignore index 3c35f44f..f82e671d 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,7 +3,7 @@ **/*.svg .umi .umi-production -/dist +**/dist .dockerignore .DS_Store .eslintignore @@ -21,6 +21,6 @@ yarn-error.log .history CNAME /build -/public +/apps/web/public /drizzle .qoder \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index 04db4e5d..85fa5922 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,18 +4,21 @@ AI agents should read this first. Keep changes small, project-specific, and back ## Project Snapshot -- This repo is `Hono + React SPA`, not Next.js or SSR. -- Runtime stack: Bun, Hono, React 19, React Router 8, Vite, Tailwind CSS 4, PostgreSQL, Drizzle ORM, Better Auth, Redis, NATS, AI SDK. -- Use `bun` / `bunx` for local development and keep the checked-in `bun.lock`. Also maintain `package-lock.json` for deployment packaging; dependency updates must keep both lockfiles aligned with `package.json`. Do not introduce yarn/pnpm lockfiles. -- Path aliases are `@app` -> `src/react-app`, `@server` -> `src/server`, and `@shared` -> `src/shared`. +- NestJS框架代码迁移与pnpm/Turborepo工具链切换完成:开发/构建入口为`apps/api/src`,Docker运行其部署产物,旧Hono入口、路由和依赖已移除;特殊数据/发布验收仍有未完成项,尚未部署生产。进度和验收见`docs/nestjs-migration.md`。使用Nest Controller、Guard、Zod Pipe、Filter和构造器注入,不恢复Hono兼容路由层。 + +- This repo is `NestJS + React SPA`, not Next.js or SSR. +- Runtime stack: Node.js 24, NestJS 12 (Express/native ws), pnpm + Turborepo tooling, React 19, React Router 8, Vite, Tailwind CSS 4, PostgreSQL, Drizzle ORM, Better Auth, Redis, NATS, AI SDK. +- Use the pinned `pnpm` version and `pnpm-lock.yaml` for development and deployment. Do not introduce Bun/npm/yarn lockfiles. Turborepo orchestrates package builds, typechecks and persistent development tasks. +- pnpm workspaces: `apps/api`, `apps/web`, `packages/shared`. Each owns its runtime dependencies; root owns lint/test/maintenance tooling. Shared must not import either app; API and Web may import shared. ESLint enforces these boundaries. +- Path aliases are `@app` -> `apps/web/src`, `@server` -> `apps/api/src`, and `@daoyou/shared` -> `packages/shared/src`. ## Key Directories -- `src/index.ts`: Bun/Hono entrypoint, WebSocket adapter, cron and message infrastructure lifecycle. -- `src/server`: Hono app, routes, auth, services, repositories, jobs, Redis, LLM, SMTP. -- `src/react-app`: React SPA routes, layouts, game shell, UI, hooks, providers. -- `src/shared`: shared contracts, game engines, config, pure logic, domain types. -- `src/server/lib/drizzle/schema.ts`: Drizzle schema for `wanjiedaoyou_*` business tables. +- `apps/api/src/main.ts`: Node/Nest entrypoint, HTTP/WS adapters and shutdown coordination; runtime module owns cron and messaging lifecycle. +- `apps/api/src`: Nest feature modules, auth, services, repositories, jobs, Redis, LLM, SMTP. +- `apps/web/src`: React SPA routes, layouts, game shell, UI, hooks, providers. +- `packages/shared/src`: shared contracts, game engines, config, pure logic, domain types. +- `apps/api/src/lib/drizzle/schema.ts`: Drizzle schema for `wanjiedaoyou_*` business tables. - `drizzle/`: Drizzle SQL migrations and snapshots. - `drizzle-auth/`: independent Drizzle migrations and snapshots for the fixed `better_auth` schema. - `docs/`: design and architecture notes; verify against current code before treating old docs as current truth. @@ -24,24 +27,25 @@ AI agents should read this first. Keep changes small, project-specific, and back ## Commands ```bash -bun install -bun run dev -bun run prd -bun run lint -bun run test -bun run build -bun run db:migrate +pnpm install +pnpm run dev +pnpm run prd +pnpm run lint +pnpm run typecheck +pnpm run test +pnpm run build +pnpm run db:migrate ``` -- `dev[:api|:web]` selects `env/local.env`; `prd[:api|:web]` selects `env/staging.env`. Bun's implicit env loading is disabled; other tools require explicitly injected variables or `bun --env-file=...`. -- `bun run build` sequentially invokes `build:client` and `build:server`; Vite configs separate client and server targets. The old V5 resolver Worker target was retired in Phase 10H. Preserve the remaining CI/CD entrypoints. -- Vitest uses node environment and discovers tests only under `src/shared`. -- Docker runtime contains only `dist`; ALTCHA uses the server-side `ALTCHA_HMAC_SECRET` and does not require a frontend site key. +- `dev[:api|:web]` selects `env/local.env`; `prd[:api|:web]` selects `env/staging.env`. Node/Nest explicitly loads the selected file; maintenance scripts use `node --env-file=... --import tsx`. +- `pnpm run build` uses Turbo to build the independent API and Web packages; Nest CLI 12 builds `apps/api` with its ESM Rspack builder; Vite builds `apps/web`. The old V5 resolver Worker target was retired in Phase 10H. Preserve the remaining CI/CD entrypoints. +- Vitest uses node environment and discovers tests only under `packages/shared/src`. +- Docker runtime contains Node, the pnpm-deployed API production dependencies, package metadata and `dist`; ALTCHA uses the server-side `ALTCHA_HMAC_SECRET` and does not require a frontend site key. - GitHub Actions currently builds and pushes the Docker image on tag pushes; it is not a lint/test quality gate. ## Skills To Use -- `daoyou-backend-api-security`: Hono routes, auth, admin, cron/internal APIs, LLM/provider security, Redis/SMTP integration boundaries. +- `daoyou-backend-api-security`: Nest controllers/guards, auth, admin, cron/internal APIs, LLM/provider security, Redis/SMTP integration boundaries. - `daoyou-data-layer`: Drizzle schema/migrations, repositories, transactions, Better Auth schema, durable models. - `daoyou-game-ui`: `GameViewportLayout` main-flow scene UI structure and review rules. - `daoyou-item-preview`: 新增道具或调整物品预览字段、文案、层级、交互与适配器时,遵循 [.agents/skills/daoyou-item-preview/SKILL.md](.agents/skills/daoyou-item-preview/SKILL.md) 的固定展示规范和分类基线,避免恢复已删除的冗余信息。 @@ -49,38 +53,40 @@ bun run db:migrate - `daoyou-map-art`: 世界总览与独立区域地图的国画水墨、彩墨、地域辨识、空间尺度及素材交付;见 [.agents/skills/daoyou-map-art/SKILL.md](.agents/skills/daoyou-map-art/SKILL.md)。 - `daoyou-game-core-domain`: combat-v6 core/rules/projection, sects, equipment, manuals, beasts, and shared inventory/reward rules. - `daoyou-beast-design`: 灵兽物种、生灵层次、命名、资质成长及出生技能池设计与审查;扩充或调整物种前读取 `.agents/skills/daoyou-beast-design/SKILL.md`,实施同时遵守领域技能。 -- Only the skills present in `.agents/skills/` are project skill entrypoints. For runtime work, inspect `package.json`, `src/index.ts`, Vite/Docker configs and `docs/local-development.md`; for condition/alchemy/market work, combine the domain, data and backend skills as applicable. +- Only the skills present in `.agents/skills/` are project skill entrypoints. For runtime work, inspect `package.json`, `apps/api/src/main.ts`, Vite/Docker configs and `docs/local-development.md`; for condition/alchemy/market work, combine the domain, data and backend skills as applicable. ## Architecture Rules -- New API routes go through `src/server/routes/api/index.ts` and existing Hono middleware: `requireUser`, `requireActiveCultivatorRef`, `requireAdmin`, `validateJson`, `validateQuery`. +- New API routes belong to Nest feature modules imported by `apps/api/src/app.module.ts`. Reuse `AccessGuard`/`@Access`, `SessionService`, `JsonBody`, `FirstQuery`, Zod pipes and existing exception filters. Use explicit constructor `@Inject`; the API build deliberately disables implicit design-type metadata. - Frontend route loaders are UX guards only; backend middleware is the security boundary. -- `/api/auth/*` is Better Auth through `src/server/lib/auth/hono.ts`. +- `/api/auth/*` is Better Auth through `apps/api/src/lib/auth/handler.ts`. - `/internal/cron/*` uses Bearer `CRON_SECRET` when configured; production requires it, while non-production without `CRON_SECRET` currently allows the request. -- Shared request/response contracts live in `src/shared/contracts`; domain DTO/types live in `src/shared/types`. -- LLM calls should use `src/server/utils/aiClient.ts`; BYOK validation truth is `src/shared/config/llm.ts`. Server routing is one `LLM_PROVIDER` table (`provider[/model][:weight]`) parsed in `src/shared/config/llmRouting.ts`; multiple routes are sticky by user id hash. Request BYOK still wins. +- Shared request/response contracts live in `packages/shared/src/contracts`; domain DTO/types live in `packages/shared/src/types`. +- LLM calls should use `apps/api/src/utils/aiClient.ts`; BYOK validation truth is `packages/shared/src/config/llm.ts`. Server routing is one `LLM_PROVIDER` table (`provider[/model][:weight]`) parsed in `packages/shared/src/config/llmRouting.ts`; multiple routes are sticky by user id hash. Request BYOK still wins. - Treat all LLM output as untrusted. Resource, reward, cost, drop, and other state-changing numbers need deterministic service/schema/resource-layer guards. -- Redis access must go through `src/server/lib/redis`; do not instantiate feature-local Redis clients. -- SMTP mail goes through `src/server/lib/admin/smtp.ts`. +- Server config: `apps/api/src/config/configuration.module.ts` uses `@nestjs/config`, ignores dotenv discovery, and publishes the validated immutable environment from `lib/config/environment.ts`. Use injected `runtimeConfig` in Nest services and that snapshot in framework-independent libraries; do not add direct `process.env` reads outside it. +- Redis access must go through `apps/api/src/lib/redis`; do not instantiate feature-local Redis clients. +- SMTP mail goes through `apps/api/src/lib/admin/smtp.ts`. ## Frontend Rules -- 新增或迁移图标渲染统一使用 `src/react-app/components/ui/GameIcon.tsx`:emoji 直接传值,SVG/WebP/PNG 使用 `icon:名称`;素材统一放在 `public/assets/icons/`,名称与路径只在 `components/ui/icons/registry.ts` 注册,业务组件不得自行解析协议或直接引用图标文件。见 `docs/game-icons.md`。 +- 新增或迁移图标渲染统一使用 `apps/web/src/components/ui/GameIcon.tsx`:emoji 直接传值,SVG/WebP/PNG 使用 `icon:名称`;素材统一放在 `apps/web/public/assets/icons/`,名称与路径只在 `components/ui/icons/registry.ts` 注册,业务组件不得自行解析协议或直接引用图标文件。见 `docs/game-icons.md`。 - Numeric data uses Tailwind default `font-mono`; prose inherits the body font. Keep quantity weight/spacing local (`font-semibold tracking-tight`), and do not override `--font-mono` or add numeric font tokens/classes. See `docs/numeric-typography.md`. -- React routes are centralized in `src/react-app/router.tsx` and loaded with `lazyRoute`. -- Game scenes use `handle={scene(...)}`; the scene id must exist in `src/react-app/components/game-shell/gameNavigation.ts`. +- React routes are centralized in `apps/web/src/router.tsx` and loaded with `lazyRoute`. +- Game scenes use `handle={scene(...)}`; the scene id must exist in `apps/web/src/components/game-shell/gameNavigation.ts`. - `/game` uses distinct genesis, narrative, viewport, activity, combat, map and dungeon layouts. V6 battles have `CombatV6Layout`; inspect `router.tsx` for the actual wrapper before changing a scene. - Main-flow game UI must follow `daoyou-game-ui`: identity layer, task layer, and navigation layer stay separate. - Do not add `InkPageShell` to game routes. -- Cross-route reusable UI belongs in `src/react-app/components/feature/**`, `src/react-app/components/ui/**`, or `src/react-app/components/game-shell/**`; `src/react-app/routes/game/**/components` is page-private. -- Reuse `src/react-app/lib/resources` hooks/store, `fetchJsonCached`, `useTaskList`, and provider contexts before adding new page-level state. +- Cross-route reusable UI belongs in `apps/web/src/components/feature/**`, `apps/web/src/components/ui/**`, or `apps/web/src/components/game-shell/**`; `apps/web/src/routes/game/**/components` is page-private. +- Reuse `apps/web/src/lib/resources` hooks/store, `fetchJsonCached`, `useTaskList`, and provider contexts before adding new page-level state. ## Data And Domain Rules -- Do not create parallel `src/db` or `src/server/db`; DB entrypoints are `src/server/lib/drizzle/db.ts` and `schema.ts`. +- Do not create parallel `src/db` or `apps/api/src/db`; DB entrypoints are `apps/api/src/lib/drizzle/db.ts` and `schema.ts`. - The main Drizzle Kit flow manages only `wanjiedaoyou_*` business tables. `drizzle.auth.config.ts` independently manages the fixed `better_auth` schema. +- Nest `DatabaseModule` exposes the same Drizzle client through `DRIZZLE_DATABASE`; never create a second pool. `RuntimeService` closes `DatabaseService` only after draining requests and messaging. - Pass `DbExecutor` / `DbTransaction` through write paths; do not open a fresh executor inside a transaction. - Current equipment/items use `inventory_items` and `cultivator_equipment_slots`; personal manuals and beasts belong to the cultivator, while sect combat progression belongs to membership. See `daoyou-data-layer` for tables and ownership. - Runtime DB access uses `pg.Pool` / node-postgres; use `runDbTasks` when a group of reads may run inside a transaction. @@ -92,7 +98,7 @@ bun run db:migrate ## High-Risk Areas -- Client/server build separation, `src/index.ts` startup/shutdown and `src/server/app.ts` routing. +- Client/server build separation, `apps/api/src/main.ts` startup/shutdown, runtime lifecycle and HTTP/WS configuration. - Auth, ALTCHA, Better Auth schema, admin allowlist, and session cookie passthrough. - LLM provider headers, prompt schemas, resource/reward/cost parsing, and metrics. - Drizzle migrations, legacy tables, JSONB model shape, and transaction boundaries. @@ -102,15 +108,15 @@ bun run db:migrate ## Verification Checklist -- Testing has only two layers: pure `src/shared` unit tests and Codex browser/Playwright simulations following `docs/testing.md`. Do not add one-off smoke, E2E, seed, benchmark, or fault-injection scripts. +- Testing has only two layers: pure `packages/shared/src` unit tests and Codex browser/Playwright simulations following `docs/testing.md`. Do not add one-off smoke, E2E, seed, benchmark, or fault-injection scripts. - Local browser test accounts and their shared password are documented in `docs/testing.md` section 3. Reuse them; query the local database read-only to select existing accounts and characters instead of asking the user for known credentials again. Complete email verification through Mailpit and never apply these credentials or test writes to staging/production. -- Unit tests are forbidden under `src/react-app` and `src/server`; do not add `*.test.*` or `*.spec.*` files there. -- New unit tests are allowed only for pure, deterministic, reusable engine/domain logic under `src/shared`. -- Do not write tests that exercise or mock databases, repositories, Hono routes, auth, Redis, LLM/SMTP providers, network APIs, or other third-party services. +- Unit tests are forbidden under `apps/web/src` and `apps/api/src`; do not add `*.test.*` or `*.spec.*` files there. +- New unit tests are allowed only for pure, deterministic, reusable engine/domain logic under `packages/shared/src`. +- Do not write tests that exercise or mock databases, repositories, HTTP controllers, auth, Redis, LLM/SMTP providers, network APIs, or other third-party services. - Frontend and backend changes must be verified with lint, typecheck/build, code inspection, and focused manual/runtime checks instead of unit tests. - For eligible shared engine changes, pick focused tests first, then broader shared-engine checks if the blast radius is large. -- Run `bun run lint`, `bun run test`, or `bun run build` when code/config changes justify it. +- Run `pnpm run lint`, `pnpm run test`, or `pnpm run build` when code/config changes justify it. - For route/layout changes, run lint/build and inspect the affected navigation and layout behavior manually. - For LLM/provider changes, run lint/build and inspect provider validation and runtime behavior without adding provider integration tests. - For data/model changes, inspect generated migrations, transaction boundaries, and build output; do not add database/repository tests. diff --git a/README.md b/README.md index a9b16294..22a64b37 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ # 万界道友

- 万界道友 Logo + 万界道友 Logo

一款 AIGC 驱动、高自由度文字体验、修仙世界观的开源游戏项目。

-> 本仓库当前实现为 `Hono + React SPA`。这里的说明以现有代码为准,已不再适用于旧版 Next.js 架构。 +> 本仓库默认入口已迁至 `NestJS + React SPA`,迁移收口进度见 [迁移记录](docs/nestjs-migration.md)。这里的说明以现有代码为准,已不再适用于旧版 Next.js 架构。 --- @@ -65,7 +65,7 @@ ## 技术概览 -- 服务端:`Hono 4` + `Bun` +- 服务端:`NestJS 12` + `Node.js 24`,pnpm workspace管理依赖,Turborepo编排任务 - 前端:`React 19` + `React Router 7` + `Vite 8` - 样式:`Tailwind CSS 4` - 数据库:`PostgreSQL` + `Drizzle ORM` @@ -78,22 +78,24 @@ ```text . -├── src/index.ts # Bun 后端入口,导出 Hono API 与 WebSocket 配置 -├── src/server/ # Hono API、认证、服务层、数据库访问 -├── src/react-app/ # React SPA -├── src/shared/ # 共享引擎、配置、类型、契约 -├── drizzle/ # 业务表 Drizzle migrations -├── drizzle-auth/ # Better Auth Drizzle migrations -├── scripts/ # 部署脚本与生产/NATS Compose -├── docker/Dockerfile.app # Bun 主服务镜像 -└── vite.config.ts +├── apps/api/ # NestJS API(nest build → dist/main.js) +│ ├── src/ # 按功能划分的 Nest 模块及服务端基础设施 +│ └── scripts/ # 数据维护工具 +├── apps/web/ # React SPA(Vite → dist/) +│ ├── src/ +│ └── public/ +├── packages/shared/src/ # 共享契约、类型及纯游戏规则 +├── drizzle/ # 业务表迁移 +├── drizzle-auth/ # Better Auth 独立迁移 +├── scripts/ # 部署、Compose 与 SponsorKit 工具 +└── docker/Dockerfile.app # 仅包含 API 生产依赖和构建产物 ``` ## 本地开发与部署 环境要求、环境变量、数据库初始化、本地开发、构建、Docker、生产 cron 与部署脚本说明已整理到 [docs/development.md](docs/development.md)。 -纯本地配置 `env/local.env` 随仓库提供,直接执行 `bun run services up -d --wait` → `bun run db:migrate` → `bun run dev`,访问 `http://127.0.0.1:5174`。预发布调试使用 `bun run prd`,读取被 Git 忽略的 `env/staging.env`;两组均可独立启动 `:api`/`:web`。详见 [本地开发指南](docs/local-development.md) 与 [测试规范](docs/testing.md)。 +首次将 `env/local.example.env` 复制为 `env/local.env`(已有文件保留),安装依赖后执行 `pnpm run services up -d --wait` → `pnpm run db:migrate` → `pnpm run dev`,访问 `http://127.0.0.1:5174`。预发布调试使用 `pnpm run prd`,读取被 Git 忽略的 `env/staging.env`;两组均可独立启动 `:api`/`:web`。详见 [本地开发指南](docs/local-development.md) 与 [测试规范](docs/testing.md)。 ## 贡献指南 diff --git a/SPONSORS.md b/SPONSORS.md index c50218ea..20ed3b3f 100644 --- a/SPONSORS.md +++ b/SPONSORS.md @@ -2,7 +2,7 @@ 感谢每一位帮助《万界道友》持续维护与成长的道友。新的赞助统一通过爱发电进行,既有历史记录继续保留。 -> 本页自动生成,请勿直接编辑。同步爱发电运行 `bun run sponsors:sync`;仅更新历史名单时运行 `bun run sponsors:render`。 +> 本页自动生成,请勿直接编辑。同步爱发电运行 `pnpm run sponsors:sync`;仅更新历史名单时运行 `pnpm run sponsors:render`。 最后更新:2026-08-07 diff --git a/apps/api/nest-cli.json b/apps/api/nest-cli.json new file mode 100644 index 00000000..2e2de591 --- /dev/null +++ b/apps/api/nest-cli.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "entryFile": "main", + "compilerOptions": { + "builder": { + "type": "rspack", + "options": { + "configPath": "rspack.config.mjs" + } + }, + "tsConfigPath": "tsconfig.build.json", + "deleteOutDir": true + } +} diff --git a/apps/api/package.json b/apps/api/package.json new file mode 100644 index 00000000..de4532d5 --- /dev/null +++ b/apps/api/package.json @@ -0,0 +1,60 @@ +{ + "name": "@daoyou/api", + "version": "0.1.0", + "private": true, + "type": "module", + "files": [ + "dist" + ], + "scripts": { + "build": "nest build", + "dev": "APP_ENV=local NODE_ENV=development nest start --watch --no-shell --env-file ../../env/local.env", + "prd": "APP_ENV=staging NODE_ENV=development nest start --watch --no-shell --env-file ../../env/staging.env", + "start": "node dist/main.js", + "typecheck": "tsc --noEmit -p tsconfig.json" + }, + "dependencies": { + "@ai-sdk/alibaba": "^2.0.45", + "@ai-sdk/deepseek": "^3.0.44", + "@better-auth/drizzle-adapter": "^1.7.4", + "@better-auth/i18n": "^1.7.4", + "@daoyou/shared": "workspace:*", + "@microfleet/ioredis-lock": "^6.0.1", + "@nestjs/common": "^12.1.2", + "@nestjs/config": "^12.0.1", + "@nestjs/core": "^12.1.2", + "@nestjs/platform-express": "^12.1.2", + "@nestjs/platform-ws": "^12.1.2", + "@nestjs/schedule": "^12.0.2", + "@nestjs/websockets": "^12.1.2", + "ai": "^7.0.99", + "altcha-lib": "^2.5.0", + "better-auth": "^1.7.4", + "cors": "^2.8.6", + "cron": "^4.4.0", + "drizzle-orm": "^0.45.2", + "express": "^5.2.1", + "ioredis": "^5.11.1", + "nats": "^2.29.3", + "nodemailer": "^9.1.1", + "pg": "^8.23.0", + "raw-body": "^3.0.2", + "reflect-metadata": "^0.2.2", + "rxjs": "^7.8.2", + "ws": "^8.22.0", + "zod": "^4.6.4" + }, + "devDependencies": { + "@nestjs/cli": "^12.0.8", + "@rspack/core": "^1.5.8", + "@types/cors": "^2.8.19", + "@types/express": "^5.0.6", + "@types/nodemailer": "^8.0.1", + "@types/pg": "^8.23.1", + "@types/ws": "^8.18.2", + "fork-ts-checker-webpack-plugin": "^9.1.0", + "tsconfig-paths-webpack-plugin": "^4.2.0", + "webpack": "^5.105.4", + "webpack-node-externals": "^3.0.0" + } +} diff --git a/apps/api/rspack.config.mjs b/apps/api/rspack.config.mjs new file mode 100644 index 00000000..7dd99967 --- /dev/null +++ b/apps/api/rspack.config.mjs @@ -0,0 +1,38 @@ +import { readFileSync } from 'node:fs'; +import { builtinModules } from 'node:module'; + +const { dependencies } = JSON.parse( + readFileSync(new URL('./package.json', import.meta.url), 'utf8'), +); + +// Nest CLI 12's ESM builder bundles workspace sources and Markdown. +// Runtime npm dependencies stay external for Node and the production image. +export default (options) => { + options.module.rules[0].use[0].options.jsc.transform.decoratorMetadata = false; + return { + ...options, + devtool: 'source-map', + externalsPresets: { node: false }, + externals: [ + ({ request }, callback) => { + const packageName = request?.startsWith('@') + ? request.split('/').slice(0, 2).join('/') + : request?.split('/')[0]; + if ( + request && + !request.startsWith('@daoyou/shared/') && + (request.startsWith('node:') || + builtinModules.includes(request) || + dependencies[packageName]) + ) { + return callback(null, `module ${request}`); + } + callback(); + }, + ], + module: { + ...options.module, + rules: [...options.module.rules, { test: /\.md$/, type: 'asset/source' }], + }, + }; +}; diff --git a/scripts/migrate-sect-task-battles-v6.ts b/apps/api/scripts/migrate-sect-task-battles-v6.ts similarity index 93% rename from scripts/migrate-sect-task-battles-v6.ts rename to apps/api/scripts/migrate-sect-task-battles-v6.ts index 398f2eab..4e0c52e1 100644 --- a/scripts/migrate-sect-task-battles-v6.ts +++ b/apps/api/scripts/migrate-sect-task-battles-v6.ts @@ -1,8 +1,8 @@ /** Maintenance command: default read-only inventory; --apply requires explicit record IDs. */ import { and, eq, inArray, sql } from 'drizzle-orm'; import { z } from 'zod'; -import { db } from '../src/server/lib/drizzle/db'; -import { sectTaskRecords } from '../src/server/lib/drizzle/schema'; +import { db } from '../src/lib/drizzle/db'; +import { sectTaskRecords } from '../src/lib/drizzle/schema'; const args = process.argv.slice(2); const apply = args.includes('--apply'); diff --git a/scripts/recalculate-pill-inventory.ts b/apps/api/scripts/recalculate-pill-inventory.ts similarity index 91% rename from scripts/recalculate-pill-inventory.ts rename to apps/api/scripts/recalculate-pill-inventory.ts index 873ebf13..2e0ce2dc 100644 --- a/scripts/recalculate-pill-inventory.ts +++ b/apps/api/scripts/recalculate-pill-inventory.ts @@ -2,18 +2,18 @@ import { and, asc, eq, sql } from 'drizzle-orm'; import { createHash } from 'node:crypto'; import { z } from 'zod'; -import { db, type DbExecutor } from '../src/server/lib/drizzle/db'; -import { inventoryItems } from '../src/server/lib/drizzle/schema'; -import { redisLockKeys, withRedisLock } from '../src/server/lib/redis/lock'; -import { lockCultivatorForStateMutation } from '../src/server/lib/repositories/playerStateRepository'; +import { db, type DbExecutor } from '../src/lib/drizzle/db'; +import { inventoryItems } from '../src/lib/drizzle/schema'; +import { redisLockKeys, withRedisLock } from '../src/lib/redis/lock'; +import { lockCultivatorForStateMutation } from '../src/lib/repositories/playerStateRepository'; import { assertInventoryIdle, inventoryItemOf, saveInventoryPlan, -} from '../src/server/lib/services/InventoryService'; -import { inventoryStackKey } from '../src/server/lib/services/inventoryStackKey'; -import { ResourceEventCommitter } from '../src/server/lib/services/ResourceEventCommitter'; -import { compactStorage, type InventoryItem } from '../src/shared/inventory'; +} from '../src/lib/services/InventoryService'; +import { inventoryStackKey } from '../src/lib/services/inventoryStackKey'; +import { ResourceEventCommitter } from '../src/lib/services/ResourceEventCommitter'; +import { compactStorage, type InventoryItem } from '../../../packages/shared/src/inventory'; const args = process.argv.slice(2); const all = args.includes('--all'); diff --git a/scripts/retire-sect-artifact-deliveries.ts b/apps/api/scripts/retire-sect-artifact-deliveries.ts similarity index 94% rename from scripts/retire-sect-artifact-deliveries.ts rename to apps/api/scripts/retire-sect-artifact-deliveries.ts index 7b147fac..32e7654f 100644 --- a/scripts/retire-sect-artifact-deliveries.ts +++ b/apps/api/scripts/retire-sect-artifact-deliveries.ts @@ -1,8 +1,8 @@ /** Offline maintenance only. No conversion of old requirements into new equipment rules. */ import { and, inArray, sql } from 'drizzle-orm'; import { z } from 'zod'; -import { db } from '../src/server/lib/drizzle/db'; -import { sectTaskRecords } from '../src/server/lib/drizzle/schema'; +import { db } from '../src/lib/drizzle/db'; +import { sectTaskRecords } from '../src/lib/drizzle/schema'; const args = process.argv.slice(2); const apply = args.includes('--apply'); diff --git a/apps/api/src/account/account.controller.ts b/apps/api/src/account/account.controller.ts new file mode 100644 index 00000000..11639520 --- /dev/null +++ b/apps/api/src/account/account.controller.ts @@ -0,0 +1,35 @@ +import { Controller, HttpCode, Inject, Post, Req } from '@nestjs/common'; +import { + AccountSetPasswordRequestSchema, + type AccountSetPasswordRequest, + type AccountSetPasswordResponse, +} from '@daoyou/shared/contracts/account'; +import { fromNodeHeaders } from 'better-auth/node'; +import type { Request } from 'express'; +import { SessionService } from '../auth/session.service'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; + +@Controller('api/account') +export class AccountController { + constructor( + @Inject(SessionService) private readonly sessions: SessionService, + ) {} + + @Post('password') + @HttpCode(200) + async setPassword( + @Req() request: Request, + @JsonBody( + { fallback: undefined }, + new ZodPipe(AccountSetPasswordRequestSchema), + ) + body: AccountSetPasswordRequest, + ): Promise { + const result = await this.sessions.setPassword( + fromNodeHeaders(request.headers), + body.newPassword, + ); + return { success: true, data: { status: result.status } }; + } +} diff --git a/apps/api/src/account/account.module.ts b/apps/api/src/account/account.module.ts new file mode 100644 index 00000000..ebd3b609 --- /dev/null +++ b/apps/api/src/account/account.module.ts @@ -0,0 +1,6 @@ +import { Module } from '@nestjs/common'; +import { AuthModule } from '../auth/auth.module'; +import { AccountController } from './account.controller'; + +@Module({ imports: [AuthModule], controllers: [AccountController] }) +export class AccountModule {} diff --git a/apps/api/src/admin/accounts.controller.ts b/apps/api/src/admin/accounts.controller.ts new file mode 100644 index 00000000..f4ce9eaa --- /dev/null +++ b/apps/api/src/admin/accounts.controller.ts @@ -0,0 +1,137 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Param, + Post, + Req, + Res, + UseFilters, +} from '@nestjs/common'; +import type { AuthUser } from '@server/lib/auth/types'; +import { + AdminAccountBanRequestSchema, + AdminAccountChangeEmailRequestSchema, + AdminAccountListQuerySchema, + type AdminAccountBanRequest, + type AdminAccountChangeEmailRequest, + type AdminAccountListQuery, +} from '@daoyou/shared/contracts/adminAccounts'; +import { fromNodeHeaders } from 'better-auth/node'; +import type { Request, Response } from 'express'; +import { Access, CurrentUser } from '../auth/access'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { AccountsService } from './accounts.service'; +import { AdminErrors } from './admin-errors'; + +function authHeaders(response: Response) { + return (headers: Headers) => { + headers.forEach((value, key) => { + if (key.toLowerCase() !== 'set-cookie') response.setHeader(key, value); + }); + for (const cookie of headers.getSetCookie()) + response.append('Set-Cookie', cookie); + }; +} + +@Controller('api/admin/accounts') +@Access('account-admin') +@UseFilters(AdminErrors) +export class AccountsController { + constructor( + @Inject(AccountsService) private readonly accounts: AccountsService, + ) {} + + @Get() + list( + @Req() request: Request, + @Res({ passthrough: true }) response: Response, + @FirstQuery(new ZodPipe(AdminAccountListQuerySchema, 'legacy-unhandled')) + query: AdminAccountListQuery, + ) { + return this.accounts.list( + fromNodeHeaders(request.headers), + query, + authHeaders(response), + ); + } + + @Post(':userId/change-email') + @HttpCode(200) + changeEmail( + @Req() request: Request, + @Res({ passthrough: true }) response: Response, + @CurrentUser() actor: AuthUser, + @Param('userId') userId: string, + @JsonBody( + { fallback: undefined }, + new ZodPipe(AdminAccountChangeEmailRequestSchema, 'legacy-unhandled'), + ) + input: AdminAccountChangeEmailRequest, + ) { + return this.accounts.changeEmail( + fromNodeHeaders(request.headers), + actor, + userId, + input, + authHeaders(response), + ); + } + + @Post(':userId/ban') + @HttpCode(200) + ban( + @Req() request: Request, + @Res({ passthrough: true }) response: Response, + @CurrentUser() actor: AuthUser, + @Param('userId') userId: string, + @JsonBody( + { fallback: undefined }, + new ZodPipe(AdminAccountBanRequestSchema, 'legacy-unhandled'), + ) + input: AdminAccountBanRequest, + ) { + return this.accounts.ban( + fromNodeHeaders(request.headers), + actor, + userId, + input, + authHeaders(response), + ); + } + + @Post(':userId/unban') + @HttpCode(200) + unban( + @Req() request: Request, + @Res({ passthrough: true }) response: Response, + @CurrentUser() actor: AuthUser, + @Param('userId') userId: string, + ) { + return this.accounts.unban( + fromNodeHeaders(request.headers), + actor, + userId, + authHeaders(response), + ); + } + + @Post(':userId/revoke-sessions') + @HttpCode(200) + revokeSessions( + @Req() request: Request, + @Res({ passthrough: true }) response: Response, + @CurrentUser() actor: AuthUser, + @Param('userId') userId: string, + ) { + return this.accounts.revokeSessions( + fromNodeHeaders(request.headers), + actor, + userId, + authHeaders(response), + ); + } +} diff --git a/src/server/routes/api/admin/accounts.router.ts b/apps/api/src/admin/accounts.service.ts similarity index 70% rename from src/server/routes/api/admin/accounts.router.ts rename to apps/api/src/admin/accounts.service.ts index 5ca29377..d5e87929 100644 --- a/src/server/routes/api/admin/accounts.router.ts +++ b/apps/api/src/admin/accounts.service.ts @@ -1,20 +1,10 @@ +import { HttpException, Injectable } from '@nestjs/common'; import { auth } from '@server/lib/auth/auth'; import { authAccounts, authSessions } from '@server/lib/auth/schema'; +import type { AuthUser } from '@server/lib/auth/types'; import { getExecutor, runDbTasks } from '@server/lib/drizzle/db'; import { cultivators } from '@server/lib/drizzle/schema'; import { - applyAuthHeaders, - getValidatedJson, - getValidatedQuery, - requireBetterAuthAdmin, - validateJson, - validateQuery, -} from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { - AdminAccountBanRequestSchema, - AdminAccountChangeEmailRequestSchema, - AdminAccountListQuerySchema, AdminAccountParamsSchema, type AdminAccountBanDuration, type AdminAccountBanRequest, @@ -22,13 +12,10 @@ import { type AdminAccountErrorResponse, type AdminAccountListItem, type AdminAccountListQuery, -} from '@shared/contracts/adminAccounts'; +} from '@daoyou/shared/contracts/adminAccounts'; import { APIError } from 'better-auth/api'; import { and, eq, inArray } from 'drizzle-orm'; -import type { Context } from 'hono'; -import { Hono } from 'hono'; - -const router = new Hono(); +type AuthHeaders = (headers: Headers) => void; const BAN_DURATION_SECONDS: Record< Exclude, @@ -69,13 +56,12 @@ function getAuthErrorDetails(error: unknown) { } function accountError( - context: Context, error: string, status: 400 | 403 | 404 | 409 | 500, code?: string, partial?: AdminAccountErrorResponse['partial'], -) { - return context.json( +): never { + throw new HttpException( { success: false, error, @@ -86,29 +72,29 @@ function accountError( ); } -function mapBetterAuthError(context: Context, error: unknown) { +function mapBetterAuthError(error: unknown): never { const details = getAuthErrorDetails(error); if ( details.code === 'USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL' || details.code === 'USER_ALREADY_EXISTS' ) { - return accountError(context, '该邮箱已被其他账号占用', 409, details.code); + return accountError('该邮箱已被其他账号占用', 409, details.code); } if (details.statusCode === 404 || details.code === 'USER_NOT_FOUND') { - return accountError(context, '目标账号不存在', 404, details.code); + return accountError('目标账号不存在', 404, details.code); } if (details.statusCode === 403) { - return accountError(context, '无账号管理权限', 403, details.code); + return accountError('无账号管理权限', 403, details.code); } if (details.code === 'YOU_CANNOT_BAN_YOURSELF') { - return accountError(context, '不能封禁当前管理员账号', 409, details.code); + return accountError('不能封禁当前管理员账号', 409, details.code); } - return accountError(context, 'Better Auth 操作失败', 500, details.code); + return accountError('Better Auth 操作失败', 500, details.code); } function logAccountEmailOperation(event: { @@ -158,18 +144,19 @@ function logAccountModerationOperation(event: { console.error('[admin-account-moderation]', payload); } -router.get( - '/', - requireBetterAuthAdmin(), - validateQuery(AdminAccountListQuerySchema), - async (c) => { - const query = getValidatedQuery(c); +@Injectable() +export class AccountsService { + async list( + headers: Headers, + query: AdminAccountListQuery, + onHeaders: AuthHeaders, + ) { const offset = (query.page - 1) * query.limit; let result; try { result = await auth.api.listUsers({ - headers: c.req.raw.headers, + headers: headers, query: { limit: query.limit, offset, @@ -193,10 +180,10 @@ router.get( returnHeaders: true, }); } catch (error) { - return mapBetterAuthError(c, error); + return mapBetterAuthError(error); } - applyAuthHeaders(c, result.headers); + onHeaders(result.headers); const users = result.response.users; const userIds = users.map((user) => user.id); @@ -303,7 +290,7 @@ router.get( }; }); - return c.json({ + return { success: true, data: { accounts, @@ -311,32 +298,32 @@ router.get( page: query.page, limit: query.limit, }, - }); - }, -); + }; + } -router.post( - '/:userId/change-email', - requireBetterAuthAdmin(), - validateJson(AdminAccountChangeEmailRequestSchema), - async (c) => { - const params = AdminAccountParamsSchema.safeParse(c.req.param()); + async changeEmail( + headers: Headers, + actor: AuthUser, + userId: string, + input: AdminAccountChangeEmailRequest, + onHeaders: AuthHeaders, + ) { + const params = AdminAccountParamsSchema.safeParse({ userId }); if (!params.success) { - return accountError(c, '目标账号 ID 格式错误', 400, 'INVALID_USER_ID'); + return accountError('目标账号 ID 格式错误', 400, 'INVALID_USER_ID'); } - const operator = c.get('user'); - const input = getValidatedJson(c); + const operator = actor; const targetUserId = params.data.userId; let target; try { const result = await auth.api.getUser({ - headers: c.req.raw.headers, + headers: headers, query: { id: targetUserId }, returnHeaders: true, }); - applyAuthHeaders(c, result.headers); + onHeaders(result.headers); target = result.response; } catch (error) { const details = getAuthErrorDetails(error); @@ -348,7 +335,7 @@ router.post( newEmail: input.newEmail, errorCode: details.code, }); - return mapBetterAuthError(c, error); + return mapBetterAuthError(error); } const currentEmail = target.email.trim().toLowerCase(); @@ -364,7 +351,6 @@ router.post( errorCode: 'STALE_CURRENT_EMAIL', }); return accountError( - c, '账号邮箱已发生变化,请刷新列表后重试', 409, 'STALE_CURRENT_EMAIL', @@ -382,18 +368,13 @@ router.post( oldEmailVerified: target.emailVerified, errorCode: 'EMAIL_UNCHANGED', }); - return accountError( - c, - '新邮箱不能与当前邮箱相同', - 409, - 'EMAIL_UNCHANGED', - ); + return accountError('新邮箱不能与当前邮箱相同', 409, 'EMAIL_UNCHANGED'); } let updatedUser; try { const result = await auth.api.adminUpdateUser({ - headers: c.req.raw.headers, + headers: headers, body: { userId: targetUserId, data: { @@ -403,7 +384,7 @@ router.post( }, returnHeaders: true, }); - applyAuthHeaders(c, result.headers); + onHeaders(result.headers); updatedUser = result.response; } catch (error) { const details = getAuthErrorDetails(error); @@ -417,16 +398,16 @@ router.post( oldEmailVerified: target.emailVerified, errorCode: details.code, }); - return mapBetterAuthError(c, error); + return mapBetterAuthError(error); } try { const result = await auth.api.revokeUserSessions({ - headers: c.req.raw.headers, + headers: headers, body: { userId: targetUserId }, returnHeaders: true, }); - applyAuthHeaders(c, result.headers); + onHeaders(result.headers); } catch (error) { const details = getAuthErrorDetails(error); logAccountEmailOperation({ @@ -440,7 +421,6 @@ router.post( errorCode: details.code, }); return accountError( - c, '邮箱已改绑,但旧会话撤销失败,请刷新后重试下线', 500, 'EMAIL_CHANGED_SESSION_REVOKE_FAILED', @@ -463,7 +443,7 @@ router.post( oldEmailVerified: target.emailVerified, }); - return c.json({ + return { success: true, data: { userId: targetUserId, @@ -471,24 +451,23 @@ router.post( emailVerified: false as const, sessionsRevoked: true as const, }, - }); - }, -); + }; + } -router.post( - '/:userId/ban', - requireBetterAuthAdmin(), - validateJson(AdminAccountBanRequestSchema), - async (c) => { - const params = AdminAccountParamsSchema.safeParse(c.req.param()); + async ban( + headers: Headers, + actor: AuthUser, + userId: string, + input: AdminAccountBanRequest, + onHeaders: AuthHeaders, + ) { + const params = AdminAccountParamsSchema.safeParse({ userId }); if (!params.success) { - return accountError(c, '目标账号 ID 格式错误', 400, 'INVALID_USER_ID'); + return accountError('目标账号 ID 格式错误', 400, 'INVALID_USER_ID'); } - const operator = c.get('user'); + const operator = actor; const targetUserId = params.data.userId; - const input = getValidatedJson(c); - if (operator?.id === targetUserId) { logAccountModerationOperation({ action: 'ban', @@ -500,7 +479,6 @@ router.post( errorCode: 'YOU_CANNOT_BAN_YOURSELF', }); return accountError( - c, '不能封禁当前管理员账号', 409, 'YOU_CANNOT_BAN_YOURSELF', @@ -509,7 +487,7 @@ router.post( try { const result = await auth.api.banUser({ - headers: c.req.raw.headers, + headers: headers, body: { userId: targetUserId, banReason: input.reason, @@ -519,7 +497,7 @@ router.post( }, returnHeaders: true, }); - applyAuthHeaders(c, result.headers); + onHeaders(result.headers); const user = result.response.user; logAccountModerationOperation({ @@ -531,7 +509,7 @@ router.post( reason: input.reason, }); - return c.json({ + return { success: true, data: { userId: targetUserId, @@ -540,7 +518,7 @@ router.post( banExpires: toIsoString(user.banExpires), sessionsRevoked: true, }, - }); + }; } catch (error) { const details = getAuthErrorDetails(error); logAccountModerationOperation({ @@ -552,109 +530,117 @@ router.post( reason: input.reason, errorCode: details.code, }); - return mapBetterAuthError(c, error); + return mapBetterAuthError(error); } - }, -); - -router.post('/:userId/unban', requireBetterAuthAdmin(), async (c) => { - const params = AdminAccountParamsSchema.safeParse(c.req.param()); - if (!params.success) { - return accountError(c, '目标账号 ID 格式错误', 400, 'INVALID_USER_ID'); } - const operator = c.get('user'); - const targetUserId = params.data.userId; + async unban( + headers: Headers, + actor: AuthUser, + userId: string, + onHeaders: AuthHeaders, + ) { + const params = AdminAccountParamsSchema.safeParse({ userId }); + if (!params.success) { + return accountError('目标账号 ID 格式错误', 400, 'INVALID_USER_ID'); + } - try { - const result = await auth.api.unbanUser({ - headers: c.req.raw.headers, - body: { userId: targetUserId }, - returnHeaders: true, - }); - applyAuthHeaders(c, result.headers); + const operator = actor; + const targetUserId = params.data.userId; - logAccountModerationOperation({ - action: 'unban', - outcome: 'success', - operatorUserId: operator?.id ?? '', - targetUserId, - }); + try { + const result = await auth.api.unbanUser({ + headers: headers, + body: { userId: targetUserId }, + returnHeaders: true, + }); + onHeaders(result.headers); - return c.json({ - success: true, - data: { - userId: targetUserId, - banned: false, - banReason: null, - banExpires: null, - sessionsRevoked: false, - }, - }); - } catch (error) { - const details = getAuthErrorDetails(error); - logAccountModerationOperation({ - action: 'unban', - outcome: 'failure', - operatorUserId: operator?.id ?? '', - targetUserId, - errorCode: details.code, - }); - return mapBetterAuthError(c, error); - } -}); + logAccountModerationOperation({ + action: 'unban', + outcome: 'success', + operatorUserId: operator?.id ?? '', + targetUserId, + }); -router.post('/:userId/revoke-sessions', requireBetterAuthAdmin(), async (c) => { - const params = AdminAccountParamsSchema.safeParse(c.req.param()); - if (!params.success) { - return accountError(c, '目标账号 ID 格式错误', 400, 'INVALID_USER_ID'); + return { + success: true, + data: { + userId: targetUserId, + banned: false, + banReason: null, + banExpires: null, + sessionsRevoked: false, + }, + }; + } catch (error) { + const details = getAuthErrorDetails(error); + logAccountModerationOperation({ + action: 'unban', + outcome: 'failure', + operatorUserId: operator?.id ?? '', + targetUserId, + errorCode: details.code, + }); + return mapBetterAuthError(error); + } } - const operator = c.get('user'); - const targetUserId = params.data.userId; + async revokeSessions( + headers: Headers, + actor: AuthUser, + userId: string, + onHeaders: AuthHeaders, + ) { + const params = AdminAccountParamsSchema.safeParse({ userId }); + if (!params.success) { + return accountError('目标账号 ID 格式错误', 400, 'INVALID_USER_ID'); + } - try { - const userResult = await auth.api.getUser({ - headers: c.req.raw.headers, - query: { id: targetUserId }, - returnHeaders: true, - }); - applyAuthHeaders(c, userResult.headers); + const operator = actor; + const targetUserId = params.data.userId; - const revokeResult = await auth.api.revokeUserSessions({ - headers: c.req.raw.headers, - body: { userId: targetUserId }, - returnHeaders: true, - }); - applyAuthHeaders(c, revokeResult.headers); + try { + const userResult = await auth.api.getUser({ + headers: headers, + query: { id: targetUserId }, + returnHeaders: true, + }); + onHeaders(userResult.headers); - console.info('[admin-account-session-revoke]', { - event: 'admin_account_session_revoke', - occurredAt: new Date().toISOString(), - outcome: 'success', - operatorUserId: operator?.id ?? '', - targetUserId, - }); + const revokeResult = await auth.api.revokeUserSessions({ + headers: headers, + body: { userId: targetUserId }, + returnHeaders: true, + }); + onHeaders(revokeResult.headers); - return c.json({ - success: true, - data: { - userId: targetUserId, - sessionsRevoked: true as const, - }, - }); - } catch (error) { - const details = getAuthErrorDetails(error); - console.error('[admin-account-session-revoke]', { - event: 'admin_account_session_revoke', - occurredAt: new Date().toISOString(), - outcome: 'failure', - operatorUserId: operator?.id ?? '', - targetUserId, - errorCode: details.code, - }); - return mapBetterAuthError(c, error); + console.info('[admin-account-session-revoke]', { + event: 'admin_account_session_revoke', + occurredAt: new Date().toISOString(), + outcome: 'success', + operatorUserId: operator?.id ?? '', + targetUserId, + }); + + return { + success: true, + data: { + userId: targetUserId, + sessionsRevoked: true as const, + }, + }; + } catch (error) { + const details = getAuthErrorDetails(error); + console.error('[admin-account-session-revoke]', { + event: 'admin_account_session_revoke', + occurredAt: new Date().toISOString(), + outcome: 'failure', + operatorUserId: operator?.id ?? '', + targetUserId, + errorCode: details.code, + }); + return mapBetterAuthError(error); + } } -}); - -export default router; +} diff --git a/apps/api/src/admin/admin-errors.ts b/apps/api/src/admin/admin-errors.ts new file mode 100644 index 00000000..978b1e9d --- /dev/null +++ b/apps/api/src/admin/admin-errors.ts @@ -0,0 +1,12 @@ +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import { apiErrorFilter } from '../http/error-filter'; + +export const AdminErrors = apiErrorFilter((error) => { + const lock = redisLockErrorResponse(error); + if (lock) return lock; + console.error('Admin API error:', error); + return Response.json( + { success: false, error: '服务器内部错误' }, + { status: 500 }, + ); +}); diff --git a/apps/api/src/admin/admin.controller.ts b/apps/api/src/admin/admin.controller.ts new file mode 100644 index 00000000..ff1a4901 --- /dev/null +++ b/apps/api/src/admin/admin.controller.ts @@ -0,0 +1,82 @@ +import { + Controller, + Get, + Inject, + Param, + Patch, + UseFilters, +} from '@nestjs/common'; +import type { AuthUser } from '@server/lib/auth/types'; +import { Access, CurrentUser } from '../auth/access'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody } from '../http/json-body'; +import { AdminErrors } from './admin-errors'; +import { AdminFeedbackService } from './feedback.service'; +import { MonitoringService } from './monitoring.service'; +import { SettingsService } from './settings.service'; + +@Controller('api/admin') +@Access('admin') +@UseFilters(AdminErrors) +export class AdminController { + constructor( + @Inject(SettingsService) private readonly settings: SettingsService, + @Inject(MonitoringService) private readonly monitoring: MonitoringService, + @Inject(AdminFeedbackService) + private readonly feedback: AdminFeedbackService, + ) {} + + @Get('session') + session(@CurrentUser() user: AuthUser) { + return { success: true, userId: user.id, email: user.email }; + } + + @Get('announcement') + announcement() { + return this.settings.announcement(); + } + + @Patch('announcement') + updateAnnouncement( + @CurrentUser() user: AuthUser, + @JsonBody({ fallback: null }) input: unknown, + ) { + return this.settings.updateAnnouncement(user.id, input); + } + + @Get('community-group') + community() { + return this.settings.community(); + } + + @Patch('community-group') + updateCommunity( + @CurrentUser() user: AuthUser, + @JsonBody({ fallback: null }) input: unknown, + ) { + return this.settings.updateCommunity(user.id, input); + } + + @Get('llm-metrics') + llmMetrics(@FirstQuery() query: Record) { + return this.monitoring.llmMetrics(query); + } + + @Get('online-users') + onlineUsers() { + return this.monitoring.onlineUsers(); + } + + @Get('feedback') + listFeedback(@FirstQuery() query: Record) { + return this.feedback.list(query); + } + + @Patch('feedback/:id/status') + updateFeedback( + @Param('id') id: string, + @JsonBody({ fallback: null }) input: unknown, + ) { + return this.feedback.update(id, input); + } +} diff --git a/apps/api/src/admin/admin.module.ts b/apps/api/src/admin/admin.module.ts new file mode 100644 index 00000000..fb590578 --- /dev/null +++ b/apps/api/src/admin/admin.module.ts @@ -0,0 +1,53 @@ +import { Module } from '@nestjs/common'; +import { AccountsController } from './accounts.controller'; +import { AccountsService } from './accounts.service'; +import { AdminController } from './admin.controller'; +import { AdminFeedbackService } from './feedback.service'; +import { AdminItemLibraryController } from './item-library.controller'; +import { AdminItemLibraryService } from './item-library.service'; +import { MonitoringService } from './monitoring.service'; +import { AdminRedeemCodesController } from './redeem-codes.controller'; +import { AdminRedeemCodesService } from './redeem-codes.service'; +import { AdminReputationShopController } from './reputation-shop.controller'; +import { AdminReputationShopService } from './reputation-shop.service'; +import { AdminRewardItemsController } from './reward-items.controller'; +import { AdminRewardItemsService } from './reward-items.service'; +import { AdminSectShopController } from './sect-shop.controller'; +import { AdminSectShopService } from './sect-shop.service'; +import { SettingsService } from './settings.service'; +import { AdminSponsorshipController } from './sponsorship.controller'; +import { AdminSponsorshipService } from './sponsorship.service'; +import { AdminSystemMailsController } from './system-mails.controller'; +import { AdminSystemMailsService } from './system-mails.service'; +import { AdminTowerController } from './tower.controller'; +import { AdminTowerService } from './tower.service'; + +@Module({ + controllers: [ + AdminSystemMailsController, + AdminItemLibraryController, + AdminRedeemCodesController, + AdminSponsorshipController, + AdminController, + AccountsController, + AdminReputationShopController, + AdminSectShopController, + AdminRewardItemsController, + AdminTowerController, + ], + providers: [ + AdminSystemMailsService, + AdminItemLibraryService, + AdminRedeemCodesService, + AdminSponsorshipService, + AccountsService, + SettingsService, + MonitoringService, + AdminFeedbackService, + AdminReputationShopService, + AdminSectShopService, + AdminRewardItemsService, + AdminTowerService, + ], +}) +export class AdminModule {} diff --git a/apps/api/src/admin/feedback.service.ts b/apps/api/src/admin/feedback.service.ts new file mode 100644 index 00000000..0bc4eb68 --- /dev/null +++ b/apps/api/src/admin/feedback.service.ts @@ -0,0 +1,235 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import { getExecutor } from '@server/lib/drizzle/db'; +import { cultivators } from '@server/lib/drizzle/schema'; +import { + findFeedbackById, + findFeedbacks, + updateFeedbackStatus, + type FeedbackStatus, + type FeedbackType, +} from '@server/lib/repositories/feedbackRepository'; +import { MailService } from '@server/lib/services/MailService'; +import { updateSpiritStones } from '@server/lib/services/cultivator/CultivatorStateRepository'; +import { and, eq, inArray } from 'drizzle-orm'; +import { z } from 'zod'; + +const VALID_STATUSES: FeedbackStatus[] = [ + 'pending', + 'processing', + 'resolved', + 'closed', +]; + +const VALID_TYPES: FeedbackType[] = ['bug', 'feature', 'balance', 'other']; + +const STATUS_LABELS: Record = { + pending: '待处理', + processing: '处理中', + resolved: '已解决', + closed: '已关闭', +}; + +const TYPE_LABELS: Record = { + bug: 'Bug 反馈', + feature: '功能建议', + balance: '游戏平衡', + other: '其他意见', +}; + +const UpdateFeedbackStatusSchema = z.object({ + status: z.enum(['pending', 'processing', 'resolved', 'closed']), + adminMessage: z.string().trim().max(1000).optional(), +}); + +const FEEDBACK_RESOLVE_REWARD = 8000; + +function buildFeedbackStatusMailContent(params: { + feedbackType: FeedbackType; + status: FeedbackStatus; + adminMessage?: string; + feedbackContent: string; +}) { + const message = params.adminMessage?.trim(); + + const lines = [ + '你提交的反馈工单状态已更新。', + '', + `反馈类型:${TYPE_LABELS[params.feedbackType]}`, + `当前状态:${STATUS_LABELS[params.status]}`, + '', + '── 你的反馈内容 ──', + params.feedbackContent, + '──────────────', + '', + `管理员留言:${message || '感谢你的反馈,我们会持续跟进。'}`, + ]; + + if (params.status === 'resolved') { + lines.push( + '', + `奖励发放:你的问题已解决,特发放 ${FEEDBACK_RESOLVE_REWARD} 灵石作为奖励。`, + ); + } + + return lines.join('\n'); +} + +@Injectable() +export class AdminFeedbackService { + async list(query: Record) { + const q = getExecutor(); + const page = Number.parseInt(query.page ?? '1', 10); + const limit = Number.parseInt(query.limit ?? '20', 10); + const status = query.status as FeedbackStatus | undefined; + const type = query.type as FeedbackType | undefined; + const search = query.search?.trim() || undefined; + + const { feedbacks, total } = await findFeedbacks({ + status: status && VALID_STATUSES.includes(status) ? status : undefined, + type: type && VALID_TYPES.includes(type) ? type : undefined, + search, + page, + limit, + }); + + const cultivatorIds = Array.from( + new Set( + feedbacks + .map((feedback) => feedback.cultivatorId) + .filter((id): id is string => Boolean(id)), + ), + ); + + const cultivatorMap = new Map< + string, + { name: string | null; realm: string | null } + >(); + + if (cultivatorIds.length > 0) { + const cultivatorRows = await q + .select({ + id: cultivators.id, + name: cultivators.name, + realm: cultivators.realm, + }) + .from(cultivators) + .where(inArray(cultivators.id, cultivatorIds)); + + for (const row of cultivatorRows) { + cultivatorMap.set(row.id, { + name: row.name, + realm: row.realm, + }); + } + } + + const enrichedFeedbacks = feedbacks.map((feedback) => { + const cultivator = feedback.cultivatorId + ? cultivatorMap.get(feedback.cultivatorId) + : null; + + return { + ...feedback, + cultivatorName: cultivator?.name ?? null, + cultivatorRealm: cultivator?.realm ?? null, + }; + }); + + return { + feedbacks: enrichedFeedbacks, + total, + page, + limit, + }; + } + + async update(id: string, input: unknown) { + const body = input; + const parsed = UpdateFeedbackStatusSchema.safeParse(body); + + if (!parsed.success) { + throw new HttpException( + { + success: false, + error: parsed.error.issues[0]?.message ?? '参数错误', + }, + 400, + ); + } + + const { status, adminMessage } = parsed.data; + const existing = await findFeedbackById(id); + + if (!existing) { + throw new HttpException({ success: false, error: '反馈不存在' }, 404); + } + + const hasStatusChanged = existing.status !== status; + const updated = await updateFeedbackStatus(id, status); + + if (!updated) { + throw new HttpException({ success: false, error: '更新失败' }, 500); + } + + let notifiedUser = false; + let rewardGranted = false; + if (hasStatusChanged) { + const q = getExecutor(); + const fallbackCultivator = existing.cultivatorId + ? null + : await q.query.cultivators.findFirst({ + where: and( + eq(cultivators.userId, existing.userId), + eq(cultivators.status, 'active'), + ), + columns: { id: true }, + }); + + const recipientCultivatorId = + existing.cultivatorId ?? fallbackCultivator?.id; + + if (recipientCultivatorId) { + // 发放奖励:仅当状态变为 resolved 时固定发放 8000 灵石 + if (status === 'resolved') { + try { + await updateSpiritStones( + existing.userId, + recipientCultivatorId, + FEEDBACK_RESOLVE_REWARD, + ); + rewardGranted = true; + } catch (err) { + console.warn( + `Failed to grant feedback reward for feedback ${id}: ${err instanceof Error ? err.message : String(err)}`, + ); + } + } + + await MailService.sendNewRewardMail( + recipientCultivatorId, + '反馈工单状态更新', + buildFeedbackStatusMailContent({ + feedbackType: existing.type as FeedbackType, + status, + adminMessage, + feedbackContent: existing.content, + }), + [], + ); + notifiedUser = true; + } else { + console.warn( + `Feedback ${id} status updated but no cultivator found for user ${existing.userId}`, + ); + } + } + + return { + success: true, + feedback: updated, + statusChanged: hasStatusChanged, + notifiedUser, + rewardGranted, + }; + } +} diff --git a/apps/api/src/admin/item-library.controller.ts b/apps/api/src/admin/item-library.controller.ts new file mode 100644 index 00000000..1621ad77 --- /dev/null +++ b/apps/api/src/admin/item-library.controller.ts @@ -0,0 +1,87 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Param, + Post, + Put, + UseFilters, +} from '@nestjs/common'; +import type { AuthUser } from '@server/lib/auth/types'; +import { Access, CurrentUser } from '../auth/access'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody } from '../http/json-body'; +import { AdminErrors } from './admin-errors'; +import { AdminItemLibraryService } from './item-library.service'; + +@Controller('api/admin/item-library') +@Access('admin') +@UseFilters(AdminErrors) +export class AdminItemLibraryController { + constructor( + @Inject(AdminItemLibraryService) + private readonly service: AdminItemLibraryService, + ) {} + @Get() + list(@FirstQuery() query: Record) { + return this.service.list(query); + } + @Post('materials/generate') + @HttpCode(200) + generateMaterials( + @CurrentUser() user: AuthUser, + @JsonBody({ fallback: null }) body: unknown, + ) { + return this.service.generateMaterials(user.id, body); + } + @Post('seeds/generate') + @HttpCode(200) + generateSeeds( + @CurrentUser() user: AuthUser, + @JsonBody({ fallback: null }) body: unknown, + ) { + return this.service.generateSeeds(user.id, body); + } + @Get('materials/daily-generation-settings') + settings() { + return this.service.settings(); + } + @Put('materials/daily-generation-settings') + updateSettings( + @CurrentUser() user: AuthUser, + @JsonBody({ fallback: null }) body: unknown, + ) { + return this.service.updateSettings(user.id, body); + } + @Post() + @HttpCode(200) + create( + @CurrentUser() user: AuthUser, + @JsonBody({ fallback: null }) body: unknown, + ) { + return this.service.create(user.id, body); + } + @Put(':id') + update( + @CurrentUser() user: AuthUser, + @Param('id') id: string, + @JsonBody({ fallback: null }) body: unknown, + ) { + return this.service.update(user.id, id, body); + } + @Post('artifact/preview') + @HttpCode(200) + retiredPreview() { + return this.service.retiredPreview(); + } + @Post(':id/archive') + @HttpCode(200) + archive(@CurrentUser() user: AuthUser, @Param('id') id: string) { + return this.service.archive(user.id, id); + } + @Get(':id') + detail(@Param('id') id: string) { + return this.service.detail(id); + } +} diff --git a/apps/api/src/admin/item-library.service.ts b/apps/api/src/admin/item-library.service.ts new file mode 100644 index 00000000..fd22a1ab --- /dev/null +++ b/apps/api/src/admin/item-library.service.ts @@ -0,0 +1,217 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import { + getItemLibraryDailyMaterialGenerationSettings, + upsertItemLibraryDailyMaterialGenerationSettings, +} from '@server/lib/repositories/appSettingsRepository'; +import { + archiveItemLibraryEntry, + createItemLibraryEntry, + findItemLibraryById, + listItemLibrary, + normalizeItemLibraryFilters, + updateItemLibraryEntry, +} from '@server/lib/repositories/itemLibraryRepository'; +import { + generateMaterialLibraryEntries, + generateSpiritSeedLibraryEntries, +} from '@server/lib/services/MaterialLibraryService'; +import { ItemLibraryDailyMaterialGenerationSettingsSchema } from '@daoyou/shared/lib/constants/appSettings'; +import { + CreateItemLibraryEntrySchema, + ItemLibraryListQuerySchema, + ItemLibraryMaterialGenerateSchema, + ItemLibrarySpiritSeedGenerateSchema, + UpdateItemLibraryEntrySchema, +} from '@daoyou/shared/lib/itemLibrary'; + +function isUniqueViolation(error: unknown): boolean { + if (!error || typeof error !== 'object') return false; + const maybe = error as { + code?: string; + }; + return maybe.code === '23505'; +} +@Injectable() +export class AdminItemLibraryService { + async list(query: Record) { + const parsed = ItemLibraryListQuerySchema.safeParse({ + status: query['status'] || undefined, + type: 'material', + materialType: query['materialType'] || undefined, + quality: query['quality'] || undefined, + q: query['q'] || undefined, + itemIds: query['itemIds'] || undefined, + page: query['page'] || undefined, + pageSize: query['pageSize'] || undefined, + }); + if (!parsed.success) { + throw new HttpException( + { error: '参数错误', details: parsed.error.flatten() }, + 400, + ); + } + const result = await listItemLibrary( + normalizeItemLibraryFilters(parsed.data), + ); + return result; + } + async generateMaterials(userId: string, inputBody: unknown) { + const body = inputBody; + const parsed = ItemLibraryMaterialGenerateSchema.safeParse(body); + if (!parsed.success) { + throw new HttpException( + { error: '参数错误', details: parsed.error.flatten() }, + 400, + ); + } + try { + const items = await generateMaterialLibraryEntries({ + request: parsed.data, + userId: userId, + }); + return { success: true, items, generated: items.length }; + } catch (error) { + throw new HttpException( + { error: error instanceof Error ? error.message : '批量生成材料失败' }, + 500, + ); + } + } + async generateSeeds(userId: string, inputBody: unknown) { + const body = inputBody; + const parsed = ItemLibrarySpiritSeedGenerateSchema.safeParse(body); + if (!parsed.success) { + throw new HttpException( + { error: '参数错误', details: parsed.error.flatten() }, + 400, + ); + } + try { + const items = await generateSpiritSeedLibraryEntries({ + request: parsed.data, + userId: userId, + }); + return { success: true, items, generated: items.length }; + } catch (error) { + throw new HttpException( + { error: error instanceof Error ? error.message : '批量生成灵种失败' }, + 500, + ); + } + } + async settings() { + const settings = await getItemLibraryDailyMaterialGenerationSettings(); + return { settings }; + } + async updateSettings(userId: string, inputBody: unknown) { + const body = inputBody; + const parsed = + ItemLibraryDailyMaterialGenerationSettingsSchema.safeParse(body); + if (!parsed.success) { + throw new HttpException( + { error: '参数错误', details: parsed.error.flatten() }, + 400, + ); + } + await upsertItemLibraryDailyMaterialGenerationSettings({ + settings: parsed.data, + updatedBy: userId, + }); + return { success: true, settings: parsed.data }; + } + async create(userId: string, inputBody: unknown) { + const body = inputBody; + if ((body as { type?: unknown } | null)?.type !== 'material') + throw new HttpException( + { error: '旧法宝与消耗品库已停用,请直接配置新版奖励' }, + 410, + ); + const parsed = CreateItemLibraryEntrySchema.safeParse(body); + if (!parsed.success) { + throw new HttpException( + { error: '参数错误', details: parsed.error.flatten() }, + 400, + ); + } + try { + const entry = await createItemLibraryEntry({ + entry: parsed.data, + userId: userId, + }); + return { success: true, item: entry }; + } catch (error) { + if (isUniqueViolation(error)) { + throw new HttpException({ error: '道具 ID 已存在' }, 409); + } + throw new HttpException( + { error: error instanceof Error ? error.message : '创建道具失败' }, + 400, + ); + } + } + async update(userId: string, idParam: string, inputBody: unknown) { + const id = idParam; + const existing = await findItemLibraryById(id); + if (existing && existing.type !== 'material') + throw new HttpException( + { error: '旧法宝与消耗品库已停用,请直接配置新版奖励' }, + 410, + ); + const body = inputBody; + if ((body as { type?: unknown } | null)?.type !== 'material') + throw new HttpException( + { error: '旧法宝与消耗品库已停用,请直接配置新版奖励' }, + 410, + ); + const parsed = UpdateItemLibraryEntrySchema.safeParse(body); + if (!parsed.success) { + throw new HttpException( + { error: '参数错误', details: parsed.error.flatten() }, + 400, + ); + } + try { + const item = await updateItemLibraryEntry({ + id, + entry: parsed.data, + userId: userId, + }); + if (!item) { + throw new HttpException({ error: '道具不存在' }, 404); + } + return { success: true, item }; + } catch (error) { + if (error instanceof HttpException) throw error; + throw new HttpException( + { error: error instanceof Error ? error.message : '更新道具失败' }, + 400, + ); + } + } + async retiredPreview() { + throw new HttpException( + { error: '旧法宝与消耗品库已停用,请直接配置新版奖励' }, + 410, + ); + } + async archive(userId: string, idParam: string) { + const existing = await findItemLibraryById(idParam); + if (!existing || existing.type !== 'material') + throw new HttpException({ error: '材料不存在' }, 404); + const item = await archiveItemLibraryEntry({ + id: idParam, + userId: userId, + }); + if (!item || item.type !== 'material') { + throw new HttpException({ error: '道具不存在' }, 404); + } + return { success: true, item }; + } + async detail(idParam: string) { + const item = await findItemLibraryById(idParam); + if (!item || item.type !== 'material') { + throw new HttpException({ error: '道具不存在' }, 404); + } + return { item }; + } +} diff --git a/apps/api/src/admin/monitoring.service.ts b/apps/api/src/admin/monitoring.service.ts new file mode 100644 index 00000000..14c95896 --- /dev/null +++ b/apps/api/src/admin/monitoring.service.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@nestjs/common'; +import { getLlmMetricsSnapshot } from '@server/lib/llm/metricsStore'; +import { getOnlineUsersSnapshot } from '@server/lib/services/onlinePresenceService'; + +@Injectable() +export class MonitoringService { + async llmMetrics(query: Record) { + const limit = Number.parseInt(query.limit ?? '300', 10); + const sceneId = query.sceneId?.trim() || undefined; + return { + success: true, + data: await getLlmMetricsSnapshot({ + limit: Number.isFinite(limit) ? limit : 300, + sceneId, + }), + }; + } + + async onlineUsers() { + return { success: true, data: await getOnlineUsersSnapshot() }; + } +} diff --git a/apps/api/src/admin/redeem-codes.controller.ts b/apps/api/src/admin/redeem-codes.controller.ts new file mode 100644 index 00000000..f62045d0 --- /dev/null +++ b/apps/api/src/admin/redeem-codes.controller.ts @@ -0,0 +1,42 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Param, + Post, + UseFilters, +} from '@nestjs/common'; +import type { AuthUser } from '@server/lib/auth/types'; +import { Access, CurrentUser } from '../auth/access'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody } from '../http/json-body'; +import { AdminErrors } from './admin-errors'; +import { AdminRedeemCodesService } from './redeem-codes.service'; + +@Controller('api/admin/redeem-codes') +@Access('admin') +@UseFilters(AdminErrors) +export class AdminRedeemCodesController { + constructor( + @Inject(AdminRedeemCodesService) + private readonly service: AdminRedeemCodesService, + ) {} + @Get() + list(@FirstQuery() query: Record) { + return this.service.list(query); + } + @Post() + @HttpCode(200) + create( + @CurrentUser() user: AuthUser, + @JsonBody({ fallback: null }) body: unknown, + ) { + return this.service.create(user.id, body); + } + @Post(':id/toggle') + @HttpCode(200) + toggle(@CurrentUser() user: AuthUser, @Param('id') id: string) { + return this.service.toggle(user.id, id); + } +} diff --git a/apps/api/src/admin/redeem-codes.service.ts b/apps/api/src/admin/redeem-codes.service.ts new file mode 100644 index 00000000..47dd68ce --- /dev/null +++ b/apps/api/src/admin/redeem-codes.service.ts @@ -0,0 +1,240 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import { getExecutor } from '@server/lib/drizzle/db'; +import { redeemCodes } from '@server/lib/drizzle/schema'; +import { + generateRedeemCode, + isValidRedeemCodeFormat, + normalizeRedeemCode, +} from '@server/lib/redeem/code'; +import { describeRedeemCodeReward } from '@server/lib/redeem/reward'; +import { + RewardSelectionsSchema, + rewardAttachments as buildRewardAttachments, +} from '@daoyou/shared/contracts/adminRewards'; +import type { MailAttachment } from '@daoyou/shared/types/mail'; +import { and, desc, eq, type SQL } from 'drizzle-orm'; +import { z } from 'zod'; + +const INVENTORY_SNAPSHOT_REWARD_PRESET_ID = '__inventory_v1_snapshot__'; +const ITEM_LIBRARY_SNAPSHOT_REWARD_PRESET_ID = '__item_library_snapshot__'; +const LEGACY_REWARD_CATALOG_SNAPSHOT_PRESET_ID = '__reward_catalog_snapshot__'; +function isSnapshotRewardPresetId(value: string | null | undefined): boolean { + return ( + value === INVENTORY_SNAPSHOT_REWARD_PRESET_ID || + value === ITEM_LIBRARY_SNAPSHOT_REWARD_PRESET_ID || + value === LEGACY_REWARD_CATALOG_SNAPSHOT_PRESET_ID + ); +} +const CreateRedeemCodeSchema = z + .object({ + code: z.string().trim().max(64).optional(), + rewardSelections: RewardSelectionsSchema.min(1, '至少选择一项奖励'), + mailTitle: z.string().trim().min(1).max(200), + mailContent: z.string().trim().min(1).max(10000), + totalLimit: z.number().int().min(1).max(100000000).nullable().optional(), + startsAt: z.string().trim().optional(), + endsAt: z.string().trim().optional(), + }) + .superRefine((value, ctx) => { + if (value.startsAt && Number.isNaN(new Date(value.startsAt).getTime())) { + ctx.addIssue({ + code: 'custom', + path: ['startsAt'], + message: '开始时间格式错误', + }); + } + if (value.endsAt && Number.isNaN(new Date(value.endsAt).getTime())) { + ctx.addIssue({ + code: 'custom', + path: ['endsAt'], + message: '结束时间格式错误', + }); + } + if (value.startsAt && value.endsAt) { + const startsAt = new Date(value.startsAt).getTime(); + const endsAt = new Date(value.endsAt).getTime(); + if ( + !Number.isNaN(startsAt) && + !Number.isNaN(endsAt) && + startsAt > endsAt + ) { + ctx.addIssue({ + code: 'custom', + path: ['endsAt'], + message: '结束时间必须晚于开始时间', + }); + } + } + }); +function isUniqueViolation(error: unknown): boolean { + if (!error || typeof error !== 'object') return false; + const maybe = error as { + code?: string; + }; + return maybe.code === '23505'; +} +async function createWithAutoCode(params: { + rewardAttachments: MailAttachment[]; + mailTitle: string; + mailContent: string; + totalLimit: number | null; + startsAt: Date | null; + endsAt: Date | null; + userId: string; +}) { + const q = getExecutor(); + for (let attempt = 0; attempt < 8; attempt += 1) { + const code = generateRedeemCode(); + try { + const [inserted] = await q + .insert(redeemCodes) + .values({ + code, + rewardPresetId: INVENTORY_SNAPSHOT_REWARD_PRESET_ID, + rewardAttachments: params.rewardAttachments, + mailTitle: params.mailTitle, + mailContent: params.mailContent, + totalLimit: params.totalLimit, + startsAt: params.startsAt, + endsAt: params.endsAt, + status: 'active', + createdBy: params.userId, + updatedBy: params.userId, + }) + .returning(); + return inserted; + } catch (error) { + if (isUniqueViolation(error)) continue; + throw error; + } + } + throw new Error('自动生成兑换码失败,请重试'); +} +@Injectable() +export class AdminRedeemCodesService { + async list(query: Record) { + const q = getExecutor(); + const status = query['status']; + const whereConditions: SQL[] = []; + if (status === 'active' || status === 'disabled') { + whereConditions.push(eq(redeemCodes.status, status)); + } + const items = await q.query.redeemCodes.findMany({ + where: whereConditions.length > 0 ? and(...whereConditions) : undefined, + orderBy: [desc(redeemCodes.createdAt)], + }); + const rows = items.map((item) => { + const hasRewardAttachments = + item.rewardAttachments !== null && item.rewardAttachments !== undefined; + let rewardSummary: string[]; + let rewardSource: 'snapshot' | 'expired_legacy' | 'broken_snapshot'; + if (hasRewardAttachments) { + try { + rewardSummary = describeRedeemCodeReward(item); + rewardSource = 'snapshot'; + } catch { + rewardSummary = ['奖励配置异常']; + rewardSource = 'broken_snapshot'; + } + } else if (isSnapshotRewardPresetId(item.rewardPresetId)) { + rewardSummary = ['奖励快照异常']; + rewardSource = 'broken_snapshot'; + } else { + rewardSummary = ['旧版兑换码(已失效)']; + rewardSource = 'expired_legacy'; + } + return { + ...item, + rewardSummary, + rewardSource, + }; + }); + return { redeemCodes: rows }; + } + async create(userId: string, inputBody: unknown) { + const q = getExecutor(); + const body = inputBody; + const parsed = CreateRedeemCodeSchema.safeParse(body); + if (!parsed.success) { + throw new HttpException( + { error: '参数错误', details: parsed.error.flatten() }, + 400, + ); + } + const startsAt = parsed.data.startsAt + ? new Date(parsed.data.startsAt) + : null; + const endsAt = parsed.data.endsAt ? new Date(parsed.data.endsAt) : null; + const totalLimit = parsed.data.totalLimit ?? null; + const manualCode = parsed.data.code + ? normalizeRedeemCode(parsed.data.code) + : ''; + const rewardAttachments = buildRewardAttachments( + parsed.data.rewardSelections, + ); + try { + if (manualCode) { + if (!isValidRedeemCodeFormat(manualCode)) { + throw new HttpException( + { error: '兑换码格式错误,仅支持 6-64 位大写字母数字' }, + 400, + ); + } + const [inserted] = await q + .insert(redeemCodes) + .values({ + code: manualCode, + rewardPresetId: INVENTORY_SNAPSHOT_REWARD_PRESET_ID, + rewardAttachments, + mailTitle: parsed.data.mailTitle, + mailContent: parsed.data.mailContent, + totalLimit, + startsAt, + endsAt, + status: 'active', + createdBy: userId, + updatedBy: userId, + }) + .returning(); + return { success: true, redeemCode: inserted }; + } + const inserted = await createWithAutoCode({ + rewardAttachments, + mailTitle: parsed.data.mailTitle, + mailContent: parsed.data.mailContent, + totalLimit, + startsAt, + endsAt, + userId: userId, + }); + return { success: true, redeemCode: inserted }; + } catch (error) { + if (error instanceof HttpException) throw error; + if (isUniqueViolation(error)) { + throw new HttpException({ error: '兑换码已存在' }, 409); + } + console.error('Create redeem code error:', error); + throw new HttpException({ error: '创建兑换码失败' }, 500); + } + } + async toggle(userId: string, idParam: string) { + const q = getExecutor(); + const id = idParam; + const item = await q.query.redeemCodes.findFirst({ + where: eq(redeemCodes.id, id), + }); + if (!item) { + throw new HttpException({ error: '兑换码不存在' }, 404); + } + const nextStatus = item.status === 'active' ? 'disabled' : 'active'; + const [updated] = await q + .update(redeemCodes) + .set({ + status: nextStatus, + updatedBy: userId, + }) + .where(eq(redeemCodes.id, item.id)) + .returning(); + return { success: true, redeemCode: updated }; + } +} diff --git a/apps/api/src/admin/reputation-shop.controller.ts b/apps/api/src/admin/reputation-shop.controller.ts new file mode 100644 index 00000000..979f9d80 --- /dev/null +++ b/apps/api/src/admin/reputation-shop.controller.ts @@ -0,0 +1,55 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Param, + Post, + Put, + UseFilters, +} from '@nestjs/common'; +import type { AuthUser } from '@server/lib/auth/types'; +import { Access, CurrentUser } from '../auth/access'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody } from '../http/json-body'; +import { AdminErrors } from './admin-errors'; +import { AdminReputationShopService } from './reputation-shop.service'; + +@Controller('api/admin/reputation-shop') +@Access('admin') +@UseFilters(AdminErrors) +export class AdminReputationShopController { + constructor( + @Inject(AdminReputationShopService) + private readonly shop: AdminReputationShopService, + ) {} + + @Get() + list(@FirstQuery() query: Record) { + return this.shop.list(query.status); + } + + @Post() + @HttpCode(200) + create( + @CurrentUser() user: AuthUser, + @JsonBody({ fallback: null }) body: unknown, + ) { + return this.shop.create(user.id, body); + } + + @Put(':id') + update( + @CurrentUser() user: AuthUser, + @Param('id') id: string, + @JsonBody({ fallback: null }) body: unknown, + ) { + return this.shop.update(user.id, id, body); + } + + @Post(':id/archive') + @HttpCode(200) + archive(@CurrentUser() user: AuthUser, @Param('id') id: string) { + return this.shop.archive(user.id, id); + } +} diff --git a/apps/api/src/admin/reputation-shop.service.ts b/apps/api/src/admin/reputation-shop.service.ts new file mode 100644 index 00000000..f6935504 --- /dev/null +++ b/apps/api/src/admin/reputation-shop.service.ts @@ -0,0 +1,81 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import { + archiveReputationShopItem, + createReputationShopItem, + listReputationShopItems, + ReputationShopError, + updateReputationShopItem, +} from '@server/lib/services/ReputationShopService'; +import { + ReputationShopItemMutationSchema, + ReputationShopListQuerySchema, +} from '@daoyou/shared/contracts/reputationShop'; + +@Injectable() +export class AdminReputationShopService { + async list(status?: string) { + const parsed = ReputationShopListQuerySchema.safeParse({ + status: status || undefined, + }); + if (!parsed.success) + throw new HttpException( + { error: '参数错误', details: parsed.error.flatten() }, + 400, + ); + return { + items: await listReputationShopItems({ status: parsed.data.status }), + }; + } + + async create(userId: string, body: unknown) { + const parsed = ReputationShopItemMutationSchema.safeParse(body); + if (!parsed.success) + throw new HttpException( + { error: '参数错误', details: parsed.error.flatten() }, + 400, + ); + try { + const item = await createReputationShopItem({ + input: parsed.data, + userId, + }); + return { success: true, item }; + } catch (error) { + if (error instanceof ReputationShopError) + throw new HttpException({ error: error.message }, error.status); + throw new HttpException( + { error: error instanceof Error ? error.message : '创建商品失败' }, + 400, + ); + } + } + + async update(userId: string, id: string, body: unknown) { + const parsed = ReputationShopItemMutationSchema.safeParse(body); + if (!parsed.success) + throw new HttpException( + { error: '参数错误', details: parsed.error.flatten() }, + 400, + ); + // Keep the missing-item response outside the legacy service-error catch. + let item; + try { + item = await updateReputationShopItem({ id, input: parsed.data, userId }); + } catch (error) { + if (error instanceof ReputationShopError) + throw new HttpException({ error: error.message }, error.status); + throw new HttpException( + { error: error instanceof Error ? error.message : '更新商品失败' }, + 400, + ); + } + if (!item) throw new HttpException({ error: '商品不存在' }, 404); + return { success: true, item }; + } + + async archive(userId: string, id: string) { + const item = await archiveReputationShopItem({ id, userId }); + if (!item) throw new HttpException({ error: '商品不存在' }, 404); + return { success: true, item }; + } +} diff --git a/apps/api/src/admin/reward-items.controller.ts b/apps/api/src/admin/reward-items.controller.ts new file mode 100644 index 00000000..bdc09af0 --- /dev/null +++ b/apps/api/src/admin/reward-items.controller.ts @@ -0,0 +1,30 @@ +import { Controller, HttpCode, Inject, Post, UseFilters } from '@nestjs/common'; +import { AdminItemGenerationSchema } from '@daoyou/shared/contracts/adminItemGeneration'; +import type { z } from 'zod'; +import { Access } from '../auth/access'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { AdminErrors } from './admin-errors'; +import { AdminRewardItemsService } from './reward-items.service'; + +@Controller('api/admin/reward-items') +@Access('admin') +@UseFilters(AdminErrors) +export class AdminRewardItemsController { + constructor( + @Inject(AdminRewardItemsService) + private readonly rewards: AdminRewardItemsService, + ) {} + + @Post('generate') + @HttpCode(200) + generate( + @JsonBody( + { fallback: undefined }, + new ZodPipe(AdminItemGenerationSchema, 'legacy-unhandled'), + ) + input: z.infer, + ) { + return this.rewards.generate(input); + } +} diff --git a/src/server/routes/api/admin/reward-items.router.ts b/apps/api/src/admin/reward-items.service.ts similarity index 70% rename from src/server/routes/api/admin/reward-items.router.ts rename to apps/api/src/admin/reward-items.service.ts index adedbb8c..92f0785b 100644 --- a/src/server/routes/api/admin/reward-items.router.ts +++ b/apps/api/src/admin/reward-items.service.ts @@ -1,33 +1,21 @@ -import { - getValidatedJson, - requireAdmin, - validateJson, -} from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; +import { HttpException, Injectable } from '@nestjs/common'; import { getQuotaCategoryForFamily } from '@server/lib/services/AlchemyRecipeRules'; -import { TALISMAN_SCENARIO_OPTIONS } from '@shared/config/talismanScenarios'; -import { AdminItemGenerationSchema } from '@shared/contracts/adminItemGeneration'; -import { RewardItemSchema } from '@shared/contracts/adminRewards'; -import { generateForgedEquipment } from '@shared/engine/combat-v6/equipment/forging'; -import { buildSpiritFruitSpec } from '@shared/engine/spirit-field/spiritFruit'; +import { TALISMAN_SCENARIO_OPTIONS } from '@daoyou/shared/config/talismanScenarios'; +import { AdminItemGenerationSchema } from '@daoyou/shared/contracts/adminItemGeneration'; +import { RewardItemSchema } from '@daoyou/shared/contracts/adminRewards'; +import { generateForgedEquipment } from '@daoyou/shared/engine/combat-v6/equipment/forging'; +import { buildSpiritFruitSpec } from '@daoyou/shared/engine/spirit-field/spiritFruit'; import { normalizeAlchemyEffectRoute, resolveAlchemyEffects, -} from '@shared/lib/alchemyEffectResolver'; -import { getAlchemyPropertyFamily } from '@shared/lib/alchemyProperties'; -import type { ConsumableSpec } from '@shared/types/consumable'; -import { Hono } from 'hono'; +} from '@daoyou/shared/lib/alchemyEffectResolver'; +import { getAlchemyPropertyFamily } from '@daoyou/shared/lib/alchemyProperties'; +import type { ConsumableSpec } from '@daoyou/shared/types/consumable'; import { randomInt, randomUUID } from 'node:crypto'; import { z } from 'zod'; - -const router = new Hono(); -router.post( - '/generate', - requireAdmin(), - validateJson(AdminItemGenerationSchema), - async (c) => { - const input = - getValidatedJson>(c); +@Injectable() +export class AdminRewardItemsService { + generate(input: z.infer) { if (input.kind === 'equipment') { const result = generateForgedEquipment({ ...input, @@ -37,17 +25,17 @@ router.post( boosts: { ore: 0, essence: 0, attributes: 0 }, }); if (!result.ok) - return c.json( + throw new HttpException( { error: result.diagnostics[0]?.message ?? '生成道装失败' }, 400, ); - return c.json({ + return { item: RewardItemSchema.parse({ definitionId: 'equipment.v6', quantity: 1, instanceData: result.instance, }), - }); + }; } let spec: ConsumableSpec; let name: string; @@ -72,7 +60,7 @@ router.post( })), }); if (route.effects.length !== input.effects.length) - return c.json( + throw new HttpException( { error: '药效不能重复,灵兽修为不能与人物药效混用' }, 400, ); @@ -101,7 +89,7 @@ router.post( }; name = input.name; } - return c.json({ + return { item: RewardItemSchema.parse({ definitionId: 'consumable.v1', quantity: 1, @@ -117,7 +105,6 @@ router.post( spec, }, }), - }); - }, -); -export default router; + }; + } +} diff --git a/apps/api/src/admin/sect-shop.controller.ts b/apps/api/src/admin/sect-shop.controller.ts new file mode 100644 index 00000000..5acfed00 --- /dev/null +++ b/apps/api/src/admin/sect-shop.controller.ts @@ -0,0 +1,54 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Param, + Post, + Put, + UseFilters, +} from '@nestjs/common'; +import type { AuthUser } from '@server/lib/auth/types'; +import { Access, CurrentUser } from '../auth/access'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody } from '../http/json-body'; +import { AdminErrors } from './admin-errors'; +import { AdminSectShopService } from './sect-shop.service'; + +@Controller('api/admin/sect-shop') +@Access('admin') +@UseFilters(AdminErrors) +export class AdminSectShopController { + constructor( + @Inject(AdminSectShopService) private readonly shop: AdminSectShopService, + ) {} + + @Get() + list(@FirstQuery() query: Record) { + return this.shop.list(query.status); + } + + @Post() + @HttpCode(200) + create( + @CurrentUser() user: AuthUser, + @JsonBody({ fallback: null }) body: unknown, + ) { + return this.shop.create(user.id, body); + } + + @Put(':id') + update( + @CurrentUser() user: AuthUser, + @Param('id') id: string, + @JsonBody({ fallback: null }) body: unknown, + ) { + return this.shop.update(user.id, id, body); + } + + @Post(':id/archive') + @HttpCode(200) + archive(@CurrentUser() user: AuthUser, @Param('id') id: string) { + return this.shop.archive(user.id, id); + } +} diff --git a/apps/api/src/admin/sect-shop.service.ts b/apps/api/src/admin/sect-shop.service.ts new file mode 100644 index 00000000..8bf5a10c --- /dev/null +++ b/apps/api/src/admin/sect-shop.service.ts @@ -0,0 +1,76 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import { + archiveSectShopItem, + createSectShopItem, + listSectShopItems, + SectShopError, + updateSectShopItem, +} from '@server/lib/services/SectShopService'; +import { + SectShopItemMutationSchema, + SectShopListQuerySchema, +} from '@daoyou/shared/contracts/sectShop'; + +@Injectable() +export class AdminSectShopService { + async list(status?: string) { + const parsed = SectShopListQuerySchema.safeParse({ + status: status || undefined, + }); + if (!parsed.success) + throw new HttpException( + { error: '参数错误', details: parsed.error.flatten() }, + 400, + ); + return { items: await listSectShopItems({ status: parsed.data.status }) }; + } + + async create(userId: string, body: unknown) { + const parsed = SectShopItemMutationSchema.safeParse(body); + if (!parsed.success) + throw new HttpException( + { error: '参数错误', details: parsed.error.flatten() }, + 400, + ); + try { + const item = await createSectShopItem({ input: parsed.data, userId }); + return { success: true, item }; + } catch (error) { + if (error instanceof SectShopError) + throw new HttpException({ error: error.message }, error.status); + throw new HttpException( + { error: error instanceof Error ? error.message : '创建商品失败' }, + 400, + ); + } + } + + async update(userId: string, id: string, body: unknown) { + const parsed = SectShopItemMutationSchema.safeParse(body); + if (!parsed.success) + throw new HttpException( + { error: '参数错误', details: parsed.error.flatten() }, + 400, + ); + // Keep the missing-item response outside the legacy service-error catch. + let item; + try { + item = await updateSectShopItem({ id, input: parsed.data, userId }); + } catch (error) { + if (error instanceof SectShopError) + throw new HttpException({ error: error.message }, error.status); + throw new HttpException( + { error: error instanceof Error ? error.message : '更新商品失败' }, + 400, + ); + } + if (!item) throw new HttpException({ error: '商品不存在' }, 404); + return { success: true, item }; + } + + async archive(userId: string, id: string) { + const item = await archiveSectShopItem({ id, userId }); + if (!item) throw new HttpException({ error: '商品不存在' }, 404); + return { success: true, item }; + } +} diff --git a/apps/api/src/admin/settings.service.ts b/apps/api/src/admin/settings.service.ts new file mode 100644 index 00000000..d0672103 --- /dev/null +++ b/apps/api/src/admin/settings.service.ts @@ -0,0 +1,65 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import { + getAppSetting, + getAuthPageAnnouncement, + getResolvedCommunityQqGroupNumber, + upsertAppSetting, +} from '@server/lib/repositories/appSettingsRepository'; +import { APP_SETTING_KEYS } from '@daoyou/shared/lib/constants/appSettings'; +import { z } from 'zod'; + +const AnnouncementSchema = z.object({ announcement: z.string() }); +const CommunitySchema = z.object({ + groupNumber: z + .string() + .trim() + .regex(/^\d{5,12}$/, '请输入 5 到 12 位数字 QQ 群号'), +}); + +@Injectable() +export class SettingsService { + async announcement() { + return { announcement: (await getAuthPageAnnouncement()) ?? '' }; + } + + async updateAnnouncement(userId: string, body: unknown) { + const parsed = AnnouncementSchema.safeParse(body); + if (!parsed.success) + throw new HttpException( + { error: '参数错误', details: parsed.error.flatten() }, + 400, + ); + const announcement = parsed.data.announcement.trim(); + await upsertAppSetting({ + key: APP_SETTING_KEYS.authPageAnnouncement, + value: announcement, + updatedBy: userId, + }); + return { ok: true, announcement }; + } + + async community() { + const resolved = await getResolvedCommunityQqGroupNumber(); + const stored = await getAppSetting(APP_SETTING_KEYS.communityQqGroupNumber); + return { + groupNumber: resolved, + customized: Boolean(stored), + storedGroupNumber: stored, + }; + } + + async updateCommunity(userId: string, body: unknown) { + const parsed = CommunitySchema.safeParse(body); + if (!parsed.success) + throw new HttpException( + { error: '参数错误', details: parsed.error.flatten() }, + 400, + ); + await upsertAppSetting({ + key: APP_SETTING_KEYS.communityQqGroupNumber, + value: parsed.data.groupNumber, + updatedBy: userId, + }); + return { ok: true, groupNumber: await getResolvedCommunityQqGroupNumber() }; + } +} diff --git a/apps/api/src/admin/sponsorship.controller.ts b/apps/api/src/admin/sponsorship.controller.ts new file mode 100644 index 00000000..72d2b62b --- /dev/null +++ b/apps/api/src/admin/sponsorship.controller.ts @@ -0,0 +1,78 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Param, + Post, + Put, + UseFilters, +} from '@nestjs/common'; +import type { AuthUser } from '@server/lib/auth/types'; +import { Access, CurrentUser } from '../auth/access'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody } from '../http/json-body'; +import { AdminErrors } from './admin-errors'; +import { AdminSponsorshipService } from './sponsorship.service'; + +@Controller('api/admin/sponsorship') +@Access('admin') +@UseFilters(AdminErrors) +export class AdminSponsorshipController { + constructor( + @Inject(AdminSponsorshipService) + private readonly service: AdminSponsorshipService, + ) {} + @Get('config') + config() { + return this.service.config(); + } + @Put('config') + updateConfig( + @CurrentUser() user: AuthUser, + @JsonBody({ fallback: null }) body: unknown, + ) { + return this.service.updateConfig(user.id, body); + } + @Post('ping') + @HttpCode(200) + ping() { + return this.service.ping(); + } + @Get('orders') + orders(@FirstQuery() query: Record) { + return this.service.orders(query); + } + @Get('orders/:id') + detail(@Param('id') id: string) { + return this.service.detail(id); + } + @Post('orders/:id/retry') + @HttpCode(200) + retry(@CurrentUser() user: AuthUser, @Param('id') id: string) { + return this.service.retry(user.id, id); + } + @Post('orders/:id/revoke') + @HttpCode(200) + revoke(@CurrentUser() user: AuthUser, @Param('id') id: string) { + return this.service.revoke(user.id, id); + } + @Post('orders/:id/rotate-claim') + @HttpCode(200) + rotateClaim(@CurrentUser() user: AuthUser, @Param('id') id: string) { + return this.service.rotateClaim(user.id, id); + } + @Post('snapshots/:id/reveal') + @HttpCode(200) + reveal(@CurrentUser() user: AuthUser, @Param('id') id: string) { + return this.service.reveal(user.id, id); + } + @Post('manual-grants') + @HttpCode(201) + grant( + @CurrentUser() user: AuthUser, + @JsonBody({ fallback: null }) body: unknown, + ) { + return this.service.grant(user.id, body); + } +} diff --git a/apps/api/src/admin/sponsorship.service.ts b/apps/api/src/admin/sponsorship.service.ts new file mode 100644 index 00000000..8b2d2721 --- /dev/null +++ b/apps/api/src/admin/sponsorship.service.ts @@ -0,0 +1,193 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import { getAfdianSponsorshipConfig } from '@server/lib/repositories/appSettingsRepository'; +import { + getSponsorshipOrderForAdmin, + grantManualSponsorshipMerit, + listSponsorshipOrdersForAdmin, + retrySponsorshipOrderAsAdmin, + revealSponsorshipSnapshot, + revokeSponsorshipOrderAsAdmin, + rotateSponsorshipClaimAsAdmin, + SponsorshipApplicationError, + updateSponsorshipConfigAsAdmin, +} from '@server/lib/services/SponsorshipApplicationService'; +import { requireSponsorshipProvider } from '@server/lib/sponsorship/providerRegistry'; +import { + AfdianSponsorshipConfigSchema, + SPONSORSHIP_TIER_IDS, +} from '@daoyou/shared/lib/sponsorship'; +import { z } from 'zod'; + +const TierConfigSchema = z + .object({ + planId: z.string().trim().max(80), + minimumAmountFen: z.number().int().min(1).max(100000000), + }) + .strict(); +const ConfigUpdateSchema = z + .object({ + tiers: z.object({ + faint_light: TierConfigSchema, + fellow_traveler: TierConfigSchema, + night_guardian: TierConfigSchema, + immortality_witness: TierConfigSchema, + }), + }) + .strict(); +const ManualGrantSchema = z + .object({ + cultivatorId: z.uuid(), + tier: z.enum(SPONSORSHIP_TIER_IDS), + supportedAt: z.string().datetime().optional(), + publicListing: z.boolean().default(true), + sendMail: z.boolean().default(true), + }) + .strict(); +function sponsorshipErrorResponse(error: unknown) { + if (error instanceof SponsorshipApplicationError) { + throw new HttpException( + { error: error.message, code: error.code }, + error.status, + ); + } + throw error; +} +@Injectable() +export class AdminSponsorshipService { + async config() { + return await getAfdianSponsorshipConfig(); + } + async updateConfig(userId: string, inputBody: unknown) { + const parsed = ConfigUpdateSchema.safeParse(inputBody); + if (!parsed.success) + throw new HttpException( + { error: '参数错误', details: parsed.error.flatten() }, + 400, + ); + const current = await getAfdianSponsorshipConfig(); + const acceptingCheckout = Object.values(parsed.data.tiers).some( + (tier) => tier.planId.length > 0, + ); + const ordersAcceptedAfter = acceptingCheckout + ? current.acceptingCheckout && current.ordersAcceptedAfter + ? current.ordersAcceptedAfter + : new Date().toISOString() + : current.ordersAcceptedAfter; + const nextConfig = AfdianSponsorshipConfigSchema.safeParse({ + ...current, + acceptingCheckout, + ordersAcceptedAfter, + tiers: parsed.data.tiers, + }); + if (!nextConfig.success) { + throw new HttpException( + { error: '档位配置错误', details: nextConfig.error.flatten() }, + 400, + ); + } + await updateSponsorshipConfigAsAdmin({ + config: nextConfig.data, + adminUserId: userId, + }); + return { success: true }; + } + async ping() { + try { + await requireSponsorshipProvider().ping(); + return { success: true }; + } catch { + throw new HttpException( + { error: '爱发电连接测试失败,请检查服务端配置' }, + 502, + ); + } + } + async orders(query: Record) { + const parsed = z + .object({ + page: z.coerce.number().int().min(1).default(1), + pageSize: z.coerce.number().int().min(1).max(100).default(50), + filter: z + .enum(['all', 'attention', 'awaiting_claim', 'fulfilled', 'revoked']) + .default('all'), + }) + .safeParse(query); + if (!parsed.success) + throw new HttpException({ error: '查询参数错误' }, 400); + const result = await listSponsorshipOrdersForAdmin(parsed.data); + return { + orders: result.items, + total: result.total, + page: result.page, + pageSize: result.pageSize, + }; + } + async detail(idParam: string) { + const id = z.uuid().safeParse(idParam); + if (!id.success) throw new HttpException({ error: '订单不存在' }, 404); + const detail = await getSponsorshipOrderForAdmin(id.data); + if (!detail) throw new HttpException({ error: '订单不存在' }, 404); + return detail; + } + async retry(userId: string, idParam: string) { + const id = z.uuid().safeParse(idParam); + if (!id.success) throw new HttpException({ error: '订单不存在' }, 404); + try { + await retrySponsorshipOrderAsAdmin(id.data, userId); + return { success: true }; + } catch (error) { + return sponsorshipErrorResponse(error); + } + } + async revoke(userId: string, idParam: string) { + const id = z.uuid().safeParse(idParam); + if (!id.success) throw new HttpException({ error: '订单不存在' }, 404); + try { + await revokeSponsorshipOrderAsAdmin(id.data, userId); + return { success: true }; + } catch (error) { + return sponsorshipErrorResponse(error); + } + } + async rotateClaim(userId: string, idParam: string) { + const id = z.uuid().safeParse(idParam); + if (!id.success) throw new HttpException({ error: '订单不存在' }, 404); + try { + await rotateSponsorshipClaimAsAdmin(id.data, userId); + return { success: true }; + } catch (error) { + return sponsorshipErrorResponse(error); + } + } + async reveal(userId: string, idParam: string) { + const id = z.uuid().safeParse(idParam); + if (!id.success) throw new HttpException({ error: '快照不存在' }, 404); + const snapshot = await revealSponsorshipSnapshot({ + snapshotId: id.data, + adminUserId: userId, + }); + if (snapshot === null) + throw new HttpException({ error: '快照不存在' }, 404); + return { snapshot }; + } + async grant(userId: string, inputBody: unknown) { + const parsed = ManualGrantSchema.safeParse(inputBody); + if (!parsed.success) + throw new HttpException( + { error: '参数错误', details: parsed.error.flatten() }, + 400, + ); + try { + await grantManualSponsorshipMerit({ + ...parsed.data, + supportedAt: parsed.data.supportedAt + ? new Date(parsed.data.supportedAt) + : new Date(), + adminUserId: userId, + }); + return { success: true }; + } catch (error) { + return sponsorshipErrorResponse(error); + } + } +} diff --git a/apps/api/src/admin/system-mails-input.ts b/apps/api/src/admin/system-mails-input.ts new file mode 100644 index 00000000..13a4e652 --- /dev/null +++ b/apps/api/src/admin/system-mails-input.ts @@ -0,0 +1,18 @@ +import { SystemMailInputSchema } from '@daoyou/shared/contracts/systemMail'; +import { z } from 'zod'; +export const CreateSchema = z + .object({ requestId: z.uuid(), input: SystemMailInputSchema }) + .strict(); +export const UpdateSchema = z + .object({ + revision: z.number().int().positive(), + input: SystemMailInputSchema, + }) + .strict(); +export const RevisionSchema = z + .object({ revision: z.number().int().positive() }) + .strict(); +export const ListSchema = z.object({ + page: z.coerce.number().int().min(1).default(1), + search: z.string().trim().max(200).default(''), +}); diff --git a/apps/api/src/admin/system-mails.controller.ts b/apps/api/src/admin/system-mails.controller.ts new file mode 100644 index 00000000..a6ba7e34 --- /dev/null +++ b/apps/api/src/admin/system-mails.controller.ts @@ -0,0 +1,91 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Param, + Post, + Put, + UseFilters, +} from '@nestjs/common'; +import type { AuthUser } from '@server/lib/auth/types'; +import type { z } from 'zod'; +import { Access, CurrentUser } from '../auth/access'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { AdminErrors } from './admin-errors'; +import { + CreateSchema, + ListSchema, + RevisionSchema, + UpdateSchema, +} from './system-mails-input'; +import { AdminSystemMailsService } from './system-mails.service'; +@Controller('api/admin/system-mails') +@Access('admin') +@UseFilters(AdminErrors) +export class AdminSystemMailsController { + constructor( + @Inject(AdminSystemMailsService) + private readonly service: AdminSystemMailsService, + ) {} + @Get() + list( + @FirstQuery(new ZodPipe(ListSchema, 'legacy-unhandled')) + query: z.infer, + ) { + return this.service.list(query); + } + @Get(':id') + detail(@Param('id') id: string) { + return this.service.detail(id); + } + @Post() + @HttpCode(200) + create( + @CurrentUser() user: AuthUser, + @JsonBody( + { fallback: undefined }, + new ZodPipe(CreateSchema, 'legacy-unhandled'), + ) + input: z.infer, + ) { + return this.service.create(user.id, input); + } + @Put(':id') + update( + @Param('id') id: string, + @JsonBody( + { fallback: undefined }, + new ZodPipe(UpdateSchema, 'legacy-unhandled'), + ) + input: z.infer, + ) { + return this.service.update(id, input); + } + @Post(':id/publish') + @HttpCode(200) + publish( + @Param('id') id: string, + @JsonBody( + { fallback: undefined }, + new ZodPipe(RevisionSchema, 'legacy-unhandled'), + ) + input: z.infer, + ) { + return this.service.publish(id, input); + } + @Post(':id/stop') + @HttpCode(200) + stop( + @Param('id') id: string, + @JsonBody( + { fallback: undefined }, + new ZodPipe(RevisionSchema, 'legacy-unhandled'), + ) + input: z.infer, + ) { + return this.service.stop(id, input); + } +} diff --git a/apps/api/src/admin/system-mails.service.ts b/apps/api/src/admin/system-mails.service.ts new file mode 100644 index 00000000..419a0b57 --- /dev/null +++ b/apps/api/src/admin/system-mails.service.ts @@ -0,0 +1,198 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import { db } from '@server/lib/drizzle/db'; +import { mails, systemMailCampaigns } from '@server/lib/drizzle/schema'; +import { SystemMailInputSchema } from '@daoyou/shared/contracts/systemMail'; +import { and, desc, eq, ilike, sql } from 'drizzle-orm'; +import { createHash } from 'node:crypto'; +import { z } from 'zod'; +import { + CreateSchema, + ListSchema, + RevisionSchema, + UpdateSchema, +} from './system-mails-input'; +const countDeliveries = sql`(select count(*)::int from ${mails} delivered where delivered.system_mail_campaign_id = ${systemMailCampaigns}.id)`; +function values(input: z.infer) { + return { + ...input, + startsAt: new Date(input.startsAt), + endsAt: new Date(input.endsAt), + }; +} +@Injectable() +export class AdminSystemMailsService { + async list(query: z.infer) { + const { page, search } = query; + const rows = await db + .select({ + id: systemMailCampaigns.id, + title: systemMailCampaigns.title, + status: systemMailCampaigns.status, + revision: systemMailCampaigns.revision, + conditions: systemMailCampaigns.conditions, + startsAt: systemMailCampaigns.startsAt, + endsAt: systemMailCampaigns.endsAt, + createdAt: systemMailCampaigns.createdAt, + publishedAt: systemMailCampaigns.publishedAt, + deliveredCount: countDeliveries, + }) + .from(systemMailCampaigns) + .where( + search ? ilike(systemMailCampaigns.title, `%${search}%`) : undefined, + ) + .orderBy( + desc(systemMailCampaigns.createdAt), + desc(systemMailCampaigns.id), + ) + .limit(21) + .offset((page - 1) * 20); + return { items: rows.slice(0, 20), hasMore: rows.length > 20 }; + } + async detail(idParam: string) { + const id = z.uuid().parse(idParam); + const [row] = await db + .select() + .from(systemMailCampaigns) + .where(eq(systemMailCampaigns.id, id)); + if (!row) throw new HttpException({ error: '邮件发布记录不存在' }, 404); + const [count] = await db + .select({ count: sql`count(*)::int` }) + .from(mails) + .where(eq(mails.systemMailCampaignId, id)); + return { + id: row.id, + title: row.title, + content: row.content, + rewardSelections: row.rewardSelections, + conditions: row.conditions, + startsAt: row.startsAt, + endsAt: row.endsAt, + status: row.status, + revision: row.revision, + createdAt: row.createdAt, + publishedAt: row.publishedAt, + deliveredCount: count?.count ?? 0, + }; + } + async create(userId: string, request: z.infer) { + const { requestId, input } = request; + const creationFingerprint = createHash('sha256') + .update(JSON.stringify(input)) + .digest('hex'); + await db + .insert(systemMailCampaigns) + .values({ + ...values(input), + id: requestId, + creationFingerprint, + createdBy: userId, + }) + .onConflictDoNothing({ target: systemMailCampaigns.id }); + const [row] = await db + .select() + .from(systemMailCampaigns) + .where(eq(systemMailCampaigns.id, requestId)); + if (!row || row.creationFingerprint !== creationFingerprint) + throw new HttpException( + { error: '此请求已用于其他内容,请重新打开编辑器' }, + 409, + ); + return { id: row.id, revision: row.revision, status: row.status }; + } + async update(idParam: string, request: z.infer) { + const id = z.uuid().parse(idParam); + const { revision, input } = request; + const [row] = await db + .update(systemMailCampaigns) + .set({ ...values(input), revision: revision + 1 }) + .where( + and( + eq(systemMailCampaigns.id, id), + eq(systemMailCampaigns.status, 'draft'), + eq(systemMailCampaigns.revision, revision), + ), + ) + .returning({ + id: systemMailCampaigns.id, + revision: systemMailCampaigns.revision, + }); + if (!row) + throw new HttpException( + { error: '草稿已变化或已发布,请关闭后重新打开' }, + 409, + ); + return row; + } + async publish(idParam: string, input: z.infer) { + const id = z.uuid().parse(idParam); + const { revision } = input; + const result = await db.transaction(async (tx) => { + const [row] = await tx + .select() + .from(systemMailCampaigns) + .where(eq(systemMailCampaigns.id, id)) + .for('update'); + if (!row) return 'missing'; + if (row.status === 'published' && row.revision === revision + 1) + return 'ok'; + if (row.status !== 'draft' || row.revision !== revision) return 'changed'; + if (row.endsAt.getTime() <= Date.now()) return 'expired'; + SystemMailInputSchema.parse({ + title: row.title, + content: row.content, + rewardSelections: row.rewardSelections, + conditions: row.conditions, + startsAt: row.startsAt.toISOString(), + endsAt: row.endsAt.toISOString(), + }); + await tx + .update(systemMailCampaigns) + .set({ + status: 'published', + publishedAt: new Date(), + revision: revision + 1, + }) + .where(eq(systemMailCampaigns.id, id)); + return 'ok'; + }); + if (result === 'missing') + throw new HttpException({ error: '邮件发布记录不存在' }, 404); + if (result !== 'ok') + throw new HttpException( + { + error: + result === 'expired' + ? '投递结束时间已过,请修改草稿' + : '草稿已变化,请重新打开核对', + }, + 409, + ); + return { success: true }; + } + async stop(idParam: string, input: z.infer) { + const id = z.uuid().parse(idParam); + const { revision } = input; + // UPDATE conflicts with the consumer's SHARE lock: after this commits, + // no new delivery transaction can observe the campaign as published. + const [row] = await db + .update(systemMailCampaigns) + .set({ status: 'stopped', revision: revision + 1 }) + .where( + and( + eq(systemMailCampaigns.id, id), + eq(systemMailCampaigns.status, 'published'), + eq(systemMailCampaigns.revision, revision), + ), + ) + .returning({ id: systemMailCampaigns.id }); + if (!row) { + const [current] = await db + .select({ status: systemMailCampaigns.status }) + .from(systemMailCampaigns) + .where(eq(systemMailCampaigns.id, id)); + if (current?.status !== 'stopped') + throw new HttpException({ error: '发布记录已变化,请重新打开' }, 409); + } + return { success: true }; + } +} diff --git a/apps/api/src/admin/tower-input.ts b/apps/api/src/admin/tower-input.ts new file mode 100644 index 00000000..fc9360d5 --- /dev/null +++ b/apps/api/src/admin/tower-input.ts @@ -0,0 +1,25 @@ +import { + TOWER_ELIGIBLE_REALMS, + TOWER_MAX_FLOOR, + TOWER_MIN_REALM, +} from '@daoyou/shared/lib/tower/helpers'; +import { REALM_VALUES } from '@daoyou/shared/types/constants'; +import { z } from 'zod'; +const SeasonKeySchema = z + .string() + .regex(/^\d{4}-W(?:0[1-9]|[1-4]\d|5[0-3])@Asia\/Shanghai$/); +export const TowerQuerySchema = z.object({ + seasonKey: SeasonKeySchema.optional(), + realm: z + .enum(REALM_VALUES) + .refine((realm) => TOWER_ELIGIBLE_REALMS.includes(realm), '蜃楼境界未开放') + .default(TOWER_MIN_REALM), + floor: z.coerce.number().int().min(1).max(TOWER_MAX_FLOOR).default(1), +}); +export const TowerRegenerateSchema = z.strictObject({ + seasonKey: SeasonKeySchema, + expectedFingerprint: z + .string() + .regex(/^[a-f0-9]{64}$/) + .nullable(), +}); diff --git a/apps/api/src/admin/tower.controller.ts b/apps/api/src/admin/tower.controller.ts new file mode 100644 index 00000000..8e31b209 --- /dev/null +++ b/apps/api/src/admin/tower.controller.ts @@ -0,0 +1,50 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Post, + Res, + UseFilters, +} from '@nestjs/common'; +import type { Response } from 'express'; +import type { z } from 'zod'; +import { Access } from '../auth/access'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { AdminErrors } from './admin-errors'; +import { TowerQuerySchema, TowerRegenerateSchema } from './tower-input'; +import { AdminTowerService } from './tower.service'; + +@Controller('api/admin/tower-enemy-sets') +@Access('admin') +@UseFilters(AdminErrors) +export class AdminTowerController { + constructor( + @Inject(AdminTowerService) private readonly tower: AdminTowerService, + ) {} + + @Get() + async read( + @FirstQuery(new ZodPipe(TowerQuerySchema, 'legacy-unhandled')) + query: z.infer, + @Res({ passthrough: true }) response: Response, + ) { + const result = await this.tower.read(query); + response.setHeader('Cache-Control', 'no-store'); + return result; + } + + @Post('regenerate') + @HttpCode(200) + regenerate( + @JsonBody( + { fallback: undefined }, + new ZodPipe(TowerRegenerateSchema, 'legacy-unhandled'), + ) + input: z.infer, + ) { + return this.tower.regenerate(input); + } +} diff --git a/apps/api/src/admin/tower.service.ts b/apps/api/src/admin/tower.service.ts new file mode 100644 index 00000000..2a3604c6 --- /dev/null +++ b/apps/api/src/admin/tower.service.ts @@ -0,0 +1,76 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import { + listTowerPublishedWeeks, + parseTowerWeekRecord, + readTowerWeekRecord, + regenerateTowerWeek, + towerWeekFingerprint, +} from '@server/lib/repositories/towerRepository'; +import type { AdminTowerView } from '@daoyou/shared/contracts/adminTower'; +import { + publishedTowerEncounter, + publishedTowerPreviews, +} from '@daoyou/shared/engine/combat-v6/tower/published'; +import { + getNextTowerSeasonMeta, + getTowerSeasonMeta, +} from '@daoyou/shared/lib/tower/season'; +import { z } from 'zod'; +import { TowerQuerySchema, TowerRegenerateSchema } from './tower-input'; +@Injectable() +export class AdminTowerService { + async read(query: z.infer) { + const currentSeason = getTowerSeasonMeta(); + const nextSeason = getNextTowerSeasonMeta( + new Date(currentSeason.seasonStartedAt), + ); + const seasonKey = query.seasonKey ?? currentSeason.seasonKey; + const [weeks, row] = await Promise.all([ + listTowerPublishedWeeks(), + readTowerWeekRecord(seasonKey), + ]); + // Read and validate this exact snapshot, keeping its fingerprint and display consistent. + const pack = parseTowerWeekRecord(row); + const summarize = (item: (typeof weeks)[number]) => ({ + seasonKey: item.seasonKey, + schemaVersion: item.schemaVersion, + contentVersion: item.contentVersion, + generatorVersion: item.generatorVersion, + publishedAt: item.createdAt.toISOString(), + }); + const data: AdminTowerView = { + currentSeason, + nextSeason, + seasonKey, + realm: query.realm, + floor: query.floor, + weeks: weeks.map(summarize), + fingerprint: towerWeekFingerprint(row), + published: row ? summarize(row) : null, + configuration: pack + ? { + season: pack.season, + previews: publishedTowerPreviews(pack), + encounter: publishedTowerEncounter(pack, query.realm, query.floor), + } + : null, + }; + return { success: true, data }; + } + async regenerate(input: z.infer) { + const current = getTowerSeasonMeta(); + const next = getNextTowerSeasonMeta(new Date(current.seasonStartedAt)); + const season = [current, next].find( + (item) => item.seasonKey === input.seasonKey, + ); + if (!season) + throw new HttpException({ error: '仅可重新生成本周或下周配置' }, 400); + const replaced = await regenerateTowerWeek( + season, + input.expectedFingerprint, + ); + if (!replaced) + throw new HttpException({ error: '周配置已变化,请刷新后重新生成' }, 409); + return { success: true }; + } +} diff --git a/apps/api/src/alchemy/alchemy-formulas.controller.ts b/apps/api/src/alchemy/alchemy-formulas.controller.ts new file mode 100644 index 00000000..761eb91b --- /dev/null +++ b/apps/api/src/alchemy/alchemy-formulas.controller.ts @@ -0,0 +1,107 @@ +import { + Controller, + Delete, + Get, + HttpCode, + Inject, + Param, + Post, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { AlchemyServiceError } from '@server/lib/services/AlchemyServiceError'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { AlchemyFormulasService } from './alchemy-formulas.service'; +import { + DiscoveryConfirmSchema, + FormulaAnalyzeSchema, + FormulaIdParamSchema, + FormulaListQuerySchema, +} from './alchemy-input'; + +function formulaErrors(message: string, includeDetails = true) { + return apiErrorFilter((error) => { + if (error instanceof z.ZodError) + return Response.json( + { error: error.issues[0]?.message || '请求参数格式错误' }, + { status: 400 }, + ); + if (error instanceof AlchemyServiceError) + return Response.json( + { + error: error.message, + ...(includeDetails ? (error.details ?? {}) : {}), + }, + { status: error.status }, + ); + return Response.json({ error: message }, { status: 500 }); + }); +} + +@Controller('api/alchemy') +@Access('active') +export class AlchemyFormulasController { + constructor( + @Inject(AlchemyFormulasService) + private readonly formulas: AlchemyFormulasService, + ) {} + + @Get('materials') + materials(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.formulas.materials(actor.cultivatorId); + } + + @Get('formulas') + @UseFilters(formulaErrors('丹方列表读取失败,请稍后再试。', false)) + list( + @CurrentCultivator() actor: ActiveCultivatorRef, + @FirstQuery() query: Record, + ) { + const input = FormulaListQuerySchema.parse({ + page: query.page, + pageSize: query.pageSize, + search: query.search || undefined, + family: query.family || undefined, + }); + return this.formulas.list(actor.cultivatorId, input); + } + + @Delete('formulas/:formulaId') + @UseFilters(formulaErrors('丹方删除失败,请稍后再试。')) + remove( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param(new ZodPipe(FormulaIdParamSchema)) + params: z.infer, + ) { + return this.formulas.remove(actor.cultivatorId, params.formulaId); + } + + @Post('formulas/:formulaId/analyze') + @HttpCode(200) + @UseFilters(formulaErrors('推演药路失败,请稍后再试。')) + analyze( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param(new ZodPipe(FormulaIdParamSchema)) + params: z.infer, + @JsonBody(new ZodPipe(FormulaAnalyzeSchema)) + input: z.infer, + ) { + return this.formulas.analyze(actor.cultivatorId, params.formulaId, input); + } + + @Post('formulas/discovery/confirm') + @HttpCode(200) + @UseFilters(formulaErrors('丹方确认失败,请稍后再试。')) + confirm( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(DiscoveryConfirmSchema)) + input: z.infer, + ) { + return this.formulas.confirm(actor.cultivatorId, input); + } +} diff --git a/apps/api/src/alchemy/alchemy-formulas.service.ts b/apps/api/src/alchemy/alchemy-formulas.service.ts new file mode 100644 index 00000000..ed4ffd6e --- /dev/null +++ b/apps/api/src/alchemy/alchemy-formulas.service.ts @@ -0,0 +1,65 @@ +import { Injectable } from '@nestjs/common'; +import { + assertAlchemyMaterialVersions, + readAlchemyMaterials, +} from '@server/lib/services/alchemy/AlchemyInventory'; +import { + analyzeFormulaMaterials, + confirmDiscoveryCandidate, + deleteCultivatorFormula, + listCultivatorFormulasPage, +} from '@server/lib/services/AlchemyFormulaService'; +import type { z } from 'zod'; +import type { + DiscoveryConfirmSchema, + FormulaAnalyzeSchema, + FormulaListQuerySchema, +} from './alchemy-input'; + +@Injectable() +export class AlchemyFormulasService { + async materials(owner: string) { + return { success: true, data: await readAlchemyMaterials(owner) }; + } + + async list(owner: string, query: z.infer) { + const result = await listCultivatorFormulasPage(owner, query); + return { + success: true, + data: { formulas: result.formulas, pagination: result.pagination }, + }; + } + + async remove(owner: string, formulaId: string) { + await deleteCultivatorFormula(owner, formulaId); + return { success: true, message: '丹方已删除' }; + } + + async analyze( + owner: string, + formulaId: string, + input: z.infer, + ) { + await assertAlchemyMaterialVersions( + owner, + input.materialIds, + input.materialVersions, + ); + return { + success: true, + data: await analyzeFormulaMaterials( + owner, + formulaId, + input.materialIds, + input.materialQuantities, + ), + }; + } + + async confirm(owner: string, input: z.infer) { + return { + success: true, + data: await confirmDiscoveryCandidate(owner, input.token, input.accept), + }; + } +} diff --git a/apps/api/src/alchemy/alchemy-input.ts b/apps/api/src/alchemy/alchemy-input.ts new file mode 100644 index 00000000..5ed4f535 --- /dev/null +++ b/apps/api/src/alchemy/alchemy-input.ts @@ -0,0 +1,55 @@ +import { + ALCHEMY_INPUT_CONSTRAINTS, + ALCHEMY_MAX_DOSE, +} from '@daoyou/shared/config/alchemyInput'; +import { JournalRequestSchema } from '@daoyou/shared/contracts/playerJournal'; +import { PILL_FAMILY_VALUES } from '@daoyou/shared/types/consumable'; +import { z } from 'zod'; + +export const DiscoveryConfirmSchema = z.object({ + token: z.string().uuid(), + accept: z.boolean(), +}); +export const FormulaIdParamSchema = z.object({ formulaId: z.string().uuid() }); +export const FormulaAnalyzeSchema = z.object({ + materialIds: z.array(z.string()).min(1).max(6), + materialVersions: z.record(z.string(), z.string().max(10000)), + materialQuantities: z + .record( + z.string(), + z + .number() + .int() + .min(ALCHEMY_INPUT_CONSTRAINTS.minQuantityPerMaterial) + .max(ALCHEMY_MAX_DOSE), + ) + .optional(), +}); +export const FormulaListQuerySchema = z.object({ + page: z.coerce.number().int().min(1).default(1), + pageSize: z.coerce.number().int().min(1).max(5).default(5), + search: z.string().trim().max(40).optional(), + family: z.enum(PILL_FAMILY_VALUES).optional(), +}); + +export const CraftSchema = z + .object({ + craftType: z.literal('alchemy'), + alchemyMode: z.enum(['improvised', 'formula']).default('improvised'), + materialIds: z + .array(z.string().min(1).max(160)) + .min(1) + .max(6) + .refine((ids) => new Set(ids).size === ids.length, '材料不能重复'), + materialVersions: z.record(z.string(), z.string().max(10000)), + materialQuantities: z + .record(z.string(), z.number().int().min(1).max(ALCHEMY_MAX_DOSE)) + .optional(), + userPrompt: z.string().trim().max(300).optional(), + formulaId: z.uuid().optional(), + analysisId: z.uuid().optional(), + }) + .strict(); +export const CraftCommandSchema = CraftSchema.extend( + JournalRequestSchema.shape, +); diff --git a/apps/api/src/alchemy/alchemy.module.ts b/apps/api/src/alchemy/alchemy.module.ts new file mode 100644 index 00000000..d0a86070 --- /dev/null +++ b/apps/api/src/alchemy/alchemy.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { AlchemyFormulasController } from './alchemy-formulas.controller'; +import { AlchemyFormulasService } from './alchemy-formulas.service'; +import { CraftController } from './craft.controller'; +import { CraftService } from './craft.service'; + +@Module({ + controllers: [AlchemyFormulasController, CraftController], + providers: [AlchemyFormulasService, CraftService], +}) +export class AlchemyModule {} diff --git a/apps/api/src/alchemy/craft.controller.ts b/apps/api/src/alchemy/craft.controller.ts new file mode 100644 index 00000000..e2467c0a --- /dev/null +++ b/apps/api/src/alchemy/craft.controller.ts @@ -0,0 +1,108 @@ +import { + All, + Controller, + Get, + HttpCode, + HttpException, + Inject, + Post, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import { AlchemyServiceError } from '@server/lib/services/AlchemyServiceError'; +import { PlayerCommandIdempotencyError } from '@server/lib/services/CommandExecutors'; +import { CraftCommandError } from '@server/lib/services/CraftApplicationService'; +import { QiServiceError } from '@server/lib/services/QiService'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody } from '../http/json-body'; +import { CraftCommandSchema, CraftSchema } from './alchemy-input'; +import { CraftService } from './craft.service'; + +const retired = new Set(['refine', 'create_skill', 'create_gongfa']); +const retiredMessage = '旧功法、神通及装备生产已停用,历史物品保留在洞府宝库'; +const CraftErrors = apiErrorFilter((error) => { + const lock = redisLockErrorResponse(error); + if (lock) return lock; + if (error instanceof z.ZodError || error instanceof SyntaxError) + return Response.json( + { success: false, error: '请求参数无效' }, + { status: 400 }, + ); + if ( + error instanceof AlchemyServiceError || + error instanceof CraftCommandError || + error instanceof PlayerCommandIdempotencyError || + error instanceof QiServiceError + ) + return Response.json( + { success: false, error: error.message }, + { status: error.status }, + ); + console.error('[alchemy] request failed', error); + return Response.json( + { success: false, error: '炼丹请求失败,请重新核对材料' }, + { status: 500 }, + ); +}); + +@Controller('api/craft') +@Access('active') +@UseFilters(CraftErrors) +export class CraftController { + constructor(@Inject(CraftService) private readonly craft: CraftService) {} + + @Get() + preview( + @CurrentCultivator() actor: ActiveCultivatorRef, + @FirstQuery() query: Record, + ) { + if (query.craftType && retired.has(query.craftType)) + throw new HttpException({ error: retiredMessage }, 410); + const input = CraftSchema.parse({ + craftType: query.craftType, + alchemyMode: query.alchemyMode, + materialIds: (query.materialIds ?? '').split(','), + materialQuantities: query.materialQuantities + ? JSON.parse(query.materialQuantities) + : undefined, + formulaId: query.formulaId, + materialVersions: query.materialVersions + ? JSON.parse(query.materialVersions) + : {}, + }); + return this.craft.preview(actor, input); + } + + @Post() + @HttpCode(200) + execute( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody() body: unknown, + ) { + if ( + body && + typeof body === 'object' && + 'craftType' in body && + typeof body.craftType === 'string' && + retired.has(body.craftType) + ) + throw new HttpException({ error: retiredMessage }, 410); + return this.craft.execute(actor, CraftCommandSchema.parse(body)); + } + + @All('pending') + @HttpCode(410) + pending() { + return { error: retiredMessage }; + } + + @All('confirm') + @HttpCode(410) + confirm() { + return { error: retiredMessage }; + } +} diff --git a/apps/api/src/alchemy/craft.service.ts b/apps/api/src/alchemy/craft.service.ts new file mode 100644 index 00000000..77e22690 --- /dev/null +++ b/apps/api/src/alchemy/craft.service.ts @@ -0,0 +1,63 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { assertAlchemyMaterialVersions } from '@server/lib/services/alchemy/AlchemyInventory'; +import { previewFormulaCraft } from '@server/lib/services/AlchemyFormulaService'; +import { previewAlchemySelection } from '@server/lib/services/alchemyServiceV2'; +import { executeCraftCommand } from '@server/lib/services/CraftApplicationService'; +import { readCraftReadinessFacts } from '@server/lib/services/cultivator/CultivatorFactsReader'; +import { getPlayerPreHeavenFates } from '@server/lib/services/cultivator/CultivatorProfileRepository'; +import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; +import type { z } from 'zod'; +import type { CraftCommandSchema, CraftSchema } from './alchemy-input'; + +@Injectable() +export class CraftService { + async preview( + actor: ActiveCultivatorRef, + input: z.infer, + ) { + const owner = actor.cultivatorId; + await assertAlchemyMaterialVersions( + owner, + input.materialIds, + input.materialVersions, + ); + const [facts, fates] = await Promise.all([ + readCraftReadinessFacts(owner), + getPlayerPreHeavenFates(actor.userId, owner), + ]); + if (input.alchemyMode === 'formula' && !input.formulaId) + throw new HttpException({ error: '请选择丹方' }, 400); + const data = + input.alchemyMode === 'formula' + ? await previewFormulaCraft( + owner, + input.formulaId!, + input.materialIds, + facts.spiritStones, + fates ?? [], + input.materialQuantities, + ) + : await previewAlchemySelection( + owner, + facts.spiritStones, + input.materialIds, + fates ?? [], + input.materialQuantities, + ); + return { success: true, data }; + } + + async execute( + actor: ActiveCultivatorRef, + input: z.infer, + ) { + return toPlayerStateMutationResponse( + await executeCraftCommand({ + userId: actor.userId, + cultivatorId: actor.cultivatorId, + input, + }), + ); + } +} diff --git a/apps/api/src/app.module.ts b/apps/api/src/app.module.ts new file mode 100644 index 00000000..708bacab --- /dev/null +++ b/apps/api/src/app.module.ts @@ -0,0 +1,103 @@ +import { getRuntimeEnvironment } from '@server/lib/config/environment'; +import { ConfigurationModule } from './config/configuration.module'; +import { Module } from '@nestjs/common'; +import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core'; +import { allowsLocalDevTools } from '@daoyou/shared/config/deployment'; +import { AccountModule } from './account/account.module'; +import { AdminModule } from './admin/admin.module'; +import { AlchemyModule } from './alchemy/alchemy.module'; +import { ArenaModule } from './arena/arena.module'; +import { AuctionModule } from './auction/auction.module'; +import { AuthModule } from './auth/auth.module'; +import { BeastsModule } from './beasts/beasts.module'; +import { BlackMarketModule } from './black-market/black-market.module'; +import { CombatModule } from './combat/combat.module'; +import { CommunityModule } from './community/community.module'; +import { CultivatorModule } from './cultivator/cultivator.module'; +import { DevToolsModule } from './dev-tools/dev-tools.module'; +import { DivinationModule } from './divination/divination.module'; +import { DungeonModule } from './dungeon/dungeon.module'; +import { EnemiesModule } from './enemies/enemies.module'; +import { EnlightenmentModule } from './enlightenment/enlightenment.module'; +import { FeedbackModule } from './feedback/feedback.module'; +import { ForgingModule } from './forging/forging.module'; +import { GenesisModule } from './genesis/genesis.module'; +import { HealthModule } from './health/health.module'; +import { ApiExceptionFilter } from './http/api-exception.filter'; +import { NotFoundModule } from './http/not-found.module'; +import { RequestContextInterceptor } from './http/request-context.interceptor'; +import { HuntsModule } from './hunts/hunts.module'; +import { InscriptionsModule } from './inscriptions/inscriptions.module'; +import { InventoryModule } from './inventory/inventory.module'; +import { LegacyItemsModule } from './legacy-items/legacy-items.module'; +import { MailModule } from './mail/mail.module'; +import { ManualsModule } from './manuals/manuals.module'; +import { MarketModule } from './market/market.module'; +import { PlayerModule } from './player/player.module'; +import { RankingsModule } from './rankings/rankings.module'; +import { RealtimeModule } from './realtime/realtime.module'; +import { ReputationShopModule } from './reputation-shop/reputation-shop.module'; +import { ReshapeModule } from './reshape/reshape.module'; +import { RuntimeModule } from './runtime/runtime.module'; +import { SectsModule } from './sects/sects.module'; +import { SocialModule } from './social/social.module'; +import { SpiritFieldModule } from './spirit-field/spirit-field.module'; +import { SponsorshipModule } from './sponsorship/sponsorship.module'; +import { StoryModule } from './story/story.module'; +import { TasksModule } from './tasks/tasks.module'; +import { TowerModule } from './tower/tower.module'; + +@Module({ + imports: [ + ConfigurationModule, + ...(allowsLocalDevTools(getRuntimeEnvironment().APP_ENV, getRuntimeEnvironment().NODE_ENV) + ? [DevToolsModule] + : []), + AuthModule, + SponsorshipModule, + GenesisModule, + ReshapeModule, + CommunityModule, + EnemiesModule, + RankingsModule, + AccountModule, + AdminModule, + AuctionModule, + BlackMarketModule, + MarketModule, + ReputationShopModule, + SpiritFieldModule, + AlchemyModule, + EnlightenmentModule, + ForgingModule, + InscriptionsModule, + ArenaModule, + HealthModule, + RuntimeModule, + DivinationModule, + RealtimeModule, + PlayerModule, + SocialModule, + StoryModule, + TasksModule, + MailModule, + CultivatorModule, + FeedbackModule, + CombatModule, + SectsModule, + BeastsModule, + InventoryModule, + LegacyItemsModule, + ManualsModule, + HuntsModule, + DungeonModule, + TowerModule, + // Namespace authorization fallbacks must follow every concrete route. + NotFoundModule, + ], + providers: [ + { provide: APP_FILTER, useClass: ApiExceptionFilter }, + { provide: APP_INTERCEPTOR, useClass: RequestContextInterceptor }, + ], +}) +export class AppModule {} diff --git a/apps/api/src/arena/arena-battles.controller.ts b/apps/api/src/arena/arena-battles.controller.ts new file mode 100644 index 00000000..37166761 --- /dev/null +++ b/apps/api/src/arena/arena-battles.controller.ts @@ -0,0 +1,111 @@ +import { + Controller, + Get, + Header, + HttpCode, + HttpException, + Inject, + Param, + Post, + Req, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { isAllowedRealtimeOrigin } from '@server/lib/http/realtimeOrigin'; +import { ArenaV6Error } from '@server/lib/services/combat-v6/CombatV6ArenaService'; +import { + ArenaV6SubmitSchema, + type ArenaV6Submit, +} from '@daoyou/shared/contracts/combatV6Arena'; +import type { Request } from 'express'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { ArenaBattlesService } from './arena-battles.service'; + +const ArenaErrors = apiErrorFilter((error) => { + if (error instanceof ArenaV6Error) + return Response.json( + { success: false, error: error.message }, + { status: error.status }, + ); + if (error instanceof z.ZodError) + return Response.json( + { success: false, error: '请求参数无效' }, + { status: 400 }, + ); + console.error('[arena-v6] request failed', error); + return Response.json( + { success: false, error: '战斗请求失败,请刷新重试' }, + { status: 500 }, + ); +}); +const CursorSchema = z.coerce.number().int().min(-1).optional(); + +@Controller('api/combat-v6/arena') +@Access('active') +@UseFilters(ArenaErrors) +export class ArenaBattlesController { + constructor( + @Inject(ArenaBattlesService) private readonly battles: ArenaBattlesService, + ) {} + + @Get([':battleId/socket', ':battleId/watch/socket']) + @HttpCode(404) + async withoutUpgrade( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('battleId') id: string, + @Req() request: Request, + ) { + if (!isAllowedRealtimeOrigin(request.get('origin'))) + throw new HttpException({ error: 'Origin forbidden' }, 403); + await this.battles.authorizeSocket( + actor, + z.uuid().parse(id), + request.path.endsWith('/watch/socket'), + ); + return { success: false, error: '接口不存在' }; + } + + @Get(':battleId') + @Header('Cache-Control', 'private, no-store') + read( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('battleId', new ZodPipe(z.uuid())) id: string, + @FirstQuery('afterEventSeq', new ZodPipe(CursorSchema)) cursor?: number, + ) { + return this.battles.read(actor, id, false, cursor); + } + + @Get(':battleId/watch') + @Header('Cache-Control', 'private, no-store') + watch( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('battleId', new ZodPipe(z.uuid())) id: string, + @FirstQuery('afterEventSeq', new ZodPipe(CursorSchema)) cursor?: number, + ) { + return this.battles.read(actor, id, true, cursor); + } + + @Post(':battleId/commands') + @HttpCode(200) + submit( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('battleId', new ZodPipe(z.uuid())) id: string, + @JsonBody({ fallback: undefined }, new ZodPipe(ArenaV6SubmitSchema)) + input: ArenaV6Submit, + ) { + return this.battles.submit(actor, id, input); + } + + @Get(':battleId/replay') + replay( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('battleId', new ZodPipe(z.uuid())) id: string, + ) { + return this.battles.replay(actor, id); + } +} diff --git a/apps/api/src/arena/arena-battles.service.ts b/apps/api/src/arena/arena-battles.service.ts new file mode 100644 index 00000000..aacce4c7 --- /dev/null +++ b/apps/api/src/arena/arena-battles.service.ts @@ -0,0 +1,59 @@ +import { Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { + arenaReplayV6, + ownedArenaV6, + submitArenaV6, + watchedArenaV6, +} from '@server/lib/services/combat-v6/CombatV6ArenaService'; +import { arenaView } from '@daoyou/shared/combat-v6/arena'; +import type { ArenaV6Submit } from '@daoyou/shared/contracts/combatV6Arena'; + +@Injectable() +export class ArenaBattlesService { + async authorizeSocket( + actor: ActiveCultivatorRef, + id: string, + spectator: boolean, + ) { + await (spectator ? watchedArenaV6 : ownedArenaV6)(id, actor); + } + + async read( + actor: ActiveCultivatorRef, + id: string, + spectator: boolean, + cursor?: number, + ) { + const { runtime, participant } = await ( + spectator ? watchedArenaV6 : ownedArenaV6 + )(id, actor); + const last = runtime.lastResults[participant.unitId]; + const incremental = + last?.revision === runtime.revision && + last.playback?.fromEventSeq === cursor; + const view = incremental + ? { + ...last, + serverNow: Date.now(), + events: last.events.filter((e) => e.seq > cursor!), + } + : arenaView(runtime, participant.unitId, Date.now()); + const unchanged = cursor === view.latestEventSeq; + return { + success: true, + data: { + session: unchanged + ? { ...view, events: [], playback: undefined } + : view, + full: !incremental && !unchanged, + }, + }; + } + async submit(actor: ActiveCultivatorRef, id: string, input: ArenaV6Submit) { + return { success: true, data: await submitArenaV6(id, actor, input) }; + } + async replay(actor: ActiveCultivatorRef, id: string) { + return { success: true, data: await arenaReplayV6(id, actor) }; + } +} diff --git a/apps/api/src/arena/arena-realtime.service.ts b/apps/api/src/arena/arena-realtime.service.ts new file mode 100644 index 00000000..395fb64c --- /dev/null +++ b/apps/api/src/arena/arena-realtime.service.ts @@ -0,0 +1,261 @@ +import { Inject, Injectable, type OnModuleDestroy } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { isAllowedRealtimeOrigin } from '@server/lib/http/realtimeOrigin'; +import { + arenaSocketState, + subscribeArenaSpectators, + subscribeArenaV6, +} from '@server/lib/services/combat-v6/CombatV6ArenaBroadcast'; +import { + ArenaV6Error, + ownedArenaV6, + watchedArenaV6, +} from '@server/lib/services/combat-v6/CombatV6ArenaService'; +import { CombatV6ArenaStore } from '@server/lib/services/combat-v6/CombatV6ArenaStore'; +import { fromNodeHeaders } from 'better-auth/node'; +import type { IncomingMessage } from 'node:http'; +import { WebSocket } from 'ws'; +import { z } from 'zod'; +import { SessionService } from '../auth/session.service'; +import type { HandshakeError } from '../http/websocket-request'; +import { arenaSocketRoute } from './arena-socket'; + +type ArenaConnection = { + actor: ActiveCultivatorRef; + battleId: string; + spectator: boolean; +}; + +@Injectable() +export class ArenaRealtimeService implements OnModuleDestroy { + private readonly pending = new WeakMap(); + private readonly counts = new Map(); + private readonly connections = new Map void>(); + private readonly work = new Set>(); + private stopping = false; + + constructor( + @Inject(SessionService) private readonly sessions: SessionService, + @Inject(CombatV6ArenaStore) private readonly store: CombatV6ArenaStore, + ) {} + + private track(promise: Promise): Promise { + this.work.add(promise); + void promise.then( + () => this.work.delete(promise), + () => this.work.delete(promise), + ); + return promise; + } + + verify( + request: IncomingMessage, + ): Promise< + HandshakeError | { connection: ArenaConnection; headers?: Headers } + > { + return this.track(this.verifyConnection(request)); + } + + private async verifyConnection( + request: IncomingMessage, + ): Promise< + HandshakeError | { connection: ArenaConnection; headers?: Headers } + > { + if (this.stopping) return { status: 503, message: 'Server closing' }; + const headers = fromNodeHeaders(request.headers); + let responseHeaders: Headers | undefined; + try { + const session = await this.sessions.getSession(headers); + responseHeaders = session.headers; + const user = session.response?.user; + if (!user) + return { status: 401, message: '未授权访问', headers: responseHeaders }; + const actor = await this.sessions.getActiveCultivator(user); + if (!actor) + return { + status: 404, + message: '当前没有活跃角色', + headers: responseHeaders, + }; + if (!isAllowedRealtimeOrigin(headers.get('origin'))) + return { + status: 403, + message: 'Origin forbidden', + body: { error: 'Origin forbidden' }, + headers: responseHeaders, + }; + const route = arenaSocketRoute( + new URL(request.url ?? '/', 'http://localhost').pathname, + ); + if (!route) + return { status: 404, message: '接口不存在', headers: responseHeaders }; + const battleId = z.uuid().parse(route.battleId); + await (route.spectator ? watchedArenaV6 : ownedArenaV6)(battleId, actor); + if (this.stopping || request.socket.destroyed) + return { status: 503, message: 'Connection closed' }; + const connection = { + actor, + battleId, + spectator: route.spectator, + }; + this.pending.set(request, connection); + return { connection, headers: responseHeaders }; + } catch (error) { + if (error instanceof ArenaV6Error) + return { + status: error.status, + message: error.message, + headers: responseHeaders, + }; + if (error instanceof z.ZodError) + return { + status: 400, + message: '请求参数无效', + headers: responseHeaders, + }; + console.error('[arena-v6] handshake failed', error); + return { + status: 500, + message: '战斗请求失败,请刷新重试', + headers: responseHeaders, + }; + } + } + + connect(socket: WebSocket, request: IncomingMessage): void { + const identity = this.pending.get(request); + this.pending.delete(request); + if (!identity || this.stopping) { + socket.close(1011, 'reconnect'); + return; + } + const { actor, battleId: id, spectator } = identity; + if ((this.counts.get(actor.userId) ?? 0) >= 4) { + socket.close(1008, 'too many connections'); + return; + } + this.counts.set(actor.userId, (this.counts.get(actor.userId) ?? 0) + 1); + const connection = crypto.randomUUID(); + const authorize = spectator ? watchedArenaV6 : ownedArenaV6; + let viewerId: string | undefined; + let dispose: (() => void) | undefined; + let heartbeat: ReturnType | undefined; + let closed = false; + let lastPong = Date.now(); + let lastRevision = -1; + let chain = Promise.resolve(); + const cleanup = () => { + if (closed) return; + closed = true; + this.connections.delete(socket); + const count = (this.counts.get(actor.userId) ?? 1) - 1; + if (count) this.counts.set(actor.userId, count); + else this.counts.delete(actor.userId); + clearInterval(heartbeat); + dispose?.(); + if (viewerId && !spectator) + void this.track( + this.store.disconnect(`${id}:${viewerId}`, connection), + ).catch((error) => console.warn('[arena-v6] disconnect failed', error)); + }; + const close = (code: number, reason: string) => { + socket.close(code, reason); + cleanup(); + }; + const send = (message: string) => { + if (closed || socket.readyState !== WebSocket.OPEN) return; + if (socket.bufferedAmount > (spectator ? 512000 : 1_048_576)) { + close(4001, 'slow consumer'); + return; + } + socket.send(message, (error) => { + if (error) close(1011, 'reconnect'); + }); + }; + const enqueue = (action: () => Promise) => { + chain = this.track( + chain + .then(async () => { + if (!closed) await action(); + }) + .catch(() => { + if (!closed) close(1011, 'reconnect'); + }), + ); + }; + this.connections.set(socket, cleanup); + socket.once('close', cleanup); + socket.once('error', cleanup); + socket.on('message', (data, binary) => { + if (closed) return; + const message = data.toString(); + if (binary || message.length > 128) { + close(1008, 'invalid message'); + return; + } + if (message === 'pong') { + lastPong = Date.now(); + if (viewerId && !spectator) { + const key = `${id}:${viewerId}`; + void this.track( + this.store.touch(key, connection).then(async () => { + if (closed) await this.store.disconnect(key, connection); + }), + ).catch(() => close(1011, 'reconnect')); + } + } + }); + const open = async () => { + const owned = await authorize(id, actor); + viewerId = owned.participant.unitId; + if (closed) return; + dispose = spectator + ? await subscribeArenaSpectators(id, { + ...actor, + send, + close: () => close(1008, 'spectator access ended'), + }) + : await subscribeArenaV6(id, () => + enqueue(async () => { + const { runtime } = await authorize(id, actor); + if (runtime.revision <= lastRevision) return; + lastRevision = runtime.revision; + send(JSON.stringify(arenaSocketState(runtime, viewerId!))); + }), + ); + if (closed) { + dispose(); + return; + } + if (!spectator) await this.store.touch(`${id}:${viewerId}`, connection); + if (closed) { + if (!spectator) + await this.store.disconnect(`${id}:${viewerId}`, connection); + return; + } + send(JSON.stringify({ type: 'ready', serverNow: Date.now() })); + if (closed) return; + heartbeat = setInterval(() => { + if (Date.now() - lastPong > 50000) { + close(4000, 'heartbeat timeout'); + return; + } + send(JSON.stringify({ type: 'ping', serverNow: Date.now() })); + enqueue(async () => { + const { runtime } = await authorize(id, actor); + send(JSON.stringify({ type: 'resync', revision: runtime.revision })); + }); + }, 25000); + }; + void this.track(open().catch(() => close(1011, 'reconnect'))); + } + + async onModuleDestroy(): Promise { + this.stopping = true; + for (const [socket, cleanup] of this.connections) { + cleanup(); + socket.terminate(); + } + while (this.work.size) await Promise.allSettled([...this.work]); + } +} diff --git a/apps/api/src/arena/arena-rooms.controller.ts b/apps/api/src/arena/arena-rooms.controller.ts new file mode 100644 index 00000000..21c01cce --- /dev/null +++ b/apps/api/src/arena/arena-rooms.controller.ts @@ -0,0 +1,135 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Param, + Post, + Res, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { + ArenaCreateRoomSchema, + ArenaJoinRoomSchema, + ArenaReadyCommandSchema, + ArenaStartCommandSchema, +} from '@daoyou/shared/contracts/arena'; +import type { Response } from 'express'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { ArenaRoomsService } from './arena-rooms.service'; + +const RoomIdSchema = z + .string() + .min(1) + .max(120) + .regex(/^arena-[A-Za-z0-9-]+$/); +const EmptySchema = z.object({}).strict(); + +@Controller('api/arena') +@Access('active') +export class ArenaRoomsController { + constructor( + @Inject(ArenaRoomsService) private readonly rooms: ArenaRoomsService, + ) {} + + @Get('room') + current(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.rooms.current(actor); + } + + @Get('rooms/:roomId') + read( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('roomId', new ZodPipe(RoomIdSchema)) roomId: string, + ) { + return this.rooms.read(actor, roomId); + } + + @Post('rooms') + @HttpCode(201) + create( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody({ fallback: undefined }, new ZodPipe(ArenaCreateRoomSchema)) + _input: z.infer, + ) { + void _input; + return this.rooms.create(actor); + } + + @Post('rooms/join') + @HttpCode(200) + join( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody({ fallback: undefined }, new ZodPipe(ArenaJoinRoomSchema)) + input: z.infer, + ) { + return this.rooms.join(actor, input); + } + + @Post('rooms/:roomId/ready') + @HttpCode(200) + ready( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('roomId', new ZodPipe(RoomIdSchema)) roomId: string, + @JsonBody({ fallback: undefined }, new ZodPipe(ArenaReadyCommandSchema)) + input: z.infer, + ) { + return this.rooms.change(actor, roomId, { + kind: 'ready', + ready: input.ready, + }); + } + + @Post('rooms/:roomId/touch') + @HttpCode(200) + touch( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('roomId', new ZodPipe(RoomIdSchema)) roomId: string, + @JsonBody({ fallback: undefined }, new ZodPipe(EmptySchema)) + _input: z.infer, + ) { + void _input; + return this.rooms.change(actor, roomId, { kind: 'touch' }); + } + + @Post('rooms/:roomId/switch-team') + @HttpCode(200) + switchTeam( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('roomId', new ZodPipe(RoomIdSchema)) roomId: string, + @JsonBody({ fallback: undefined }, new ZodPipe(EmptySchema)) + _input: z.infer, + ) { + void _input; + return this.rooms.change(actor, roomId, { kind: 'switch-team' }); + } + + @Post('rooms/:roomId/start') + @HttpCode(200) + async start( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('roomId', new ZodPipe(RoomIdSchema)) roomId: string, + @JsonBody({ fallback: undefined }, new ZodPipe(ArenaStartCommandSchema)) + input: z.infer, + @Res({ passthrough: true }) response: Response, + ) { + const result = await this.rooms.start(actor, roomId, input.requestId); + response.status(result.status); + return result.body; + } + + @Post('rooms/:roomId/leave') + @HttpCode(200) + leave( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('roomId', new ZodPipe(RoomIdSchema)) roomId: string, + @JsonBody({ fallback: undefined }, new ZodPipe(EmptySchema)) + _input: z.infer, + ) { + void _input; + return this.rooms.leave(actor, roomId); + } +} diff --git a/apps/api/src/arena/arena-rooms.service.ts b/apps/api/src/arena/arena-rooms.service.ts new file mode 100644 index 00000000..ae469237 --- /dev/null +++ b/apps/api/src/arena/arena-rooms.service.ts @@ -0,0 +1,214 @@ +import { HttpException, Inject, Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { getExecutor } from '@server/lib/drizzle/db'; +import { cultivators } from '@server/lib/drizzle/schema'; +import { ArenaBattleStartOrchestrator } from '@server/lib/services/ArenaBattleStartOrchestrator'; +import { ArenaRoomService } from '@server/lib/services/ArenaRoomService'; +import { publishArenaRoomChanges } from '@server/lib/services/arenaRoomBroadcaster'; +import { CombatV6BuildError } from '@server/lib/services/combat-v6/CombatV6BuildService'; +import type { ArenaRoomV1 } from '@daoyou/shared/contracts/arena'; +import { REALM_STAGE_VALUES, REALM_VALUES } from '@daoyou/shared/types/constants'; +import { and, eq } from 'drizzle-orm'; +import { z } from 'zod'; + +function roomError(error: unknown): never { + if (error instanceof CombatV6BuildError) + throw new HttpException( + { error: error.message, code: error.code }, + error.status, + ); + const message = error instanceof Error ? error.message : '擂台房间操作失败'; + if (/不存在|过期|邀请码无效/.test(message)) + throw new HttpException({ error: message }, 404); + if ( + /已经|已满|不能|需要|准备|房主|不接受|不在|不可用|状态已变化/.test(message) + ) + throw new HttpException({ error: message }, 409); + throw error; +} + +function userIds(room: ArenaRoomV1) { + return room.teams.alpha + .concat(room.teams.beta, room.spectators ?? []) + .map((seat) => seat.userId); +} +function publish(room: ArenaRoomV1): void { + publishArenaRoomChanges(userIds(room), { + roomId: room.roomId, + revision: room.revision, + status: room.status, + room, + }); +} + +@Injectable() +export class ArenaRoomsService { + constructor( + @Inject(ArenaRoomService) private readonly rooms: ArenaRoomService, + @Inject(ArenaBattleStartOrchestrator) + private readonly starts: ArenaBattleStartOrchestrator, + ) {} + + private async identity(actor: ActiveCultivatorRef) { + const row = await getExecutor().query.cultivators.findFirst({ + columns: { + id: true, + userId: true, + name: true, + realm: true, + realm_stage: true, + }, + where: and( + eq(cultivators.id, actor.cultivatorId), + eq(cultivators.userId, actor.userId), + eq(cultivators.status, 'active'), + ), + }); + if (!row) throw new HttpException({ error: '当前没有可用的活跃角色' }, 404); + const realm = z.enum(REALM_VALUES).safeParse(row.realm); + const realmStage = z.enum(REALM_STAGE_VALUES).safeParse(row.realm_stage); + if (!realm.success || !realmStage.success) + throw new HttpException({ error: '当前角色境界数据无效' }, 500); + return { + userId: row.userId, + cultivatorId: row.id, + displayName: row.name, + realm: realm.data, + realmStage: realmStage.data, + }; + } + + private async memberRoom( + actor: ActiveCultivatorRef, + roomId: string, + message: string, + ) { + const room = await this.rooms.getRoom(roomId); + if (!room) + throw new HttpException({ error: '擂台房间不存在或已过期' }, 404); + if ( + !room.teams.alpha + .concat(room.teams.beta, room.spectators ?? []) + .some( + (seat) => + seat.userId === actor.userId && + seat.cultivatorId === actor.cultivatorId, + ) + ) + throw new HttpException({ error: message }, 403); + return room; + } + + async current(actor: ActiveCultivatorRef) { + const identity = await this.identity(actor); + return { + room: await this.rooms.getRoomForCultivator(identity.cultivatorId), + }; + } + async read(actor: ActiveCultivatorRef, roomId: string) { + await this.identity(actor); + return { room: await this.memberRoom(actor, roomId, '你不在此擂台房间中') }; + } + async create(actor: ActiveCultivatorRef) { + const identity = await this.identity(actor); + try { + const room = await this.rooms.createRoom(identity); + publish(room); + return { room }; + } catch (error) { + roomError(error); + } + } + async join( + actor: ActiveCultivatorRef, + input: { inviteCode: string; role: 'participant' | 'spectator' }, + ) { + const identity = await this.identity(actor); + try { + const room = await this.rooms.joinRoom({ ...identity, ...input }); + publish(room); + return { room }; + } catch (error) { + roomError(error); + } + } + async change( + actor: ActiveCultivatorRef, + roomId: string, + command: + { kind: 'ready'; ready: boolean } | { kind: 'touch' | 'switch-team' }, + ) { + const identity = await this.identity(actor); + await this.memberRoom(actor, roomId, '当前活跃修士不在此擂台房间中'); + try { + const room = await (command.kind === 'ready' + ? this.rooms.setReady(roomId, identity.userId, command.ready) + : command.kind === 'touch' + ? this.rooms.touch(roomId, identity.userId) + : this.rooms.switchTeam(roomId, identity.userId)); + publish(room); + return { room }; + } catch (error) { + roomError(error); + } + } + async start(actor: ActiveCultivatorRef, roomId: string, requestId: string) { + const identity = await this.identity(actor); + await this.memberRoom(actor, roomId, '当前活跃修士不在此擂台房间中'); + try { + const result = await this.starts.start({ + roomId, + hostUserId: identity.userId, + requestId, + }); + publish(result.room); + return { + status: result.pending ? 202 : 200, + body: { + room: result.room, + pending: result.pending, + ...(result.room.battleMatchId + ? { battleMatchId: result.room.battleMatchId } + : {}), + }, + }; + } catch (error) { + const current = await this.rooms.getRoom(roomId).catch(() => null); + if (current) publish(current); + if ( + error instanceof Error && + /Battle matchmaker|Battle session|fetch failed|timed out/i.test( + error.message, + ) + ) { + console.error('[arena-start] battle orchestration failed', { + roomId, + error, + }); + throw new HttpException( + { error: '战斗服务暂不可用,开擂状态已保留' }, + 503, + ); + } + roomError(error); + } + } + async leave(actor: ActiveCultivatorRef, roomId: string) { + const identity = await this.identity(actor); + await this.memberRoom(actor, roomId, '当前活跃修士不在此擂台房间中'); + try { + const previous = await this.rooms.getRoom(roomId); + if (!previous) + throw new HttpException({ error: '擂台房间不存在或已过期' }, 404); + const room = await this.rooms.leave(roomId, identity.userId); + publishArenaRoomChanges(userIds(previous), { + roomId, + revision: room?.revision ?? previous.revision + 1, + status: room?.status ?? 'cancelled', + }); + return { room: null }; + } catch (error) { + roomError(error); + } + } +} diff --git a/apps/api/src/arena/arena-socket.ts b/apps/api/src/arena/arena-socket.ts new file mode 100644 index 00000000..2f254343 --- /dev/null +++ b/apps/api/src/arena/arena-socket.ts @@ -0,0 +1,8 @@ +export const ARENA_SOCKET_PATH = '/api/combat-v6/arena'; + +export function arenaSocketRoute(pathname: string) { + const match = /^\/api\/combat-v6\/arena\/([^/]+)\/(watch\/)?socket\/?$/.exec( + pathname, + ); + return match ? { battleId: match[1], spectator: !!match[2] } : null; +} diff --git a/apps/api/src/arena/arena.gateway.ts b/apps/api/src/arena/arena.gateway.ts new file mode 100644 index 00000000..aa371746 --- /dev/null +++ b/apps/api/src/arena/arena.gateway.ts @@ -0,0 +1,16 @@ +import { Inject } from '@nestjs/common'; +import { WebSocketGateway, type OnGatewayConnection } from '@nestjs/websockets'; +import type { IncomingMessage } from 'node:http'; +import type { WebSocket } from 'ws'; +import { ArenaRealtimeService } from './arena-realtime.service'; +import { ARENA_SOCKET_PATH } from './arena-socket'; + +@WebSocketGateway({ path: ARENA_SOCKET_PATH, maxPayload: 2048 }) +export class ArenaGateway implements OnGatewayConnection { + constructor( + @Inject(ArenaRealtimeService) private readonly arena: ArenaRealtimeService, + ) {} + handleConnection(socket: WebSocket, request: IncomingMessage): void { + this.arena.connect(socket, request); + } +} diff --git a/apps/api/src/arena/arena.module.ts b/apps/api/src/arena/arena.module.ts new file mode 100644 index 00000000..0dac8b2a --- /dev/null +++ b/apps/api/src/arena/arena.module.ts @@ -0,0 +1,32 @@ +import { Module } from '@nestjs/common'; +import { ArenaBattleStartOrchestrator } from '@server/lib/services/ArenaBattleStartOrchestrator'; +import { ArenaRoomService } from '@server/lib/services/ArenaRoomService'; +import { CombatV6ArenaStore } from '@server/lib/services/combat-v6/CombatV6ArenaStore'; +import { AuthModule } from '../auth/auth.module'; +import { ArenaBattlesController } from './arena-battles.controller'; +import { ArenaBattlesService } from './arena-battles.service'; +import { ArenaRealtimeService } from './arena-realtime.service'; +import { ArenaRoomsController } from './arena-rooms.controller'; +import { ArenaRoomsService } from './arena-rooms.service'; +import { ArenaGateway } from './arena.gateway'; + +@Module({ + imports: [AuthModule], + controllers: [ArenaRoomsController, ArenaBattlesController], + providers: [ + { provide: ArenaRoomService, useFactory: () => new ArenaRoomService() }, + { provide: CombatV6ArenaStore, useFactory: () => new CombatV6ArenaStore() }, + { + provide: ArenaBattleStartOrchestrator, + useFactory: (rooms: ArenaRoomService) => + new ArenaBattleStartOrchestrator(rooms), + inject: [ArenaRoomService], + }, + ArenaRoomsService, + ArenaBattlesService, + ArenaRealtimeService, + ArenaGateway, + ], + exports: [ArenaRealtimeService], +}) +export class ArenaModule {} diff --git a/apps/api/src/auction/auction-errors.ts b/apps/api/src/auction/auction-errors.ts new file mode 100644 index 00000000..1b8b205f --- /dev/null +++ b/apps/api/src/auction/auction-errors.ts @@ -0,0 +1,71 @@ +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import { AuctionServiceError } from '@server/lib/services/AuctionService'; +import { BeastError } from '@server/lib/services/combat-v6/BeastMutationGuard'; +import { PlayerCommandIdempotencyError } from '@server/lib/services/CommandExecutors'; +import { InventoryError } from '@server/lib/services/InventoryService'; +import { z } from 'zod'; +import { apiErrorFilter } from '../http/error-filter'; + +const statusMap: Record = { + INSUFFICIENT_FUNDS: 400, + LISTING_NOT_FOUND: 404, + LISTING_EXPIRED: 400, + NOT_OWNER: 403, + MAX_LISTINGS: 400, + ITEM_NOT_FOUND: 404, + CONCURRENT_PURCHASE: 429, + INVALID_ITEM_TYPE: 400, + INVALID_PRICE: 400, + INVALID_QUANTITY: 400, + INVALID_ITEM_QUALITY: 400, + SAME_OWNER: 403, + INVALID_VISIBILITY: 400, + TARGET_NOT_FRIEND: 403, + MISSING_TALISMAN: 400, + NOT_TARGET_BUYER: 403, +}; + +export const AuctionListingsErrors = apiErrorFilter((error) => { + if (error instanceof z.ZodError) + return Response.json( + { error: '参数错误', details: error.issues }, + { status: 400 }, + ); + console.error('Auction Listings API Error:', error); + return Response.json({ error: '获取拍卖列表失败' }, { status: 500 }); +}); + +export function auctionMutationErrors( + operation: 'buy' | 'list' | 'list-beast' | 'cancel', +) { + return apiErrorFilter((error) => { + const lock = redisLockErrorResponse(error); + if (lock) return lock; + if (operation === 'buy' && error instanceof z.ZodError) + return Response.json( + { error: '参数错误', details: error.issues }, + { status: 400 }, + ); + if ( + error instanceof InventoryError || + error instanceof PlayerCommandIdempotencyError || + (operation === 'list-beast' && error instanceof BeastError) + ) + return Response.json({ error: error.message }, { status: 409 }); + if (error instanceof AuctionServiceError) + return Response.json( + { error: error.message }, + { status: statusMap[error.code] || 400 }, + ); + const action = + operation === 'buy' ? 'Buy' : operation === 'cancel' ? 'Cancel' : 'List'; + console.error(`Auction ${action} API Error:`, error); + const message = + operation === 'buy' + ? '购买失败,请稍后重试' + : operation === 'cancel' + ? '下架失败,请稍后重试' + : '上架失败,请稍后重试'; + return Response.json({ error: message }, { status: 500 }); + }); +} diff --git a/apps/api/src/auction/auction-input.ts b/apps/api/src/auction/auction-input.ts new file mode 100644 index 00000000..56d52da8 --- /dev/null +++ b/apps/api/src/auction/auction-input.ts @@ -0,0 +1,18 @@ +import { AUCTION_ITEM_TYPES } from '@daoyou/shared/contracts/auction'; +import { QUALITY_VALUES } from '@daoyou/shared/types/constants'; +import { z } from 'zod'; + +export const ListingsSchema = z.object({ + scope: z.enum(['all', 'mine']).default('all'), + assetType: z.enum(['item', 'beast']).optional(), + itemType: z.enum(AUCTION_ITEM_TYPES).optional(), + itemCategory: z.string().trim().min(1).max(50).optional(), + itemQuality: z.enum(QUALITY_VALUES).optional(), + itemName: z.string().trim().min(1).max(200).optional(), + sellerName: z.string().trim().min(1).max(100).optional(), + minPrice: z.number().int().min(0).optional(), + maxPrice: z.number().int().min(0).optional(), + sortBy: z.enum(['price_asc', 'price_desc', 'latest']).optional(), + page: z.number().int().min(1).optional(), + limit: z.number().int().min(1).max(100).optional(), +}); diff --git a/apps/api/src/auction/auction.controller.ts b/apps/api/src/auction/auction.controller.ts new file mode 100644 index 00000000..786b2b82 --- /dev/null +++ b/apps/api/src/auction/auction.controller.ts @@ -0,0 +1,111 @@ +import { + Controller, + Delete, + Get, + HttpCode, + HttpException, + Inject, + Param, + Post, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { + AuctionBeastListSchema, + AuctionBuySchema, + AuctionListSchema, +} from '@daoyou/shared/contracts/auction'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { AuctionListingsErrors, auctionMutationErrors } from './auction-errors'; +import { ListingsSchema } from './auction-input'; +import { AuctionService } from './auction.service'; + +@Controller('api/auction') +@Access('active') +export class AuctionController { + constructor( + @Inject(AuctionService) private readonly auction: AuctionService, + ) {} + + @Get('listings') + @UseFilters(AuctionListingsErrors) + listings( + @CurrentCultivator() actor: ActiveCultivatorRef, + @FirstQuery() query: Record, + ) { + const params = ListingsSchema.parse({ + scope: query.scope || undefined, + assetType: query.assetType || undefined, + itemType: query.itemType || undefined, + itemCategory: query.itemCategory || undefined, + itemQuality: query.itemQuality || undefined, + itemName: query.itemName || undefined, + sellerName: query.sellerName || undefined, + minPrice: query.minPrice ? Number(query.minPrice) : undefined, + maxPrice: query.maxPrice ? Number(query.maxPrice) : undefined, + sortBy: query.sortBy || undefined, + page: query.page ? Number(query.page) : undefined, + limit: query.limit ? Number(query.limit) : undefined, + }); + return this.auction.listings(actor.cultivatorId, params); + } + + @Post('buy') + @HttpCode(200) + @UseFilters(auctionMutationErrors('buy')) + buy( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(AuctionBuySchema)) + input: z.infer, + ) { + return this.auction.buy(actor, input); + } + + @Post('list') + @HttpCode(200) + @UseFilters(auctionMutationErrors('list')) + list( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody({ fallback: undefined }) body: unknown, + ) { + const request = AuctionListSchema.safeParse(body); + if (!request.success) + throw new HttpException( + { error: '参数错误', details: request.error.issues }, + 400, + ); + return this.auction.list(actor, request.data); + } + + @Post('list-beast') + @HttpCode(200) + @UseFilters(auctionMutationErrors('list-beast')) + listBeast( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody({ fallback: undefined }) body: unknown, + ) { + const request = AuctionBeastListSchema.safeParse(body); + if (!request.success) + throw new HttpException( + { error: '参数错误', details: request.error.issues }, + 400, + ); + return this.auction.listBeast(actor, request.data); + } + + @Delete(':id') + @UseFilters(auctionMutationErrors('cancel')) + cancel( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('id') id: string, + ) { + const parsed = z.uuid().safeParse(id); + if (!parsed.success) + throw new HttpException({ error: '货单标识无效' }, 400); + return this.auction.cancel(actor, parsed.data); + } +} diff --git a/apps/api/src/auction/auction.module.ts b/apps/api/src/auction/auction.module.ts new file mode 100644 index 00000000..31d975cc --- /dev/null +++ b/apps/api/src/auction/auction.module.ts @@ -0,0 +1,6 @@ +import { Module } from '@nestjs/common'; +import { AuctionController } from './auction.controller'; +import { AuctionService } from './auction.service'; + +@Module({ controllers: [AuctionController], providers: [AuctionService] }) +export class AuctionModule {} diff --git a/apps/api/src/auction/auction.service.ts b/apps/api/src/auction/auction.service.ts new file mode 100644 index 00000000..225a6b5f --- /dev/null +++ b/apps/api/src/auction/auction.service.ts @@ -0,0 +1,70 @@ +import { Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import * as auctionRepository from '@server/lib/repositories/auctionRepository'; +import { + buyAuctionListing, + cancelAuctionListing, + listAuctionBeast, + listAuctionItem, +} from '@server/lib/services/AuctionApplicationService'; +import { publicAuctionListing } from '@server/lib/services/AuctionService'; +import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; +import type { + AuctionBeastListRequest, + AuctionBuySchema, + AuctionListRequest, +} from '@daoyou/shared/contracts/auction'; +import type { z } from 'zod'; +import type { ListingsSchema } from './auction-input'; + +@Injectable() +export class AuctionService { + async listings(owner: string, params: z.infer) { + if (params.assetType === 'beast' || params.itemType === 'beast') + params.itemQuality = undefined; + const result = await auctionRepository.findActiveListings({ + ...params, + viewerCultivatorId: owner, + }); + const page = params.page || 1; + const limit = params.limit || 20; + const totalPages = Math.ceil(result.total / limit); + return { + listings: result.listings.map(publicAuctionListing), + pagination: { + page, + limit, + total: result.total, + totalPages, + hasMore: page < totalPages, + }, + }; + } + + async buy( + actor: ActiveCultivatorRef, + input: z.infer, + ) { + return toPlayerStateMutationResponse( + await buyAuctionListing({ actor, ...input }), + ); + } + + async list(actor: ActiveCultivatorRef, input: AuctionListRequest) { + return toPlayerStateMutationResponse( + await listAuctionItem({ actor, ...input }), + ); + } + + async listBeast(actor: ActiveCultivatorRef, input: AuctionBeastListRequest) { + return toPlayerStateMutationResponse( + await listAuctionBeast({ actor, ...input }), + ); + } + + async cancel(actor: ActiveCultivatorRef, listingId: string) { + return toPlayerStateMutationResponse( + await cancelAuctionListing({ actor, listingId }), + ); + } +} diff --git a/apps/api/src/auth/access.guard.ts b/apps/api/src/auth/access.guard.ts new file mode 100644 index 00000000..c1835b00 --- /dev/null +++ b/apps/api/src/auth/access.guard.ts @@ -0,0 +1,72 @@ +import { + HttpException, + Inject, + Injectable, + type CanActivate, + type ExecutionContext, +} from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { isAdminIdentity, isAdminUserId } from '@server/lib/auth/adminAccess'; +import { fromNodeHeaders } from 'better-auth/node'; +import type { Response } from 'express'; +import type { GameRequest } from '../http/request'; +import { ACCESS_POLICY, type AccessPolicy } from './access'; +import { SessionService } from './session.service'; + +@Injectable() +export class AccessGuard implements CanActivate { + constructor( + @Inject(Reflector) private readonly reflector: Reflector, + @Inject(SessionService) private readonly sessions: SessionService, + ) {} + + async canActivate(context: ExecutionContext): Promise { + const policy = + this.reflector.getAllAndOverride(ACCESS_POLICY, [ + context.getHandler(), + context.getClass(), + ]) ?? 'user'; + if (policy === 'public') return true; + const request = context.switchToHttp().getRequest(); + const response = context.switchToHttp().getResponse(); + const session = await this.sessions.getSession( + fromNodeHeaders(request.headers), + ); + session.headers?.forEach((value, key) => { + if (key.toLowerCase() !== 'set-cookie') response.setHeader(key, value); + }); + for (const cookie of session.headers?.getSetCookie() ?? []) { + response.append('Set-Cookie', cookie); + } + const user = session.response?.user; + if (!user) + throw new HttpException({ success: false, error: '未授权访问' }, 401); + request.gameContext.user = { + id: user.id, + email: user.email, + name: user.name, + }; + if (policy === 'admin' && !isAdminIdentity(user)) { + throw new HttpException({ success: false, error: '无管理员权限' }, 403); + } + if (policy === 'account-admin' && !isAdminUserId(user.id)) { + throw new HttpException( + { + success: false, + error: '账号管理需要在 ADMIN_USER_IDS 中配置当前管理员用户 ID', + }, + 403, + ); + } + if (policy === 'active') { + const ref = await this.sessions.getActiveCultivator(user); + if (!ref) + throw new HttpException( + { success: false, error: '当前没有活跃角色' }, + 404, + ); + request.gameContext.activeCultivatorRef = ref; + } + return true; + } +} diff --git a/apps/api/src/auth/access.ts b/apps/api/src/auth/access.ts new file mode 100644 index 00000000..907c102b --- /dev/null +++ b/apps/api/src/auth/access.ts @@ -0,0 +1,23 @@ +import { + createParamDecorator, + SetMetadata, + type ExecutionContext, +} from '@nestjs/common'; +import type { GameRequest } from '../http/request'; + +export type AccessPolicy = + 'public' | 'user' | 'active' | 'admin' | 'account-admin'; +export const ACCESS_POLICY = Symbol('access-policy'); +export const Access = (policy: AccessPolicy) => + SetMetadata(ACCESS_POLICY, policy); + +export const CurrentUser = createParamDecorator( + (_data: unknown, context: ExecutionContext) => + context.switchToHttp().getRequest().gameContext.user, +); + +export const CurrentCultivator = createParamDecorator( + (_data: unknown, context: ExecutionContext) => + context.switchToHttp().getRequest().gameContext + .activeCultivatorRef, +); diff --git a/apps/api/src/auth/auth.module.ts b/apps/api/src/auth/auth.module.ts new file mode 100644 index 00000000..6a402097 --- /dev/null +++ b/apps/api/src/auth/auth.module.ts @@ -0,0 +1,17 @@ +import { Module } from '@nestjs/common'; +import { APP_GUARD } from '@nestjs/core'; +import { AccessGuard } from './access.guard'; +import { CaptchaController } from './captcha.controller'; +import { CaptchaService } from './captcha.service'; +import { SessionService } from './session.service'; + +@Module({ + controllers: [CaptchaController], + providers: [ + CaptchaService, + SessionService, + { provide: APP_GUARD, useClass: AccessGuard }, + ], + exports: [SessionService], +}) +export class AuthModule {} diff --git a/apps/api/src/auth/captcha.controller.ts b/apps/api/src/auth/captcha.controller.ts new file mode 100644 index 00000000..25a71de4 --- /dev/null +++ b/apps/api/src/auth/captcha.controller.ts @@ -0,0 +1,24 @@ +import { Controller, Get, Header, Inject } from '@nestjs/common'; +import { FirstQuery } from '../http/first-query'; +import { Access } from './access'; +import { CaptchaService } from './captcha.service'; + +@Controller('api/captcha') +@Access('public') +export class CaptchaController { + constructor( + @Inject(CaptchaService) private readonly captcha: CaptchaService, + ) {} + + @Get('config') + @Header('Cache-Control', 'no-store') + config() { + return this.captcha.config(); + } + + @Get('challenge') + @Header('Cache-Control', 'no-store') + challenge(@FirstQuery('action') action: string = '') { + return this.captcha.challenge(action); + } +} diff --git a/apps/api/src/auth/captcha.service.ts b/apps/api/src/auth/captcha.service.ts new file mode 100644 index 00000000..b884f19c --- /dev/null +++ b/apps/api/src/auth/captcha.service.ts @@ -0,0 +1,37 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import { + createAltchaChallenge, + isAltchaAction, + isAltchaServerEnabled, +} from '@server/lib/auth/altcha'; + +@Injectable() +export class CaptchaService { + config() { + return { enabled: isAltchaServerEnabled() }; + } + + async challenge(action: string) { + if (!isAltchaAction(action)) { + throw new HttpException( + { success: false, error: '无效的人机验证场景' }, + 400, + ); + } + if (!isAltchaServerEnabled()) { + throw new HttpException( + { success: false, error: '人机验证服务未配置' }, + 503, + ); + } + try { + return await createAltchaChallenge(action); + } catch (error) { + console.error('[altcha] challenge creation failed', error); + throw new HttpException( + { success: false, error: '人机验证服务暂不可用' }, + 503, + ); + } + } +} diff --git a/apps/api/src/auth/session.service.ts b/apps/api/src/auth/session.service.ts new file mode 100644 index 00000000..22b098a4 --- /dev/null +++ b/apps/api/src/auth/session.service.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@nestjs/common'; +import { resolveActiveCultivatorRef } from '@server/lib/auth/activeCultivator'; +import { auth } from '@server/lib/auth/auth'; +import type { AuthUser } from '@server/lib/auth/types'; + +@Injectable() +export class SessionService { + getSession(headers: Headers) { + return auth.api.getSession({ headers, returnHeaders: true }); + } + + getActiveCultivator(user: AuthUser) { + return resolveActiveCultivatorRef(user); + } + + setPassword(headers: Headers, newPassword: string) { + return auth.api.setPassword({ headers, body: { newPassword } }); + } +} diff --git a/apps/api/src/beasts/beasts.controller.ts b/apps/api/src/beasts/beasts.controller.ts new file mode 100644 index 00000000..71092b75 --- /dev/null +++ b/apps/api/src/beasts/beasts.controller.ts @@ -0,0 +1,135 @@ +import { + Controller, + Get, + Header, + HttpCode, + Inject, + Param, + Post, + Put, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { BeastError } from '@server/lib/services/combat-v6/CombatV6BeastService'; +import { + BeastAllocateSchema, + BeastClaimSchema, + BeastFusionRequestSchema, + BeastLineupRequestSchema, + BeastRenameSchema, + BeastRestSchema, +} from '@daoyou/shared/contracts/combatV6Beasts'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { CombatErrors, combatErrorResponse } from '../combat/combat-errors'; +import { apiErrorFilter } from '../http/error-filter'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { BeastsService } from './beasts.service'; + +const FusionErrors = apiErrorFilter((error) => { + if (error instanceof BeastError) + return Response.json( + { success: false, error: error.message, code: 'BEAST_FUSION_REJECTED' }, + { status: 409 }, + ); + return combatErrorResponse(error); +}); + +@Controller('api/combat-v6/beasts') +@Access('active') +@UseFilters(CombatErrors) +export class BeastsController { + constructor(@Inject(BeastsService) private readonly beasts: BeastsService) {} + + @Get() + @Header('Cache-Control', 'no-store') + read(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.beasts.read(actor.cultivatorId); + } + + @Get('fusions/:requestId') + @Header('Cache-Control', 'no-store') + fusion( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('requestId', new ZodPipe(z.uuid())) requestId: string, + ) { + return this.beasts.fusion(actor.cultivatorId, requestId); + } + + @Post('fuse') + @HttpCode(200) + @UseFilters(FusionErrors) + fuse( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody( + { fallback: undefined }, + new ZodPipe(BeastFusionRequestSchema, 'legacy-unhandled'), + ) + input: z.infer, + ) { + return this.beasts.fuse(actor.cultivatorId, input); + } + + @Post('claim') + @HttpCode(200) + claim( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(BeastClaimSchema)) + input: z.infer, + ) { + return this.beasts.claim(actor.cultivatorId, input.speciesId); + } + + @Put('lineup') + lineup( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(BeastLineupRequestSchema)) + input: z.infer, + ) { + return this.beasts.lineup(actor.cultivatorId, input); + } + + @Post('rest') + @HttpCode(200) + rest( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(BeastRestSchema)) + input: z.infer, + ) { + return this.beasts.rest(actor.cultivatorId, input); + } + + @Post('allocate') + @HttpCode(200) + allocate( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(BeastAllocateSchema)) + input: z.infer, + ) { + return this.beasts.allocate(actor.cultivatorId, input); + } + + @Post('rename') + @HttpCode(200) + rename( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody( + { fallback: undefined }, + new ZodPipe(BeastRenameSchema, 'legacy-unhandled'), + ) + input: z.infer, + ) { + return this.beasts.rename(actor.cultivatorId, input); + } + + @Post('release') + @HttpCode(200) + release( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(BeastRestSchema)) + input: z.infer, + ) { + return this.beasts.release(actor.cultivatorId, input); + } +} diff --git a/apps/api/src/beasts/beasts.module.ts b/apps/api/src/beasts/beasts.module.ts new file mode 100644 index 00000000..ef6cb6cf --- /dev/null +++ b/apps/api/src/beasts/beasts.module.ts @@ -0,0 +1,6 @@ +import { Module } from '@nestjs/common'; +import { BeastsController } from './beasts.controller'; +import { BeastsService } from './beasts.service'; + +@Module({ controllers: [BeastsController], providers: [BeastsService] }) +export class BeastsModule {} diff --git a/apps/api/src/beasts/beasts.service.ts b/apps/api/src/beasts/beasts.service.ts new file mode 100644 index 00000000..5237c3d7 --- /dev/null +++ b/apps/api/src/beasts/beasts.service.ts @@ -0,0 +1,74 @@ +import { Injectable } from '@nestjs/common'; +import { db } from '@server/lib/drizzle/db'; +import { readBeastRoster } from '@server/lib/repositories/combatV6BeastRepository'; +import { + allocateBeastPoints, + claimStarterBeast, + fuseOwnedBeasts, + readBeastFusion, + releaseBeast, + renameBeast, + restBeast, + updateBeastLineup, +} from '@server/lib/services/combat-v6/CombatV6BeastService'; +import type { + BeastAllocateSchema, + BeastFusionRequest, + BeastLineupRequestSchema, + BeastRenameSchema, + BeastRestSchema, +} from '@daoyou/shared/contracts/combatV6Beasts'; +import type { z } from 'zod'; + +@Injectable() +export class BeastsService { + async read(owner: string) { + return { success: true, data: await readBeastRoster(owner, db) }; + } + async fusion(owner: string, requestId: string) { + return { success: true, data: await readBeastFusion(owner, requestId) }; + } + async fuse(owner: string, input: BeastFusionRequest) { + return { success: true, data: await fuseOwnedBeasts(owner, input) }; + } + async claim(owner: string, speciesId: string) { + return { success: true, data: await claimStarterBeast(owner, speciesId) }; + } + async lineup(owner: string, input: z.infer) { + return { success: true, data: await updateBeastLineup(owner, input) }; + } + async rest(owner: string, input: z.infer) { + return { + success: true, + data: await restBeast(owner, input.beastId, input.expectedRevision), + }; + } + async allocate(owner: string, input: z.infer) { + return { + success: true, + data: await allocateBeastPoints( + owner, + input.beastId, + input.expectedRevision, + input.points, + ), + }; + } + async rename(owner: string, input: z.infer) { + return { + success: true, + data: await renameBeast( + owner, + input.beastId, + input.expectedRevision, + input.name, + ), + }; + } + async release(owner: string, input: z.infer) { + return { + success: true, + data: await releaseBeast(owner, input.beastId, input.expectedRevision), + }; + } +} diff --git a/apps/api/src/black-market/black-market-input.ts b/apps/api/src/black-market/black-market-input.ts new file mode 100644 index 00000000..b470d809 --- /dev/null +++ b/apps/api/src/black-market/black-market-input.ts @@ -0,0 +1,24 @@ +import { BLACK_MARKET_NPC_IDS } from '@daoyou/shared/types/blackMarket'; +import { z } from 'zod'; + +export const OpenSessionSchema = z.object({ + npcId: z.enum(BLACK_MARKET_NPC_IDS), +}); +export const InteractSchema = z + .object({ + message: z.string().trim().min(1).max(240).optional(), + offeredPrice: z.number().int().min(1).max(2_000_000_000).optional(), + version: z.number().int().min(1), + }) + .superRefine((value, context) => { + if (!value.message && !value.offeredPrice) + context.addIssue({ + code: 'custom', + message: '请说点什么,或给出灵石报价', + }); + }); +export const LeaveSchema = z.object({ version: z.number().int().min(1) }); +export const CommitSchema = z.object({ + version: z.number().int().min(1), + expectedPrice: z.number().int().min(1).max(2_000_000_000), +}); diff --git a/apps/api/src/black-market/black-market.controller.ts b/apps/api/src/black-market/black-market.controller.ts new file mode 100644 index 00000000..f6809e92 --- /dev/null +++ b/apps/api/src/black-market/black-market.controller.ts @@ -0,0 +1,136 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Param, + Post, + Res, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import { BlackMarketServiceError } from '@server/lib/services/black-market/BlackMarketService'; +import { + QiInsufficientError, + QiServiceError, +} from '@server/lib/services/QiService'; +import type { Response as ExpressResponse } from 'express'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { JsonBody } from '../http/json-body'; +import { withRequestAbort } from '../http/request-abort'; +import { streamSseEvents } from '../http/sse'; +import { ZodPipe } from '../http/zod.pipe'; +import { + CommitSchema, + InteractSchema, + LeaveSchema, + OpenSessionSchema, +} from './black-market-input'; +import { BlackMarketService } from './black-market.service'; + +const BlackMarketErrors = apiErrorFilter((error) => { + const lock = redisLockErrorResponse(error); + if (lock) return lock; + if (error instanceof z.ZodError) + return Response.json( + { error: error.issues[0]?.message || '参数错误' }, + { status: 400 }, + ); + if (error instanceof BlackMarketServiceError) + return Response.json({ error: error.message }, { status: error.status }); + if (error instanceof QiInsufficientError) + return Response.json( + { + error: error.code, + message: error.message, + required: error.required, + current: error.current, + action: error.action, + }, + { status: 409 }, + ); + if (error instanceof QiServiceError) + return Response.json({ error: error.message }, { status: error.status }); + console.error('black market api error:', error); + return Response.json({ error: '黑市暂时闭门,请稍后再来' }, { status: 500 }); +}); + +@Controller('api/black-market') +@Access('active') +@UseFilters(BlackMarketErrors) +export class BlackMarketController { + constructor( + @Inject(BlackMarketService) private readonly market: BlackMarketService, + ) {} + + @Get(':nodeId') + read( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('nodeId') nodeId: string, + ) { + return this.market.read(actor, nodeId); + } + + @Post(':nodeId/sessions') + @HttpCode(200) + open( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('nodeId') nodeId: string, + @JsonBody(new ZodPipe(OpenSessionSchema)) + input: z.infer, + ) { + return this.market.open(actor, nodeId, input); + } + + @Post(':nodeId/sessions/:sessionId/interact') + @HttpCode(200) + interact( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('nodeId') nodeId: string, + @Param('sessionId') sessionId: string, + @JsonBody(new ZodPipe(InteractSchema)) + input: z.infer, + @Res() response: ExpressResponse, + ) { + return withRequestAbort(response, async (signal) => { + const prepared = await this.market.prepare( + actor, + nodeId, + sessionId, + input, + signal, + ); + signal.throwIfAborted(); + await streamSseEvents(response, (stream, _isAborted, replySignal) => + this.market.reply(prepared, replySignal, (event) => + stream.writeSSE({ data: JSON.stringify(event) }), + ), + ); + }); + } + + @Post(':nodeId/sessions/:sessionId/commit') + @HttpCode(200) + commit( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('nodeId') nodeId: string, + @Param('sessionId') sessionId: string, + @JsonBody(new ZodPipe(CommitSchema)) input: z.infer, + ) { + return this.market.commit(actor, nodeId, sessionId, input); + } + + @Post(':nodeId/sessions/:sessionId/leave') + @HttpCode(200) + leave( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('nodeId') nodeId: string, + @Param('sessionId') sessionId: string, + @JsonBody(new ZodPipe(LeaveSchema)) input: z.infer, + ) { + return this.market.leave(actor, nodeId, sessionId, input); + } +} diff --git a/apps/api/src/black-market/black-market.module.ts b/apps/api/src/black-market/black-market.module.ts new file mode 100644 index 00000000..2c5db88c --- /dev/null +++ b/apps/api/src/black-market/black-market.module.ts @@ -0,0 +1,19 @@ +import { Module } from '@nestjs/common'; +import { + BlackMarketConversationService, + blackMarketConversationService, +} from '@server/lib/services/black-market/BlackMarketConversationService'; +import { BlackMarketController } from './black-market.controller'; +import { BlackMarketService } from './black-market.service'; + +@Module({ + controllers: [BlackMarketController], + providers: [ + { + provide: BlackMarketConversationService, + useValue: blackMarketConversationService, + }, + BlackMarketService, + ], +}) +export class BlackMarketModule {} diff --git a/apps/api/src/black-market/black-market.service.ts b/apps/api/src/black-market/black-market.service.ts new file mode 100644 index 00000000..277f5c0f --- /dev/null +++ b/apps/api/src/black-market/black-market.service.ts @@ -0,0 +1,131 @@ +import { Inject, Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { BlackMarketConversationService } from '@server/lib/services/black-market/BlackMarketConversationService'; +import { + commitBlackMarketPurchase, + completeBlackMarketReply, + getBlackMarketOverview, + leaveBlackMarketSession, + openBlackMarketSession, + prepareBlackMarketInteraction, +} from '@server/lib/services/black-market/BlackMarketService'; +import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; +import type { BlackMarketInteractStreamEvent } from '@daoyou/shared/types/blackMarket'; +import type { z } from 'zod'; +import type { + CommitSchema, + InteractSchema, + LeaveSchema, + OpenSessionSchema, +} from './black-market-input'; + +@Injectable() +export class BlackMarketService { + constructor( + @Inject(BlackMarketConversationService) + private readonly conversation: BlackMarketConversationService, + ) {} + read(actor: ActiveCultivatorRef, nodeId: string) { + return getBlackMarketOverview({ actor, nodeId }); + } + + async open( + actor: ActiveCultivatorRef, + nodeId: string, + input: z.infer, + ) { + return toPlayerStateMutationResponse( + await openBlackMarketSession({ actor, nodeId, ...input }), + ); + } + + prepare( + actor: ActiveCultivatorRef, + nodeId: string, + sessionId: string, + command: z.infer, + abortSignal: AbortSignal, + ) { + return prepareBlackMarketInteraction({ + actor, + nodeId, + sessionId, + command, + abortSignal, + }); + } + + async reply( + prepared: Awaited>, + signal: AbortSignal, + emit: (event: BlackMarketInteractStreamEvent) => Promise, + ) { + await emit({ + type: 'resolved', + result: prepared.result, + messageId: prepared.messageId, + gesture: prepared.gesture, + fallbackBody: prepared.fallbackBody, + }); + if (signal.aborted) return; + let body = ''; + try { + const reply = this.conversation.streamTurnReply({ + context: prepared.replyContext, + proposal: prepared.proposal, + negotiationOutcome: prepared.negotiationOutcome, + abortSignal: signal, + }); + for await (const chunk of reply.textStream) { + signal.throwIfAborted(); + body += chunk; + await emit({ + type: 'reply-chunk', + messageId: prepared.messageId, + text: chunk, + }); + } + body = body.trim(); + if (!body) throw new Error('empty black market reply'); + signal.throwIfAborted(); + await completeBlackMarketReply({ + sessionId: prepared.sessionId, + messageId: prepared.messageId, + body, + }); + await emit({ + type: 'reply-complete', + messageId: prepared.messageId, + body, + }); + } catch (error) { + console.warn('[black-market] reply stream fallback', { error }); + if (!signal.aborted) + await emit({ + type: 'reply-error', + messageId: prepared.messageId, + fallbackBody: prepared.fallbackBody, + }); + } + } + + async commit( + actor: ActiveCultivatorRef, + nodeId: string, + sessionId: string, + input: z.infer, + ) { + return toPlayerStateMutationResponse( + await commitBlackMarketPurchase({ actor, nodeId, sessionId, ...input }), + ); + } + + leave( + actor: ActiveCultivatorRef, + nodeId: string, + sessionId: string, + input: z.infer, + ) { + return leaveBlackMarketSession({ actor, nodeId, sessionId, ...input }); + } +} diff --git a/apps/api/src/combat/auto-errors.ts b/apps/api/src/combat/auto-errors.ts new file mode 100644 index 00000000..20a51ba4 --- /dev/null +++ b/apps/api/src/combat/auto-errors.ts @@ -0,0 +1,11 @@ +import { apiErrorFilter } from '../http/error-filter'; +import { JsonBodyParseError } from '../http/json-body'; + +// Auto commands parse inputs before their route-local service catch block. +export const AutoErrors = apiErrorFilter((error) => { + if (error instanceof JsonBodyParseError) return undefined; + return Response.json( + { error: error instanceof Error ? error.message : '自动指令提交失败' }, + { status: 409 }, + ); +}); diff --git a/apps/api/src/combat/auto-strategy.controller.ts b/apps/api/src/combat/auto-strategy.controller.ts new file mode 100644 index 00000000..823748e8 --- /dev/null +++ b/apps/api/src/combat/auto-strategy.controller.ts @@ -0,0 +1,82 @@ +import { + Controller, + Delete, + Get, + Header, + Inject, + Put, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { AutoStrategyError } from '@server/lib/services/combat-v6/CombatV6AutoStrategyService'; +import { CombatV6BuildError } from '@server/lib/services/combat-v6/CombatV6BuildService'; +import { SaveAutoStrategySchema } from '@daoyou/shared/combat-v6/auto-strategy'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { AutoStrategyService } from './auto-strategy.service'; + +const MutationSchema = z.strictObject({ + pathId: z.string().min(1).max(160), + strategy: SaveAutoStrategySchema, +}); +const ResetSchema = z.strictObject({ pathId: z.string().min(1).max(160) }); +const StrategyErrors = apiErrorFilter((error) => { + if (error instanceof AutoStrategyError || error instanceof CombatV6BuildError) + return Response.json( + { success: false, error: error.message }, + { status: 409 }, + ); + if (error instanceof z.ZodError) + return Response.json( + { success: false, error: '策略格式无效' }, + { status: 400 }, + ); + console.error('[combat-auto-strategy]', error); + return Response.json( + { success: false, error: '策略操作失败,请稍后重试' }, + { status: 500 }, + ); +}); + +@Controller('api/combat-v6/auto-strategy') +@Access('active') +@UseFilters(StrategyErrors) +export class AutoStrategyController { + constructor( + @Inject(AutoStrategyService) + private readonly strategies: AutoStrategyService, + ) {} + + @Get() + @Header('Cache-Control', 'no-store') + read(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.strategies.read(actor.cultivatorId); + } + + @Put() + @Header('Cache-Control', 'no-store') + save( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody({ fallback: undefined }, new ZodPipe(MutationSchema)) + input: z.infer, + ) { + return this.strategies.save( + actor.cultivatorId, + input.pathId, + input.strategy, + ); + } + + @Delete() + @Header('Cache-Control', 'no-store') + reset( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody({ fallback: undefined }, new ZodPipe(ResetSchema)) + input: z.infer, + ) { + return this.strategies.reset(actor.cultivatorId, input.pathId); + } +} diff --git a/apps/api/src/combat/auto-strategy.service.ts b/apps/api/src/combat/auto-strategy.service.ts new file mode 100644 index 00000000..f1e2ba9b --- /dev/null +++ b/apps/api/src/combat/auto-strategy.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@nestjs/common'; +import { + readCombatAutoStrategy, + resetCombatAutoStrategy, + saveCombatAutoStrategy, +} from '@server/lib/services/combat-v6/CombatV6AutoStrategyService'; +import type { AutoStrategy } from '@daoyou/shared/combat-v6/auto-strategy'; + +@Injectable() +export class AutoStrategyService { + async read(owner: string) { + return { success: true, data: await readCombatAutoStrategy(owner) }; + } + async save(owner: string, pathId: string, strategy: AutoStrategy) { + return { + success: true, + data: await saveCombatAutoStrategy(owner, pathId, strategy), + }; + } + async reset(owner: string, pathId: string) { + return { + success: true, + data: await resetCombatAutoStrategy(owner, pathId), + }; + } +} diff --git a/apps/api/src/combat/breakthrough.controller.ts b/apps/api/src/combat/breakthrough.controller.ts new file mode 100644 index 00000000..741c6809 --- /dev/null +++ b/apps/api/src/combat/breakthrough.controller.ts @@ -0,0 +1,104 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Param, + Post, + Put, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { CombatAutoRequestSchema } from '@daoyou/shared/combat-v6/auto'; +import { + CombatV6TrainingCommandRequestSchema, + CombatV6TrainingEventsQuerySchema, + CombatV6TrainingRevisionRequestSchema, +} from '@daoyou/shared/contracts/combatV6'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { BreakthroughService } from './breakthrough.service'; +import { taskBattleErrors } from './task-battle-errors'; + +@Controller('api/combat-v6/breakthrough/tasks/:taskId/sessions') +@Access('active') +@UseFilters(taskBattleErrors('突破战斗参数无效')) +export class BreakthroughController { + constructor( + @Inject(BreakthroughService) + private readonly breakthrough: BreakthroughService, + ) {} + + @Get('current') + current( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('taskId', new ZodPipe(z.uuid())) taskId: string, + ) { + return this.breakthrough.read(actor.cultivatorId, taskId); + } + + @Get(':id') + read( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('taskId', new ZodPipe(z.uuid())) taskId: string, + @Param('id', new ZodPipe(z.uuid())) id: string, + @FirstQuery(new ZodPipe(CombatV6TrainingEventsQuerySchema)) + query: z.infer, + ) { + return this.breakthrough.read( + actor.cultivatorId, + taskId, + id, + query.afterEventSeq, + ); + } + + @Put(':id/commands/:unitId') + submit( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('taskId', new ZodPipe(z.uuid())) taskId: string, + @Param('id', new ZodPipe(z.uuid())) id: string, + @Param('unitId') unitId: string, + @JsonBody(new ZodPipe(CombatV6TrainingCommandRequestSchema)) + input: z.infer, + ) { + return this.breakthrough.change(actor, taskId, id, input.expectedRevision, { + unitId, + commands: input.commands, + }); + } + + @Post(':id/resolve') + @HttpCode(200) + resolve( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('taskId', new ZodPipe(z.uuid())) taskId: string, + @Param('id', new ZodPipe(z.uuid())) id: string, + @JsonBody(new ZodPipe(CombatV6TrainingRevisionRequestSchema)) + input: z.infer, + ) { + return this.breakthrough.change(actor, taskId, id, input.expectedRevision); + } + + @Post(':id/auto') + @HttpCode(200) + auto( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('taskId', new ZodPipe(z.uuid())) taskId: string, + @Param('id', new ZodPipe(z.uuid())) id: string, + @JsonBody(new ZodPipe(CombatAutoRequestSchema)) + input: z.infer, + ) { + return this.breakthrough.change( + actor, + taskId, + id, + input.expectedRevision, + undefined, + input.round, + ); + } +} diff --git a/apps/api/src/combat/breakthrough.service.ts b/apps/api/src/combat/breakthrough.service.ts new file mode 100644 index 00000000..031bf4f1 --- /dev/null +++ b/apps/api/src/combat/breakthrough.service.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { + changeBreakthroughBattle, + getBreakthroughBattle, +} from '@server/lib/services/combat-v6/CombatV6BreakthroughService'; + +@Injectable() +export class BreakthroughService { + async read(owner: string, taskId: string, id?: string, after?: number) { + return { + success: true, + data: await getBreakthroughBattle(owner, taskId, id, after), + }; + } + async change( + actor: ActiveCultivatorRef, + taskId: string, + id: string, + revision: number, + command?: Parameters[4], + round?: number, + ) { + return { + success: true, + data: await changeBreakthroughBattle( + actor, + taskId, + id, + revision, + command, + round, + ), + }; + } +} diff --git a/apps/api/src/combat/combat-activity.controller.ts b/apps/api/src/combat/combat-activity.controller.ts new file mode 100644 index 00000000..e47c39fb --- /dev/null +++ b/apps/api/src/combat/combat-activity.controller.ts @@ -0,0 +1,18 @@ +import { Controller, Get, Inject } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { Access, CurrentCultivator } from '../auth/access'; +import { CombatActivityService } from './combat-activity.service'; + +@Controller('api/combat-v6/activity') +@Access('active') +export class CombatActivityController { + constructor( + @Inject(CombatActivityService) + private readonly activity: CombatActivityService, + ) {} + + @Get() + read(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.activity.read(actor.cultivatorId); + } +} diff --git a/apps/api/src/combat/combat-activity.service.ts b/apps/api/src/combat/combat-activity.service.ts new file mode 100644 index 00000000..fb5891d5 --- /dev/null +++ b/apps/api/src/combat/combat-activity.service.ts @@ -0,0 +1,12 @@ +import { Injectable } from '@nestjs/common'; +import { readCombatActivityNotice } from '@server/lib/services/combat-v6/CombatActivityNotice'; + +@Injectable() +export class CombatActivityService { + async read(cultivatorId: string) { + return { + success: true, + data: await readCombatActivityNotice(cultivatorId), + }; + } +} diff --git a/apps/api/src/combat/combat-errors.ts b/apps/api/src/combat/combat-errors.ts new file mode 100644 index 00000000..76023367 --- /dev/null +++ b/apps/api/src/combat/combat-errors.ts @@ -0,0 +1,86 @@ +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import { PlayerCommandIdempotencyError } from '@server/lib/services/CommandExecutors'; +import { InventoryError } from '@server/lib/services/InventoryService'; +import { + QiInsufficientError, + QiServiceError, +} from '@server/lib/services/QiService'; +import { BeastError } from '@server/lib/services/combat-v6/CombatV6BeastService'; +import { CombatV6BuildError } from '@server/lib/services/combat-v6/CombatV6BuildService'; +import { CombatV6TrainingSessionError } from '@server/lib/services/combat-v6/CombatV6TrainingSessionService'; +import { WildError } from '@server/lib/services/combat-v6/CombatV6WildSessionService'; +import { TrainingHostError } from '@daoyou/shared/engine/combat-v6/encounter'; +import { InventoryRuleError } from '@daoyou/shared/inventory'; +import { ZodError } from 'zod'; +import { apiErrorFilter } from '../http/error-filter'; + +export function combatErrorResponse(error: unknown): Response { + if (error instanceof QiInsufficientError) + return Response.json( + { + success: false, + code: error.code, + error: '天地灵气不足,待自然恢复或使用恢复符箓后再试。', + }, + { status: 409 }, + ); + if ( + error instanceof QiServiceError || + error instanceof PlayerCommandIdempotencyError + ) + return Response.json( + { success: false, error: error.message }, + { status: error.status }, + ); + if (error instanceof InventoryError || error instanceof InventoryRuleError) + return Response.json( + { success: false, error: error.message }, + { status: 409 }, + ); + if (error instanceof BeastError) + return Response.json( + { success: false, error: error.message }, + { status: error.status }, + ); + const coordination = redisLockErrorResponse(error); + if (coordination) return coordination; + if (error instanceof TrainingHostError) + return Response.json( + { + success: false, + code: 'WILD_COMMAND_NOT_ALLOWED', + error: error.message, + }, + { status: 400 }, + ); + if (error instanceof ZodError) + return Response.json( + { + success: false, + code: 'INVALID_REQUEST', + error: error.issues[0]?.message ?? '参数错误', + details: error.issues, + }, + { status: 400 }, + ); + if ( + error instanceof CombatV6BuildError || + error instanceof WildError || + error instanceof CombatV6TrainingSessionError + ) + return Response.json( + { success: false, code: error.code, error: error.message }, + { status: error.status }, + ); + console.error('combat-v6 api error:', error); + return Response.json( + { + success: false, + code: 'COMBAT_V6_INTERNAL_ERROR', + error: '练功房暂不可用,请稍后再试', + }, + { status: 500 }, + ); +} + +export const CombatErrors = apiErrorFilter(combatErrorResponse); diff --git a/apps/api/src/combat/combat.module.ts b/apps/api/src/combat/combat.module.ts new file mode 100644 index 00000000..16303efd --- /dev/null +++ b/apps/api/src/combat/combat.module.ts @@ -0,0 +1,41 @@ +import { Module } from '@nestjs/common'; +import { AutoStrategyController } from './auto-strategy.controller'; +import { AutoStrategyService } from './auto-strategy.service'; +import { BreakthroughController } from './breakthrough.controller'; +import { BreakthroughService } from './breakthrough.service'; +import { CombatActivityController } from './combat-activity.controller'; +import { CombatActivityService } from './combat-activity.service'; +import { + ReplaysController, + SharedReplaysController, +} from './replays.controller'; +import { ReplaysService } from './replays.service'; +import { SectTaskBattleController } from './sect-task.controller'; +import { SectTaskBattleService } from './sect-task.service'; +import { TrainingController } from './training.controller'; +import { TrainingService } from './training.service'; +import { WildController } from './wild.controller'; +import { WildService } from './wild.service'; + +@Module({ + controllers: [ + CombatActivityController, + ReplaysController, + SharedReplaysController, + TrainingController, + WildController, + AutoStrategyController, + BreakthroughController, + SectTaskBattleController, + ], + providers: [ + CombatActivityService, + ReplaysService, + TrainingService, + WildService, + AutoStrategyService, + BreakthroughService, + SectTaskBattleService, + ], +}) +export class CombatModule {} diff --git a/apps/api/src/combat/replays.controller.ts b/apps/api/src/combat/replays.controller.ts new file mode 100644 index 00000000..ccd3b76f --- /dev/null +++ b/apps/api/src/combat/replays.controller.ts @@ -0,0 +1,93 @@ +import { + Controller, + Get, + Header, + HttpCode, + HttpException, + Inject, + Param, + Post, + Res, + UseFilters, + type PipeTransform, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { CombatV6ReplayParamsSchema } from '@daoyou/shared/contracts/combatV6'; +import { CombatV6HistoryQuerySchema } from '@daoyou/shared/contracts/combatV6Replay'; +import type { Response } from 'express'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { FirstQuery } from '../http/first-query'; +import { ZodPipe } from '../http/zod.pipe'; +import { CombatErrors } from './combat-errors'; +import { ReplaysService } from './replays.service'; + +@Controller('api/combat-v6/replays') +@Access('active') +export class ReplaysController { + constructor( + @Inject(ReplaysService) private readonly replays: ReplaysService, + ) {} + + @Get() + @Header('Cache-Control', 'private, no-store') + @UseFilters(CombatErrors) + list( + @CurrentCultivator() actor: ActiveCultivatorRef, + @FirstQuery(new ZodPipe(CombatV6HistoryQuerySchema)) + query: z.infer, + ) { + return this.replays.list(actor.cultivatorId, query); + } + + @Get(':battleId') + @Header('Cache-Control', 'private, no-store') + @UseFilters(CombatErrors) + async read( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param(new ZodPipe(CombatV6ReplayParamsSchema)) + params: z.infer, + @Res({ passthrough: true }) response: Response, + ) { + const result = await this.replays.read(actor, params.battleId); + response.status(result.status); + return result.body; + } + + @Post(':battleId/share') + @HttpCode(200) + share( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param(new ZodPipe(CombatV6ReplayParamsSchema)) + params: z.infer, + ) { + return this.replays.share(actor, params.battleId); + } +} + +class ShareCodePipe implements PipeTransform { + transform(value: string) { + const parsed = z.uuid().safeParse(value); + if (!parsed.success) + throw new HttpException({ success: false, error: '分享链接无效' }, 404); + return parsed.data; + } +} + +@Controller('api/combat-v6-shares') +@Access('public') +export class SharedReplaysController { + constructor( + @Inject(ReplaysService) private readonly replays: ReplaysService, + ) {} + + @Get(':shareCode') + async read( + @Param('shareCode', new ShareCodePipe()) shareCode: string, + @Res({ passthrough: true }) response: Response, + ) { + const result = await this.replays.shared(shareCode); + response.setHeader('Cache-Control', 'public, max-age=60'); + return result; + } +} diff --git a/apps/api/src/combat/replays.service.ts b/apps/api/src/combat/replays.service.ts new file mode 100644 index 00000000..8eb647bf --- /dev/null +++ b/apps/api/src/combat/replays.service.ts @@ -0,0 +1,101 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { + createCombatV6ReplayShare, + findOwnedCombatV6Replay, + findSharedCombatV6Replay, + listOwnedCombatV6Replays, +} from '@server/lib/repositories/combatV6ReplayRepository'; +import { CombatV6ArenaStore } from '@server/lib/services/combat-v6/CombatV6ArenaStore'; +import { combatV6ReplayView } from '@daoyou/shared/combat-v6/replay'; +import { COMBAT_V6_REPLAY_ERROR_CODE } from '@daoyou/shared/contracts/combatV6'; +import type { CombatV6HistoryQuerySchema } from '@daoyou/shared/contracts/combatV6Replay'; +import type { z } from 'zod'; + +@Injectable() +export class ReplaysService { + private readonly arena = new CombatV6ArenaStore(); + + async read(actor: ActiveCultivatorRef, battleId: string) { + const archive = await findOwnedCombatV6Replay(battleId, actor.cultivatorId); + if ( + archive?.replay && + archive.replay.participants.some( + (p) => + p.cultivatorId === actor.cultivatorId && p.userId === actor.userId, + ) + ) { + return { + status: 200, + body: { + success: true, + data: combatV6ReplayView( + archive.replay, + actor.cultivatorId, + actor.userId, + ), + }, + }; + } + const arena = await this.arena.get(battleId); + if ( + arena?.stage === 'finished' && + arena.participants.some( + (p) => + p.userId === actor.userId && p.cultivatorId === actor.cultivatorId, + ) + ) { + return { + status: 202, + body: { + success: false, + code: COMBAT_V6_REPLAY_ERROR_CODE.Pending, + error: '战斗回放正在归档,请稍后重试', + }, + }; + } + return { + status: 404, + body: { + success: false, + code: COMBAT_V6_REPLAY_ERROR_CODE.NotFound, + error: '战斗回放不存在', + }, + }; + } + + async list(owner: string, query: z.infer) { + return { + success: true, + data: await listOwnedCombatV6Replays(owner, query), + }; + } + + async share(actor: ActiveCultivatorRef, battleId: string) { + const shareCode = await createCombatV6ReplayShare( + battleId, + actor.cultivatorId, + actor.userId, + ); + if (!shareCode) + throw new HttpException({ success: false, error: '战斗回放不存在' }, 404); + return { success: true, data: { shareCode } }; + } + + async shared(shareCode: string) { + const archive = await findSharedCombatV6Replay(shareCode); + const viewer = archive?.replay?.participants.find( + (p) => p.cultivatorId === archive.shareViewerCultivatorId, + ); + if (!archive?.replay || !viewer) + throw new HttpException({ success: false, error: '战斗回放不存在' }, 404); + return { + success: true, + data: combatV6ReplayView( + archive.replay, + viewer.cultivatorId, + viewer.userId, + ), + }; + } +} diff --git a/apps/api/src/combat/sect-task.controller.ts b/apps/api/src/combat/sect-task.controller.ts new file mode 100644 index 00000000..dd1d81b5 --- /dev/null +++ b/apps/api/src/combat/sect-task.controller.ts @@ -0,0 +1,91 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Param, + Post, + Put, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { CombatAutoRequestSchema } from '@daoyou/shared/combat-v6/auto'; +import { + CombatV6TrainingCommandRequestSchema, + CombatV6TrainingEventsQuerySchema, + CombatV6TrainingRevisionRequestSchema, +} from '@daoyou/shared/contracts/combatV6'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { SectTaskBattleService } from './sect-task.service'; +import { taskBattleErrors } from './task-battle-errors'; + +@Controller('api/combat-v6/sect-tasks/sessions') +@Access('active') +@UseFilters(taskBattleErrors('宗门战斗参数无效')) +export class SectTaskBattleController { + constructor( + @Inject(SectTaskBattleService) + private readonly battle: SectTaskBattleService, + ) {} + + @Get('current') + current(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.battle.read(actor.cultivatorId); + } + + @Get(':id') + read( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('id', new ZodPipe(z.uuid())) id: string, + @FirstQuery(new ZodPipe(CombatV6TrainingEventsQuerySchema)) + query: z.infer, + ) { + return this.battle.read(actor.cultivatorId, id, query.afterEventSeq); + } + + @Put(':id/commands/:unitId') + submit( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('id', new ZodPipe(z.uuid())) id: string, + @Param('unitId') unitId: string, + @JsonBody(new ZodPipe(CombatV6TrainingCommandRequestSchema)) + input: z.infer, + ) { + return this.battle.change(actor, id, input.expectedRevision, { + unitId, + commands: input.commands, + }); + } + + @Post(':id/resolve') + @HttpCode(200) + resolve( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('id', new ZodPipe(z.uuid())) id: string, + @JsonBody(new ZodPipe(CombatV6TrainingRevisionRequestSchema)) + input: z.infer, + ) { + return this.battle.change(actor, id, input.expectedRevision); + } + + @Post(':id/auto') + @HttpCode(200) + auto( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('id', new ZodPipe(z.uuid())) id: string, + @JsonBody(new ZodPipe(CombatAutoRequestSchema)) + input: z.infer, + ) { + return this.battle.change( + actor, + id, + input.expectedRevision, + undefined, + input.round, + ); + } +} diff --git a/apps/api/src/combat/sect-task.service.ts b/apps/api/src/combat/sect-task.service.ts new file mode 100644 index 00000000..330d2967 --- /dev/null +++ b/apps/api/src/combat/sect-task.service.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { + changeSectTaskBattle, + getSectTaskBattle, +} from '@server/lib/services/combat-v6/CombatV6SectTaskService'; + +@Injectable() +export class SectTaskBattleService { + async read(owner: string, id?: string, after?: number) { + return { success: true, data: await getSectTaskBattle(owner, id, after) }; + } + async change( + actor: ActiveCultivatorRef, + id: string, + revision: number, + command?: Parameters[3], + round?: number, + ) { + return { + success: true, + data: await changeSectTaskBattle(actor, id, revision, command, round), + }; + } +} diff --git a/apps/api/src/combat/task-battle-errors.ts b/apps/api/src/combat/task-battle-errors.ts new file mode 100644 index 00000000..05a2684d --- /dev/null +++ b/apps/api/src/combat/task-battle-errors.ts @@ -0,0 +1,21 @@ +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import { ZodError } from 'zod'; +import { apiErrorFilter } from '../http/error-filter'; + +export function taskBattleErrors(invalidMessage: string) { + return apiErrorFilter( + (error) => + redisLockErrorResponse(error) ?? + Response.json( + { + error: + error instanceof ZodError + ? invalidMessage + : error instanceof Error + ? error.message + : String(error), + }, + { status: error instanceof ZodError ? 400 : 409 }, + ), + ); +} diff --git a/apps/api/src/combat/training.controller.ts b/apps/api/src/combat/training.controller.ts new file mode 100644 index 00000000..47ab4562 --- /dev/null +++ b/apps/api/src/combat/training.controller.ts @@ -0,0 +1,163 @@ +import { getRuntimeEnvironment } from '@server/lib/config/environment'; +import { + Controller, + Delete, + Get, + HttpCode, + HttpException, + Inject, + Param, + Post, + Put, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { CombatAutoRequestSchema } from '@daoyou/shared/combat-v6/auto'; +import { + CombatV6TrainingCommandParamsSchema, + CombatV6TrainingCommandRequestSchema, + CombatV6TrainingCreateRequestSchema, + CombatV6TrainingEventsQuerySchema, + CombatV6TrainingRevisionRequestSchema, + CombatV6TrainingSessionParamsSchema, +} from '@daoyou/shared/contracts/combatV6'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { AutoErrors } from './auto-errors'; +import { CombatErrors } from './combat-errors'; +import { TrainingService } from './training.service'; + +class TraceParamsPipe extends ZodPipe< + z.infer +> { + constructor() { + super(CombatV6TrainingSessionParamsSchema); + } + override transform(value: unknown) { + if (getRuntimeEnvironment().NODE_ENV === 'production') + throw new HttpException( + { + success: false, + code: 'TRAINING_SESSION_NOT_FOUND', + error: '训练会话不存在', + }, + 404, + ); + return super.transform(value); + } +} + +@Controller('api/combat-v6/training') +@Access('active') +@UseFilters(CombatErrors) +export class TrainingController { + constructor( + @Inject(TrainingService) private readonly training: TrainingService, + ) {} + + @Get('content') + content() { + return this.training.content(); + } + + @Get('sessions/current') + current( + @CurrentCultivator() actor: ActiveCultivatorRef, + @FirstQuery(new ZodPipe(CombatV6TrainingEventsQuerySchema)) + query: z.infer, + ) { + return this.training.current(actor, query.afterEventSeq); + } + + @Post('sessions') + @HttpCode(200) + create( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(CombatV6TrainingCreateRequestSchema)) + input: z.infer, + ) { + return this.training.create(actor, input); + } + + @Get('sessions/:sessionId') + read( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param(new ZodPipe(CombatV6TrainingSessionParamsSchema)) + params: z.infer, + @FirstQuery(new ZodPipe(CombatV6TrainingEventsQuerySchema)) + query: z.infer, + ) { + return this.training.read(actor, params.sessionId, query.afterEventSeq); + } + + @Put('sessions/:sessionId/commands/:unitId') + submit( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param(new ZodPipe(CombatV6TrainingCommandParamsSchema)) + params: z.infer, + @JsonBody(new ZodPipe(CombatV6TrainingCommandRequestSchema)) + input: z.infer, + ) { + return this.training.submit(actor, params.sessionId, params.unitId, input); + } + + @Post('sessions/:sessionId/resolve') + @HttpCode(200) + resolve( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param(new ZodPipe(CombatV6TrainingSessionParamsSchema)) + params: z.infer, + @JsonBody(new ZodPipe(CombatV6TrainingRevisionRequestSchema)) + input: z.infer, + ) { + return this.training.resolve( + actor, + params.sessionId, + input.expectedRevision, + ); + } + + @Post('sessions/:id/auto') + @HttpCode(200) + @UseFilters(AutoErrors) + auto( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('id', new ZodPipe(z.uuid(), 'legacy-unhandled')) id: string, + @JsonBody(new ZodPipe(CombatAutoRequestSchema, 'legacy-unhandled')) + input: z.infer, + ) { + return this.training.resolve( + actor, + id, + input.expectedRevision, + input.round, + ); + } + + @Delete('sessions/:sessionId') + abandon( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param(new ZodPipe(CombatV6TrainingSessionParamsSchema)) + params: z.infer, + @JsonBody(new ZodPipe(CombatV6TrainingRevisionRequestSchema)) + input: z.infer, + ) { + return this.training.abandon( + actor, + params.sessionId, + input.expectedRevision, + ); + } + + @Get('sessions/:sessionId/trace') + trace( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param(new TraceParamsPipe()) + params: z.infer, + ) { + return this.training.trace(actor, params.sessionId); + } +} diff --git a/apps/api/src/combat/training.service.ts b/apps/api/src/combat/training.service.ts new file mode 100644 index 00000000..301ab453 --- /dev/null +++ b/apps/api/src/combat/training.service.ts @@ -0,0 +1,88 @@ +import { Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { + COMBAT_V6_TRAINING_CONTENT_VIEW, + combatV6TrainingSessionStore, +} from '@server/lib/services/combat-v6/CombatV6TrainingSessionService'; +import type { + CombatV6TrainingCommandRequestSchema, + CombatV6TrainingCreateRequest, +} from '@daoyou/shared/contracts/combatV6'; +import type { z } from 'zod'; + +@Injectable() +export class TrainingService { + content() { + return { success: true, data: COMBAT_V6_TRAINING_CONTENT_VIEW }; + } + async current(actor: ActiveCultivatorRef, after: number) { + return { + success: true, + data: await combatV6TrainingSessionStore.current(actor, after), + }; + } + async create( + actor: ActiveCultivatorRef, + input: CombatV6TrainingCreateRequest, + ) { + return { + success: true, + data: await combatV6TrainingSessionStore.create( + actor, + input.encounterId, + input.tier, + ), + }; + } + async read(actor: ActiveCultivatorRef, id: string, after: number) { + return { + success: true, + data: await combatV6TrainingSessionStore.get(actor, id, after), + }; + } + async submit( + actor: ActiveCultivatorRef, + id: string, + unitId: string, + input: z.infer, + ) { + return { + success: true, + data: await combatV6TrainingSessionStore.submit( + actor, + id, + input.expectedRevision, + unitId, + input.commands, + ), + }; + } + async resolve( + actor: ActiveCultivatorRef, + id: string, + revision: number, + round?: number, + ) { + return { + success: true, + data: await combatV6TrainingSessionStore.resolve( + actor, + id, + revision, + round, + ), + }; + } + async abandon(actor: ActiveCultivatorRef, id: string, revision: number) { + return { + success: true, + data: await combatV6TrainingSessionStore.abandon(actor, id, revision), + }; + } + async trace(actor: ActiveCultivatorRef, id: string) { + return { + success: true, + data: await combatV6TrainingSessionStore.trace(actor, id), + }; + } +} diff --git a/apps/api/src/combat/wild.controller.ts b/apps/api/src/combat/wild.controller.ts new file mode 100644 index 00000000..8f77c867 --- /dev/null +++ b/apps/api/src/combat/wild.controller.ts @@ -0,0 +1,135 @@ +import { + Controller, + Delete, + Get, + HttpCode, + Inject, + Param, + Post, + Put, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { CombatAutoRequestSchema } from '@daoyou/shared/combat-v6/auto'; +import { + CombatV6TrainingCommandParamsSchema, + CombatV6TrainingCommandRequestSchema, + CombatV6TrainingEventsQuerySchema, + CombatV6TrainingRevisionRequestSchema, + CombatV6TrainingSessionParamsSchema, +} from '@daoyou/shared/contracts/combatV6'; +import { + WildExploreRequestSchema, + WildStartRequestSchema, +} from '@daoyou/shared/contracts/combatV6Wild'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { AutoErrors } from './auto-errors'; +import { CombatErrors } from './combat-errors'; +import { WildService } from './wild.service'; + +@Controller('api/combat-v6/wild') +@Access('active') +@UseFilters(CombatErrors) +export class WildController { + constructor(@Inject(WildService) private readonly wild: WildService) {} + + @Get('regions/:nodeId') + region( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('nodeId') nodeId: string, + ) { + return this.wild.region(actor, nodeId); + } + + @Post('explorations') + @HttpCode(200) + explore( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody( + { fallback: undefined }, + new ZodPipe(WildExploreRequestSchema, 'legacy-unhandled'), + ) + input: z.infer, + ) { + return this.wild.explore(actor, input); + } + + @Post('sessions') + @HttpCode(200) + start( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody( + { fallback: undefined }, + new ZodPipe(WildStartRequestSchema, 'legacy-unhandled'), + ) + input: z.infer, + ) { + return this.wild.start(actor, input.encounterId); + } + + @Get('sessions/current') + current(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.wild.current(actor); + } + + @Get('sessions/:sessionId') + read( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param(new ZodPipe(CombatV6TrainingSessionParamsSchema)) + params: z.infer, + @FirstQuery(new ZodPipe(CombatV6TrainingEventsQuerySchema)) + query: z.infer, + ) { + return this.wild.read(actor, params.sessionId, query.afterEventSeq); + } + + @Put('sessions/:sessionId/commands/:unitId') + submit( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param(new ZodPipe(CombatV6TrainingCommandParamsSchema)) + params: z.infer, + @JsonBody(new ZodPipe(CombatV6TrainingCommandRequestSchema)) + input: z.infer, + ) { + return this.wild.submit(actor, params.sessionId, params.unitId, input); + } + + @Post('sessions/:sessionId/resolve') + @HttpCode(200) + resolve( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param(new ZodPipe(CombatV6TrainingSessionParamsSchema)) + params: z.infer, + @JsonBody(new ZodPipe(CombatV6TrainingRevisionRequestSchema)) + input: z.infer, + ) { + return this.wild.resolve(actor, params.sessionId, input.expectedRevision); + } + + @Post('sessions/:id/auto') + @HttpCode(200) + @UseFilters(AutoErrors) + auto( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('id', new ZodPipe(z.uuid(), 'legacy-unhandled')) id: string, + @JsonBody(new ZodPipe(CombatAutoRequestSchema, 'legacy-unhandled')) + input: z.infer, + ) { + return this.wild.resolve(actor, id, input.expectedRevision, input.round); + } + + @Delete('sessions/:sessionId') + abandon( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param(new ZodPipe(CombatV6TrainingSessionParamsSchema)) + params: z.infer, + @JsonBody(new ZodPipe(CombatV6TrainingRevisionRequestSchema)) + input: z.infer, + ) { + return this.wild.abandon(actor, params.sessionId, input.expectedRevision); + } +} diff --git a/apps/api/src/combat/wild.service.ts b/apps/api/src/combat/wild.service.ts new file mode 100644 index 00000000..397dfaa5 --- /dev/null +++ b/apps/api/src/combat/wild.service.ts @@ -0,0 +1,68 @@ +import { Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; +import { wildSessions } from '@server/lib/services/combat-v6/CombatV6WildSessionService'; +import type { CombatV6TrainingCommandRequestSchema } from '@daoyou/shared/contracts/combatV6'; +import type { WildExploreRequestSchema } from '@daoyou/shared/contracts/combatV6Wild'; +import type { z } from 'zod'; + +@Injectable() +export class WildService { + async region(actor: ActiveCultivatorRef, nodeId: string) { + return { success: true, data: await wildSessions.region(actor, nodeId) }; + } + async explore( + actor: ActiveCultivatorRef, + input: z.infer, + ) { + return toPlayerStateMutationResponse( + await wildSessions.explore(actor, input.nodeId, input.requestId), + ); + } + async start(actor: ActiveCultivatorRef, encounterId: string) { + return { + success: true, + data: await wildSessions.start(actor, encounterId), + }; + } + async current(actor: ActiveCultivatorRef) { + return { success: true, data: await wildSessions.current(actor) }; + } + async read(actor: ActiveCultivatorRef, id: string, after: number) { + return { success: true, data: await wildSessions.get(actor, id, after) }; + } + async submit( + actor: ActiveCultivatorRef, + id: string, + unitId: string, + input: z.infer, + ) { + return { + success: true, + data: await wildSessions.submit( + actor, + id, + input.expectedRevision, + unitId, + input.commands, + ), + }; + } + async resolve( + actor: ActiveCultivatorRef, + id: string, + revision: number, + round?: number, + ) { + return { + success: true, + data: await wildSessions.resolve(actor, id, revision, round), + }; + } + async abandon(actor: ActiveCultivatorRef, id: string, revision: number) { + return { + success: true, + data: await wildSessions.abandon(actor, id, revision), + }; + } +} diff --git a/apps/api/src/community/community.controller.ts b/apps/api/src/community/community.controller.ts new file mode 100644 index 00000000..5e62160f --- /dev/null +++ b/apps/api/src/community/community.controller.ts @@ -0,0 +1,21 @@ +import { Controller, Get, Inject } from '@nestjs/common'; +import { Access } from '../auth/access'; +import { CommunityService } from './community.service'; + +@Controller('api/community') +@Access('public') +export class CommunityController { + constructor( + @Inject(CommunityService) private readonly community: CommunityService, + ) {} + + @Get('qq-group') + qqGroup() { + return this.community.qqGroup(); + } + + @Get('announcement') + announcement() { + return this.community.announcement(); + } +} diff --git a/apps/api/src/community/community.module.ts b/apps/api/src/community/community.module.ts new file mode 100644 index 00000000..c9a9200a --- /dev/null +++ b/apps/api/src/community/community.module.ts @@ -0,0 +1,6 @@ +import { Module } from '@nestjs/common'; +import { CommunityController } from './community.controller'; +import { CommunityService } from './community.service'; + +@Module({ controllers: [CommunityController], providers: [CommunityService] }) +export class CommunityModule {} diff --git a/apps/api/src/community/community.service.ts b/apps/api/src/community/community.service.ts new file mode 100644 index 00000000..f557e09d --- /dev/null +++ b/apps/api/src/community/community.service.ts @@ -0,0 +1,38 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import { + getAuthPageAnnouncement, + getResolvedCommunityQqGroupNumber, +} from '@server/lib/repositories/appSettingsRepository'; + +@Injectable() +export class CommunityService { + async qqGroup() { + let groupNumber: string; + try { + groupNumber = await getResolvedCommunityQqGroupNumber(); + } catch { + throw new HttpException( + { success: false, error: 'QQ 群配置暂不可用,请稍后重试' }, + 503, + ); + } + return { + success: true, + groupNumber, + joinHint: '请复制群号后前往 QQ 搜索并申请加群', + }; + } + + async announcement() { + let announcement: string | null; + try { + announcement = await getAuthPageAnnouncement(); + } catch { + throw new HttpException( + { success: false, error: '公告配置暂不可用,请稍后重试' }, + 503, + ); + } + return { success: true, announcement }; + } +} diff --git a/apps/api/src/config/configuration.module.ts b/apps/api/src/config/configuration.module.ts new file mode 100644 index 00000000..99a8454c --- /dev/null +++ b/apps/api/src/config/configuration.module.ts @@ -0,0 +1,18 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { getRuntimeEnvironment } from '@server/lib/config/environment'; +import { runtimeConfig } from './runtime.config'; + +@Module({ + imports: [ + ConfigModule.forRoot({ + isGlobal: true, + ignoreEnvFile: true, + cache: true, + validate: () => getRuntimeEnvironment(), + load: [runtimeConfig], + }), + ], + exports: [ConfigModule], +}) +export class ConfigurationModule {} diff --git a/apps/api/src/config/runtime.config.ts b/apps/api/src/config/runtime.config.ts new file mode 100644 index 00000000..7e516b2c --- /dev/null +++ b/apps/api/src/config/runtime.config.ts @@ -0,0 +1,6 @@ +import { registerAs } from '@nestjs/config'; +import { getRuntimeEnvironment } from '@server/lib/config/environment'; + +export const runtimeConfig = registerAs('runtime', () => + getRuntimeEnvironment(), +); diff --git a/src/server/config/text-filter.dictionary.json b/apps/api/src/config/text-filter.dictionary.json similarity index 100% rename from src/server/config/text-filter.dictionary.json rename to apps/api/src/config/text-filter.dictionary.json diff --git a/apps/api/src/cultivator/condition.controller.ts b/apps/api/src/cultivator/condition.controller.ts new file mode 100644 index 00000000..9edc46b0 --- /dev/null +++ b/apps/api/src/cultivator/condition.controller.ts @@ -0,0 +1,145 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Post, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import { AttributeResetServiceError } from '@server/lib/services/AttributeResetService'; +import { + QiInsufficientError, + QiServiceError, +} from '@server/lib/services/QiService'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { JsonBody, JsonBodyParseError } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { ConditionService } from './condition.service'; + +const ConsumeSchema = z.object({ + consumableId: z.string().uuid(), + revision: z.number().int().nonnegative().optional(), + quantity: z.number().int().min(1).max(99).default(1), +}); +const BreakthroughSchema = z.object({}).strict(); + +function qiErrorResponse(error: unknown) { + if (error instanceof QiInsufficientError) + return Response.json( + { + error: error.code, + message: error.message, + required: error.required, + current: error.current, + action: error.action, + }, + { status: 409 }, + ); + if (error instanceof QiServiceError) + return Response.json({ error: error.message }, { status: error.status }); +} + +const ConsumeErrors = apiErrorFilter((error) => { + if (error instanceof JsonBodyParseError) return undefined; + if (error instanceof z.ZodError) + return Response.json( + { success: false, error: '请求参数格式错误' }, + { status: 400 }, + ); + const lock = redisLockErrorResponse(error); + if (lock) return lock; + if (error instanceof AttributeResetServiceError) + return Response.json({ error: error.message }, { status: error.status }); + return ( + qiErrorResponse(error) ?? + Response.json( + { + success: false, + error: error instanceof Error ? error.message : '使用失败', + }, + { status: 400 }, + ) + ); +}); +const InnErrors = apiErrorFilter((error) => { + if (error instanceof Error && error.message.startsWith('囊中羞涩,灵石不足')) + return Response.json( + { success: false, error: error.message }, + { status: 400 }, + ); +}); +const BreakthroughErrors = apiErrorFilter( + (error) => + redisLockErrorResponse(error) ?? + Response.json( + { + success: false, + error: error instanceof Error ? error.message : '肉身进阶失败', + }, + { status: 400 }, + ), +); +const MarrowWashErrors = apiErrorFilter((error) => { + const lock = redisLockErrorResponse(error); + if (lock) return lock; + const qi = qiErrorResponse(error); + if (qi) return qi; + const message = error instanceof Error ? error.message : '洗髓破限失败'; + return Response.json( + { success: false, error: message }, + { status: message === '角色不存在' ? 404 : 400 }, + ); +}); + +@Controller('api/cultivator') +@Access('active') +export class ConditionController { + constructor( + @Inject(ConditionService) private readonly condition: ConditionService, + ) {} + + @Post('consume') + @HttpCode(200) + @UseFilters(ConsumeErrors) + consume( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(ConsumeSchema)) input: z.infer, + ) { + return this.condition.consume(actor, input); + } + + @Post('inn-recovery') + @HttpCode(200) + @UseFilters(InnErrors) + recover(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.condition.recover(actor); + } + + @Get('body-cultivation/breakthrough') + readiness(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.condition.readiness(actor); + } + + @Post('body-cultivation/breakthrough') + @HttpCode(200) + @UseFilters(BreakthroughErrors) + breakthrough( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody({ fallback: {} }, new ZodPipe(BreakthroughSchema)) + _input: z.infer, + ) { + void _input; + return this.condition.breakthrough(actor); + } + + @Post('marrow-wash/breakthrough') + @HttpCode(200) + @UseFilters(MarrowWashErrors) + marrowWash(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.condition.marrowWash(actor); + } +} diff --git a/apps/api/src/cultivator/condition.service.ts b/apps/api/src/cultivator/condition.service.ts new file mode 100644 index 00000000..c953c127 --- /dev/null +++ b/apps/api/src/cultivator/condition.service.ts @@ -0,0 +1,59 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { + getBodyCultivationBreakthroughPreviewData, + loadPlayerBodyCultivationFacts, +} from '@server/lib/services/BodyCultivationBreakthroughService'; +import { + breakthroughBodyCultivation, + breakthroughCultivatorMarrowWash, + consumeCultivatorConsumable, + recoverCultivatorAtInn, +} from '@server/lib/services/CultivatorConditionApplicationService'; +import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; +import type { BodyCultivationBreakthroughReadinessResponse } from '@daoyou/shared/contracts/bodyCultivation'; + +@Injectable() +export class ConditionService { + async consume( + actor: ActiveCultivatorRef, + input: { consumableId: string; revision?: number; quantity: number }, + ) { + return toPlayerStateMutationResponse( + await consumeCultivatorConsumable({ actor, ...input }), + ); + } + + async recover(actor: ActiveCultivatorRef) { + return toPlayerStateMutationResponse( + await recoverCultivatorAtInn({ actor }), + ); + } + + async readiness( + actor: ActiveCultivatorRef, + ): Promise { + const facts = await loadPlayerBodyCultivationFacts( + actor.userId, + actor.cultivatorId, + ); + if (!facts) + throw new HttpException({ success: false, error: '角色不存在' }, 404); + return { + success: true, + data: getBodyCultivationBreakthroughPreviewData(facts), + }; + } + + async breakthrough(actor: ActiveCultivatorRef) { + return toPlayerStateMutationResponse( + await breakthroughBodyCultivation({ actor }), + ); + } + + async marrowWash(actor: ActiveCultivatorRef) { + return toPlayerStateMutationResponse( + await breakthroughCultivatorMarrowWash({ actor }), + ); + } +} diff --git a/apps/api/src/cultivator/cultivation.controller.ts b/apps/api/src/cultivator/cultivation.controller.ts new file mode 100644 index 00000000..7d7dd279 --- /dev/null +++ b/apps/api/src/cultivator/cultivation.controller.ts @@ -0,0 +1,128 @@ +import { + Controller, + HttpCode, + Inject, + Post, + Res, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import { JournalIdempotencyError } from '@server/lib/repositories/playerJournalRepository'; +import { + QiInsufficientError, + QiServiceError, +} from '@server/lib/services/QiService'; +import { RetreatCommandError } from '@server/lib/services/RetreatApplicationService'; +import { YieldCommandError } from '@server/lib/services/YieldApplicationService'; +import { JournalRequestSchema } from '@daoyou/shared/contracts/playerJournal'; +import { + RetreatRequestSchema, + type RetreatRequest, +} from '@daoyou/shared/contracts/retreat'; +import type { Response as ExpressResponse } from 'express'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { JsonBody } from '../http/json-body'; +import { withRequestAbort } from '../http/request-abort'; +import { streamSseEvents } from '../http/sse'; +import { ZodPipe } from '../http/zod.pipe'; +import { RetreatService } from './retreat.service'; +import { YieldService } from './yield.service'; + +const RetreatErrors = apiErrorFilter((error) => { + const lock = redisLockErrorResponse(error); + if (lock) return lock; + if (error instanceof QiInsufficientError) + return Response.json( + { + error: error.code, + message: error.message, + required: error.required, + current: error.current, + action: error.action, + }, + { status: 409 }, + ); + if (error instanceof QiServiceError) + return Response.json({ error: error.message }, { status: error.status }); + if (error instanceof JournalIdempotencyError) + return Response.json({ error: error.message }, { status: 409 }); + if (error instanceof RetreatCommandError) + return Response.json( + { success: false, error: error.message, ...error.payload }, + { status: error.status }, + ); + if (error instanceof z.ZodError) + return Response.json({ error: '闭关突破参数无效' }, { status: 400 }); + console.error('闭关突破请求失败:', error); + return Response.json({ error: '闭关突破暂时无法完成' }, { status: 500 }); +}); + +const YieldErrors = apiErrorFilter((error) => { + const lock = redisLockErrorResponse(error); + if (lock) return lock; + if (error instanceof YieldCommandError) + return Response.json( + { success: false, error: error.message }, + { status: error.status }, + ); + console.error('历练请求失败:', error); + return Response.json( + { success: false, error: '服务器内部错误' }, + { status: 500 }, + ); +}); + +@Controller('api/cultivator') +@Access('active') +export class CultivationController { + constructor( + @Inject(RetreatService) private readonly retreat: RetreatService, + @Inject(YieldService) private readonly yields: YieldService, + ) {} + + @Post('retreat') + @HttpCode(200) + @UseFilters(RetreatErrors) + cultivate( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody({ fallback: undefined }, new ZodPipe(RetreatRequestSchema)) + input: RetreatRequest, + @Res() response: ExpressResponse, + ) { + return withRequestAbort(response, async (signal) => { + const execution = await this.retreat.execute(actor, input); + if (signal.aborted) return; + return streamSseEvents(response, (stream, _isAborted, streamSignal) => + this.retreat.stream(execution, streamSignal, (event) => + stream.writeSSE({ data: JSON.stringify(event) }), + ), + ); + }); + } + + @Post('yield') + @HttpCode(200) + @UseFilters(YieldErrors) + claimYield( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody( + { fallback: undefined }, + new ZodPipe(JournalRequestSchema, 'legacy-unhandled'), + ) + input: z.infer, + @Res() response: ExpressResponse, + ) { + return withRequestAbort(response, async (signal) => { + const execution = await this.yields.execute(actor, input.requestId); + if (signal.aborted) return; + return streamSseEvents(response, (stream, _isAborted, streamSignal) => + this.yields.stream(execution, streamSignal, (event) => + stream.writeSSE({ data: JSON.stringify(event) }), + ), + ); + }); + } +} diff --git a/apps/api/src/cultivator/cultivator.module.ts b/apps/api/src/cultivator/cultivator.module.ts new file mode 100644 index 00000000..cfed64a3 --- /dev/null +++ b/apps/api/src/cultivator/cultivator.module.ts @@ -0,0 +1,27 @@ +import { Module } from '@nestjs/common'; +import { ConditionController } from './condition.controller'; +import { ConditionService } from './condition.service'; +import { CultivationController } from './cultivation.controller'; +import { LifecycleController } from './lifecycle.controller'; +import { LifecycleService } from './lifecycle.service'; +import { ProfileController } from './profile.controller'; +import { ProfileService } from './profile.service'; +import { RetreatService } from './retreat.service'; +import { YieldService } from './yield.service'; + +@Module({ + controllers: [ + ProfileController, + ConditionController, + LifecycleController, + CultivationController, + ], + providers: [ + ProfileService, + ConditionService, + LifecycleService, + RetreatService, + YieldService, + ], +}) +export class CultivatorModule {} diff --git a/apps/api/src/cultivator/lifecycle.controller.ts b/apps/api/src/cultivator/lifecycle.controller.ts new file mode 100644 index 00000000..c284812c --- /dev/null +++ b/apps/api/src/cultivator/lifecycle.controller.ts @@ -0,0 +1,47 @@ +import { getRuntimeEnvironment } from '@server/lib/config/environment'; +import { Controller, Delete, Get, Inject, UseFilters } from '@nestjs/common'; +import type { AuthUser } from '@server/lib/auth/types'; +import { isRedisLockContention } from '@server/lib/redis/lock'; +import { Access, CurrentUser } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { FirstQuery } from '../http/first-query'; +import { LifecycleService } from './lifecycle.service'; +const DeleteErrors = apiErrorFilter((error) => + isRedisLockContention(error) + ? Response.json( + { error: '角色正在执行其他操作,请稍后重试' }, + { status: 429 }, + ) + : undefined, +); +const ContextErrors = apiErrorFilter((error) => { + console.error('获取转世上下文 API 错误:', error); + return Response.json( + { + error: + getRuntimeEnvironment().NODE_ENV === 'development' + ? error instanceof Error + ? error.message + : '获取转世上下文失败' + : '获取转世上下文失败,请稍后再试', + }, + { status: 500 }, + ); +}); +@Controller('api/cultivators') +@Access('user') +export class LifecycleController { + constructor( + @Inject(LifecycleService) private readonly lifecycle: LifecycleService, + ) {} + @Delete() + @UseFilters(DeleteErrors) + delete(@CurrentUser() user: AuthUser, @FirstQuery() query: { id?: string }) { + return this.lifecycle.delete(user.id, query.id); + } + @Get('reincarnate-context') + @UseFilters(ContextErrors) + reincarnateContext(@CurrentUser() user: AuthUser) { + return this.lifecycle.reincarnateContext(user.id); + } +} diff --git a/apps/api/src/cultivator/lifecycle.service.ts b/apps/api/src/cultivator/lifecycle.service.ts new file mode 100644 index 00000000..957525e9 --- /dev/null +++ b/apps/api/src/cultivator/lifecycle.service.ts @@ -0,0 +1,17 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import { deleteCultivatorCommand } from '@server/lib/services/CultivatorProfileApplicationService'; +import { getLastDeadCultivatorSummary } from '@server/lib/services/cultivator/CultivatorProfileRepository'; +@Injectable() +export class LifecycleService { + async delete(userId: string, cultivatorId: string | undefined) { + if (!cultivatorId) throw new HttpException({ error: '请提供角色ID' }, 400); + const success = await deleteCultivatorCommand({ userId, cultivatorId }); + if (!success) + throw new HttpException({ error: '删除角色失败或角色不存在' }, 404); + return { success: true, message: '角色删除成功' }; + } + async reincarnateContext(userId: string) { + const summary = await getLastDeadCultivatorSummary(userId); + return { success: true, data: summary ?? null }; + } +} diff --git a/apps/api/src/cultivator/profile.controller.ts b/apps/api/src/cultivator/profile.controller.ts new file mode 100644 index 00000000..1dfe4a38 --- /dev/null +++ b/apps/api/src/cultivator/profile.controller.ts @@ -0,0 +1,154 @@ +import { + Controller, + Get, + HttpCode, + HttpException, + Inject, + Post, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import { AttributeResetServiceError } from '@server/lib/services/AttributeResetService'; +import { RedeemClaimError } from '@server/lib/services/RedeemCodeApplicationService'; +import { + AttributeAllocationSchema, + type AttributeAllocationRequest, +} from '@daoyou/shared/contracts/characterAttributes'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody, JsonBodyParseError } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { ProfileService } from './profile.service'; + +const TitleSchema = z.object({ + title: z.string().min(2).max(8).optional().nullable(), +}); +const RedeemSchema = z.object({ code: z.string().trim().min(1).max(64) }); +const AllocateErrors = apiErrorFilter((error) => { + if (error instanceof JsonBodyParseError) return undefined; + if (error instanceof z.ZodError) + return Response.json( + { error: '参数错误', details: error.flatten() }, + { status: 400 }, + ); + const lock = redisLockErrorResponse(error); + if (lock) return lock; + const message = error instanceof Error ? error.message : '属性分配失败'; + if ( + [ + '角色不存在', + '未分配属性点不足', + '属性不能低于基础值', + '属性总点数超过当前境界预算', + ].includes(message) + ) + return Response.json( + { error: message }, + { status: message === '角色不存在' ? 404 : 400 }, + ); +}); +const ResetErrors = apiErrorFilter((error) => { + const lock = redisLockErrorResponse(error); + if (lock) return lock; + if (error instanceof AttributeResetServiceError) + return Response.json({ error: error.message }, { status: error.status }); +}); +const RedeemErrors = apiErrorFilter((error) => { + if (error instanceof JsonBodyParseError) return undefined; + if (error instanceof z.ZodError) + return Response.json( + { error: '参数错误', details: error.flatten() }, + { status: 400 }, + ); + const lock = redisLockErrorResponse(error); + if (lock) return lock; + if ( + error && + typeof error === 'object' && + 'code' in error && + error.code === '23505' + ) + return Response.json({ error: '该兑换码你已使用过' }, { status: 400 }); + if (error instanceof RedeemClaimError) + return Response.json({ error: error.message }, { status: error.status }); + console.error('Redeem claim error:', error); + return Response.json({ error: '兑换失败,请稍后重试' }, { status: 500 }); +}); + +@Controller('api/cultivator') +@Access('active') +export class ProfileController { + constructor( + @Inject(ProfileService) private readonly profile: ProfileService, + ) {} + + @Post('active-reincarnate') + @HttpCode(200) + reincarnate(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.profile.reincarnate(actor); + } + + @Post('equip') + retiredEquip() { + throw new HttpException({ error: '旧产物装配已停用' }, 410); + } + + @Get('qi/logs') + qiLogs( + @CurrentCultivator() actor: ActiveCultivatorRef, + @FirstQuery('page') page?: string, + @FirstQuery('pageSize') pageSize?: string, + ) { + return this.profile.qiLogs(actor.cultivatorId, page, pageSize); + } + + @Post('title') + @HttpCode(200) + title( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(TitleSchema)) input: z.infer, + ) { + return this.profile.title(actor, input.title); + } + + @Post('attributes/preview') + @HttpCode(200) + preview( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody({ fallback: undefined }, new ZodPipe(AttributeAllocationSchema)) + input: AttributeAllocationRequest, + ) { + return this.profile.preview(actor, input); + } + + @Post('attributes/allocate') + @HttpCode(200) + @UseFilters(AllocateErrors) + allocate( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(AttributeAllocationSchema)) + input: AttributeAllocationRequest, + ) { + return this.profile.allocate(actor, input); + } + + @Post('attributes/reset') + @HttpCode(200) + @UseFilters(ResetErrors) + reset(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.profile.reset(actor); + } + + @Post('redeem-code/claim') + @HttpCode(200) + @UseFilters(RedeemErrors) + redeem( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(RedeemSchema)) input: z.infer, + ) { + return this.profile.redeem(actor, input.code); + } +} diff --git a/apps/api/src/cultivator/profile.service.ts b/apps/api/src/cultivator/profile.service.ts new file mode 100644 index 00000000..472fc8ca --- /dev/null +++ b/apps/api/src/cultivator/profile.service.ts @@ -0,0 +1,113 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { db } from '@server/lib/drizzle/db'; +import { + isValidRedeemCodeFormat, + normalizeRedeemCode, +} from '@server/lib/redeem/code'; +import { loadPlayerRetreatFacts } from '@server/lib/services/cultivator/CultivatorConditionFactsReader'; +import { + allocateCultivatorAttributes, + reincarnateActiveCultivator, + resetCultivatorAttributes, + updateCultivatorTitle, +} from '@server/lib/services/CultivatorProfileApplicationService'; +import { QiService } from '@server/lib/services/QiService'; +import { claimRedeemCode } from '@server/lib/services/RedeemCodeApplicationService'; +import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; +import type { + AttributeAllocationRequest, + AttributePreviewData, +} from '@daoyou/shared/contracts/characterAttributes'; +import { + CHARACTER_ATTRIBUTE_LABELS, + projectCharacterDisplay, +} from '@daoyou/shared/lib/cultivatorDisplay'; + +function parsePositiveInt(value: string | undefined, fallback: number) { + if (!value) return fallback; + const parsed = Number.parseInt(value, 10); + return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback; +} + +@Injectable() +export class ProfileService { + async reincarnate(actor: ActiveCultivatorRef) { + return toPlayerStateMutationResponse( + await reincarnateActiveCultivator({ actor }), + ); + } + + async qiLogs(cultivatorId: string, page?: string, pageSize?: string) { + return { + success: true, + data: await QiService.listLogs(cultivatorId, { + page: parsePositiveInt(page, 1), + pageSize: Math.min(100, parsePositiveInt(pageSize, 20)), + }), + }; + } + + async title(actor: ActiveCultivatorRef, title?: string | null) { + return toPlayerStateMutationResponse( + await updateCultivatorTitle({ ...actor, title: title || null }), + ); + } + + async preview(actor: ActiveCultivatorRef, delta: AttributeAllocationRequest) { + const data = await db.transaction( + async (tx): Promise => { + const facts = await loadPlayerRetreatFacts( + actor.userId, + actor.cultivatorId, + tx, + ); + if (!facts) throw new HttpException({ error: '角色不存在' }, 404); + const keys = Object.keys( + CHARACTER_ATTRIBUTE_LABELS, + ) as (keyof typeof facts.attributes)[]; + const spent = keys.reduce((sum, key) => sum + delta[key], 0); + if (spent > facts.unallocated_attribute_points) + throw new HttpException({ error: '未分配属性点不足' }, 400); + const attributes = { ...facts.attributes }; + for (const key of keys) attributes[key] += delta[key]; + return { + current: facts.combatV6ResourceAuthority.attrs, + preview: projectCharacterDisplay( + { ...facts, attributes }, + facts.combatV6ResourceAuthority.build, + ), + }; + }, + { isolationLevel: 'repeatable read', accessMode: 'read only' }, + ); + return { success: true, data }; + } + + async allocate( + actor: ActiveCultivatorRef, + delta: AttributeAllocationRequest, + ) { + return toPlayerStateMutationResponse( + await allocateCultivatorAttributes({ actor, delta }), + ); + } + + async reset(actor: ActiveCultivatorRef) { + return toPlayerStateMutationResponse( + await resetCultivatorAttributes(actor), + ); + } + + async redeem(actor: ActiveCultivatorRef, code: string) { + const normalizedCode = normalizeRedeemCode(code); + if (!isValidRedeemCodeFormat(normalizedCode)) + throw new HttpException( + { error: '兑换码格式错误,仅支持 6-64 位大写字母数字' }, + 400, + ); + return toPlayerStateMutationResponse( + await claimRedeemCode({ ...actor, code: normalizedCode }), + ); + } +} diff --git a/apps/api/src/cultivator/retreat.service.ts b/apps/api/src/cultivator/retreat.service.ts new file mode 100644 index 00000000..63b5d6d4 --- /dev/null +++ b/apps/api/src/cultivator/retreat.service.ts @@ -0,0 +1,73 @@ +import { Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { executeRetreatCommand } from '@server/lib/services/RetreatApplicationService'; +import { streamAiText } from '@server/utils/aiClient'; +import { + getBreakthroughStoryPrompt, + getLifespanExhaustedStoryPrompt, +} from '@server/utils/prompts'; +import type { + RetreatRequest, + RetreatStreamEvent, +} from '@daoyou/shared/contracts/retreat'; + +@Injectable() +export class RetreatService { + execute(actor: ActiveCultivatorRef, input: RetreatRequest) { + return executeRetreatCommand({ + userId: actor.userId, + cultivatorId: actor.cultivatorId, + action: input.action, + years: input.action === 'cultivate' ? input.years : 0, + requestId: input.requestId, + }); + } + + async stream( + execution: Awaited>, + signal: AbortSignal, + emit: (event: RetreatStreamEvent) => Promise, + ) { + const { committed, storySource, onStoryComplete } = execution; + await emit({ type: 'result', data: committed.result }); + if (committed.state.changes.length || committed.state.replayed) { + await emit({ type: 'state', state: committed.state }); + } + if (!storySource) return; + + let accumulatedStory = ''; + try { + const prompt = + storySource.type === 'breakthrough' + ? getBreakthroughStoryPrompt(storySource.payload) + : getLifespanExhaustedStoryPrompt(storySource.payload); + const result = streamAiText({ + system: prompt[0], + prompt: prompt[1], + abortSignal: signal, + sceneId: + storySource.type === 'breakthrough' + ? 'breakthrough-story' + : 'lifespan-exhausted', + }); + for await (const chunk of result.textStream) { + accumulatedStory += chunk; + await emit({ type: 'chunk', text: chunk }); + } + } catch (error) { + console.error('Retreat story stream error:', error); + if (!signal.aborted) { + await emit({ + type: 'error', + error: '天机推演中断,此番结果已然落定。', + }); + } + } finally { + try { + await onStoryComplete?.(accumulatedStory); + } catch (error) { + console.error('Retreat story persist error:', error); + } + } + } +} diff --git a/apps/api/src/cultivator/yield.service.ts b/apps/api/src/cultivator/yield.service.ts new file mode 100644 index 00000000..7c8857f9 --- /dev/null +++ b/apps/api/src/cultivator/yield.service.ts @@ -0,0 +1,67 @@ +import { Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { renderPrompt } from '@server/lib/prompts'; +import { executeYieldCommand } from '@server/lib/services/YieldApplicationService'; +import { streamAiText } from '@server/utils/aiClient'; +import { getGameConceptLabel } from '@daoyou/shared/lib/gameConceptDisplay'; + +type YieldExecution = Awaited>; +type YieldStreamEvent = + | { type: 'result'; data: YieldExecution['committed']['result'] } + | { type: 'state'; state: YieldExecution['committed']['state'] } + | { type: 'chunk'; text: string } + | { type: 'error'; error: string }; + +@Injectable() +export class YieldService { + execute(actor: ActiveCultivatorRef, requestId: string) { + return executeYieldCommand({ + userId: actor.userId, + cultivatorId: actor.cultivatorId, + requestId, + }); + } + + async stream( + { committed, result }: YieldExecution, + signal: AbortSignal, + emit: (event: YieldStreamEvent) => Promise, + ) { + await emit({ type: 'result', data: committed.result }); + if (committed.state.changes.length || committed.state.replayed) { + await emit({ type: 'state', state: committed.state }); + } + if (committed.state.replayed) return; + + const extra = [ + result.expGain ? `修为精进 ${result.expGain} 点` : '', + result.insightGain + ? `${getGameConceptLabel('comprehension_insight')} ${result.insightGain} 点` + : '', + ] + .filter(Boolean) + .join(';'); + const { system, user: prompt } = renderPrompt('yield-story', { + cultivatorRealm: result.cultivatorRealm, + cultivatorName: result.cultivatorName, + amount: result.amount, + extraYieldText: extra ? `;${extra}` : '', + }); + try { + const aiStreamResult = streamAiText({ + system, + prompt, + abortSignal: signal, + sceneId: 'yield-story', + }); + for await (const chunk of aiStreamResult.textStream) { + await emit({ type: 'chunk', text: chunk }); + } + } catch (error) { + console.error('Stream processing error:', error); + if (!signal.aborted) { + await emit({ type: 'error', error: '天机推演中断...' }); + } + } + } +} diff --git a/apps/api/src/database/database.module.ts b/apps/api/src/database/database.module.ts new file mode 100644 index 00000000..03dea7ce --- /dev/null +++ b/apps/api/src/database/database.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { db } from '@server/lib/drizzle/db'; +import { DatabaseService, DRIZZLE_DATABASE } from './database.service'; + +// Repositories and Nest providers share the existing transaction-aware client. +@Module({ + providers: [{ provide: DRIZZLE_DATABASE, useValue: db }, DatabaseService], + exports: [DRIZZLE_DATABASE, DatabaseService], +}) +export class DatabaseModule {} diff --git a/apps/api/src/database/database.service.ts b/apps/api/src/database/database.service.ts new file mode 100644 index 00000000..524bedd1 --- /dev/null +++ b/apps/api/src/database/database.service.ts @@ -0,0 +1,24 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { closeDatabase, type DbClient } from '@server/lib/drizzle/db'; +import { sql } from 'drizzle-orm'; + +export const DRIZZLE_DATABASE = Symbol('DRIZZLE_DATABASE'); + +@Injectable() +export class DatabaseService { + constructor(@Inject(DRIZZLE_DATABASE) readonly client: DbClient) {} + + async healthStatus(): Promise<'up' | 'down'> { + try { + await this.client.execute(sql`SELECT 1`); + return 'up'; + } catch { + return 'down'; + } + } + + // RuntimeService invokes this after HTTP and messaging have drained. + close(): Promise { + return closeDatabase(); + } +} diff --git a/apps/api/src/dev-tools/dev-tools.controller.ts b/apps/api/src/dev-tools/dev-tools.controller.ts new file mode 100644 index 00000000..9e866881 --- /dev/null +++ b/apps/api/src/dev-tools/dev-tools.controller.ts @@ -0,0 +1,57 @@ +import { + Controller, + Delete, + HttpCode, + Inject, + Param, + Patch, + Post, + Req, + UseFilters, +} from '@nestjs/common'; +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import type { Request } from 'express'; +import { Access } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { readRequestBody } from '../http/json-body'; +import { DevToolsService } from './dev-tools.service'; + +const DevToolsErrors = apiErrorFilter((error) => { + const lock = redisLockErrorResponse(error); + if (lock) return lock; + console.error('Dev tools API error:', error); + return Response.json( + { success: false, error: '服务器内部错误' }, + { status: 500 }, + ); +}); + +@Controller('api/dev') +@Access('public') +@UseFilters(DevToolsErrors) +export class DevToolsController { + constructor( + @Inject(DevToolsService) private readonly service: DevToolsService, + ) {} + + @Post('resources') + @HttpCode(200) + async grant(@Req() request: Request) { + return this.service.grant(await readRequestBody(request)); + } + + @Delete('cultivators/:id/inventory/bag') + clearBag(@Param('id') id: string) { + return this.service.clearBag(id); + } + + @Delete('cultivators/:id/divination') + resetDivination(@Param('id') id: string) { + return this.service.resetDivination(id); + } + + @Patch('cultivators/:id') + async patch(@Param('id') id: string, @Req() request: Request) { + return this.service.patch(id, await readRequestBody(request)); + } +} diff --git a/apps/api/src/dev-tools/dev-tools.module.ts b/apps/api/src/dev-tools/dev-tools.module.ts new file mode 100644 index 00000000..4f6f42e3 --- /dev/null +++ b/apps/api/src/dev-tools/dev-tools.module.ts @@ -0,0 +1,27 @@ +import { getRuntimeEnvironment } from '@server/lib/config/environment'; +import { + Module, + RequestMethod, + type MiddlewareConsumer, + type NestModule, +} from '@nestjs/common'; +import { allowsLocalDevTools } from '@daoyou/shared/config/deployment'; +import type { NextFunction, Request, Response } from 'express'; +import { DevToolsController } from './dev-tools.controller'; +import { DevToolsService } from './dev-tools.service'; + +@Module({ controllers: [DevToolsController], providers: [DevToolsService] }) +export class DevToolsModule implements NestModule { + configure(consumer: MiddlewareConsumer) { + consumer + .apply((_request: Request, response: Response, next: NextFunction) => { + if (!allowsLocalDevTools(getRuntimeEnvironment().APP_ENV, getRuntimeEnvironment().NODE_ENV)) { + response.status(404).json({ success: false, error: '接口不存在' }); + return; + } + response.setHeader('Cache-Control', 'no-store'); + next(); + }) + .forRoutes({ path: 'api/dev{/*path}', method: RequestMethod.ALL }); + } +} diff --git a/apps/api/src/dev-tools/dev-tools.service.ts b/apps/api/src/dev-tools/dev-tools.service.ts new file mode 100644 index 00000000..be7c24d0 --- /dev/null +++ b/apps/api/src/dev-tools/dev-tools.service.ts @@ -0,0 +1,93 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import { patchDevCultivator } from '@server/lib/services/DevCultivatorService'; +import { resetDevDivination } from '@server/lib/services/DevDivinationService'; +import { clearDevInventoryBag } from '@server/lib/services/DevInventoryService'; +import { DivinationError } from '@server/lib/services/DivinationService'; +import { grantDevResources } from '@server/lib/services/ForgingService'; +import { InventoryError } from '@server/lib/services/InventoryService'; +import { QiServiceError } from '@server/lib/services/QiService'; +import { DevCultivatorPatchSchema } from '@daoyou/shared/contracts/devTools'; +import { DevGrantSchema } from '@daoyou/shared/contracts/forging'; +import { InventoryRuleError } from '@daoyou/shared/inventory'; +import { z } from 'zod'; + +@Injectable() +export class DevToolsService { + async grant(body: Uint8Array | undefined) { + try { + return { + success: true, + ...(await grantDevResources( + DevGrantSchema.parse(JSON.parse(new TextDecoder().decode(body))), + )), + }; + } catch (error) { + if (error instanceof z.ZodError) + throw new HttpException({ success: false, error: '发放参数无效' }, 400); + if ( + error instanceof InventoryError || + error instanceof InventoryRuleError || + error instanceof QiServiceError + ) + throw new HttpException({ success: false, error: error.message }, 409); + throw error; + } + } + + async clearBag(id: string) { + try { + return { + success: true, + ...(await clearDevInventoryBag(z.uuid().parse(id))), + }; + } catch (error) { + if (error instanceof z.ZodError) + throw new HttpException({ success: false, error: '角色 ID 无效' }, 400); + if (error instanceof InventoryError) + throw new HttpException({ success: false, error: error.message }, 409); + throw error; + } + } + + async resetDivination(id: string) { + try { + return { + success: true, + ...(await resetDevDivination(z.uuid().parse(id))), + }; + } catch (error) { + if (error instanceof z.ZodError) + throw new HttpException({ success: false, error: '角色 ID 无效' }, 400); + if (error instanceof DivinationError) + throw new HttpException( + { success: false, error: error.message }, + error.status, + ); + throw error; + } + } + + async patch(id: string, body: Uint8Array | undefined) { + try { + // The original endpoint validates the ID before decoding the request body. + return { + success: true, + ...(await patchDevCultivator( + z.uuid().parse(id), + DevCultivatorPatchSchema.parse( + JSON.parse(new TextDecoder().decode(body)), + ), + )), + }; + } catch (error) { + if (error instanceof z.ZodError) + throw new HttpException( + { success: false, error: '角色调整参数无效' }, + 400, + ); + if (error instanceof InventoryError) + throw new HttpException({ success: false, error: error.message }, 409); + throw error; + } + } +} diff --git a/apps/api/src/divination/divination-exception.filter.ts b/apps/api/src/divination/divination-exception.filter.ts new file mode 100644 index 00000000..97152c0d --- /dev/null +++ b/apps/api/src/divination/divination-exception.filter.ts @@ -0,0 +1,18 @@ +import { + Catch, + type ArgumentsHost, + type ExceptionFilter, +} from '@nestjs/common'; +import { DivinationError } from '@server/lib/services/DivinationService'; +import type { Response } from 'express'; + +@Catch(DivinationError) +export class DivinationExceptionFilter implements ExceptionFilter { + catch(error: DivinationError, host: ArgumentsHost) { + host + .switchToHttp() + .getResponse() + .status(error.status) + .json({ error: error.message }); + } +} diff --git a/apps/api/src/divination/divination.controller.ts b/apps/api/src/divination/divination.controller.ts new file mode 100644 index 00000000..17789ee2 --- /dev/null +++ b/apps/api/src/divination/divination.controller.ts @@ -0,0 +1,66 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Post, + Res, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { + DivinationDrawSchema, + DivinationInterpretSchema, +} from '@daoyou/shared/contracts/divination'; +import type { Response } from 'express'; +import type { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { JsonBody } from '../http/json-body'; +import { streamSseEvents } from '../http/sse'; +import { ZodPipe } from '../http/zod.pipe'; +import { DivinationExceptionFilter } from './divination-exception.filter'; +import { DivinationService } from './divination.service'; + +@Controller('api/divination') +@Access('active') +@UseFilters(DivinationExceptionFilter) +export class DivinationController { + constructor( + @Inject(DivinationService) private readonly divination: DivinationService, + ) {} + + @Get() + read(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.divination.read(actor); + } + + @Post('draw') + @HttpCode(200) + draw( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(DivinationDrawSchema)) + body: z.infer, + ) { + return this.divination.draw(actor, body.direction); + } + + @Post('interpret') + @HttpCode(200) + interpret( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(DivinationInterpretSchema)) + body: z.infer, + @Res() response: Response, + ) { + return streamSseEvents(response, (stream, isAborted, signal) => + this.divination.interpret(actor, body.drawId, signal, async (event) => { + if (isAborted()) return; + try { + await stream.writeSSE({ data: JSON.stringify(event) }); + } catch { + // Settlement is independent of delivery of the final event. + } + }), + ); + } +} diff --git a/apps/api/src/divination/divination.module.ts b/apps/api/src/divination/divination.module.ts new file mode 100644 index 00000000..d4a45f00 --- /dev/null +++ b/apps/api/src/divination/divination.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { DivinationController } from './divination.controller'; +import { DivinationService } from './divination.service'; +import { DivineFortuneController } from './divine-fortune.controller'; +import { DivineFortuneService } from './divine-fortune.service'; + +@Module({ + controllers: [DivinationController, DivineFortuneController], + providers: [DivinationService, DivineFortuneService], +}) +export class DivinationModule {} diff --git a/apps/api/src/divination/divination.service.ts b/apps/api/src/divination/divination.service.ts new file mode 100644 index 00000000..7ede528b --- /dev/null +++ b/apps/api/src/divination/divination.service.ts @@ -0,0 +1,46 @@ +import { Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import { + DivinationError, + drawDivination, + interpretDivination, + readDivination, +} from '@server/lib/services/DivinationService'; +import { InventoryError } from '@server/lib/services/InventoryService'; +import type { DivinationStreamEvent } from '@daoyou/shared/contracts/divination'; + +@Injectable() +export class DivinationService { + read(actor: ActiveCultivatorRef) { + return readDivination(actor); + } + + draw( + actor: ActiveCultivatorRef, + direction: Parameters[1], + ) { + return drawDivination(actor, direction); + } + + async interpret( + actor: ActiveCultivatorRef, + drawId: string, + signal: AbortSignal, + emit: (event: DivinationStreamEvent) => Promise, + ) { + try { + await interpretDivination(actor, drawId, signal, emit); + } catch (error) { + console.warn('[divination] interpretation incomplete', { drawId, error }); + await emit({ + type: 'error', + message: redisLockErrorResponse(error) + ? '此签正在处理,请稍后重新查看。' + : error instanceof DivinationError || error instanceof InventoryError + ? error.message + : '解签暂未完成,请稍后继续。', + }); + } + } +} diff --git a/apps/api/src/divination/divine-fortune.controller.ts b/apps/api/src/divination/divine-fortune.controller.ts new file mode 100644 index 00000000..5acb2c3a --- /dev/null +++ b/apps/api/src/divination/divine-fortune.controller.ts @@ -0,0 +1,15 @@ +import { Controller, Get, Inject } from '@nestjs/common'; +import { Access } from '../auth/access'; +import { DivineFortuneService } from './divine-fortune.service'; +@Controller('api/divine-fortune') +@Access('public') +export class DivineFortuneController { + constructor( + @Inject(DivineFortuneService) + private readonly fortune: DivineFortuneService, + ) {} + @Get() + read() { + return this.fortune.read(); + } +} diff --git a/apps/api/src/divination/divine-fortune.service.ts b/apps/api/src/divination/divine-fortune.service.ts new file mode 100644 index 00000000..fe7d0bbf --- /dev/null +++ b/apps/api/src/divination/divine-fortune.service.ts @@ -0,0 +1,44 @@ +import { Injectable } from '@nestjs/common'; +import { redis } from '@server/lib/redis'; +import { parseRedisJson } from '@server/lib/redis/json'; +import { generateAiObject } from '@server/utils/aiClient'; +import { + getDivineFortunePrompt, + getRandomFallbackFortune, +} from '@server/utils/divineFortune'; +import { + DivineFortuneSchema, + type DivineFortune, +} from '@daoyou/shared/lib/divineFortune'; +const CACHE_KEY = 'divine_fortune_data'; +const CACHE_TTL = 60 * 60 * 24; +@Injectable() +export class DivineFortuneService { + async read() { + try { + const cachedRaw = await redis.get(CACHE_KEY); + const cachedFortune = parseRedisJson(cachedRaw, CACHE_KEY); + if (cachedRaw && !cachedFortune) await redis.del(CACHE_KEY); + if (cachedFortune) + return { success: true, data: cachedFortune, cached: true }; + const [systemPrompt, userPrompt] = getDivineFortunePrompt(); + const aiResponse = await generateAiObject({ + system: systemPrompt, + prompt: userPrompt, + schema: DivineFortuneSchema, + name: 'DivineFortune', + sceneId: 'divine-fortune', + }); + const fortune = aiResponse.output; + await redis.set(CACHE_KEY, JSON.stringify(fortune), 'EX', CACHE_TTL); + return { success: true, data: fortune }; + } catch (error) { + console.error('天机推演 API 错误:', error); + return { + success: true, + data: getRandomFallbackFortune(), + fallback: true, + }; + } + } +} diff --git a/apps/api/src/dungeon/dungeon-battle.controller.ts b/apps/api/src/dungeon/dungeon-battle.controller.ts new file mode 100644 index 00000000..0d99f2cd --- /dev/null +++ b/apps/api/src/dungeon/dungeon-battle.controller.ts @@ -0,0 +1,139 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Param, + Post, + Put, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { CombatAutoRequestSchema } from '@daoyou/shared/combat-v6/auto'; +import { + CombatV6TrainingCommandRequestSchema, + CombatV6TrainingEventsQuerySchema, + CombatV6TrainingRevisionRequestSchema, +} from '@daoyou/shared/contracts/combatV6'; +import { DungeonBeginBattleRequestSchema } from '@daoyou/shared/contracts/combatV6Dungeon'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { AutoErrors } from '../combat/auto-errors'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { + DungeonBeginErrors, + DungeonCompleteErrors, + DungeonResolveErrors, + DungeonSubmitErrors, +} from './dungeon-errors'; +import { DungeonService } from './dungeon.service'; + +const BattleIdSchema = z.object({ + battleId: z.string().min(1), + requestId: z.string().min(1).max(120).optional(), +}); + +@Controller('api/dungeon/battle') +@Access('active') +export class DungeonBattleController { + constructor( + @Inject(DungeonService) private readonly dungeon: DungeonService, + ) {} + + @Post('sessions/:id/auto') + @HttpCode(200) + @UseFilters(AutoErrors) + auto( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('id', new ZodPipe(z.uuid(), 'legacy-unhandled')) id: string, + @JsonBody(new ZodPipe(CombatAutoRequestSchema, 'legacy-unhandled')) + input: z.infer, + ) { + return this.dungeon.resolve(actor, id, input.expectedRevision, input.round); + } + + @Get('sessions/current') + current(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.dungeon.current(actor.cultivatorId); + } + + @Get('sessions/:id') + read( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('id', new ZodPipe(z.uuid())) id: string, + @FirstQuery(new ZodPipe(CombatV6TrainingEventsQuerySchema)) + query: z.infer, + ) { + return this.dungeon.read(actor.cultivatorId, id, query.afterEventSeq); + } + + @Put('sessions/:id/commands/:unitId') + @UseFilters(DungeonSubmitErrors) + submit( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('id', new ZodPipe(z.uuid())) id: string, + @Param('unitId') unitId: string, + @JsonBody( + new ZodPipe(CombatV6TrainingCommandRequestSchema, 'legacy-unhandled'), + ) + input: z.infer, + ) { + return this.dungeon.submit(actor, id, unitId, input); + } + + @Post('sessions/:id/resolve') + @HttpCode(200) + @UseFilters(DungeonResolveErrors) + resolve( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('id', new ZodPipe(z.uuid())) id: string, + @JsonBody( + new ZodPipe(CombatV6TrainingRevisionRequestSchema, 'legacy-unhandled'), + ) + input: z.infer, + ) { + return this.dungeon.resolve(actor, id, input.expectedRevision); + } + + @Get('probe') + @HttpCode(410) + retiredProbe() { + return { error: '旧查探入口已停用' }; + } + + @Post('begin') + @HttpCode(200) + @UseFilters(DungeonBeginErrors) + begin( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(DungeonBeginBattleRequestSchema)) + input: z.infer, + ) { + return this.dungeon.execute(actor, { kind: 'battle-begin', ...input }); + } + + @Post('abandon') + @HttpCode(410) + retiredAbandon() { + return { error: '旧放弃入口已停用,请使用战斗内逃跑' }; + } + + @Post('complete') + @HttpCode(200) + @UseFilters(DungeonCompleteErrors) + complete( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(BattleIdSchema)) + input: z.infer, + ) { + return this.dungeon.execute(actor, { kind: 'battle-execute', ...input }); + } + + @Post('execute/v5') + @HttpCode(410) + retiredExecute() { + return { error: '旧战斗已停用,请刷新页面' }; + } +} diff --git a/apps/api/src/dungeon/dungeon-errors.ts b/apps/api/src/dungeon/dungeon-errors.ts new file mode 100644 index 00000000..d63a7f9e --- /dev/null +++ b/apps/api/src/dungeon/dungeon-errors.ts @@ -0,0 +1,131 @@ +import { DungeonFlowError } from '@server/lib/dungeon/service_v2'; +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import { DungeonStartError } from '@server/lib/services/DungeonApplicationService'; +import { + QiInsufficientError, + QiServiceError, +} from '@server/lib/services/QiService'; +import { ZodError } from 'zod'; +import { apiErrorFilter } from '../http/error-filter'; +import { JsonBodyParseError } from '../http/json-body'; + +function flowError(error: unknown): Response | undefined { + return ( + redisLockErrorResponse(error) ?? + (error instanceof DungeonFlowError + ? Response.json( + { error: error.message, code: error.code }, + { status: error.status }, + ) + : undefined) + ); +} + +export const DungeonStartErrors = apiErrorFilter((error) => { + const flow = flowError(error); + if (flow) return flow; + if (error instanceof DungeonStartError) + return Response.json( + { + error: error.message, + ...(error.readiness ? { readiness: error.readiness } : {}), + }, + { status: error.status }, + ); + if (error instanceof QiInsufficientError) + return Response.json( + { + error: error.code, + message: error.message, + required: error.required, + current: error.current, + action: error.action, + }, + { status: 409 }, + ); + if (error instanceof QiServiceError) + return Response.json({ error: error.message }, { status: error.status }); +}); + +export const DungeonActionErrors = apiErrorFilter((error) => { + const flow = flowError(error); + if (flow) return flow; + if (error instanceof ZodError) + return Response.json( + { error: '探索请求无效,请刷新后重试' }, + { status: 400 }, + ); + const message = error instanceof Error ? error.message : '副本推进失败'; + const status = + error instanceof DungeonStartError || + /不足|没有符合条件|资源消耗失败|所选物品|提交的材料|提交数量|选择需要提交/.test( + message, + ) + ? 409 + : 500; + return Response.json({ error: message }, { status }); +}); + +function recoveryErrors(fallback: string) { + return apiErrorFilter( + (error) => + flowError(error) ?? + Response.json( + { + error: error instanceof Error ? error.message : fallback, + }, + { + status: + error instanceof ZodError + ? 400 + : error instanceof DungeonStartError + ? 409 + : 500, + }, + ), + ); +} + +export const DungeonRecoverErrors = recoveryErrors('副本恢复失败'); +export const DungeonContinueErrors = recoveryErrors('副本推进失败'); +export const DungeonEscapeErrors = recoveryErrors('副本结算失败'); + +export const DungeonQuitErrors = apiErrorFilter( + (error) => + flowError(error) ?? + (error instanceof DungeonStartError + ? Response.json({ error: error.message }, { status: 409 }) + : undefined), +); + +export const DungeonBeginErrors = apiErrorFilter( + (error) => + redisLockErrorResponse(error) ?? + (error instanceof DungeonFlowError + ? Response.json({ error: error.message }, { status: 409 }) + : undefined), +); + +export const DungeonCompleteErrors = apiErrorFilter((error) => { + const flow = flowError(error); + if (flow) return flow; + const message = error instanceof Error ? error.message : '遭遇战执行失败'; + return Response.json( + { error: message }, + { status: /遭遇战|修真者/.test(message) ? 404 : 500 }, + ); +}); + +function battleCommandErrors(fallback: string) { + return apiErrorFilter((error) => { + // Body parsing precedes the original command handler's catch block. + if (error instanceof JsonBodyParseError) return undefined; + return Response.json( + { error: error instanceof Error ? error.message : fallback }, + { status: 409 }, + ); + }); +} + +export const DungeonSubmitErrors = battleCommandErrors('提交失败'); +export const DungeonResolveErrors = battleCommandErrors('结算失败'); diff --git a/apps/api/src/dungeon/dungeon.controller.ts b/apps/api/src/dungeon/dungeon.controller.ts new file mode 100644 index 00000000..d818dcb1 --- /dev/null +++ b/apps/api/src/dungeon/dungeon.controller.ts @@ -0,0 +1,133 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Post, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { + DungeonActionRequestSchema, + DungeonFlowRequestSchema, +} from '@daoyou/shared/contracts/combatV6Dungeon'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { + DungeonActionErrors, + DungeonContinueErrors, + DungeonEscapeErrors, + DungeonQuitErrors, + DungeonRecoverErrors, + DungeonStartErrors, +} from './dungeon-errors'; +import { DungeonService } from './dungeon.service'; + +const StartSchema = z.object({ mapNodeId: z.string().min(1) }); +const StateSchema = z.object({ runId: z.uuid().optional() }).strict(); +const RecoverSchema = DungeonFlowRequestSchema.extend({ + action: z.enum([ + 'retry', + 'retry_continue', + 'retry_settle', + 'safe_retreat', + 'force_quit', + ]), +}); + +@Controller('api/dungeon') +@Access('active') +export class DungeonController { + constructor( + @Inject(DungeonService) private readonly dungeon: DungeonService, + ) {} + + @Post('start') + @HttpCode(200) + @UseFilters(DungeonStartErrors) + start( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(StartSchema)) input: z.infer, + ) { + return this.dungeon.execute(actor, { kind: 'start', ...input }); + } + + @Get('state') + state( + @CurrentCultivator() actor: ActiveCultivatorRef, + @FirstQuery(new ZodPipe(StateSchema)) query: z.infer, + ) { + return this.dungeon.state(actor.cultivatorId, query.runId); + } + + @Post('action') + @HttpCode(200) + @UseFilters(DungeonActionErrors) + action( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(DungeonActionRequestSchema)) + input: z.infer, + ) { + return this.dungeon.execute(actor, { kind: 'action', ...input }); + } + + @Post('recover') + @HttpCode(200) + @UseFilters(DungeonRecoverErrors) + recover( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(RecoverSchema)) input: z.infer, + ) { + return this.dungeon.execute(actor, { kind: 'recover', ...input }); + } + + @Post('quit') + @HttpCode(200) + @UseFilters(DungeonQuitErrors) + quit( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(DungeonFlowRequestSchema)) + input: z.infer, + ) { + return this.dungeon.execute(actor, { kind: 'quit', ...input }); + } + + @Get('history') + history( + @CurrentCultivator() actor: ActiveCultivatorRef, + @FirstQuery('page') page?: string, + @FirstQuery('pageSize') pageSize?: string, + ) { + return this.dungeon.history(actor.cultivatorId, page, pageSize); + } + + @Get('limit') + limit(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.dungeon.limit(actor.cultivatorId); + } + + @Post('looting/continue') + @HttpCode(200) + @UseFilters(DungeonContinueErrors) + continue( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(DungeonFlowRequestSchema)) + input: z.infer, + ) { + return this.dungeon.execute(actor, { kind: 'looting-continue', ...input }); + } + + @Post('looting/escape') + @HttpCode(200) + @UseFilters(DungeonEscapeErrors) + escape( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(DungeonFlowRequestSchema)) + input: z.infer, + ) { + return this.dungeon.execute(actor, { kind: 'looting-escape', ...input }); + } +} diff --git a/apps/api/src/dungeon/dungeon.module.ts b/apps/api/src/dungeon/dungeon.module.ts new file mode 100644 index 00000000..35a272a6 --- /dev/null +++ b/apps/api/src/dungeon/dungeon.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { DungeonBattleController } from './dungeon-battle.controller'; +import { DungeonController } from './dungeon.controller'; +import { DungeonService } from './dungeon.service'; + +@Module({ + controllers: [DungeonController, DungeonBattleController], + providers: [DungeonService], +}) +export class DungeonModule {} diff --git a/apps/api/src/dungeon/dungeon.service.ts b/apps/api/src/dungeon/dungeon.service.ts new file mode 100644 index 00000000..b917b788 --- /dev/null +++ b/apps/api/src/dungeon/dungeon.service.ts @@ -0,0 +1,122 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { getExecutor } from '@server/lib/drizzle/db'; +import { dungeonHistories } from '@server/lib/drizzle/schema'; +import { + changeDungeonBattle, + getDungeonBattle, +} from '@server/lib/dungeon/combatV6'; +import { + checkDungeonLimit, + getDungeonLimitConfig, +} from '@server/lib/dungeon/dungeonLimiter'; +import { + executeDungeonCommand, + readDungeonState, +} from '@server/lib/services/DungeonApplicationService'; +import type { CombatV6TrainingCommandRequestSchema } from '@daoyou/shared/contracts/combatV6'; +import { desc, eq, sql } from 'drizzle-orm'; +import type { z } from 'zod'; + +@Injectable() +export class DungeonService { + execute( + actor: ActiveCultivatorRef, + command: Parameters[0]['command'], + ) { + return executeDungeonCommand({ + userId: actor.userId, + cultivatorId: actor.cultivatorId, + command, + }); + } + + async state(owner: string, runId?: string) { + return { state: await readDungeonState(owner, runId) }; + } + + async history(owner: string, pageQuery?: string, pageSizeQuery?: string) { + const page = Math.max(1, parseInt(pageQuery || '1', 10)); + const pageSize = Math.min( + 50, + Math.max(1, parseInt(pageSizeQuery || '10', 10)), + ); + const offset = (page - 1) * pageSize; + const countResult = await getExecutor() + .select({ count: sql`count(*)` }) + .from(dungeonHistories) + .where(eq(dungeonHistories.cultivatorId, owner)); + const total = Number(countResult[0]?.count || 0); + const records = await getExecutor() + .select({ + id: dungeonHistories.id, + theme: dungeonHistories.theme, + result: dungeonHistories.result, + log: dungeonHistories.log, + realGains: dungeonHistories.realGains, + createdAt: dungeonHistories.createdAt, + }) + .from(dungeonHistories) + .where(eq(dungeonHistories.cultivatorId, owner)) + .orderBy(desc(dungeonHistories.createdAt)) + .limit(pageSize) + .offset(offset); + return { + success: true, + data: { + records, + pagination: { + page, + pageSize, + total, + totalPages: Math.ceil(total / pageSize), + }, + }, + }; + } + + async limit(owner: string) { + const limit = await checkDungeonLimit(owner); + return { + success: true, + data: { ...limit, dailyLimit: getDungeonLimitConfig().dailyLimit }, + }; + } + + async current(owner: string) { + return { success: true, data: await getDungeonBattle(owner) }; + } + + async read(owner: string, id: string, after: number) { + const data = await getDungeonBattle(owner, id, after); + if (!data) throw new HttpException({ error: '战斗不存在' }, 404); + return { success: true, data }; + } + + async submit( + actor: ActiveCultivatorRef, + id: string, + unitId: string, + input: z.infer, + ) { + return { + success: true, + data: await changeDungeonBattle(actor, id, input.expectedRevision, { + unitId, + commands: input.commands, + }), + }; + } + + async resolve( + actor: ActiveCultivatorRef, + id: string, + revision: number, + round?: number, + ) { + return { + success: true, + data: await changeDungeonBattle(actor, id, revision, undefined, round), + }; + } +} diff --git a/apps/api/src/enemies/enemies.controller.ts b/apps/api/src/enemies/enemies.controller.ts new file mode 100644 index 00000000..ab50dac2 --- /dev/null +++ b/apps/api/src/enemies/enemies.controller.ts @@ -0,0 +1,33 @@ +import { getRuntimeEnvironment } from '@server/lib/config/environment'; +import { Controller, Get, Inject, Param, UseFilters } from '@nestjs/common'; +import type { AuthUser } from '@server/lib/auth/types'; +import { Access, CurrentUser } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { EnemiesService } from './enemies.service'; + +const EnemyErrors = apiErrorFilter((error) => { + console.error('获取敌人数据 API 错误:', error); + return Response.json( + { + error: + getRuntimeEnvironment().NODE_ENV === 'development' && error instanceof Error + ? error.message + : '获取敌人数据失败,请稍后重试', + }, + { status: 500 }, + ); +}); + +@Controller('api/enemies') +@Access('user') +@UseFilters(EnemyErrors) +export class EnemiesController { + constructor( + @Inject(EnemiesService) private readonly enemies: EnemiesService, + ) {} + + @Get(':id') + read(@CurrentUser() user: AuthUser, @Param('id') id: string) { + return this.enemies.read(user.id, id); + } +} diff --git a/apps/api/src/enemies/enemies.module.ts b/apps/api/src/enemies/enemies.module.ts new file mode 100644 index 00000000..126bc099 --- /dev/null +++ b/apps/api/src/enemies/enemies.module.ts @@ -0,0 +1,6 @@ +import { Module } from '@nestjs/common'; +import { EnemiesController } from './enemies.controller'; +import { EnemiesService } from './enemies.service'; + +@Module({ controllers: [EnemiesController], providers: [EnemiesService] }) +export class EnemiesModule {} diff --git a/src/server/routes/api/enemies.router.ts b/apps/api/src/enemies/enemies.service.ts similarity index 64% rename from src/server/routes/api/enemies.router.ts rename to apps/api/src/enemies/enemies.service.ts index 2c22e648..81c9e5ca 100644 --- a/src/server/routes/api/enemies.router.ts +++ b/apps/api/src/enemies/enemies.service.ts @@ -1,24 +1,12 @@ +import { HttpException, Injectable } from '@nestjs/common'; import { getExecutor } from '@server/lib/drizzle/db'; import { cultivators, spiritualRoots } from '@server/lib/drizzle/schema'; -import { requireUser } from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; import { and, eq } from 'drizzle-orm'; -import { Hono } from 'hono'; -const router = new Hono(); - -router.get('/:id', requireUser(), async (c) => { - const user = c.get('user'); - if (!user) { - return c.json({ error: '未授权访问' }, 401); - } - - const id = c.req.param('id'); - if (!id) { - return c.json({ error: '请提供有效的敌人ID' }, 400); - } - - try { +@Injectable() +export class EnemiesService { + async read(userId: string, id: string) { + if (!id) throw new HttpException({ error: '请提供有效的敌人ID' }, 400); const q = getExecutor(); const [enemy] = await q .select({ @@ -38,13 +26,13 @@ router.get('/:id', requireUser(), async (c) => { .where( and( eq(cultivators.id, id), - eq(cultivators.userId, user.id), + eq(cultivators.userId, userId), eq(cultivators.status, 'active'), ), ) .limit(1); if (!enemy) { - return c.json({ error: '敌人角色不存在' }, 404); + throw new HttpException({ error: '敌人角色不存在' }, 404); } const roots = await q .select({ @@ -55,7 +43,7 @@ router.get('/:id', requireUser(), async (c) => { }) .from(spiritualRoots) .where(eq(spiritualRoots.cultivatorId, id)); - return c.json({ + return { success: true, data: { id: enemy.id, @@ -78,18 +66,6 @@ router.get('/:id', requireUser(), async (c) => { 6, ), }, - }); - } catch (error) { - console.error('获取敌人数据 API 错误:', error); - const errorMessage = - process.env.NODE_ENV === 'development' - ? error instanceof Error - ? error.message - : '获取敌人数据失败,请稍后重试' - : '获取敌人数据失败,请稍后重试'; - - return c.json({ error: errorMessage }, 500); + }; } -}); - -export default router; +} diff --git a/apps/api/src/enlightenment/enlightenment.controller.ts b/apps/api/src/enlightenment/enlightenment.controller.ts new file mode 100644 index 00000000..ac54ca83 --- /dev/null +++ b/apps/api/src/enlightenment/enlightenment.controller.ts @@ -0,0 +1,77 @@ +import { + Controller, + Get, + Header, + HttpCode, + Inject, + Post, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import { PlayerCommandIdempotencyError } from '@server/lib/services/CommandExecutors'; +import { InventoryError } from '@server/lib/services/InventoryService'; +import { QiServiceError } from '@server/lib/services/QiService'; +import { + EnlightenmentRequestSchema, + type EnlightenmentRequest, +} from '@daoyou/shared/contracts/enlightenment'; +import { InventoryRuleError } from '@daoyou/shared/inventory'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { EnlightenmentService } from './enlightenment.service'; + +const EnlightenmentErrors = apiErrorFilter((error) => { + const lock = redisLockErrorResponse(error); + if (lock) return lock; + if (error instanceof z.ZodError) + return Response.json( + { success: false, error: '典籍参数无效' }, + { status: 400 }, + ); + if ( + error instanceof InventoryError || + error instanceof InventoryRuleError || + error instanceof QiServiceError || + error instanceof PlayerCommandIdempotencyError + ) + return Response.json( + { success: false, code: 'ENLIGHTENMENT_REJECTED', error: error.message }, + { status: 409 }, + ); + console.error('[enlightenment] request failed', error); + return Response.json( + { success: false, error: '参悟结果暂未确认,请重试核对本次结果' }, + { status: 500 }, + ); +}); + +@Controller('api/combat-v6/enlightenment') +@Access('active') +@UseFilters(EnlightenmentErrors) +export class EnlightenmentController { + constructor( + @Inject(EnlightenmentService) + private readonly service: EnlightenmentService, + ) {} + + @Get() + @Header('Cache-Control', 'no-store') + read(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.service.read(actor); + } + + @Post() + @HttpCode(200) + @Header('Cache-Control', 'no-store') + mutate( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody({ fallback: undefined }, new ZodPipe(EnlightenmentRequestSchema)) + input: EnlightenmentRequest, + ) { + return this.service.mutate(actor, input); + } +} diff --git a/apps/api/src/enlightenment/enlightenment.module.ts b/apps/api/src/enlightenment/enlightenment.module.ts new file mode 100644 index 00000000..20e00e78 --- /dev/null +++ b/apps/api/src/enlightenment/enlightenment.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { EnlightenmentController } from './enlightenment.controller'; +import { EnlightenmentService } from './enlightenment.service'; + +@Module({ + controllers: [EnlightenmentController], + providers: [EnlightenmentService], +}) +export class EnlightenmentModule {} diff --git a/apps/api/src/enlightenment/enlightenment.service.ts b/apps/api/src/enlightenment/enlightenment.service.ts new file mode 100644 index 00000000..eaf29c60 --- /dev/null +++ b/apps/api/src/enlightenment/enlightenment.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { + enlightenManual, + readEnlightenment, +} from '@server/lib/services/EnlightenmentService'; +import type { EnlightenmentRequest } from '@daoyou/shared/contracts/enlightenment'; + +@Injectable() +export class EnlightenmentService { + async read(actor: ActiveCultivatorRef) { + return { success: true, data: await readEnlightenment(actor) }; + } + + async mutate(actor: ActiveCultivatorRef, input: EnlightenmentRequest) { + return { success: true, ...(await enlightenManual(actor, input)) }; + } +} diff --git a/apps/api/src/feedback/feedback.controller.ts b/apps/api/src/feedback/feedback.controller.ts new file mode 100644 index 00000000..335488c1 --- /dev/null +++ b/apps/api/src/feedback/feedback.controller.ts @@ -0,0 +1,40 @@ +import { Controller, HttpCode, Inject, Post, UseFilters } from '@nestjs/common'; +import type { AuthUser } from '@server/lib/auth/types'; +import { + FeedbackCreateRequestSchema, + type FeedbackCreateRequest, +} from '@daoyou/shared/contracts/feedback'; +import { ZodError } from 'zod'; +import { Access, CurrentUser } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { FeedbackService } from './feedback.service'; + +const FeedbackErrors = apiErrorFilter((error) => { + if (error instanceof ZodError) return undefined; + console.error('Create feedback error:', error); + return Response.json( + { success: false, error: '提交反馈失败,请稍后重试' }, + { status: 500 }, + ); +}); + +@Controller('api/feedback') +@Access('user') +export class FeedbackController { + constructor( + @Inject(FeedbackService) private readonly feedback: FeedbackService, + ) {} + + @Post() + @HttpCode(200) + @UseFilters(FeedbackErrors) + create( + @CurrentUser() user: AuthUser, + @JsonBody({ fallback: undefined }, new ZodPipe(FeedbackCreateRequestSchema)) + input: FeedbackCreateRequest, + ) { + return this.feedback.create(user.id, input); + } +} diff --git a/apps/api/src/feedback/feedback.module.ts b/apps/api/src/feedback/feedback.module.ts new file mode 100644 index 00000000..85326792 --- /dev/null +++ b/apps/api/src/feedback/feedback.module.ts @@ -0,0 +1,6 @@ +import { Module } from '@nestjs/common'; +import { FeedbackController } from './feedback.controller'; +import { FeedbackService } from './feedback.service'; + +@Module({ controllers: [FeedbackController], providers: [FeedbackService] }) +export class FeedbackModule {} diff --git a/apps/api/src/feedback/feedback.service.ts b/apps/api/src/feedback/feedback.service.ts new file mode 100644 index 00000000..31542924 --- /dev/null +++ b/apps/api/src/feedback/feedback.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@nestjs/common'; +import { getExecutor } from '@server/lib/drizzle/db'; +import { cultivators } from '@server/lib/drizzle/schema'; +import { createFeedback } from '@server/lib/repositories/feedbackRepository'; +import type { FeedbackCreateRequest } from '@daoyou/shared/contracts/feedback'; +import { and, eq } from 'drizzle-orm'; + +@Injectable() +export class FeedbackService { + async create(userId: string, input: FeedbackCreateRequest) { + const active = await getExecutor().query.cultivators.findFirst({ + where: and( + eq(cultivators.userId, userId), + eq(cultivators.status, 'active'), + ), + }); + const feedback = await createFeedback({ + userId, + cultivatorId: active?.id ?? null, + ...input, + }); + return { success: true, data: { feedbackId: feedback.id } }; + } +} diff --git a/apps/api/src/forging/forging.controller.ts b/apps/api/src/forging/forging.controller.ts new file mode 100644 index 00000000..a8522cd1 --- /dev/null +++ b/apps/api/src/forging/forging.controller.ts @@ -0,0 +1,111 @@ +import { + Controller, + Get, + Header, + HttpCode, + Inject, + Post, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import { PlayerCommandIdempotencyError } from '@server/lib/services/CommandExecutors'; +import { InventoryError } from '@server/lib/services/InventoryService'; +import { QiServiceError } from '@server/lib/services/QiService'; +import { + ForgeRequestSchema, + VaultQuerySchema, + WithdrawMaterialSchema, + WithdrawVaultPageSchema, +} from '@daoyou/shared/contracts/forging'; +import { InventoryRuleError } from '@daoyou/shared/inventory'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { ForgingService } from './forging.service'; + +const ForgingErrors = apiErrorFilter((error) => { + const lock = redisLockErrorResponse(error); + if (lock) return lock; + if (error instanceof z.ZodError) + return Response.json( + { success: false, error: '请求参数或材料数据无效' }, + { status: 400 }, + ); + if ( + error instanceof InventoryError || + error instanceof InventoryRuleError || + error instanceof QiServiceError || + error instanceof PlayerCommandIdempotencyError + ) + return Response.json( + { success: false, error: error.message }, + { status: 409 }, + ); + console.error('[forging] request failed', error); + return Response.json( + { success: false, error: '请求未完成,请核对物品状态后重试' }, + { status: 500 }, + ); +}); + +@Controller('api/combat-v6/forging') +@Access('active') +@UseFilters(ForgingErrors) +export class ForgingController { + constructor( + @Inject(ForgingService) private readonly forging: ForgingService, + ) {} + + @Get() + @Header('Cache-Control', 'no-store') + read(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.forging.read(actor.cultivatorId); + } + + @Post() + @HttpCode(200) + @Header('Cache-Control', 'no-store') + forge( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody({ fallback: undefined }, new ZodPipe(ForgeRequestSchema)) + input: z.infer, + ) { + return this.forging.forge(actor, input); + } + + @Get('vault') + @Header('Cache-Control', 'no-store') + vault( + @CurrentCultivator() actor: ActiveCultivatorRef, + @FirstQuery(new ZodPipe(VaultQuerySchema)) + query: z.infer, + ) { + return this.forging.vault(actor.cultivatorId, query); + } + + @Post('vault/withdraw') + @HttpCode(200) + @Header('Cache-Control', 'no-store') + withdraw( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody({ fallback: undefined }, new ZodPipe(WithdrawMaterialSchema)) + input: z.infer, + ) { + return this.forging.withdraw(actor.cultivatorId, input); + } + + @Post('vault/withdraw-page') + @HttpCode(200) + @Header('Cache-Control', 'no-store') + withdrawPage( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody({ fallback: undefined }, new ZodPipe(WithdrawVaultPageSchema)) + input: z.infer, + ) { + return this.forging.withdrawPage(actor.cultivatorId, input); + } +} diff --git a/apps/api/src/forging/forging.module.ts b/apps/api/src/forging/forging.module.ts new file mode 100644 index 00000000..361ceecc --- /dev/null +++ b/apps/api/src/forging/forging.module.ts @@ -0,0 +1,6 @@ +import { Module } from '@nestjs/common'; +import { ForgingController } from './forging.controller'; +import { ForgingService } from './forging.service'; + +@Module({ controllers: [ForgingController], providers: [ForgingService] }) +export class ForgingModule {} diff --git a/apps/api/src/forging/forging.service.ts b/apps/api/src/forging/forging.service.ts new file mode 100644 index 00000000..3f38a04e --- /dev/null +++ b/apps/api/src/forging/forging.service.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { + forgeEquipment, + readForge, + readVault, + withdrawMaterial, + withdrawVaultPage, +} from '@server/lib/services/ForgingService'; +import type { + ForgeRequest, + VaultQuerySchema, + WithdrawMaterialSchema, + WithdrawVaultPageSchema, +} from '@daoyou/shared/contracts/forging'; +import type { z } from 'zod'; + +@Injectable() +export class ForgingService { + async read(owner: string) { + return { success: true, data: await readForge(owner) }; + } + + async forge(actor: ActiveCultivatorRef, input: ForgeRequest) { + return { + success: true, + ...(await forgeEquipment(actor.cultivatorId, input, actor.userId)), + }; + } + + async vault(owner: string, query: z.infer) { + return { success: true, data: await readVault(owner, query) }; + } + + async withdraw(owner: string, input: z.infer) { + return { success: true, ...(await withdrawMaterial(owner, input)) }; + } + + async withdrawPage( + owner: string, + input: z.infer, + ) { + return { success: true, ...(await withdrawVaultPage(owner, input)) }; + } +} diff --git a/apps/api/src/genesis/genesis.controller.ts b/apps/api/src/genesis/genesis.controller.ts new file mode 100644 index 00000000..2350947e --- /dev/null +++ b/apps/api/src/genesis/genesis.controller.ts @@ -0,0 +1,85 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Post, + Req, + UseFilters, +} from '@nestjs/common'; +import type { AuthUser } from '@server/lib/auth/types'; +import { getRequestIp } from '@server/lib/http/requestIp'; +import { CultivatorCreationCommandError } from '@server/lib/services/CultivatorCreationApplicationService'; +import { fromNodeHeaders } from 'better-auth/node'; +import type { Request } from 'express'; +import type { z } from 'zod'; +import { Access, CurrentUser } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { GenesisService, SaveCharacterSchema } from './genesis.service'; + +const SaveErrors = apiErrorFilter((error) => + error instanceof CultivatorCreationCommandError + ? Response.json({ error: error.message }, { status: 400 }) + : undefined, +); + +@Controller('api/generate-character') +@Access('user') +export class GenerateCharacterController { + constructor( + @Inject(GenesisService) private readonly genesis: GenesisService, + ) {} + @Get('quota') + quota(@CurrentUser() user: AuthUser, @Req() request: Request) { + return this.genesis.quota( + user, + getRequestIp(fromNodeHeaders(request.headers)), + ); + } + @Post() + @HttpCode(200) + generate( + @CurrentUser() user: AuthUser, + @Req() request: Request, + @JsonBody() input: unknown, + ) { + return this.genesis.generate( + user, + getRequestIp(fromNodeHeaders(request.headers)), + input, + ); + } +} + +@Controller('api/generate-fates') +@Access('user') +export class GenerateFatesController { + constructor( + @Inject(GenesisService) private readonly genesis: GenesisService, + ) {} + @Post() + @HttpCode(200) + generate(@JsonBody() input: unknown) { + return this.genesis.fates(input); + } +} + +@Controller('api/save-character') +@Access('user') +export class SaveCharacterController { + constructor( + @Inject(GenesisService) private readonly genesis: GenesisService, + ) {} + @Post() + @HttpCode(200) + @UseFilters(SaveErrors) + save( + @CurrentUser() user: AuthUser, + @JsonBody(new ZodPipe(SaveCharacterSchema)) + input: z.infer, + ) { + return this.genesis.save(user, input); + } +} diff --git a/apps/api/src/genesis/genesis.module.ts b/apps/api/src/genesis/genesis.module.ts new file mode 100644 index 00000000..a71c2722 --- /dev/null +++ b/apps/api/src/genesis/genesis.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { + GenerateCharacterController, + GenerateFatesController, + SaveCharacterController, +} from './genesis.controller'; +import { GenesisService } from './genesis.service'; +@Module({ + controllers: [ + GenerateCharacterController, + GenerateFatesController, + SaveCharacterController, + ], + providers: [GenesisService], +}) +export class GenesisModule {} diff --git a/apps/api/src/genesis/genesis.service.ts b/apps/api/src/genesis/genesis.service.ts new file mode 100644 index 00000000..14d2f6cb --- /dev/null +++ b/apps/api/src/genesis/genesis.service.ts @@ -0,0 +1,156 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import type { AuthUser } from '@server/lib/auth/types'; +import { + consumeCharacterGenerationQuota, + getCharacterGenerationQuota, +} from '@server/lib/redis/characterGenerationLimiter'; +import { + checkAndIncrementReroll, + getTempCharacter, + getTempFates, + saveTempCharacter, + saveTempFates, +} from '@server/lib/repositories/redisCultivatorRepository'; +import { createCultivatorFromTemp } from '@server/lib/services/CultivatorCreationApplicationService'; +import { FATE_REROLL_LIMIT } from '@server/lib/services/FateConfig'; +import { FateEngine } from '@server/lib/services/FateEngine'; +import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; +import { generateCultivatorFromAI } from '@server/utils/characterEngine'; +import { normalizeFreeformLlmInput } from '@server/utils/llmPayload'; +import { + CHARACTER_GENERATION_LIMIT_REACHED_CODE, + type CharacterGenerationQuota, + type CharacterGenerationQuotaResponse, + type GenerateCharacterResponse, +} from '@daoyou/shared/contracts/character-generation'; +import { z } from 'zod'; + +const GenerateCharacterSchema = z.object({ userInput: z.string() }); +const GenerateFatesSchema = z.object({ tempId: z.string().min(1) }); +export const SaveCharacterSchema = z.object({ + tempCultivatorId: z.string(), + selectedFateIndices: z.array(z.number()).length(3), +}); +const MIN_PROMPT_LENGTH = 2; +const MAX_PROMPT_LENGTH = 200; +function quotaExceededMessage(quota: CharacterGenerationQuota): string { + switch (quota.limitedBy) { + case 'email': + return '该邮箱今日角色推演次数已用尽(每日限 6 次),请明日再试。'; + case 'ip': + return '当前网络今日角色推演次数已用尽(每日限 6 次),请明日再试。'; + case 'both': + return '该邮箱与当前网络今日角色推演次数均已用尽,请明日再试。'; + default: + return '今日角色推演次数已用尽,请明日再试。'; + } +} + +@Injectable() +export class GenesisService { + async quota( + user: AuthUser, + ip: string | undefined, + ): Promise { + const quota = await getCharacterGenerationQuota({ email: user.email, ip }); + return { success: true, data: { quota } }; + } + + async generate( + user: AuthUser, + ip: string | undefined, + input: unknown, + ): Promise { + const parsed = GenerateCharacterSchema.safeParse(input); + if (!parsed.success) + throw new HttpException( + { success: false, error: '请求参数格式错误,请重新输入角色描述。' }, + 400, + ); + const userInput = normalizeFreeformLlmInput(parsed.data.userInput); + const promptLength = Array.from(userInput).length; + if (promptLength < MIN_PROMPT_LENGTH) + throw new HttpException( + { + success: false, + error: `角色描述至少需要 ${MIN_PROMPT_LENGTH} 个字。`, + }, + 400, + ); + if (promptLength > MAX_PROMPT_LENGTH) + throw new HttpException( + { + success: false, + error: `角色描述过长(当前 ${promptLength} 字,最多 ${MAX_PROMPT_LENGTH} 字)。`, + code: 'PROMPT_TOO_LONG', + details: { + currentLength: promptLength, + maxLength: MAX_PROMPT_LENGTH, + }, + }, + 422, + ); + const quotaResult = await consumeCharacterGenerationQuota({ + email: user.email, + ip, + }); + if (!quotaResult.allowed) + throw new HttpException( + { + success: false, + code: CHARACTER_GENERATION_LIMIT_REACHED_CODE, + error: quotaExceededMessage(quotaResult.quota), + quota: quotaResult.quota, + }, + 429, + ); + const { cultivator } = await generateCultivatorFromAI(userInput); + const tempCultivatorId = await saveTempCharacter(cultivator); + return { + success: true, + data: { cultivator, tempCultivatorId, quota: quotaResult.quota }, + }; + } + + async fates(input: unknown) { + const parsed = GenerateFatesSchema.safeParse(input); + if (!parsed.success) + throw new HttpException( + { success: false, error: '请求参数格式错误' }, + 400, + ); + const { tempId } = parsed.data; + const cultivator = await getTempCharacter(tempId); + if (!cultivator) + throw new HttpException( + { success: false, error: '角色推演已过期,请重新生成。' }, + 404, + ); + const previousFates = await getTempFates(tempId); + let remainingRerolls = FATE_REROLL_LIMIT; + if (previousFates && previousFates.length > 0) { + const rerollCheck = await checkAndIncrementReroll( + tempId, + FATE_REROLL_LIMIT, + ); + if (!rerollCheck.allowed) + throw new HttpException( + { + success: false, + error: `逆天改命次数已尽(最多 ${FATE_REROLL_LIMIT} 次)`, + }, + 400, + ); + remainingRerolls = rerollCheck.remaining; + } + const fates = await FateEngine.generateCandidatePool(); + await saveTempFates(tempId, fates); + return { success: true, data: { fates, remainingRerolls } }; + } + + async save(user: AuthUser, input: z.infer) { + return toPlayerStateMutationResponse( + await createCultivatorFromTemp({ userId: user.id, ...input }), + ); + } +} diff --git a/apps/api/src/health/health.controller.ts b/apps/api/src/health/health.controller.ts new file mode 100644 index 00000000..d898032f --- /dev/null +++ b/apps/api/src/health/health.controller.ts @@ -0,0 +1,50 @@ +import { Controller, Get, HttpException, Inject } from '@nestjs/common'; +import { getMessageInfrastructureHealthStatus } from '@server/lib/mq/domainEventRegistry'; +import { getNatsHealthStatus } from '@server/lib/nats'; +import { getRedisHealthStatus } from '@server/lib/redis'; +import { Access } from '../auth/access'; +import { DatabaseService } from '../database/database.service'; + +@Controller('api/health-check') +@Access('public') +export class HealthController { + constructor( + @Inject(DatabaseService) private readonly database: DatabaseService, + ) {} + + @Get() + async health() { + const [database, redis, nats] = await Promise.all([ + this.database.healthStatus(), + getRedisHealthStatus(), + getNatsHealthStatus(), + ]); + const messaging = getMessageInfrastructureHealthStatus(); + if ( + database === 'down' || + redis === 'down' || + nats === 'down' || + messaging === 'down' + ) { + throw new HttpException( + { + success: false, + error: + database === 'down' + ? 'Database unavailable' + : redis === 'down' + ? 'Redis unavailable' + : nats === 'down' + ? 'NATS unavailable' + : 'Message infrastructure unavailable', + database, + redis, + nats, + messaging, + }, + 503, + ); + } + return { success: true, message: 'OK', database, redis, nats, messaging }; + } +} diff --git a/apps/api/src/health/health.module.ts b/apps/api/src/health/health.module.ts new file mode 100644 index 00000000..d2723cd5 --- /dev/null +++ b/apps/api/src/health/health.module.ts @@ -0,0 +1,6 @@ +import { Module } from '@nestjs/common'; +import { DatabaseModule } from '../database/database.module'; +import { HealthController } from './health.controller'; + +@Module({ imports: [DatabaseModule], controllers: [HealthController] }) +export class HealthModule {} diff --git a/apps/api/src/http/api-exception.filter.ts b/apps/api/src/http/api-exception.filter.ts new file mode 100644 index 00000000..d3047c18 --- /dev/null +++ b/apps/api/src/http/api-exception.filter.ts @@ -0,0 +1,68 @@ +import { + Catch, + HttpException, + NotFoundException, + type ArgumentsHost, + type ExceptionFilter, +} from '@nestjs/common'; +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import type { Request, Response } from 'express'; + +@Catch() +export class ApiExceptionFilter implements ExceptionFilter { + async catch(error: unknown, host: ArgumentsHost): Promise { + const response = host.switchToHttp().getResponse(); + const request = host.switchToHttp().getRequest(); + if (response.headersSent) { + response.end(); + return; + } + const lock = redisLockErrorResponse(error); + if (lock) { + lock.headers.forEach((value, name) => response.setHeader(name, value)); + response.status(lock.status).send(await lock.text()); + return; + } + if (error instanceof Error && 'type' in error) { + if (error.type === 'entity.too.large') { + response.status(413).json({ success: false, error: '请求内容过大' }); + return; + } + } + if (error instanceof HttpException) { + const originalBody = error.getResponse(); + const message = + typeof originalBody === 'object' + ? (originalBody as { message?: unknown }).message + : originalBody; + if ( + error instanceof NotFoundException && + typeof message === 'string' && + message.startsWith('Cannot ') + ) { + const path = request.path; + if (!/^\/(api|internal)(\/|$)/.test(path)) { + response + .status(302) + .setHeader('Location', 'https://client.daoyou.org'); + response.end(); + return; + } + response.status(404).json({ success: false, error: '接口不存在' }); + return; + } + const body = error.getResponse(); + response + .status(error.getStatus()) + .json( + typeof body === 'string' ? { success: false, error: body } : body, + ); + return; + } + console.error('Unhandled API error:', error); + response.status(500).json({ + success: false, + error: '服务器内部错误', + }); + } +} diff --git a/apps/api/src/http/chat-input.pipe.ts b/apps/api/src/http/chat-input.pipe.ts new file mode 100644 index 00000000..bf13f5d8 --- /dev/null +++ b/apps/api/src/http/chat-input.pipe.ts @@ -0,0 +1,18 @@ +import { HttpException, type PipeTransform } from '@nestjs/common'; + +export class RejectRetiredBattleSharePipe implements PipeTransform { + transform(value: unknown) { + if ( + typeof value === 'object' && + value !== null && + 'messageType' in value && + value.messageType === 'battle_showcase' + ) { + throw new HttpException( + { success: false, error: '旧版战报分享已停用' }, + 410, + ); + } + return value; + } +} diff --git a/apps/api/src/http/configure-http.ts b/apps/api/src/http/configure-http.ts new file mode 100644 index 00000000..1d56b467 --- /dev/null +++ b/apps/api/src/http/configure-http.ts @@ -0,0 +1,151 @@ +import type { NestExpressApplication } from '@nestjs/platform-express'; +import { handleAuthRequest } from '@server/lib/auth/handler'; +import { runWithContext } from '@server/lib/http/context'; +import { apiCorsOptions } from '@server/lib/http/cors'; +import { isAllowedWriteOrigin } from '@server/lib/http/originPolicy'; +import { getRequestIp } from '@server/lib/http/requestIp'; +import { checkApiIpRateLimit } from '@server/lib/redis/apiIpRateLimiter'; +import { LlmByokConfigSchema } from '@daoyou/shared/config/llm'; +import { fromNodeHeaders, toNodeHandler } from 'better-auth/node'; +import cors from 'cors'; +import type { NextFunction, Response } from 'express'; +import { readRequestBody } from './json-body'; +import type { GameRequest } from './request'; +import { RequestWorkService } from './request-work.service'; + +export function configureHttp(app: NestExpressApplication): void { + const work = app.get(RequestWorkService); + app.disable('x-powered-by'); + // FirstQuery owns business query decoding, including malformed escapes. + app.set('query parser', false); + app.use((request: GameRequest, response: Response, next: NextFunction) => { + request.gameContext = {}; + const startedAt = performance.now(); + response.once('finish', () => { + if ( + !/^\/(api|internal)\//.test(request.path) || + request.path === '/api/health-check' + ) + return; + const duration = Math.round(performance.now() - startedAt); + if (response.statusCode >= 400 || duration >= 1_000) { + console.info( + `[HTTP] ${request.method.padEnd(7)} ${request.path} → ${response.statusCode} (${duration}ms)`, + ); + } + }); + runWithContext(request.gameContext, next); + }); + app.use( + '/api', + cors({ + origin: (origin, callback) => + callback(null, apiCorsOptions.origin(origin ?? '') || false), + allowedHeaders: apiCorsOptions.allowHeaders, + methods: apiCorsOptions.allowMethods, + credentials: apiCorsOptions.credentials, + maxAge: apiCorsOptions.maxAge, + optionsSuccessStatus: 204, + }), + ); + app.use((request: GameRequest, response: Response, next: NextFunction) => { + if ( + request.path.startsWith('/api/') && + ['POST', 'PUT', 'PATCH', 'DELETE'].includes(request.method) + ) { + const origin = request.get('origin'); + const requiresOrigin = + request.get('cookie') !== undefined || + request.get('sec-fetch-site') !== undefined; + if ((!origin && requiresOrigin) || !isAllowedWriteOrigin(origin)) { + response + .status(403) + .json({ success: false, error: 'Forbidden origin' }); + return; + } + } + const provider = request.get('x-llm-provider'); + const apiKey = request.get('x-llm-api-key'); + const model = request.get('x-llm-model'); + if (provider !== undefined || apiKey !== undefined || model !== undefined) { + const result = LlmByokConfigSchema.safeParse({ provider, apiKey, model }); + if (!result.success) { + response + .status(400) + .json({ success: false, error: 'LLM 配置不完整或格式无效' }); + return; + } + request.gameContext.llmConfig = result.data; + } + next(); + }); + app.use( + async (request: GameRequest, response: Response, next: NextFunction) => { + if ( + !request.path.startsWith('/api/') || + request.path === '/api/health-check' + ) + return next(); + const ip = getRequestIp(fromNodeHeaders(request.headers)); + if (!ip) return next(); + try { + const result = await checkApiIpRateLimit(ip); + response.setHeader('X-RateLimit-Limit', result.limit); + response.setHeader('X-RateLimit-Remaining', result.remaining); + response.setHeader( + 'X-RateLimit-Reset', + Math.ceil(result.resetAt.getTime() / 1000), + ); + if (!result.allowed) { + response.setHeader('Retry-After', result.retryAfterSeconds); + response + .status(429) + .json({ success: false, error: '请求过于频繁,请稍后再试' }); + return; + } + } catch (error) { + console.warn( + '[api-rate-limit] redis check failed; allowing request', + error, + ); + } + next(); + }, + ); + const authHandler = toNodeHandler(handleAuthRequest); + app.use((request: GameRequest, response: Response, next: NextFunction) => { + if (request.path.startsWith('/api/auth/')) { + const done = work.begin(); + return authHandler(request, response).catch(next).finally(done); + } + next(); + }); + // Better Auth owns its body. Business bodies are read by JsonBody after + // guards; only the webhook has a route-specific transport limit. + app.use( + async (request: GameRequest, response: Response, next: NextFunction) => { + if ( + request.method === 'POST' && + request.path === '/api/sponsorship/providers/afdian/webhook' + ) { + try { + await readRequestBody(request, 256 * 1_024); + } catch (error) { + if ( + error instanceof Error && + 'type' in error && + error.type === 'entity.too.large' + ) { + // The legacy app's onError maps the body-limit exception to this contract. + response + .status(500) + .json({ success: false, error: '服务器内部错误' }); + return; + } + return next(error); + } + } + next(); + }, + ); +} diff --git a/apps/api/src/http/error-filter.ts b/apps/api/src/http/error-filter.ts new file mode 100644 index 00000000..2c78d137 --- /dev/null +++ b/apps/api/src/http/error-filter.ts @@ -0,0 +1,38 @@ +import { + Catch, + HttpException, + type ArgumentsHost, + type ExceptionFilter, + type Type, +} from '@nestjs/common'; +import type { Response as ExpressResponse } from 'express'; +import { ApiExceptionFilter } from './api-exception.filter'; + +/** Preserve each API's existing error contract while keeping controllers transport-only. */ +export function apiErrorFilter( + mapError: (error: unknown) => Response | undefined, +): Type { + @Catch() + class ContractExceptionFilter extends ApiExceptionFilter { + override async catch(error: unknown, host: ArgumentsHost): Promise { + // Guard failures and explicit HTTP outcomes already carry their public contract. + if ( + error instanceof HttpException || + (error instanceof Error && + 'type' in error && + error.type === 'entity.too.large') + ) + return super.catch(error, host); + const mapped = mapError(error); + if (!mapped) return super.catch(error, host); + const response = host.switchToHttp().getResponse(); + if (response.headersSent) { + response.end(); + return; + } + mapped.headers.forEach((value, name) => response.setHeader(name, value)); + response.status(mapped.status).send(await mapped.text()); + } + } + return ContractExceptionFilter; +} diff --git a/apps/api/src/http/first-query.ts b/apps/api/src/http/first-query.ts new file mode 100644 index 00000000..860d37ce --- /dev/null +++ b/apps/api/src/http/first-query.ts @@ -0,0 +1,39 @@ +import { createParamDecorator, type ExecutionContext } from '@nestjs/common'; +import type { Request } from 'express'; + +function decodeQuery(value: string): string { + const spaced = value.replace(/\+/g, ' '); + try { + return decodeURIComponent(spaced); + } catch { + // Preserve malformed UTF-8 escapes while decoding separate valid runs. + return spaced.replace(/(?:%[\da-f]{2})+/gi, (run) => { + try { + return decodeURIComponent(run); + } catch { + return run; + } + }); + } +} + +/** Preserve Hono's first-value semantics for query objects and named values. */ +export const FirstQuery = createParamDecorator( + (key: string | undefined, context: ExecutionContext) => { + const request = context.switchToHttp().getRequest(); + const url = request.originalUrl.split('#', 1)[0]; + const start = url.indexOf('?'); + const fields = start === -1 ? [] : url.slice(start + 1).split('&'); + const values: Record = Object.create(null); + for (const field of fields) { + const separator = field.indexOf('='); + const name = separator === -1 ? field : field.slice(0, separator); + const value = separator === -1 ? '' : field.slice(separator + 1); + // Named Hono queries prefer a literal key over earlier encoded aliases. + if (key && !/[%+]/.test(key) && name === key) return decodeQuery(value); + const decodedName = decodeQuery(name); + if (decodedName) values[decodedName] ??= decodeQuery(value); + } + return key ? values[key] : values; + }, +); diff --git a/apps/api/src/http/json-body.ts b/apps/api/src/http/json-body.ts new file mode 100644 index 00000000..39347ded --- /dev/null +++ b/apps/api/src/http/json-body.ts @@ -0,0 +1,64 @@ +import { + createParamDecorator, + type ExecutionContext, + type PipeTransform, + type Type, +} from '@nestjs/common'; +import type { Request } from 'express'; +import getRawBody from 'raw-body'; + +/** Preserve the former Bun server's 128 MiB ceiling without inflating bodies. */ +export async function readRequestBody( + request: Request, + limit = 128 * 1_024 * 1_024, +): Promise { + request.body ??= await getRawBody(request, { + length: request.headers['content-length'], + limit, + }); + return request.body; +} + +export class JsonBodyParseError extends SyntaxError { + constructor(error: SyntaxError) { + super(error.message, { cause: error }); + } +} + +type JsonBodyOptions = { fallback: unknown }; + +const RequestBody = createParamDecorator( + (_data: undefined, context: ExecutionContext): Request => + context.switchToHttp().getRequest(), +); + +class JsonBodyPipe implements PipeTransform> { + constructor(private readonly options?: JsonBodyOptions) {} + + async transform(request: Request): Promise { + const body = await readRequestBody(request); + try { + return JSON.parse(new TextDecoder().decode(body)); + } catch (error) { + if (this.options) return this.options.fallback; + if (error instanceof SyntaxError) throw new JsonBodyParseError(error); + throw error; + } + } +} + +/** Nest awaits pipes, but not a custom parameter factory before its first pipe. */ +export function JsonBody( + optionsOrPipe?: JsonBodyOptions | PipeTransform | Type, + ...pipes: (PipeTransform | Type)[] +): ParameterDecorator { + if (optionsOrPipe && 'fallback' in optionsOrPipe) { + return RequestBody(undefined, new JsonBodyPipe(optionsOrPipe), ...pipes); + } + return RequestBody( + undefined, + new JsonBodyPipe(), + ...(optionsOrPipe ? [optionsOrPipe] : []), + ...pipes, + ); +} diff --git a/apps/api/src/http/not-found.module.ts b/apps/api/src/http/not-found.module.ts new file mode 100644 index 00000000..d15d34cc --- /dev/null +++ b/apps/api/src/http/not-found.module.ts @@ -0,0 +1,56 @@ +import { All, Controller, HttpCode, Module, Req, Res } from '@nestjs/common'; +import type { Request, Response } from 'express'; +import { Access } from '../auth/access'; + +// These legacy routers authorized every method and child path, including misses. +// Keep the fallback module last so concrete feature routes always win. +@Controller() +@Access('active') +class ActiveNotFoundController { + @All([ + 'api/combat-v6{/{*path}}', + 'api/black-market{/{*path}}', + 'api/craft{/{*path}}', + 'api/divination{/{*path}}', + 'api/spirit-field{/{*path}}', + 'api/tower{/{*path}}', + 'api/hunts{/{*path}}', + 'api/artifact-migration{/{*path}}', + 'api/manual-migration{/{*path}}', + ]) + @HttpCode(404) + notFound( + @Req() request: Request, + @Res({ passthrough: true }) response: Response, + ) { + // These headers followed authorization in the former router middleware. + if (/^\/api\/hunts(?:\/|$)/.test(request.path)) { + response.setHeader('Cache-Control', 'private, no-store'); + } else if ( + /^\/api\/combat-v6\/(auto-strategy|manuals|sect|enlightenment|forging|inscriptions)(?:\/|$)/.test( + request.path, + ) + ) { + response.setHeader('Cache-Control', 'no-store'); + } + return { success: false, error: '接口不存在' }; + } +} + +@Controller() +@Access('admin') +class AdminNotFoundController { + @All([ + 'api/admin/system-mails{/{*path}}', + 'api/admin/sponsorship{/{*path}}', + 'api/admin/tower-enemy-sets{/{*path}}', + 'api/admin/manual-migration{/{*path}}', + ]) + @HttpCode(404) + notFound() { + return { success: false, error: '接口不存在' }; + } +} + +@Module({ controllers: [ActiveNotFoundController, AdminNotFoundController] }) +export class NotFoundModule {} diff --git a/apps/api/src/http/request-abort.ts b/apps/api/src/http/request-abort.ts new file mode 100644 index 00000000..5fc181a8 --- /dev/null +++ b/apps/api/src/http/request-abort.ts @@ -0,0 +1,18 @@ +import type { Response } from 'express'; + +/** Keep cancellation attached while an operation is preparing its response. */ +export async function withRequestAbort( + response: Response, + operation: (signal: AbortSignal) => Promise, +): Promise { + const controller = new AbortController(); + const abort = () => controller.abort(); + response.once('close', abort); + if (response.destroyed) abort(); + try { + return await operation(controller.signal); + } finally { + response.removeListener('close', abort); + controller.abort(); + } +} diff --git a/apps/api/src/http/request-context.interceptor.ts b/apps/api/src/http/request-context.interceptor.ts new file mode 100644 index 00000000..93ab5415 --- /dev/null +++ b/apps/api/src/http/request-context.interceptor.ts @@ -0,0 +1,31 @@ +import { + Inject, + Injectable, + type CallHandler, + type ExecutionContext, + type NestInterceptor, +} from '@nestjs/common'; +import { runWithContext } from '@server/lib/http/context'; +import type { Response } from 'express'; +import { Observable, finalize, of } from 'rxjs'; +import type { GameRequest } from './request'; +import { RequestWorkService } from './request-work.service'; + +@Injectable() +export class RequestContextInterceptor implements NestInterceptor { + constructor( + @Inject(RequestWorkService) private readonly work: RequestWorkService, + ) {} + + intercept(context: ExecutionContext, next: CallHandler): Observable { + const request = context.switchToHttp().getRequest(); + const response = context.switchToHttp().getResponse(); + if (response.destroyed) return of(undefined); + return new Observable((subscriber) => + runWithContext(request.gameContext, () => { + const done = this.work.begin(); + return next.handle().pipe(finalize(done)).subscribe(subscriber); + }), + ); + } +} diff --git a/apps/api/src/http/request-work.service.ts b/apps/api/src/http/request-work.service.ts new file mode 100644 index 00000000..86b551bf --- /dev/null +++ b/apps/api/src/http/request-work.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@nestjs/common'; + +/** Keep dependencies alive until handlers finish, including after client aborts. */ +@Injectable() +export class RequestWorkService { + private readonly pending = new Set>(); + + begin(): () => void { + let resolve!: () => void; + const work = new Promise((done) => { + resolve = done; + }); + this.pending.add(work); + return () => { + this.pending.delete(work); + resolve(); + }; + } + + async drain(): Promise { + while (this.pending.size) await Promise.all([...this.pending]); + } +} diff --git a/apps/api/src/http/request.ts b/apps/api/src/http/request.ts new file mode 100644 index 00000000..067577c8 --- /dev/null +++ b/apps/api/src/http/request.ts @@ -0,0 +1,4 @@ +import type { RequestContext } from '@server/lib/http/context'; +import type { Request } from 'express'; + +export type GameRequest = Request & { gameContext: RequestContext }; diff --git a/apps/api/src/http/sse.ts b/apps/api/src/http/sse.ts new file mode 100644 index 00000000..deffd05e --- /dev/null +++ b/apps/api/src/http/sse.ts @@ -0,0 +1,46 @@ +import type { Response } from 'express'; + +type SseEvent = { data: string; event?: string; id?: string; retry?: number }; + +export async function streamSseEvents( + response: Response, + handler: ( + stream: { writeSSE: (event: SseEvent) => Promise }, + isAborted: () => boolean, + signal: AbortSignal, + ) => Promise, +): Promise { + const controller = new AbortController(); + const abort = () => controller.abort(); + response.once('close', abort); + response.status(200).set({ + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + Connection: 'keep-alive', + 'X-Accel-Buffering': 'no', + }); + response.flushHeaders(); + const stream = { + async writeSSE(event: SseEvent): Promise { + controller.signal.throwIfAborted(); + const fields = [ + ...(event.event === undefined ? [] : [`event: ${event.event}`]), + ...(event.id === undefined ? [] : [`id: ${event.id}`]), + ...(event.retry === undefined ? [] : [`retry: ${event.retry}`]), + ...event.data.split(/\r\n|\r|\n/).map((line) => `data: ${line}`), + ]; + await new Promise((resolve, reject) => { + response.write(`${fields.join('\n')}\n\n`, (error) => + error ? reject(error) : resolve(), + ); + }); + }, + }; + try { + await handler(stream, () => controller.signal.aborted, controller.signal); + } finally { + response.removeListener('close', abort); + controller.abort(); + if (!response.destroyed) response.end(); + } +} diff --git a/apps/api/src/http/websocket-request.ts b/apps/api/src/http/websocket-request.ts new file mode 100644 index 00000000..d3c5d8e6 --- /dev/null +++ b/apps/api/src/http/websocket-request.ts @@ -0,0 +1,61 @@ +import { apiCorsOptions } from '@server/lib/http/cors'; +import { getRequestIp } from '@server/lib/http/requestIp'; +import { checkApiIpRateLimit } from '@server/lib/redis/apiIpRateLimiter'; +import { LlmByokConfigSchema } from '@daoyou/shared/config/llm'; + +export type HandshakeError = { + status: number; + message: string; + headers?: Headers; + body?: { error: string }; +}; + +/** Upgrade requests bypass Express middleware and need the same API admission checks. */ +export async function verifyWebSocketRequest( + headers: Headers, +): Promise { + const responseHeaders = new Headers({ + Vary: 'Origin', + 'Access-Control-Allow-Credentials': 'true', + }); + const origin = apiCorsOptions.origin(headers.get('origin') ?? ''); + if (origin) responseHeaders.set('Access-Control-Allow-Origin', origin); + const provider = headers.get('x-llm-provider') ?? undefined; + const apiKey = headers.get('x-llm-api-key') ?? undefined; + const model = headers.get('x-llm-model') ?? undefined; + if ( + (provider !== undefined || apiKey !== undefined || model !== undefined) && + !LlmByokConfigSchema.safeParse({ provider, apiKey, model }).success + ) + return { + status: 400, + message: 'LLM 配置不完整或格式无效', + headers: responseHeaders, + }; + const ip = getRequestIp(headers); + if (ip) { + try { + const limit = await checkApiIpRateLimit(ip); + responseHeaders.set('X-RateLimit-Limit', String(limit.limit)); + responseHeaders.set('X-RateLimit-Remaining', String(limit.remaining)); + responseHeaders.set( + 'X-RateLimit-Reset', + String(Math.ceil(limit.resetAt.getTime() / 1000)), + ); + if (!limit.allowed) { + responseHeaders.set('Retry-After', String(limit.retryAfterSeconds)); + return { + status: 429, + message: '请求过于频繁,请稍后再试', + headers: responseHeaders, + }; + } + } catch (error) { + console.warn( + '[api-rate-limit] redis check failed; allowing request', + error, + ); + } + } + return { headers: responseHeaders }; +} diff --git a/apps/api/src/http/zod.pipe.ts b/apps/api/src/http/zod.pipe.ts new file mode 100644 index 00000000..4840f381 --- /dev/null +++ b/apps/api/src/http/zod.pipe.ts @@ -0,0 +1,29 @@ +import { HttpException, Injectable, type PipeTransform } from '@nestjs/common'; +import { ZodError, type ZodType } from 'zod'; + +@Injectable() +export class ZodPipe implements PipeTransform { + constructor( + private readonly schema: ZodType, + // Middleware validation runs outside route-local domain error handling. + private readonly errorContract?: 'legacy-unhandled', + ) {} + + transform(value: unknown): T { + try { + return this.schema.parse(value); + } catch (error) { + if ( + this.errorContract === 'legacy-unhandled' && + error instanceof ZodError + ) { + // Hono's app.onError intercepts these before jsonError can map them to 400. + throw new HttpException( + { success: false, error: '服务器内部错误' }, + 500, + ); + } + throw error; + } + } +} diff --git a/apps/api/src/hunts/hunts.controller.ts b/apps/api/src/hunts/hunts.controller.ts new file mode 100644 index 00000000..8fd3a749 --- /dev/null +++ b/apps/api/src/hunts/hunts.controller.ts @@ -0,0 +1,118 @@ +import { + Controller, + Get, + Header, + HttpCode, + Inject, + Param, + Post, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { isRedisLockContention } from '@server/lib/redis/lock'; +import { ArenaV6Error } from '@server/lib/services/combat-v6/CombatV6ArenaService'; +import { + HuntCreateTeamSchema, + HuntEventIdSchema, + HuntTeamCommandSchema, + type HuntTeamCommand, +} from '@daoyou/shared/contracts/hunts'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { HuntsService } from './hunts.service'; + +const HuntsErrors = apiErrorFilter((error) => { + let status = 500; + let message = '暂时联系不上讨伐队伍,请稍后再试'; + if (error instanceof ArenaV6Error) { + status = error.status; + message = error.message; + } else if (error instanceof z.ZodError) { + status = 400; + message = '未能完成操作,请刷新后再试'; + } else if (isRedisLockContention(error)) { + status = 409; + message = '队伍正忙,请稍后再试'; + } else { + console.error('[hunt] request failed', error); + } + return Response.json( + { success: false, error: message }, + { + status, + headers: { 'Cache-Control': 'private, no-store' }, + }, + ); +}); +const JoinSchema = z.object({ teamId: z.uuid().optional() }).strict(); + +@Controller('api/hunts') +@Access('active') +@UseFilters(HuntsErrors) +export class HuntsController { + constructor(@Inject(HuntsService) private readonly hunts: HuntsService) {} + + @Get() + @Header('Cache-Control', 'private, no-store') + events() { + return this.hunts.events(); + } + + @Get('battles/:battleId/reward') + @Header('Cache-Control', 'private, no-store') + reward( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('battleId', new ZodPipe(z.uuid())) battleId: string, + ) { + return this.hunts.reward(actor, battleId); + } + + @Get(':eventId') + @Header('Cache-Control', 'private, no-store') + lobby( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('eventId', new ZodPipe(HuntEventIdSchema)) + eventId: z.infer, + ) { + return this.hunts.lobby(actor, eventId); + } + + @Post('teams') + @HttpCode(200) + @Header('Cache-Control', 'private, no-store') + create( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody({ fallback: undefined }, new ZodPipe(HuntCreateTeamSchema)) + input: z.infer, + ) { + return this.hunts.create(actor, input); + } + + @Post(':eventId/join') + @HttpCode(200) + @Header('Cache-Control', 'private, no-store') + join( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('eventId', new ZodPipe(HuntEventIdSchema)) + eventId: z.infer, + @JsonBody({ fallback: undefined }, new ZodPipe(JoinSchema)) + input: z.infer, + ) { + return this.hunts.join(actor, eventId, input.teamId); + } + + @Post('teams/:teamId') + @HttpCode(200) + @Header('Cache-Control', 'private, no-store') + command( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('teamId', new ZodPipe(z.uuid())) teamId: string, + @JsonBody({ fallback: undefined }, new ZodPipe(HuntTeamCommandSchema)) + input: HuntTeamCommand, + ) { + return this.hunts.command(actor, teamId, input); + } +} diff --git a/apps/api/src/hunts/hunts.module.ts b/apps/api/src/hunts/hunts.module.ts new file mode 100644 index 00000000..9fb83a52 --- /dev/null +++ b/apps/api/src/hunts/hunts.module.ts @@ -0,0 +1,6 @@ +import { Module } from '@nestjs/common'; +import { HuntsController } from './hunts.controller'; +import { HuntsService } from './hunts.service'; + +@Module({ controllers: [HuntsController], providers: [HuntsService] }) +export class HuntsModule {} diff --git a/apps/api/src/hunts/hunts.service.ts b/apps/api/src/hunts/hunts.service.ts new file mode 100644 index 00000000..87e4f450 --- /dev/null +++ b/apps/api/src/hunts/hunts.service.ts @@ -0,0 +1,60 @@ +import { Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { huntBattleReward } from '@server/lib/services/hunts/HuntRewardProjector'; +import { + commandHuntTeam, + createHuntTeam, + huntLobby, + joinHuntTeam, +} from '@server/lib/services/hunts/HuntTeamService'; +import type { + HuntCreateTeamSchema, + HuntEventIdSchema, + HuntTeamCommand, +} from '@daoyou/shared/contracts/hunts'; +import { huntEventsAt } from '@daoyou/shared/hunts/config'; +import type { z } from 'zod'; + +@Injectable() +export class HuntsService { + events() { + return { + success: true, + data: { events: huntEventsAt(Date.now()), serverNow: Date.now() }, + }; + } + + async reward(actor: ActiveCultivatorRef, battleId: string) { + return { success: true, data: await huntBattleReward(battleId, actor) }; + } + + async lobby( + actor: ActiveCultivatorRef, + eventId: z.infer, + ) { + return { success: true, data: await huntLobby(eventId, actor) }; + } + + async create( + actor: ActiveCultivatorRef, + input: z.infer, + ) { + return { success: true, data: await createHuntTeam(actor, input) }; + } + + async join( + actor: ActiveCultivatorRef, + eventId: z.infer, + teamId?: string, + ) { + return { success: true, data: await joinHuntTeam(actor, eventId, teamId) }; + } + + async command( + actor: ActiveCultivatorRef, + teamId: string, + input: HuntTeamCommand, + ) { + return { success: true, data: await commandHuntTeam(actor, teamId, input) }; + } +} diff --git a/apps/api/src/inscriptions/inscriptions.controller.ts b/apps/api/src/inscriptions/inscriptions.controller.ts new file mode 100644 index 00000000..d7537166 --- /dev/null +++ b/apps/api/src/inscriptions/inscriptions.controller.ts @@ -0,0 +1,80 @@ +import { + Controller, + Get, + Header, + HttpCode, + Inject, + Post, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import { PlayerCommandIdempotencyError } from '@server/lib/services/CommandExecutors'; +import { InventoryError } from '@server/lib/services/InventoryService'; +import { + QiInsufficientError, + QiServiceError, +} from '@server/lib/services/QiService'; +import { + InscriptionRequestSchema, + type InscriptionRequest, +} from '@daoyou/shared/contracts/inscriptions'; +import { InventoryRuleError } from '@daoyou/shared/inventory'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { InscriptionsService } from './inscriptions.service'; + +const InscriptionsErrors = apiErrorFilter((error) => { + const lock = redisLockErrorResponse(error); + if (lock) return lock; + if (error instanceof z.ZodError) + return Response.json( + { success: false, code: 'INSCRIPTION_REJECTED', error: '阵纹参数无效' }, + { status: 400 }, + ); + if ( + error instanceof InventoryError || + error instanceof InventoryRuleError || + error instanceof QiServiceError || + error instanceof QiInsufficientError || + error instanceof PlayerCommandIdempotencyError + ) + return Response.json( + { success: false, code: 'INSCRIPTION_REJECTED', error: error.message }, + { status: 409 }, + ); + console.error('[inscriptions] request failed', error); + return Response.json( + { success: false, error: '本次结果暂未确认,请重试核对' }, + { status: 500 }, + ); +}); + +@Controller('api/combat-v6/inscriptions') +@Access('active') +@UseFilters(InscriptionsErrors) +export class InscriptionsController { + constructor( + @Inject(InscriptionsService) private readonly service: InscriptionsService, + ) {} + + @Get() + @Header('Cache-Control', 'no-store') + read(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.service.read(actor); + } + + @Post() + @HttpCode(200) + @Header('Cache-Control', 'no-store') + mutate( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody({ fallback: undefined }, new ZodPipe(InscriptionRequestSchema)) + input: InscriptionRequest, + ) { + return this.service.mutate(actor, input); + } +} diff --git a/apps/api/src/inscriptions/inscriptions.module.ts b/apps/api/src/inscriptions/inscriptions.module.ts new file mode 100644 index 00000000..3709108f --- /dev/null +++ b/apps/api/src/inscriptions/inscriptions.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { InscriptionsController } from './inscriptions.controller'; +import { InscriptionsService } from './inscriptions.service'; + +@Module({ + controllers: [InscriptionsController], + providers: [InscriptionsService], +}) +export class InscriptionsModule {} diff --git a/apps/api/src/inscriptions/inscriptions.service.ts b/apps/api/src/inscriptions/inscriptions.service.ts new file mode 100644 index 00000000..cb1f2ca2 --- /dev/null +++ b/apps/api/src/inscriptions/inscriptions.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { + mutateInscriptions, + readInscriptions, +} from '@server/lib/services/InscriptionService'; +import type { InscriptionRequest } from '@daoyou/shared/contracts/inscriptions'; + +@Injectable() +export class InscriptionsService { + async read(actor: ActiveCultivatorRef) { + return { success: true, data: await readInscriptions(actor) }; + } + + async mutate(actor: ActiveCultivatorRef, input: InscriptionRequest) { + return { success: true, ...(await mutateInscriptions(actor, input)) }; + } +} diff --git a/apps/api/src/inventory/inventory.controller.ts b/apps/api/src/inventory/inventory.controller.ts new file mode 100644 index 00000000..a006a68b --- /dev/null +++ b/apps/api/src/inventory/inventory.controller.ts @@ -0,0 +1,50 @@ +import { + Controller, + Get, + Header, + HttpCode, + Inject, + Post, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { + InventoryActionSchema, + InventoryQuerySchema, +} from '@daoyou/shared/contracts/inventory'; +import type { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { CombatErrors } from '../combat/combat-errors'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { InventoryService } from './inventory.service'; + +@Controller('api/combat-v6/inventory') +@Access('active') +@UseFilters(CombatErrors) +export class InventoryController { + constructor( + @Inject(InventoryService) private readonly inventory: InventoryService, + ) {} + + @Get() + @Header('Cache-Control', 'no-store') + read( + @CurrentCultivator() actor: ActiveCultivatorRef, + @FirstQuery(new ZodPipe(InventoryQuerySchema)) + query: z.infer, + ) { + return this.inventory.read(actor.cultivatorId, query); + } + + @Post() + @HttpCode(200) + mutate( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(InventoryActionSchema)) + action: z.infer, + ) { + return this.inventory.mutate(actor.cultivatorId, action); + } +} diff --git a/apps/api/src/inventory/inventory.module.ts b/apps/api/src/inventory/inventory.module.ts new file mode 100644 index 00000000..c6a41f64 --- /dev/null +++ b/apps/api/src/inventory/inventory.module.ts @@ -0,0 +1,6 @@ +import { Module } from '@nestjs/common'; +import { InventoryController } from './inventory.controller'; +import { InventoryService } from './inventory.service'; + +@Module({ controllers: [InventoryController], providers: [InventoryService] }) +export class InventoryModule {} diff --git a/apps/api/src/inventory/inventory.service.ts b/apps/api/src/inventory/inventory.service.ts new file mode 100644 index 00000000..ea8f30f8 --- /dev/null +++ b/apps/api/src/inventory/inventory.service.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@nestjs/common'; +import { + mutateInventory, + readInventory, +} from '@server/lib/services/InventoryService'; +import { readResourceWithMeta } from '@server/lib/services/ResourceReadService'; +import type { + InventoryActionSchema, + InventoryQuerySchema, +} from '@daoyou/shared/contracts/inventory'; +import type { z } from 'zod'; + +@Injectable() +export class InventoryService { + async read(owner: string, query: z.infer) { + if (query.location === 'bag' && query.kind === 'all' && !query.search) { + return readResourceWithMeta( + { kind: 'cultivator', id: owner }, + 'inventory.bag', + (tx) => readInventory(owner, query, tx), + ); + } + return { success: true, data: await readInventory(owner, query) }; + } + + async mutate(owner: string, action: z.infer) { + return { success: true, ...(await mutateInventory(owner, action)) }; + } +} diff --git a/apps/api/src/legacy-items/battle-records.controller.ts b/apps/api/src/legacy-items/battle-records.controller.ts new file mode 100644 index 00000000..aaa0b398 --- /dev/null +++ b/apps/api/src/legacy-items/battle-records.controller.ts @@ -0,0 +1,16 @@ +import { All, Controller, HttpCode } from '@nestjs/common'; +import { Access } from '../auth/access'; + +@Controller('api/battle-records') +@Access('public') +export class LegacyBattleRecordsController { + @All(['', '*path']) + @HttpCode(410) + retired() { + return { + success: false, + error: '旧版战绩已停止查看,请前往新版战绩。', + code: 'LEGACY_BATTLE_HISTORY_RETIRED', + }; + } +} diff --git a/apps/api/src/legacy-items/inventory.controller.ts b/apps/api/src/legacy-items/inventory.controller.ts new file mode 100644 index 00000000..94744563 --- /dev/null +++ b/apps/api/src/legacy-items/inventory.controller.ts @@ -0,0 +1,58 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Post, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import { Access, CurrentCultivator } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody } from '../http/json-body'; +import { LegacyInventoryService } from './inventory.service'; + +const LegacyInventoryErrors = apiErrorFilter((error) => { + const lock = redisLockErrorResponse(error); + if (lock) return lock; + console.error('Legacy inventory API error:', error); + return Response.json( + { success: false, error: '服务器内部错误' }, + { status: 500 }, + ); +}); + +@Controller('api/cultivator/inventory') +@Access('active') +@UseFilters(LegacyInventoryErrors) +export class LegacyInventoryController { + constructor( + @Inject(LegacyInventoryService) + private readonly inventory: LegacyInventoryService, + ) {} + + @Get() + list( + @CurrentCultivator() actor: ActiveCultivatorRef, + @FirstQuery() query: Record, + ) { + return this.inventory.list(actor, query); + } + + @Post('discard') + @HttpCode(200) + discard( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody() body: unknown, + ) { + return this.inventory.discard(actor, body); + } + + @Post('identify') + @HttpCode(410) + identify() { + return { error: '未鉴定材料已弃用,无法鉴定或迁移' }; + } +} diff --git a/apps/api/src/legacy-items/inventory.service.ts b/apps/api/src/legacy-items/inventory.service.ts new file mode 100644 index 00000000..d4aa22a9 --- /dev/null +++ b/apps/api/src/legacy-items/inventory.service.ts @@ -0,0 +1,200 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { discardInventoryItem } from '@server/lib/services/InventoryApplicationService'; +import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; +import { readResourceWithMeta } from '@server/lib/services/ResourceReadService'; +import { getPaginatedInventoryByType } from '@server/lib/services/cultivator/CultivatorInventoryRepository'; +import { + ELEMENT_VALUES, + MATERIAL_TYPE_VALUES, + QUALITY_VALUES, + type ElementType, + type MaterialType, + type Quality, +} from '@daoyou/shared/types/constants'; +import { z } from 'zod'; +const DiscardSchema = z.object({ + itemId: z.string(), + itemType: z.enum(['artifact', 'consumable', 'material']), +}); +function parseList( + raw: string | null, + values: readonly T[], + label: string, +): T[] | undefined { + if (!raw) return undefined; + const parsed = raw + .split(',') + .map((value) => value.trim()) + .filter(Boolean) as T[]; + if (parsed.length === 0) return undefined; + const allowed = new Set(values); + if (parsed.some((value) => !allowed.has(value))) { + throw new Error(`无效的${label},支持:${values.join(', ')}`); + } + return parsed; +} +@Injectable() +export class LegacyInventoryService { + async list( + actor: ActiveCultivatorRef, + query: Record, + ) { + const type = query['type']; + if (!type) { + throw new HttpException( + { success: false, error: '必须指定背包类型和分页参数' }, + 400, + ); + } + if (!['artifacts', 'materials', 'consumables'].includes(type)) { + throw new HttpException( + { + success: false, + error: '无效的背包类型,仅支持 artifacts | materials | consumables', + }, + 400, + ); + } + const page = Math.max(1, parseInt(query['page'] || '1', 10)); + const pageSize = Math.min( + 100, + Math.max(1, parseInt(query['pageSize'] || '20', 10)), + ); + const consumableKind = query['consumableKind']; + if (consumableKind && consumableKind !== 'pill') { + throw new HttpException( + { success: false, error: '无效的消耗品分类' }, + 400, + ); + } + let materialTypes: MaterialType[] | undefined; + let excludeMaterialTypes: MaterialType[] | undefined; + let materialRanks: Quality[] | undefined; + let materialElements: ElementType[] | undefined; + try { + materialTypes = parseList( + query['materialTypes'] ?? null, + MATERIAL_TYPE_VALUES, + '材料类型', + ); + excludeMaterialTypes = parseList( + query['excludeMaterialTypes'] ?? null, + MATERIAL_TYPE_VALUES, + '材料类型', + ); + materialRanks = parseList( + query['materialRanks'] ?? null, + QUALITY_VALUES, + '材料品级', + ); + materialElements = parseList( + query['materialElements'] ?? null, + ELEMENT_VALUES, + '材料属性', + ); + } catch (error) { + throw new HttpException( + { + success: false, + error: + error instanceof Error ? error.message : '材料类型参数解析失败', + }, + 400, + ); + } + const validSortBy = [ + 'createdAt', + 'rank', + 'type', + 'element', + 'quantity', + 'name', + ] as const; + const validSortOrder = ['asc', 'desc'] as const; + const materialSortBy = query['materialSortBy']; + const materialSortOrder = query['materialSortOrder']; + if ( + materialSortBy && + !validSortBy.includes(materialSortBy as (typeof validSortBy)[number]) + ) { + throw new HttpException( + { + success: false, + error: `无效的排序字段,支持:${validSortBy.join(', ')}`, + }, + 400, + ); + } + if ( + materialSortOrder && + !validSortOrder.includes( + materialSortOrder as (typeof validSortOrder)[number], + ) + ) { + throw new HttpException( + { + success: false, + error: `无效的排序方向,支持:${validSortOrder.join(', ')}`, + }, + 400, + ); + } + const options = { + page, + pageSize, + materialTypes, + excludeMaterialTypes, + materialRanks, + materialElements, + materialSortBy: materialSortBy as + (typeof validSortBy)[number] | undefined, + materialSortOrder: materialSortOrder as 'asc' | 'desc' | undefined, + consumableKind: consumableKind as 'pill' | undefined, + }; + const scope = { kind: 'cultivator' as const, id: actor.cultivatorId }; + if (type === 'artifacts') { + return await readResourceWithMeta(scope, 'inventory.artifacts', (q) => + getPaginatedInventoryByType( + actor.userId, + actor.cultivatorId, + { ...options, type: 'artifacts' }, + q, + ), + ); + } + if (type === 'materials') { + return await readResourceWithMeta(scope, 'inventory.materials', (q) => + getPaginatedInventoryByType( + actor.userId, + actor.cultivatorId, + { ...options, type: 'materials' }, + q, + ), + ); + } + return await readResourceWithMeta(scope, 'inventory.consumables', (q) => + getPaginatedInventoryByType( + actor.userId, + actor.cultivatorId, + { ...options, type: 'consumables' }, + q, + ), + ); + } + async discard(actor: ActiveCultivatorRef, body: unknown) { + const { itemId, itemType } = DiscardSchema.parse(body); + if (itemType === 'artifact') { + throw new HttpException( + { success: false, error: '历史法宝已停用,仅支持查看' }, + 410, + ); + } + const committed = await discardInventoryItem({ + actor: { userId: actor.userId, cultivatorId: actor.cultivatorId }, + itemId, + itemType, + }); + return toPlayerStateMutationResponse(committed); + } +} diff --git a/apps/api/src/legacy-items/legacy-items.module.ts b/apps/api/src/legacy-items/legacy-items.module.ts new file mode 100644 index 00000000..5dd5ec0e --- /dev/null +++ b/apps/api/src/legacy-items/legacy-items.module.ts @@ -0,0 +1,48 @@ +import { + Module, + RequestMethod, + type MiddlewareConsumer, + type NestModule, +} from '@nestjs/common'; +import type { NextFunction, Request, Response } from 'express'; +import { LegacyBattleRecordsController } from './battle-records.controller'; +import { LegacyInventoryController } from './inventory.controller'; +import { LegacyInventoryService } from './inventory.service'; +import { + ArtifactMigrationController, + ManualMigrationAdminController, + ManualMigrationController, +} from './migration.controller'; +import { MigrationService } from './migration.service'; +import { ProductsController } from './products.controller'; +import { ProductsService } from './products.service'; + +@Module({ + controllers: [ + LegacyBattleRecordsController, + LegacyInventoryController, + ProductsController, + ArtifactMigrationController, + ManualMigrationController, + ManualMigrationAdminController, + ], + providers: [ProductsService, MigrationService, LegacyInventoryService], +}) +export class LegacyItemsModule implements NestModule { + configure(consumer: MiddlewareConsumer) { + // Apply before access guards, including denied and unknown migration requests. + consumer + .apply((_request: Request, response: Response, next: NextFunction) => { + response.setHeader('Cache-Control', 'no-store'); + next(); + }) + .forRoutes( + { path: 'api/artifact-migration{/*path}', method: RequestMethod.ALL }, + { path: 'api/manual-migration{/*path}', method: RequestMethod.ALL }, + { + path: 'api/admin/manual-migration{/*path}', + method: RequestMethod.ALL, + }, + ); + } +} diff --git a/apps/api/src/legacy-items/migration-errors.ts b/apps/api/src/legacy-items/migration-errors.ts new file mode 100644 index 00000000..a02d6ad5 --- /dev/null +++ b/apps/api/src/legacy-items/migration-errors.ts @@ -0,0 +1,30 @@ +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import { InventoryError } from '@server/lib/services/InventoryService'; +import { InventoryRuleError } from '@daoyou/shared/inventory'; +import { ZodError } from 'zod'; +import { apiErrorFilter } from '../http/error-filter'; + +export function migrationErrors(kind: 'manual' | 'artifact') { + return apiErrorFilter((error) => { + const lock = redisLockErrorResponse(error); + if (lock) return lock; + if (error instanceof ZodError) + return Response.json( + { success: false, error: '兑换参数无效' }, + { status: 400 }, + ); + if (error instanceof InventoryError || error instanceof InventoryRuleError) + return Response.json( + { success: false, error: error.message }, + { status: 409 }, + ); + console.error(`[${kind}-migration]`, error); + return Response.json( + { + success: false, + error: `结果暂未确认,请刷新旧${kind === 'manual' ? '功法' : '法宝'}列表并核对背包`, + }, + { status: 500 }, + ); + }); +} diff --git a/apps/api/src/legacy-items/migration.controller.ts b/apps/api/src/legacy-items/migration.controller.ts new file mode 100644 index 00000000..53c212f5 --- /dev/null +++ b/apps/api/src/legacy-items/migration.controller.ts @@ -0,0 +1,97 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Post, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { + ExchangeArtifactSchema, + type ExchangeArtifact, +} from '@daoyou/shared/contracts/artifactMigration'; +import { + ExchangeManualSchema, + type ExchangeManual, +} from '@daoyou/shared/contracts/manualMigration'; +import { Access, CurrentCultivator } from '../auth/access'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { migrationErrors } from './migration-errors'; +import { MigrationService } from './migration.service'; + +const ManualErrors = migrationErrors('manual'); +const ArtifactErrors = migrationErrors('artifact'); + +@Controller('api/artifact-migration') +@Access('active') +@UseFilters(ArtifactErrors) +export class ArtifactMigrationController { + constructor( + @Inject(MigrationService) private readonly migration: MigrationService, + ) {} + + @Get('availability') + availability(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.migration.artifactAvailability(actor); + } + + @Get() + read(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.migration.artifacts(actor); + } + + @Post('exchange') + @HttpCode(200) + exchange( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody({ fallback: undefined }, new ZodPipe(ExchangeArtifactSchema)) + input: ExchangeArtifact, + ) { + return this.migration.exchangeArtifact(actor, input); + } +} + +@Controller('api/manual-migration') +@Access('active') +@UseFilters(ManualErrors) +export class ManualMigrationController { + constructor( + @Inject(MigrationService) private readonly migration: MigrationService, + ) {} + + @Get('availability') + availability(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.migration.manualAvailability(actor); + } + + @Get() + read(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.migration.manuals(actor); + } + + @Post('exchange') + @HttpCode(200) + exchange( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody({ fallback: undefined }, new ZodPipe(ExchangeManualSchema)) + input: ExchangeManual, + ) { + return this.migration.exchangeManual(actor, input); + } +} + +@Controller('api/admin/manual-migration') +@Access('admin') +@UseFilters(ManualErrors) +export class ManualMigrationAdminController { + constructor( + @Inject(MigrationService) private readonly migration: MigrationService, + ) {} + + @Get() + read() { + return this.migration.manualAdmin(); + } +} diff --git a/apps/api/src/legacy-items/migration.service.ts b/apps/api/src/legacy-items/migration.service.ts new file mode 100644 index 00000000..2f99fc0a --- /dev/null +++ b/apps/api/src/legacy-items/migration.service.ts @@ -0,0 +1,49 @@ +import { Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { + artifactMigrationAvailability, + exchangeArtifactMigration, + readArtifactMigration, +} from '@server/lib/services/legacy/ArtifactMigrationService'; +import { + exchangeManualMigration, + manualMigrationAvailability, + readManualMigration, + readManualMigrationAdmin, +} from '@server/lib/services/legacy/ManualMigrationService'; +import type { ExchangeArtifact } from '@daoyou/shared/contracts/artifactMigration'; +import type { ExchangeManual } from '@daoyou/shared/contracts/manualMigration'; + +@Injectable() +export class MigrationService { + async artifactAvailability(actor: ActiveCultivatorRef) { + return { success: true, data: await artifactMigrationAvailability(actor) }; + } + + async artifacts(actor: ActiveCultivatorRef) { + return { success: true, data: await readArtifactMigration(actor) }; + } + + async exchangeArtifact(actor: ActiveCultivatorRef, input: ExchangeArtifact) { + return { + success: true, + ...(await exchangeArtifactMigration(actor, input)), + }; + } + + async manualAvailability(actor: ActiveCultivatorRef) { + return { success: true, data: await manualMigrationAvailability(actor) }; + } + + async manuals(actor: ActiveCultivatorRef) { + return { success: true, data: await readManualMigration(actor) }; + } + + async exchangeManual(actor: ActiveCultivatorRef, input: ExchangeManual) { + return { success: true, ...(await exchangeManualMigration(actor, input)) }; + } + + async manualAdmin() { + return { success: true, data: await readManualMigrationAdmin() }; + } +} diff --git a/apps/api/src/legacy-items/products.controller.ts b/apps/api/src/legacy-items/products.controller.ts new file mode 100644 index 00000000..e948e98b --- /dev/null +++ b/apps/api/src/legacy-items/products.controller.ts @@ -0,0 +1,63 @@ +import { + Controller, + Delete, + Get, + HttpCode, + Inject, + Param, + Post, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import { Access, CurrentCultivator } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { FirstQuery } from '../http/first-query'; +import { ProductsService } from './products.service'; + +const ProductsErrors = apiErrorFilter((error) => { + const lock = redisLockErrorResponse(error); + if (lock) return lock; + console.error('旧产物请求失败:', error); + return Response.json( + { success: false, error: '服务器内部错误' }, + { status: 500 }, + ); +}); + +@Controller('api/v2/products') +@Access('active') +@UseFilters(ProductsErrors) +export class ProductsController { + constructor( + @Inject(ProductsService) private readonly products: ProductsService, + ) {} + + @Get() + list( + @CurrentCultivator() actor: ActiveCultivatorRef, + @FirstQuery() query: Record, + ) { + return this.products.list(actor, query); + } + + @Post('equip') + @HttpCode(410) + equip() { + return { error: '旧产物装配已停用' }; + } + + @Get(':id') + read( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('id') id: string, + ) { + return this.products.read(actor, id); + } + + @Delete(':id') + @HttpCode(410) + delete() { + return { error: '历史产物直接删除已停用' }; + } +} diff --git a/apps/api/src/legacy-items/products.service.ts b/apps/api/src/legacy-items/products.service.ts new file mode 100644 index 00000000..5c2d317c --- /dev/null +++ b/apps/api/src/legacy-items/products.service.ts @@ -0,0 +1,55 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import * as products from '@server/lib/repositories/creationProductRepository'; +import type { LegacyProductType } from '@daoyou/shared/legacy/products'; + +@Injectable() +export class ProductsService { + async list( + actor: ActiveCultivatorRef, + query: Record, + ) { + const type = query.type; + if (!type || !['skill', 'gongfa', 'artifact'].includes(type)) { + throw new HttpException( + { error: '请指定有效的产物类型 (skill|gongfa|artifact)' }, + 400, + ); + } + const page = Math.max(1, parseInt(query.page || '1', 10)); + const pageSize = Math.min( + 100, + Math.max(1, parseInt(query.pageSize || '20', 10)), + ); + const [total, items] = await Promise.all([ + products.countByType(actor.cultivatorId, type as LegacyProductType), + products.findByTypeAndCultivatorPage( + actor.cultivatorId, + type as LegacyProductType, + { page, pageSize }, + ), + ]); + const totalPages = Math.ceil(total / pageSize); + return { + success: true, + data: { + items, + pagination: { + page, + pageSize, + total, + totalPages, + hasMore: page < totalPages, + }, + }, + }; + } + + async read(actor: ActiveCultivatorRef, id: string) { + const product = await products.findById(id); + if (!product || product.cultivatorId !== actor.cultivatorId) { + throw new HttpException({ error: '产物不存在' }, 404); + } + return { success: true, data: product }; + } +} diff --git a/src/server/lib/admin/smtp.ts b/apps/api/src/lib/admin/smtp.ts similarity index 63% rename from src/server/lib/admin/smtp.ts rename to apps/api/src/lib/admin/smtp.ts index 8d6414f9..7adba05e 100644 --- a/src/server/lib/admin/smtp.ts +++ b/apps/api/src/lib/admin/smtp.ts @@ -1,15 +1,16 @@ +import { getRuntimeEnvironment } from '@server/lib/config/environment'; import nodemailer from 'nodemailer'; export function createSmtpTransporter() { - const host = process.env.SMTP_HOST; - const port = Number(process.env.SMTP_PORT ?? 465); + const host = getRuntimeEnvironment().SMTP_HOST; + const port = Number(getRuntimeEnvironment().SMTP_PORT ?? 465); const secure = - process.env.SMTP_SECURE !== undefined - ? process.env.SMTP_SECURE === 'true' + getRuntimeEnvironment().SMTP_SECURE !== undefined + ? getRuntimeEnvironment().SMTP_SECURE === 'true' : port === 465; - const user = process.env.SMTP_USER; - const pass = process.env.SMTP_PASS; - const from = process.env.MAIL_FROM; + const user = getRuntimeEnvironment().SMTP_USER; + const pass = getRuntimeEnvironment().SMTP_PASS; + const from = getRuntimeEnvironment().MAIL_FROM; if (!host || !port || !user || !pass || !from) { throw new Error( diff --git a/apps/api/src/lib/auth/activeCultivator.ts b/apps/api/src/lib/auth/activeCultivator.ts new file mode 100644 index 00000000..d940bdc0 --- /dev/null +++ b/apps/api/src/lib/auth/activeCultivator.ts @@ -0,0 +1,117 @@ +import { getRuntimeEnvironment } from '@server/lib/config/environment'; +import { getExecutor } from '@server/lib/drizzle/db'; +import { cultivators } from '@server/lib/drizzle/schema'; +import { redis } from '@server/lib/redis'; +import { and, eq } from 'drizzle-orm'; +import type { ActiveCultivatorRef, AuthUser } from './types'; + +const ACTIVE_REF_REDIS_TTL_SECONDS = 600; + +type CachedActiveCultivatorRef = ActiveCultivatorRef & { + cachedAt: string; +}; + +function activeRefCacheKey(userId: string): string { + return `active-cultivator:user:${userId}`; +} + +async function readActiveRefRedisCache( + userId: string, +): Promise { + if (!getRuntimeEnvironment().REDIS_URL) { + return null; + } + + try { + const raw = await redis.get(activeRefCacheKey(userId)); + if (!raw) { + return null; + } + + const parsed = JSON.parse(raw) as CachedActiveCultivatorRef; + if ( + parsed.userId !== userId || + !parsed.cultivatorId || + parsed.status !== 'active' + ) { + return null; + } + + const ref: ActiveCultivatorRef = { + userId: parsed.userId, + cultivatorId: parsed.cultivatorId, + status: 'active', + }; + return ref; + } catch (error) { + console.warn('[active-cultivator-ref] redis read failed', error); + return null; + } +} + +async function writeActiveRefRedisCache(ref: ActiveCultivatorRef) { + if (!getRuntimeEnvironment().REDIS_URL) { + return; + } + + try { + const payload: CachedActiveCultivatorRef = { + ...ref, + cachedAt: new Date().toISOString(), + }; + await redis.set( + activeRefCacheKey(ref.userId), + JSON.stringify(payload), + 'EX', + ACTIVE_REF_REDIS_TTL_SECONDS, + ); + } catch (error) { + console.warn('[active-cultivator-ref] redis write failed', error); + } +} + +export async function invalidateActiveCultivatorRef(userId: string) { + if (!getRuntimeEnvironment().REDIS_URL) { + return; + } + + try { + await redis.del(activeRefCacheKey(userId)); + } catch (error) { + console.warn('[active-cultivator-ref] redis invalidate failed', error); + } +} + +export async function resolveActiveCultivatorRef( + user: AuthUser, +): Promise { + const redisCached = await readActiveRefRedisCache(user.id); + if (redisCached) { + return redisCached; + } + + const executor = getExecutor(); + const row = await executor.query.cultivators.findFirst({ + columns: { + id: true, + userId: true, + status: true, + }, + where: and( + eq(cultivators.userId, user.id), + eq(cultivators.status, 'active'), + ), + }); + + if (!row) { + return null; + } + + const ref: ActiveCultivatorRef = { + userId: row.userId, + cultivatorId: row.id, + status: 'active', + }; + await writeActiveRefRedisCache(ref); + return ref; +} diff --git a/src/server/lib/auth/adminAccess.ts b/apps/api/src/lib/auth/adminAccess.ts similarity index 86% rename from src/server/lib/auth/adminAccess.ts rename to apps/api/src/lib/auth/adminAccess.ts index 2abfb1fb..5ebe3743 100644 --- a/src/server/lib/auth/adminAccess.ts +++ b/apps/api/src/lib/auth/adminAccess.ts @@ -1,5 +1,6 @@ +import { getRuntimeEnvironment } from '@server/lib/config/environment'; function parseCommaSeparatedEnv(name: 'ADMIN_EMAILS' | 'ADMIN_USER_IDS') { - return (process.env[name] ?? '') + return (getRuntimeEnvironment()[name] ?? '') .split(',') .map((value) => value.trim().toLowerCase()) .filter(Boolean); diff --git a/src/server/lib/auth/altcha.ts b/apps/api/src/lib/auth/altcha.ts similarity index 94% rename from src/server/lib/auth/altcha.ts rename to apps/api/src/lib/auth/altcha.ts index 07626703..b05dbf4c 100644 --- a/src/server/lib/auth/altcha.ts +++ b/apps/api/src/lib/auth/altcha.ts @@ -1,3 +1,4 @@ +import { getRuntimeEnvironment } from '@server/lib/config/environment'; import { redis } from '@server/lib/redis'; import { createChallenge, @@ -6,7 +7,7 @@ import { type Payload, } from 'altcha-lib'; import { deriveKey } from 'altcha-lib/algorithms/pbkdf2'; -import { deriveHmacKeySecret } from 'altcha-lib/frameworks/hono'; +import { deriveHmacKeySecret } from 'altcha-lib/frameworks/shared'; export const ALTCHA_ACTIONS = [ 'sign-in', @@ -32,7 +33,7 @@ let derivedSecretSource: string | null = null; let derivedSecretPromise: Promise | null = null; function getAltchaSecret(): string | null { - return process.env.ALTCHA_HMAC_SECRET?.trim() || null; + return getRuntimeEnvironment().ALTCHA_HMAC_SECRET?.trim() || null; } async function getAltchaKeySignatureSecret(secret: string): Promise { diff --git a/src/server/lib/auth/auth.ts b/apps/api/src/lib/auth/auth.ts similarity index 96% rename from src/server/lib/auth/auth.ts rename to apps/api/src/lib/auth/auth.ts index f2080073..e85d55e7 100644 --- a/src/server/lib/auth/auth.ts +++ b/apps/api/src/lib/auth/auth.ts @@ -1,3 +1,4 @@ +import { getRuntimeEnvironment } from '@server/lib/config/environment'; import { drizzleAdapter } from '@better-auth/drizzle-adapter'; import { i18n, type TranslationDictionary } from '@better-auth/i18n'; import { betterAuth } from 'better-auth'; @@ -18,7 +19,7 @@ import { import { BETTER_AUTH_SCHEMA_NAME, betterAuthSchema } from './schema'; function getRequiredEnv(name: 'BETTER_AUTH_SECRET' | 'BETTER_AUTH_URL') { - const value = process.env[name]; + const value = getRuntimeEnvironment()[name]; if (!value) { throw new Error(`Missing Better Auth config: ${name}`); @@ -28,8 +29,8 @@ function getRequiredEnv(name: 'BETTER_AUTH_SECRET' | 'BETTER_AUTH_URL') { } function getGitHubProviderConfig() { - const clientId = process.env.GITHUB_CLIENT_ID; - const clientSecret = process.env.GITHUB_CLIENT_SECRET; + const clientId = getRuntimeEnvironment().GITHUB_CLIENT_ID; + const clientSecret = getRuntimeEnvironment().GITHUB_CLIENT_SECRET; if (!clientId || !clientSecret) { return undefined; diff --git a/src/server/lib/auth/cookieDomain.ts b/apps/api/src/lib/auth/cookieDomain.ts similarity index 67% rename from src/server/lib/auth/cookieDomain.ts rename to apps/api/src/lib/auth/cookieDomain.ts index e0f2c126..13dbd062 100644 --- a/src/server/lib/auth/cookieDomain.ts +++ b/apps/api/src/lib/auth/cookieDomain.ts @@ -1,3 +1,4 @@ +import { getRuntimeEnvironment } from '@server/lib/config/environment'; export type CrossSubDomainCookiesConfig = { enabled: true; domain: string; @@ -5,7 +6,7 @@ export type CrossSubDomainCookiesConfig = { export function getCookieDomainConfig(): CrossSubDomainCookiesConfig | undefined { - const domain = process.env.BETTER_AUTH_COOKIE_DOMAIN?.trim(); + const domain = getRuntimeEnvironment().BETTER_AUTH_COOKIE_DOMAIN?.trim(); if (!domain) { return undefined; @@ -18,11 +19,11 @@ export function getCookieDomainConfig(): } export function getCrossSiteCookieConfig() { - if (process.env.BETTER_AUTH_CROSS_SITE_COOKIES !== 'true') { + if (getRuntimeEnvironment().BETTER_AUTH_CROSS_SITE_COOKIES !== 'true') { return undefined; } - if (new URL(process.env.BETTER_AUTH_URL ?? '').protocol !== 'https:') { + if (new URL(getRuntimeEnvironment().BETTER_AUTH_URL ?? '').protocol !== 'https:') { throw new Error( 'BETTER_AUTH_CROSS_SITE_COOKIES requires an HTTPS BETTER_AUTH_URL', ); diff --git a/src/server/lib/auth/hono.ts b/apps/api/src/lib/auth/handler.ts similarity index 78% rename from src/server/lib/auth/hono.ts rename to apps/api/src/lib/auth/handler.ts index 4cea9c59..b8a88228 100644 --- a/src/server/lib/auth/hono.ts +++ b/apps/api/src/lib/auth/handler.ts @@ -7,7 +7,6 @@ import { auth } from '@server/lib/auth/auth'; import { authUsers } from '@server/lib/auth/schema'; import { db } from '@server/lib/drizzle/db'; import { eq } from 'drizzle-orm'; -import type { Context } from 'hono'; const CAPTCHA_ACTION_BY_PATH = new Map([ ['/api/auth/sign-in/email', 'sign-in'], @@ -49,8 +48,8 @@ function authError(message: string, status = 400) { ); } -async function validateCaptcha(context: Context): Promise { - const action = CAPTCHA_ACTION_BY_PATH.get(context.req.path); +async function validateCaptcha(request: Request): Promise { + const action = CAPTCHA_ACTION_BY_PATH.get(new URL(request.url).pathname); if (!action) { return null; } @@ -59,8 +58,8 @@ async function validateCaptcha(context: Context): Promise { return null; } - const body = await readRequestBody(context.req.raw); - const captchaPayloadHeader = context.req.header('x-altcha-payload'); + const body = await readRequestBody(request); + const captchaPayloadHeader = request.headers.get('x-altcha-payload'); const captchaPayloadBody = typeof body.altcha === 'string' ? body.altcha @@ -87,13 +86,13 @@ async function validateCaptcha(context: Context): Promise { } async function validateOtpSignUpName( - context: Context, + request: Request, ): Promise { - if (context.req.path !== '/api/auth/sign-in/email-otp') { + if (new URL(request.url).pathname !== '/api/auth/sign-in/email-otp') { return null; } - const body = await readRequestBody(context.req.raw); + const body = await readRequestBody(request); const email = typeof body.email === 'string' ? body.email.trim().toLowerCase() : ''; const name = typeof body.name === 'string' ? body.name.trim() : ''; @@ -115,25 +114,25 @@ async function validateOtpSignUpName( return null; } -export async function handleAuthRequest(context: Context): Promise { +export async function handleAuthRequest(request: Request): Promise { if ( - context.req.path === ADMIN_AUTH_PATH || - context.req.path.startsWith(`${ADMIN_AUTH_PATH}/`) + new URL(request.url).pathname === ADMIN_AUTH_PATH || + new URL(request.url).pathname.startsWith(`${ADMIN_AUTH_PATH}/`) ) { return authError('未找到该接口', 404); } - if (context.req.method === 'POST') { - const captchaError = await validateCaptcha(context); + if (request.method === 'POST') { + const captchaError = await validateCaptcha(request); if (captchaError) { return captchaError; } - const otpNameError = await validateOtpSignUpName(context); + const otpNameError = await validateOtpSignUpName(request); if (otpNameError) { return otpNameError; } } - return auth.handler(context.req.raw); + return auth.handler(request); } diff --git a/src/server/lib/auth/schema.ts b/apps/api/src/lib/auth/schema.ts similarity index 100% rename from src/server/lib/auth/schema.ts rename to apps/api/src/lib/auth/schema.ts diff --git a/apps/api/src/lib/auth/types.ts b/apps/api/src/lib/auth/types.ts new file mode 100644 index 00000000..7bffcfdd --- /dev/null +++ b/apps/api/src/lib/auth/types.ts @@ -0,0 +1,11 @@ +export type AuthUser = { + id: string; + email: string; + name: string; +}; + +export type ActiveCultivatorRef = { + userId: string; + cultivatorId: string; + status: 'active'; +}; diff --git a/apps/api/src/lib/config/environment.ts b/apps/api/src/lib/config/environment.ts new file mode 100644 index 00000000..51195e90 --- /dev/null +++ b/apps/api/src/lib/config/environment.ts @@ -0,0 +1,79 @@ +import { z } from 'zod'; + +const positiveInteger = (fallback: string, max = Number.MAX_SAFE_INTEGER) => + z + .string() + .default(fallback) + .refine( + (value) => + /^\d+$/.test(value) && Number(value) > 0 && Number(value) <= max, + 'must be a positive integer within the allowed range', + ); + +const environmentSchema = z + .object({ + NODE_ENV: z + .enum(['development', 'production', 'test']) + .default('development'), + APP_ENV: z.enum(['local', 'staging', 'production']).optional(), + HOST: z.string().min(1).default('0.0.0.0'), + PORT: positiveInteger('3000', 65535), + DATABASE_URL: z + .url() + .refine( + (value) => /^postgres(?:ql)?:/.test(value), + 'must be a PostgreSQL URL', + ), + DB_MAX_CONNECTIONS: positiveInteger('20'), + BETTER_AUTH_SECRET: z.string().min(32), + BETTER_AUTH_URL: z.url(), + REDIS_URL: z + .union([ + z.literal(''), + z + .url() + .refine((value) => /^rediss?:/.test(value), 'must be a Redis URL'), + ]) + .optional(), + NATS_SERVERS: z.string().trim().min(1), + NATS_USER: z.string().trim().min(1), + NATS_PASSWORD: z.string().min(1), + CRON_SECRET: z.string().optional(), + SMTP_PORT: positiveInteger('465', 65535), + API_IP_RATE_LIMIT_WINDOW_SECONDS: positiveInteger('60'), + API_IP_RATE_LIMIT_MAX_REQUESTS: positiveInteger('300'), + }) + .catchall(z.string().optional()) + .superRefine((env, ctx) => { + if (env.NODE_ENV === 'production' && !env.CRON_SECRET?.trim()) { + ctx.addIssue({ + code: 'custom', + path: ['CRON_SECRET'], + message: 'is required in production', + }); + } + if (env.NODE_ENV === 'production' && env.APP_ENV === 'local') { + ctx.addIssue({ + code: 'custom', + path: ['APP_ENV'], + message: 'local is forbidden in production', + }); + } + }); + +export type RuntimeEnvironment = z.infer; +let environment: RuntimeEnvironment | undefined; + +// Also used by framework-independent repositories and maintenance commands. +// One immutable startup snapshot; no implicit dotenv files or secret logging. +export function getRuntimeEnvironment(): RuntimeEnvironment { + if (environment) return environment; + const result = environmentSchema.safeParse(process.env); + if (!result.success) { + throw new Error( + `Invalid server configuration: ${result.error.issues.map((issue) => `${issue.path.join('.')}: ${issue.message}`).join('; ')}`, + ); + } + environment = Object.freeze(result.data); + return environment; +} diff --git a/src/server/lib/drizzle/db.ts b/apps/api/src/lib/drizzle/db.ts similarity index 89% rename from src/server/lib/drizzle/db.ts rename to apps/api/src/lib/drizzle/db.ts index b338dd2e..c77b336f 100644 --- a/src/server/lib/drizzle/db.ts +++ b/apps/api/src/lib/drizzle/db.ts @@ -1,3 +1,4 @@ +import { getRuntimeEnvironment } from '@server/lib/config/environment'; import { betterAuthSchema } from '@server/lib/auth/schema'; import { is } from 'drizzle-orm'; import { drizzle } from 'drizzle-orm/node-postgres'; @@ -5,8 +6,8 @@ import { PgTransaction } from 'drizzle-orm/pg-core'; import { Pool } from 'pg'; import * as schema from './schema'; -const connectionString = process.env.DATABASE_URL; -const maxConnections = Number(process.env.DB_MAX_CONNECTIONS || 20); +const connectionString = getRuntimeEnvironment().DATABASE_URL; +const maxConnections = Number(getRuntimeEnvironment().DB_MAX_CONNECTIONS || 20); if (!connectionString) { throw new Error('Missing DATABASE_URL'); @@ -52,6 +53,10 @@ export const db = drizzle(pool, { }, }); +export async function closeDatabase(): Promise { + await pool.end(); +} + export type DbClient = typeof db; export type DbTransaction = Parameters< diff --git a/src/server/lib/drizzle/schema.ts b/apps/api/src/lib/drizzle/schema.ts similarity index 98% rename from src/server/lib/drizzle/schema.ts rename to apps/api/src/lib/drizzle/schema.ts index ebabe649..fad46565 100644 --- a/src/server/lib/drizzle/schema.ts +++ b/apps/api/src/lib/drizzle/schema.ts @@ -1,39 +1,39 @@ -import type { PlayerJournalEvent } from '@shared/contracts/playerJournal'; -import type { StoryStatus, StoryTrack } from '@shared/story/schema'; -import type { SystemMailConditions } from '@shared/contracts/systemMail'; -import type { RewardSelection } from '@shared/contracts/adminRewards'; -import type { ItemGrant } from '@shared/inventory'; -import type { StoredTowerWeek } from '@shared/engine/combat-v6/tower/published'; -import type { TowerClaims } from '@shared/lib/tower/reward-state'; -import type { DivinationDice, DivinationDirection } from '@shared/lib/divination'; -import type { WildEncounter, WildRuntime } from '@shared/contracts/combatV6Wild'; -import type { BattleReplayV1 } from '@shared/contracts/battleReplay'; -import type { AutoStrategy } from '@shared/combat-v6/auto-strategy'; -import type { CombatV6ReplayV1 } from '@shared/contracts/combatV6Runtime'; +import type { PlayerJournalEvent } from '@daoyou/shared/contracts/playerJournal'; +import type { StoryStatus, StoryTrack } from '@daoyou/shared/story/schema'; +import type { SystemMailConditions } from '@daoyou/shared/contracts/systemMail'; +import type { RewardSelection } from '@daoyou/shared/contracts/adminRewards'; +import type { ItemGrant } from '@daoyou/shared/inventory'; +import type { StoredTowerWeek } from '@daoyou/shared/engine/combat-v6/tower/published'; +import type { TowerClaims } from '@daoyou/shared/lib/tower/reward-state'; +import type { DivinationDice, DivinationDirection } from '@daoyou/shared/lib/divination'; +import type { WildEncounter, WildRuntime } from '@daoyou/shared/contracts/combatV6Wild'; +import type { BattleReplayV1 } from '@daoyou/shared/contracts/battleReplay'; +import type { AutoStrategy } from '@daoyou/shared/combat-v6/auto-strategy'; +import type { CombatV6ReplayV1 } from '@daoyou/shared/contracts/combatV6Runtime'; import type { ResourceChangeOperation, ResourceScopeKind, ResourceTopic, -} from '@shared/contracts/resources'; +} from '@daoyou/shared/contracts/resources'; import type { BeastLineup, SummonedBeast, -} from '@shared/engine/combat-v6/beasts'; -import type { SpiritFieldPlotState } from '@shared/engine/spirit-field/types'; +} from '@daoyou/shared/engine/combat-v6/beasts'; +import type { SpiritFieldPlotState } from '@daoyou/shared/engine/spirit-field/types'; import type { ItemLibraryEditorConfig, ItemLibraryPayload, -} from '@shared/lib/itemLibrary'; -import type { SponsorshipTierId } from '@shared/lib/sponsorship'; -import type { TowerPreparedEnemy } from '@shared/lib/tower'; -import type { BattleRecordV3 } from '@shared/types/battle'; +} from '@daoyou/shared/lib/itemLibrary'; +import type { SponsorshipTierId } from '@daoyou/shared/lib/sponsorship'; +import type { TowerPreparedEnemy } from '@daoyou/shared/lib/tower'; +import type { BattleRecordV3 } from '@daoyou/shared/types/battle'; import type { AlchemyFormulaBlueprint, AlchemyFormulaMastery, AlchemyFormulaPattern, PillFamily, -} from '@shared/types/consumable'; -import type { MailAttachment } from '@shared/types/mail'; +} from '@daoyou/shared/types/consumable'; +import type { MailAttachment } from '@daoyou/shared/types/mail'; import { sql } from 'drizzle-orm'; import { bigint, diff --git a/src/server/lib/dungeon/combatV6.ts b/apps/api/src/lib/dungeon/combatV6.ts similarity index 92% rename from src/server/lib/dungeon/combatV6.ts rename to apps/api/src/lib/dungeon/combatV6.ts index 4877d315..3517c813 100644 --- a/src/server/lib/dungeon/combatV6.ts +++ b/apps/api/src/lib/dungeon/combatV6.ts @@ -1,4 +1,4 @@ -import { publicUnitAppearances } from '@shared/combat-v6/unit-appearance'; +import { publicUnitAppearances } from '@daoyou/shared/combat-v6/unit-appearance'; import { db, type DbExecutor } from '@server/lib/drizzle/db'; import { cultivatorBeasts, @@ -17,39 +17,39 @@ import { assembleCombatV6TrainingPlayer } from '@server/lib/services/combat-v6/C import { ConditionService } from '@server/lib/services/ConditionService'; import { getCultivatorPreHeavenFates } from '@server/lib/services/cultivator/CultivatorProfileRepository'; import { ResourceEventCommitter } from '@server/lib/services/ResourceEventCommitter'; -import { automaticCommands } from '@shared/combat-v6/auto'; +import { automaticCommands } from '@daoyou/shared/combat-v6/auto'; import { combatV6Display, combatV6DisplayEvent, combatV6Playback, combatV6Units, visibleUnitNames, -} from '@shared/combat-v6/presentation'; -import { createCombatV6Replay } from '@shared/combat-v6/replay'; -import { liveReplayDelta } from '@shared/combat-v6/replay-timeline'; -import type { CombatV6CommandGroup } from '@shared/contracts/combatV6'; +} from '@daoyou/shared/combat-v6/presentation'; +import { createCombatV6Replay } from '@daoyou/shared/combat-v6/replay'; +import { liveReplayDelta } from '@daoyou/shared/combat-v6/replay-timeline'; +import type { CombatV6CommandGroup } from '@daoyou/shared/contracts/combatV6'; import type { DungeonEncounterView, DungeonSessionView, -} from '@shared/contracts/combatV6Dungeon'; -import { beastDeathIds } from '@shared/engine/combat-v6/beasts'; +} from '@daoyou/shared/contracts/combatV6Dungeon'; +import { beastDeathIds } from '@daoyou/shared/engine/combat-v6/beasts'; import { beastVictoryExperience, gainBeastExp, -} from '@shared/engine/combat-v6/beasts/progression'; +} from '@daoyou/shared/engine/combat-v6/beasts/progression'; import { carryDungeonBeastResources, createDungeonHost, DungeonHost, type DungeonBattleSnapshot, -} from '@shared/engine/combat-v6/dungeon/host'; -import { projectCharacterToCombatV6 } from '@shared/engine/combat-v6/projection'; -import { combatCharacterLevel } from '@shared/engine/combat-v6/projection/character-level'; -import { evaluateFateContext } from '@shared/lib/fates'; -import { getMapNode, resolveDungeonMapConfig } from '@shared/lib/game/mapSystem'; -import { appendDungeonReward } from '@shared/rewards/dungeon'; -import type { CultivatorCondition } from '@shared/types/condition'; -import type { RealmType } from '@shared/types/constants'; +} from '@daoyou/shared/engine/combat-v6/dungeon/host'; +import { projectCharacterToCombatV6 } from '@daoyou/shared/engine/combat-v6/projection'; +import { combatCharacterLevel } from '@daoyou/shared/engine/combat-v6/projection/character-level'; +import { evaluateFateContext } from '@daoyou/shared/lib/fates'; +import { getMapNode, resolveDungeonMapConfig } from '@daoyou/shared/lib/game/mapSystem'; +import { appendDungeonReward } from '@daoyou/shared/rewards/dungeon'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; +import type { RealmType } from '@daoyou/shared/types/constants'; import { and, eq, ne } from 'drizzle-orm'; import { randomInt, randomUUID } from 'node:crypto'; import { hasActiveDungeon } from './occupancy'; diff --git a/src/server/lib/dungeon/costs.ts b/apps/api/src/lib/dungeon/costs.ts similarity index 86% rename from src/server/lib/dungeon/costs.ts rename to apps/api/src/lib/dungeon/costs.ts index ee521829..9bdd8565 100644 --- a/src/server/lib/dungeon/costs.ts +++ b/apps/api/src/lib/dungeon/costs.ts @@ -1,7 +1,7 @@ -import type { DungeonMaterialSelection } from '@shared/contracts/combatV6Dungeon'; -import type { ResourceOperation } from '@shared/engine/resource/types'; -import { consumeDungeonMaterials } from '@shared/lib/dungeon/materialCosts'; -import type { DungeonOptionCost } from '@shared/lib/dungeon/types'; +import type { DungeonMaterialSelection } from '@daoyou/shared/contracts/combatV6Dungeon'; +import type { ResourceOperation } from '@daoyou/shared/engine/resource/types'; +import { consumeDungeonMaterials } from '@daoyou/shared/lib/dungeon/materialCosts'; +import type { DungeonOptionCost } from '@daoyou/shared/lib/dungeon/types'; import { and, eq } from 'drizzle-orm'; import { getExecutor, diff --git a/src/server/lib/dungeon/dungeonLimiter.ts b/apps/api/src/lib/dungeon/dungeonLimiter.ts similarity index 100% rename from src/server/lib/dungeon/dungeonLimiter.ts rename to apps/api/src/lib/dungeon/dungeonLimiter.ts diff --git a/src/server/lib/dungeon/llmContext.ts b/apps/api/src/lib/dungeon/llmContext.ts similarity index 98% rename from src/server/lib/dungeon/llmContext.ts rename to apps/api/src/lib/dungeon/llmContext.ts index 48ec8152..294b93fe 100644 --- a/src/server/lib/dungeon/llmContext.ts +++ b/apps/api/src/lib/dungeon/llmContext.ts @@ -1,6 +1,6 @@ import { truncateText } from '@server/utils/llmPayload'; -import type { ResolvedDungeonMapConfig } from '@shared/lib/game/mapSystem'; -import type { RealmType } from '@shared/types/constants'; +import type { ResolvedDungeonMapConfig } from '@daoyou/shared/lib/game/mapSystem'; +import type { RealmType } from '@daoyou/shared/types/constants'; import type { DungeonOptionCost, DungeonRoundLlmContext, diff --git a/src/server/lib/dungeon/occupancy.ts b/apps/api/src/lib/dungeon/occupancy.ts similarity index 100% rename from src/server/lib/dungeon/occupancy.ts rename to apps/api/src/lib/dungeon/occupancy.ts diff --git a/src/server/lib/dungeon/reward/RewardFactory.ts b/apps/api/src/lib/dungeon/reward/RewardFactory.ts similarity index 96% rename from src/server/lib/dungeon/reward/RewardFactory.ts rename to apps/api/src/lib/dungeon/reward/RewardFactory.ts index 84db7d94..edb009df 100644 --- a/src/server/lib/dungeon/reward/RewardFactory.ts +++ b/apps/api/src/lib/dungeon/reward/RewardFactory.ts @@ -10,22 +10,22 @@ * - 材料生成简化,使用 AI 提供的元素和类型信息 */ -import type { ResourceOperation } from '@shared/engine/resource/types'; -import { YieldCalculator } from '@shared/engine/yield/YieldCalculator'; -import { calculateDungeonExp } from '@shared/engine/cultivation/ExpBudgetCalculator'; +import type { ResourceOperation } from '@daoyou/shared/engine/resource/types'; +import { YieldCalculator } from '@daoyou/shared/engine/yield/YieldCalculator'; +import { calculateDungeonExp } from '@daoyou/shared/engine/cultivation/ExpBudgetCalculator'; import { getDungeonRewardBonus, type DungeonDifficultyTier, -} from '@shared/lib/game/mapSystem'; +} from '@daoyou/shared/lib/game/mapSystem'; import type { ElementType, MaterialType, Quality, RealmStage, RealmType, -} from '@shared/types/constants'; -import { QUALITY_VALUES } from '@shared/types/constants'; -import type { Material } from '@shared/types/cultivator'; +} from '@daoyou/shared/types/constants'; +import { QUALITY_VALUES } from '@daoyou/shared/types/constants'; +import type { Material } from '@daoyou/shared/types/cultivator'; import type { PlayerInfo } from '../types'; import { REALM_QUALITY_CAP, diff --git a/src/server/lib/dungeon/reward/index.ts b/apps/api/src/lib/dungeon/reward/index.ts similarity index 100% rename from src/server/lib/dungeon/reward/index.ts rename to apps/api/src/lib/dungeon/reward/index.ts diff --git a/src/server/lib/dungeon/reward/rewardConfig.ts b/apps/api/src/lib/dungeon/reward/rewardConfig.ts similarity index 97% rename from src/server/lib/dungeon/reward/rewardConfig.ts rename to apps/api/src/lib/dungeon/reward/rewardConfig.ts index dbefed02..d0eb0917 100644 --- a/src/server/lib/dungeon/reward/rewardConfig.ts +++ b/apps/api/src/lib/dungeon/reward/rewardConfig.ts @@ -4,7 +4,7 @@ * 定义每个境界的奖励数值范围和评级倍率 */ -import type { Quality, RealmType } from '@shared/types/constants'; +import type { Quality, RealmType } from '@daoyou/shared/types/constants'; import type { RewardRangeConfig, ValueRange } from './types'; /** diff --git a/src/server/lib/dungeon/reward/types.ts b/apps/api/src/lib/dungeon/reward/types.ts similarity index 95% rename from src/server/lib/dungeon/reward/types.ts rename to apps/api/src/lib/dungeon/reward/types.ts index 2b703d05..16c95267 100644 --- a/src/server/lib/dungeon/reward/types.ts +++ b/apps/api/src/lib/dungeon/reward/types.ts @@ -6,7 +6,7 @@ * - 程序根据境界门槛和评级生成具体数值 */ -import type { ElementType } from '@shared/types/constants'; +import type { ElementType } from '@daoyou/shared/types/constants'; /** * 奖励类型 - 限定为资源引擎支持的类型 diff --git a/src/server/lib/dungeon/rewards.ts b/apps/api/src/lib/dungeon/rewards.ts similarity index 83% rename from src/server/lib/dungeon/rewards.ts rename to apps/api/src/lib/dungeon/rewards.ts index 277a613f..ca490d14 100644 --- a/src/server/lib/dungeon/rewards.ts +++ b/apps/api/src/lib/dungeon/rewards.ts @@ -1,19 +1,19 @@ import type { DbExecutor } from '@server/lib/drizzle/db'; import { generateRealmMaterials } from '@server/lib/services/MaterialRewardService'; -import { getRealmStageLevel } from '@shared/config/realmProgression'; -import { MaterialFactsSchema } from '@shared/items/definitions/materials'; -import { DUNGEON_MATERIAL_QUALITY_CHANCE_BY_REALM } from '@shared/rewards/dungeon-material-quality'; +import { getRealmStageLevel } from '@daoyou/shared/config/realmProgression'; +import { MaterialFactsSchema } from '@daoyou/shared/items/definitions/materials'; +import { DUNGEON_MATERIAL_QUALITY_CHANCE_BY_REALM } from '@daoyou/shared/rewards/dungeon-material-quality'; import { getMapNode, resolveDungeonMapConfig, -} from '@shared/lib/game/mapSystem'; +} from '@daoyou/shared/lib/game/mapSystem'; import { planDungeonReward, planDungeonStepResources, type DungeonRewardEntry, type DungeonRewardSource, -} from '@shared/rewards/dungeon'; -import type { RealmType } from '@shared/types/constants'; +} from '@daoyou/shared/rewards/dungeon'; +import type { RealmType } from '@daoyou/shared/types/constants'; import type { DungeonState } from './types'; /** Resolve library facts before recording rewards; retries reuse recorded facts. */ diff --git a/src/server/lib/dungeon/service_v2.ts b/apps/api/src/lib/dungeon/service_v2.ts similarity index 99% rename from src/server/lib/dungeon/service_v2.ts rename to apps/api/src/lib/dungeon/service_v2.ts index 2f491121..80a55319 100644 --- a/src/server/lib/dungeon/service_v2.ts +++ b/apps/api/src/lib/dungeon/service_v2.ts @@ -10,38 +10,38 @@ import { updateCultivator } from '@server/lib/services/cultivator/CultivatorStat import { resourceEngine } from '@server/lib/services/resource/ResourceEngine'; import { generateAiObject } from '@server/utils/aiClient'; import { stableCompactStringify } from '@server/utils/llmPayload'; -import { getRealmStageNaturalAttributeValue } from '@shared/config/realmProgression'; -import { TYPE_DESCRIPTIONS } from '@shared/engine/material/creation/config'; +import { getRealmStageNaturalAttributeValue } from '@daoyou/shared/config/realmProgression'; +import { TYPE_DESCRIPTIONS } from '@daoyou/shared/engine/material/creation/config'; import type { ResourceOperation, ResourceOperationResult, ResourceOperationSettlement, -} from '@shared/engine/resource/types'; +} from '@daoyou/shared/engine/resource/types'; import { calculateDungeonMaterialCost, calculateDungeonResourceCost, calculateDungeonStatLoss, DUNGEON_LIFESPAN_COST_MAX, -} from '@shared/lib/dungeon/costPolicy'; -import type { SatelliteNode } from '@shared/lib/game/mapSystem'; +} from '@daoyou/shared/lib/dungeon/costPolicy'; +import type { SatelliteNode } from '@daoyou/shared/lib/game/mapSystem'; import { canChallengeDungeonRealm, getMapNode, isSatelliteNode, resolveDungeonMapConfig, -} from '@shared/lib/game/mapSystem'; +} from '@daoyou/shared/lib/game/mapSystem'; import { appendDungeonReward, dungeonRewardItemName, -} from '@shared/rewards/dungeon'; -import type { CultivatorCondition } from '@shared/types/condition'; +} from '@daoyou/shared/rewards/dungeon'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; import { REALM_STAGE_VALUES, REALM_VALUES, RealmType, type RealmStage, -} from '@shared/types/constants'; -import type { Cultivator } from '@shared/types/cultivator'; +} from '@daoyou/shared/types/constants'; +import type { Cultivator } from '@daoyou/shared/types/cultivator'; import { randomUUID } from 'crypto'; import { and, desc, eq, isNull, ne } from 'drizzle-orm'; import { z } from 'zod'; @@ -134,7 +134,7 @@ type DungeonSettlementOptions = { }; type DungeonFlowOptions = { - materialSelections?: import('@shared/contracts/combatV6Dungeon').DungeonMaterialSelection[]; + materialSelections?: import('@daoyou/shared/contracts/combatV6Dungeon').DungeonMaterialSelection[]; deferPersistence?: boolean; lease?: RedisLeaseContext; }; diff --git a/src/server/lib/dungeon/types.ts b/apps/api/src/lib/dungeon/types.ts similarity index 95% rename from src/server/lib/dungeon/types.ts rename to apps/api/src/lib/dungeon/types.ts index f0bdd7f2..6f9b49d4 100644 --- a/src/server/lib/dungeon/types.ts +++ b/apps/api/src/lib/dungeon/types.ts @@ -1,8 +1,8 @@ -import type { ResourceOperation } from '@shared/engine/resource/types'; -import { ItemGrantSchema } from '@shared/inventory'; -import { DUNGEON_COST_RANK_VALUES } from '@shared/lib/dungeon/costPolicy'; -import type { DungeonEndDisposition } from '@shared/lib/dungeon/settlementPolicy'; -import { ENEMY_RACE_VALUES, REALM_STAGE_VALUES } from '@shared/types/constants'; +import type { ResourceOperation } from '@daoyou/shared/engine/resource/types'; +import { ItemGrantSchema } from '@daoyou/shared/inventory'; +import { DUNGEON_COST_RANK_VALUES } from '@daoyou/shared/lib/dungeon/costPolicy'; +import type { DungeonEndDisposition } from '@daoyou/shared/lib/dungeon/settlementPolicy'; +import { ENEMY_RACE_VALUES, REALM_STAGE_VALUES } from '@daoyou/shared/types/constants'; import { z } from 'zod'; // === AI Interaction Schemas === @@ -464,7 +464,7 @@ export type DungeonRecoverAction = 'retry' | 'retry_continue' | 'retry_settle' | 'safe_retreat' | 'force_quit'; export interface DungeonCostLedgerEntry { - materialSelections?: import('@shared/contracts/combatV6Dungeon').DungeonMaterialSelection[]; + materialSelections?: import('@daoyou/shared/contracts/combatV6Dungeon').DungeonMaterialSelection[]; actionId: string; round: number; choiceId?: number; @@ -481,7 +481,7 @@ export interface DungeonGainLedgerEntry { } export interface DungeonPendingAction { - materialSelections?: import('@shared/contracts/combatV6Dungeon').DungeonMaterialSelection[]; + materialSelections?: import('@daoyou/shared/contracts/combatV6Dungeon').DungeonMaterialSelection[]; actionId: string; choiceId?: number; choiceText?: string; @@ -495,11 +495,11 @@ export interface DungeonPendingAction { // === Internal State Management === export interface DungeonState { - encounter?: import('@shared/contracts/combatV6Dungeon').DungeonEncounterView; + encounter?: import('@daoyou/shared/contracts/combatV6Dungeon').DungeonEncounterView; rewardSeed?: number; - v6Rewards?: import('@shared/rewards/dungeon').DungeonRewardEntry[]; + v6Rewards?: import('@daoyou/shared/rewards/dungeon').DungeonRewardEntry[]; beastResources?: Record; - endDisposition?: import('@shared/lib/dungeon/settlementPolicy').DungeonEndDisposition; + endDisposition?: import('@daoyou/shared/lib/dungeon/settlementPolicy').DungeonEndDisposition; runId?: string; cultivatorId: string; mapNodeId: string; diff --git a/apps/api/src/lib/generation/CharacterGenerator.ts b/apps/api/src/lib/generation/CharacterGenerator.ts new file mode 100644 index 00000000..f800739f --- /dev/null +++ b/apps/api/src/lib/generation/CharacterGenerator.ts @@ -0,0 +1,20 @@ +import { buildGeneratedCharacter } from '@daoyou/shared/engine/cultivator/creation/CharacterGenerator'; +import { CultivatorAIRawSchema } from '@daoyou/shared/engine/cultivator/creation/types'; +import { generateAiObject } from '@server/utils/aiClient'; +import { + getCharacterGenerationPrompt, + getCharacterGenerationUserPrompt, +} from './character-prompts'; + +export class CharacterGenerator { + static async generate(userInput: string) { + const response = await generateAiObject({ + system: getCharacterGenerationPrompt(), + prompt: getCharacterGenerationUserPrompt(userInput), + schema: CultivatorAIRawSchema, + name: '修仙真形骨架', + sceneId: 'character-generation', + }); + return buildGeneratedCharacter(response.output, userInput, Math.random); + } +} diff --git a/apps/api/src/lib/generation/MaterialGenerator.ts b/apps/api/src/lib/generation/MaterialGenerator.ts new file mode 100644 index 00000000..ea7c3416 --- /dev/null +++ b/apps/api/src/lib/generation/MaterialGenerator.ts @@ -0,0 +1,134 @@ +import { + BASE_PRICES, + TYPE_MULTIPLIERS, +} from '@daoyou/shared/engine/material/creation/config'; +import { getFallbackMaterialPreset } from '@daoyou/shared/engine/material/creation/fallbackPresets'; +import { MaterialGenerator as MaterialSkeletonGenerator } from '@daoyou/shared/engine/material/creation/MaterialGenerator'; +import { + MaterialAISchema, + type GeneratedMaterial, + type MaterialRandomOptions, + type MaterialSkeleton, +} from '@daoyou/shared/engine/material/creation/types'; +import { + type MaterialType, + type Quality, +} from '@daoyou/shared/types/constants'; +import { generateAiArray } from '@server/utils/aiClient'; +import { + getMaterialGenerationPrompt, + getMaterialGenerationUserPrompt, +} from './material-prompts'; + +export class MaterialGenerator extends MaterialSkeletonGenerator { + /** + * 生成一批随机材料 + * @param count 数量 + * @param options 随机参数配置 + */ + public static async generateRandom( + count: number = 10, + options: MaterialRandomOptions = {}, + ): Promise { + // 1. 生成随机骨架 + const skeletons = this.generateRandomSkeletons(count, options); + // 2. 填充详细信息 (AI) + return this.fillMaterialDetails(skeletons); + } + + /** + * 根据指定的骨架生成材料 (批量) + * 用于系统奖励、副本掉落等确定性场景 + * @param skeletons 指定的骨架列表 + */ + public static async generateFromSkeletons( + skeletons: MaterialSkeleton[], + ): Promise { + if (skeletons.some((skeleton) => skeleton.type === 'seed')) { + throw new Error('灵植种子必须使用 SpiritSeedGenerator 生成'); + } + return this.fillMaterialDetails(skeletons); + } + + // ===== Private Core Logic ===== + + /** + * 核心方法:调用 AI 为骨架填充 Name, Description, Element + */ + private static async fillMaterialDetails( + skeletons: MaterialSkeleton[], + ): Promise { + if (skeletons.length === 0) return []; + + const prompt = getMaterialGenerationPrompt(); + const userPrompt = getMaterialGenerationUserPrompt(skeletons); + try { + const aiResponse = await generateAiArray({ + system: prompt, + prompt: userPrompt, + elementSchema: MaterialAISchema, + name: 'MaterialTextList', + sceneId: 'material-generation', + }); + + // 组合结果 + return skeletons.map((skeleton, index) => { + const aiData = aiResponse.output[index] || { + name: '未知材料', + description: '天道感应模糊...', + element: skeleton.forcedElement || '金', + }; + + // 最终元素:优先使用骨架强制指定的,否则使用 AI 生成的 + const finalElement = skeleton.forcedElement || aiData.element; + + // 计算价格 + const price = this.calculatePrice(skeleton.rank, skeleton.type); + + return { + name: aiData.name, + type: skeleton.type, + rank: skeleton.rank, + element: finalElement, + description: aiData.description, + quantity: skeleton.quantity, + price, + }; + }); + } catch (error) { + console.error('Material Generation Failed:', error); + // AI 失败时仍返回可发放的材料,避免奖励邮件出现空附件 + return this.buildFallbackMaterials(skeletons); + } + } + + private static buildFallbackMaterials( + skeletons: MaterialSkeleton[], + ): GeneratedMaterial[] { + return skeletons.map((skeleton) => { + const preset = getFallbackMaterialPreset(skeleton.type, skeleton.rank); + const finalElement = skeleton.forcedElement || preset.element; + return { + name: preset.name, + type: skeleton.type, + rank: skeleton.rank, + element: finalElement, + description: preset.description, + quantity: skeleton.quantity, + price: this.calculatePrice(skeleton.rank, skeleton.type), + }; + }); + } + + private static calculatePrice(rank: Quality, type: MaterialType): number { + const base = BASE_PRICES[rank]; + const multiplier = TYPE_MULTIPLIERS[type] || 1.0; + const variation = 0.8 + Math.random() * 0.4; // +/- 20% + let price = Math.floor(base * multiplier * variation); + + if (price > 1000) price = Math.floor(price / 100) * 100; + else if (price > 100) price = Math.floor(price / 10) * 10; + + return Math.max(1, price); + } +} diff --git a/apps/api/src/lib/generation/SpiritSeedGenerator.ts b/apps/api/src/lib/generation/SpiritSeedGenerator.ts new file mode 100644 index 00000000..9521cbcb --- /dev/null +++ b/apps/api/src/lib/generation/SpiritSeedGenerator.ts @@ -0,0 +1,216 @@ +import { + CREATION_MATERIAL_SEMANTIC_TAGS, + CreationTags, +} from '@daoyou/shared/engine/shared/tag-domain'; +import { getSpiritFieldQualityBalance } from '@daoyou/shared/engine/spirit-field/config'; +import { buildSpiritFieldSeedMaterialFromPlant } from '@daoyou/shared/engine/spirit-field/seedMaterial'; +import { SpiritSeedGenerator as SpiritSeedSkeletonGenerator } from '@daoyou/shared/engine/spirit-field/SpiritSeedGenerator'; +import { + SPIRIT_FIELD_CULTIVATION_METHODS, + SPIRIT_FIELD_OUTCOME_KINDS, + SPIRIT_SEED_GROWTH_FORMS, + SPIRIT_SEED_GROWTH_TRAITS, + SPIRIT_SEED_HABITAT_TAGS, + SPIRIT_SEED_HARVEST_PARTS, + SPIRIT_SEED_USE_TAGS, + type SpiritFieldPlantSnapshot, + type SpiritSeedIdentity, + type SpiritSeedRandomOptions, + type SpiritSeedSkeleton, +} from '@daoyou/shared/engine/spirit-field/types'; +import { + ELEMENT_VALUES, + type ElementType, + type Quality, +} from '@daoyou/shared/types/constants'; +import type { Material } from '@daoyou/shared/types/cultivator'; +import { renderPromptSystem, renderPromptUser } from '@server/lib/prompts'; +import { generateAiArray } from '@server/utils/aiClient'; +import { z } from 'zod'; + +const SpiritSeedAISchema = z + .object({ + seedName: z.string().trim().min(2).max(12), + seedDescription: z.string().trim().min(12).max(100), + clueTexts: z.array(z.string().trim().min(6).max(48)).min(2).max(3), + element: z.enum(ELEMENT_VALUES), + growthForm: z.enum(SPIRIT_SEED_GROWTH_FORMS), + harvestPart: z.enum(SPIRIT_SEED_HARVEST_PARTS), + preferredMethods: z + .array(z.enum(SPIRIT_FIELD_CULTIVATION_METHODS)) + .min(2) + .max(6), + avoidedMethods: z.array(z.enum(SPIRIT_FIELD_CULTIVATION_METHODS)).max(4), + preferredHabitats: z.array(z.enum(SPIRIT_SEED_HABITAT_TAGS)).min(1).max(3), + avoidedHabitats: z.array(z.enum(SPIRIT_SEED_HABITAT_TAGS)).max(2), + growthTraits: z.array(z.enum(SPIRIT_SEED_GROWTH_TRAITS)).min(1).max(4), + useTags: z.array(z.enum(SPIRIT_SEED_USE_TAGS)).min(1).max(4), + outcomeBiases: z.array(z.enum(SPIRIT_FIELD_OUTCOME_KINDS)).min(1).max(3), + creationTags: z + .array(z.enum(CREATION_MATERIAL_SEMANTIC_TAGS)) + .min(1) + .max(5), + }) + .strict(); + +export interface SpiritSeedBatchSpec { + rank: Quality; + quantity: number; + element?: ElementType; + regionTags?: string[]; +} + +function requestList(skeletons: SpiritSeedSkeleton[]): string { + return skeletons + .map( + (item, index) => + `${index + 1}. 品质=${item.rank};元素=${item.forcedElement ?? '自选'};地域=${item.regionTags?.join('、') || '无'}`, + ) + .join('\n'); +} +function fallbackIdentity( + skeleton: SpiritSeedSkeleton, + index: number, +): SpiritSeedIdentity { + const element = + skeleton.forcedElement ?? + ELEMENT_VALUES[index % ELEMENT_VALUES.length] ?? + '木'; + return { + seedName: `${element}纹眠籽`, + seedDescription: `灰青种壳上浮着一线${element}行微光,握在掌中时灵机时隐时现。`, + clueTexts: [ + '种壳遇到温和灵机时会轻轻发热', + '其内生机不喜骤然催逼,似宜循序培护', + ], + element, + growthForm: 'herb', + harvestPart: 'leaf', + preferredMethods: [ + 'seasonal_nurture', + 'intrinsic_infusion', + 'natural_form', + ], + avoidedMethods: ['monster_blood'], + preferredHabitats: ['mountain'], + avoidedHabitats: [], + growthTraits: ['qi-sensitive'], + useTags: ['alchemy'], + outcomeBiases: ['herb'], + creationTags: [ + CreationTags.MATERIAL.SEMANTIC_WOOD, + CreationTags.MATERIAL.SEMANTIC_ALCHEMY, + ], + }; +} + +function normalizeIdentityClues( + identity: SpiritSeedIdentity, + fallback: SpiritSeedIdentity, +): SpiritSeedIdentity { + const hiddenTokens = [ + ...identity.preferredMethods, + ...identity.avoidedMethods, + ...identity.preferredHabitats, + ...identity.avoidedHabitats, + ...identity.growthTraits, + ...identity.useTags, + ...identity.outcomeBiases, + ...identity.creationTags, + ]; + const clueTexts = identity.clueTexts.filter( + (clue) => + !hiddenTokens.some((token) => clue.includes(token)) && + !/偏好|忌讳|内部(?:规则|标签)|产物倾向|概率|分数|评分/.test(clue), + ); + const avoidedMethods = identity.avoidedMethods.filter( + (method) => !identity.preferredMethods.includes(method), + ); + const seedDescription = + hiddenTokens.some((token) => identity.seedDescription.includes(token)) || + /偏好|忌讳|内部(?:规则|标签)|产物倾向|概率|分数|评分/.test( + identity.seedDescription, + ) + ? fallback.seedDescription + : identity.seedDescription; + return { + ...identity, + seedDescription, + clueTexts: + clueTexts.length >= 2 ? clueTexts.slice(0, 3) : fallback.clueTexts, + avoidedMethods, + }; +} + +export class SpiritSeedGenerator extends SpiritSeedSkeletonGenerator { + static async generateRandom( + count: number, + options: SpiritSeedRandomOptions = {}, + ): Promise>> { + return this.generateFromSkeletons( + this.generateRandomSkeletons(count, options), + ); + } + static async generateBatches( + batches: readonly SpiritSeedBatchSpec[], + ): Promise>> { + return this.generateFromSkeletons( + batches.map((batch) => ({ + rank: batch.rank, + quantity: Math.max(1, Math.floor(batch.quantity)), + forcedElement: batch.element, + regionTags: batch.regionTags?.slice(0, 8), + })), + ); + } + static async generateFromSkeletons( + skeletons: SpiritSeedSkeleton[], + ): Promise>> { + if (skeletons.length === 0) return []; + let identities: SpiritSeedIdentity[]; + try { + const response = await generateAiArray({ + system: renderPromptSystem('spirit-seed-generation'), + prompt: renderPromptUser('spirit-seed-generation', { + requestList: requestList(skeletons), + }), + elementSchema: SpiritSeedAISchema, + name: 'SpiritSeedIdentityList', + description: '灵种身份、隐性习性与玩家线索', + sceneId: 'spirit-seed-generation', + maxOutputTokens: Math.min( + 8_000, + Math.max(1_600, skeletons.length * 1_000), + ), + }); + identities = skeletons.map((skeleton, index) => { + const fallback = fallbackIdentity(skeleton, index); + const identity = { + ...(response.output[index] ?? fallback), + element: + skeleton.forcedElement ?? + response.output[index]?.element ?? + fallback.element, + }; + return normalizeIdentityClues(identity, fallback); + }); + } catch (error) { + console.error('[spirit-seed-generation] fallback', error); + identities = skeletons.map(fallbackIdentity); + } + return skeletons.map((skeleton, index) => { + const identity = identities[index] ?? fallbackIdentity(skeleton, index); + const balance = getSpiritFieldQualityBalance(skeleton.rank); + const plant: SpiritFieldPlantSnapshot = { + id: globalThis.crypto.randomUUID(), + ...identity, + quality: skeleton.rank, + minRealm: balance.minRealm, + stageDurationMs: balance.stageDurationMs, + baseYieldMin: balance.baseYield[0], + baseYieldMax: balance.baseYield[1], + }; + return buildSpiritFieldSeedMaterialFromPlant(plant, skeleton.quantity); + }); + } +} diff --git a/src/shared/engine/cultivator/creation/prompts.ts b/apps/api/src/lib/generation/character-prompts.ts similarity index 100% rename from src/shared/engine/cultivator/creation/prompts.ts rename to apps/api/src/lib/generation/character-prompts.ts diff --git a/src/shared/engine/material/creation/prompts.ts b/apps/api/src/lib/generation/material-prompts.ts similarity index 79% rename from src/shared/engine/material/creation/prompts.ts rename to apps/api/src/lib/generation/material-prompts.ts index 9e21acd4..2543eef8 100644 --- a/src/shared/engine/material/creation/prompts.ts +++ b/apps/api/src/lib/generation/material-prompts.ts @@ -1,6 +1,9 @@ +import { + QUALITY_TO_RANK, + TYPE_DESCRIPTIONS, +} from '@daoyou/shared/engine/material/creation/config'; +import type { MaterialSkeleton } from '@daoyou/shared/engine/material/creation/types'; import { renderPromptSystem, renderPromptUser } from '@server/lib/prompts'; -import { QUALITY_TO_RANK, TYPE_DESCRIPTIONS } from './config'; -import type { MaterialSkeleton } from './types'; export function getMaterialGenerationPrompt(): string { return renderPromptSystem('material-generation'); diff --git a/apps/api/src/lib/http/context.ts b/apps/api/src/lib/http/context.ts new file mode 100644 index 00000000..76954da5 --- /dev/null +++ b/apps/api/src/lib/http/context.ts @@ -0,0 +1,19 @@ +import type { ActiveCultivatorRef, AuthUser } from '@server/lib/auth/types'; +import type { LlmByokConfig } from '@daoyou/shared/config/llm'; +import { AsyncLocalStorage } from 'node:async_hooks'; + +export type RequestContext = { + user?: AuthUser; + activeCultivatorRef?: ActiveCultivatorRef; + llmConfig?: LlmByokConfig; +}; + +const contextStore = new AsyncLocalStorage(); + +export function runWithContext(context: RequestContext, fn: () => T): T { + return contextStore.run(context, fn); +} + +export function getCurrentContext(): RequestContext | undefined { + return contextStore.getStore(); +} diff --git a/src/server/lib/http/cors.ts b/apps/api/src/lib/http/cors.ts similarity index 100% rename from src/server/lib/http/cors.ts rename to apps/api/src/lib/http/cors.ts diff --git a/apps/api/src/lib/http/errors.ts b/apps/api/src/lib/http/errors.ts new file mode 100644 index 00000000..38b38783 --- /dev/null +++ b/apps/api/src/lib/http/errors.ts @@ -0,0 +1,58 @@ +import { getRuntimeEnvironment } from '@server/lib/config/environment'; +import { + isRedisLockContention, + LockAcquisitionError, + RedisLeaseLostError, +} from '@server/lib/redis/lock'; +import { CombatV6MutationLockedError } from '@server/lib/services/combat-v6/CombatV6MutationGuard'; + +export function errorBody( + message: string, + status = 500, + details?: unknown, +): Response { + const body: { success: false; error: string; details?: unknown } = { + success: false, + error: message, + }; + + if (getRuntimeEnvironment().NODE_ENV === 'development' && details) { + body.details = details instanceof Error ? details.message : details; + } + + return Response.json(body, { status }); +} + +export function redisLockErrorResponse(error: unknown): Response | null { + if (error instanceof CombatV6MutationLockedError) + return Response.json( + { success: false, code: error.code, error: error.message }, + { status: 409 }, + ); + if (isRedisLockContention(error)) { + const response = errorBody('操作正在处理中,请稍后重试', 429); + response.headers.set('Retry-After', '1'); + return response; + } + if (error instanceof LockAcquisitionError) { + const response = errorBody('Redis 协调服务暂不可用,请稍后重试', 503); + response.headers.set('Retry-After', '1'); + return response; + } + if (error instanceof RedisLeaseLostError) { + const response = errorBody(error.message, 503); + response.headers.set('Retry-After', '1'); + return response; + } + if ( + typeof error === 'object' && + error !== null && + 'code' in error && + error.code === '55P03' + ) { + const response = errorBody('数据库事务繁忙,请稍后重试', 503); + response.headers.set('Retry-After', '1'); + return response; + } + return null; +} diff --git a/apps/api/src/lib/http/originPolicy.ts b/apps/api/src/lib/http/originPolicy.ts new file mode 100644 index 00000000..29966eef --- /dev/null +++ b/apps/api/src/lib/http/originPolicy.ts @@ -0,0 +1,20 @@ +import { getRuntimeEnvironment } from '@server/lib/config/environment'; +import { isAllowedPublicWebOrigin, normalizeOrigin } from './origins'; + +function getApiSelfOrigin() { + return normalizeOrigin(getRuntimeEnvironment().BETTER_AUTH_URL); +} + +export function isAllowedWriteOrigin(origin: string | undefined | null) { + if (!origin) { + return true; + } + + const normalized = normalizeOrigin(origin); + if (!normalized) { + return false; + } + + const selfOrigin = getApiSelfOrigin(); + return normalized === selfOrigin || isAllowedPublicWebOrigin(normalized); +} diff --git a/src/server/lib/http/origins.ts b/apps/api/src/lib/http/origins.ts similarity index 87% rename from src/server/lib/http/origins.ts rename to apps/api/src/lib/http/origins.ts index 74e3b05f..329c2646 100644 --- a/src/server/lib/http/origins.ts +++ b/apps/api/src/lib/http/origins.ts @@ -1,3 +1,4 @@ +import { getRuntimeEnvironment } from '@server/lib/config/environment'; export function normalizeOrigin(value: string | undefined | null) { const trimmed = value?.trim(); if (!trimmed) { @@ -27,7 +28,7 @@ function splitOrigins(value: string | undefined) { } export function getPublicWebOrigins() { - return splitOrigins(process.env.PUBLIC_WEB_ORIGINS); + return splitOrigins(getRuntimeEnvironment().PUBLIC_WEB_ORIGINS); } export function isAllowedPublicWebOrigin(origin: string | undefined | null) { diff --git a/src/server/lib/http/realtimeOrigin.ts b/apps/api/src/lib/http/realtimeOrigin.ts similarity index 100% rename from src/server/lib/http/realtimeOrigin.ts rename to apps/api/src/lib/http/realtimeOrigin.ts diff --git a/src/server/lib/http/requestIp.ts b/apps/api/src/lib/http/requestIp.ts similarity index 52% rename from src/server/lib/http/requestIp.ts rename to apps/api/src/lib/http/requestIp.ts index b144371f..0424565a 100644 --- a/src/server/lib/http/requestIp.ts +++ b/apps/api/src/lib/http/requestIp.ts @@ -1,5 +1,3 @@ -import type { Context } from 'hono'; - function firstForwardedIp(value?: string | null): string | undefined { const first = value?.split(',')[0]?.trim(); return first || undefined; @@ -10,10 +8,10 @@ function normalizedHeaderIp(value?: string | null): string | undefined { return normalized || undefined; } -export function getRequestIp(context: Context): string | undefined { +export function getRequestIp(headers: Headers): string | undefined { return ( - normalizedHeaderIp(context.req.header('cf-connecting-ip')) ?? - firstForwardedIp(context.req.header('x-forwarded-for')) ?? - normalizedHeaderIp(context.req.header('x-real-ip')) + normalizedHeaderIp(headers.get('cf-connecting-ip')) ?? + firstForwardedIp(headers.get('x-forwarded-for')) ?? + normalizedHeaderIp(headers.get('x-real-ip')) ); } diff --git a/src/server/lib/jobs/internalCron.ts b/apps/api/src/lib/jobs/internalCron.ts similarity index 99% rename from src/server/lib/jobs/internalCron.ts rename to apps/api/src/lib/jobs/internalCron.ts index 1aface0b..0668a10e 100644 --- a/src/server/lib/jobs/internalCron.ts +++ b/apps/api/src/lib/jobs/internalCron.ts @@ -33,7 +33,7 @@ import { sendSponsorshipAdminDigest, } from '@server/lib/services/SponsorshipApplicationService'; import { getSponsorshipProvider } from '@server/lib/sponsorship/providerRegistry'; -import { RANKING_REWARDS, REALM_VALUES } from '@shared/types/constants'; +import { RANKING_REWARDS, REALM_VALUES } from '@daoyou/shared/types/constants'; import { eq } from 'drizzle-orm'; const RANK_REWARD_SETTLED_PREFIX = 'golden_rank:weekly_rewards:settled:'; diff --git a/apps/api/src/lib/jobs/schedules.ts b/apps/api/src/lib/jobs/schedules.ts new file mode 100644 index 00000000..503e7504 --- /dev/null +++ b/apps/api/src/lib/jobs/schedules.ts @@ -0,0 +1,40 @@ +import type { BackgroundCommandType } from '@daoyou/shared/contracts/backgroundCommands'; + +const AUCTION_EXPIRE_SCHEDULE = '*/2 * * * *'; +// Schedules use UTC. 16:00 UTC equals 00:00 Asia/Shanghai. +const RANK_REWARDS_SCHEDULE = '0 16 * * *'; +// Market refresh: every 5 minutes to pre-generate listings before 15-min cycle ends +const MARKET_REFRESH_SCHEDULE = '*/5 * * * *'; +const RESOURCE_REPLAY_CLEANUP_SCHEDULE = '30 18 * * *'; +const EXPIRED_DATA_CLEANUP_SCHEDULE = '45 18 * * *'; +// 17:00 UTC equals 01:00 Asia/Shanghai. +const MATERIAL_LIBRARY_DAILY_GENERATION_SCHEDULE = '0 17 * * *'; +const SPONSORSHIP_RECONCILE_SCHEDULE = '*/10 * * * *'; +const SPONSORSHIP_DEEP_RECONCILE_SCHEDULE = '15 19 * * *'; +const SPONSORSHIP_CLEANUP_SCHEDULE = '30 19 * * *'; +const SPONSORSHIP_ADMIN_DIGEST_SCHEDULE = '0 1 * * *'; + +export const BACKGROUND_SCHEDULES = [ + { type: 'auction.expire', expression: AUCTION_EXPIRE_SCHEDULE }, + { type: 'ranking.rewards.distribute', expression: RANK_REWARDS_SCHEDULE }, + { type: 'market.refresh', expression: MARKET_REFRESH_SCHEDULE }, + { + type: 'resource-replay.cleanup', + expression: RESOURCE_REPLAY_CLEANUP_SCHEDULE, + }, + { type: 'expired-data.cleanup', expression: EXPIRED_DATA_CLEANUP_SCHEDULE }, + { + type: 'material-library.generate', + expression: MATERIAL_LIBRARY_DAILY_GENERATION_SCHEDULE, + }, + { type: 'sponsorship.reconcile', expression: SPONSORSHIP_RECONCILE_SCHEDULE }, + { + type: 'sponsorship.deep-reconcile', + expression: SPONSORSHIP_DEEP_RECONCILE_SCHEDULE, + }, + { type: 'sponsorship.cleanup', expression: SPONSORSHIP_CLEANUP_SCHEDULE }, + { + type: 'sponsorship.admin-digest', + expression: SPONSORSHIP_ADMIN_DIGEST_SCHEDULE, + }, +] satisfies ReadonlyArray<{ type: BackgroundCommandType; expression: string }>; diff --git a/src/server/lib/lifespan/handleLifespan.ts b/apps/api/src/lib/lifespan/handleLifespan.ts similarity index 92% rename from src/server/lib/lifespan/handleLifespan.ts rename to apps/api/src/lib/lifespan/handleLifespan.ts index 13db109a..5f51c9ed 100644 --- a/src/server/lib/lifespan/handleLifespan.ts +++ b/apps/api/src/lib/lifespan/handleLifespan.ts @@ -1,14 +1,14 @@ import { cultivators } from '@server/lib/drizzle/schema'; import type { DbTransaction } from '@server/lib/drizzle/db'; -import { invalidateActiveCultivatorRef } from '@server/lib/hono/middleware'; +import { invalidateActiveCultivatorRef } from '@server/lib/auth/activeCultivator'; import { findActiveCultivatorOwnerId } from '@server/lib/repositories/cultivatorRepository'; import { updateCultivator } from '@server/lib/services/cultivator/CultivatorStateRepository'; -import type { BreakthroughModifiers } from '@server/utils/breakthroughCalculator'; +import type { BreakthroughModifiers } from '@daoyou/shared/engine/cultivation/breakthroughCalculator'; import type { LifespanExhaustedStoryPayload, RetreatStoryCultivator, } from '@server/utils/prompts'; -import { RealmStage, RealmType } from '@shared/types/constants'; +import { RealmStage, RealmType } from '@daoyou/shared/types/constants'; import { eq } from 'drizzle-orm'; export interface ConsumeLifespanResult { diff --git a/src/server/lib/llm/metricsStore.ts b/apps/api/src/lib/llm/metricsStore.ts similarity index 98% rename from src/server/lib/llm/metricsStore.ts rename to apps/api/src/lib/llm/metricsStore.ts index 747e455e..1d1e0c0b 100644 --- a/src/server/lib/llm/metricsStore.ts +++ b/apps/api/src/lib/llm/metricsStore.ts @@ -1,3 +1,4 @@ +import { getRuntimeEnvironment } from '@server/lib/config/environment'; import { redis } from '@server/lib/redis'; import type { LlmCallMetrics, LlmSceneId } from '@server/lib/llm/types'; @@ -60,7 +61,7 @@ function clamp(value: number, min: number, max: number): number { } function hasRedisConfigured(): boolean { - return Boolean(process.env.REDIS_URL); + return Boolean(getRuntimeEnvironment().REDIS_URL); } function appendMemoryEvent(event: LlmCallMetricEvent): void { diff --git a/src/server/lib/llm/providers.ts b/apps/api/src/lib/llm/providers.ts similarity index 87% rename from src/server/lib/llm/providers.ts rename to apps/api/src/lib/llm/providers.ts index 71c0cdb6..19562273 100644 --- a/src/server/lib/llm/providers.ts +++ b/apps/api/src/lib/llm/providers.ts @@ -1,10 +1,11 @@ +import { getRuntimeEnvironment } from '@server/lib/config/environment'; import { createAlibaba } from '@ai-sdk/alibaba'; import { createDeepSeek, deepSeek } from '@ai-sdk/deepseek'; import type { LanguageModel } from 'ai'; import { LLM_PROVIDER_DEFAULT_MODELS, type LlmProviderId, -} from '@shared/config/llm'; +} from '@daoyou/shared/config/llm'; export interface LlmProviderDef { id: LlmProviderId; @@ -17,7 +18,7 @@ export interface LlmProviderDef { } const ALIBABA_BASE_URL = - process.env.ALIBABA_BASE_URL?.trim() || + getRuntimeEnvironment().ALIBABA_BASE_URL?.trim() || 'https://dashscope.aliyuncs.com/compatible-mode/v1'; export const LLM_PROVIDERS: Record = { diff --git a/src/server/lib/llm/types.ts b/apps/api/src/lib/llm/types.ts similarity index 95% rename from src/server/lib/llm/types.ts rename to apps/api/src/lib/llm/types.ts index 01ea493c..385c78d2 100644 --- a/src/server/lib/llm/types.ts +++ b/apps/api/src/lib/llm/types.ts @@ -1,4 +1,4 @@ -import type { LlmProviderId } from '@shared/config/llm'; +import type { LlmProviderId } from '@daoyou/shared/config/llm'; export type LlmSceneId = | 'alchemy-formula-analysis' diff --git a/src/server/lib/mq/DomainEventExecutor.ts b/apps/api/src/lib/mq/DomainEventExecutor.ts similarity index 96% rename from src/server/lib/mq/DomainEventExecutor.ts rename to apps/api/src/lib/mq/DomainEventExecutor.ts index 3e6e4cf2..cb945908 100644 --- a/src/server/lib/mq/DomainEventExecutor.ts +++ b/apps/api/src/lib/mq/DomainEventExecutor.ts @@ -7,7 +7,7 @@ import { import type { DomainEventEnvelope, DomainEventType, -} from '@shared/contracts/domainEvents'; +} from '@daoyou/shared/contracts/domainEvents'; type DomainEventSkipResult = { status: 'already_processed' }; diff --git a/src/server/lib/mq/backgroundCommandConsumer.ts b/apps/api/src/lib/mq/backgroundCommandConsumer.ts similarity index 99% rename from src/server/lib/mq/backgroundCommandConsumer.ts rename to apps/api/src/lib/mq/backgroundCommandConsumer.ts index efc86724..2b8bcfe8 100644 --- a/src/server/lib/mq/backgroundCommandConsumer.ts +++ b/apps/api/src/lib/mq/backgroundCommandConsumer.ts @@ -3,7 +3,7 @@ import { parseBackgroundCommandEnvelope, type BackgroundCommandEnvelope, type BackgroundCommandType, -} from '@shared/contracts/backgroundCommands'; +} from '@daoyou/shared/contracts/backgroundCommands'; import { JSONCodec, type ConsumerMessages, type JsMsg } from 'nats'; import { runAuctionExpireJob, diff --git a/src/server/lib/mq/backgroundCommandPublisher.ts b/apps/api/src/lib/mq/backgroundCommandPublisher.ts similarity index 95% rename from src/server/lib/mq/backgroundCommandPublisher.ts rename to apps/api/src/lib/mq/backgroundCommandPublisher.ts index f184193d..cf1b2332 100644 --- a/src/server/lib/mq/backgroundCommandPublisher.ts +++ b/apps/api/src/lib/mq/backgroundCommandPublisher.ts @@ -4,7 +4,7 @@ import { BACKGROUND_COMMAND_STREAM, parseBackgroundCommandEnvelope, type BackgroundCommandType, -} from '@shared/contracts/backgroundCommands'; +} from '@daoyou/shared/contracts/backgroundCommands'; import { JSONCodec } from 'nats'; import { randomUUID } from 'node:crypto'; diff --git a/src/server/lib/mq/combatV6Messaging.ts b/apps/api/src/lib/mq/combatV6Messaging.ts similarity index 89% rename from src/server/lib/mq/combatV6Messaging.ts rename to apps/api/src/lib/mq/combatV6Messaging.ts index 6efd7b74..1fc2bdce 100644 --- a/src/server/lib/mq/combatV6Messaging.ts +++ b/apps/api/src/lib/mq/combatV6Messaging.ts @@ -1,4 +1,3 @@ -import { startHuntWorld, stopHuntWorld } from '../services/hunts/HuntWorldService'; import { getJetStreamClient } from '@server/lib/nats'; import { archiveCombatV6Replay, @@ -19,12 +18,16 @@ import { parseCombatV6Replay, type CombatV6ReplayArchiveMessageV1, type CombatV6ReplayV1, -} from '@shared/contracts/combatV6Runtime'; +} from '@daoyou/shared/contracts/combatV6Runtime'; import { DOMAIN_EVENT_STREAM, parseDomainEventEnvelope, -} from '@shared/contracts/domainEvents'; +} from '@daoyou/shared/contracts/domainEvents'; import { JSONCodec, type ConsumerMessages, type JsMsg } from 'nats'; +import { + startHuntWorld, + stopHuntWorld, +} from '../services/hunts/HuntWorldService'; import { COMBAT_V6_REPLAY_ARCHIVE_CONSUMER, consumerRetryDelayMs, @@ -37,6 +40,18 @@ let messages: ConsumerMessages | undefined; let consumerTask: Promise | undefined; let healthy = false; let stopping = false; +const publications = new Set>(); + +function scheduleOutboxPublish(): void { + const work = publishOutboxes() + .catch((error) => + console.warn('[combat-v6] outbox publish failed', { error }), + ) + .finally(() => { + publications.delete(work); + }); + publications.add(work); +} async function publishOutboxes(): Promise { await combatV6TrainingSessionStore.expireDue(); @@ -148,25 +163,18 @@ export async function startCombatV6Messaging(): Promise { })().finally(() => { healthy = false; }); - timer = setInterval( - () => - void publishOutboxes().catch((error) => - console.warn('[combat-v6] outbox publish failed', { error }), - ), - 10_000, - ); + timer = setInterval(scheduleOutboxPublish, 10_000); timer.unref(); - void publishOutboxes().catch((error) => - console.warn('[combat-v6] initial outbox publish failed', { error }), - ); + scheduleOutboxPublish(); } export async function stopCombatV6Messaging(): Promise { - await stopHuntWorld(); - await stopArenaV6Coordinator(); - stopping = true; if (timer) clearInterval(timer); timer = undefined; + await stopHuntWorld(); + await stopArenaV6Coordinator(); + await Promise.all([...publications]); + stopping = true; messages?.stop(); await consumerTask; consumerTask = undefined; diff --git a/src/server/lib/mq/domainEventConsumer.ts b/apps/api/src/lib/mq/domainEventConsumer.ts similarity index 99% rename from src/server/lib/mq/domainEventConsumer.ts rename to apps/api/src/lib/mq/domainEventConsumer.ts index 1bea3bac..a75c53e5 100644 --- a/src/server/lib/mq/domainEventConsumer.ts +++ b/apps/api/src/lib/mq/domainEventConsumer.ts @@ -4,7 +4,7 @@ import { parseDomainEventEnvelope, type DomainEventEnvelope, type DomainEventType, -} from '@shared/contracts/domainEvents'; +} from '@daoyou/shared/contracts/domainEvents'; import { JSONCodec, type ConsumerMessages, type JsMsg } from 'nats'; import { consumerRetryDelayMs, diff --git a/src/server/lib/mq/domainEventRegistry.ts b/apps/api/src/lib/mq/domainEventRegistry.ts similarity index 58% rename from src/server/lib/mq/domainEventRegistry.ts rename to apps/api/src/lib/mq/domainEventRegistry.ts index 5e7f45d3..753a76a4 100644 --- a/src/server/lib/mq/domainEventRegistry.ts +++ b/apps/api/src/lib/mq/domainEventRegistry.ts @@ -1,4 +1,3 @@ -import { projectHuntReward } from '../services/hunts/HuntRewardProjector'; import { db } from '@server/lib/drizzle/db'; import { closeNatsConnection, getNatsConnection } from '@server/lib/nats'; import { claimMessageForConsumer } from '@server/lib/repositories/messageConsumptionRepository'; @@ -22,7 +21,8 @@ import { import { isDomainEventType, type DomainEventEnvelope, -} from '@shared/contracts/domainEvents'; +} from '@daoyou/shared/contracts/domainEvents'; +import { projectHuntReward } from '../services/hunts/HuntRewardProjector'; import { isBackgroundCommandConsumerHealthy, startBackgroundCommandConsumer, @@ -49,101 +49,114 @@ let registered = false; export async function registerMessageInfrastructure(): Promise { if (registered) return; - - await getNatsConnection(); - await ensureMessageTopology(); - await Promise.all([ - startBackgroundCommandConsumer(), - startDomainEventConsumer({ - consumerName: DOMAIN_EVENT_CONSUMERS.systemMailProjector.name, - concurrency: DOMAIN_EVENT_CONSUMERS.systemMailProjector.concurrency, - acceptedTypes: ['cultivator.mail-audience.observed'], - handle: async (event) => { - if (!isDomainEventType(event, 'cultivator.mail-audience.observed')) - throw new Error('系统邮件事件类型错误'); - await executeDomainEvent({ - consumerName: DOMAIN_EVENT_CONSUMERS.systemMailProjector.name, - source: 'system_mail_audience', - event, - handle: projectSystemMailAudience, - }); - }, - }), - startCombatV6Messaging(), - startDomainEventConsumer({ - consumerName: DOMAIN_EVENT_CONSUMERS.combatV6Condition.name, - concurrency: DOMAIN_EVENT_CONSUMERS.combatV6Condition.concurrency, - acceptedTypes: ['combat.v6.battle.finished'], - handle: async (event) => { - if (event.type === 'combat.v6.battle.finished') { - const data = ( - event as DomainEventEnvelope<'combat.v6.battle.finished'> - ).data; - if (data.sourceType === 'hunt') { - await projectHuntReward(data.battleId); - } else if (data.sourceType !== 'arena-sparring') { - await projectCombatV6Condition(data.battleId); + try { + await getNatsConnection(); + await ensureMessageTopology(); + const starts = await Promise.allSettled([ + startBackgroundCommandConsumer(), + startDomainEventConsumer({ + consumerName: DOMAIN_EVENT_CONSUMERS.systemMailProjector.name, + concurrency: DOMAIN_EVENT_CONSUMERS.systemMailProjector.concurrency, + acceptedTypes: ['cultivator.mail-audience.observed'], + handle: async (event) => { + if (!isDomainEventType(event, 'cultivator.mail-audience.observed')) + throw new Error('系统邮件事件类型错误'); + await executeDomainEvent({ + consumerName: DOMAIN_EVENT_CONSUMERS.systemMailProjector.name, + source: 'system_mail_audience', + event, + handle: projectSystemMailAudience, + }); + }, + }), + startCombatV6Messaging(), + startDomainEventConsumer({ + consumerName: DOMAIN_EVENT_CONSUMERS.combatV6Condition.name, + concurrency: DOMAIN_EVENT_CONSUMERS.combatV6Condition.concurrency, + acceptedTypes: ['combat.v6.battle.finished'], + handle: async (event) => { + if (event.type === 'combat.v6.battle.finished') { + const data = ( + event as DomainEventEnvelope<'combat.v6.battle.finished'> + ).data; + if (data.sourceType === 'hunt') { + await projectHuntReward(data.battleId); + } else if (data.sourceType !== 'arena-sparring') { + await projectCombatV6Condition(data.battleId); + } } - } - }, - }), - startDomainEventConsumer({ - consumerName: DOMAIN_EVENT_CONSUMERS.sectFacilityProjector.name, - concurrency: DOMAIN_EVENT_CONSUMERS.sectFacilityProjector.concurrency, - acceptedTypes: ['sect.construction.donated'], - handle: handleSectConstructionEvent, - }), - startDomainEventConsumer({ - consumerName: DOMAIN_EVENT_CONSUMERS.taskProjector.name, - concurrency: DOMAIN_EVENT_CONSUMERS.taskProjector.concurrency, - acceptedTypes: [ - 'alchemy.craft.completed', - 'ranking.challenge.completed', - 'dungeon.run.settled', - 'yield.claimed', - ], - handle: handleTaskEvent, - }), - startDomainEventConsumer({ - consumerName: DOMAIN_EVENT_CONSUMERS.yieldRewardProjector.name, - concurrency: DOMAIN_EVENT_CONSUMERS.yieldRewardProjector.concurrency, - acceptedTypes: ['yield.claimed'], - handle: handleYieldRewardEvent, - }), - startDomainEventConsumer({ - consumerName: DOMAIN_EVENT_CONSUMERS.worldRumorProjector.name, - concurrency: DOMAIN_EVENT_CONSUMERS.worldRumorProjector.concurrency, - acceptedTypes: [ - 'cultivator.realm.changed', - 'craft.item.created', - 'equipment.forged', - 'market.material.revealed', - 'ranking.position.changed', - 'beast.exceptional.acquired', - ], - handle: handleWorldRumorEvent, - }), - startDomainEventConsumer({ - consumerName: DOMAIN_EVENT_CONSUMERS.rankingRealmProjector.name, - concurrency: DOMAIN_EVENT_CONSUMERS.rankingRealmProjector.concurrency, - acceptedTypes: ['cultivator.realm.changed'], - handle: handleRankingRealmEvent, - }), - startDomainEventConsumer({ - consumerName: DOMAIN_EVENT_CONSUMERS.mailNotificationProjector.name, - concurrency: DOMAIN_EVENT_CONSUMERS.mailNotificationProjector.concurrency, - acceptedTypes: ['mail.created'], - handle: handleMailCreatedEvent, - }), - startDomainEventConsumer({ - consumerName: DOMAIN_EVENT_CONSUMERS.sponsorshipOrderProjector.name, - concurrency: DOMAIN_EVENT_CONSUMERS.sponsorshipOrderProjector.concurrency, - acceptedTypes: ['sponsorship.order.received'], - handle: handleSponsorshipOrderEvent, - }), - ]); - startTransactionalMessageRelay(); - registered = true; + }, + }), + startDomainEventConsumer({ + consumerName: DOMAIN_EVENT_CONSUMERS.sectFacilityProjector.name, + concurrency: DOMAIN_EVENT_CONSUMERS.sectFacilityProjector.concurrency, + acceptedTypes: ['sect.construction.donated'], + handle: handleSectConstructionEvent, + }), + startDomainEventConsumer({ + consumerName: DOMAIN_EVENT_CONSUMERS.taskProjector.name, + concurrency: DOMAIN_EVENT_CONSUMERS.taskProjector.concurrency, + acceptedTypes: [ + 'alchemy.craft.completed', + 'ranking.challenge.completed', + 'dungeon.run.settled', + 'yield.claimed', + ], + handle: handleTaskEvent, + }), + startDomainEventConsumer({ + consumerName: DOMAIN_EVENT_CONSUMERS.yieldRewardProjector.name, + concurrency: DOMAIN_EVENT_CONSUMERS.yieldRewardProjector.concurrency, + acceptedTypes: ['yield.claimed'], + handle: handleYieldRewardEvent, + }), + startDomainEventConsumer({ + consumerName: DOMAIN_EVENT_CONSUMERS.worldRumorProjector.name, + concurrency: DOMAIN_EVENT_CONSUMERS.worldRumorProjector.concurrency, + acceptedTypes: [ + 'cultivator.realm.changed', + 'craft.item.created', + 'equipment.forged', + 'market.material.revealed', + 'ranking.position.changed', + 'beast.exceptional.acquired', + ], + handle: handleWorldRumorEvent, + }), + startDomainEventConsumer({ + consumerName: DOMAIN_EVENT_CONSUMERS.rankingRealmProjector.name, + concurrency: DOMAIN_EVENT_CONSUMERS.rankingRealmProjector.concurrency, + acceptedTypes: ['cultivator.realm.changed'], + handle: handleRankingRealmEvent, + }), + startDomainEventConsumer({ + consumerName: DOMAIN_EVENT_CONSUMERS.mailNotificationProjector.name, + concurrency: + DOMAIN_EVENT_CONSUMERS.mailNotificationProjector.concurrency, + acceptedTypes: ['mail.created'], + handle: handleMailCreatedEvent, + }), + startDomainEventConsumer({ + consumerName: DOMAIN_EVENT_CONSUMERS.sponsorshipOrderProjector.name, + concurrency: + DOMAIN_EVENT_CONSUMERS.sponsorshipOrderProjector.concurrency, + acceptedTypes: ['sponsorship.order.received'], + handle: handleSponsorshipOrderEvent, + }), + ]); + const failures = starts.filter((result) => result.status === 'rejected'); + if (failures.length) { + throw new AggregateError( + failures.map((result) => result.reason), + 'Message infrastructure startup failed', + ); + } + startTransactionalMessageRelay(); + registered = true; + } catch (error) { + await shutdownMessageInfrastructure(); + throw error; + } } async function handleSectConstructionEvent(event: DomainEventEnvelope) { @@ -250,9 +263,9 @@ async function handleSponsorshipOrderEvent(event: DomainEventEnvelope) { } export async function shutdownMessageInfrastructure(): Promise { - if (!registered) return; + // A failed bootstrap may have opened consumers before registration completed. registered = false; - stopTransactionalMessageRelay(); + await stopTransactionalMessageRelay(); await stopBackgroundCommandConsumer(); await stopCombatV6Messaging(); await stopDomainEventConsumers(); diff --git a/src/server/lib/mq/domainEventWriter.ts b/apps/api/src/lib/mq/domainEventWriter.ts similarity index 94% rename from src/server/lib/mq/domainEventWriter.ts rename to apps/api/src/lib/mq/domainEventWriter.ts index 213ead42..bb4cae2c 100644 --- a/src/server/lib/mq/domainEventWriter.ts +++ b/apps/api/src/lib/mq/domainEventWriter.ts @@ -3,11 +3,11 @@ import { createTransactionalMessage } from '@server/lib/repositories/transaction import type { DomainEventData, DomainEventType, -} from '@shared/contracts/domainEvents'; +} from '@daoyou/shared/contracts/domainEvents'; import { DOMAIN_EVENT_DEFINITIONS, parseDomainEventEnvelope, -} from '@shared/contracts/domainEvents'; +} from '@daoyou/shared/contracts/domainEvents'; import { randomUUID } from 'node:crypto'; export interface DomainEventWriter { diff --git a/src/server/lib/mq/natsTopology.ts b/apps/api/src/lib/mq/natsTopology.ts similarity index 98% rename from src/server/lib/mq/natsTopology.ts rename to apps/api/src/lib/mq/natsTopology.ts index ea07d4c0..abcf6e8a 100644 --- a/src/server/lib/mq/natsTopology.ts +++ b/apps/api/src/lib/mq/natsTopology.ts @@ -2,12 +2,12 @@ import { getJetStreamManager } from '@server/lib/nats'; import { BACKGROUND_COMMAND_STREAM, BACKGROUND_COMMAND_SUBJECT_PREFIX, -} from '@shared/contracts/backgroundCommands'; +} from '@daoyou/shared/contracts/backgroundCommands'; import { DOMAIN_EVENT_STREAM, DOMAIN_EVENT_SUBJECT_PREFIX, -} from '@shared/contracts/domainEvents'; -import { COMBAT_V6_REPLAY_STREAM, COMBAT_V6_REPLAY_SUBJECT } from '@shared/contracts/combatV6Runtime'; +} from '@daoyou/shared/contracts/domainEvents'; +import { COMBAT_V6_REPLAY_STREAM, COMBAT_V6_REPLAY_SUBJECT } from '@daoyou/shared/contracts/combatV6Runtime'; import { AckPolicy, DeliverPolicy, diff --git a/src/server/lib/mq/transactionalMessagePublisher.ts b/apps/api/src/lib/mq/transactionalMessagePublisher.ts similarity index 100% rename from src/server/lib/mq/transactionalMessagePublisher.ts rename to apps/api/src/lib/mq/transactionalMessagePublisher.ts diff --git a/apps/api/src/lib/mq/transactionalMessageRelay.ts b/apps/api/src/lib/mq/transactionalMessageRelay.ts new file mode 100644 index 00000000..f56e6b08 --- /dev/null +++ b/apps/api/src/lib/mq/transactionalMessageRelay.ts @@ -0,0 +1,48 @@ +import { listPendingTransactionalMessages } from '@server/lib/repositories/transactionalMessageRepository'; +import { publishTransactionalMessage } from './transactionalMessagePublisher'; + +const RECOVERY_INTERVAL_MS = 5_000; +const RECOVERY_BATCH_SIZE = 100; + +let recoveryTimer: ReturnType | undefined; +let recoveryWork: Promise | undefined; + +async function recoverPendingMessages(): Promise { + const messages = await listPendingTransactionalMessages(RECOVERY_BATCH_SIZE); + for (const message of messages) { + try { + await publishTransactionalMessage(message.id); + } catch (error) { + console.error('[transactional-message] publish failed', { + messageId: message.id, + messageKey: message.messageKey, + error, + }); + } + } +} + +function scheduleRecovery(): void { + if (recoveryWork) return; + recoveryWork = recoverPendingMessages() + .catch((error) => + console.error('[transactional-message] recovery failed', error), + ) + .finally(() => { + recoveryWork = undefined; + }); +} + +export function startTransactionalMessageRelay(): void { + if (recoveryTimer) return; + + scheduleRecovery(); + recoveryTimer = setInterval(scheduleRecovery, RECOVERY_INTERVAL_MS); + recoveryTimer.unref(); +} + +export async function stopTransactionalMessageRelay(): Promise { + clearInterval(recoveryTimer); + recoveryTimer = undefined; + await recoveryWork; +} diff --git a/src/server/lib/nats/index.ts b/apps/api/src/lib/nats/index.ts similarity index 89% rename from src/server/lib/nats/index.ts rename to apps/api/src/lib/nats/index.ts index 532f278a..ac586162 100644 --- a/src/server/lib/nats/index.ts +++ b/apps/api/src/lib/nats/index.ts @@ -1,3 +1,4 @@ +import { getRuntimeEnvironment } from '@server/lib/config/environment'; import { connect, deadline, @@ -12,11 +13,11 @@ const NATS_CONNECT_TIMEOUT_MS = 5_000; let connectionPromise: Promise | undefined; function requiredNatsConfig() { - const servers = process.env.NATS_SERVERS?.split(',') + const servers = getRuntimeEnvironment().NATS_SERVERS?.split(',') .map((server) => server.trim()) .filter(Boolean); - const user = process.env.NATS_USER?.trim(); - const pass = process.env.NATS_PASSWORD; + const user = getRuntimeEnvironment().NATS_USER?.trim(); + const pass = getRuntimeEnvironment().NATS_PASSWORD; if (!servers?.length) throw new Error('NATS_SERVERS is required'); if (!user) throw new Error('NATS_USER is required'); @@ -30,7 +31,7 @@ async function createNatsConnection(): Promise { const connection = await connect({ ...config, name: - process.env.NATS_CLIENT_NAME ?? `daoyou-api-${hostname()}-${process.pid}`, + getRuntimeEnvironment().NATS_CLIENT_NAME ?? `daoyou-api-${hostname()}-${process.pid}`, timeout: NATS_CONNECT_TIMEOUT_MS, maxReconnectAttempts: -1, reconnectTimeWait: 1_000, diff --git a/src/server/lib/prompts/index.ts b/apps/api/src/lib/prompts/index.ts similarity index 100% rename from src/server/lib/prompts/index.ts rename to apps/api/src/lib/prompts/index.ts diff --git a/src/server/lib/prompts/registry.ts b/apps/api/src/lib/prompts/registry.ts similarity index 100% rename from src/server/lib/prompts/registry.ts rename to apps/api/src/lib/prompts/registry.ts diff --git a/src/server/lib/redeem/code.ts b/apps/api/src/lib/redeem/code.ts similarity index 100% rename from src/server/lib/redeem/code.ts rename to apps/api/src/lib/redeem/code.ts diff --git a/src/server/lib/redeem/reward.ts b/apps/api/src/lib/redeem/reward.ts similarity index 85% rename from src/server/lib/redeem/reward.ts rename to apps/api/src/lib/redeem/reward.ts index ddf975ce..2f990f97 100644 --- a/src/server/lib/redeem/reward.ts +++ b/apps/api/src/lib/redeem/reward.ts @@ -1,8 +1,8 @@ import { parseMailAttachments, summarizeMailAttachments, -} from '@shared/lib/itemLibrary'; -import type { MailAttachment } from '@shared/types/mail'; +} from '@daoyou/shared/lib/itemLibrary'; +import type { MailAttachment } from '@daoyou/shared/types/mail'; interface RedeemCodeRewardSource { rewardAttachments?: unknown; diff --git a/src/server/lib/redis/apiIpRateLimiter.ts b/apps/api/src/lib/redis/apiIpRateLimiter.ts similarity index 96% rename from src/server/lib/redis/apiIpRateLimiter.ts rename to apps/api/src/lib/redis/apiIpRateLimiter.ts index 4db68baa..05ddde0c 100644 --- a/src/server/lib/redis/apiIpRateLimiter.ts +++ b/apps/api/src/lib/redis/apiIpRateLimiter.ts @@ -1,3 +1,4 @@ +import { getRuntimeEnvironment } from '@server/lib/config/environment'; import { redis } from './index'; const KEY_PREFIX = 'api:rate-limit:ip'; @@ -67,7 +68,7 @@ export type ApiIpRateLimitOptions = { }; function parsePositiveIntegerEnv(name: string, fallback: number): number { - const raw = process.env[name]; + const raw = getRuntimeEnvironment()[name]; if (!raw) { return fallback; } @@ -149,7 +150,7 @@ export async function checkApiIpRateLimit( const windowMs = windowSeconds * 1000; const resetMs = nowMs + windowMs; - if (!process.env.REDIS_URL) { + if (!getRuntimeEnvironment().REDIS_URL) { return buildFallbackResult({ allowed: true, limit: maxRequests, diff --git a/src/server/lib/redis/characterGenerationLimiter.ts b/apps/api/src/lib/redis/characterGenerationLimiter.ts similarity index 98% rename from src/server/lib/redis/characterGenerationLimiter.ts rename to apps/api/src/lib/redis/characterGenerationLimiter.ts index e990ee55..1e0e4bc2 100644 --- a/src/server/lib/redis/characterGenerationLimiter.ts +++ b/apps/api/src/lib/redis/characterGenerationLimiter.ts @@ -4,7 +4,7 @@ import { CHARACTER_GENERATION_DAILY_LIMIT, type CharacterGenerationLimitedBy, type CharacterGenerationQuota, -} from '@shared/contracts/character-generation'; +} from '@daoyou/shared/contracts/character-generation'; import { redis } from './index'; const KEY_PREFIX = 'character_generation:daily'; diff --git a/src/server/lib/redis/cooldownLimiter.ts b/apps/api/src/lib/redis/cooldownLimiter.ts similarity index 100% rename from src/server/lib/redis/cooldownLimiter.ts rename to apps/api/src/lib/redis/cooldownLimiter.ts diff --git a/src/server/lib/redis/index.ts b/apps/api/src/lib/redis/index.ts similarity index 89% rename from src/server/lib/redis/index.ts rename to apps/api/src/lib/redis/index.ts index 2f94c781..d0dadaca 100644 --- a/src/server/lib/redis/index.ts +++ b/apps/api/src/lib/redis/index.ts @@ -1,3 +1,4 @@ +import { getRuntimeEnvironment } from '@server/lib/config/environment'; import Redis from 'ioredis'; const REDIS_CONNECT_TIMEOUT_MS = 4_000; @@ -17,7 +18,7 @@ const REDIS_RESET_ERROR_MESSAGES = [ let redisClient: Redis | null = null; export function isRedisConfigured(): boolean { - return Boolean(process.env.REDIS_URL); + return Boolean(getRuntimeEnvironment().REDIS_URL); } function clearRedisClientIfCurrent(client: Redis): void { @@ -91,13 +92,13 @@ function createRedisClient( /** Dedicated connection for blocking/subscriber workloads owned by shared infrastructure. */ export function createDedicatedRedisClient(label: string): Redis { - const redisUrl = process.env.REDIS_URL; + const redisUrl = getRuntimeEnvironment().REDIS_URL; if (!redisUrl) throw new Error('REDIS_URL is required before using Redis'); return createRedisClient(redisUrl, { label }); } function getRedisClient(): Redis { - const redisUrl = process.env.REDIS_URL; + const redisUrl = getRuntimeEnvironment().REDIS_URL; if (!redisUrl) { throw new Error('REDIS_URL is required before using Redis'); } @@ -153,3 +154,14 @@ const redis = new Proxy({} as Redis, { }); export { getRedisClient, redis }; + +export async function closeRedisConnection(): Promise { + const client = redisClient; + redisClient = null; + if (!client) return; + try { + await client.quit(); + } finally { + client.disconnect(false); + } +} diff --git a/src/server/lib/redis/json.ts b/apps/api/src/lib/redis/json.ts similarity index 100% rename from src/server/lib/redis/json.ts rename to apps/api/src/lib/redis/json.ts diff --git a/src/server/lib/redis/lock.ts b/apps/api/src/lib/redis/lock.ts similarity index 100% rename from src/server/lib/redis/lock.ts rename to apps/api/src/lib/redis/lock.ts diff --git a/src/server/lib/redis/rankingChallenge.ts b/apps/api/src/lib/redis/rankingChallenge.ts similarity index 93% rename from src/server/lib/redis/rankingChallenge.ts rename to apps/api/src/lib/redis/rankingChallenge.ts index 0c7e7d0b..464fb97d 100644 --- a/src/server/lib/redis/rankingChallenge.ts +++ b/apps/api/src/lib/redis/rankingChallenge.ts @@ -1,10 +1,10 @@ -import { MAX_DAILY_RANKING_CHALLENGES } from '@shared/combat-v6/ranking'; +import { MAX_DAILY_RANKING_CHALLENGES } from '@daoyou/shared/combat-v6/ranking'; import type { RankingChallengeRequest, RankingChallengeResult, -} from '@shared/contracts/combatV6Ranking'; -import type { CombatV6ReplayV1 } from '@shared/contracts/combatV6Runtime'; -import type { RankingBattleInput } from '@shared/engine/combat-v6/ranking/battle'; +} from '@daoyou/shared/contracts/combatV6Ranking'; +import type { CombatV6ReplayV1 } from '@daoyou/shared/contracts/combatV6Runtime'; +import type { RankingBattleInput } from '@daoyou/shared/engine/combat-v6/ranking/battle'; import { redis } from './index'; export const rankingQuotaKey = (owner: string, day: string) => diff --git a/src/server/lib/redis/rankings.ts b/apps/api/src/lib/redis/rankings.ts similarity index 92% rename from src/server/lib/redis/rankings.ts rename to apps/api/src/lib/redis/rankings.ts index e388e82a..32668ffd 100644 --- a/src/server/lib/redis/rankings.ts +++ b/apps/api/src/lib/redis/rankings.ts @@ -3,13 +3,13 @@ import { cultivators, sectMemberships } from '@server/lib/drizzle/schema'; import { MAX_DAILY_RANKING_CHALLENGES, rankingDay, -} from '@shared/combat-v6/ranking'; -import { SECT_DISCIPLE_RANKS, SECT_RANK_LABELS } from '@shared/engine/sect'; -import { productionSectRuntime } from '@shared/engine/sect/content'; -import { getBodyCultivationRankingTag } from '@shared/lib/bodyCultivation/ranking'; -import type { CultivatorCondition } from '@shared/types/condition'; -import { REALM_VALUES, type RealmType } from '@shared/types/constants'; -import type { BattleRankingItem } from '@shared/types/rankings'; +} from '@daoyou/shared/combat-v6/ranking'; +import { SECT_DISCIPLE_RANKS, SECT_RANK_LABELS } from '@daoyou/shared/engine/sect'; +import { productionSectRuntime } from '@daoyou/shared/engine/sect/content'; +import { getBodyCultivationRankingTag } from '@daoyou/shared/lib/bodyCultivation/ranking'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; +import { REALM_VALUES, type RealmType } from '@daoyou/shared/types/constants'; +import type { BattleRankingItem } from '@daoyou/shared/types/rankings'; import { and, eq, inArray } from 'drizzle-orm'; import { redis } from './index'; import { rankingQuotaKey } from './rankingChallenge'; diff --git a/src/server/lib/redis/sectConstructionDaily.ts b/apps/api/src/lib/redis/sectConstructionDaily.ts similarity index 96% rename from src/server/lib/redis/sectConstructionDaily.ts rename to apps/api/src/lib/redis/sectConstructionDaily.ts index 2df8f1fc..525114ef 100644 --- a/src/server/lib/redis/sectConstructionDaily.ts +++ b/apps/api/src/lib/redis/sectConstructionDaily.ts @@ -1,4 +1,4 @@ -import type { SectConstructionMemberData } from '@shared/contracts/sect'; +import type { SectConstructionMemberData } from '@daoyou/shared/contracts/sect'; import { redis } from '.'; const KEY_PREFIX = 'sect:construction:daily'; diff --git a/src/server/lib/redis/worldChatLimiter.ts b/apps/api/src/lib/redis/worldChatLimiter.ts similarity index 95% rename from src/server/lib/redis/worldChatLimiter.ts rename to apps/api/src/lib/redis/worldChatLimiter.ts index ba312fec..48f3272e 100644 --- a/src/server/lib/redis/worldChatLimiter.ts +++ b/apps/api/src/lib/redis/worldChatLimiter.ts @@ -1,4 +1,4 @@ -import { REALM_ORDER, type RealmType } from '@shared/types/constants'; +import { REALM_ORDER, type RealmType } from '@daoyou/shared/types/constants'; import { acquireRedisCooldown } from './cooldownLimiter'; const MAX_CHAT_COOLDOWN_SECONDS = 60; diff --git a/src/server/lib/repositories/SpiritFieldRepository.ts b/apps/api/src/lib/repositories/SpiritFieldRepository.ts similarity index 97% rename from src/server/lib/repositories/SpiritFieldRepository.ts rename to apps/api/src/lib/repositories/SpiritFieldRepository.ts index a59f07e6..a4a7e857 100644 --- a/src/server/lib/repositories/SpiritFieldRepository.ts +++ b/apps/api/src/lib/repositories/SpiritFieldRepository.ts @@ -4,7 +4,7 @@ import { createDefaultSpiritFieldPlots, normalizeSpiritFieldPlots, type SpiritFieldPlotState, -} from '@shared/engine/spirit-field'; +} from '@daoyou/shared/engine/spirit-field'; import { eq, sql } from 'drizzle-orm'; export interface SpiritFieldRecord { diff --git a/src/server/lib/repositories/accountDeletionRepository.ts b/apps/api/src/lib/repositories/accountDeletionRepository.ts similarity index 100% rename from src/server/lib/repositories/accountDeletionRepository.ts rename to apps/api/src/lib/repositories/accountDeletionRepository.ts diff --git a/src/server/lib/repositories/appSettingsRepository.ts b/apps/api/src/lib/repositories/appSettingsRepository.ts similarity index 97% rename from src/server/lib/repositories/appSettingsRepository.ts rename to apps/api/src/lib/repositories/appSettingsRepository.ts index af8ba2e9..42c5dfc1 100644 --- a/src/server/lib/repositories/appSettingsRepository.ts +++ b/apps/api/src/lib/repositories/appSettingsRepository.ts @@ -6,12 +6,12 @@ import { DEFAULT_ITEM_LIBRARY_DAILY_MATERIAL_GENERATION_SETTINGS, ItemLibraryDailyMaterialGenerationSettingsSchema, type ItemLibraryDailyMaterialGenerationSettings, -} from '@shared/lib/constants/appSettings'; +} from '@daoyou/shared/lib/constants/appSettings'; import { AfdianSponsorshipConfigSchema, DEFAULT_AFDIAN_SPONSORSHIP_CONFIG, type AfdianSponsorshipConfig, -} from '@shared/lib/sponsorship'; +} from '@daoyou/shared/lib/sponsorship'; import { eq } from 'drizzle-orm'; export async function getAppSetting(key: string): Promise { diff --git a/src/server/lib/repositories/auctionRepository.ts b/apps/api/src/lib/repositories/auctionRepository.ts similarity index 99% rename from src/server/lib/repositories/auctionRepository.ts rename to apps/api/src/lib/repositories/auctionRepository.ts index fc08cf97..24695f90 100644 --- a/src/server/lib/repositories/auctionRepository.ts +++ b/apps/api/src/lib/repositories/auctionRepository.ts @@ -1,7 +1,7 @@ import type { AuctionAssetType, AuctionItemType, -} from '@shared/contracts/auction'; +} from '@daoyou/shared/contracts/auction'; import { and, asc, diff --git a/src/server/lib/repositories/characterLoadoutRepository.ts b/apps/api/src/lib/repositories/characterLoadoutRepository.ts similarity index 93% rename from src/server/lib/repositories/characterLoadoutRepository.ts rename to apps/api/src/lib/repositories/characterLoadoutRepository.ts index 252c9cbd..c15826e6 100644 --- a/src/server/lib/repositories/characterLoadoutRepository.ts +++ b/apps/api/src/lib/repositories/characterLoadoutRepository.ts @@ -5,8 +5,8 @@ import { cultivatorManualStates, inventoryItems, } from '@server/lib/drizzle/schema'; -import type { DaoEquipmentLoadoutV1 } from '@shared/engine/combat-v6/equipment'; -import type { CultivatorManualStateV1 } from '@shared/engine/combat-v6/manuals'; +import type { DaoEquipmentLoadoutV1 } from '@daoyou/shared/engine/combat-v6/equipment'; +import type { CultivatorManualStateV1 } from '@daoyou/shared/engine/combat-v6/manuals'; import { eq } from 'drizzle-orm'; import { readActiveSectCombatProgress } from './sectCombatRepository'; diff --git a/src/server/lib/repositories/combatV6AutoStrategyRepository.ts b/apps/api/src/lib/repositories/combatV6AutoStrategyRepository.ts similarity index 96% rename from src/server/lib/repositories/combatV6AutoStrategyRepository.ts rename to apps/api/src/lib/repositories/combatV6AutoStrategyRepository.ts index 083f0239..ff5ef889 100644 --- a/src/server/lib/repositories/combatV6AutoStrategyRepository.ts +++ b/apps/api/src/lib/repositories/combatV6AutoStrategyRepository.ts @@ -3,7 +3,7 @@ import { cultivatorAutoStrategies } from '@server/lib/drizzle/schema'; import { AutoStrategySchema, type AutoStrategy, -} from '@shared/combat-v6/auto-strategy'; +} from '@daoyou/shared/combat-v6/auto-strategy'; import { and, eq } from 'drizzle-orm'; export async function readCustomAutoStrategy( diff --git a/src/server/lib/repositories/combatV6BeastRepository.ts b/apps/api/src/lib/repositories/combatV6BeastRepository.ts similarity index 91% rename from src/server/lib/repositories/combatV6BeastRepository.ts rename to apps/api/src/lib/repositories/combatV6BeastRepository.ts index f6810eea..36af4feb 100644 --- a/src/server/lib/repositories/combatV6BeastRepository.ts +++ b/apps/api/src/lib/repositories/combatV6BeastRepository.ts @@ -4,20 +4,20 @@ import { cultivatorBeasts, cultivators, } from '@server/lib/drizzle/schema'; -import type { WildSettlement } from '@shared/contracts/combatV6Wild'; +import type { WildSettlement } from '@daoyou/shared/contracts/combatV6Wild'; import { BeastLineupSchema, type BeastRoster, BeastSchema, type SummonedBeast, loseBeastLifespan, -} from '@shared/engine/combat-v6/beasts'; +} from '@daoyou/shared/engine/combat-v6/beasts'; import { BEAST_CAPACITY, gainBeastExp, -} from '@shared/engine/combat-v6/beasts/progression'; -import { combatCharacterLevel } from '@shared/engine/combat-v6/projection/character-level'; -import type { RealmStage, RealmType } from '@shared/types/constants'; +} from '@daoyou/shared/engine/combat-v6/beasts/progression'; +import { combatCharacterLevel } from '@daoyou/shared/engine/combat-v6/projection/character-level'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; import { and, eq, inArray } from 'drizzle-orm'; /** Identity and ownership have a single authority in relational columns. */ diff --git a/src/server/lib/repositories/combatV6ReplayRepository.ts b/apps/api/src/lib/repositories/combatV6ReplayRepository.ts similarity index 98% rename from src/server/lib/repositories/combatV6ReplayRepository.ts rename to apps/api/src/lib/repositories/combatV6ReplayRepository.ts index fa2c0422..f1f581ba 100644 --- a/src/server/lib/repositories/combatV6ReplayRepository.ts +++ b/apps/api/src/lib/repositories/combatV6ReplayRepository.ts @@ -7,8 +7,8 @@ import { COMBAT_V6_REPLAY_SOURCES, type CombatV6HistoryPage, type CombatV6HistoryQuery, -} from '@shared/contracts/combatV6Replay'; -import type { CombatV6ReplayV1 } from '@shared/contracts/combatV6Runtime'; +} from '@daoyou/shared/contracts/combatV6Replay'; +import type { CombatV6ReplayV1 } from '@daoyou/shared/contracts/combatV6Runtime'; import { and, desc, eq, inArray, isNotNull, isNull } from 'drizzle-orm'; import { randomUUID } from 'crypto'; diff --git a/src/server/lib/repositories/combatV6WildSearchRepository.ts b/apps/api/src/lib/repositories/combatV6WildSearchRepository.ts similarity index 96% rename from src/server/lib/repositories/combatV6WildSearchRepository.ts rename to apps/api/src/lib/repositories/combatV6WildSearchRepository.ts index ccfe4d35..2d650c12 100644 --- a/src/server/lib/repositories/combatV6WildSearchRepository.ts +++ b/apps/api/src/lib/repositories/combatV6WildSearchRepository.ts @@ -3,7 +3,7 @@ import { WildRuntimeSchema, type WildEncounter, type WildRuntime, -} from '@shared/contracts/combatV6Wild'; +} from '@daoyou/shared/contracts/combatV6Wild'; import { eq } from 'drizzle-orm'; import { db, type DbExecutor, type DbTransaction } from '../drizzle/db'; import { wildSearches } from '../drizzle/schema'; diff --git a/src/server/lib/repositories/creationProductRepository.ts b/apps/api/src/lib/repositories/creationProductRepository.ts similarity index 99% rename from src/server/lib/repositories/creationProductRepository.ts rename to apps/api/src/lib/repositories/creationProductRepository.ts index 7176a006..3d8c350e 100644 --- a/src/server/lib/repositories/creationProductRepository.ts +++ b/apps/api/src/lib/repositories/creationProductRepository.ts @@ -1,6 +1,6 @@ import { getExecutor, type DbExecutor } from '@server/lib/drizzle/db'; import * as schema from '@server/lib/drizzle/schema'; -import type { LegacyProductType as CreationProductType } from '@shared/legacy/products'; +import type { LegacyProductType as CreationProductType } from '@daoyou/shared/legacy/products'; import { and, desc, eq, inArray, sql } from 'drizzle-orm'; export type CreationProductRecord = typeof schema.creationProducts.$inferSelect; diff --git a/src/server/lib/repositories/cultivatorRepository.ts b/apps/api/src/lib/repositories/cultivatorRepository.ts similarity index 100% rename from src/server/lib/repositories/cultivatorRepository.ts rename to apps/api/src/lib/repositories/cultivatorRepository.ts diff --git a/src/server/lib/repositories/feedbackRepository.ts b/apps/api/src/lib/repositories/feedbackRepository.ts similarity index 100% rename from src/server/lib/repositories/feedbackRepository.ts rename to apps/api/src/lib/repositories/feedbackRepository.ts diff --git a/src/server/lib/repositories/itemLibraryRepository.ts b/apps/api/src/lib/repositories/itemLibraryRepository.ts similarity index 98% rename from src/server/lib/repositories/itemLibraryRepository.ts rename to apps/api/src/lib/repositories/itemLibraryRepository.ts index 237bbcaf..27604103 100644 --- a/src/server/lib/repositories/itemLibraryRepository.ts +++ b/apps/api/src/lib/repositories/itemLibraryRepository.ts @@ -7,8 +7,8 @@ import { type ItemLibraryEntry, type ItemLibraryListQuery, type UpdateItemLibraryEntry, -} from '@shared/lib/itemLibrary'; -import { assertCurrentRewardItem } from '@shared/lib/retiredDraw'; +} from '@daoyou/shared/lib/itemLibrary'; +import { assertCurrentRewardItem } from '@daoyou/shared/lib/retiredDraw'; import { and, asc, diff --git a/src/server/lib/repositories/messageConsumptionRepository.ts b/apps/api/src/lib/repositories/messageConsumptionRepository.ts similarity index 100% rename from src/server/lib/repositories/messageConsumptionRepository.ts rename to apps/api/src/lib/repositories/messageConsumptionRepository.ts diff --git a/src/server/lib/repositories/playerJournalRepository.ts b/apps/api/src/lib/repositories/playerJournalRepository.ts similarity index 99% rename from src/server/lib/repositories/playerJournalRepository.ts rename to apps/api/src/lib/repositories/playerJournalRepository.ts index ec8d8d2f..c52e2cf3 100644 --- a/src/server/lib/repositories/playerJournalRepository.ts +++ b/apps/api/src/lib/repositories/playerJournalRepository.ts @@ -13,7 +13,7 @@ import { type PlayerJournalPage, type PlayerJournalQuerySchema, type StoredJournalEvent, -} from '@shared/contracts/playerJournal'; +} from '@daoyou/shared/contracts/playerJournal'; import { and, desc, eq, sql } from 'drizzle-orm'; import { createHash } from 'node:crypto'; import type { z } from 'zod'; diff --git a/src/server/lib/repositories/playerStateRepository.ts b/apps/api/src/lib/repositories/playerStateRepository.ts similarity index 99% rename from src/server/lib/repositories/playerStateRepository.ts rename to apps/api/src/lib/repositories/playerStateRepository.ts index 6022f31c..1162fe39 100644 --- a/src/server/lib/repositories/playerStateRepository.ts +++ b/apps/api/src/lib/repositories/playerStateRepository.ts @@ -19,7 +19,7 @@ import { type ResourceChangeDescriptor, type ResourceScope, type ResourceTopic, -} from '@shared/contracts/resources'; +} from '@daoyou/shared/contracts/resources'; import { and, asc, eq, inArray, like, lt, min, sql } from 'drizzle-orm'; import { ZodError } from 'zod'; diff --git a/src/server/lib/repositories/redisCultivatorRepository.ts b/apps/api/src/lib/repositories/redisCultivatorRepository.ts similarity index 97% rename from src/server/lib/repositories/redisCultivatorRepository.ts rename to apps/api/src/lib/repositories/redisCultivatorRepository.ts index 948dd38d..e2419c96 100644 --- a/src/server/lib/repositories/redisCultivatorRepository.ts +++ b/apps/api/src/lib/repositories/redisCultivatorRepository.ts @@ -1,4 +1,4 @@ -import { Cultivator } from '@shared/types/cultivator'; +import { Cultivator } from '@daoyou/shared/types/cultivator'; import { redis } from '../redis'; import { parseRedisJson } from '../redis/json'; diff --git a/src/server/lib/repositories/retentionRepository.ts b/apps/api/src/lib/repositories/retentionRepository.ts similarity index 100% rename from src/server/lib/repositories/retentionRepository.ts rename to apps/api/src/lib/repositories/retentionRepository.ts diff --git a/src/server/lib/repositories/sectChatRepository.ts b/apps/api/src/lib/repositories/sectChatRepository.ts similarity index 98% rename from src/server/lib/repositories/sectChatRepository.ts rename to apps/api/src/lib/repositories/sectChatRepository.ts index 2b5660f7..891bd0f9 100644 --- a/src/server/lib/repositories/sectChatRepository.ts +++ b/apps/api/src/lib/repositories/sectChatRepository.ts @@ -4,7 +4,7 @@ import type { WorldChatMessageDTO, WorldChatMessageType, WorldChatPayload, -} from '@shared/types/world-chat'; +} from '@daoyou/shared/types/world-chat'; import { randomUUID } from 'crypto'; const SECT_CHAT_MAX_MESSAGES = 100; diff --git a/src/server/lib/repositories/sectCombatRepository.ts b/apps/api/src/lib/repositories/sectCombatRepository.ts similarity index 97% rename from src/server/lib/repositories/sectCombatRepository.ts rename to apps/api/src/lib/repositories/sectCombatRepository.ts index e5c36c7f..f85868fe 100644 --- a/src/server/lib/repositories/sectCombatRepository.ts +++ b/apps/api/src/lib/repositories/sectCombatRepository.ts @@ -7,12 +7,12 @@ import { sectMeridianNodes, sectMethodProgress, } from '@server/lib/drizzle/schema'; -import { createFreshCombatV6MethodLevels } from '@shared/engine/combat-v6/build-state'; +import { createFreshCombatV6MethodLevels } from '@daoyou/shared/engine/combat-v6/build-state'; import { COMBAT_V6_SECT_DEFINITIONS, type CombatV6SectId, type SectCombatProgressV6, -} from '@shared/engine/combat-v6/content'; +} from '@daoyou/shared/engine/combat-v6/content'; import { and, eq } from 'drizzle-orm'; export type ActiveSectMembership = { diff --git a/src/server/lib/repositories/sectOrganizationRepository.ts b/apps/api/src/lib/repositories/sectOrganizationRepository.ts similarity index 99% rename from src/server/lib/repositories/sectOrganizationRepository.ts rename to apps/api/src/lib/repositories/sectOrganizationRepository.ts index c6a6435e..1330279a 100644 --- a/src/server/lib/repositories/sectOrganizationRepository.ts +++ b/apps/api/src/lib/repositories/sectOrganizationRepository.ts @@ -13,7 +13,7 @@ import { sectStipendClaims, sectTaskRecords, } from '@server/lib/drizzle/schema'; -import type { SectDiscipleRank, SectOffice } from '@shared/engine/sect'; +import type { SectDiscipleRank, SectOffice } from '@daoyou/shared/engine/sect'; import { and, asc, diff --git a/src/server/lib/repositories/sectRepository.ts b/apps/api/src/lib/repositories/sectRepository.ts similarity index 93% rename from src/server/lib/repositories/sectRepository.ts rename to apps/api/src/lib/repositories/sectRepository.ts index df569eaf..8d87818a 100644 --- a/src/server/lib/repositories/sectRepository.ts +++ b/apps/api/src/lib/repositories/sectRepository.ts @@ -1,5 +1,5 @@ -import { createFreshCombatV6MethodLevels } from '@shared/engine/combat-v6/build-state'; -import { COMBAT_V6_SECT_DEFINITIONS, type CombatV6SectId } from '@shared/engine/combat-v6/content'; +import { createFreshCombatV6MethodLevels } from '@daoyou/shared/engine/combat-v6/build-state'; +import { COMBAT_V6_SECT_DEFINITIONS, type CombatV6SectId } from '@daoyou/shared/engine/combat-v6/content'; import { type DbExecutor, type DbTransaction, @@ -10,17 +10,17 @@ import { sectCombatStates, sectMethodProgress, } from '@server/lib/drizzle/schema'; -import { getOrInitCultivationProgress } from '@server/utils/cultivationUtils'; -import type { ResourceDataMap } from '@shared/contracts/resources'; +import { getOrInitCultivationProgress } from '@daoyou/shared/engine/cultivation/cultivationUtils'; +import type { ResourceDataMap } from '@daoyou/shared/contracts/resources'; import { type CultivatorSectState, type SectDefinition, type SectRuntime, type SectTrainingCost, -} from '@shared/engine/sect'; -import { productionSectRuntime } from '@shared/engine/sect/content'; -import type { RealmStage, RealmType } from '@shared/types/constants'; -import type { CultivationProgress } from '@shared/types/cultivator'; +} from '@daoyou/shared/engine/sect'; +import { productionSectRuntime } from '@daoyou/shared/engine/sect/content'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; +import type { CultivationProgress } from '@daoyou/shared/types/cultivator'; import { and, eq, sql } from 'drizzle-orm'; export type SectMembershipRow = typeof sectMemberships.$inferSelect; diff --git a/src/server/lib/repositories/storyRepository.ts b/apps/api/src/lib/repositories/storyRepository.ts similarity index 94% rename from src/server/lib/repositories/storyRepository.ts rename to apps/api/src/lib/repositories/storyRepository.ts index 6e7cd344..12bff782 100644 --- a/src/server/lib/repositories/storyRepository.ts +++ b/apps/api/src/lib/repositories/storyRepository.ts @@ -4,8 +4,8 @@ import { type DbTransaction, } from '@server/lib/drizzle/db'; import { cultivatorStories } from '@server/lib/drizzle/schema'; -import { parseStoryProgress } from '@shared/story/catalog'; -import type { StoryProgress } from '@shared/story/schema'; +import { parseStoryProgress } from '@daoyou/shared/story/catalog'; +import type { StoryProgress } from '@daoyou/shared/story/schema'; import { and, eq } from 'drizzle-orm'; export type CultivatorStoryRecord = typeof cultivatorStories.$inferSelect; diff --git a/src/server/lib/repositories/taskRepository.ts b/apps/api/src/lib/repositories/taskRepository.ts similarity index 99% rename from src/server/lib/repositories/taskRepository.ts rename to apps/api/src/lib/repositories/taskRepository.ts index f4d8cfed..caaf09ff 100644 --- a/src/server/lib/repositories/taskRepository.ts +++ b/apps/api/src/lib/repositories/taskRepository.ts @@ -5,7 +5,7 @@ import type { TaskInstanceMetadata, TaskObjectiveState, TaskStatus, -} from '@shared/types/task'; +} from '@daoyou/shared/types/task'; import { and, asc, eq, sql } from 'drizzle-orm'; export type CultivatorTaskRecord = typeof schema.cultivatorTasks.$inferSelect; diff --git a/src/server/lib/repositories/towerRepository.ts b/apps/api/src/lib/repositories/towerRepository.ts similarity index 94% rename from src/server/lib/repositories/towerRepository.ts rename to apps/api/src/lib/repositories/towerRepository.ts index 48b5abf7..cdc94705 100644 --- a/src/server/lib/repositories/towerRepository.ts +++ b/apps/api/src/lib/repositories/towerRepository.ts @@ -2,15 +2,15 @@ import { publishTowerWeek, validatePublishedTowerWeek, type PublishedTowerWeek, -} from '@shared/engine/combat-v6/tower/published'; -import { TOWER_STRATEGY_VERSION } from '@shared/engine/combat-v6/tower/strategy'; +} from '@daoyou/shared/engine/combat-v6/tower/published'; +import { TOWER_STRATEGY_VERSION } from '@daoyou/shared/engine/combat-v6/tower/strategy'; import { advanceTowerRewardWeek, TowerClaimsSchema, type TowerRewardState, -} from '@shared/lib/tower/reward-state'; -import { getTowerSeasonMeta } from '@shared/lib/tower/season'; -import type { TowerSeasonMeta } from '@shared/lib/tower/types'; +} from '@daoyou/shared/lib/tower/reward-state'; +import { getTowerSeasonMeta } from '@daoyou/shared/lib/tower/season'; +import type { TowerSeasonMeta } from '@daoyou/shared/lib/tower/types'; import { and, desc, eq, gte, lt, ne } from 'drizzle-orm'; import { createHash } from 'node:crypto'; import { db, type DbExecutor, type DbTransaction } from '../drizzle/db'; diff --git a/src/server/lib/repositories/transactionalMessageRepository.ts b/apps/api/src/lib/repositories/transactionalMessageRepository.ts similarity index 100% rename from src/server/lib/repositories/transactionalMessageRepository.ts rename to apps/api/src/lib/repositories/transactionalMessageRepository.ts diff --git a/src/server/lib/repositories/worldChatRepository.ts b/apps/api/src/lib/repositories/worldChatRepository.ts similarity index 99% rename from src/server/lib/repositories/worldChatRepository.ts rename to apps/api/src/lib/repositories/worldChatRepository.ts index 152783c7..d404a321 100644 --- a/src/server/lib/repositories/worldChatRepository.ts +++ b/apps/api/src/lib/repositories/worldChatRepository.ts @@ -6,7 +6,7 @@ import type { WorldChatMessageDTO, WorldChatMessageType, WorldChatPayload, -} from '@shared/types/world-chat'; +} from '@daoyou/shared/types/world-chat'; import { randomUUID } from 'crypto'; const WORLD_CHAT_LIST_KEY = 'world_chat:messages'; diff --git a/src/server/lib/services/AlchemyFormulaAnalyzer.ts b/apps/api/src/lib/services/AlchemyFormulaAnalyzer.ts similarity index 98% rename from src/server/lib/services/AlchemyFormulaAnalyzer.ts rename to apps/api/src/lib/services/AlchemyFormulaAnalyzer.ts index 34a61c4f..c55ba018 100644 --- a/src/server/lib/services/AlchemyFormulaAnalyzer.ts +++ b/apps/api/src/lib/services/AlchemyFormulaAnalyzer.ts @@ -5,18 +5,18 @@ import { GENERATABLE_ALCHEMY_PROPERTY_KEY_VALUES, getAlchemyPropertyLabel, normalizeWeightedAlchemyProperties, -} from '@shared/lib/alchemyProperties'; +} from '@daoyou/shared/lib/alchemyProperties'; import type { AlchemyFormula, AlchemyRecipePlan, FormulaFitBand, FormulaMaterialJudgment, -} from '@shared/types/consumable'; +} from '@daoyou/shared/types/consumable'; import { ALCHEMY_FOCUS_MODE_VALUES, FORMULA_FIT_BAND_VALUES, FORMULA_MATERIAL_VERDICT_VALUES, -} from '@shared/types/consumable'; +} from '@daoyou/shared/types/consumable'; import { z } from 'zod'; import type { PreparedAlchemyMaterial } from './AlchemyRecipeRules'; diff --git a/src/server/lib/services/AlchemyFormulaService.ts b/apps/api/src/lib/services/AlchemyFormulaService.ts similarity index 97% rename from src/server/lib/services/AlchemyFormulaService.ts rename to apps/api/src/lib/services/AlchemyFormulaService.ts index 50e653c9..24779b1d 100644 --- a/src/server/lib/services/AlchemyFormulaService.ts +++ b/apps/api/src/lib/services/AlchemyFormulaService.ts @@ -14,46 +14,46 @@ import { type PreparedAlchemyMaterial, } from '@server/lib/services/AlchemyRecipeRules'; import { getCultivatorPreHeavenFates } from '@server/lib/services/cultivator/CultivatorProfileRepository'; -import { ALCHEMY_MAX_DOSE } from '@shared/config/alchemyInput'; -import type { ResourceOperationSettlement } from '@shared/engine/resource/types'; -import type { AlchemyBagMaterial } from '@shared/inventory/alchemy'; +import { ALCHEMY_MAX_DOSE } from '@daoyou/shared/config/alchemyInput'; +import type { ResourceOperationSettlement } from '@daoyou/shared/engine/resource/types'; +import type { AlchemyBagMaterial } from '@daoyou/shared/inventory/alchemy'; import { calculateAlchemyCost, calculateHighestMaterialRank, -} from '@shared/lib/alchemyCost'; +} from '@daoyou/shared/lib/alchemyCost'; import { normalizeAlchemyEffectRoute, validateAlchemyEffectRoute, -} from '@shared/lib/alchemyEffectResolver'; -import { getFormulaFitPolicy } from '@shared/lib/alchemyFormulaFit'; -import { isAlchemyMaterialType } from '@shared/lib/alchemyMaterials'; +} from '@daoyou/shared/lib/alchemyEffectResolver'; +import { getFormulaFitPolicy } from '@daoyou/shared/lib/alchemyFormulaFit'; +import { isAlchemyMaterialType } from '@daoyou/shared/lib/alchemyMaterials'; import { formatAlchemyPropertyVector, normalizeWeightedAlchemyProperties, -} from '@shared/lib/alchemyProperties'; +} from '@daoyou/shared/lib/alchemyProperties'; import { buildAlchemyYieldPreview, calculateAlchemyQiCost, rollAlchemyYieldProfile, toAlchemyYieldDisplayProfile, type AlchemyYieldFactors, -} from '@shared/lib/alchemyYield'; +} from '@daoyou/shared/lib/alchemyYield'; import { getBreakthroughPillLabel, getNextMajorRealm, -} from '@shared/lib/breakthroughPill'; +} from '@daoyou/shared/lib/breakthroughPill'; import { evaluateFateContext, getAlchemySpiritStoneMultiplier, scaleFateAdjustedCost, -} from '@shared/lib/fates'; +} from '@daoyou/shared/lib/fates'; import { QUALITY_ORDER, type ElementType, type MaterialType, type Quality, type RealmType, -} from '@shared/types/constants'; +} from '@daoyou/shared/types/constants'; import type { AlchemyBatchDisplayProfile, AlchemyBatchProfile, @@ -69,9 +69,9 @@ import type { PillFamily, PillSpec, WeightedAlchemyProperty, -} from '@shared/types/consumable'; -import { PILL_QUOTA_CATEGORY_VALUES } from '@shared/types/consumable'; -import type { Consumable, PreHeavenFate } from '@shared/types/cultivator'; +} from '@daoyou/shared/types/consumable'; +import { PILL_QUOTA_CATEGORY_VALUES } from '@daoyou/shared/types/consumable'; +import type { Consumable, PreHeavenFate } from '@daoyou/shared/types/cultivator'; import { and, desc, eq, ilike, or, sql } from 'drizzle-orm'; import { consumeAlchemyMaterials, @@ -1187,7 +1187,7 @@ export interface PreparedFormulaCraft { consumable: Consumable; craftedConsumables: Consumable[]; consumables: Consumable[]; - yieldProfile: import('@shared/types/consumable').AlchemyYieldDisplayProfile; + yieldProfile: import('@daoyou/shared/types/consumable').AlchemyYieldDisplayProfile; formulaProgress: FormulaProgress; }; inventoryChanges: ResourceOperationSettlement['inventoryChanges']; diff --git a/src/server/lib/services/AlchemyNarrativeEnricher.ts b/apps/api/src/lib/services/AlchemyNarrativeEnricher.ts similarity index 90% rename from src/server/lib/services/AlchemyNarrativeEnricher.ts rename to apps/api/src/lib/services/AlchemyNarrativeEnricher.ts index 2771ae62..61108944 100644 --- a/src/server/lib/services/AlchemyNarrativeEnricher.ts +++ b/apps/api/src/lib/services/AlchemyNarrativeEnricher.ts @@ -1,12 +1,13 @@ +import { getRuntimeEnvironment } from '@server/lib/config/environment'; import { renderPrompt } from '@server/lib/prompts'; import { generateAiObject } from '@server/utils/aiClient'; -import { formatAlchemyPropertyVector } from '@shared/lib/alchemyProperties'; -import type { ElementType, Quality } from '@shared/types/constants'; +import { formatAlchemyPropertyVector } from '@daoyou/shared/lib/alchemyProperties'; +import type { ElementType, Quality } from '@daoyou/shared/types/constants'; import type { AlchemyFocusMode, PillFamily, WeightedAlchemyProperty, -} from '@shared/types/consumable'; +} from '@daoyou/shared/types/consumable'; import { z } from 'zod'; function getPillFamilyLabel(family: PillFamily): string { @@ -78,8 +79,8 @@ export class AlchemyNarrativeEnricher { } private static resolveDefaultEnabled(): boolean { - if (process.env.DISABLE_LLM_NAMING === 'true') return false; - if (process.env.ENABLE_LLM_NAMING === 'false') return false; + if (getRuntimeEnvironment().DISABLE_LLM_NAMING === 'true') return false; + if (getRuntimeEnvironment().ENABLE_LLM_NAMING === 'false') return false; return true; } diff --git a/src/server/lib/services/AlchemyRecipePlanner.ts b/apps/api/src/lib/services/AlchemyRecipePlanner.ts similarity index 94% rename from src/server/lib/services/AlchemyRecipePlanner.ts rename to apps/api/src/lib/services/AlchemyRecipePlanner.ts index 0d4695b3..dd13cac1 100644 --- a/src/server/lib/services/AlchemyRecipePlanner.ts +++ b/apps/api/src/lib/services/AlchemyRecipePlanner.ts @@ -5,13 +5,13 @@ import { GENERATABLE_ALCHEMY_PROPERTY_KEY_VALUES, getAlchemyPropertyLabel, normalizeWeightedAlchemyProperties, -} from '@shared/lib/alchemyProperties'; -import { mergeAlchemyMaterialPropertyHints } from '@shared/lib/alchemyMaterialHints'; -import { ELEMENT_VALUES } from '@shared/types/constants'; +} from '@daoyou/shared/lib/alchemyProperties'; +import { mergeAlchemyMaterialPropertyHints } from '@daoyou/shared/lib/alchemyMaterialHints'; +import { ELEMENT_VALUES } from '@daoyou/shared/types/constants'; import { ALCHEMY_FOCUS_MODE_VALUES, type AlchemyRecipePlan, -} from '@shared/types/consumable'; +} from '@daoyou/shared/types/consumable'; import { z } from 'zod'; import type { PreparedAlchemyMaterial } from './AlchemyRecipeRules'; diff --git a/src/server/lib/services/AlchemyRecipeRules.ts b/apps/api/src/lib/services/AlchemyRecipeRules.ts similarity index 97% rename from src/server/lib/services/AlchemyRecipeRules.ts rename to apps/api/src/lib/services/AlchemyRecipeRules.ts index 687d688b..c2a5e818 100644 --- a/src/server/lib/services/AlchemyRecipeRules.ts +++ b/apps/api/src/lib/services/AlchemyRecipeRules.ts @@ -3,23 +3,23 @@ import { BASE_TOXICITY_BY_TYPE, QUALITY_STABILITY_BONUS, type AlchemyMaterialType, -} from '@shared/config/alchemyConfig'; -import { MATERIAL_ESSENCE_BY_QUALITY } from '@shared/config/alchemyEssenceConfig'; +} from '@daoyou/shared/config/alchemyConfig'; +import { MATERIAL_ESSENCE_BY_QUALITY } from '@daoyou/shared/config/alchemyEssenceConfig'; import { getAlchemyPropertyFamily, getAlchemyPropertyLabel, isLongTermAlchemyProperty, normalizeWeightedAlchemyProperties, sortWeightedAlchemyProperties, -} from '@shared/lib/alchemyProperties'; +} from '@daoyou/shared/lib/alchemyProperties'; import { calculateEffectiveEssence, calculateQualityPotential, calculateRawEssence, type AlchemyEssenceMaterial, -} from '@shared/lib/alchemyYield'; -import type { ElementType, Quality } from '@shared/types/constants'; -import { QUALITY_ORDER } from '@shared/types/constants'; +} from '@daoyou/shared/lib/alchemyYield'; +import type { ElementType, Quality } from '@daoyou/shared/types/constants'; +import { QUALITY_ORDER } from '@daoyou/shared/types/constants'; import type { AlchemyBatchProfile, AlchemyFocusMode, @@ -31,7 +31,7 @@ import type { PillFamily, PillQuotaCategory, WeightedAlchemyProperty, -} from '@shared/types/consumable'; +} from '@daoyou/shared/types/consumable'; import { AlchemyServiceError } from './AlchemyServiceError'; export interface PreparedAlchemyMaterial { diff --git a/src/server/lib/services/AlchemyServiceError.ts b/apps/api/src/lib/services/AlchemyServiceError.ts similarity index 100% rename from src/server/lib/services/AlchemyServiceError.ts rename to apps/api/src/lib/services/AlchemyServiceError.ts diff --git a/src/server/lib/services/ArenaBattleStartOrchestrator.ts b/apps/api/src/lib/services/ArenaBattleStartOrchestrator.ts similarity index 96% rename from src/server/lib/services/ArenaBattleStartOrchestrator.ts rename to apps/api/src/lib/services/ArenaBattleStartOrchestrator.ts index 7ff3ad5f..a93b1049 100644 --- a/src/server/lib/services/ArenaBattleStartOrchestrator.ts +++ b/apps/api/src/lib/services/ArenaBattleStartOrchestrator.ts @@ -1,5 +1,5 @@ import { isRedisLockContention, withRedisLock } from '@server/lib/redis/lock'; -import type { ArenaRoomV1 } from '@shared/contracts/arena'; +import type { ArenaRoomV1 } from '@daoyou/shared/contracts/arena'; import { ArenaRoomService } from './ArenaRoomService'; import { createArenaV6 } from './combat-v6/CombatV6ArenaService'; import { CombatV6ArenaStore } from './combat-v6/CombatV6ArenaStore'; diff --git a/src/server/lib/services/ArenaRoomService.ts b/apps/api/src/lib/services/ArenaRoomService.ts similarity index 99% rename from src/server/lib/services/ArenaRoomService.ts rename to apps/api/src/lib/services/ArenaRoomService.ts index dd7df806..4f37d931 100644 --- a/src/server/lib/services/ArenaRoomService.ts +++ b/apps/api/src/lib/services/ArenaRoomService.ts @@ -15,8 +15,8 @@ import { type ArenaRoomStatusV1, type ArenaRoomV1, type ArenaTeamIdV1, -} from '@shared/contracts/arena'; -import type { RealmStage, RealmType } from '@shared/types/constants'; +} from '@daoyou/shared/contracts/arena'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; const ROOM_KEY_PREFIX = 'arena:room:v1:'; const CODE_KEY_PREFIX = 'arena:room-code:v1:'; diff --git a/src/server/lib/services/AttributeResetService.ts b/apps/api/src/lib/services/AttributeResetService.ts similarity index 94% rename from src/server/lib/services/AttributeResetService.ts rename to apps/api/src/lib/services/AttributeResetService.ts index 31a168ab..60bf7bdb 100644 --- a/src/server/lib/services/AttributeResetService.ts +++ b/apps/api/src/lib/services/AttributeResetService.ts @@ -9,10 +9,10 @@ import { consumeConsumableById } from '@server/lib/services/cultivator/Cultivato import { ATTRIBUTE_RESET_TALISMAN_NAME, ATTRIBUTE_RESET_TALISMAN_SCENARIO, -} from '@shared/config/attributeResetTalisman'; -import { getRealmStageNaturalAttributeValue } from '@shared/config/realmProgression'; -import type { RealmStage, RealmType } from '@shared/types/constants'; -import type { Attributes } from '@shared/types/cultivator'; +} from '@daoyou/shared/config/attributeResetTalisman'; +import { getRealmStageNaturalAttributeValue } from '@daoyou/shared/config/realmProgression'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; +import type { Attributes } from '@daoyou/shared/types/cultivator'; import { eq } from 'drizzle-orm'; import { findBagTalisman } from './BagConsumables'; diff --git a/src/server/lib/services/AuctionApplicationService.ts b/apps/api/src/lib/services/AuctionApplicationService.ts similarity index 98% rename from src/server/lib/services/AuctionApplicationService.ts rename to apps/api/src/lib/services/AuctionApplicationService.ts index 5717a18a..5deb42bc 100644 --- a/src/server/lib/services/AuctionApplicationService.ts +++ b/apps/api/src/lib/services/AuctionApplicationService.ts @@ -4,8 +4,8 @@ import { redisLockKeys, withRedisLock } from '@server/lib/redis/lock'; import type { AuctionBeastListRequest, AuctionListRequest, -} from '@shared/contracts/auction'; -import type { ResourceChangeDescriptor } from '@shared/contracts/resources'; +} from '@daoyou/shared/contracts/auction'; +import type { ResourceChangeDescriptor } from '@daoyou/shared/contracts/resources'; import { eq } from 'drizzle-orm'; import { buyItem, diff --git a/src/server/lib/services/AuctionService.ts b/apps/api/src/lib/services/AuctionService.ts similarity index 97% rename from src/server/lib/services/AuctionService.ts rename to apps/api/src/lib/services/AuctionService.ts index 01c70241..39139e48 100644 --- a/src/server/lib/services/AuctionService.ts +++ b/apps/api/src/lib/services/AuctionService.ts @@ -3,9 +3,9 @@ import * as auctionRepository from '@server/lib/repositories/auctionRepository'; import { AUCTION_MAX_TRANSACTION_TOTAL, calculateAuctionSettlement, -} from '@shared/config/auctionConfig'; -import { AUCTION_PRIVATE_LISTING_TALISMAN_SCENARIO } from '@shared/config/socialConfig'; -import type { AuctionBeastListRequest } from '@shared/contracts/auction'; +} from '@daoyou/shared/config/auctionConfig'; +import { AUCTION_PRIVATE_LISTING_TALISMAN_SCENARIO } from '@daoyou/shared/config/socialConfig'; +import type { AuctionBeastListRequest } from '@daoyou/shared/contracts/auction'; import { auctionBlockReason, auctionItemCategory, @@ -14,13 +14,13 @@ import { AuctionSnapshotSchema, type AuctionListingView, type AuctionListRequest, -} from '@shared/contracts/auction'; +} from '@daoyou/shared/contracts/auction'; import { beastAuctionBlockReason, BeastTransferSchema, -} from '@shared/contracts/beastTrade'; -import { itemDefinition, ItemGrantSchema } from '@shared/inventory'; -import type { MailAttachment } from '@shared/types/mail'; +} from '@daoyou/shared/contracts/beastTrade'; +import { itemDefinition, ItemGrantSchema } from '@daoyou/shared/inventory'; +import type { MailAttachment } from '@daoyou/shared/types/mail'; import { and, eq, sql } from 'drizzle-orm'; import { getExecutor, type DbTransaction } from '../drizzle/db'; import * as schema from '../drizzle/schema'; diff --git a/src/server/lib/services/BagConsumables.ts b/apps/api/src/lib/services/BagConsumables.ts similarity index 95% rename from src/server/lib/services/BagConsumables.ts rename to apps/api/src/lib/services/BagConsumables.ts index 6191a6d6..1feb5b74 100644 --- a/src/server/lib/services/BagConsumables.ts +++ b/apps/api/src/lib/services/BagConsumables.ts @@ -1,6 +1,6 @@ import { recordJournalChange } from './JournalSettlement'; -import { ConsumableFactsSchema } from '@shared/items/definitions/consumables'; -import type { Consumable } from '@shared/types/cultivator'; +import { ConsumableFactsSchema } from '@daoyou/shared/items/definitions/consumables'; +import type { Consumable } from '@daoyou/shared/types/cultivator'; import { and, asc, eq } from 'drizzle-orm'; import { getExecutor, diff --git a/src/server/lib/services/BagRecycleService.ts b/apps/api/src/lib/services/BagRecycleService.ts similarity index 90% rename from src/server/lib/services/BagRecycleService.ts rename to apps/api/src/lib/services/BagRecycleService.ts index 9d2d4641..404a9828 100644 --- a/src/server/lib/services/BagRecycleService.ts +++ b/apps/api/src/lib/services/BagRecycleService.ts @@ -5,21 +5,21 @@ import type { RecycleQuote, RecycleResult, RecycleSelection, -} from '@shared/contracts/recycle'; -import { InventoryEquipmentSchema } from '@shared/inventory/equipment'; -import { recycleBlockingReason } from '@shared/inventory/recycle'; +} from '@daoyou/shared/contracts/recycle'; +import { InventoryEquipmentSchema } from '@daoyou/shared/inventory/equipment'; +import { recycleBlockingReason } from '@daoyou/shared/inventory/recycle'; import { blueprintRecycleUnitPrice, equipmentRecycleUnitPrice, manualJadeRecycleUnitPrice, seedRecycleUnitPrice, -} from '@shared/inventory/recyclePrice'; -import { ConsumableFactsSchema } from '@shared/items/definitions/consumables'; -import { SeedFactsSchema } from '@shared/items/definitions/seeds'; -import { materialFactsOf } from '@shared/items/material'; -import { findItemDefinition } from '@shared/items/registry'; -import { calculateSpiritFruitRecycleUnitPrice } from '@shared/lib/pillRecyclePrice'; -import { QUALITY_ORDER } from '@shared/types/constants'; +} from '@daoyou/shared/inventory/recyclePrice'; +import { ConsumableFactsSchema } from '@daoyou/shared/items/definitions/consumables'; +import { SeedFactsSchema } from '@daoyou/shared/items/definitions/seeds'; +import { materialFactsOf } from '@daoyou/shared/items/material'; +import { findItemDefinition } from '@daoyou/shared/items/registry'; +import { calculateSpiritFruitRecycleUnitPrice } from '@daoyou/shared/lib/pillRecyclePrice'; +import { QUALITY_ORDER } from '@daoyou/shared/types/constants'; import { and, eq, inArray, sql } from 'drizzle-orm'; import { randomUUID } from 'node:crypto'; import { playerCommandExecutor } from './CommandExecutors'; diff --git a/src/server/lib/services/BodyCultivationBreakthroughService.ts b/apps/api/src/lib/services/BodyCultivationBreakthroughService.ts similarity index 88% rename from src/server/lib/services/BodyCultivationBreakthroughService.ts rename to apps/api/src/lib/services/BodyCultivationBreakthroughService.ts index a66b2071..d97f61e1 100644 --- a/src/server/lib/services/BodyCultivationBreakthroughService.ts +++ b/apps/api/src/lib/services/BodyCultivationBreakthroughService.ts @@ -4,9 +4,9 @@ import { type DbTransaction, } from '@server/lib/drizzle/db'; import * as schema from '@server/lib/drizzle/schema'; -import type { BodyCultivationBreakthroughReadinessData } from '@shared/contracts/bodyCultivation'; -import { previewBodyCultivationRealmBreakthrough } from '@shared/lib/bodyCultivation/breakthrough'; -import type { Cultivator } from '@shared/types/cultivator'; +import type { BodyCultivationBreakthroughReadinessData } from '@daoyou/shared/contracts/bodyCultivation'; +import { previewBodyCultivationRealmBreakthrough } from '@daoyou/shared/lib/bodyCultivation/breakthrough'; +import type { Cultivator } from '@daoyou/shared/types/cultivator'; import { and, eq } from 'drizzle-orm'; export type BodyCultivationFacts = Pick< diff --git a/src/server/lib/services/CommandExecutors.ts b/apps/api/src/lib/services/CommandExecutors.ts similarity index 98% rename from src/server/lib/services/CommandExecutors.ts rename to apps/api/src/lib/services/CommandExecutors.ts index 252d8076..4dfc8935 100644 --- a/src/server/lib/services/CommandExecutors.ts +++ b/apps/api/src/lib/services/CommandExecutors.ts @@ -13,12 +13,12 @@ import { type RedisLeaseContext, } from '@server/lib/redis/lock'; import { publishResourceEvents } from '@server/lib/services/playerStateBroadcaster'; -import type { PlayerResourceMutationMeta } from '@shared/contracts/player'; -import type { ResourceChangeDescriptor } from '@shared/contracts/resources'; +import type { PlayerResourceMutationMeta } from '@daoyou/shared/contracts/player'; +import type { ResourceChangeDescriptor } from '@daoyou/shared/contracts/resources'; import { createHash } from 'node:crypto'; import { captureJournalSettlement } from './JournalSettlement'; import { claimJournalOperation, completeJournalOperation, isJournalActivity, journalOperationKey } from '@server/lib/repositories/playerJournalRepository'; -import type { PlayerJournalEvent } from '@shared/contracts/playerJournal'; +import type { PlayerJournalEvent } from '@daoyou/shared/contracts/playerJournal'; import { baselinesFromResourceChanges, resourceEventCommitter, diff --git a/src/server/lib/services/ConditionService.ts b/apps/api/src/lib/services/ConditionService.ts similarity index 96% rename from src/server/lib/services/ConditionService.ts rename to apps/api/src/lib/services/ConditionService.ts index 2e2daf4a..070855da 100644 --- a/src/server/lib/services/ConditionService.ts +++ b/apps/api/src/lib/services/ConditionService.ts @@ -2,25 +2,25 @@ import { characterResourceMaxima, normalizeCharacterResource, type CultivatorDisplayInput, -} from '@shared/lib/cultivatorDisplay'; +} from '@daoyou/shared/lib/cultivatorDisplay'; import { getBreakthroughPenalty, isConditionStatusActive, projectNaturalRecoveryResources, -} from '@shared/lib/condition'; -import { evaluateFateContext } from '@shared/lib/fates'; +} from '@daoyou/shared/lib/condition'; +import { evaluateFateContext } from '@daoyou/shared/lib/fates'; import { isConditionStatusKey, -} from '@shared/lib/conditionStatusRegistry'; +} from '@daoyou/shared/lib/conditionStatusRegistry'; import { createDefaultBodyCultivationState, normalizeBodyCultivationState, -} from '@shared/lib/bodyCultivation/normalize'; +} from '@daoyou/shared/lib/bodyCultivation/normalize'; import { breakthroughBodyCultivationRealm as advanceBodyCultivationRealm, -} from '@shared/lib/bodyCultivation/breakthrough'; -import { PILL_TOXICITY_CAP } from '@shared/config/consumableSystem'; -import { normalizeMarrowWashState } from '@shared/lib/marrowWash'; +} from '@daoyou/shared/lib/bodyCultivation/breakthrough'; +import { PILL_TOXICITY_CAP } from '@daoyou/shared/config/consumableSystem'; +import { normalizeMarrowWashState } from '@daoyou/shared/lib/marrowWash'; import type { BodyCultivationRealm, ConditionStatusDuration, @@ -29,8 +29,8 @@ import type { ConditionResourcePoint, CultivatorCondition, TemperingTrackKey, -} from '@shared/types/condition'; -import type { Cultivator } from '@shared/types/cultivator'; +} from '@daoyou/shared/types/condition'; +import type { Cultivator } from '@daoyou/shared/types/cultivator'; export type ConditionCultivatorFacts = CultivatorDisplayInput & Pick; diff --git a/src/server/lib/services/ConsumableUseEngine.ts b/apps/api/src/lib/services/ConsumableUseEngine.ts similarity index 95% rename from src/server/lib/services/ConsumableUseEngine.ts rename to apps/api/src/lib/services/ConsumableUseEngine.ts index 6a78af0b..bc195f08 100644 --- a/src/server/lib/services/ConsumableUseEngine.ts +++ b/apps/api/src/lib/services/ConsumableUseEngine.ts @@ -7,28 +7,28 @@ import type { RedisLeaseContext } from '@server/lib/redis/lock'; import { loadPlayerConsumableOperationFacts } from '@server/lib/services/cultivator/CultivatorConditionFactsReader'; import { consumeConsumableById } from '@server/lib/services/cultivator/CultivatorInventoryRepository'; import { replaceSpiritualRoots } from '@server/lib/services/cultivator/CultivatorProfileRepository'; -import { stripExpCapForStorage } from '@server/utils/cultivationUtils'; +import { stripExpCapForStorage } from '@daoyou/shared/engine/cultivation/cultivationUtils'; import { ATTRIBUTE_RESET_TALISMAN_NAME, ATTRIBUTE_RESET_TALISMAN_SCENARIO, -} from '@shared/config/attributeResetTalisman'; +} from '@daoyou/shared/config/attributeResetTalisman'; import { QI_RESTORE_TALISMAN_SCENARIOS, isQiRestoreTalismanScenario, -} from '@shared/config/qiSystem'; +} from '@daoyou/shared/config/qiSystem'; import { SECT_MERIDIAN_RESET_TALISMAN_NAME, SECT_MERIDIAN_RESET_TALISMAN_SCENARIO, -} from '@shared/config/sectMeridianResetTalisman'; +} from '@daoyou/shared/config/sectMeridianResetTalisman'; import { isPillConsumable, isSpiritFruitConsumable, isTalismanConsumable, -} from '@shared/lib/consumables'; -import { canUseDungeonRecoveryPill } from '@shared/lib/dungeon/rest'; -import { getAttributeLabel } from '@shared/lib/gameConceptDisplay'; -import { getTrackConfig } from '@shared/lib/trackConfigRegistry'; -import type { Consumable } from '@shared/types/cultivator'; +} from '@daoyou/shared/lib/consumables'; +import { canUseDungeonRecoveryPill } from '@daoyou/shared/lib/dungeon/rest'; +import { getAttributeLabel } from '@daoyou/shared/lib/gameConceptDisplay'; +import { getTrackConfig } from '@daoyou/shared/lib/trackConfigRegistry'; +import type { Consumable } from '@daoyou/shared/types/cultivator'; import { randomUUID } from 'crypto'; import { and, eq, ne } from 'drizzle-orm'; import { diff --git a/src/server/lib/services/CraftApplicationService.ts b/apps/api/src/lib/services/CraftApplicationService.ts similarity index 96% rename from src/server/lib/services/CraftApplicationService.ts rename to apps/api/src/lib/services/CraftApplicationService.ts index 101577fe..905e49df 100644 --- a/src/server/lib/services/CraftApplicationService.ts +++ b/apps/api/src/lib/services/CraftApplicationService.ts @@ -3,11 +3,11 @@ import type { DbTransaction } from '@server/lib/drizzle/db'; import { createDomainEvent } from '@server/lib/mq/domainEventWriter'; import { publishTransactionalMessageBestEffort } from '@server/lib/mq/transactionalMessagePublisher'; import { normalizeFreeformLlmInput } from '@server/utils/llmPayload'; -import type { ResourceChangeDescriptor } from '@shared/contracts/resources'; -import type { ResourceOperationSettlement } from '@shared/engine/resource/types'; -import { QUALITY_ORDER, type Quality } from '@shared/types/constants'; -import type { AlchemyMode } from '@shared/types/consumable'; -import type { Consumable } from '@shared/types/cultivator'; +import type { ResourceChangeDescriptor } from '@daoyou/shared/contracts/resources'; +import type { ResourceOperationSettlement } from '@daoyou/shared/engine/resource/types'; +import { QUALITY_ORDER, type Quality } from '@daoyou/shared/types/constants'; +import type { AlchemyMode } from '@daoyou/shared/types/consumable'; +import type { Consumable } from '@daoyou/shared/types/cultivator'; import { assertAlchemyMaterialVersions } from './alchemy/AlchemyInventory'; import { prepareFormulaCraft } from './AlchemyFormulaService'; import { prepareAlchemyCraft } from './alchemyServiceV2'; diff --git a/src/server/lib/services/CultivatorConditionApplicationService.ts b/apps/api/src/lib/services/CultivatorConditionApplicationService.ts similarity index 98% rename from src/server/lib/services/CultivatorConditionApplicationService.ts rename to apps/api/src/lib/services/CultivatorConditionApplicationService.ts index 3d13eaeb..55254eff 100644 --- a/src/server/lib/services/CultivatorConditionApplicationService.ts +++ b/apps/api/src/lib/services/CultivatorConditionApplicationService.ts @@ -5,13 +5,13 @@ import { readMarrowWashFacts } from '@server/lib/services/cultivator/CultivatorF import { getCultivatorConsumableById } from '@server/lib/services/cultivator/CultivatorInventoryRepository'; import { setSpiritualRootMarrowWashBonus } from '@server/lib/services/cultivator/CultivatorProfileRepository'; import { updateCultivator } from '@server/lib/services/cultivator/CultivatorStateRepository'; -import { stripExpCapForStorage } from '@server/utils/cultivationUtils'; +import { stripExpCapForStorage } from '@daoyou/shared/engine/cultivation/cultivationUtils'; import { breakthroughMarrowWash, MARROW_WASH_BREAKTHROUGH_QI_COST, SPIRITUAL_ROOT_EFFECTIVE_STRENGTH_CAP, -} from '@shared/lib/marrowWash'; -import type { RealmStage, RealmType } from '@shared/types/constants'; +} from '@daoyou/shared/lib/marrowWash'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; import { randomUUID } from 'crypto'; import { and, eq, sql } from 'drizzle-orm'; import { inventoryItems } from '../drizzle/schema'; diff --git a/src/server/lib/services/CultivatorConditionResourceChanges.ts b/apps/api/src/lib/services/CultivatorConditionResourceChanges.ts similarity index 93% rename from src/server/lib/services/CultivatorConditionResourceChanges.ts rename to apps/api/src/lib/services/CultivatorConditionResourceChanges.ts index c927cb62..aac72de6 100644 --- a/src/server/lib/services/CultivatorConditionResourceChanges.ts +++ b/apps/api/src/lib/services/CultivatorConditionResourceChanges.ts @@ -1,18 +1,18 @@ -import { getOrInitCultivationProgress } from '@server/utils/cultivationUtils'; -import { isAttributeResetTalismanScenario } from '@shared/config/attributeResetTalisman'; -import { isSectMeridianResetTalismanScenario } from '@shared/config/sectMeridianResetTalisman'; +import { getOrInitCultivationProgress } from '@daoyou/shared/engine/cultivation/cultivationUtils'; +import { isAttributeResetTalismanScenario } from '@daoyou/shared/config/attributeResetTalisman'; +import { isSectMeridianResetTalismanScenario } from '@daoyou/shared/config/sectMeridianResetTalisman'; import { RESOURCE_DATA_SCHEMAS, type ResourceChangeDescriptor, type ResourceDataMap, -} from '@shared/contracts/resources'; -import { isTalismanConsumable } from '@shared/lib/consumables'; -import type { RealmStage, RealmType } from '@shared/types/constants'; +} from '@daoyou/shared/contracts/resources'; +import { isTalismanConsumable } from '@daoyou/shared/lib/consumables'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; import type { Consumable, CultivationProgress, Cultivator, -} from '@shared/types/cultivator'; +} from '@daoyou/shared/types/cultivator'; import { qiCurrencyChange, qiCurrencyPatch, diff --git a/src/server/lib/services/CultivatorCreationApplicationService.ts b/apps/api/src/lib/services/CultivatorCreationApplicationService.ts similarity index 93% rename from src/server/lib/services/CultivatorCreationApplicationService.ts rename to apps/api/src/lib/services/CultivatorCreationApplicationService.ts index ea669924..257655d1 100644 --- a/src/server/lib/services/CultivatorCreationApplicationService.ts +++ b/apps/api/src/lib/services/CultivatorCreationApplicationService.ts @@ -1,5 +1,5 @@ import type { DbTransaction } from '@server/lib/drizzle/db'; -import { invalidateActiveCultivatorRef } from '@server/lib/hono/middleware'; +import { invalidateActiveCultivatorRef } from '@server/lib/auth/activeCultivator'; import { redisLockKeys, withRedisLock, @@ -9,8 +9,8 @@ import { getTempCharacter, getTempFates, } from '@server/lib/repositories/redisCultivatorRepository'; -import type { ResourceChangeDescriptor } from '@shared/contracts/resources'; -import type { Cultivator } from '@shared/types/cultivator'; +import type { ResourceChangeDescriptor } from '@daoyou/shared/contracts/resources'; +import type { Cultivator } from '@daoyou/shared/types/cultivator'; import { MailService } from './MailService'; import { TaskService } from './TaskService'; import { playerCommandExecutor } from './CommandExecutors'; diff --git a/src/server/lib/services/CultivatorProfileApplicationService.ts b/apps/api/src/lib/services/CultivatorProfileApplicationService.ts similarity index 97% rename from src/server/lib/services/CultivatorProfileApplicationService.ts rename to apps/api/src/lib/services/CultivatorProfileApplicationService.ts index b2573a49..b0d1b1c9 100644 --- a/src/server/lib/services/CultivatorProfileApplicationService.ts +++ b/apps/api/src/lib/services/CultivatorProfileApplicationService.ts @@ -2,7 +2,7 @@ import { breakthroughHistory, cultivators, } from '@server/lib/drizzle/schema'; -import { invalidateActiveCultivatorRef } from '@server/lib/hono/middleware'; +import { invalidateActiveCultivatorRef } from '@server/lib/auth/activeCultivator'; import { redisLockKeys, withRedisLock, @@ -10,9 +10,9 @@ import { import { getRealmStageNaturalAttributeValue, getRealmStageUnallocatedAttributeBudget, -} from '@shared/config/realmProgression'; -import type { ResourceChangeDescriptor } from '@shared/contracts/resources'; -import type { RealmStage, RealmType } from '@shared/types/constants'; +} from '@daoyou/shared/config/realmProgression'; +import type { ResourceChangeDescriptor } from '@daoyou/shared/contracts/resources'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; import { and, eq } from 'drizzle-orm'; import { AttributeResetService, diff --git a/src/server/lib/services/DevCultivatorService.ts b/apps/api/src/lib/services/DevCultivatorService.ts similarity index 94% rename from src/server/lib/services/DevCultivatorService.ts rename to apps/api/src/lib/services/DevCultivatorService.ts index f091879f..8bcc55ae 100644 --- a/src/server/lib/services/DevCultivatorService.ts +++ b/apps/api/src/lib/services/DevCultivatorService.ts @@ -1,15 +1,16 @@ +import { getRuntimeEnvironment } from '@server/lib/config/environment'; import { getOrInitCultivationProgress, stripExpCapForStorage, -} from '@server/utils/cultivationUtils'; -import { allowsLocalDevTools } from '@shared/config/deployment'; -import type { DevCultivatorPatch } from '@shared/contracts/devTools'; -import { compileCurrentSectCombatV6 } from '@shared/engine/combat-v6/content'; -import { combatCharacterLevel } from '@shared/engine/combat-v6/projection/character-level'; -import { SECT_PROGRESSION } from '@shared/engine/combat-v6/sect-progression/pack'; -import type { CultivatorCondition } from '@shared/types/condition'; -import type { RealmStage, RealmType } from '@shared/types/constants'; -import type { CultivationProgress } from '@shared/types/cultivator'; +} from '@daoyou/shared/engine/cultivation/cultivationUtils'; +import { allowsLocalDevTools } from '@daoyou/shared/config/deployment'; +import type { DevCultivatorPatch } from '@daoyou/shared/contracts/devTools'; +import { compileCurrentSectCombatV6 } from '@daoyou/shared/engine/combat-v6/content'; +import { combatCharacterLevel } from '@daoyou/shared/engine/combat-v6/projection/character-level'; +import { SECT_PROGRESSION } from '@daoyou/shared/engine/combat-v6/sect-progression/pack'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; +import type { CultivationProgress } from '@daoyou/shared/types/cultivator'; import { and, eq } from 'drizzle-orm'; import { db } from '../drizzle/db'; import { @@ -52,7 +53,7 @@ export async function patchDevCultivator( owner: string, input: DevCultivatorPatch, ) { - if (!allowsLocalDevTools(process.env.APP_ENV, process.env.NODE_ENV)) + if (!allowsLocalDevTools(getRuntimeEnvironment().APP_ENV, getRuntimeEnvironment().NODE_ENV)) throw new InventoryError('仅允许纯本地环境使用'); return withRedisLock( { diff --git a/src/server/lib/services/DevDivinationService.ts b/apps/api/src/lib/services/DevDivinationService.ts similarity index 83% rename from src/server/lib/services/DevDivinationService.ts rename to apps/api/src/lib/services/DevDivinationService.ts index 29688f7d..5bcff31c 100644 --- a/src/server/lib/services/DevDivinationService.ts +++ b/apps/api/src/lib/services/DevDivinationService.ts @@ -1,4 +1,5 @@ -import { allowsLocalDevTools } from '@shared/config/deployment'; +import { getRuntimeEnvironment } from '@server/lib/config/environment'; +import { allowsLocalDevTools } from '@daoyou/shared/config/deployment'; import { eq } from 'drizzle-orm'; import { db } from '../drizzle/db'; import { cultivators, dailyDivinations } from '../drizzle/schema'; @@ -6,7 +7,7 @@ import { redisLockKeys, withRedisLock } from '../redis/lock'; import { DivinationError } from './DivinationService'; export async function resetDevDivination(owner: string) { - if (!allowsLocalDevTools(process.env.APP_ENV, process.env.NODE_ENV)) + if (!allowsLocalDevTools(getRuntimeEnvironment().APP_ENV, getRuntimeEnvironment().NODE_ENV)) throw new DivinationError('仅允许纯本地环境使用'); return withRedisLock( { diff --git a/src/server/lib/services/DevInventoryService.ts b/apps/api/src/lib/services/DevInventoryService.ts similarity index 92% rename from src/server/lib/services/DevInventoryService.ts rename to apps/api/src/lib/services/DevInventoryService.ts index 8ae19414..442bda2c 100644 --- a/src/server/lib/services/DevInventoryService.ts +++ b/apps/api/src/lib/services/DevInventoryService.ts @@ -1,4 +1,5 @@ -import { allowsLocalDevTools } from '@shared/config/deployment'; +import { getRuntimeEnvironment } from '@server/lib/config/environment'; +import { allowsLocalDevTools } from '@daoyou/shared/config/deployment'; import { and, eq, inArray } from 'drizzle-orm'; import { db } from '../drizzle/db'; import { @@ -13,7 +14,7 @@ import { ResourceEventCommitter } from './ResourceEventCommitter'; /** Only the local bag and its equipment references are cleared; storage is retained. */ export async function clearDevInventoryBag(owner: string) { - if (!allowsLocalDevTools(process.env.APP_ENV, process.env.NODE_ENV)) + if (!allowsLocalDevTools(getRuntimeEnvironment().APP_ENV, getRuntimeEnvironment().NODE_ENV)) throw new InventoryError('仅允许纯本地环境使用'); const committed = await withRedisLock( { diff --git a/src/server/lib/services/DivinationService.ts b/apps/api/src/lib/services/DivinationService.ts similarity index 98% rename from src/server/lib/services/DivinationService.ts rename to apps/api/src/lib/services/DivinationService.ts index 74eb13c9..bac75361 100644 --- a/src/server/lib/services/DivinationService.ts +++ b/apps/api/src/lib/services/DivinationService.ts @@ -7,12 +7,12 @@ import { type RedisLeaseContext, } from '@server/lib/redis/lock'; import { streamAiText } from '@server/utils/aiClient'; -import { QI_RESTORE_TALISMAN_SCENARIOS } from '@shared/config/qiSystem'; +import { QI_RESTORE_TALISMAN_SCENARIOS } from '@daoyou/shared/config/qiSystem'; import type { DivinationRecord, DivinationStreamEvent, DivinationView, -} from '@shared/contracts/divination'; +} from '@daoyou/shared/contracts/divination'; import { DIVINATION_DIRECTIONS, divinationDayKey, @@ -21,7 +21,7 @@ import { resolveDivination, type DivinationDice, type DivinationDirection, -} from '@shared/lib/divination'; +} from '@daoyou/shared/lib/divination'; import { and, eq } from 'drizzle-orm'; import { randomInt, randomUUID } from 'node:crypto'; import { assertInventoryIdle, grantInventory } from './InventoryService'; diff --git a/src/server/lib/services/DungeonApplicationService.ts b/apps/api/src/lib/services/DungeonApplicationService.ts similarity index 97% rename from src/server/lib/services/DungeonApplicationService.ts rename to apps/api/src/lib/services/DungeonApplicationService.ts index 6f81533b..9d8f6077 100644 --- a/src/server/lib/services/DungeonApplicationService.ts +++ b/apps/api/src/lib/services/DungeonApplicationService.ts @@ -10,17 +10,17 @@ import { withRedisLock, type RedisLeaseContext, } from '@server/lib/redis/lock'; -import type { DungeonExpectedState } from '@shared/contracts/combatV6Dungeon'; +import type { DungeonExpectedState } from '@daoyou/shared/contracts/combatV6Dungeon'; import { RESOURCE_DATA_SCHEMAS, type ResourceChangeDescriptor, -} from '@shared/contracts/resources'; -import { dungeonReadiness } from '@shared/lib/dungeon/readiness'; +} from '@daoyou/shared/contracts/resources'; +import { dungeonReadiness } from '@daoyou/shared/lib/dungeon/readiness'; import { canChallengeDungeonRealm, getMapNode, isSatelliteNode, -} from '@shared/lib/game/mapSystem'; +} from '@daoyou/shared/lib/game/mapSystem'; import { playerCommandExecutor } from './CommandExecutors'; @@ -35,7 +35,7 @@ type DungeonCommand = actionId: string; runId: string; round: number; - materialSelections: import('@shared/contracts/combatV6Dungeon').DungeonMaterialSelection[]; + materialSelections: import('@daoyou/shared/contracts/combatV6Dungeon').DungeonMaterialSelection[]; } | { kind: 'battle-begin'; encounterId: string } | { diff --git a/src/server/lib/services/EnlightenmentService.ts b/apps/api/src/lib/services/EnlightenmentService.ts similarity index 91% rename from src/server/lib/services/EnlightenmentService.ts rename to apps/api/src/lib/services/EnlightenmentService.ts index c0bd5dbc..37f1b5d5 100644 --- a/src/server/lib/services/EnlightenmentService.ts +++ b/apps/api/src/lib/services/EnlightenmentService.ts @@ -2,27 +2,27 @@ import { describeJournal } from './JournalSettlement'; import { getOrInitCultivationProgress, stripExpCapForStorage, -} from '@server/utils/cultivationUtils'; +} from '@daoyou/shared/engine/cultivation/cultivationUtils'; import type { EnlightenmentRequest, EnlightenmentResult, EnlightenmentView, -} from '@shared/contracts/enlightenment'; -import { addItems } from '@shared/inventory'; -import { inventoryStackIdentity } from '@shared/inventory/stack-key'; -import { evaluateFateContext } from '@shared/lib/fates'; -import { projectNaturalQiState } from '@shared/lib/qi'; +} from '@daoyou/shared/contracts/enlightenment'; +import { addItems } from '@daoyou/shared/inventory'; +import { inventoryStackIdentity } from '@daoyou/shared/inventory/stack-key'; +import { evaluateFateContext } from '@daoyou/shared/lib/fates'; +import { projectNaturalQiState } from '@daoyou/shared/lib/qi'; import { prepareEnlightenment, rollEnlightenment, -} from '@shared/manuals/enlightenment'; -import type { RealmStage, RealmType } from '@shared/types/constants'; -import type { CultivationProgress } from '@shared/types/cultivator'; +} from '@daoyou/shared/manuals/enlightenment'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; +import type { CultivationProgress } from '@daoyou/shared/types/cultivator'; import { and, eq, inArray, or } from 'drizzle-orm'; import { randomInt, randomUUID } from 'node:crypto'; import { db, type DbExecutor } from '../drizzle/db'; import { cultivators, inventoryItems } from '../drizzle/schema'; -import type { ActiveCultivatorRef } from '../hono/types'; +import type { ActiveCultivatorRef } from '../auth/types'; import { playerCommandExecutor } from './CommandExecutors'; import { getCultivatorPreHeavenFates } from './cultivator/CultivatorProfileRepository'; import { diff --git a/src/server/lib/services/FateConfig.ts b/apps/api/src/lib/services/FateConfig.ts similarity index 93% rename from src/server/lib/services/FateConfig.ts rename to apps/api/src/lib/services/FateConfig.ts index 5c5f9343..3308abf4 100644 --- a/src/server/lib/services/FateConfig.ts +++ b/apps/api/src/lib/services/FateConfig.ts @@ -1,4 +1,4 @@ -import type { Quality } from '@shared/types/constants'; +import type { Quality } from '@daoyou/shared/types/constants'; export const FATE_SLOT_COUNT = 3; export const FATE_CANDIDATE_COUNT = 6; diff --git a/src/server/lib/services/FateEngine.ts b/apps/api/src/lib/services/FateEngine.ts similarity index 97% rename from src/server/lib/services/FateEngine.ts rename to apps/api/src/lib/services/FateEngine.ts index 3fd14487..862bdbf6 100644 --- a/src/server/lib/services/FateEngine.ts +++ b/apps/api/src/lib/services/FateEngine.ts @@ -3,12 +3,12 @@ import { createHash } from 'node:crypto'; import { normalizeFate as normalizeSharedFate, normalizeFates as normalizeSharedFates, -} from '@shared/lib/fates'; +} from '@daoyou/shared/lib/fates'; import type { FateGenerationCategory, PreHeavenFate, -} from '@shared/types/cultivator'; -import type { Quality } from '@shared/types/constants'; +} from '@daoyou/shared/types/cultivator'; +import type { Quality } from '@daoyou/shared/types/constants'; import { FATE_CANDIDATE_COUNT, FATE_DUAL_SIDED_CHANCE, diff --git a/src/server/lib/services/FateFragmentRegistry.ts b/apps/api/src/lib/services/FateFragmentRegistry.ts similarity index 99% rename from src/server/lib/services/FateFragmentRegistry.ts rename to apps/api/src/lib/services/FateFragmentRegistry.ts index d8c5e692..448f1967 100644 --- a/src/server/lib/services/FateFragmentRegistry.ts +++ b/apps/api/src/lib/services/FateFragmentRegistry.ts @@ -2,9 +2,9 @@ import type { FateEffectEntry, FateEffectPolarity, FateEffectType, -} from '@shared/types/cultivator'; -import type { Quality } from '@shared/types/constants'; -import { QUALITY_ORDER } from '@shared/types/constants'; +} from '@daoyou/shared/types/cultivator'; +import type { Quality } from '@daoyou/shared/types/constants'; +import { QUALITY_ORDER } from '@daoyou/shared/types/constants'; import { FATE_QUALITY_SCALE, FATE_ROLL_VERSION, diff --git a/src/server/lib/services/FateReshapeApplicationService.ts b/apps/api/src/lib/services/FateReshapeApplicationService.ts similarity index 98% rename from src/server/lib/services/FateReshapeApplicationService.ts rename to apps/api/src/lib/services/FateReshapeApplicationService.ts index 46b76926..072f78d0 100644 --- a/src/server/lib/services/FateReshapeApplicationService.ts +++ b/apps/api/src/lib/services/FateReshapeApplicationService.ts @@ -1,6 +1,6 @@ import type { DbTransaction } from '@server/lib/drizzle/db'; import { redisLockKeys, withRedisLock } from '@server/lib/redis/lock'; -import type { ResourceChangeDescriptor } from '@shared/contracts/resources'; +import type { ResourceChangeDescriptor } from '@daoyou/shared/contracts/resources'; import { playerCommandExecutor } from './CommandExecutors'; import { FateReshapeService, diff --git a/src/server/lib/services/FateReshapeService.ts b/apps/api/src/lib/services/FateReshapeService.ts similarity index 98% rename from src/server/lib/services/FateReshapeService.ts rename to apps/api/src/lib/services/FateReshapeService.ts index 87e533cb..eab82586 100644 --- a/src/server/lib/services/FateReshapeService.ts +++ b/apps/api/src/lib/services/FateReshapeService.ts @@ -12,11 +12,11 @@ import { getPlayerPreHeavenFates, replacePreHeavenFates, } from '@server/lib/services/cultivator/CultivatorProfileRepository'; -import type { Consumable } from '@shared/types/cultivator'; +import type { Consumable } from '@daoyou/shared/types/cultivator'; import type { FateReshapeSessionDTO, FateReshapeSessionStore, -} from '@shared/types/fateReshape'; +} from '@daoyou/shared/types/fateReshape'; import { getExecutor, type DbExecutor, diff --git a/src/server/lib/services/ForgingNarrativeService.ts b/apps/api/src/lib/services/ForgingNarrativeService.ts similarity index 74% rename from src/server/lib/services/ForgingNarrativeService.ts rename to apps/api/src/lib/services/ForgingNarrativeService.ts index 41b0ad8b..c8dbcdac 100644 --- a/src/server/lib/services/ForgingNarrativeService.ts +++ b/apps/api/src/lib/services/ForgingNarrativeService.ts @@ -1,12 +1,13 @@ +import { getRuntimeEnvironment } from '@server/lib/config/environment'; import { renderPrompt } from '@server/lib/prompts'; import { generateAiObject } from '@server/utils/aiClient'; import { truncateText } from '@server/utils/llmPayload'; -import { equipmentRealm } from '@shared/engine/combat-v6/equipment/realm'; -import type { DaoEquipmentSlot } from '@shared/engine/combat-v6/equipment/types'; -import { DAO_WEAPONS, type DaoWeaponType } from '@shared/engine/combat-v6/equipment/weapons'; -import { ForgedEquipmentCopySchema } from '@shared/forging/narrative'; -import { EQUIPMENT_SLOT_NAMES } from '@shared/items/definitions/equipment-blueprints'; -import type { MaterialFacts } from '@shared/items/definitions/materials'; +import { equipmentRealm } from '@daoyou/shared/engine/combat-v6/equipment/realm'; +import type { DaoEquipmentSlot } from '@daoyou/shared/engine/combat-v6/equipment/types'; +import { DAO_WEAPONS, type DaoWeaponType } from '@daoyou/shared/engine/combat-v6/equipment/weapons'; +import { ForgedEquipmentCopySchema } from '@daoyou/shared/forging/narrative'; +import { EQUIPMENT_SLOT_NAMES } from '@daoyou/shared/items/definitions/equipment-blueprints'; +import type { MaterialFacts } from '@daoyou/shared/items/definitions/materials'; import { z } from 'zod'; // 提供商只接收基础 JSON Schema;Unicode 字数及纯文本规则在本地校验。 @@ -25,8 +26,8 @@ export async function generateForgingNarrative(input: { intent?: string; }) { if ( - process.env.DISABLE_LLM_NAMING === 'true' || - process.env.ENABLE_LLM_NAMING === 'false' + getRuntimeEnvironment().DISABLE_LLM_NAMING === 'true' || + getRuntimeEnvironment().ENABLE_LLM_NAMING === 'false' ) return null; try { diff --git a/src/server/lib/services/ForgingService.ts b/apps/api/src/lib/services/ForgingService.ts similarity index 95% rename from src/server/lib/services/ForgingService.ts rename to apps/api/src/lib/services/ForgingService.ts index 6b6c260f..406d2cfc 100644 --- a/src/server/lib/services/ForgingService.ts +++ b/apps/api/src/lib/services/ForgingService.ts @@ -6,35 +6,35 @@ import type { VaultView, WithdrawMaterialSchema, WithdrawVaultPageSchema, -} from '@shared/contracts/forging'; +} from '@daoyou/shared/contracts/forging'; import { BEAST_SPECIES, BeastSchema, generateStarterBeast, -} from '@shared/engine/combat-v6/beasts'; +} from '@daoyou/shared/engine/combat-v6/beasts'; import { BEAST_CAPACITY, gainBeastExp, nextBeastExp, -} from '@shared/engine/combat-v6/beasts/progression'; -import { generateForgedEquipment } from '@shared/engine/combat-v6/equipment/forging'; -import { buildSpiritFieldSeedMaterialFromPlant } from '@shared/engine/spirit-field/seedMaterial'; -import { forgingCost, forgingInputs, validateForgeWeaponType } from '@shared/forging/rules'; -import { storyMarkForSignal } from '@shared/story/signals'; +} from '@daoyou/shared/engine/combat-v6/beasts/progression'; +import { generateForgedEquipment } from '@daoyou/shared/engine/combat-v6/equipment/forging'; +import { buildSpiritFieldSeedMaterialFromPlant } from '@daoyou/shared/engine/spirit-field/seedMaterial'; +import { forgingCost, forgingInputs, validateForgeWeaponType } from '@daoyou/shared/forging/rules'; +import { storyMarkForSignal } from '@daoyou/shared/story/signals'; import { addItems, itemDefinition, type InventoryItem, -} from '@shared/inventory'; -import { consumableFactsOf } from '@shared/items/definitions/consumables'; +} from '@daoyou/shared/inventory'; +import { consumableFactsOf } from '@daoyou/shared/items/definitions/consumables'; import { INVENTORY_MATERIAL_TYPES, MaterialFactsSchema, -} from '@shared/items/definitions/materials'; -import { seedFactsOf } from '@shared/items/definitions/seeds'; -import { legacyMaterialUnavailableReason } from '@shared/items/legacy-material'; -import { materialFactsOf } from '@shared/items/material'; -import { parseMailAttachments } from '@shared/lib/itemLibrary'; +} from '@daoyou/shared/items/definitions/materials'; +import { seedFactsOf } from '@daoyou/shared/items/definitions/seeds'; +import { legacyMaterialUnavailableReason } from '@daoyou/shared/items/legacy-material'; +import { materialFactsOf } from '@daoyou/shared/items/material'; +import { parseMailAttachments } from '@daoyou/shared/lib/itemLibrary'; import { and, asc, count, eq, gte, ilike, inArray, or, sql } from 'drizzle-orm'; import { randomInt, randomUUID } from 'node:crypto'; import type { z } from 'zod'; diff --git a/src/server/lib/services/FriendService.ts b/apps/api/src/lib/services/FriendService.ts similarity index 98% rename from src/server/lib/services/FriendService.ts rename to apps/api/src/lib/services/FriendService.ts index ad1a8ea3..13fda4a7 100644 --- a/src/server/lib/services/FriendService.ts +++ b/apps/api/src/lib/services/FriendService.ts @@ -8,8 +8,8 @@ import type { FriendCultivatorSummary, FriendSearchResult, FriendTargetResponse, -} from '@shared/contracts/friends'; -import { MAX_FRIENDS_PER_CULTIVATOR } from '@shared/config/socialConfig'; +} from '@daoyou/shared/contracts/friends'; +import { MAX_FRIENDS_PER_CULTIVATOR } from '@daoyou/shared/config/socialConfig'; import { and, desc, eq, inArray, ne, or, sql } from 'drizzle-orm'; export class FriendServiceError extends Error { diff --git a/src/server/lib/services/IdentityReshapeService.ts b/apps/api/src/lib/services/IdentityReshapeService.ts similarity index 99% rename from src/server/lib/services/IdentityReshapeService.ts rename to apps/api/src/lib/services/IdentityReshapeService.ts index 17d61766..d077c273 100644 --- a/src/server/lib/services/IdentityReshapeService.ts +++ b/apps/api/src/lib/services/IdentityReshapeService.ts @@ -21,14 +21,14 @@ import { IDENTITY_RESHAPE_TALISMAN_NAME, selectIdentityReshapeQuestions, validateIdentityReshapeAnswers, -} from '@shared/config/identityReshape'; +} from '@daoyou/shared/config/identityReshape'; import { IdentityReshapeCandidateSchema, type IdentityReshapeAnswer, type IdentityReshapeNameCheck, type IdentityReshapeSessionDTO, type IdentityReshapeSessionStore, -} from '@shared/types/identityReshape'; +} from '@daoyou/shared/types/identityReshape'; import { and, eq, sql } from 'drizzle-orm'; import { findBagTalisman } from './BagConsumables'; import { playerCommandExecutor } from './CommandExecutors'; diff --git a/src/server/lib/services/InnRecoveryService.ts b/apps/api/src/lib/services/InnRecoveryService.ts similarity index 87% rename from src/server/lib/services/InnRecoveryService.ts rename to apps/api/src/lib/services/InnRecoveryService.ts index 07709195..b663aeff 100644 --- a/src/server/lib/services/InnRecoveryService.ts +++ b/apps/api/src/lib/services/InnRecoveryService.ts @@ -2,16 +2,16 @@ import { calculateInnRecoverySpiritStoneCost, calculateInnRecoveryLossAmount, rollInnRecoveryLossPercent, -} from '@shared/config/innRecovery'; +} from '@daoyou/shared/config/innRecovery'; import { createDefaultCultivationProgress, syncBottleneckState, -} from '@server/utils/cultivationUtils'; -import { evaluateFateContext, getInnSpiritStoneMultiplier } from '@shared/lib/fates'; +} from '@daoyou/shared/engine/cultivation/cultivationUtils'; +import { evaluateFateContext, getInnSpiritStoneMultiplier } from '@daoyou/shared/lib/fates'; import { ConditionService } from './ConditionService'; -import type { CultivatorCondition } from '@shared/types/condition'; -import type { CultivationProgress, Cultivator } from '@shared/types/cultivator'; -import type { CultivatorDisplayInput } from '@shared/lib/cultivatorDisplay'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; +import type { CultivationProgress, Cultivator } from '@daoyou/shared/types/cultivator'; +import type { CultivatorDisplayInput } from '@daoyou/shared/lib/cultivatorDisplay'; export type InnRecoveryFacts = CultivatorDisplayInput & Pick< diff --git a/src/server/lib/services/InscriptionService.ts b/apps/api/src/lib/services/InscriptionService.ts similarity index 94% rename from src/server/lib/services/InscriptionService.ts rename to apps/api/src/lib/services/InscriptionService.ts index 6b6c4bee..caa331b8 100644 --- a/src/server/lib/services/InscriptionService.ts +++ b/apps/api/src/lib/services/InscriptionService.ts @@ -2,27 +2,27 @@ import type { InscriptionRequest, InscriptionResult, InscriptionView, -} from '@shared/contracts/inscriptions'; -import type { ResourceChangeDescriptor } from '@shared/contracts/resources'; +} from '@daoyou/shared/contracts/inscriptions'; +import type { ResourceChangeDescriptor } from '@daoyou/shared/contracts/resources'; import { prepareInscriptionDraw, prepareInscriptionEquipment, prepareInscriptionStrengthen, rollInscriptionDraw, type InscriptionCost, -} from '@shared/inscriptions/rules'; +} from '@daoyou/shared/inscriptions/rules'; import { addItems, type InventoryItem, type ItemGrant, -} from '@shared/inventory'; -import { inventoryStackIdentity } from '@shared/inventory/stack-key'; -import { projectNaturalQiState } from '@shared/lib/qi'; +} from '@daoyou/shared/inventory'; +import { inventoryStackIdentity } from '@daoyou/shared/inventory/stack-key'; +import { projectNaturalQiState } from '@daoyou/shared/lib/qi'; import { and, eq, gte, inArray, or, sql } from 'drizzle-orm'; import { randomInt, randomUUID } from 'node:crypto'; import { db, type DbExecutor } from '../drizzle/db'; import { cultivators, inventoryItems } from '../drizzle/schema'; -import type { ActiveCultivatorRef } from '../hono/types'; +import type { ActiveCultivatorRef } from '../auth/types'; import { playerCommandExecutor } from './CommandExecutors'; import { assertInventoryIdle, diff --git a/src/server/lib/services/InventoryApplicationService.ts b/apps/api/src/lib/services/InventoryApplicationService.ts similarity index 94% rename from src/server/lib/services/InventoryApplicationService.ts rename to apps/api/src/lib/services/InventoryApplicationService.ts index 1f0d0b6f..99eb0574 100644 --- a/src/server/lib/services/InventoryApplicationService.ts +++ b/apps/api/src/lib/services/InventoryApplicationService.ts @@ -1,5 +1,5 @@ import { consumables, materials } from '@server/lib/drizzle/schema'; -import type { ResourceChangeDescriptor } from '@shared/contracts/resources'; +import type { ResourceChangeDescriptor } from '@daoyou/shared/contracts/resources'; import { and, eq } from 'drizzle-orm'; import { playerCommandExecutor } from './CommandExecutors'; import { MarketServiceError } from './MarketService'; diff --git a/src/server/lib/services/InventoryService.ts b/apps/api/src/lib/services/InventoryService.ts similarity index 96% rename from src/server/lib/services/InventoryService.ts rename to apps/api/src/lib/services/InventoryService.ts index c63dd5f4..adff9112 100644 --- a/src/server/lib/services/InventoryService.ts +++ b/apps/api/src/lib/services/InventoryService.ts @@ -3,15 +3,15 @@ import type { InventoryAction, InventoryQuerySchema, InventoryView, -} from '@shared/contracts/inventory'; -import { previewBeastFeeding } from '@shared/engine/combat-v6/beasts/feeding'; -import { refineBeast } from '@shared/engine/combat-v6/beasts/refinement'; -import { BEAST_REFINEMENT } from '@shared/engine/combat-v6/beasts/refinement-config'; -import { rejuvenateBeast } from '@shared/engine/combat-v6/beasts/rejuvenation'; +} from '@daoyou/shared/contracts/inventory'; +import { previewBeastFeeding } from '@daoyou/shared/engine/combat-v6/beasts/feeding'; +import { refineBeast } from '@daoyou/shared/engine/combat-v6/beasts/refinement'; +import { BEAST_REFINEMENT } from '@daoyou/shared/engine/combat-v6/beasts/refinement-config'; +import { rejuvenateBeast } from '@daoyou/shared/engine/combat-v6/beasts/rejuvenation'; import { compileDaoEquipmentSpecialLoadoutV1, type DaoEquipmentInstanceV1, -} from '@shared/engine/combat-v6/equipment'; +} from '@daoyou/shared/engine/combat-v6/equipment'; import { addItems, BAG_CAPACITY, @@ -23,15 +23,15 @@ import { sortBag, type InventoryItem, type ItemGrant, -} from '@shared/inventory'; -import { changeEquipmentLocation } from '@shared/inventory/equipment-location'; -import { INVENTORY_KINDS } from '@shared/inventory/sorting'; -import { BEAST_REJUVENATION } from '@shared/items/definitions/beast-rejuvenation'; -import { ConsumableFactsSchema } from '@shared/items/definitions/consumables'; -import { MaterialFactsSchema } from '@shared/items/definitions/materials'; -import { SeedFactsSchema } from '@shared/items/definitions/seeds'; -import { ITEM_DEFINITIONS } from '@shared/items/registry'; -import { QUALITY_VALUES } from '@shared/types/constants'; +} from '@daoyou/shared/inventory'; +import { changeEquipmentLocation } from '@daoyou/shared/inventory/equipment-location'; +import { INVENTORY_KINDS } from '@daoyou/shared/inventory/sorting'; +import { BEAST_REJUVENATION } from '@daoyou/shared/items/definitions/beast-rejuvenation'; +import { ConsumableFactsSchema } from '@daoyou/shared/items/definitions/consumables'; +import { MaterialFactsSchema } from '@daoyou/shared/items/definitions/materials'; +import { SeedFactsSchema } from '@daoyou/shared/items/definitions/seeds'; +import { ITEM_DEFINITIONS } from '@daoyou/shared/items/registry'; +import { QUALITY_VALUES } from '@daoyou/shared/types/constants'; import { and, asc, diff --git a/src/server/lib/services/JournalSettlement.ts b/apps/api/src/lib/services/JournalSettlement.ts similarity index 94% rename from src/server/lib/services/JournalSettlement.ts rename to apps/api/src/lib/services/JournalSettlement.ts index ee50ad86..4bd0f1bf 100644 --- a/src/server/lib/services/JournalSettlement.ts +++ b/apps/api/src/lib/services/JournalSettlement.ts @@ -10,13 +10,13 @@ import { type JournalActivity, type JournalChange, type PlayerJournalEvent, -} from '@shared/contracts/playerJournal'; -import type { InventoryItem } from '@shared/inventory'; +} from '@daoyou/shared/contracts/playerJournal'; +import type { InventoryItem } from '@daoyou/shared/inventory'; import { compactJournalChanges, inventoryJournalChanges, -} from '@shared/playerJournal'; -import type { CultivationProgress } from '@shared/types/cultivator'; +} from '@daoyou/shared/playerJournal'; +import type { CultivationProgress } from '@daoyou/shared/types/cultivator'; import { and, eq } from 'drizzle-orm'; // Only explicitly enrolled settlements collect changes. The transaction owns the diff --git a/src/server/lib/services/MailBeastDelivery.ts b/apps/api/src/lib/services/MailBeastDelivery.ts similarity index 92% rename from src/server/lib/services/MailBeastDelivery.ts rename to apps/api/src/lib/services/MailBeastDelivery.ts index 8e51e27e..b8431f0f 100644 --- a/src/server/lib/services/MailBeastDelivery.ts +++ b/apps/api/src/lib/services/MailBeastDelivery.ts @@ -5,9 +5,9 @@ import { BeastTransferSchema, planBeastMailClaims, receiveTradedBeast, -} from '@shared/contracts/beastTrade'; -import { BEAST_CAPACITY } from '@shared/engine/combat-v6/beasts/progression'; -import type { MailAttachment } from '@shared/types/mail'; +} from '@daoyou/shared/contracts/beastTrade'; +import { BEAST_CAPACITY } from '@daoyou/shared/engine/combat-v6/beasts/progression'; +import type { MailAttachment } from '@daoyou/shared/types/mail'; import { count, eq } from 'drizzle-orm'; import { assertBeastIdle, diff --git a/src/server/lib/services/MailDomainEventProjector.ts b/apps/api/src/lib/services/MailDomainEventProjector.ts similarity index 80% rename from src/server/lib/services/MailDomainEventProjector.ts rename to apps/api/src/lib/services/MailDomainEventProjector.ts index 16d67b22..ce862eb4 100644 --- a/src/server/lib/services/MailDomainEventProjector.ts +++ b/apps/api/src/lib/services/MailDomainEventProjector.ts @@ -1,6 +1,6 @@ import type { DbTransaction } from '@server/lib/drizzle/db'; -import type { DomainEventEnvelope } from '@shared/contracts/domainEvents'; -import type { ResourceChangeDescriptor } from '@shared/contracts/resources'; +import type { DomainEventEnvelope } from '@daoyou/shared/contracts/domainEvents'; +import type { ResourceChangeDescriptor } from '@daoyou/shared/contracts/resources'; import { readPlayerMailSummary } from './PlayerResourceReaderService'; export async function projectMailCreated( diff --git a/src/server/lib/services/MailInventory.ts b/apps/api/src/lib/services/MailInventory.ts similarity index 86% rename from src/server/lib/services/MailInventory.ts rename to apps/api/src/lib/services/MailInventory.ts index 73e3f258..811a26ae 100644 --- a/src/server/lib/services/MailInventory.ts +++ b/apps/api/src/lib/services/MailInventory.ts @@ -1,17 +1,17 @@ import { beastTradePreview, BeastTransferSchema, -} from '@shared/contracts/beastTrade'; -import { MailInventoryGrantSchema } from '@shared/contracts/mail'; -import { consumableFactsOf } from '@shared/items/definitions/consumables'; +} from '@daoyou/shared/contracts/beastTrade'; +import { MailInventoryGrantSchema } from '@daoyou/shared/contracts/mail'; +import { consumableFactsOf } from '@daoyou/shared/items/definitions/consumables'; import { INVENTORY_MATERIAL_TYPES, MaterialFactsSchema, -} from '@shared/items/definitions/materials'; -import { seedFactsOf, SeedFactsSchema } from '@shared/items/definitions/seeds'; -import { assertCurrentRewardItem } from '@shared/lib/retiredDraw'; -import type { Consumable, Material } from '@shared/types/cultivator'; -import type { MailAttachment } from '@shared/types/mail'; +} from '@daoyou/shared/items/definitions/materials'; +import { seedFactsOf, SeedFactsSchema } from '@daoyou/shared/items/definitions/seeds'; +import { assertCurrentRewardItem } from '@daoyou/shared/lib/retiredDraw'; +import type { Consumable, Material } from '@daoyou/shared/types/cultivator'; +import type { MailAttachment } from '@daoyou/shared/types/mail'; import type { DbTransaction } from '../drizzle/db'; import { grantInventory } from './InventoryService'; import { sanitizeMaterialForClient } from './materialDetailsPrivacy'; diff --git a/src/server/lib/services/MailService.ts b/apps/api/src/lib/services/MailService.ts similarity index 96% rename from src/server/lib/services/MailService.ts rename to apps/api/src/lib/services/MailService.ts index aedd2e03..ec9d1758 100644 --- a/src/server/lib/services/MailService.ts +++ b/apps/api/src/lib/services/MailService.ts @@ -2,11 +2,11 @@ import { db, getExecutor, type DbTransaction } from '@server/lib/drizzle/db'; import { mails } from '@server/lib/drizzle/schema'; import { createDomainEvent } from '@server/lib/mq/domainEventWriter'; import { publishTransactionalMessageBestEffort } from '@server/lib/mq/transactionalMessagePublisher'; -import type { MailAttachment } from '@shared/types/mail'; +import type { MailAttachment } from '@daoyou/shared/types/mail'; import { eq } from 'drizzle-orm'; import { newRewardAttachment } from './MailInventory'; -export type { MailAttachment, MailAttachmentType } from '@shared/types/mail'; +export type { MailAttachment, MailAttachmentType } from '@daoyou/shared/types/mail'; export class MailService { static async sendNewRewardMail( diff --git a/src/server/lib/services/MarketApplicationService.ts b/apps/api/src/lib/services/MarketApplicationService.ts similarity index 94% rename from src/server/lib/services/MarketApplicationService.ts rename to apps/api/src/lib/services/MarketApplicationService.ts index faa4734d..fc64ae47 100644 --- a/src/server/lib/services/MarketApplicationService.ts +++ b/apps/api/src/lib/services/MarketApplicationService.ts @@ -3,9 +3,9 @@ import { cultivators } from '@server/lib/drizzle/schema'; import { redisLockKeys, withRedisLock } from '@server/lib/redis/lock'; import { findPlayerMutationRequest } from '@server/lib/repositories/playerStateRepository'; import { getPlayerPreHeavenFates } from '@server/lib/services/cultivator/CultivatorProfileRepository'; -import type { MarketBuyInput } from '@shared/contracts/market'; -import type { ResourceChangeDescriptor } from '@shared/contracts/resources'; -import type { PreHeavenFate } from '@shared/types/cultivator'; +import type { MarketBuyInput } from '@daoyou/shared/contracts/market'; +import type { ResourceChangeDescriptor } from '@daoyou/shared/contracts/resources'; +import type { PreHeavenFate } from '@daoyou/shared/types/cultivator'; import { eq } from 'drizzle-orm'; import { playerCommandExecutor } from './CommandExecutors'; import { readCultivatorRealm } from './cultivator/CultivatorFactsReader'; diff --git a/src/server/lib/services/MarketInventoryDelivery.ts b/apps/api/src/lib/services/MarketInventoryDelivery.ts similarity index 82% rename from src/server/lib/services/MarketInventoryDelivery.ts rename to apps/api/src/lib/services/MarketInventoryDelivery.ts index e725af42..11eb5200 100644 --- a/src/server/lib/services/MarketInventoryDelivery.ts +++ b/apps/api/src/lib/services/MarketInventoryDelivery.ts @@ -1,7 +1,7 @@ import type { DbTransaction } from '@server/lib/drizzle/db'; -import { MaterialFactsSchema } from '@shared/items/definitions/materials'; -import { seedFactsOf } from '@shared/items/definitions/seeds'; -import type { Material } from '@shared/types/cultivator'; +import { MaterialFactsSchema } from '@daoyou/shared/items/definitions/materials'; +import { seedFactsOf } from '@daoyou/shared/items/definitions/seeds'; +import type { Material } from '@daoyou/shared/types/cultivator'; import { grantInventory } from './InventoryService'; /** Market materials enter the same inventory as crafting output. Seeds preserve their growing facts. */ diff --git a/src/server/lib/services/MarketRecycleService.ts b/apps/api/src/lib/services/MarketRecycleService.ts similarity index 94% rename from src/server/lib/services/MarketRecycleService.ts rename to apps/api/src/lib/services/MarketRecycleService.ts index c2e826e9..aa79d58f 100644 --- a/src/server/lib/services/MarketRecycleService.ts +++ b/apps/api/src/lib/services/MarketRecycleService.ts @@ -8,16 +8,16 @@ import { LOW_TIER_ANCHOR_FACTOR, PRODUCE_PRICE_FACTOR_MIN, RECYCLE_PRICE_FACTOR_CAP, -} from '@shared/config/marketConfig'; +} from '@daoyou/shared/config/marketConfig'; import { BASE_PRICES, TYPE_MULTIPLIERS, -} from '@shared/engine/material/creation/config'; -import { getMaterialTypeLabel } from '@shared/lib/gameConceptDisplay'; -import { calculatePillRecycleUnitPrice as calculatePillRecyclePrice } from '@shared/lib/pillRecyclePrice'; -import { QUALITY_ORDER, type Quality } from '@shared/types/constants'; -import type { Consumable, Material } from '@shared/types/cultivator'; -import type { HighTierAppraisal } from '@shared/types/market'; +} from '@daoyou/shared/engine/material/creation/config'; +import { getMaterialTypeLabel } from '@daoyou/shared/lib/gameConceptDisplay'; +import { calculatePillRecycleUnitPrice as calculatePillRecyclePrice } from '@daoyou/shared/lib/pillRecyclePrice'; +import { QUALITY_ORDER, type Quality } from '@daoyou/shared/types/constants'; +import type { Consumable, Material } from '@daoyou/shared/types/cultivator'; +import type { HighTierAppraisal } from '@daoyou/shared/types/market'; const APPRAISAL_RATING_STEPS: HighTierAppraisal['rating'][] = [ 'C', diff --git a/src/server/lib/services/MarketScheduler.ts b/apps/api/src/lib/services/MarketScheduler.ts similarity index 88% rename from src/server/lib/services/MarketScheduler.ts rename to apps/api/src/lib/services/MarketScheduler.ts index 04251617..86f535b3 100644 --- a/src/server/lib/services/MarketScheduler.ts +++ b/apps/api/src/lib/services/MarketScheduler.ts @@ -2,8 +2,8 @@ import { preGenerateMarket } from '@server/lib/services/MarketService'; import { getEnabledMarketNodeIds, getMarketConfigByNodeId, -} from '@shared/lib/game/marketConfig'; -import type { MarketLayer } from '@shared/types/market'; +} from '@daoyou/shared/lib/game/marketConfig'; +import type { MarketLayer } from '@daoyou/shared/types/market'; const MARKET_LAYERS: MarketLayer[] = ['common', 'treasure', 'heaven']; @@ -13,7 +13,7 @@ const MARKET_LAYERS: MarketLayer[] = ['common', 'treasure', 'heaven']; * 遍历所有已启用的坊市节点及普通坊市层级, * 检查当前周期的缓存是否存在,不存在则生成。 * - * 由 internalCronScheduler 每 5 分钟调度一次。 + * 由 Nest CronService 每 5 分钟发布调度命令。 */ export async function runMarketRefreshJob(): Promise<{ success: true; diff --git a/src/server/lib/services/MarketService.ts b/apps/api/src/lib/services/MarketService.ts similarity index 96% rename from src/server/lib/services/MarketService.ts rename to apps/api/src/lib/services/MarketService.ts index 1e89e32f..c7878690 100644 --- a/src/server/lib/services/MarketService.ts +++ b/apps/api/src/lib/services/MarketService.ts @@ -1,3 +1,4 @@ +import { SpiritSeedGenerator } from '@server/lib/generation/SpiritSeedGenerator'; import { getExecutor, type DbTransaction } from '@server/lib/drizzle/db'; import { cultivators, @@ -10,27 +11,26 @@ import { redisLockKeys, withRedisLock, } from '@server/lib/redis/lock'; -import type { MarketPurchaseResult } from '@shared/contracts/market'; +import type { MarketPurchaseResult } from '@daoyou/shared/contracts/market'; import { BASE_PRICES, QUALITY_CHANCE_MAP, TYPE_CHANCE_MAP, TYPE_MULTIPLIERS, -} from '@shared/engine/material/creation/config'; -import { MARKET_PRESET_POOL } from '@shared/engine/material/creation/marketPresets'; +} from '@daoyou/shared/engine/material/creation/config'; +import { MARKET_PRESET_POOL } from '@daoyou/shared/engine/material/creation/marketPresets'; import { getSpiritFieldMarketSeedSlotCount, - SpiritSeedGenerator, -} from '@shared/engine/spirit-field'; -import { readSpiritFieldSeedSpec } from '@shared/engine/spirit-field/seedMaterial'; -import { MaterialFactsSchema } from '@shared/items/definitions/materials'; -import { findItemDefinition } from '@shared/items/registry'; +} from '@daoyou/shared/engine/spirit-field'; +import { readSpiritFieldSeedSpec } from '@daoyou/shared/engine/spirit-field/seedMaterial'; +import { MaterialFactsSchema } from '@daoyou/shared/items/definitions/materials'; +import { findItemDefinition } from '@daoyou/shared/items/registry'; import { evaluateFateContext, getMarketPurchasePriceMultiplier, scaleFateAdjustedCost, -} from '@shared/lib/fates'; -import { sampleBeastMarketStock } from '@shared/lib/game/beastMarket'; +} from '@daoyou/shared/lib/fates'; +import { sampleBeastMarketStock } from '@daoyou/shared/lib/game/beastMarket'; import { BLACK_MARKET_HIGH_TIER_MIN, getCurrentCycle, @@ -45,10 +45,10 @@ import { MARKET_STALE_RETRY_MS, resolveLayerConfig, validateLayerAccess, -} from '@shared/lib/game/marketConfig'; -import type { MaterialType, Quality, RealmType } from '@shared/types/constants'; -import { QUALITY_ORDER, QUALITY_VALUES } from '@shared/types/constants'; -import type { PreHeavenFate } from '@shared/types/cultivator'; +} from '@daoyou/shared/lib/game/marketConfig'; +import type { MaterialType, Quality, RealmType } from '@daoyou/shared/types/constants'; +import { QUALITY_ORDER, QUALITY_VALUES } from '@daoyou/shared/types/constants'; +import type { PreHeavenFate } from '@daoyou/shared/types/cultivator'; import type { MarketAccessState, MarketItemListing, @@ -58,8 +58,8 @@ import type { MysteryRevealContext, RegionProfile, ResolvedLayerConfig, -} from '@shared/types/market'; -import { MARKET_PRESET_FALLBACK_LAYERS } from '@shared/types/market'; +} from '@daoyou/shared/types/market'; +import { MARKET_PRESET_FALLBACK_LAYERS } from '@daoyou/shared/types/market'; import { and, eq, inArray, sql } from 'drizzle-orm'; import { createHash } from 'node:crypto'; import { grantInventory } from './InventoryService'; diff --git a/src/server/lib/services/MaterialLibraryService.ts b/apps/api/src/lib/services/MaterialLibraryService.ts similarity index 97% rename from src/server/lib/services/MaterialLibraryService.ts rename to apps/api/src/lib/services/MaterialLibraryService.ts index 3158c7d3..763ef870 100644 --- a/src/server/lib/services/MaterialLibraryService.ts +++ b/apps/api/src/lib/services/MaterialLibraryService.ts @@ -1,25 +1,25 @@ +import { SpiritSeedGenerator } from '@server/lib/generation/SpiritSeedGenerator'; import { getExecutor, type DbExecutor, type DbTransaction, } from '@server/lib/drizzle/db'; import { itemLibrary } from '@server/lib/drizzle/schema'; -import { MaterialGenerator } from '@shared/engine/material/creation/MaterialGenerator'; -import type { MaterialSkeleton } from '@shared/engine/material/creation/types'; -import { SpiritSeedGenerator } from '@shared/engine/spirit-field'; +import { MaterialGenerator } from '@server/lib/generation/MaterialGenerator'; +import type { MaterialSkeleton } from '@daoyou/shared/engine/material/creation/types'; import { ItemLibraryEntrySchema, type CreateItemLibraryEntry, type ItemLibraryEntry, type ItemLibraryMaterialGenerateInput, type ItemLibrarySpiritSeedGenerateInput, -} from '@shared/lib/itemLibrary'; +} from '@daoyou/shared/lib/itemLibrary'; import { QUALITY_ORDER, type MaterialType, type Quality, -} from '@shared/types/constants'; -import type { Material } from '@shared/types/cultivator'; +} from '@daoyou/shared/types/constants'; +import type { Material } from '@daoyou/shared/types/cultivator'; import { and, asc, eq, gte, inArray, sql } from 'drizzle-orm'; import { computeItemLibrarySampleKey } from './itemLibrarySampleKey'; diff --git a/src/server/lib/services/MaterialRewardService.ts b/apps/api/src/lib/services/MaterialRewardService.ts similarity index 91% rename from src/server/lib/services/MaterialRewardService.ts rename to apps/api/src/lib/services/MaterialRewardService.ts index 0699e1b8..fe85946d 100644 --- a/src/server/lib/services/MaterialRewardService.ts +++ b/apps/api/src/lib/services/MaterialRewardService.ts @@ -1,6 +1,6 @@ import type { DbExecutor } from '@server/lib/drizzle/db'; -import { MaterialGenerator } from '@shared/engine/material/creation/MaterialGenerator'; -import { YieldCalculator } from '@shared/engine/yield/YieldCalculator'; +import { MaterialGenerator } from '@server/lib/generation/MaterialGenerator'; +import { YieldCalculator } from '@daoyou/shared/engine/yield/YieldCalculator'; import { MATERIAL_TYPE_VALUES, QUALITY_ORDER, @@ -8,8 +8,8 @@ import { type MaterialType, type Quality, type RealmType, -} from '@shared/types/constants'; -import type { Material } from '@shared/types/cultivator'; +} from '@daoyou/shared/types/constants'; +import type { Material } from '@daoyou/shared/types/cultivator'; import { materialLibraryEntryToMaterial, sampleMaterialLibraryEntryByPreferences, diff --git a/src/server/lib/services/PersistentStateService.ts b/apps/api/src/lib/services/PersistentStateService.ts similarity index 100% rename from src/server/lib/services/PersistentStateService.ts rename to apps/api/src/lib/services/PersistentStateService.ts diff --git a/src/server/lib/services/PillOperationExecutor.ts b/apps/api/src/lib/services/PillOperationExecutor.ts similarity index 96% rename from src/server/lib/services/PillOperationExecutor.ts rename to apps/api/src/lib/services/PillOperationExecutor.ts index b9356e55..24d1b248 100644 --- a/src/server/lib/services/PillOperationExecutor.ts +++ b/apps/api/src/lib/services/PillOperationExecutor.ts @@ -1,52 +1,52 @@ -import { getOrInitCultivationProgress } from '@server/utils/cultivationUtils'; -import { COMPREHENSION_INSIGHT_CAP } from '@shared/config/cultivationTuning'; +import { getOrInitCultivationProgress } from '@daoyou/shared/engine/cultivation/cultivationUtils'; +import { COMPREHENSION_INSIGHT_CAP } from '@daoyou/shared/config/cultivationTuning'; import { CULTIVATION_PILL_MAX_QUALITY_BY_REALM, getMinimumPillQualityByRealm, PILL_TOXICITY_CAP, REALM_PILL_USAGE_LIMITS, -} from '@shared/config/consumableSystem'; -import type { CultivatorDisplayInput } from '@shared/lib/cultivatorDisplay'; +} from '@daoyou/shared/config/consumableSystem'; +import type { CultivatorDisplayInput } from '@daoyou/shared/lib/cultivatorDisplay'; import { BODY_CULTIVATION_REALM_REQUIREMENTS, BODY_REALM_LABELS, getBodyTrackKeyFromPath, isBodyCultivationTrackPath, isLegacyTemperingTrackPath, -} from '@shared/lib/bodyCultivation/config'; +} from '@daoyou/shared/lib/bodyCultivation/config'; import { createDefaultBodyCultivationState, normalizeBodyCultivationState, -} from '@shared/lib/bodyCultivation/normalize'; -import { getConditionStatusCureTargets } from '@shared/lib/condition'; -import { getConditionStatusTemplate } from '@shared/lib/conditionStatusRegistry'; +} from '@daoyou/shared/lib/bodyCultivation/normalize'; +import { getConditionStatusCureTargets } from '@daoyou/shared/lib/condition'; +import { getConditionStatusTemplate } from '@daoyou/shared/lib/conditionStatusRegistry'; import { isPillConsumable, isSpiritFruitConsumable, -} from '@shared/lib/consumables'; +} from '@daoyou/shared/lib/consumables'; import { CULTIVATION_BOOST_STATUS_KEY, getCultivationBoostPercent, -} from '@shared/lib/cultivationBoost'; +} from '@daoyou/shared/lib/cultivationBoost'; import { advanceMarrowWashTowardBreakthrough, getMarrowWashLevelCapByCultivationRealm, getNextMarrowWashBreakthroughLevel, isMarrowWashBreakthroughRequired, normalizeMarrowWashState, -} from '@shared/lib/marrowWash'; +} from '@daoyou/shared/lib/marrowWash'; import { BREAKTHROUGH_FOCUS_STATUS_KEY, CLEAR_MIND_STATUS_KEY, getBreakthroughFocusBonus, getProtectMeridiansReductionPercent, PROTECT_MERIDIANS_STATUS_KEY, -} from '@shared/lib/pillEffectScaling'; +} from '@daoyou/shared/lib/pillEffectScaling'; import { getPillUsageLimitReachedText, getPrimaryPillQuotaCategory, -} from '@shared/lib/pillUsageText'; -import { getTrackConfig } from '@shared/lib/trackConfigRegistry'; +} from '@daoyou/shared/lib/pillUsageText'; +import { getTrackConfig } from '@daoyou/shared/lib/trackConfigRegistry'; import type { BodyCultivationTrackPath, ConditionStatusDuration, @@ -54,14 +54,14 @@ import type { ConditionStatusKey, ConditionTrackPath, CultivatorCondition, -} from '@shared/types/condition'; -import { QUALITY_ORDER } from '@shared/types/constants'; -import type { ConditionOperation, PillSpec } from '@shared/types/consumable'; +} from '@daoyou/shared/types/condition'; +import { QUALITY_ORDER } from '@daoyou/shared/types/constants'; +import type { ConditionOperation, PillSpec } from '@daoyou/shared/types/consumable'; import type { Consumable, CultivationProgress, Cultivator, -} from '@shared/types/cultivator'; +} from '@daoyou/shared/types/cultivator'; import { ConditionService } from './ConditionService'; const EXECUTION_ORDER: ConditionOperation['type'][] = [ diff --git a/src/server/lib/services/PlayerMailApplicationService.ts b/apps/api/src/lib/services/PlayerMailApplicationService.ts similarity index 98% rename from src/server/lib/services/PlayerMailApplicationService.ts rename to apps/api/src/lib/services/PlayerMailApplicationService.ts index f670c133..cea21146 100644 --- a/src/server/lib/services/PlayerMailApplicationService.ts +++ b/apps/api/src/lib/services/PlayerMailApplicationService.ts @@ -4,9 +4,9 @@ import { getExecutor } from '@server/lib/drizzle/db'; import { mails } from '@server/lib/drizzle/schema'; import { redisLockKeys, withRedisLock } from '@server/lib/redis/lock'; import { resourceEngine } from '@server/lib/services/resource/ResourceEngine'; -import type { ResourceChangeDescriptor } from '@shared/contracts/resources'; -import type { ResourceOperationSettlement } from '@shared/engine/resource/types'; -import { attachmentsToResourceOperations } from '@shared/lib/itemLibrary'; +import type { ResourceChangeDescriptor } from '@daoyou/shared/contracts/resources'; +import type { ResourceOperationSettlement } from '@daoyou/shared/engine/resource/types'; +import { attachmentsToResourceOperations } from '@daoyou/shared/lib/itemLibrary'; import { and, eq, inArray } from 'drizzle-orm'; import { playerCommandExecutor } from './CommandExecutors'; import { diff --git a/src/server/lib/services/PlayerMailService.ts b/apps/api/src/lib/services/PlayerMailService.ts similarity index 95% rename from src/server/lib/services/PlayerMailService.ts rename to apps/api/src/lib/services/PlayerMailService.ts index c692a96b..43987af2 100644 --- a/src/server/lib/services/PlayerMailService.ts +++ b/apps/api/src/lib/services/PlayerMailService.ts @@ -1,12 +1,12 @@ import { getExecutor, type DbTransaction } from '@server/lib/drizzle/db'; import * as schema from '@server/lib/drizzle/schema'; -import { FRIEND_MAIL_TALISMAN_SCENARIO } from '@shared/config/socialConfig'; +import { FRIEND_MAIL_TALISMAN_SCENARIO } from '@daoyou/shared/config/socialConfig'; import { mailGiftBlockReason, type SendMailRequest, -} from '@shared/contracts/mail'; -import { itemDefinition, ItemGrantSchema } from '@shared/inventory'; -import type { MailAttachment } from '@shared/types/mail'; +} from '@daoyou/shared/contracts/mail'; +import { itemDefinition, ItemGrantSchema } from '@daoyou/shared/inventory'; +import type { MailAttachment } from '@daoyou/shared/types/mail'; import { and, eq } from 'drizzle-orm'; import { assertFriend, FriendServiceError } from './FriendService'; import { diff --git a/src/server/lib/services/PlayerResourceReaderService.ts b/apps/api/src/lib/services/PlayerResourceReaderService.ts similarity index 96% rename from src/server/lib/services/PlayerResourceReaderService.ts rename to apps/api/src/lib/services/PlayerResourceReaderService.ts index ff5852cb..4f7b3cac 100644 --- a/src/server/lib/services/PlayerResourceReaderService.ts +++ b/apps/api/src/lib/services/PlayerResourceReaderService.ts @@ -18,16 +18,16 @@ import { import { getPlayerIdentityCultivatorById } from '@server/lib/services/cultivator/CultivatorProfileRepository'; import { QiService } from '@server/lib/services/QiService'; import { getSectCombatView } from '@server/lib/services/combat-v6/CombatV6BuildService'; -import { getOrInitCultivationProgress } from '@server/utils/cultivationUtils'; +import { getOrInitCultivationProgress } from '@daoyou/shared/engine/cultivation/cultivationUtils'; import { PLAYER_RESOURCE_KEYS, type PlayerResourceKey, type PlayerResourceMap, type PlayerResourcesData, -} from '@shared/contracts/player'; -import type { ResourceTopic } from '@shared/contracts/resources'; -import { RESOURCE_DATA_SCHEMAS } from '@shared/contracts/resources'; -import type { RealmStage, RealmType } from '@shared/types/constants'; +} from '@daoyou/shared/contracts/player'; +import type { ResourceTopic } from '@daoyou/shared/contracts/resources'; +import { RESOURCE_DATA_SCHEMAS } from '@daoyou/shared/contracts/resources'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; import { and, desc, eq, sql } from 'drizzle-orm'; export function readPlayerResourcesSnapshot(args: { diff --git a/src/server/lib/services/QiResourceChanges.ts b/apps/api/src/lib/services/QiResourceChanges.ts similarity index 93% rename from src/server/lib/services/QiResourceChanges.ts rename to apps/api/src/lib/services/QiResourceChanges.ts index aaebf574..8b974846 100644 --- a/src/server/lib/services/QiResourceChanges.ts +++ b/apps/api/src/lib/services/QiResourceChanges.ts @@ -1,7 +1,7 @@ import type { ResourceChangeDescriptor, ResourceDataMap, -} from '@shared/contracts/resources'; +} from '@daoyou/shared/contracts/resources'; export type QiSettlementBaseline = { qiAfter: number; diff --git a/src/server/lib/services/QiService.ts b/apps/api/src/lib/services/QiService.ts similarity index 97% rename from src/server/lib/services/QiService.ts rename to apps/api/src/lib/services/QiService.ts index 365b58b8..8f72fa42 100644 --- a/src/server/lib/services/QiService.ts +++ b/apps/api/src/lib/services/QiService.ts @@ -1,3 +1,4 @@ +import { getRuntimeEnvironment } from '@server/lib/config/environment'; import { QI_ACTION_COSTS, QI_DAILY_RESTORE_ITEM_LIMIT, @@ -5,16 +6,16 @@ import { QI_OVERFLOW_MAX, QI_REFRESH_TIMEZONE, type QiAction, -} from '@shared/config/qiSystem'; -import type { QiLogsResponse, QiState } from '@shared/contracts/qi'; -import { projectNaturalQiState } from '@shared/lib/qi'; +} from '@daoyou/shared/config/qiSystem'; +import type { QiLogsResponse, QiState } from '@daoyou/shared/contracts/qi'; +import { projectNaturalQiState } from '@daoyou/shared/lib/qi'; import type { QiLogMetadata, QiLogStatus, QiReservationResult, QiRestoreResult, QiRestoreSource, -} from '@shared/types/qi'; +} from '@daoyou/shared/types/qi'; import { and, desc, eq, sql } from 'drizzle-orm'; import { getExecutor, type DbTransaction } from '../drizzle/db'; import { cultivators, qiLogs } from '../drizzle/schema'; @@ -22,7 +23,7 @@ import { cultivators, qiLogs } from '../drizzle/schema'; const RESTORE_STATUS: QiLogStatus = 'restore_committed'; function isQiEnabled() { - return process.env.QI_SYSTEM_ENABLED !== 'false'; + return getRuntimeEnvironment().QI_SYSTEM_ENABLED !== 'false'; } function getDateInTimezone(date = new Date()): string { diff --git a/src/server/lib/services/RankingApplicationService.ts b/apps/api/src/lib/services/RankingApplicationService.ts similarity index 100% rename from src/server/lib/services/RankingApplicationService.ts rename to apps/api/src/lib/services/RankingApplicationService.ts diff --git a/src/server/lib/services/RealmChangedDomainEventProjector.ts b/apps/api/src/lib/services/RealmChangedDomainEventProjector.ts similarity index 88% rename from src/server/lib/services/RealmChangedDomainEventProjector.ts rename to apps/api/src/lib/services/RealmChangedDomainEventProjector.ts index fa640a1a..69d7a0e7 100644 --- a/src/server/lib/services/RealmChangedDomainEventProjector.ts +++ b/apps/api/src/lib/services/RealmChangedDomainEventProjector.ts @@ -1,5 +1,5 @@ import { removeFromAllRankingRealmsExcept } from '@server/lib/redis/rankings'; -import type { DomainEventEnvelope } from '@shared/contracts/domainEvents'; +import type { DomainEventEnvelope } from '@daoyou/shared/contracts/domainEvents'; import type { FeatureCommandResult } from './CommandExecutors'; export async function projectRealmChangedRanking( diff --git a/src/server/lib/services/RedeemCodeApplicationService.ts b/apps/api/src/lib/services/RedeemCodeApplicationService.ts similarity index 97% rename from src/server/lib/services/RedeemCodeApplicationService.ts rename to apps/api/src/lib/services/RedeemCodeApplicationService.ts index bf920090..82f3d4d8 100644 --- a/src/server/lib/services/RedeemCodeApplicationService.ts +++ b/apps/api/src/lib/services/RedeemCodeApplicationService.ts @@ -1,6 +1,6 @@ import { redeemCodeClaims, redeemCodes } from '@server/lib/drizzle/schema'; import { resolveRedeemCodeRewardAttachments } from '@server/lib/redeem/reward'; -import { materializeRewardAttachments } from '@shared/contracts/adminRewards'; +import { materializeRewardAttachments } from '@daoyou/shared/contracts/adminRewards'; import { and, eq, sql } from 'drizzle-orm'; import { randomUUID } from 'node:crypto'; import { playerCommandExecutor } from './CommandExecutors'; diff --git a/src/server/lib/services/ReputationShopApplicationService.ts b/apps/api/src/lib/services/ReputationShopApplicationService.ts similarity index 94% rename from src/server/lib/services/ReputationShopApplicationService.ts rename to apps/api/src/lib/services/ReputationShopApplicationService.ts index 4965c5ea..f7e0b808 100644 --- a/src/server/lib/services/ReputationShopApplicationService.ts +++ b/apps/api/src/lib/services/ReputationShopApplicationService.ts @@ -1,5 +1,5 @@ import type { DbTransaction } from '@server/lib/drizzle/db'; -import type { ResourceChangeDescriptor } from '@shared/contracts/resources'; +import type { ResourceChangeDescriptor } from '@daoyou/shared/contracts/resources'; import { playerCommandExecutor } from './CommandExecutors'; import { buyReputationShopItem } from './ReputationShopService'; diff --git a/src/server/lib/services/ReputationShopService.ts b/apps/api/src/lib/services/ReputationShopService.ts similarity index 98% rename from src/server/lib/services/ReputationShopService.ts rename to apps/api/src/lib/services/ReputationShopService.ts index aeda3e34..ed0aaf12 100644 --- a/src/server/lib/services/ReputationShopService.ts +++ b/apps/api/src/lib/services/ReputationShopService.ts @@ -13,14 +13,14 @@ import { RewardItemSchema, materializeRewardItem, rewardDisplayItem, -} from '@shared/contracts/adminRewards'; +} from '@daoyou/shared/contracts/adminRewards'; import { REPUTATION_SHOP_MAX_PRICE, type ReputationShopItemMutation, type ReputationShopItemStatus, type ReputationShopItemView, -} from '@shared/contracts/reputationShop'; -import { getItemExchangePurchaseWeek } from '@shared/lib/itemExchangeShop'; +} from '@daoyou/shared/contracts/reputationShop'; +import { getItemExchangePurchaseWeek } from '@daoyou/shared/lib/itemExchangeShop'; import { and, asc, desc, eq, sql, type SQL } from 'drizzle-orm'; import { randomUUID } from 'node:crypto'; import { grantInventory } from './InventoryService'; diff --git a/src/server/lib/services/ResourceEventCommitter.ts b/apps/api/src/lib/services/ResourceEventCommitter.ts similarity index 94% rename from src/server/lib/services/ResourceEventCommitter.ts rename to apps/api/src/lib/services/ResourceEventCommitter.ts index 75f8e149..e5e54f57 100644 --- a/src/server/lib/services/ResourceEventCommitter.ts +++ b/apps/api/src/lib/services/ResourceEventCommitter.ts @@ -5,16 +5,16 @@ import { type ScopedResourceChangeDescriptor, type ScopeVersionCommit, } from '@server/lib/repositories/playerStateRepository'; -import type { PlayerResourceMutationMeta } from '@shared/contracts/player'; +import type { PlayerResourceMutationMeta } from '@daoyou/shared/contracts/player'; import { refreshCombatV6CharacterResources } from './combat-v6/CombatV6CharacterResourceRefresh'; -import { withCharacterPanelInvalidations } from '@shared/lib/characterResourceChanges'; -import { withBagInvalidations } from '@shared/lib/bagResourceChanges'; +import { withCharacterPanelInvalidations } from '@daoyou/shared/lib/characterResourceChanges'; +import { withBagInvalidations } from '@daoyou/shared/lib/bagResourceChanges'; import { RESOURCE_TOPIC_SCOPE_KIND, type ResourceChange, type ResourceChangeDescriptor, type ResourceScope, -} from '@shared/contracts/resources'; +} from '@daoyou/shared/contracts/resources'; export type ResourceEventActor = { userId?: string | null; diff --git a/src/server/lib/services/ResourceMutationResponse.ts b/apps/api/src/lib/services/ResourceMutationResponse.ts similarity index 88% rename from src/server/lib/services/ResourceMutationResponse.ts rename to apps/api/src/lib/services/ResourceMutationResponse.ts index 64dcb6b3..471b6cfe 100644 --- a/src/server/lib/services/ResourceMutationResponse.ts +++ b/apps/api/src/lib/services/ResourceMutationResponse.ts @@ -1,7 +1,7 @@ import type { PlayerResourceMutationMeta, PlayerStateMutationResponse, -} from '@shared/contracts/player'; +} from '@daoyou/shared/contracts/player'; export function toPlayerStateMutationResponse(committed: { result: T; diff --git a/src/server/lib/services/ResourceReadService.ts b/apps/api/src/lib/services/ResourceReadService.ts similarity index 94% rename from src/server/lib/services/ResourceReadService.ts rename to apps/api/src/lib/services/ResourceReadService.ts index c7bd71b0..dc02b832 100644 --- a/src/server/lib/services/ResourceReadService.ts +++ b/apps/api/src/lib/services/ResourceReadService.ts @@ -12,8 +12,8 @@ import type { ResourceReadResponse, ResourceScope, ResourceTopic, -} from '@shared/contracts/resources'; -import { RESOURCE_DATA_SCHEMAS } from '@shared/contracts/resources'; +} from '@daoyou/shared/contracts/resources'; +import { RESOURCE_DATA_SCHEMAS } from '@daoyou/shared/contracts/resources'; import { sql } from 'drizzle-orm'; export async function readResourceWithMeta( diff --git a/src/server/lib/services/RetreatApplicationService.ts b/apps/api/src/lib/services/RetreatApplicationService.ts similarity index 97% rename from src/server/lib/services/RetreatApplicationService.ts rename to apps/api/src/lib/services/RetreatApplicationService.ts index c1dae385..60f03f93 100644 --- a/src/server/lib/services/RetreatApplicationService.ts +++ b/apps/api/src/lib/services/RetreatApplicationService.ts @@ -16,17 +16,17 @@ import type { BreakthroughStoryPayload, LifespanExhaustedStoryPayload, } from '@server/utils/prompts'; -import { getRetreatQiCost } from '@shared/config/qiSystem'; -import { RESOURCE_DATA_SCHEMAS } from '@shared/contracts/resources'; -import type { RetreatResultData } from '@shared/contracts/retreat'; +import { getRetreatQiCost } from '@daoyou/shared/config/qiSystem'; +import { RESOURCE_DATA_SCHEMAS } from '@daoyou/shared/contracts/resources'; +import type { RetreatResultData } from '@daoyou/shared/contracts/retreat'; import { attemptBreakthrough, performCultivation, -} from '@shared/engine/cultivation/CultivationEngine'; -import type { BreakthroughHistoryEntry } from '@shared/types/cultivator'; +} from '@daoyou/shared/engine/cultivation/CultivationEngine'; +import type { BreakthroughHistoryEntry } from '@daoyou/shared/types/cultivator'; import { randomUUID } from 'crypto'; import { findJournalOperation } from '@server/lib/repositories/playerJournalRepository'; -import { retreatResultFromJournal } from '@shared/contracts/playerJournal'; +import { retreatResultFromJournal } from '@daoyou/shared/contracts/playerJournal'; import { playerCommandExecutor } from './CommandExecutors'; import { PillOperationExecutor } from './PillOperationExecutor'; import { QiService } from './QiService'; diff --git a/src/server/lib/services/RetreatResourceChanges.ts b/apps/api/src/lib/services/RetreatResourceChanges.ts similarity index 98% rename from src/server/lib/services/RetreatResourceChanges.ts rename to apps/api/src/lib/services/RetreatResourceChanges.ts index 03ddac05..1632d806 100644 --- a/src/server/lib/services/RetreatResourceChanges.ts +++ b/apps/api/src/lib/services/RetreatResourceChanges.ts @@ -1,7 +1,7 @@ import type { ResourceChangeDescriptor, ResourceDataMap, -} from '@shared/contracts/resources'; +} from '@daoyou/shared/contracts/resources'; import { qiCurrencyChange, type QiSettlementBaseline, diff --git a/src/server/lib/services/SectError.ts b/apps/api/src/lib/services/SectError.ts similarity index 100% rename from src/server/lib/services/SectError.ts rename to apps/api/src/lib/services/SectError.ts diff --git a/src/server/lib/services/SectFacilityService.ts b/apps/api/src/lib/services/SectFacilityService.ts similarity index 100% rename from src/server/lib/services/SectFacilityService.ts rename to apps/api/src/lib/services/SectFacilityService.ts diff --git a/src/server/lib/services/SectMeridianResetService.ts b/apps/api/src/lib/services/SectMeridianResetService.ts similarity index 100% rename from src/server/lib/services/SectMeridianResetService.ts rename to apps/api/src/lib/services/SectMeridianResetService.ts diff --git a/src/server/lib/services/SectShopService.ts b/apps/api/src/lib/services/SectShopService.ts similarity index 97% rename from src/server/lib/services/SectShopService.ts rename to apps/api/src/lib/services/SectShopService.ts index 97d3e67c..14aa6adb 100644 --- a/src/server/lib/services/SectShopService.ts +++ b/apps/api/src/lib/services/SectShopService.ts @@ -9,14 +9,14 @@ import { RewardItemSchema, materializeRewardItem, rewardDisplayItem, -} from '@shared/contracts/adminRewards'; +} from '@daoyou/shared/contracts/adminRewards'; import type { SectShopItemData, SectShopItemMutation, SectShopItemStatus, -} from '@shared/contracts/sectShop'; -import { SECT_SHOP_MAX_PRICE } from '@shared/contracts/sectShop'; -import { getItemExchangePurchaseWeek } from '@shared/lib/itemExchangeShop'; +} from '@daoyou/shared/contracts/sectShop'; +import { SECT_SHOP_MAX_PRICE } from '@daoyou/shared/contracts/sectShop'; +import { getItemExchangePurchaseWeek } from '@daoyou/shared/lib/itemExchangeShop'; import { and, asc, desc, eq, sql, type SQL } from 'drizzle-orm'; import { randomUUID } from 'node:crypto'; import { assertInventoryIdle, grantInventory } from './InventoryService'; diff --git a/src/server/lib/services/SponsorshipApplicationService.ts b/apps/api/src/lib/services/SponsorshipApplicationService.ts similarity index 99% rename from src/server/lib/services/SponsorshipApplicationService.ts rename to apps/api/src/lib/services/SponsorshipApplicationService.ts index 9ddae34e..efbdba1a 100644 --- a/src/server/lib/services/SponsorshipApplicationService.ts +++ b/apps/api/src/lib/services/SponsorshipApplicationService.ts @@ -1,3 +1,4 @@ +import { getRuntimeEnvironment } from '@server/lib/config/environment'; import { sendViaSmtp } from '@server/lib/admin/smtp'; import { getAdminUserIds } from '@server/lib/auth/adminAccess'; import { authUsers } from '@server/lib/auth/schema'; @@ -33,7 +34,7 @@ import { resolveSponsorshipTier, SPONSORSHIP_TIER_META, type SponsorshipTierId, -} from '@shared/lib/sponsorship'; +} from '@daoyou/shared/lib/sponsorship'; import { and, asc, @@ -67,7 +68,7 @@ export class SponsorshipApplicationError extends Error { } function isAutomaticFulfillmentEnabled(): boolean { - return process.env.SPONSORSHIP_FULFILLMENT_ENABLED === 'true'; + return getRuntimeEnvironment().SPONSORSHIP_FULFILLMENT_ENABLED === 'true'; } function snapshotExpiry(now = new Date()): Date { @@ -1729,7 +1730,7 @@ export async function grantManualSponsorshipMerit(args: { export async function sendSponsorshipAdminDigest( now = new Date(), ): Promise { - const configuredEmails = (process.env.ADMIN_EMAILS ?? '') + const configuredEmails = (getRuntimeEnvironment().ADMIN_EMAILS ?? '') .split(',') .map((email) => email.trim().toLowerCase()) .filter(Boolean); diff --git a/src/server/lib/services/StoryApplicationService.ts b/apps/api/src/lib/services/StoryApplicationService.ts similarity index 95% rename from src/server/lib/services/StoryApplicationService.ts rename to apps/api/src/lib/services/StoryApplicationService.ts index 684db84a..8dd05970 100644 --- a/src/server/lib/services/StoryApplicationService.ts +++ b/apps/api/src/lib/services/StoryApplicationService.ts @@ -1,4 +1,4 @@ -import type { StoryView } from '@shared/story/schema'; +import type { StoryView } from '@daoyou/shared/story/schema'; import { playerCommandExecutor } from './CommandExecutors'; import { StoryService } from './StoryService'; diff --git a/src/server/lib/services/StoryDomainEventProjector.ts b/apps/api/src/lib/services/StoryDomainEventProjector.ts similarity index 83% rename from src/server/lib/services/StoryDomainEventProjector.ts rename to apps/api/src/lib/services/StoryDomainEventProjector.ts index 162ca36b..a8622a53 100644 --- a/src/server/lib/services/StoryDomainEventProjector.ts +++ b/apps/api/src/lib/services/StoryDomainEventProjector.ts @@ -3,9 +3,9 @@ import { StoryService } from '@server/lib/services/StoryService'; import { isDomainEventType, type DomainEventEnvelope, -} from '@shared/contracts/domainEvents'; -import type { ResourceChangeDescriptor } from '@shared/contracts/resources'; -import { storyMarkForSignal } from '@shared/story/signals'; +} from '@daoyou/shared/contracts/domainEvents'; +import type { ResourceChangeDescriptor } from '@daoyou/shared/contracts/resources'; +import { storyMarkForSignal } from '@daoyou/shared/story/signals'; export async function projectStoryDomainEvent( event: DomainEventEnvelope, diff --git a/src/server/lib/services/StoryService.ts b/apps/api/src/lib/services/StoryService.ts similarity index 96% rename from src/server/lib/services/StoryService.ts rename to apps/api/src/lib/services/StoryService.ts index 5de3aed6..93dc2bd6 100644 --- a/src/server/lib/services/StoryService.ts +++ b/apps/api/src/lib/services/StoryService.ts @@ -10,13 +10,13 @@ import { updateCultivatorStory, } from '@server/lib/repositories/storyRepository'; import { lockCultivatorForStateMutation } from '@server/lib/repositories/playerStateRepository'; -import { getGuideLesson } from '@shared/guide/catalog'; -import type { ResourceChangeDescriptor } from '@shared/contracts/resources'; -import { storyReward } from '@shared/story/grants'; +import { getGuideLesson } from '@daoyou/shared/guide/catalog'; +import type { ResourceChangeDescriptor } from '@daoyou/shared/contracts/resources'; +import { storyReward } from '@daoyou/shared/story/grants'; import { getStoryChapter, restingStoryProgress, -} from '@shared/story/catalog'; +} from '@daoyou/shared/story/catalog'; import { acknowledgeGuide, acknowledgePerformance, @@ -25,7 +25,7 @@ import { resolveStory, rewindToUnwatchedPerformance, type StoryResolution, -} from '@shared/story/resolve'; +} from '@daoyou/shared/story/resolve'; import { emptyStoryFacts, STORY_MARK_FACT_IDS, @@ -34,7 +34,7 @@ import { type StoryFacts, type StoryProgress, type StoryView, -} from '@shared/story/schema'; +} from '@daoyou/shared/story/schema'; import { and, eq, sql } from 'drizzle-orm'; import { grantInventory } from './InventoryService'; diff --git a/src/server/lib/services/SystemMailService.ts b/apps/api/src/lib/services/SystemMailService.ts similarity index 97% rename from src/server/lib/services/SystemMailService.ts rename to apps/api/src/lib/services/SystemMailService.ts index 831fb336..69598502 100644 --- a/src/server/lib/services/SystemMailService.ts +++ b/apps/api/src/lib/services/SystemMailService.ts @@ -11,14 +11,14 @@ import { redis } from '@server/lib/redis'; import { materializeRewardAttachments, rewardAttachments, -} from '@shared/contracts/adminRewards'; -import type { DomainEventEnvelope } from '@shared/contracts/domainEvents'; +} from '@daoyou/shared/contracts/adminRewards'; +import type { DomainEventEnvelope } from '@daoyou/shared/contracts/domainEvents'; import { isSystemMailInWindow, matchesSystemMailConditions, SystemMailAudienceSnapshotSchema, SystemMailConditionsSchema, -} from '@shared/contracts/systemMail'; +} from '@daoyou/shared/contracts/systemMail'; import { and, asc, eq, gt, lte, notExists, sql } from 'drizzle-orm'; import { randomUUID } from 'node:crypto'; import { MailService } from './MailService'; diff --git a/src/server/lib/services/TalismanScenarioService.ts b/apps/api/src/lib/services/TalismanScenarioService.ts similarity index 100% rename from src/server/lib/services/TalismanScenarioService.ts rename to apps/api/src/lib/services/TalismanScenarioService.ts diff --git a/src/server/lib/services/TaskApplicationService.ts b/apps/api/src/lib/services/TaskApplicationService.ts similarity index 94% rename from src/server/lib/services/TaskApplicationService.ts rename to apps/api/src/lib/services/TaskApplicationService.ts index f38a8257..143bcb64 100644 --- a/src/server/lib/services/TaskApplicationService.ts +++ b/apps/api/src/lib/services/TaskApplicationService.ts @@ -1,5 +1,5 @@ import type { DbTransaction } from '@server/lib/drizzle/db'; -import type { ResourceChangeDescriptor } from '@shared/contracts/resources'; +import type { ResourceChangeDescriptor } from '@daoyou/shared/contracts/resources'; import { playerCommandExecutor } from './CommandExecutors'; import { readPlayerTaskSummary } from './PlayerResourceReaderService'; import { TaskService } from './TaskService'; diff --git a/src/server/lib/services/TaskDomainEventProjector.ts b/apps/api/src/lib/services/TaskDomainEventProjector.ts similarity index 95% rename from src/server/lib/services/TaskDomainEventProjector.ts rename to apps/api/src/lib/services/TaskDomainEventProjector.ts index 149600d3..8dc2886d 100644 --- a/src/server/lib/services/TaskDomainEventProjector.ts +++ b/apps/api/src/lib/services/TaskDomainEventProjector.ts @@ -3,8 +3,8 @@ import { lockCultivatorForStateMutation } from '@server/lib/repositories/playerS import { isDomainEventType, type DomainEventEnvelope, -} from '@shared/contracts/domainEvents'; -import type { ResourceChangeDescriptor } from '@shared/contracts/resources'; +} from '@daoyou/shared/contracts/domainEvents'; +import type { ResourceChangeDescriptor } from '@daoyou/shared/contracts/resources'; import type { FeatureCommandResult } from './CommandExecutors'; import { readPlayerTaskSummary } from './PlayerResourceReaderService'; import { TaskService } from './TaskService'; diff --git a/src/server/lib/services/TaskService.ts b/apps/api/src/lib/services/TaskService.ts similarity index 98% rename from src/server/lib/services/TaskService.ts rename to apps/api/src/lib/services/TaskService.ts index 2b102993..2952c856 100644 --- a/src/server/lib/services/TaskService.ts +++ b/apps/api/src/lib/services/TaskService.ts @@ -16,14 +16,14 @@ import { updateCultivatorTask, type CultivatorTaskRecord, } from '@server/lib/repositories/taskRepository'; -import { getNextStage } from '@server/utils/breakthroughCalculator'; -import { getOrInitCultivationProgress } from '@server/utils/cultivationUtils'; -import { getBreakthroughPillLabel } from '@shared/lib/breakthroughPill'; -import { isConditionStatusActive } from '@shared/lib/condition'; -import type { ConditionStatusKey } from '@shared/types/condition'; -import { REALM_ORDER, type RealmType } from '@shared/types/constants'; -import type { CultivationProgress, Cultivator } from '@shared/types/cultivator'; -import type { MailAttachment } from '@shared/types/mail'; +import { getNextStage } from '@daoyou/shared/engine/cultivation/breakthroughCalculator'; +import { getOrInitCultivationProgress } from '@daoyou/shared/engine/cultivation/cultivationUtils'; +import { getBreakthroughPillLabel } from '@daoyou/shared/lib/breakthroughPill'; +import { isConditionStatusActive } from '@daoyou/shared/lib/condition'; +import type { ConditionStatusKey } from '@daoyou/shared/types/condition'; +import { REALM_ORDER, type RealmType } from '@daoyou/shared/types/constants'; +import type { CultivationProgress, Cultivator } from '@daoyou/shared/types/cultivator'; +import type { MailAttachment } from '@daoyou/shared/types/mail'; import type { TaskActionLink, TaskEvent, @@ -35,7 +35,7 @@ import type { TaskProgressSnapshot, TaskStageProgress, TaskStatus, -} from '@shared/types/task'; +} from '@daoyou/shared/types/task'; import { getBreakthroughTaskDefinition, getBreakthroughTaskDefinitionByTransition, diff --git a/src/server/lib/services/WorldRumorDomainEventProjector.ts b/apps/api/src/lib/services/WorldRumorDomainEventProjector.ts similarity index 93% rename from src/server/lib/services/WorldRumorDomainEventProjector.ts rename to apps/api/src/lib/services/WorldRumorDomainEventProjector.ts index c5871ce1..a81ca6a1 100644 --- a/src/server/lib/services/WorldRumorDomainEventProjector.ts +++ b/apps/api/src/lib/services/WorldRumorDomainEventProjector.ts @@ -2,11 +2,11 @@ import { createMessage } from '@server/lib/repositories/worldChatRepository'; import { isDomainEventType, type DomainEventEnvelope, -} from '@shared/contracts/domainEvents'; -import { alchemyShowcaseSnapshot } from '@shared/items/alchemyShowcase'; -import { forgingShowcaseSnapshot } from '@shared/items/forgingShowcase'; -import { QUALITY_ORDER } from '@shared/types/constants'; -import type { WorldChatPayload } from '@shared/types/world-chat'; +} from '@daoyou/shared/contracts/domainEvents'; +import { alchemyShowcaseSnapshot } from '@daoyou/shared/items/alchemyShowcase'; +import { forgingShowcaseSnapshot } from '@daoyou/shared/items/forgingShowcase'; +import { QUALITY_ORDER } from '@daoyou/shared/types/constants'; +import type { WorldChatPayload } from '@daoyou/shared/types/world-chat'; import type { FeatureCommandResult } from './CommandExecutors'; type RumorProjectionResult = FeatureCommandResult<{ diff --git a/src/server/lib/services/YieldApplicationService.ts b/apps/api/src/lib/services/YieldApplicationService.ts similarity index 94% rename from src/server/lib/services/YieldApplicationService.ts rename to apps/api/src/lib/services/YieldApplicationService.ts index 9ff3efde..faa04ac0 100644 --- a/src/server/lib/services/YieldApplicationService.ts +++ b/apps/api/src/lib/services/YieldApplicationService.ts @@ -8,13 +8,13 @@ import { updateCultivationExp, updateSpiritStones, } from '@server/lib/services/cultivator/CultivatorStateRepository'; -import { getOrInitCultivationProgress } from '@server/utils/cultivationUtils'; -import { MailInventoryGrantSchema } from '@shared/contracts/mail'; -import type { GeneratedMaterial } from '@shared/engine/material/creation/types'; -import { YieldCalculator } from '@shared/engine/yield/YieldCalculator'; -import { planYieldRewards } from '@shared/rewards/yield'; -import type { RealmStage, RealmType } from '@shared/types/constants'; -import type { CultivationProgress } from '@shared/types/cultivator'; +import { getOrInitCultivationProgress } from '@daoyou/shared/engine/cultivation/cultivationUtils'; +import { MailInventoryGrantSchema } from '@daoyou/shared/contracts/mail'; +import type { GeneratedMaterial } from '@daoyou/shared/engine/material/creation/types'; +import { YieldCalculator } from '@daoyou/shared/engine/yield/YieldCalculator'; +import { planYieldRewards } from '@daoyou/shared/rewards/yield'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; +import type { CultivationProgress } from '@daoyou/shared/types/cultivator'; import { and, eq } from 'drizzle-orm'; import { getExecutor } from '../drizzle/db'; import { playerCommandExecutor } from './CommandExecutors'; diff --git a/src/server/lib/services/YieldDomainEventProjector.ts b/apps/api/src/lib/services/YieldDomainEventProjector.ts similarity index 86% rename from src/server/lib/services/YieldDomainEventProjector.ts rename to apps/api/src/lib/services/YieldDomainEventProjector.ts index 0a2968c4..2acd344b 100644 --- a/src/server/lib/services/YieldDomainEventProjector.ts +++ b/apps/api/src/lib/services/YieldDomainEventProjector.ts @@ -1,8 +1,8 @@ import type { DbTransaction } from '@server/lib/drizzle/db'; -import type { DomainEventEnvelope } from '@shared/contracts/domainEvents'; -import type { ItemGrant } from '@shared/inventory'; -import { findItemDefinition } from '@shared/items/registry'; -import type { RealmType } from '@shared/types/constants'; +import type { DomainEventEnvelope } from '@daoyou/shared/contracts/domainEvents'; +import type { ItemGrant } from '@daoyou/shared/inventory'; +import { findItemDefinition } from '@daoyou/shared/items/registry'; +import type { RealmType } from '@daoyou/shared/types/constants'; import { MailService, type MailAttachment } from './MailService'; import { generateRealmMaterials } from './MaterialRewardService'; diff --git a/src/server/lib/services/alchemy/AlchemyInventory.ts b/apps/api/src/lib/services/alchemy/AlchemyInventory.ts similarity index 93% rename from src/server/lib/services/alchemy/AlchemyInventory.ts rename to apps/api/src/lib/services/alchemy/AlchemyInventory.ts index c2aa2fd3..aa86f063 100644 --- a/src/server/lib/services/alchemy/AlchemyInventory.ts +++ b/apps/api/src/lib/services/alchemy/AlchemyInventory.ts @@ -8,10 +8,10 @@ import { groupAlchemyBagMaterials, groupAlchemyStorageMaterials, type AlchemyBagMaterial, -} from '@shared/inventory/alchemy'; -import { inventoryStackIdentity } from '@shared/inventory/stack-key'; -import { consumableFactsOf } from '@shared/items/definitions/consumables'; -import type { Consumable } from '@shared/types/cultivator'; +} from '@daoyou/shared/inventory/alchemy'; +import { inventoryStackIdentity } from '@daoyou/shared/inventory/stack-key'; +import { consumableFactsOf } from '@daoyou/shared/items/definitions/consumables'; +import type { Consumable } from '@daoyou/shared/types/cultivator'; import { and, eq, inArray } from 'drizzle-orm'; import { AlchemyServiceError } from '../AlchemyServiceError'; import { diff --git a/src/server/lib/services/alchemy/AlchemyOutputAssembler.ts b/apps/api/src/lib/services/alchemy/AlchemyOutputAssembler.ts similarity index 90% rename from src/server/lib/services/alchemy/AlchemyOutputAssembler.ts rename to apps/api/src/lib/services/alchemy/AlchemyOutputAssembler.ts index 6a53f8ff..a8d8f95d 100644 --- a/src/server/lib/services/alchemy/AlchemyOutputAssembler.ts +++ b/apps/api/src/lib/services/alchemy/AlchemyOutputAssembler.ts @@ -1,11 +1,11 @@ import { calculateSingleElixirScore } from '@server/utils/rankingUtils'; -import { resolveAlchemyEffects } from '@shared/lib/alchemyEffectResolver'; +import { resolveAlchemyEffects } from '@daoyou/shared/lib/alchemyEffectResolver'; import type { AlchemyEffectRoute, AlchemyYieldProfile, PillSpec, -} from '@shared/types/consumable'; -import type { Consumable } from '@shared/types/cultivator'; +} from '@daoyou/shared/types/consumable'; +import type { Consumable } from '@daoyou/shared/types/cultivator'; export interface AlchemyOutputDraft { name: string; diff --git a/src/server/lib/services/alchemyServiceV2.ts b/apps/api/src/lib/services/alchemyServiceV2.ts similarity index 94% rename from src/server/lib/services/alchemyServiceV2.ts rename to apps/api/src/lib/services/alchemyServiceV2.ts index e27df75d..1dd950f1 100644 --- a/src/server/lib/services/alchemyServiceV2.ts +++ b/apps/api/src/lib/services/alchemyServiceV2.ts @@ -8,38 +8,38 @@ import { type PreparedAlchemyMaterial, } from '@server/lib/services/AlchemyRecipeRules'; import { getCultivatorPreHeavenFates } from '@server/lib/services/cultivator/CultivatorProfileRepository'; -import { ELEMENT_PREFIX_MAP } from '@shared/config/alchemyConfig'; -import { ALCHEMY_MAX_DOSE } from '@shared/config/alchemyInput'; -import type { ResourceOperationSettlement } from '@shared/engine/resource/types'; -import type { AlchemyBagMaterial } from '@shared/inventory/alchemy'; +import { ELEMENT_PREFIX_MAP } from '@daoyou/shared/config/alchemyConfig'; +import { ALCHEMY_MAX_DOSE } from '@daoyou/shared/config/alchemyInput'; +import type { ResourceOperationSettlement } from '@daoyou/shared/engine/resource/types'; +import type { AlchemyBagMaterial } from '@daoyou/shared/inventory/alchemy'; import { calculateAlchemyCost, calculateHighestMaterialRank, -} from '@shared/lib/alchemyCost'; -import { normalizeAlchemyEffectRoute } from '@shared/lib/alchemyEffectResolver'; -import { isAlchemyMaterialType } from '@shared/lib/alchemyMaterials'; +} from '@daoyou/shared/lib/alchemyCost'; +import { normalizeAlchemyEffectRoute } from '@daoyou/shared/lib/alchemyEffectResolver'; +import { isAlchemyMaterialType } from '@daoyou/shared/lib/alchemyMaterials'; import { calculateAlchemyQiCost, rollAlchemyYieldProfile, toAlchemyYieldDisplayProfile, -} from '@shared/lib/alchemyYield'; +} from '@daoyou/shared/lib/alchemyYield'; import { getBreakthroughPillLabel, getNextMajorRealm, -} from '@shared/lib/breakthroughPill'; +} from '@daoyou/shared/lib/breakthroughPill'; import { evaluateFateContext, getAlchemySpiritStoneMultiplier, scaleFateAdjustedCost, -} from '@shared/lib/fates'; +} from '@daoyou/shared/lib/fates'; import type { ElementType, MaterialType, Quality, RealmType, -} from '@shared/types/constants'; -import type { AlchemyRecipePlan, PillSpec } from '@shared/types/consumable'; -import type { Consumable, PreHeavenFate } from '@shared/types/cultivator'; +} from '@daoyou/shared/types/constants'; +import type { AlchemyRecipePlan, PillSpec } from '@daoyou/shared/types/consumable'; +import type { Consumable, PreHeavenFate } from '@daoyou/shared/types/cultivator'; import { and, eq, sql } from 'drizzle-orm'; import { consumeAlchemyMaterials, @@ -84,7 +84,7 @@ export interface ImprovisedAlchemyCraftResult { consumables?: Consumable[]; /** 本炉新增数量,不能使用合堆后的库存行作为结果展示数据。 */ craftedConsumables?: Consumable[]; - yieldProfile?: import('@shared/types/consumable').AlchemyYieldDisplayProfile; + yieldProfile?: import('@daoyou/shared/types/consumable').AlchemyYieldDisplayProfile; formulaDiscovery?: Awaited>; } @@ -228,7 +228,7 @@ function buildFallbackDescription( function buildAlchemySpec( synthesis: ReturnType, materialNames: string[], - route: import('@shared/types/consumable').AlchemyEffectRoute, + route: import('@daoyou/shared/types/consumable').AlchemyEffectRoute, ): Omit { return { kind: 'pill', diff --git a/src/server/lib/services/arenaRoomBroadcaster.ts b/apps/api/src/lib/services/arenaRoomBroadcaster.ts similarity index 95% rename from src/server/lib/services/arenaRoomBroadcaster.ts rename to apps/api/src/lib/services/arenaRoomBroadcaster.ts index 5a58335e..136e63cc 100644 --- a/src/server/lib/services/arenaRoomBroadcaster.ts +++ b/apps/api/src/lib/services/arenaRoomBroadcaster.ts @@ -7,8 +7,8 @@ import { parsePubSubEnvelope, } from '@server/lib/services/pubSubEnvelope'; import { encodeNatsSubjectToken } from '@server/lib/services/natsCorePubSub'; -import type { ArenaRoomChangedPayloadV1 } from '@shared/contracts/realtime'; -import type { ArenaRoomV1 } from '@shared/contracts/arena'; +import type { ArenaRoomChangedPayloadV1 } from '@daoyou/shared/contracts/realtime'; +import type { ArenaRoomV1 } from '@daoyou/shared/contracts/arena'; const SUBJECT_PREFIX = 'daoyou.realtime.arena-room.user'; const ROOM_STATUSES = new Set([ diff --git a/src/server/lib/services/black-market/BlackMarketConversationService.ts b/apps/api/src/lib/services/black-market/BlackMarketConversationService.ts similarity index 98% rename from src/server/lib/services/black-market/BlackMarketConversationService.ts rename to apps/api/src/lib/services/black-market/BlackMarketConversationService.ts index 5dda2942..b76413b1 100644 --- a/src/server/lib/services/black-market/BlackMarketConversationService.ts +++ b/apps/api/src/lib/services/black-market/BlackMarketConversationService.ts @@ -1,7 +1,7 @@ import { renderPrompt } from '@server/lib/prompts'; import { generateAiObject, streamAiText } from '@server/utils/aiClient'; import { truncateText } from '@server/utils/llmPayload'; -import type { BlackMarketNegotiationOutcome } from '@shared/lib/blackMarketNegotiation'; +import type { BlackMarketNegotiationOutcome } from '@daoyou/shared/lib/blackMarketNegotiation'; import { z } from 'zod'; import type { BlackMarketTurnContext, BlackMarketTurnProposal } from './types'; diff --git a/src/server/lib/services/black-market/BlackMarketEntryService.ts b/apps/api/src/lib/services/black-market/BlackMarketEntryService.ts similarity index 96% rename from src/server/lib/services/black-market/BlackMarketEntryService.ts rename to apps/api/src/lib/services/black-market/BlackMarketEntryService.ts index 98ee2f27..154f5499 100644 --- a/src/server/lib/services/black-market/BlackMarketEntryService.ts +++ b/apps/api/src/lib/services/black-market/BlackMarketEntryService.ts @@ -6,8 +6,8 @@ import { QiService } from '@server/lib/services/QiService'; import { blackMarketEntryCost, blackMarketEntryId, -} from '@shared/lib/blackMarketRules'; -import type { BlackMarketNpcId } from '@shared/types/blackMarket'; +} from '@daoyou/shared/lib/blackMarketRules'; +import type { BlackMarketNpcId } from '@daoyou/shared/types/blackMarket'; import { createHash } from 'node:crypto'; const ENTRY_SOURCE = 'black_market_entry'; diff --git a/src/server/lib/services/black-market/BlackMarketMaskService.ts b/apps/api/src/lib/services/black-market/BlackMarketMaskService.ts similarity index 89% rename from src/server/lib/services/black-market/BlackMarketMaskService.ts rename to apps/api/src/lib/services/black-market/BlackMarketMaskService.ts index 45fc9169..2b7a7b8b 100644 --- a/src/server/lib/services/black-market/BlackMarketMaskService.ts +++ b/apps/api/src/lib/services/black-market/BlackMarketMaskService.ts @@ -1,6 +1,6 @@ -import { blackMarketUnit } from '@shared/lib/blackMarketRules'; -import type { MaterialType } from '@shared/types/constants'; -import type { Material } from '@shared/types/cultivator'; +import { blackMarketUnit } from '@daoyou/shared/lib/blackMarketRules'; +import type { MaterialType } from '@daoyou/shared/types/constants'; +import type { Material } from '@daoyou/shared/types/cultivator'; const MASKS: Record> = { seed: [ diff --git a/src/server/lib/services/black-market/BlackMarketNpcConfig.ts b/apps/api/src/lib/services/black-market/BlackMarketNpcConfig.ts similarity index 97% rename from src/server/lib/services/black-market/BlackMarketNpcConfig.ts rename to apps/api/src/lib/services/black-market/BlackMarketNpcConfig.ts index d074dbe2..627b938b 100644 --- a/src/server/lib/services/black-market/BlackMarketNpcConfig.ts +++ b/apps/api/src/lib/services/black-market/BlackMarketNpcConfig.ts @@ -1,7 +1,7 @@ import type { BlackMarketNpcId, BlackMarketNpcSummary, -} from '@shared/types/blackMarket'; +} from '@daoyou/shared/types/blackMarket'; export interface BlackMarketNpcConfig extends Omit< BlackMarketNpcSummary, diff --git a/src/server/lib/services/black-market/BlackMarketObservationService.ts b/apps/api/src/lib/services/black-market/BlackMarketObservationService.ts similarity index 97% rename from src/server/lib/services/black-market/BlackMarketObservationService.ts rename to apps/api/src/lib/services/black-market/BlackMarketObservationService.ts index 413451bf..9103ddbc 100644 --- a/src/server/lib/services/black-market/BlackMarketObservationService.ts +++ b/apps/api/src/lib/services/black-market/BlackMarketObservationService.ts @@ -3,8 +3,8 @@ import { redis } from '@server/lib/redis'; import { parseRedisJson } from '@server/lib/redis/json'; import { generateAiObject } from '@server/utils/aiClient'; import { stableCompactStringify, truncateText } from '@server/utils/llmPayload'; -import { sanitizeBlackMarketObservationText } from '@shared/lib/blackMarketObservations'; -import type { Material } from '@shared/types/cultivator'; +import { sanitizeBlackMarketObservationText } from '@daoyou/shared/lib/blackMarketObservations'; +import type { Material } from '@daoyou/shared/types/cultivator'; import { createHash } from 'node:crypto'; import { z } from 'zod'; import type { BlackMarketInternalObservation } from './types'; diff --git a/src/server/lib/services/black-market/BlackMarketPerceptionService.ts b/apps/api/src/lib/services/black-market/BlackMarketPerceptionService.ts similarity index 98% rename from src/server/lib/services/black-market/BlackMarketPerceptionService.ts rename to apps/api/src/lib/services/black-market/BlackMarketPerceptionService.ts index 0d2d9d96..197be653 100644 --- a/src/server/lib/services/black-market/BlackMarketPerceptionService.ts +++ b/apps/api/src/lib/services/black-market/BlackMarketPerceptionService.ts @@ -1,7 +1,7 @@ import { renderPrompt } from '@server/lib/prompts'; import { generateAiObject } from '@server/utils/aiClient'; import { truncateText } from '@server/utils/llmPayload'; -import type { BlackMarketPricingState } from '@shared/lib/blackMarketPricing'; +import type { BlackMarketPricingState } from '@daoyou/shared/lib/blackMarketPricing'; import { z } from 'zod'; import type { BlackMarketNpcConfig } from './BlackMarketNpcConfig'; import type { diff --git a/src/server/lib/services/black-market/BlackMarketService.ts b/apps/api/src/lib/services/black-market/BlackMarketService.ts similarity index 98% rename from src/server/lib/services/black-market/BlackMarketService.ts rename to apps/api/src/lib/services/black-market/BlackMarketService.ts index a47a3aef..3b53af42 100644 --- a/src/server/lib/services/black-market/BlackMarketService.ts +++ b/apps/api/src/lib/services/black-market/BlackMarketService.ts @@ -1,3 +1,4 @@ +import { getRuntimeEnvironment } from '@server/lib/config/environment'; import { deliverMarketMaterial } from '../MarketInventoryDelivery'; import type { DbTransaction } from '@server/lib/drizzle/db'; import { cultivators } from '@server/lib/drizzle/schema'; @@ -9,21 +10,21 @@ import { sampleMaterialLibraryEntryByPreferences, } from '@server/lib/services/MaterialLibraryService'; import { readCultivatorRealm } from '@server/lib/services/cultivator/CultivatorFactsReader'; -import type { ResourceChangeDescriptor } from '@shared/contracts/resources'; -import { MaterialFactsSchema } from '@shared/items/definitions/materials'; +import type { ResourceChangeDescriptor } from '@daoyou/shared/contracts/resources'; +import { MaterialFactsSchema } from '@daoyou/shared/items/definitions/materials'; import { applyBlackMarketBeliefPatch, describeBlackMarketClaimMode, -} from '@shared/lib/blackMarketBelief'; +} from '@daoyou/shared/lib/blackMarketBelief'; import { applyBlackMarketPriceDecision, assessOffer, -} from '@shared/lib/blackMarketNegotiation'; +} from '@daoyou/shared/lib/blackMarketNegotiation'; import { applyBlackMarketBeliefPressure, computeBlackMarketTrueValue, createBlackMarketPricing, -} from '@shared/lib/blackMarketPricing'; +} from '@daoyou/shared/lib/blackMarketPricing'; import { BLACK_MARKET_ENTRY_COST, BLACK_MARKET_MAX_INSPECTIONS, @@ -35,7 +36,7 @@ import { blackMarketTurnsRemaining, blackMarketUnit, classifyBlackMarketReveal, -} from '@shared/lib/blackMarketRules'; +} from '@daoyou/shared/lib/blackMarketRules'; import { BLACK_MARKET_QUALITY_WEIGHTS, getMarketConfigByNodeId, @@ -43,7 +44,7 @@ import { getRegionProfile, isMarketNodeEnabled, validateLayerAccess, -} from '@shared/lib/game/marketConfig'; +} from '@daoyou/shared/lib/game/marketConfig'; import { type BlackMarketInteractCommand, type BlackMarketInteractionResult, @@ -53,8 +54,8 @@ import { type BlackMarketOverview, type BlackMarketReveal, type BlackMarketSessionView, -} from '@shared/types/blackMarket'; -import { QUALITY_ORDER } from '@shared/types/constants'; +} from '@daoyou/shared/types/blackMarket'; +import { QUALITY_ORDER } from '@daoyou/shared/types/constants'; import { and, eq, sql } from 'drizzle-orm'; import { createHmac, randomUUID } from 'node:crypto'; import { @@ -92,7 +93,7 @@ export class BlackMarketServiceError extends Error { } function secret(): string { - const value = process.env.BETTER_AUTH_SECRET?.trim(); + const value = getRuntimeEnvironment().BETTER_AUTH_SECRET?.trim(); if (!value) throw new Error('BETTER_AUTH_SECRET is required for black market'); return value; diff --git a/src/server/lib/services/black-market/BlackMarketSessionRepository.ts b/apps/api/src/lib/services/black-market/BlackMarketSessionRepository.ts similarity index 100% rename from src/server/lib/services/black-market/BlackMarketSessionRepository.ts rename to apps/api/src/lib/services/black-market/BlackMarketSessionRepository.ts diff --git a/src/server/lib/services/black-market/types.ts b/apps/api/src/lib/services/black-market/types.ts similarity index 88% rename from src/server/lib/services/black-market/types.ts rename to apps/api/src/lib/services/black-market/types.ts index 7f778741..8207c012 100644 --- a/src/server/lib/services/black-market/types.ts +++ b/apps/api/src/lib/services/black-market/types.ts @@ -1,14 +1,14 @@ -import type { BlackMarketObservationCandidate } from '@shared/lib/blackMarketObservations'; -import type { BlackMarketPricingState } from '@shared/lib/blackMarketPricing'; -import type { BlackMarketBeliefPatch } from '@shared/lib/blackMarketBelief'; +import type { BlackMarketObservationCandidate } from '@daoyou/shared/lib/blackMarketObservations'; +import type { BlackMarketPricingState } from '@daoyou/shared/lib/blackMarketPricing'; +import type { BlackMarketBeliefPatch } from '@daoyou/shared/lib/blackMarketBelief'; import type { BlackMarketInspectionKind, BlackMarketMessage, BlackMarketNpcId, BlackMarketReveal, BlackMarketSessionPhase, -} from '@shared/types/blackMarket'; -import type { Material } from '@shared/types/cultivator'; +} from '@daoyou/shared/types/blackMarket'; +import type { Material } from '@daoyou/shared/types/cultivator'; export interface BlackMarketInternalObservation extends BlackMarketObservationCandidate { source: 'surface' | 'inspection'; @@ -165,12 +165,12 @@ export interface BlackMarketTurnResult { } export interface BlackMarketPreparedTurn { - result: import('@shared/types/blackMarket').BlackMarketInteractionResult; + result: import('@daoyou/shared/types/blackMarket').BlackMarketInteractionResult; sessionId: string; messageId: string; gesture: string; fallbackBody: string; replyContext: BlackMarketTurnContext; proposal: BlackMarketTurnProposal; - negotiationOutcome?: import('@shared/lib/blackMarketNegotiation').BlackMarketNegotiationOutcome; + negotiationOutcome?: import('@daoyou/shared/lib/blackMarketNegotiation').BlackMarketNegotiationOutcome; } diff --git a/src/server/lib/services/chatMessageApplication.ts b/apps/api/src/lib/services/chatMessageApplication.ts similarity index 95% rename from src/server/lib/services/chatMessageApplication.ts rename to apps/api/src/lib/services/chatMessageApplication.ts index 71b15141..92b3d89d 100644 --- a/src/server/lib/services/chatMessageApplication.ts +++ b/apps/api/src/lib/services/chatMessageApplication.ts @@ -2,16 +2,16 @@ import { db } from '@server/lib/drizzle/db'; import { readBeastRoster } from '@server/lib/repositories/combatV6BeastRepository'; import { createCombatV6ReplayShare, findOwnedCombatV6Replay } from '@server/lib/repositories/combatV6ReplayRepository'; import { readCultivatorPublicIdentity } from '@server/lib/services/cultivator/CultivatorFactsReader'; -import { beastTradePreview } from '@shared/contracts/beastTrade'; -import type { WorldChatCreateMessageRequest } from '@shared/contracts/world-chat'; -import { inventoryShowcaseSnapshot } from '@shared/items/showcase'; +import { beastTradePreview } from '@daoyou/shared/contracts/beastTrade'; +import type { WorldChatCreateMessageRequest } from '@daoyou/shared/contracts/world-chat'; +import { inventoryShowcaseSnapshot } from '@daoyou/shared/items/showcase'; import type { WorldChatItemShowcasePayload, WorldChatMessageChannel, WorldChatMessageDTO, WorldChatMessageType, WorldChatPayload, -} from '@shared/types/world-chat'; +} from '@daoyou/shared/types/world-chat'; import { readInventory } from './InventoryService'; import { textFilter } from './textFilter'; diff --git a/src/server/lib/services/combat-v6/BeastMutationGuard.ts b/apps/api/src/lib/services/combat-v6/BeastMutationGuard.ts similarity index 100% rename from src/server/lib/services/combat-v6/BeastMutationGuard.ts rename to apps/api/src/lib/services/combat-v6/BeastMutationGuard.ts diff --git a/src/server/lib/services/combat-v6/CombatActivityNotice.ts b/apps/api/src/lib/services/combat-v6/CombatActivityNotice.ts similarity index 97% rename from src/server/lib/services/combat-v6/CombatActivityNotice.ts rename to apps/api/src/lib/services/combat-v6/CombatActivityNotice.ts index d83075d8..d874d79a 100644 --- a/src/server/lib/services/combat-v6/CombatActivityNotice.ts +++ b/apps/api/src/lib/services/combat-v6/CombatActivityNotice.ts @@ -9,8 +9,8 @@ import { activeSectTaskBattle } from '@server/lib/services/combat-v6/CombatV6Sec import { CombatV6WildStore } from '@server/lib/services/combat-v6/CombatV6WildStore'; import { pendingRanking } from '@server/lib/services/combat-v6/CombatV6RankingService'; import { towerRunKey } from '@server/lib/tower/occupancy'; -import type { CombatActivityNotice } from '@shared/contracts/combatActivity'; -import { shouldExpireTowerRun } from '@shared/lib/tower/lifecycle'; +import type { CombatActivityNotice } from '@daoyou/shared/contracts/combatActivity'; +import { shouldExpireTowerRun } from '@daoyou/shared/lib/tower/lifecycle'; import { and, eq, inArray, isNotNull, ne, or } from 'drizzle-orm'; const runtime = new CombatV6RuntimeStore(); diff --git a/src/server/lib/services/combat-v6/CombatOccupancy.ts b/apps/api/src/lib/services/combat-v6/CombatOccupancy.ts similarity index 100% rename from src/server/lib/services/combat-v6/CombatOccupancy.ts rename to apps/api/src/lib/services/combat-v6/CombatOccupancy.ts diff --git a/src/server/lib/services/combat-v6/CombatV6ArenaBroadcast.ts b/apps/api/src/lib/services/combat-v6/CombatV6ArenaBroadcast.ts similarity index 95% rename from src/server/lib/services/combat-v6/CombatV6ArenaBroadcast.ts rename to apps/api/src/lib/services/combat-v6/CombatV6ArenaBroadcast.ts index 267fcb20..cb12d34f 100644 --- a/src/server/lib/services/combat-v6/CombatV6ArenaBroadcast.ts +++ b/apps/api/src/lib/services/combat-v6/CombatV6ArenaBroadcast.ts @@ -1,9 +1,9 @@ -import { arenaView } from '@shared/combat-v6/arena'; +import { arenaView } from '@daoyou/shared/combat-v6/arena'; import type { ArenaRuntime, ArenaSocketMessage, -} from '@shared/contracts/combatV6Arena'; -import { ARENA_PUBLIC_VIEW } from '@shared/contracts/combatV6Arena'; +} from '@daoyou/shared/contracts/combatV6Arena'; +import { ARENA_PUBLIC_VIEW } from '@daoyou/shared/contracts/combatV6Arena'; import { ArenaRoomService } from '../ArenaRoomService'; import { publishNatsCoreMessage, diff --git a/src/server/lib/services/combat-v6/CombatV6ArenaService.ts b/apps/api/src/lib/services/combat-v6/CombatV6ArenaService.ts similarity index 94% rename from src/server/lib/services/combat-v6/CombatV6ArenaService.ts rename to apps/api/src/lib/services/combat-v6/CombatV6ArenaService.ts index 6480fb0d..a45b1cd9 100644 --- a/src/server/lib/services/combat-v6/CombatV6ArenaService.ts +++ b/apps/api/src/lib/services/combat-v6/CombatV6ArenaService.ts @@ -1,19 +1,19 @@ import { ConditionService } from '../ConditionService'; -import { evaluateFateContext } from '@shared/lib/fates'; +import { evaluateFateContext } from '@daoyou/shared/lib/fates'; import { getCultivatorPreHeavenFates } from '../cultivator/CultivatorProfileRepository'; -import type { HuntTeam } from '@shared/contracts/hunts'; +import type { HuntTeam } from '@daoyou/shared/contracts/hunts'; import { prepareHuntReward } from '../hunts/HuntRewardService'; -import { HUNT_BOSSES, huntIsOpen } from '@shared/hunts/config'; -import { huntRealmAllowed } from '@shared/hunts/rules'; -import type { RealmType } from '@shared/types/constants'; +import { HUNT_BOSSES, huntIsOpen } from '@daoyou/shared/hunts/config'; +import { huntRealmAllowed } from '@daoyou/shared/hunts/rules'; +import type { RealmType } from '@daoyou/shared/types/constants'; import { huntEnemies, HUNT_SKILLS, HUNT_STATUSES, huntNpcCommand, -} from '@shared/engine/combat-v6/hunts/content'; -import { playerAppearances } from '@shared/combat-v6/unit-appearance'; -import type { CombatV6UnitAppearance } from '@shared/contracts/combatV6'; +} from '@daoyou/shared/engine/combat-v6/hunts/content'; +import { playerAppearances } from '@daoyou/shared/combat-v6/unit-appearance'; +import type { CombatV6UnitAppearance } from '@daoyou/shared/contracts/combatV6'; import { db } from '@server/lib/drizzle/db'; import { cultivators } from '@server/lib/drizzle/schema'; import { getJetStreamClient } from '@server/lib/nats'; @@ -31,34 +31,34 @@ import { arenaWaitingUnits, resolveArena, validateArenaCommand, -} from '@shared/combat-v6/arena'; -import { AUTO_POLICY_VERSION, automaticCommands } from '@shared/combat-v6/auto'; -import { validateCommandGroup } from '@shared/combat-v6/controlled-commands'; +} from '@daoyou/shared/combat-v6/arena'; +import { AUTO_POLICY_VERSION, automaticCommands } from '@daoyou/shared/combat-v6/auto'; +import { validateCommandGroup } from '@daoyou/shared/combat-v6/controlled-commands'; import { combatV6ReplayView, createCombatV6Replay, -} from '@shared/combat-v6/replay'; -import { startReplayTimeline } from '@shared/combat-v6/replay-timeline'; -import type { ArenaRoomV1 } from '@shared/contracts/arena'; +} from '@daoyou/shared/combat-v6/replay'; +import { startReplayTimeline } from '@daoyou/shared/combat-v6/replay-timeline'; +import type { ArenaRoomV1 } from '@daoyou/shared/contracts/arena'; import { ARENA_PUBLIC_VIEW, ARENA_V6_PROTOCOL, type ArenaRuntime, type ArenaV6Submit, -} from '@shared/contracts/combatV6Arena'; +} from '@daoyou/shared/contracts/combatV6Arena'; import { DOMAIN_EVENT_DEFINITIONS, DOMAIN_EVENT_STREAM, parseDomainEventEnvelope, -} from '@shared/contracts/domainEvents'; +} from '@daoyou/shared/contracts/domainEvents'; import { BEAST_SKILLS, BEAST_STATUS_DEFS, projectBeastRoster, -} from '@shared/engine/combat-v6/beasts'; -import type { SkillDef, StatusDef } from '@shared/engine/combat-v6/core'; -import { projectCharacterToCombatV6 } from '@shared/engine/combat-v6/projection'; -import { characterBattleSkills } from '@shared/engine/combat-v6/projection/character-battle-skills'; +} from '@daoyou/shared/engine/combat-v6/beasts'; +import type { SkillDef, StatusDef } from '@daoyou/shared/engine/combat-v6/core'; +import { projectCharacterToCombatV6 } from '@daoyou/shared/engine/combat-v6/projection'; +import { characterBattleSkills } from '@daoyou/shared/engine/combat-v6/projection/character-battle-skills'; import { and, eq } from 'drizzle-orm'; import { JSONCodec } from 'nats'; import { ArenaRoomService } from '../ArenaRoomService'; diff --git a/src/server/lib/services/combat-v6/CombatV6ArenaStore.ts b/apps/api/src/lib/services/combat-v6/CombatV6ArenaStore.ts similarity index 98% rename from src/server/lib/services/combat-v6/CombatV6ArenaStore.ts rename to apps/api/src/lib/services/combat-v6/CombatV6ArenaStore.ts index 87d33842..3991ce74 100644 --- a/src/server/lib/services/combat-v6/CombatV6ArenaStore.ts +++ b/apps/api/src/lib/services/combat-v6/CombatV6ArenaStore.ts @@ -2,7 +2,7 @@ import { redis } from '@server/lib/redis'; import { ARENA_V6_PROTOCOL, type ArenaRuntime, -} from '@shared/contracts/combatV6Arena'; +} from '@daoyou/shared/contracts/combatV6Arena'; const root = 'combat:v6:arena'; export const arenaRuntimeKey = (id: string) => `${root}:runtime:${id}`; diff --git a/src/server/lib/services/combat-v6/CombatV6AutoStrategyService.ts b/apps/api/src/lib/services/combat-v6/CombatV6AutoStrategyService.ts similarity index 93% rename from src/server/lib/services/combat-v6/CombatV6AutoStrategyService.ts rename to apps/api/src/lib/services/combat-v6/CombatV6AutoStrategyService.ts index 4830ca85..64183c3a 100644 --- a/src/server/lib/services/combat-v6/CombatV6AutoStrategyService.ts +++ b/apps/api/src/lib/services/combat-v6/CombatV6AutoStrategyService.ts @@ -4,13 +4,13 @@ import { removeCustomAutoStrategy, saveCustomAutoStrategy, } from '@server/lib/repositories/combatV6AutoStrategyRepository'; -import { autoStatusChoices } from '@shared/combat-v6/auto-status-options'; +import { autoStatusChoices } from '@daoyou/shared/combat-v6/auto-status-options'; import { defaultAutoStrategy, type AutoStrategy, -} from '@shared/combat-v6/auto-strategy'; -import { COMBAT_V6_SECT_DEFINITIONS } from '@shared/engine/combat-v6/content'; -import { projectCharacterToCombatV6 } from '@shared/engine/combat-v6/projection'; +} from '@daoyou/shared/combat-v6/auto-strategy'; +import { COMBAT_V6_SECT_DEFINITIONS } from '@daoyou/shared/engine/combat-v6/content'; +import { projectCharacterToCombatV6 } from '@daoyou/shared/engine/combat-v6/projection'; import { assembleCombatV6TrainingPlayer } from './CombatV6BuildService'; export class AutoStrategyError extends Error {} diff --git a/src/server/lib/services/combat-v6/CombatV6BeastService.ts b/apps/api/src/lib/services/combat-v6/CombatV6BeastService.ts similarity index 97% rename from src/server/lib/services/combat-v6/CombatV6BeastService.ts rename to apps/api/src/lib/services/combat-v6/CombatV6BeastService.ts index 9cce2bde..23088d35 100644 --- a/src/server/lib/services/combat-v6/CombatV6BeastService.ts +++ b/apps/api/src/lib/services/combat-v6/CombatV6BeastService.ts @@ -16,29 +16,29 @@ import { readBeastRoster, } from '@server/lib/repositories/combatV6BeastRepository'; import { lockCultivatorForStateMutation } from '@server/lib/repositories/playerStateRepository'; -import { beastTradePreview } from '@shared/contracts/beastTrade'; +import { beastTradePreview } from '@daoyou/shared/contracts/beastTrade'; import { BeastNameSchema, type BeastFusionRequest, type BeastFusionResponse, -} from '@shared/contracts/combatV6Beasts'; +} from '@daoyou/shared/contracts/combatV6Beasts'; import { BEAST_STARTER_SPECIES, BeastLineupSchema, canDeployBeast, generateStarterBeast, type BeastLineup, -} from '@shared/engine/combat-v6/beasts'; +} from '@daoyou/shared/engine/combat-v6/beasts'; import { beastFusionReason, fuseBeasts, -} from '@shared/engine/combat-v6/beasts/fusion'; +} from '@daoyou/shared/engine/combat-v6/beasts/fusion'; import { allocateBeast, BEAST_CAPACITY, beastRestCost, type BeastAllocationSchema, -} from '@shared/engine/combat-v6/beasts/progression'; +} from '@daoyou/shared/engine/combat-v6/beasts/progression'; import { and, eq, inArray } from 'drizzle-orm'; import { createHash, randomInt, randomUUID } from 'node:crypto'; import type { z } from 'zod'; diff --git a/src/server/lib/services/combat-v6/CombatV6BreakthroughOccupancy.ts b/apps/api/src/lib/services/combat-v6/CombatV6BreakthroughOccupancy.ts similarity index 100% rename from src/server/lib/services/combat-v6/CombatV6BreakthroughOccupancy.ts rename to apps/api/src/lib/services/combat-v6/CombatV6BreakthroughOccupancy.ts diff --git a/src/server/lib/services/combat-v6/CombatV6BreakthroughService.ts b/apps/api/src/lib/services/combat-v6/CombatV6BreakthroughService.ts similarity index 93% rename from src/server/lib/services/combat-v6/CombatV6BreakthroughService.ts rename to apps/api/src/lib/services/combat-v6/CombatV6BreakthroughService.ts index f0b93e6e..dcbbca77 100644 --- a/src/server/lib/services/combat-v6/CombatV6BreakthroughService.ts +++ b/apps/api/src/lib/services/combat-v6/CombatV6BreakthroughService.ts @@ -1,4 +1,4 @@ -import { publicUnitAppearances } from '@shared/combat-v6/unit-appearance'; +import { publicUnitAppearances } from '@daoyou/shared/combat-v6/unit-appearance'; import { db, type DbTransaction } from '@server/lib/drizzle/db'; import { cultivators, cultivatorTasks } from '@server/lib/drizzle/schema'; import { dungeonPlayer } from '@server/lib/dungeon/combatV6'; @@ -11,31 +11,31 @@ import { findCultivatorTaskById, updateCultivatorTask, } from '@server/lib/repositories/taskRepository'; -import { automaticCommands } from '@shared/combat-v6/auto'; +import { automaticCommands } from '@daoyou/shared/combat-v6/auto'; import { combatV6Display, combatV6DisplayEvent, combatV6Units, visibleUnitNames, -} from '@shared/combat-v6/presentation'; -import { createCombatV6Replay } from '@shared/combat-v6/replay'; -import { liveReplayDelta } from '@shared/combat-v6/replay-timeline'; -import type { CombatV6CommandGroup } from '@shared/contracts/combatV6'; +} from '@daoyou/shared/combat-v6/presentation'; +import { createCombatV6Replay } from '@daoyou/shared/combat-v6/replay'; +import { liveReplayDelta } from '@daoyou/shared/combat-v6/replay-timeline'; +import type { CombatV6CommandGroup } from '@daoyou/shared/contracts/combatV6'; import { BreakthroughBattlePointerSchema, type BreakthroughRuntime, type BreakthroughSessionView, -} from '@shared/contracts/combatV6Breakthrough'; -import { beastDeathIds } from '@shared/engine/combat-v6/beasts'; +} from '@daoyou/shared/contracts/combatV6Breakthrough'; +import { beastDeathIds } from '@daoyou/shared/engine/combat-v6/beasts'; import { BREAKTHROUGH_CHALLENGES, BreakthroughHost, createBreakthroughHost, type BreakthroughChallengeId, -} from '@shared/engine/combat-v6/breakthrough/host'; -import { hasActiveConditionStatus } from '@shared/lib/condition'; -import type { CultivatorCondition } from '@shared/types/condition'; -import type { TaskInstanceMetadata } from '@shared/types/task'; +} from '@daoyou/shared/engine/combat-v6/breakthrough/host'; +import { hasActiveConditionStatus } from '@daoyou/shared/lib/condition'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; +import type { TaskInstanceMetadata } from '@daoyou/shared/types/task'; import { eq } from 'drizzle-orm'; import { randomInt, randomUUID } from 'node:crypto'; import { ConditionService } from '../ConditionService'; diff --git a/src/server/lib/services/combat-v6/CombatV6BuildService.ts b/apps/api/src/lib/services/combat-v6/CombatV6BuildService.ts similarity index 94% rename from src/server/lib/services/combat-v6/CombatV6BuildService.ts rename to apps/api/src/lib/services/combat-v6/CombatV6BuildService.ts index cabe6182..3c09322b 100644 --- a/src/server/lib/services/combat-v6/CombatV6BuildService.ts +++ b/apps/api/src/lib/services/combat-v6/CombatV6BuildService.ts @@ -7,7 +7,7 @@ import { } from '@server/lib/drizzle/schema'; import { readBeastRoster } from '@server/lib/repositories/combatV6BeastRepository'; import { readCustomAutoStrategy } from '@server/lib/repositories/combatV6AutoStrategyRepository'; -import { defaultAutoStrategy } from '@shared/combat-v6/auto-strategy'; +import { defaultAutoStrategy } from '@daoyou/shared/combat-v6/auto-strategy'; import { characterIdentityRow, findActiveSectMembership, @@ -21,20 +21,20 @@ import { ResourceEventCommitter } from '@server/lib/services/ResourceEventCommit import type { SectPathSelectionRequest, SectCombatView, -} from '@shared/contracts/combatV6'; -import { COMBAT_V6_BUILD_ERROR_CODE } from '@shared/contracts/combatV6'; +} from '@daoyou/shared/contracts/combatV6'; +import { COMBAT_V6_BUILD_ERROR_CODE } from '@daoyou/shared/contracts/combatV6'; import { createSectCombatView, createFreshCombatV6MethodLevels, -} from '@shared/engine/combat-v6/build-state'; +} from '@daoyou/shared/engine/combat-v6/build-state'; import { COMBAT_V6_SECT_DEFINITIONS, type CombatV6SectId, -} from '@shared/engine/combat-v6/content'; -import type { CombatV6TrainingPlayerInput } from '@shared/engine/combat-v6/encounter'; -import { projectCharacterToCombatV6 } from '@shared/engine/combat-v6/projection'; -import type { CultivatorCondition } from '@shared/types/condition'; -import type { RealmStage, RealmType } from '@shared/types/constants'; +} from '@daoyou/shared/engine/combat-v6/content'; +import type { CombatV6TrainingPlayerInput } from '@daoyou/shared/engine/combat-v6/encounter'; +import { projectCharacterToCombatV6 } from '@daoyou/shared/engine/combat-v6/projection'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; import { and, eq, isNull, sql } from 'drizzle-orm'; import { assertCombatV6MutationAllowed } from './CombatV6MutationGuard'; diff --git a/src/server/lib/services/combat-v6/CombatV6CharacterResourceRefresh.ts b/apps/api/src/lib/services/combat-v6/CombatV6CharacterResourceRefresh.ts similarity index 84% rename from src/server/lib/services/combat-v6/CombatV6CharacterResourceRefresh.ts rename to apps/api/src/lib/services/combat-v6/CombatV6CharacterResourceRefresh.ts index 0e83e857..b28a7cf9 100644 --- a/src/server/lib/services/combat-v6/CombatV6CharacterResourceRefresh.ts +++ b/apps/api/src/lib/services/combat-v6/CombatV6CharacterResourceRefresh.ts @@ -1,8 +1,8 @@ import type { DbTransaction } from '@server/lib/drizzle/db'; import { cultivators } from '@server/lib/drizzle/schema'; -import { rebaseCharacterResources } from '@shared/lib/cultivatorDisplay'; -import { evaluateFateContext } from '@shared/lib/fates'; -import type { CultivatorCondition } from '@shared/types/condition'; +import { rebaseCharacterResources } from '@daoyou/shared/lib/cultivatorDisplay'; +import { evaluateFateContext } from '@daoyou/shared/lib/fates'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; import { eq } from 'drizzle-orm'; import { getCultivatorPreHeavenFates } from '../cultivator/CultivatorProfileRepository'; import { readCombatV6ConditionAuthority } from './CombatV6ConditionAuthority'; diff --git a/src/server/lib/services/combat-v6/CombatV6ConditionAuthority.ts b/apps/api/src/lib/services/combat-v6/CombatV6ConditionAuthority.ts similarity index 88% rename from src/server/lib/services/combat-v6/CombatV6ConditionAuthority.ts rename to apps/api/src/lib/services/combat-v6/CombatV6ConditionAuthority.ts index e58e3238..2239ce57 100644 --- a/src/server/lib/services/combat-v6/CombatV6ConditionAuthority.ts +++ b/apps/api/src/lib/services/combat-v6/CombatV6ConditionAuthority.ts @@ -7,14 +7,14 @@ import { hasActiveDungeon } from '@server/lib/dungeon/occupancy'; import { characterIdentityRow, } from '@server/lib/repositories/sectCombatRepository'; -import { projectCharacterDisplaySnapshot } from '@shared/lib/cultivatorDisplay'; -import type { CultivatorCondition } from '@shared/types/condition'; -import type { RealmStage, RealmType } from '@shared/types/constants'; +import { projectCharacterDisplaySnapshot } from '@daoyou/shared/lib/cultivatorDisplay'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; import { CombatV6WildStore } from './CombatV6WildStore'; import { activeSectTaskBattle } from './CombatV6SectTaskOccupancy'; import { activeBreakthroughBattle } from './CombatV6BreakthroughOccupancy'; -import { SectTaskRecordPayloadSchema } from '@shared/engine/sect'; -import { SectV6TargetSchema } from '@shared/contracts/combatV6SectTask'; +import { SectTaskRecordPayloadSchema } from '@daoyou/shared/engine/sect'; +import { SectV6TargetSchema } from '@daoyou/shared/contracts/combatV6SectTask'; /** Read-model annotation only. Never persisted into cultivators.condition. */ export async function readCombatV6ConditionAuthority( diff --git a/src/server/lib/services/combat-v6/CombatV6ConditionProjector.ts b/apps/api/src/lib/services/combat-v6/CombatV6ConditionProjector.ts similarity index 96% rename from src/server/lib/services/combat-v6/CombatV6ConditionProjector.ts rename to apps/api/src/lib/services/combat-v6/CombatV6ConditionProjector.ts index 93805d9b..562424eb 100644 --- a/src/server/lib/services/combat-v6/CombatV6ConditionProjector.ts +++ b/apps/api/src/lib/services/combat-v6/CombatV6ConditionProjector.ts @@ -12,10 +12,10 @@ import { COMBAT_V6_CONDITION_CONSUMER, } from '@server/lib/repositories/messageConsumptionRepository'; import { lockCultivatorForStateMutation } from '@server/lib/repositories/playerStateRepository'; -import { beastTradePreview } from '@shared/contracts/beastTrade'; -import { settleWildResources } from '@shared/engine/combat-v6/wild/rules'; -import { storyMarkForSignal } from '@shared/story/signals'; -import type { CultivatorCondition } from '@shared/types/condition'; +import { beastTradePreview } from '@daoyou/shared/contracts/beastTrade'; +import { settleWildResources } from '@daoyou/shared/engine/combat-v6/wild/rules'; +import { storyMarkForSignal } from '@daoyou/shared/story/signals'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; import { and, eq } from 'drizzle-orm'; import { ConditionService } from '../ConditionService'; import { grantInventory } from '../InventoryService'; diff --git a/src/server/lib/services/combat-v6/CombatV6ManualService.ts b/apps/api/src/lib/services/combat-v6/CombatV6ManualService.ts similarity index 95% rename from src/server/lib/services/combat-v6/CombatV6ManualService.ts rename to apps/api/src/lib/services/combat-v6/CombatV6ManualService.ts index 6d370c9e..ab8aa414 100644 --- a/src/server/lib/services/combat-v6/CombatV6ManualService.ts +++ b/apps/api/src/lib/services/combat-v6/CombatV6ManualService.ts @@ -15,14 +15,14 @@ import { getOrInitCultivationProgress, stripExpCapForStorage, syncBottleneckState, -} from '@server/utils/cultivationUtils'; +} from '@daoyou/shared/engine/cultivation/cultivationUtils'; import type { ManualAction, ManualView, -} from '@shared/contracts/combatV6Manuals'; -import { manualJadeCost, previewManualAction } from '@shared/manuals/action'; -import type { RealmStage, RealmType } from '@shared/types/constants'; -import type { CultivationProgress } from '@shared/types/cultivator'; +} from '@daoyou/shared/contracts/combatV6Manuals'; +import { manualJadeCost, previewManualAction } from '@daoyou/shared/manuals/action'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; +import type { CultivationProgress } from '@daoyou/shared/types/cultivator'; import { and, eq } from 'drizzle-orm'; import { assertInventoryIdle, diff --git a/src/server/lib/services/combat-v6/CombatV6MembershipBoundary.ts b/apps/api/src/lib/services/combat-v6/CombatV6MembershipBoundary.ts similarity index 100% rename from src/server/lib/services/combat-v6/CombatV6MembershipBoundary.ts rename to apps/api/src/lib/services/combat-v6/CombatV6MembershipBoundary.ts diff --git a/src/server/lib/services/combat-v6/CombatV6MutationGuard.ts b/apps/api/src/lib/services/combat-v6/CombatV6MutationGuard.ts similarity index 100% rename from src/server/lib/services/combat-v6/CombatV6MutationGuard.ts rename to apps/api/src/lib/services/combat-v6/CombatV6MutationGuard.ts diff --git a/src/server/lib/services/combat-v6/CombatV6RankingService.ts b/apps/api/src/lib/services/combat-v6/CombatV6RankingService.ts similarity index 98% rename from src/server/lib/services/combat-v6/CombatV6RankingService.ts rename to apps/api/src/lib/services/combat-v6/CombatV6RankingService.ts index 7f764ee5..77ac686f 100644 --- a/src/server/lib/services/combat-v6/CombatV6RankingService.ts +++ b/apps/api/src/lib/services/combat-v6/CombatV6RankingService.ts @@ -29,16 +29,16 @@ import { MAX_DAILY_RANKING_CHALLENGES, rankingDay, rankingOrderAfterBattle, -} from '@shared/combat-v6/ranking'; -import { createCombatV6Replay } from '@shared/combat-v6/replay'; +} from '@daoyou/shared/combat-v6/ranking'; +import { createCombatV6Replay } from '@daoyou/shared/combat-v6/replay'; import type { RankingChallengeRequest, RankingChallengeResult, -} from '@shared/contracts/combatV6Ranking'; +} from '@daoyou/shared/contracts/combatV6Ranking'; import { compileRankingBattle, simulateRankingBattle, -} from '@shared/engine/combat-v6/ranking/battle'; +} from '@daoyou/shared/engine/combat-v6/ranking/battle'; import { and, eq } from 'drizzle-orm'; import { assertInventoryIdle } from '../InventoryService'; import { assembleCombatV6TrainingPlayer } from './CombatV6BuildService'; diff --git a/src/server/lib/services/combat-v6/CombatV6RuntimeStore.ts b/apps/api/src/lib/services/combat-v6/CombatV6RuntimeStore.ts similarity index 99% rename from src/server/lib/services/combat-v6/CombatV6RuntimeStore.ts rename to apps/api/src/lib/services/combat-v6/CombatV6RuntimeStore.ts index 709eb742..2bb38b42 100644 --- a/src/server/lib/services/combat-v6/CombatV6RuntimeStore.ts +++ b/apps/api/src/lib/services/combat-v6/CombatV6RuntimeStore.ts @@ -4,7 +4,7 @@ import { parseCombatV6Runtime, type CombatV6BattleFinishedRecordV1, type CombatV6RedisRuntimeV1, -} from '@shared/contracts/combatV6Runtime'; +} from '@daoyou/shared/contracts/combatV6Runtime'; const PREFIX = 'combat:v6'; const OUTBOX_TTL_SECONDS = 24 * 60 * 60; diff --git a/src/server/lib/services/combat-v6/CombatV6SectService.ts b/apps/api/src/lib/services/combat-v6/CombatV6SectService.ts similarity index 93% rename from src/server/lib/services/combat-v6/CombatV6SectService.ts rename to apps/api/src/lib/services/combat-v6/CombatV6SectService.ts index 58ce98c5..d4221406 100644 --- a/src/server/lib/services/combat-v6/CombatV6SectService.ts +++ b/apps/api/src/lib/services/combat-v6/CombatV6SectService.ts @@ -15,11 +15,11 @@ import { loadSectCultivatorProgress, spendTrainingResources, } from '@server/lib/repositories/sectRepository'; -import type { SectV6Action, SectV6View } from '@shared/contracts/combatV6Sect'; -import { COMBAT_V6_SECT_DEFINITIONS } from '@shared/engine/combat-v6/content'; -import { combatCharacterLevel } from '@shared/engine/combat-v6/projection/character-level'; -import { sectV6Change } from '@shared/engine/combat-v6/sect-progression'; -import type { RealmStage, RealmType } from '@shared/types/constants'; +import type { SectV6Action, SectV6View } from '@daoyou/shared/contracts/combatV6Sect'; +import { COMBAT_V6_SECT_DEFINITIONS } from '@daoyou/shared/engine/combat-v6/content'; +import { combatCharacterLevel } from '@daoyou/shared/engine/combat-v6/projection/character-level'; +import { sectV6Change } from '@daoyou/shared/engine/combat-v6/sect-progression'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; import { and, eq } from 'drizzle-orm'; import { assertInventoryIdle, InventoryError } from '../InventoryService'; import { ResourceEventCommitter } from '../ResourceEventCommitter'; diff --git a/src/server/lib/services/combat-v6/CombatV6SectTaskOccupancy.ts b/apps/api/src/lib/services/combat-v6/CombatV6SectTaskOccupancy.ts similarity index 100% rename from src/server/lib/services/combat-v6/CombatV6SectTaskOccupancy.ts rename to apps/api/src/lib/services/combat-v6/CombatV6SectTaskOccupancy.ts diff --git a/src/server/lib/services/combat-v6/CombatV6SectTaskService.ts b/apps/api/src/lib/services/combat-v6/CombatV6SectTaskService.ts similarity index 94% rename from src/server/lib/services/combat-v6/CombatV6SectTaskService.ts rename to apps/api/src/lib/services/combat-v6/CombatV6SectTaskService.ts index 018cad31..a9746bf0 100644 --- a/src/server/lib/services/combat-v6/CombatV6SectTaskService.ts +++ b/apps/api/src/lib/services/combat-v6/CombatV6SectTaskService.ts @@ -1,5 +1,5 @@ import { runJournalSettlement, describeJournal } from '../JournalSettlement'; -import { publicUnitAppearances } from '@shared/combat-v6/unit-appearance'; +import { publicUnitAppearances } from '@daoyou/shared/combat-v6/unit-appearance'; import { isNotNull } from 'drizzle-orm'; import { db, type DbTransaction } from '@server/lib/drizzle/db'; import { @@ -15,35 +15,35 @@ import { redisLockKeys, withRedisLock } from '@server/lib/redis/lock'; import { settleBeastDeaths } from '@server/lib/repositories/combatV6BeastRepository'; import { archiveCombatV6Replay } from '@server/lib/repositories/combatV6ReplayRepository'; import { lockCultivatorForStateMutation } from '@server/lib/repositories/playerStateRepository'; -import { automaticCommands } from '@shared/combat-v6/auto'; +import { automaticCommands } from '@daoyou/shared/combat-v6/auto'; import { combatV6Display, combatV6DisplayEvent, combatV6Units, visibleUnitNames, -} from '@shared/combat-v6/presentation'; -import { createCombatV6Replay } from '@shared/combat-v6/replay'; -import { liveReplayDelta } from '@shared/combat-v6/replay-timeline'; -import type { CombatV6CommandGroup } from '@shared/contracts/combatV6'; +} from '@daoyou/shared/combat-v6/presentation'; +import { createCombatV6Replay } from '@daoyou/shared/combat-v6/replay'; +import { liveReplayDelta } from '@daoyou/shared/combat-v6/replay-timeline'; +import type { CombatV6CommandGroup } from '@daoyou/shared/contracts/combatV6'; import { SectV6TargetSchema, type SectTaskBattleRuntime, type SectTaskSessionView, -} from '@shared/contracts/combatV6SectTask'; -import { beastDeathIds } from '@shared/engine/combat-v6/beasts'; -import { combatCharacterLevel } from '@shared/engine/combat-v6/projection/character-level'; +} from '@daoyou/shared/contracts/combatV6SectTask'; +import { beastDeathIds } from '@daoyou/shared/engine/combat-v6/beasts'; +import { combatCharacterLevel } from '@daoyou/shared/engine/combat-v6/projection/character-level'; import { createSectBattleHost, freezeSectBattleOpponent, freezeSectNpcOpponent, SectBattleHost, -} from '@shared/engine/combat-v6/sect/host'; +} from '@daoyou/shared/engine/combat-v6/sect/host'; import { resolveSectBattleTargetRealmCandidates, SectTaskRecordPayloadSchema, -} from '@shared/engine/sect'; -import { productionSectRuntime } from '@shared/engine/sect/content'; -import type { CultivatorCondition } from '@shared/types/condition'; +} from '@daoyou/shared/engine/sect'; +import { productionSectRuntime } from '@daoyou/shared/engine/sect/content'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; import { and, eq, inArray, ne, sql } from 'drizzle-orm'; import { randomInt, randomUUID } from 'node:crypto'; import { ConditionService } from '../ConditionService'; @@ -392,7 +392,7 @@ export async function changeSectTaskBattle( .tasks.get(row.taskId); if (!definition) invalidSectTask('任务定义缺失'); describeJournal(tx, actor.cultivatorId, definition.presentation.title); - let changes: import('@shared/contracts/resources').ResourceChangeDescriptor[] = + let changes: import('@daoyou/shared/contracts/resources').ResourceChangeDescriptor[] = []; if (host.trace().outcome === 'victory') { const fulfilled = await fulfillSectV6Task({ diff --git a/src/server/lib/services/combat-v6/CombatV6SectTransfer.ts b/apps/api/src/lib/services/combat-v6/CombatV6SectTransfer.ts similarity index 95% rename from src/server/lib/services/combat-v6/CombatV6SectTransfer.ts rename to apps/api/src/lib/services/combat-v6/CombatV6SectTransfer.ts index 60af4b65..a8f95b09 100644 --- a/src/server/lib/services/combat-v6/CombatV6SectTransfer.ts +++ b/apps/api/src/lib/services/combat-v6/CombatV6SectTransfer.ts @@ -12,12 +12,12 @@ import { import { createEmptySectCombatProgressV6, createFreshCombatV6MethodLevels, -} from '@shared/engine/combat-v6/build-state'; +} from '@daoyou/shared/engine/combat-v6/build-state'; import { COMBAT_V6_SECT_DEFINITIONS, type CombatV6SectId, -} from '@shared/engine/combat-v6/content'; -import { transferSectProgress } from '@shared/engine/combat-v6/sect-progression'; +} from '@daoyou/shared/engine/combat-v6/content'; +import { transferSectProgress } from '@daoyou/shared/engine/combat-v6/sect-progression'; import { eq } from 'drizzle-orm'; import { InventoryError } from '../InventoryService'; diff --git a/src/server/lib/services/combat-v6/CombatV6TrainingSessionService.ts b/apps/api/src/lib/services/combat-v6/CombatV6TrainingSessionService.ts similarity index 96% rename from src/server/lib/services/combat-v6/CombatV6TrainingSessionService.ts rename to apps/api/src/lib/services/combat-v6/CombatV6TrainingSessionService.ts index e4a26098..de9cbee8 100644 --- a/src/server/lib/services/combat-v6/CombatV6TrainingSessionService.ts +++ b/apps/api/src/lib/services/combat-v6/CombatV6TrainingSessionService.ts @@ -1,33 +1,33 @@ -import { publicUnitAppearances } from '@shared/combat-v6/unit-appearance'; +import { publicUnitAppearances } from '@daoyou/shared/combat-v6/unit-appearance'; import { db } from '@server/lib/drizzle/db'; import { redisLockKeys, withRedisLock } from '@server/lib/redis/lock'; import { findActiveSectMembership } from '@server/lib/repositories/sectCombatRepository'; -import { automaticCommands } from '@shared/combat-v6/auto'; +import { automaticCommands } from '@daoyou/shared/combat-v6/auto'; import { combatV6Display, combatV6DisplayEvent, combatV6Playback, combatV6Units, visibleUnitNames, -} from '@shared/combat-v6/presentation'; -import { liveReplayDelta } from '@shared/combat-v6/replay-timeline'; +} from '@daoyou/shared/combat-v6/presentation'; +import { liveReplayDelta } from '@daoyou/shared/combat-v6/replay-timeline'; import { COMBAT_V6_TRAINING_ERROR_CODE, type CombatV6TrainingCommandV1, type CombatV6TrainingSessionViewV1, -} from '@shared/contracts/combatV6'; +} from '@daoyou/shared/contracts/combatV6'; import { CombatV6BattleFinishedRecordV1Schema, type CombatV6BattleFinishedRecordV1, type CombatV6RedisRuntimeV1, type CombatV6TerminalOutboxV1, type CombatV6TerminalReason, -} from '@shared/contracts/combatV6Runtime'; +} from '@daoyou/shared/contracts/combatV6Runtime'; import { DOMAIN_EVENT_DEFINITIONS, parseDomainEventEnvelope, type DomainEventEnvelope, -} from '@shared/contracts/domainEvents'; +} from '@daoyou/shared/contracts/domainEvents'; import { COMBAT_V6_TRAINING_ENCOUNTERS_V1, createCombatV6TrainingHostV1, @@ -36,7 +36,7 @@ import { type CombatV6TrainingHostV1, type CombatV6TrainingTierV1, type TrainingEncounterOutcome, -} from '@shared/engine/combat-v6/encounter'; +} from '@daoyou/shared/engine/combat-v6/encounter'; import { randomInt, randomUUID } from 'node:crypto'; import { hasActiveCombat } from './CombatOccupancy'; import { @@ -184,7 +184,7 @@ export class CombatV6TrainingSessionService { battleId: string, expectedRevision: number, unitId: string, - commands: import('@shared/contracts/combatV6').CombatV6CommandGroup, + commands: import('@daoyou/shared/contracts/combatV6').CombatV6CommandGroup, ) { const runtime = await this.require(actor, battleId); this.assertRevision(runtime, expectedRevision); diff --git a/src/server/lib/services/combat-v6/CombatV6WildSessionService.ts b/apps/api/src/lib/services/combat-v6/CombatV6WildSessionService.ts similarity index 93% rename from src/server/lib/services/combat-v6/CombatV6WildSessionService.ts rename to apps/api/src/lib/services/combat-v6/CombatV6WildSessionService.ts index dcd38d70..84b1539c 100644 --- a/src/server/lib/services/combat-v6/CombatV6WildSessionService.ts +++ b/apps/api/src/lib/services/combat-v6/CombatV6WildSessionService.ts @@ -9,50 +9,50 @@ import { import { lockCultivatorForStateMutation } from '@server/lib/repositories/playerStateRepository'; import { findActiveSectMembership } from '@server/lib/repositories/sectCombatRepository'; -import { automaticCommands } from '@shared/combat-v6/auto'; +import { automaticCommands } from '@daoyou/shared/combat-v6/auto'; import { combatV6Display, combatV6DisplayEvent, combatV6Playback, combatV6Units, visibleUnitNames, -} from '@shared/combat-v6/presentation'; -import { liveReplayDelta } from '@shared/combat-v6/replay-timeline'; -import { publicUnitAppearances } from '@shared/combat-v6/unit-appearance'; -import { QI_ACTION_COSTS } from '@shared/config/qiSystem'; -import type { CombatV6TrainingCommandV1 } from '@shared/contracts/combatV6'; +} from '@daoyou/shared/combat-v6/presentation'; +import { liveReplayDelta } from '@daoyou/shared/combat-v6/replay-timeline'; +import { publicUnitAppearances } from '@daoyou/shared/combat-v6/unit-appearance'; +import { QI_ACTION_COSTS } from '@daoyou/shared/config/qiSystem'; +import type { CombatV6TrainingCommandV1 } from '@daoyou/shared/contracts/combatV6'; import type { CombatV6TerminalOutboxV1, CombatV6TerminalReason, -} from '@shared/contracts/combatV6Runtime'; +} from '@daoyou/shared/contracts/combatV6Runtime'; import type { WildRuntime, WildSessionView, WildSettlement, -} from '@shared/contracts/combatV6Wild'; +} from '@daoyou/shared/contracts/combatV6Wild'; import { wildEncounterView, type WildEncounter, type WildRegionView, -} from '@shared/contracts/combatV6Wild'; -import { DOMAIN_EVENT_DEFINITIONS } from '@shared/contracts/domainEvents'; -import { beastDeathIds } from '@shared/engine/combat-v6/beasts'; -import { SeededRng } from '@shared/engine/combat-v6/core'; -import { projectCharacterToCombatV6 } from '@shared/engine/combat-v6/projection'; +} from '@daoyou/shared/contracts/combatV6Wild'; +import { DOMAIN_EVENT_DEFINITIONS } from '@daoyou/shared/contracts/domainEvents'; +import { beastDeathIds } from '@daoyou/shared/engine/combat-v6/beasts'; +import { SeededRng } from '@daoyou/shared/engine/combat-v6/core'; +import { projectCharacterToCombatV6 } from '@daoyou/shared/engine/combat-v6/projection'; import { getWildRegion, WILD_CONTENT_VERSION, -} from '@shared/engine/combat-v6/wild/content'; +} from '@daoyou/shared/engine/combat-v6/wild/content'; import { generateWildEncounter, generateWildIndividual, -} from '@shared/engine/combat-v6/wild/generator'; -import { createWildHost, WildHost } from '@shared/engine/combat-v6/wild/host'; -import { WILD_EXPLORATION_COOLDOWN_MS } from '@shared/engine/combat-v6/wild/rules'; -import { evaluateFateContext } from '@shared/lib/fates'; -import { WILD_DROP_POOLS, wildItemRewards } from '@shared/rewards/wild'; +} from '@daoyou/shared/engine/combat-v6/wild/generator'; +import { createWildHost, WildHost } from '@daoyou/shared/engine/combat-v6/wild/host'; +import { WILD_EXPLORATION_COOLDOWN_MS } from '@daoyou/shared/engine/combat-v6/wild/rules'; +import { evaluateFateContext } from '@daoyou/shared/lib/fates'; +import { WILD_DROP_POOLS, wildItemRewards } from '@daoyou/shared/rewards/wild'; -import { REALM_ORDER } from '@shared/types/constants'; +import { REALM_ORDER } from '@daoyou/shared/types/constants'; import { eq } from 'drizzle-orm'; import { createHash, randomInt, randomUUID } from 'node:crypto'; import { playerCommandExecutor } from '../CommandExecutors'; @@ -516,7 +516,7 @@ export class CombatV6WildSessionService { id: string, expected: number, unitId: string, - commands: import('@shared/contracts/combatV6').CombatV6CommandGroup, + commands: import('@daoyou/shared/contracts/combatV6').CombatV6CommandGroup, ) { return this.change(actor, id, expected, (host) => { if (unitId !== host.playerId) diff --git a/src/server/lib/services/combat-v6/CombatV6WildStore.ts b/apps/api/src/lib/services/combat-v6/CombatV6WildStore.ts similarity index 98% rename from src/server/lib/services/combat-v6/CombatV6WildStore.ts rename to apps/api/src/lib/services/combat-v6/CombatV6WildStore.ts index 6a72439c..5f2a81ea 100644 --- a/src/server/lib/services/combat-v6/CombatV6WildStore.ts +++ b/apps/api/src/lib/services/combat-v6/CombatV6WildStore.ts @@ -2,13 +2,13 @@ import { redis } from '@server/lib/redis'; import type { CombatV6ReplayV1, CombatV6TerminalOutboxV1, -} from '@shared/contracts/combatV6Runtime'; +} from '@daoyou/shared/contracts/combatV6Runtime'; import { WildRuntimeSchema, WildSettlementSchema, type WildRuntime, type WildSettlement, -} from '@shared/contracts/combatV6Wild'; +} from '@daoyou/shared/contracts/combatV6Wild'; const root = 'combat:v6'; const run = (id: string) => `${root}:runtime:${id}`; diff --git a/src/server/lib/services/consumablePersistence.ts b/apps/api/src/lib/services/consumablePersistence.ts similarity index 82% rename from src/server/lib/services/consumablePersistence.ts rename to apps/api/src/lib/services/consumablePersistence.ts index 9dd655b3..cafd7fdf 100644 --- a/src/server/lib/services/consumablePersistence.ts +++ b/apps/api/src/lib/services/consumablePersistence.ts @@ -1,7 +1,7 @@ import * as schema from '@server/lib/drizzle/schema'; -import { assertConsumableSpec, stableSerializeConsumableSpec } from '@shared/lib/consumables'; -import type { Consumable } from '@shared/types/cultivator'; -import type { ConsumableType, Quality } from '@shared/types/constants'; +import { assertConsumableSpec, stableSerializeConsumableSpec } from '@daoyou/shared/lib/consumables'; +import type { Consumable } from '@daoyou/shared/types/cultivator'; +import type { ConsumableType, Quality } from '@daoyou/shared/types/constants'; export type ConsumableRow = typeof schema.consumables.$inferSelect; diff --git a/src/server/lib/services/creationProductArtifactSupport.ts b/apps/api/src/lib/services/creationProductArtifactSupport.ts similarity index 89% rename from src/server/lib/services/creationProductArtifactSupport.ts rename to apps/api/src/lib/services/creationProductArtifactSupport.ts index f9b759f2..6c9900ff 100644 --- a/src/server/lib/services/creationProductArtifactSupport.ts +++ b/apps/api/src/lib/services/creationProductArtifactSupport.ts @@ -1,6 +1,6 @@ import type { CreationProductRecord } from '@server/lib/repositories/creationProductRepository'; -import { legacyModifiers, legacyRecord } from '@shared/legacy/products'; -import type { Artifact } from '@shared/types/cultivator'; +import { legacyModifiers, legacyRecord } from '@daoyou/shared/legacy/products'; +import type { Artifact } from '@daoyou/shared/types/cultivator'; function safeRecordJson(value: unknown): Record { return legacyRecord(value); diff --git a/src/server/lib/services/cultivator/CultivatorCombatProjectionReader.ts b/apps/api/src/lib/services/cultivator/CultivatorCombatProjectionReader.ts similarity index 88% rename from src/server/lib/services/cultivator/CultivatorCombatProjectionReader.ts rename to apps/api/src/lib/services/cultivator/CultivatorCombatProjectionReader.ts index 19bc0a5f..e18b3829 100644 --- a/src/server/lib/services/cultivator/CultivatorCombatProjectionReader.ts +++ b/apps/api/src/lib/services/cultivator/CultivatorCombatProjectionReader.ts @@ -8,9 +8,9 @@ import { characterIdentityRow, } from '@server/lib/repositories/sectCombatRepository'; import { findCultivatorOwnerStatusById } from '@server/lib/repositories/cultivatorRepository'; -import { publicCombatV6Build } from '@shared/combat-v6/public-build'; -import type { CultivatorInspectionData } from '@shared/contracts/player'; -import type { CultivatorCondition } from '@shared/types/condition'; +import { publicCombatV6Build } from '@daoyou/shared/combat-v6/public-build'; +import type { CultivatorInspectionData } from '@daoyou/shared/contracts/player'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; import { getPlayerIdentityCultivatorById } from './CultivatorProfileRepository'; export async function loadCultivatorInspectionData( diff --git a/src/server/lib/services/cultivator/CultivatorConditionFactsReader.ts b/apps/api/src/lib/services/cultivator/CultivatorConditionFactsReader.ts similarity index 94% rename from src/server/lib/services/cultivator/CultivatorConditionFactsReader.ts rename to apps/api/src/lib/services/cultivator/CultivatorConditionFactsReader.ts index 0db393ac..35974bd3 100644 --- a/src/server/lib/services/cultivator/CultivatorConditionFactsReader.ts +++ b/apps/api/src/lib/services/cultivator/CultivatorConditionFactsReader.ts @@ -7,11 +7,11 @@ import { import * as schema from '@server/lib/drizzle/schema'; import { hasActiveDungeon } from '@server/lib/dungeon/occupancy'; import { loadCultivatorSectState } from '@server/lib/repositories/sectRepository'; -import { getOrInitCultivationProgress } from '@server/utils/cultivationUtils'; -import type { CultivatorDisplayInput } from '@shared/lib/cultivatorDisplay'; -import type { CultivatorCondition } from '@shared/types/condition'; -import type { RealmStage, RealmType } from '@shared/types/constants'; -import type { CultivationProgress, Cultivator } from '@shared/types/cultivator'; +import { getOrInitCultivationProgress } from '@daoyou/shared/engine/cultivation/cultivationUtils'; +import type { CultivatorDisplayInput } from '@daoyou/shared/lib/cultivatorDisplay'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; +import type { CultivationProgress, Cultivator } from '@daoyou/shared/types/cultivator'; import { and, eq } from 'drizzle-orm'; import { readCombatV6ConditionAuthority } from '../combat-v6/CombatV6ConditionAuthority'; import { diff --git a/src/server/lib/services/cultivator/CultivatorFactsReader.ts b/apps/api/src/lib/services/cultivator/CultivatorFactsReader.ts similarity index 95% rename from src/server/lib/services/cultivator/CultivatorFactsReader.ts rename to apps/api/src/lib/services/cultivator/CultivatorFactsReader.ts index fbaee8ba..655be651 100644 --- a/src/server/lib/services/cultivator/CultivatorFactsReader.ts +++ b/apps/api/src/lib/services/cultivator/CultivatorFactsReader.ts @@ -7,9 +7,9 @@ import { cultivators, spiritualRoots } from '@server/lib/drizzle/schema'; import type { CultivationProgress, Cultivator, -} from '@shared/types/cultivator'; -import type { CultivatorCondition } from '@shared/types/condition'; -import type { RealmStage, RealmType } from '@shared/types/constants'; +} from '@daoyou/shared/types/cultivator'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; import { and, eq } from 'drizzle-orm'; import { mapSpiritualRoots } from './CultivatorProfileRepository'; diff --git a/src/server/lib/services/cultivator/CultivatorInventoryRepository.ts b/apps/api/src/lib/services/cultivator/CultivatorInventoryRepository.ts similarity index 98% rename from src/server/lib/services/cultivator/CultivatorInventoryRepository.ts rename to apps/api/src/lib/services/cultivator/CultivatorInventoryRepository.ts index bd89cac8..d8ad2c69 100644 --- a/src/server/lib/services/cultivator/CultivatorInventoryRepository.ts +++ b/apps/api/src/lib/services/cultivator/CultivatorInventoryRepository.ts @@ -1,20 +1,20 @@ import * as creationProductRepository from '@server/lib/repositories/creationProductRepository'; import { calculateSingleElixirScore } from '@server/utils/rankingUtils'; -import { legacyProductForGrant } from '@shared/legacy/products'; -import { buildConsumableStackKey } from '@shared/lib/consumables'; +import { legacyProductForGrant } from '@daoyou/shared/legacy/products'; +import { buildConsumableStackKey } from '@daoyou/shared/lib/consumables'; import { ELEMENT_VALUES, ElementType, MaterialType, Quality, QUALITY_ORDER, -} from '@shared/types/constants'; +} from '@daoyou/shared/types/constants'; import type { Artifact, Consumable, Cultivator, Material, -} from '@shared/types/cultivator'; +} from '@daoyou/shared/types/cultivator'; import { and, asc, diff --git a/src/server/lib/services/cultivator/CultivatorProfileRepository.ts b/apps/api/src/lib/services/cultivator/CultivatorProfileRepository.ts similarity index 97% rename from src/server/lib/services/cultivator/CultivatorProfileRepository.ts rename to apps/api/src/lib/services/cultivator/CultivatorProfileRepository.ts index f7b83a0e..b7ca7a38 100644 --- a/src/server/lib/services/cultivator/CultivatorProfileRepository.ts +++ b/apps/api/src/lib/services/cultivator/CultivatorProfileRepository.ts @@ -3,11 +3,11 @@ import { hasCultivatorOwnership, hasDeadCultivatorByUserId, } from '@server/lib/repositories/cultivatorRepository'; -import type { PlayerIdentityCultivator } from '@shared/contracts/player'; +import type { PlayerIdentityCultivator } from '@daoyou/shared/contracts/player'; import { clampSpiritualRootEffectiveStrength, SPIRITUAL_ROOT_EFFECTIVE_STRENGTH_CAP, -} from '@shared/lib/marrowWash'; +} from '@daoyou/shared/lib/marrowWash'; import { ElementType, GenderType, @@ -15,8 +15,8 @@ import { RealmStage, RealmType, SpiritualRootGrade, -} from '@shared/types/constants'; -import type { Cultivator, PreHeavenFate } from '@shared/types/cultivator'; +} from '@daoyou/shared/types/constants'; +import type { Cultivator, PreHeavenFate } from '@daoyou/shared/types/cultivator'; import { and, desc, eq, sql } from 'drizzle-orm'; import { db, @@ -26,7 +26,7 @@ import { type DbTransaction, } from '../../drizzle/db'; import * as schema from '../../drizzle/schema'; -import { openingStoryProgress } from '@shared/story/catalog'; +import { openingStoryProgress } from '@daoyou/shared/story/catalog'; import { insertCultivatorStory } from '@server/lib/repositories/storyRepository'; import { ConditionService } from '../ConditionService'; import { FateEngine } from '../FateEngine'; diff --git a/src/server/lib/services/cultivator/CultivatorStateRepository.ts b/apps/api/src/lib/services/cultivator/CultivatorStateRepository.ts similarity index 97% rename from src/server/lib/services/cultivator/CultivatorStateRepository.ts rename to apps/api/src/lib/services/cultivator/CultivatorStateRepository.ts index 941d68dd..09037105 100644 --- a/src/server/lib/services/cultivator/CultivatorStateRepository.ts +++ b/apps/api/src/lib/services/cultivator/CultivatorStateRepository.ts @@ -5,19 +5,19 @@ import { getOrInitCultivationProgress, stripExpCapForStorage, syncBottleneckState, -} from '@server/utils/cultivationUtils'; -import type { CultivatorCondition } from '@shared/types/condition'; -import { COMPREHENSION_INSIGHT_CAP } from '@shared/config/cultivationTuning'; +} from '@daoyou/shared/engine/cultivation/cultivationUtils'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; +import { COMPREHENSION_INSIGHT_CAP } from '@daoyou/shared/config/cultivationTuning'; import { RealmStage, RealmType -} from '@shared/types/constants'; +} from '@daoyou/shared/types/constants'; import type { BreakthroughHistoryEntry, CultivationProgress, Cultivator, RetreatRecord -} from '@shared/types/cultivator'; +} from '@daoyou/shared/types/cultivator'; import { and,eq,sql } from 'drizzle-orm'; import { db, diff --git a/src/server/lib/services/hunts/HuntRewardProjector.ts b/apps/api/src/lib/services/hunts/HuntRewardProjector.ts similarity index 94% rename from src/server/lib/services/hunts/HuntRewardProjector.ts rename to apps/api/src/lib/services/hunts/HuntRewardProjector.ts index 444a7f6c..690b3bc0 100644 --- a/src/server/lib/services/hunts/HuntRewardProjector.ts +++ b/apps/api/src/lib/services/hunts/HuntRewardProjector.ts @@ -10,19 +10,19 @@ import { journalOperationKey, } from '@server/lib/repositories/playerJournalRepository'; import { lockCultivatorForStateMutation } from '@server/lib/repositories/playerStateRepository'; -import { getOrInitCultivationProgress } from '@server/utils/cultivationUtils'; -import type { HuntBattleReward } from '@shared/contracts/hunts'; -import type { JournalChange } from '@shared/contracts/playerJournal'; -import { HUNT_BOSSES } from '@shared/hunts/config'; +import { getOrInitCultivationProgress } from '@daoyou/shared/engine/cultivation/cultivationUtils'; +import type { HuntBattleReward } from '@daoyou/shared/contracts/hunts'; +import type { JournalChange } from '@daoyou/shared/contracts/playerJournal'; +import { HUNT_BOSSES } from '@daoyou/shared/hunts/config'; import { huntParticipantSucceeded, settleHuntResources, -} from '@shared/hunts/settlement'; -import { dungeonRewardItemName } from '@shared/rewards/dungeon'; -import { HuntRewardSnapshotSchema } from '@shared/rewards/hunt'; -import type { CultivatorCondition } from '@shared/types/condition'; -import type { RealmStage, RealmType } from '@shared/types/constants'; -import type { CultivationProgress } from '@shared/types/cultivator'; +} from '@daoyou/shared/hunts/settlement'; +import { dungeonRewardItemName } from '@daoyou/shared/rewards/dungeon'; +import { HuntRewardSnapshotSchema } from '@daoyou/shared/rewards/hunt'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; +import type { CultivationProgress } from '@daoyou/shared/types/cultivator'; import { and, eq } from 'drizzle-orm'; import { z } from 'zod'; import { ArenaV6Error, ownedArenaV6 } from '../combat-v6/CombatV6ArenaService'; diff --git a/src/server/lib/services/hunts/HuntRewardService.ts b/apps/api/src/lib/services/hunts/HuntRewardService.ts similarity index 81% rename from src/server/lib/services/hunts/HuntRewardService.ts rename to apps/api/src/lib/services/hunts/HuntRewardService.ts index cb970518..02aa47e7 100644 --- a/src/server/lib/services/hunts/HuntRewardService.ts +++ b/apps/api/src/lib/services/hunts/HuntRewardService.ts @@ -1,8 +1,8 @@ import type { DbExecutor } from '@server/lib/drizzle/db'; -import type { HuntEvent } from '@shared/hunts/config'; -import { MaterialFactsSchema } from '@shared/items/definitions/materials'; -import { HuntRewardSnapshotSchema, planHuntReward } from '@shared/rewards/hunt'; -import { HUNT_MATERIAL_QUALITY_CHANCE_BY_REALM } from '@shared/rewards/hunt-material-quality'; +import type { HuntEvent } from '@daoyou/shared/hunts/config'; +import { MaterialFactsSchema } from '@daoyou/shared/items/definitions/materials'; +import { HuntRewardSnapshotSchema, planHuntReward } from '@daoyou/shared/rewards/hunt'; +import { HUNT_MATERIAL_QUALITY_CHANCE_BY_REALM } from '@daoyou/shared/rewards/hunt-material-quality'; import { generateRealmMaterials } from '../MaterialRewardService'; import { computeItemLibrarySampleKey } from '../itemLibrarySampleKey'; diff --git a/src/server/lib/services/hunts/HuntTeamService.ts b/apps/api/src/lib/services/hunts/HuntTeamService.ts similarity index 98% rename from src/server/lib/services/hunts/HuntTeamService.ts rename to apps/api/src/lib/services/hunts/HuntTeamService.ts index e10210db..6e535eba 100644 --- a/src/server/lib/services/hunts/HuntTeamService.ts +++ b/apps/api/src/lib/services/hunts/HuntTeamService.ts @@ -6,24 +6,24 @@ import { journalOperationKey } from '@server/lib/repositories/playerJournalRepos import { ARENA_SPARRING_RULES_V1, type ArenaRoomV1, -} from '@shared/contracts/arena'; +} from '@daoyou/shared/contracts/arena'; import type { HuntLobby, HuntMember, HuntTeam, HuntTeamCommand, -} from '@shared/contracts/hunts'; +} from '@daoyou/shared/contracts/hunts'; import { huntEventById, huntIsOpen, type HuntEvent, -} from '@shared/hunts/config'; +} from '@daoyou/shared/hunts/config'; import { huntRealmAllowed, huntStartError, selectHuntTeam, -} from '@shared/hunts/rules'; -import { REALM_VALUES, type RealmType } from '@shared/types/constants'; +} from '@daoyou/shared/hunts/rules'; +import { REALM_VALUES, type RealmType } from '@daoyou/shared/types/constants'; import { and, eq } from 'drizzle-orm'; import { hasActiveCombat } from '../combat-v6/CombatOccupancy'; import { ArenaV6Error, createArenaV6 } from '../combat-v6/CombatV6ArenaService'; diff --git a/src/server/lib/services/hunts/HuntWorldService.ts b/apps/api/src/lib/services/hunts/HuntWorldService.ts similarity index 96% rename from src/server/lib/services/hunts/HuntWorldService.ts rename to apps/api/src/lib/services/hunts/HuntWorldService.ts index d2423e0b..0c1fb7c4 100644 --- a/src/server/lib/services/hunts/HuntWorldService.ts +++ b/apps/api/src/lib/services/hunts/HuntWorldService.ts @@ -1,7 +1,7 @@ import { redis } from '@server/lib/redis'; import { withRedisLock } from '@server/lib/redis/lock'; import { createMessage } from '@server/lib/repositories/worldChatRepository'; -import { HUNT_BOSSES, huntEventsAt } from '@shared/hunts/config'; +import { HUNT_BOSSES, huntEventsAt } from '@daoyou/shared/hunts/config'; import { cleanupHuntTeams } from './HuntTeamService'; export async function refreshHuntWorld() { await withRedisLock( diff --git a/src/server/lib/services/inventoryStackKey.ts b/apps/api/src/lib/services/inventoryStackKey.ts similarity index 84% rename from src/server/lib/services/inventoryStackKey.ts rename to apps/api/src/lib/services/inventoryStackKey.ts index ddbabf65..85e14011 100644 --- a/src/server/lib/services/inventoryStackKey.ts +++ b/apps/api/src/lib/services/inventoryStackKey.ts @@ -1,4 +1,4 @@ -import { inventoryStackIdentity } from '@shared/inventory/stack-key'; +import { inventoryStackIdentity } from '@daoyou/shared/inventory/stack-key'; import { createHash } from 'node:crypto'; export function inventoryStackKey( diff --git a/src/server/lib/services/itemLibrarySampleKey.ts b/apps/api/src/lib/services/itemLibrarySampleKey.ts similarity index 100% rename from src/server/lib/services/itemLibrarySampleKey.ts rename to apps/api/src/lib/services/itemLibrarySampleKey.ts diff --git a/src/server/routes/api/artifact-migration/service.ts b/apps/api/src/lib/services/legacy/ArtifactMigrationService.ts similarity index 96% rename from src/server/routes/api/artifact-migration/service.ts rename to apps/api/src/lib/services/legacy/ArtifactMigrationService.ts index 9963a4d6..25544a51 100644 --- a/src/server/routes/api/artifact-migration/service.ts +++ b/apps/api/src/lib/services/legacy/ArtifactMigrationService.ts @@ -1,6 +1,6 @@ import { db, type DbExecutor } from '@server/lib/drizzle/db'; import { creationProducts, cultivators } from '@server/lib/drizzle/schema'; -import type { ActiveCultivatorRef } from '@server/lib/hono/types'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; import { playerCommandExecutor } from '@server/lib/services/CommandExecutors'; import { updateSpiritStones } from '@server/lib/services/cultivator/CultivatorStateRepository'; import { @@ -12,13 +12,13 @@ import { artifactMigrationPlan, artifactMigrationRealm, drawArtifactBlueprints, -} from '@shared/artifact-migration/rules'; +} from '@daoyou/shared/artifact-migration/rules'; import type { ArtifactMigrationResult, ArtifactMigrationView, ExchangeArtifact, -} from '@shared/contracts/artifactMigration'; -import { generateForgedEquipment } from '@shared/engine/combat-v6/equipment/forging'; +} from '@daoyou/shared/contracts/artifactMigration'; +import { generateForgedEquipment } from '@daoyou/shared/engine/combat-v6/equipment/forging'; import { and, eq } from 'drizzle-orm'; import { randomInt, randomUUID } from 'node:crypto'; diff --git a/src/server/routes/api/manual-migration/service.ts b/apps/api/src/lib/services/legacy/ManualMigrationService.ts similarity index 97% rename from src/server/routes/api/manual-migration/service.ts rename to apps/api/src/lib/services/legacy/ManualMigrationService.ts index c4751dce..e43bab72 100644 --- a/src/server/routes/api/manual-migration/service.ts +++ b/apps/api/src/lib/services/legacy/ManualMigrationService.ts @@ -1,6 +1,6 @@ import { db, type DbExecutor } from '@server/lib/drizzle/db'; import { creationProducts, cultivators } from '@server/lib/drizzle/schema'; -import type { ActiveCultivatorRef } from '@server/lib/hono/types'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; import { readCharacterManuals } from '@server/lib/repositories/characterLoadoutRepository'; import { playerCommandExecutor } from '@server/lib/services/CommandExecutors'; import { @@ -15,14 +15,14 @@ import type { ManualMigrationResult, ManualMigrationSource, ManualMigrationView, -} from '@shared/contracts/manualMigration'; +} from '@daoyou/shared/contracts/manualMigration'; import { buildManualMigrationPolicy, drawLegacyManual, MANUAL_MIGRATION_CONFIG, manualMigrationPlan, validateManualSelection, -} from '@shared/manual-migration/rules'; +} from '@daoyou/shared/manual-migration/rules'; import { and, eq, sql } from 'drizzle-orm'; import { randomInt } from 'node:crypto'; diff --git a/src/server/lib/services/materialDetailsPrivacy.ts b/apps/api/src/lib/services/materialDetailsPrivacy.ts similarity index 94% rename from src/server/lib/services/materialDetailsPrivacy.ts rename to apps/api/src/lib/services/materialDetailsPrivacy.ts index bd577244..f9b65b00 100644 --- a/src/server/lib/services/materialDetailsPrivacy.ts +++ b/apps/api/src/lib/services/materialDetailsPrivacy.ts @@ -1,4 +1,4 @@ -import type { Material } from '@shared/types/cultivator'; +import type { Material } from '@daoyou/shared/types/cultivator'; export const HIDDEN_MYSTERY_REVEAL_KEY = '__serverHiddenMysteryReveal'; diff --git a/src/server/lib/services/materialInventory.ts b/apps/api/src/lib/services/materialInventory.ts similarity index 93% rename from src/server/lib/services/materialInventory.ts rename to apps/api/src/lib/services/materialInventory.ts index 13ffab34..0610ed55 100644 --- a/src/server/lib/services/materialInventory.ts +++ b/apps/api/src/lib/services/materialInventory.ts @@ -1,8 +1,8 @@ import { getExecutor, type DbTransaction } from '@server/lib/drizzle/db'; import { materials } from '@server/lib/drizzle/schema'; -import type { Material } from '@shared/types/cultivator'; +import type { Material } from '@daoyou/shared/types/cultivator'; import { and, asc, eq, sql } from 'drizzle-orm'; -import { readSpiritFieldSeedSpec } from '@shared/engine/spirit-field/seedMaterial'; +import { readSpiritFieldSeedSpec } from '@daoyou/shared/engine/spirit-field/seedMaterial'; export type MaterialInventoryWrite = Pick< Material, diff --git a/src/server/lib/services/natsCorePubSub.ts b/apps/api/src/lib/services/natsCorePubSub.ts similarity index 100% rename from src/server/lib/services/natsCorePubSub.ts rename to apps/api/src/lib/services/natsCorePubSub.ts diff --git a/src/server/lib/services/onlinePresenceService.ts b/apps/api/src/lib/services/onlinePresenceService.ts similarity index 99% rename from src/server/lib/services/onlinePresenceService.ts rename to apps/api/src/lib/services/onlinePresenceService.ts index 037637b3..f9699927 100644 --- a/src/server/lib/services/onlinePresenceService.ts +++ b/apps/api/src/lib/services/onlinePresenceService.ts @@ -3,7 +3,7 @@ import { redis } from '@server/lib/redis'; import { db } from '@server/lib/drizzle/db'; import { cultivators } from '@server/lib/drizzle/schema'; import { getPubSubInstanceId } from '@server/lib/services/pubSubEnvelope'; -import type { AdminOnlineUsersSnapshot } from '@shared/contracts/adminOnlineUsers'; +import type { AdminOnlineUsersSnapshot } from '@daoyou/shared/contracts/adminOnlineUsers'; import { eq } from 'drizzle-orm'; const ONLINE_CULTIVATORS_KEY = 'admin:online:cultivators:v1'; diff --git a/src/server/lib/services/playerStateBroadcaster.ts b/apps/api/src/lib/services/playerStateBroadcaster.ts similarity index 96% rename from src/server/lib/services/playerStateBroadcaster.ts rename to apps/api/src/lib/services/playerStateBroadcaster.ts index 03a91f1f..63cbb4f5 100644 --- a/src/server/lib/services/playerStateBroadcaster.ts +++ b/apps/api/src/lib/services/playerStateBroadcaster.ts @@ -1,8 +1,8 @@ import type { ResourceChange, ResourceScope, -} from '@shared/contracts/resources'; -import { ResourceChangeSchema } from '@shared/contracts/resources'; +} from '@daoyou/shared/contracts/resources'; +import { ResourceChangeSchema } from '@daoyou/shared/contracts/resources'; import { encodeNatsSubjectToken, publishNatsCoreMessage, diff --git a/src/server/lib/services/pubSubEnvelope.ts b/apps/api/src/lib/services/pubSubEnvelope.ts similarity index 100% rename from src/server/lib/services/pubSubEnvelope.ts rename to apps/api/src/lib/services/pubSubEnvelope.ts diff --git a/src/server/lib/services/resource/ResourceEngine.ts b/apps/api/src/lib/services/resource/ResourceEngine.ts similarity index 98% rename from src/server/lib/services/resource/ResourceEngine.ts rename to apps/api/src/lib/services/resource/ResourceEngine.ts index 6c7b20fd..1710b9fa 100644 --- a/src/server/lib/services/resource/ResourceEngine.ts +++ b/apps/api/src/lib/services/resource/ResourceEngine.ts @@ -19,9 +19,9 @@ import type { ResourceOperationResult, ResourceOperationSettlement, ResourceValidationResult, -} from '@shared/engine/resource/types'; -import { getGameConceptLabel } from '@shared/lib/gameConceptDisplay'; -import type { Artifact, Consumable, Material } from '@shared/types/cultivator'; +} from '@daoyou/shared/engine/resource/types'; +import { getGameConceptLabel } from '@daoyou/shared/lib/gameConceptDisplay'; +import type { Artifact, Consumable, Material } from '@daoyou/shared/types/cultivator'; import { and, eq, inArray, sql } from 'drizzle-orm'; function createSettlement(): ResourceOperationSettlement { diff --git a/src/server/lib/services/sect-organization/ClaimSectTaskRewardHandler.ts b/apps/api/src/lib/services/sect-organization/ClaimSectTaskRewardHandler.ts similarity index 97% rename from src/server/lib/services/sect-organization/ClaimSectTaskRewardHandler.ts rename to apps/api/src/lib/services/sect-organization/ClaimSectTaskRewardHandler.ts index 17fc4601..e2dd95a4 100644 --- a/src/server/lib/services/sect-organization/ClaimSectTaskRewardHandler.ts +++ b/apps/api/src/lib/services/sect-organization/ClaimSectTaskRewardHandler.ts @@ -1,12 +1,12 @@ import type { SectTaskActionData, SectTaskRewardReceipt, -} from '@shared/contracts/sect'; +} from '@daoyou/shared/contracts/sect'; import { resolveSectTaskClaimReward, SectTask, type SectTaskDefinition, -} from '@shared/engine/sect'; +} from '@daoyou/shared/engine/sect'; import type { SectCommandEffects } from './SectCommandEffects'; import type { SectDomainEventDispatcherFactory } from './SectDomainEventDispatcher'; import { invalidSectTask } from './SectTaskApplicationSupport'; diff --git a/src/server/lib/services/sect-organization/GetSectTasksQueryHandler.ts b/apps/api/src/lib/services/sect-organization/GetSectTasksQueryHandler.ts similarity index 98% rename from src/server/lib/services/sect-organization/GetSectTasksQueryHandler.ts rename to apps/api/src/lib/services/sect-organization/GetSectTasksQueryHandler.ts index 6d3080cf..0edb9c8e 100644 --- a/src/server/lib/services/sect-organization/GetSectTasksQueryHandler.ts +++ b/apps/api/src/lib/services/sect-organization/GetSectTasksQueryHandler.ts @@ -1,4 +1,4 @@ -import type { SectTasksData, SectTaskViewData } from '@shared/contracts/sect'; +import type { SectTasksData, SectTaskViewData } from '@daoyou/shared/contracts/sect'; import { SectCapabilityAuthorizer } from './SectCapabilityAuthorizer'; import { requireSectMembership, diff --git a/src/server/lib/services/sect-organization/PostgresSectOrganizationAdapters.ts b/apps/api/src/lib/services/sect-organization/PostgresSectOrganizationAdapters.ts similarity index 98% rename from src/server/lib/services/sect-organization/PostgresSectOrganizationAdapters.ts rename to apps/api/src/lib/services/sect-organization/PostgresSectOrganizationAdapters.ts index b924db2c..31c60f5d 100644 --- a/src/server/lib/services/sect-organization/PostgresSectOrganizationAdapters.ts +++ b/apps/api/src/lib/services/sect-organization/PostgresSectOrganizationAdapters.ts @@ -18,12 +18,12 @@ import { type SectRuntime, type SectSubmissionItemFacts, type SectSubmissionItemKind, -} from '@shared/engine/sect'; -import { itemDefinition } from '@shared/inventory'; -import { InventoryEquipmentSchema } from '@shared/inventory/equipment'; -import { ConsumableFactsSchema } from '@shared/items/definitions/consumables'; -import { MaterialFactsSchema } from '@shared/items/definitions/materials'; -import { materialFactsOf } from '@shared/items/material'; +} from '@daoyou/shared/engine/sect'; +import { itemDefinition } from '@daoyou/shared/inventory'; +import { InventoryEquipmentSchema } from '@daoyou/shared/inventory/equipment'; +import { ConsumableFactsSchema } from '@daoyou/shared/items/definitions/consumables'; +import { MaterialFactsSchema } from '@daoyou/shared/items/definitions/materials'; +import { materialFactsOf } from '@daoyou/shared/items/material'; import { and, eq, inArray } from 'drizzle-orm'; import { assertInventoryIdle, diff --git a/src/server/lib/services/sect-organization/SectAdmissionApplicationService.ts b/apps/api/src/lib/services/sect-organization/SectAdmissionApplicationService.ts similarity index 94% rename from src/server/lib/services/sect-organization/SectAdmissionApplicationService.ts rename to apps/api/src/lib/services/sect-organization/SectAdmissionApplicationService.ts index f91a2dcc..78359b06 100644 --- a/src/server/lib/services/sect-organization/SectAdmissionApplicationService.ts +++ b/apps/api/src/lib/services/sect-organization/SectAdmissionApplicationService.ts @@ -1,6 +1,6 @@ -import type { ResourceChangeDescriptor } from '@shared/contracts/resources'; -import type { SectContextData } from '@shared/contracts/sect'; -import type { SectAdmissionContext, SectRuntime } from '@shared/engine/sect'; +import type { ResourceChangeDescriptor } from '@daoyou/shared/contracts/resources'; +import type { SectContextData } from '@daoyou/shared/contracts/sect'; +import type { SectAdmissionContext, SectRuntime } from '@daoyou/shared/engine/sect'; import { SectError } from '../SectError'; import type { SectAdmissionRepository, diff --git a/src/server/lib/services/sect-organization/SectBenefitService.ts b/apps/api/src/lib/services/sect-organization/SectBenefitService.ts similarity index 98% rename from src/server/lib/services/sect-organization/SectBenefitService.ts rename to apps/api/src/lib/services/sect-organization/SectBenefitService.ts index 41892409..46ebefa4 100644 --- a/src/server/lib/services/sect-organization/SectBenefitService.ts +++ b/apps/api/src/lib/services/sect-organization/SectBenefitService.ts @@ -5,7 +5,7 @@ import { type SectCraftContextKey, type SectDiscipleRank, type SectOrganizationModule, -} from '@shared/engine/sect'; +} from '@daoyou/shared/engine/sect'; import type { SectBenefitQueryContext, SectMembershipRecord, diff --git a/src/server/lib/services/sect-organization/SectCapabilityAuthorizer.ts b/apps/api/src/lib/services/sect-organization/SectCapabilityAuthorizer.ts similarity index 96% rename from src/server/lib/services/sect-organization/SectCapabilityAuthorizer.ts rename to apps/api/src/lib/services/sect-organization/SectCapabilityAuthorizer.ts index 9e42d257..c060d90c 100644 --- a/src/server/lib/services/sect-organization/SectCapabilityAuthorizer.ts +++ b/apps/api/src/lib/services/sect-organization/SectCapabilityAuthorizer.ts @@ -3,7 +3,7 @@ import type { SectCapabilityKey, SectDiscipleRank, SectOrganizationModule, -} from '@shared/engine/sect'; +} from '@daoyou/shared/engine/sect'; import { SectError } from '../SectError'; import type { SectMembershipRecord, SectModuleResolver } from './ports'; diff --git a/src/server/lib/services/sect-organization/SectCommandEffects.ts b/apps/api/src/lib/services/sect-organization/SectCommandEffects.ts similarity index 86% rename from src/server/lib/services/sect-organization/SectCommandEffects.ts rename to apps/api/src/lib/services/sect-organization/SectCommandEffects.ts index 08aa641a..fa4e5017 100644 --- a/src/server/lib/services/sect-organization/SectCommandEffects.ts +++ b/apps/api/src/lib/services/sect-organization/SectCommandEffects.ts @@ -1,5 +1,5 @@ -import type { ResourceChangeDescriptor } from '@shared/contracts/resources'; -import type { SectTaskSettlementData } from '@shared/contracts/sect'; +import type { ResourceChangeDescriptor } from '@daoyou/shared/contracts/resources'; +import type { SectTaskSettlementData } from '@daoyou/shared/contracts/sect'; export interface SectCommandEffects { resourceChanges: ResourceChangeDescriptor[]; diff --git a/src/server/lib/services/sect-organization/SectConstructionApplicationService.ts b/apps/api/src/lib/services/sect-organization/SectConstructionApplicationService.ts similarity index 94% rename from src/server/lib/services/sect-organization/SectConstructionApplicationService.ts rename to apps/api/src/lib/services/sect-organization/SectConstructionApplicationService.ts index eb89b518..2306d9af 100644 --- a/src/server/lib/services/sect-organization/SectConstructionApplicationService.ts +++ b/apps/api/src/lib/services/sect-organization/SectConstructionApplicationService.ts @@ -1,7 +1,7 @@ import { getSectConstructionDailyStatus } from '@server/lib/redis/sectConstructionDaily'; -import type { ResourceChangeDescriptor } from '@shared/contracts/resources'; -import type { SectConstructionMemberData } from '@shared/contracts/sect'; -import { quoteSectConstructionDonation } from '@shared/engine/sect'; +import type { ResourceChangeDescriptor } from '@daoyou/shared/contracts/resources'; +import type { SectConstructionMemberData } from '@daoyou/shared/contracts/sect'; +import { quoteSectConstructionDonation } from '@daoyou/shared/engine/sect'; import type { SectBenefitService } from './SectBenefitService'; import { organizationError, diff --git a/src/server/lib/services/sect-organization/SectConstructionCommand.ts b/apps/api/src/lib/services/sect-organization/SectConstructionCommand.ts similarity index 97% rename from src/server/lib/services/sect-organization/SectConstructionCommand.ts rename to apps/api/src/lib/services/sect-organization/SectConstructionCommand.ts index 29f9dac8..82895ca7 100644 --- a/src/server/lib/services/sect-organization/SectConstructionCommand.ts +++ b/apps/api/src/lib/services/sect-organization/SectConstructionCommand.ts @@ -4,7 +4,7 @@ import { reserveSectConstructionDaily, type SectConstructionDailyRecord, } from '@server/lib/redis/sectConstructionDaily'; -import { quoteSectConstructionDonation } from '@shared/engine/sect'; +import { quoteSectConstructionDonation } from '@daoyou/shared/engine/sect'; import { sectOrganizationFacade } from '.'; import { SectError } from '../SectError'; import { diff --git a/src/server/lib/services/sect-organization/SectConstructionSettlementService.ts b/apps/api/src/lib/services/sect-organization/SectConstructionSettlementService.ts similarity index 85% rename from src/server/lib/services/sect-organization/SectConstructionSettlementService.ts rename to apps/api/src/lib/services/sect-organization/SectConstructionSettlementService.ts index e9695924..cf009f55 100644 --- a/src/server/lib/services/sect-organization/SectConstructionSettlementService.ts +++ b/apps/api/src/lib/services/sect-organization/SectConstructionSettlementService.ts @@ -1,10 +1,10 @@ import { type DbTransaction } from '@server/lib/drizzle/db'; import * as organizationRepository from '@server/lib/repositories/sectOrganizationRepository'; -import type { DomainEventEnvelope } from '@shared/contracts/domainEvents'; -import type { ResourceChangeDescriptor } from '@shared/contracts/resources'; -import type { SectInfrastructureData } from '@shared/contracts/sect'; -import { applySectFacilityConstruction } from '@shared/engine/sect'; -import { productionSectRuntime } from '@shared/engine/sect/content'; +import type { DomainEventEnvelope } from '@daoyou/shared/contracts/domainEvents'; +import type { ResourceChangeDescriptor } from '@daoyou/shared/contracts/resources'; +import type { SectInfrastructureData } from '@daoyou/shared/contracts/sect'; +import { applySectFacilityConstruction } from '@daoyou/shared/engine/sect'; +import { productionSectRuntime } from '@daoyou/shared/engine/sect/content'; import { mapFacilities } from './applicationSupport'; export async function projectSectConstructionDonation( diff --git a/src/server/lib/services/sect-organization/SectDomainEventDispatcher.ts b/apps/api/src/lib/services/sect-organization/SectDomainEventDispatcher.ts similarity index 99% rename from src/server/lib/services/sect-organization/SectDomainEventDispatcher.ts rename to apps/api/src/lib/services/sect-organization/SectDomainEventDispatcher.ts index 4fb93879..48c0851e 100644 --- a/src/server/lib/services/sect-organization/SectDomainEventDispatcher.ts +++ b/apps/api/src/lib/services/sect-organization/SectDomainEventDispatcher.ts @@ -2,7 +2,7 @@ import { ContributionBalance, SectTask, type SectDomainEvent, -} from '@shared/engine/sect'; +} from '@daoyou/shared/engine/sect'; import { organizationError } from './applicationSupport'; import type { SectTaskItemRewardGrantStrategyRegistry } from './TaskRewardStrategies'; import type { diff --git a/src/server/lib/services/sect-organization/SectEconomyApplicationService.ts b/apps/api/src/lib/services/sect-organization/SectEconomyApplicationService.ts similarity index 95% rename from src/server/lib/services/sect-organization/SectEconomyApplicationService.ts rename to apps/api/src/lib/services/sect-organization/SectEconomyApplicationService.ts index 1422dace..39f946b3 100644 --- a/src/server/lib/services/sect-organization/SectEconomyApplicationService.ts +++ b/apps/api/src/lib/services/sect-organization/SectEconomyApplicationService.ts @@ -2,9 +2,9 @@ import { buySectShopItem, listSectShopItems, } from '@server/lib/services/SectShopService'; -import type { ResourceChangeDescriptor } from '@shared/contracts/resources'; -import { SectMembership, SectStipendClaim } from '@shared/engine/sect'; -import type { RealmType } from '@shared/types/constants'; +import type { ResourceChangeDescriptor } from '@daoyou/shared/contracts/resources'; +import { SectMembership, SectStipendClaim } from '@daoyou/shared/engine/sect'; +import type { RealmType } from '@daoyou/shared/types/constants'; import { mapFacilities, organizationError, diff --git a/src/server/lib/services/sect-organization/SectEconomyCommand.ts b/apps/api/src/lib/services/sect-organization/SectEconomyCommand.ts similarity index 100% rename from src/server/lib/services/sect-organization/SectEconomyCommand.ts rename to apps/api/src/lib/services/sect-organization/SectEconomyCommand.ts diff --git a/src/server/lib/services/sect-organization/SectMembershipApplicationService.ts b/apps/api/src/lib/services/sect-organization/SectMembershipApplicationService.ts similarity index 97% rename from src/server/lib/services/sect-organization/SectMembershipApplicationService.ts rename to apps/api/src/lib/services/sect-organization/SectMembershipApplicationService.ts index 58f19e97..0fd5be5c 100644 --- a/src/server/lib/services/sect-organization/SectMembershipApplicationService.ts +++ b/apps/api/src/lib/services/sect-organization/SectMembershipApplicationService.ts @@ -1,16 +1,16 @@ -import type { ResourceChangeDescriptor } from '@shared/contracts/resources'; +import type { ResourceChangeDescriptor } from '@daoyou/shared/contracts/resources'; import type { SectMemberData, SectContextData, SectInfrastructureData, SectPromotionEvaluationData, SectStipendData, -} from '@shared/contracts/sect'; +} from '@daoyou/shared/contracts/sect'; import { PromotionRequirementSpecification, SectMembership, -} from '@shared/engine/sect'; -import type { Cultivator } from '@shared/types/cultivator'; +} from '@daoyou/shared/engine/sect'; +import type { Cultivator } from '@daoyou/shared/types/cultivator'; import type { SectBenefitService } from './SectBenefitService'; import type { SectDomainEventDispatcherFactory } from './SectDomainEventDispatcher'; import { diff --git a/src/server/lib/services/sect-organization/SectMembershipCommand.ts b/apps/api/src/lib/services/sect-organization/SectMembershipCommand.ts similarity index 96% rename from src/server/lib/services/sect-organization/SectMembershipCommand.ts rename to apps/api/src/lib/services/sect-organization/SectMembershipCommand.ts index 09364aec..d88e3bee 100644 --- a/src/server/lib/services/sect-organization/SectMembershipCommand.ts +++ b/apps/api/src/lib/services/sect-organization/SectMembershipCommand.ts @@ -1,6 +1,6 @@ import type { DbTransaction } from '@server/lib/drizzle/db'; import { cultivators } from '@server/lib/drizzle/schema'; -import type { RealmStage, RealmType } from '@shared/types/constants'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; import { eq } from 'drizzle-orm'; import { SectError } from '../SectError'; import { StoryService } from '../StoryService'; diff --git a/src/server/lib/services/sect-organization/SectOrganizationClock.ts b/apps/api/src/lib/services/sect-organization/SectOrganizationClock.ts similarity index 100% rename from src/server/lib/services/sect-organization/SectOrganizationClock.ts rename to apps/api/src/lib/services/sect-organization/SectOrganizationClock.ts diff --git a/src/server/lib/services/sect-organization/SectOrganizationFacade.ts b/apps/api/src/lib/services/sect-organization/SectOrganizationFacade.ts similarity index 96% rename from src/server/lib/services/sect-organization/SectOrganizationFacade.ts rename to apps/api/src/lib/services/sect-organization/SectOrganizationFacade.ts index 33a246a2..2410d88e 100644 --- a/src/server/lib/services/sect-organization/SectOrganizationFacade.ts +++ b/apps/api/src/lib/services/sect-organization/SectOrganizationFacade.ts @@ -1,4 +1,4 @@ -import type { SectRuntime } from '@shared/engine/sect'; +import type { SectRuntime } from '@daoyou/shared/engine/sect'; import type { GetSectTasksQueryHandler } from './GetSectTasksQueryHandler'; import { SectAdmissionApplicationService } from './SectAdmissionApplicationService'; import type { SectConstructionApplicationService } from './SectConstructionApplicationService'; diff --git a/src/server/lib/services/sect-organization/SectOrganizationPlugins.ts b/apps/api/src/lib/services/sect-organization/SectOrganizationPlugins.ts similarity index 99% rename from src/server/lib/services/sect-organization/SectOrganizationPlugins.ts rename to apps/api/src/lib/services/sect-organization/SectOrganizationPlugins.ts index 1ad7bbff..5771a32b 100644 --- a/src/server/lib/services/sect-organization/SectOrganizationPlugins.ts +++ b/apps/api/src/lib/services/sect-organization/SectOrganizationPlugins.ts @@ -1,4 +1,4 @@ -import type { SectOrganizationModule } from '@shared/engine/sect'; +import type { SectOrganizationModule } from '@daoyou/shared/engine/sect'; import { createStandardSectDomainEventDispatcher } from './SectDomainEventDispatcher'; import { CompletedDailyTaskProgressStrategy, diff --git a/src/server/lib/services/sect-organization/SectTaskApplicationService.ts b/apps/api/src/lib/services/sect-organization/SectTaskApplicationService.ts similarity index 98% rename from src/server/lib/services/sect-organization/SectTaskApplicationService.ts rename to apps/api/src/lib/services/sect-organization/SectTaskApplicationService.ts index 37f76807..346addb8 100644 --- a/src/server/lib/services/sect-organization/SectTaskApplicationService.ts +++ b/apps/api/src/lib/services/sect-organization/SectTaskApplicationService.ts @@ -1,12 +1,12 @@ -import type { ResourceChangeDescriptor } from '@shared/contracts/resources'; -import type { SectTaskActionData } from '@shared/contracts/sect'; +import type { ResourceChangeDescriptor } from '@daoyou/shared/contracts/resources'; +import type { SectTaskActionData } from '@daoyou/shared/contracts/sect'; import { SECT_TASK_ABANDON_COOLDOWN_MS, SectTask, SectTaskRecordPayloadSchema, resolveSectTaskAbandonAvailability, type SectTaskDefinition, -} from '@shared/engine/sect'; +} from '@daoyou/shared/engine/sect'; import { z } from 'zod'; import { ClaimSectTaskRewardHandler } from './ClaimSectTaskRewardHandler'; import { SectCapabilityAuthorizer } from './SectCapabilityAuthorizer'; diff --git a/src/server/lib/services/sect-organization/SectTaskApplicationSupport.ts b/apps/api/src/lib/services/sect-organization/SectTaskApplicationSupport.ts similarity index 97% rename from src/server/lib/services/sect-organization/SectTaskApplicationSupport.ts rename to apps/api/src/lib/services/sect-organization/SectTaskApplicationSupport.ts index 0ed4237e..b2ce52f1 100644 --- a/src/server/lib/services/sect-organization/SectTaskApplicationSupport.ts +++ b/apps/api/src/lib/services/sect-organization/SectTaskApplicationSupport.ts @@ -1,7 +1,7 @@ import type { SectTaskAvailabilityDecision, SectTaskDefinition, -} from '@shared/engine/sect'; +} from '@daoyou/shared/engine/sect'; import { SectError } from '../SectError'; import type { SectMembershipRecord, SectQueryContext } from './ports'; diff --git a/src/server/lib/services/sect-organization/SectTaskCommand.ts b/apps/api/src/lib/services/sect-organization/SectTaskCommand.ts similarity index 100% rename from src/server/lib/services/sect-organization/SectTaskCommand.ts rename to apps/api/src/lib/services/sect-organization/SectTaskCommand.ts diff --git a/src/server/lib/services/sect-organization/SectTaskOfferService.ts b/apps/api/src/lib/services/sect-organization/SectTaskOfferService.ts similarity index 97% rename from src/server/lib/services/sect-organization/SectTaskOfferService.ts rename to apps/api/src/lib/services/sect-organization/SectTaskOfferService.ts index cec645a2..0f746d8a 100644 --- a/src/server/lib/services/sect-organization/SectTaskOfferService.ts +++ b/apps/api/src/lib/services/sect-organization/SectTaskOfferService.ts @@ -7,8 +7,8 @@ import { type SectTaskOfferSnapshot, type SectTaskRecordPayload, type SectTaskRewardCadence, -} from '@shared/engine/sect'; -import type { RealmStage, RealmType } from '@shared/types/constants'; +} from '@daoyou/shared/engine/sect'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; import { organizationError } from './applicationSupport'; import type { SectTaskOfferPolicyRegistry, diff --git a/src/server/lib/services/sect-organization/SectTaskSettlement.ts b/apps/api/src/lib/services/sect-organization/SectTaskSettlement.ts similarity index 96% rename from src/server/lib/services/sect-organization/SectTaskSettlement.ts rename to apps/api/src/lib/services/sect-organization/SectTaskSettlement.ts index 9d57a513..61526d9d 100644 --- a/src/server/lib/services/sect-organization/SectTaskSettlement.ts +++ b/apps/api/src/lib/services/sect-organization/SectTaskSettlement.ts @@ -1,4 +1,4 @@ -import type { DailyTaskDifficulty } from '@shared/engine/cultivation/exp-gain-strategies/types'; +import type { DailyTaskDifficulty } from '@daoyou/shared/engine/cultivation/exp-gain-strategies/types'; import { assertSectRealmQualityRules, assertStandardSectTaskRequirementCurve, @@ -10,8 +10,8 @@ import { type SectTaskDefinition, type SectTaskRewardCadence, type SectTaskRewardSnapshot, -} from '@shared/engine/sect'; -import type { RealmStage, RealmType } from '@shared/types/constants'; +} from '@daoyou/shared/engine/sect'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; import { z, type ZodType } from 'zod'; import { organizationError } from './applicationSupport'; import type { SectMembershipRecord, SectQueryContext } from './ports'; diff --git a/src/server/lib/services/sect-organization/SectTaskSubmissionQueryService.ts b/apps/api/src/lib/services/sect-organization/SectTaskSubmissionQueryService.ts similarity index 93% rename from src/server/lib/services/sect-organization/SectTaskSubmissionQueryService.ts rename to apps/api/src/lib/services/sect-organization/SectTaskSubmissionQueryService.ts index 93327e26..d44fe4fc 100644 --- a/src/server/lib/services/sect-organization/SectTaskSubmissionQueryService.ts +++ b/apps/api/src/lib/services/sect-organization/SectTaskSubmissionQueryService.ts @@ -1,8 +1,8 @@ -import type { SectSubmissionCandidatesData } from '@shared/contracts/sect'; +import type { SectSubmissionCandidatesData } from '@daoyou/shared/contracts/sect'; import { matchSectDeliveryCandidate, type SectTaskDefinition, -} from '@shared/engine/sect'; +} from '@daoyou/shared/engine/sect'; import { SectError } from '../SectError'; import type { SectQueryContext } from './ports'; diff --git a/src/server/lib/services/sect-organization/SectTaskViewAssembler.ts b/apps/api/src/lib/services/sect-organization/SectTaskViewAssembler.ts similarity index 98% rename from src/server/lib/services/sect-organization/SectTaskViewAssembler.ts rename to apps/api/src/lib/services/sect-organization/SectTaskViewAssembler.ts index e521d4c7..248c9d0d 100644 --- a/src/server/lib/services/sect-organization/SectTaskViewAssembler.ts +++ b/apps/api/src/lib/services/sect-organization/SectTaskViewAssembler.ts @@ -1,4 +1,4 @@ -import type { SectTaskViewData } from '@shared/contracts/sect'; +import type { SectTaskViewData } from '@daoyou/shared/contracts/sect'; import { resolveSectTaskClaimReward, resolveSectTaskAbandonAvailability, @@ -7,7 +7,7 @@ import { readSectBattleTargetSnapshot, summarizeSectBattleTarget, type SectTaskDefinition, -} from '@shared/engine/sect'; +} from '@daoyou/shared/engine/sect'; import type { SectTaskRecord } from './ports'; import type { SectTaskExecutor } from './task-executors/SectTaskExecutor'; diff --git a/src/server/lib/services/sect-organization/SectTransferApplicationService.ts b/apps/api/src/lib/services/sect-organization/SectTransferApplicationService.ts similarity index 98% rename from src/server/lib/services/sect-organization/SectTransferApplicationService.ts rename to apps/api/src/lib/services/sect-organization/SectTransferApplicationService.ts index 763d2ef8..7aaaf659 100644 --- a/src/server/lib/services/sect-organization/SectTransferApplicationService.ts +++ b/apps/api/src/lib/services/sect-organization/SectTransferApplicationService.ts @@ -15,18 +15,18 @@ import { SectError } from '@server/lib/services/SectError'; import { CHEAT_HEAVEN_TALISMAN_NAME, CHEAT_HEAVEN_TALISMAN_SCENARIO, -} from '@shared/config/sectTransferTalisman'; +} from '@daoyou/shared/config/sectTransferTalisman'; import type { SectContextData, SectTransferPreviewData, -} from '@shared/contracts/sect'; +} from '@daoyou/shared/contracts/sect'; import { resolveSectTaskClaimReward, SectTaskRecordPayloadSchema, type SectDiscipleRank, type SectRuntime, -} from '@shared/engine/sect'; -import type { Consumable } from '@shared/types/cultivator'; +} from '@daoyou/shared/engine/sect'; +import type { Consumable } from '@daoyou/shared/types/cultivator'; import { and, eq } from 'drizzle-orm'; import { findBagTalisman } from '../BagConsumables'; import { diff --git a/src/server/lib/services/sect-organization/SectTransferCommand.ts b/apps/api/src/lib/services/sect-organization/SectTransferCommand.ts similarity index 95% rename from src/server/lib/services/sect-organization/SectTransferCommand.ts rename to apps/api/src/lib/services/sect-organization/SectTransferCommand.ts index 64e0645d..4c610965 100644 --- a/src/server/lib/services/sect-organization/SectTransferCommand.ts +++ b/apps/api/src/lib/services/sect-organization/SectTransferCommand.ts @@ -1,4 +1,4 @@ -import type { ResourceChangeDescriptor } from '@shared/contracts/resources'; +import type { ResourceChangeDescriptor } from '@daoyou/shared/contracts/resources'; import { executeSectPlayerCommand, type SectCommandArgs, diff --git a/src/server/lib/services/sect-organization/TaskRewardStrategies.ts b/apps/api/src/lib/services/sect-organization/TaskRewardStrategies.ts similarity index 93% rename from src/server/lib/services/sect-organization/TaskRewardStrategies.ts rename to apps/api/src/lib/services/sect-organization/TaskRewardStrategies.ts index bf87d1ef..ebc7e5e9 100644 --- a/src/server/lib/services/sect-organization/TaskRewardStrategies.ts +++ b/apps/api/src/lib/services/sect-organization/TaskRewardStrategies.ts @@ -1,5 +1,5 @@ -import type { SectTaskRewardSnapshot } from '@shared/engine/sect'; -import type { Material } from '@shared/types/cultivator'; +import type { SectTaskRewardSnapshot } from '@daoyou/shared/engine/sect'; +import type { Material } from '@daoyou/shared/types/cultivator'; import { organizationError } from './applicationSupport'; import type { SectRewardGateway } from './ports'; import type { SectCommandEffects } from './SectCommandEffects'; diff --git a/src/server/lib/services/sect-organization/applicationSupport.ts b/apps/api/src/lib/services/sect-organization/applicationSupport.ts similarity index 95% rename from src/server/lib/services/sect-organization/applicationSupport.ts rename to apps/api/src/lib/services/sect-organization/applicationSupport.ts index 91e1d913..53a3d475 100644 --- a/src/server/lib/services/sect-organization/applicationSupport.ts +++ b/apps/api/src/lib/services/sect-organization/applicationSupport.ts @@ -2,8 +2,8 @@ import { type SectDiscipleRank, type SectFacilityState, type SectOrganizationModule, -} from '@shared/engine/sect'; -import type { RealmType } from '@shared/types/constants'; +} from '@daoyou/shared/engine/sect'; +import type { RealmType } from '@daoyou/shared/types/constants'; import { SectError } from '../SectError'; import type { SectFacilityRecord, diff --git a/src/server/lib/services/sect-organization/commandSupport.ts b/apps/api/src/lib/services/sect-organization/commandSupport.ts similarity index 82% rename from src/server/lib/services/sect-organization/commandSupport.ts rename to apps/api/src/lib/services/sect-organization/commandSupport.ts index b043f9cd..04d2b856 100644 --- a/src/server/lib/services/sect-organization/commandSupport.ts +++ b/apps/api/src/lib/services/sect-organization/commandSupport.ts @@ -1,7 +1,7 @@ import type { DbTransaction } from '@server/lib/drizzle/db'; import { playerCommandExecutor } from '@server/lib/services/CommandExecutors'; -import type { ResourceChangeDescriptor } from '@shared/contracts/resources'; -import type { SectRuntime } from '@shared/engine/sect'; +import type { ResourceChangeDescriptor } from '@daoyou/shared/contracts/resources'; +import type { SectRuntime } from '@daoyou/shared/engine/sect'; export type SectCommandArgs = { userId: string; diff --git a/src/server/lib/services/sect-organization/index.ts b/apps/api/src/lib/services/sect-organization/index.ts similarity index 100% rename from src/server/lib/services/sect-organization/index.ts rename to apps/api/src/lib/services/sect-organization/index.ts diff --git a/src/server/lib/services/sect-organization/ports.ts b/apps/api/src/lib/services/sect-organization/ports.ts similarity index 96% rename from src/server/lib/services/sect-organization/ports.ts rename to apps/api/src/lib/services/sect-organization/ports.ts index d072b901..055d558f 100644 --- a/src/server/lib/services/sect-organization/ports.ts +++ b/apps/api/src/lib/services/sect-organization/ports.ts @@ -1,7 +1,7 @@ import type { DbExecutor, DbTransaction } from '@server/lib/drizzle/db'; import type { DomainEventWriter } from '@server/lib/mq/domainEventWriter'; -import type { ResourceDataMap } from '@shared/contracts/resources'; -import type { SectTaskSettlementData } from '@shared/contracts/sect'; +import type { ResourceDataMap } from '@daoyou/shared/contracts/resources'; +import type { SectTaskSettlementData } from '@daoyou/shared/contracts/sect'; import type { CultivatorSectState, SectDefinition, @@ -11,9 +11,9 @@ import type { SectSubmissionItemFacts, SectSubmissionItemKind, SectTaskRecordPayload, -} from '@shared/engine/sect'; -import type { Quality, RealmStage, RealmType } from '@shared/types/constants'; -import type { Material } from '@shared/types/cultivator'; +} from '@daoyou/shared/engine/sect'; +import type { Quality, RealmStage, RealmType } from '@daoyou/shared/types/constants'; +import type { Material } from '@daoyou/shared/types/cultivator'; import type { SectCommandEffects } from './SectCommandEffects'; export interface Clock { @@ -240,7 +240,7 @@ export interface SectRewardMaterialCatalogGateway { export interface SectBattleGateway { freeze( context: import('./task-executors/SectTaskExecutor').SectTaskEnrollmentContext, - ): Promise; + ): Promise; start( context: import('./task-executors/SectTaskExecutor').SectTaskExecutionContext, ): Promise<{ battleId: string }>; diff --git a/src/server/lib/services/sect-organization/productionSectOrganization.ts b/apps/api/src/lib/services/sect-organization/productionSectOrganization.ts similarity index 95% rename from src/server/lib/services/sect-organization/productionSectOrganization.ts rename to apps/api/src/lib/services/sect-organization/productionSectOrganization.ts index e0680cbf..447ef171 100644 --- a/src/server/lib/services/sect-organization/productionSectOrganization.ts +++ b/apps/api/src/lib/services/sect-organization/productionSectOrganization.ts @@ -3,8 +3,8 @@ import { type DbExecutor, type DbTransaction, } from '@server/lib/drizzle/db'; -import type { SectCraftContextKey } from '@shared/engine/sect'; -import { productionSectRuntime } from '@shared/engine/sect/content'; +import type { SectCraftContextKey } from '@daoyou/shared/engine/sect'; +import { productionSectRuntime } from '@daoyou/shared/engine/sect/content'; import { ClaimSectTaskRewardHandler } from './ClaimSectTaskRewardHandler'; import { GetSectTasksQueryHandler } from './GetSectTasksQueryHandler'; import { diff --git a/src/server/lib/services/sect-organization/task-executors/SectTaskExecutor.ts b/apps/api/src/lib/services/sect-organization/task-executors/SectTaskExecutor.ts similarity index 99% rename from src/server/lib/services/sect-organization/task-executors/SectTaskExecutor.ts rename to apps/api/src/lib/services/sect-organization/task-executors/SectTaskExecutor.ts index 3864372b..d76f2252 100644 --- a/src/server/lib/services/sect-organization/task-executors/SectTaskExecutor.ts +++ b/apps/api/src/lib/services/sect-organization/task-executors/SectTaskExecutor.ts @@ -2,7 +2,7 @@ import { SectTaskSubmissionInputSchema, type SectTaskActionOutcome, type SectTaskSubmissionInput, -} from '@shared/contracts/sect'; +} from '@daoyou/shared/contracts/sect'; import { describeSectDeliveryRequirement, matchSectDeliveryRequirement, @@ -28,7 +28,7 @@ import { type SectTaskDefinition, type SectTaskRecordPayload, type SweepDirection, -} from '@shared/engine/sect'; +} from '@daoyou/shared/engine/sect'; import { z, type ZodType } from 'zod'; import { SectError } from '../../SectError'; import type { diff --git a/src/server/lib/services/sect-organization/testing/FixtureSectOrganizationPlugin.ts b/apps/api/src/lib/services/sect-organization/testing/FixtureSectOrganizationPlugin.ts similarity index 100% rename from src/server/lib/services/sect-organization/testing/FixtureSectOrganizationPlugin.ts rename to apps/api/src/lib/services/sect-organization/testing/FixtureSectOrganizationPlugin.ts diff --git a/src/server/lib/services/sectChatBroadcaster.ts b/apps/api/src/lib/services/sectChatBroadcaster.ts similarity index 96% rename from src/server/lib/services/sectChatBroadcaster.ts rename to apps/api/src/lib/services/sectChatBroadcaster.ts index d1e23b57..7da27be9 100644 --- a/src/server/lib/services/sectChatBroadcaster.ts +++ b/apps/api/src/lib/services/sectChatBroadcaster.ts @@ -7,7 +7,7 @@ import { createPubSubEnvelope, parsePubSubEnvelope, } from '@server/lib/services/pubSubEnvelope'; -import type { WorldChatMessageDTO } from '@shared/types/world-chat'; +import type { WorldChatMessageDTO } from '@daoyou/shared/types/world-chat'; type Listener = (message: WorldChatMessageDTO) => void; type SectSubscription = { diff --git a/src/server/lib/services/spirit-field/SpiritFieldInventory.ts b/apps/api/src/lib/services/spirit-field/SpiritFieldInventory.ts similarity index 89% rename from src/server/lib/services/spirit-field/SpiritFieldInventory.ts rename to apps/api/src/lib/services/spirit-field/SpiritFieldInventory.ts index cbb5bd34..d8bb32f0 100644 --- a/src/server/lib/services/spirit-field/SpiritFieldInventory.ts +++ b/apps/api/src/lib/services/spirit-field/SpiritFieldInventory.ts @@ -1,8 +1,8 @@ -import { ConsumableFactsSchema } from '@shared/items/definitions/consumables'; -import { SeedFactsSchema } from '@shared/items/definitions/seeds'; -import { materialFactsOf } from '@shared/items/material'; -import { findItemDefinition } from '@shared/items/registry'; -import { isPillConsumable } from '@shared/lib/consumables'; +import { ConsumableFactsSchema } from '@daoyou/shared/items/definitions/consumables'; +import { SeedFactsSchema } from '@daoyou/shared/items/definitions/seeds'; +import { materialFactsOf } from '@daoyou/shared/items/material'; +import { findItemDefinition } from '@daoyou/shared/items/registry'; +import { isPillConsumable } from '@daoyou/shared/lib/consumables'; import { and, eq, or } from 'drizzle-orm'; import { getExecutor, diff --git a/src/server/lib/services/spirit-field/SpiritFieldLlmService.ts b/apps/api/src/lib/services/spirit-field/SpiritFieldLlmService.ts similarity index 98% rename from src/server/lib/services/spirit-field/SpiritFieldLlmService.ts rename to apps/api/src/lib/services/spirit-field/SpiritFieldLlmService.ts index 9235f61a..23424c87 100644 --- a/src/server/lib/services/spirit-field/SpiritFieldLlmService.ts +++ b/apps/api/src/lib/services/spirit-field/SpiritFieldLlmService.ts @@ -1,6 +1,6 @@ import { renderPrompt } from '@server/lib/prompts'; import { generateAiObject } from '@server/utils/aiClient'; -import { SPIRIT_FIELD_METHOD_MAP, getAffinityScore, type SpiritFieldCultivationMethod, type SpiritFieldHarvestSettlement, type SpiritFieldPlantSnapshot, type SpiritFieldStageHistory, type SpiritFieldStageJudgment } from '@shared/engine/spirit-field'; +import { SPIRIT_FIELD_METHOD_MAP, getAffinityScore, type SpiritFieldCultivationMethod, type SpiritFieldHarvestSettlement, type SpiritFieldPlantSnapshot, type SpiritFieldStageHistory, type SpiritFieldStageJudgment } from '@daoyou/shared/engine/spirit-field'; import { z } from 'zod'; const judgmentSchema = z.object({ diff --git a/src/server/lib/services/spirit-field/SpiritFieldService.ts b/apps/api/src/lib/services/spirit-field/SpiritFieldService.ts similarity index 97% rename from src/server/lib/services/spirit-field/SpiritFieldService.ts rename to apps/api/src/lib/services/spirit-field/SpiritFieldService.ts index 34cd23ee..8a77ecfa 100644 --- a/src/server/lib/services/spirit-field/SpiritFieldService.ts +++ b/apps/api/src/lib/services/spirit-field/SpiritFieldService.ts @@ -1,3 +1,4 @@ +import { SpiritSeedGenerator } from '@server/lib/generation/SpiritSeedGenerator'; import { getExecutor, type DbExecutor, @@ -15,16 +16,15 @@ import { qiCurrencyChange } from '@server/lib/services/QiResourceChanges'; import { QiService } from '@server/lib/services/QiService'; import { loadPlayerConsumableOperationFacts } from '@server/lib/services/cultivator/CultivatorConditionFactsReader'; import { updateSpiritStones } from '@server/lib/services/cultivator/CultivatorStateRepository'; -import type { ResourceChangeDescriptor } from '@shared/contracts/resources'; +import type { ResourceChangeDescriptor } from '@daoyou/shared/contracts/resources'; import type { SpiritFieldCultivateRequest, SpiritFieldHarvestRequest, SpiritFieldSowRequest, -} from '@shared/contracts/spiritField'; +} from '@daoyou/shared/contracts/spiritField'; import { SPIRIT_FIELD_METHODS, SPIRIT_FIELD_STARTER_BATCHES, - SpiritSeedGenerator, advanceSpiritFieldPlotToDecision, buildSpiritFruitSpec, canPlantSpiritFieldSeed, @@ -36,13 +36,13 @@ import { settleSpiritFieldHarvest, type SpiritFieldCultivationMethod, type SpiritFieldPlotState, -} from '@shared/engine/spirit-field'; -import type { InventoryItem } from '@shared/inventory'; -import { consumableFactsOf } from '@shared/items/definitions/consumables'; -import { MaterialFactsSchema } from '@shared/items/definitions/materials'; -import { seedFactsOf } from '@shared/items/definitions/seeds'; -import type { MaterialType, RealmType } from '@shared/types/constants'; -import type { Consumable, Material } from '@shared/types/cultivator'; +} from '@daoyou/shared/engine/spirit-field'; +import type { InventoryItem } from '@daoyou/shared/inventory'; +import { consumableFactsOf } from '@daoyou/shared/items/definitions/consumables'; +import { MaterialFactsSchema } from '@daoyou/shared/items/definitions/materials'; +import { seedFactsOf } from '@daoyou/shared/items/definitions/seeds'; +import type { MaterialType, RealmType } from '@daoyou/shared/types/constants'; +import type { Consumable, Material } from '@daoyou/shared/types/cultivator'; import { and, eq } from 'drizzle-orm'; import { findPlayerMutationRequest } from '../../repositories/playerStateRepository'; import { grantInventory } from '../InventoryService'; diff --git a/src/server/lib/services/taskDefinitions.ts b/apps/api/src/lib/services/taskDefinitions.ts similarity index 99% rename from src/server/lib/services/taskDefinitions.ts rename to apps/api/src/lib/services/taskDefinitions.ts index bf48e8c4..e7c9e0ed 100644 --- a/src/server/lib/services/taskDefinitions.ts +++ b/apps/api/src/lib/services/taskDefinitions.ts @@ -1,9 +1,9 @@ -import { BREAKTHROUGH_CHALLENGES } from '@shared/engine/combat-v6/breakthrough/host'; +import { BREAKTHROUGH_CHALLENGES } from '@daoyou/shared/engine/combat-v6/breakthrough/host'; import type { TaskDefinition, TaskInstanceMetadata, TaskStageDefinition, -} from '@shared/types/task'; +} from '@daoyou/shared/types/task'; type TaskLinkKind = | 'alchemy' diff --git a/src/server/lib/services/textFilter.ts b/apps/api/src/lib/services/textFilter.ts similarity index 76% rename from src/server/lib/services/textFilter.ts rename to apps/api/src/lib/services/textFilter.ts index 65e55dbf..836a19fb 100644 --- a/src/server/lib/services/textFilter.ts +++ b/apps/api/src/lib/services/textFilter.ts @@ -1,5 +1,5 @@ import dictionary from '@server/config/text-filter.dictionary.json'; -import { createTextFilter } from '@shared/text-filter'; +import { createTextFilter } from '@daoyou/shared/text-filter'; // Imported JSON is bundled into dist; invalid dictionaries fail at module startup. export const textFilter = createTextFilter(dictionary); diff --git a/src/server/lib/services/worldChatBroadcaster.ts b/apps/api/src/lib/services/worldChatBroadcaster.ts similarity index 95% rename from src/server/lib/services/worldChatBroadcaster.ts rename to apps/api/src/lib/services/worldChatBroadcaster.ts index 72143ac7..184d7b46 100644 --- a/src/server/lib/services/worldChatBroadcaster.ts +++ b/apps/api/src/lib/services/worldChatBroadcaster.ts @@ -6,7 +6,7 @@ import { createPubSubEnvelope, parsePubSubEnvelope, } from '@server/lib/services/pubSubEnvelope'; -import type { WorldChatMessageDTO } from '@shared/types/world-chat'; +import type { WorldChatMessageDTO } from '@daoyou/shared/types/world-chat'; type Listener = (message: WorldChatMessageDTO) => void; diff --git a/src/server/lib/sponsorship/afdianProvider.ts b/apps/api/src/lib/sponsorship/afdianProvider.ts similarity index 91% rename from src/server/lib/sponsorship/afdianProvider.ts rename to apps/api/src/lib/sponsorship/afdianProvider.ts index bf2047af..a9380e89 100644 --- a/src/server/lib/sponsorship/afdianProvider.ts +++ b/apps/api/src/lib/sponsorship/afdianProvider.ts @@ -1,10 +1,11 @@ +import { getRuntimeEnvironment } from '@server/lib/config/environment'; import { buildAfdianCheckoutUrl, buildAfdianWebhookSignedText, createAfdianApiEnvelope, -} from '@shared/lib/afdianProtocol'; -import { parseCnyAmountToFen } from '@shared/lib/sponsorship'; -import { createPublicKey, verify as verifySignature } from 'node:crypto'; +} from '@daoyou/shared/lib/afdianProtocol'; +import { parseCnyAmountToFen } from '@daoyou/shared/lib/sponsorship'; +import { createHash, createPublicKey, verify as verifySignature } from 'node:crypto'; import { z } from 'zod'; import type { ProviderOrder, @@ -89,7 +90,7 @@ export class AfdianSponsorshipProvider implements SponsorshipProvider { isConfigured(): boolean { return Boolean( - process.env.AFDIAN_USER_ID?.trim() && process.env.AFDIAN_TOKEN?.trim(), + getRuntimeEnvironment().AFDIAN_USER_ID?.trim() && getRuntimeEnvironment().AFDIAN_TOKEN?.trim(), ); } @@ -154,8 +155,8 @@ export class AfdianSponsorshipProvider implements SponsorshipProvider { } private requireCredentials(): { userId: string; token: string } { - const userId = process.env.AFDIAN_USER_ID?.trim(); - const token = process.env.AFDIAN_TOKEN?.trim(); + const userId = getRuntimeEnvironment().AFDIAN_USER_ID?.trim(); + const token = getRuntimeEnvironment().AFDIAN_TOKEN?.trim(); if (!userId || !token) throw new Error('爱发电 Provider 未配置'); return { userId, token }; } @@ -171,7 +172,7 @@ export class AfdianSponsorshipProvider implements SponsorshipProvider { token: credentials.token, params, timestampSeconds: ts, - }); + }, (text) => createHash('md5').update(text).digest('hex')); const response = await fetch(`${AFDIAN_API_BASE_URL}${path}`, { method: 'POST', headers: { 'content-type': 'application/json' }, diff --git a/src/server/lib/sponsorship/claimCode.ts b/apps/api/src/lib/sponsorship/claimCode.ts similarity index 100% rename from src/server/lib/sponsorship/claimCode.ts rename to apps/api/src/lib/sponsorship/claimCode.ts diff --git a/src/server/lib/sponsorship/providerRegistry.ts b/apps/api/src/lib/sponsorship/providerRegistry.ts similarity index 79% rename from src/server/lib/sponsorship/providerRegistry.ts rename to apps/api/src/lib/sponsorship/providerRegistry.ts index 3e2a9781..54efa945 100644 --- a/src/server/lib/sponsorship/providerRegistry.ts +++ b/apps/api/src/lib/sponsorship/providerRegistry.ts @@ -1,8 +1,9 @@ +import { getRuntimeEnvironment } from '@server/lib/config/environment'; import { afdianSponsorshipProvider } from './afdianProvider'; import type { SponsorshipProvider } from './types'; export function getSponsorshipProvider(): SponsorshipProvider | null { - const provider = process.env.SPONSORSHIP_PROVIDER?.trim() || 'disabled'; + const provider = getRuntimeEnvironment().SPONSORSHIP_PROVIDER?.trim() || 'disabled'; if (provider === 'disabled') return null; if (provider === 'afdian') return afdianSponsorshipProvider; throw new Error(`Unsupported SPONSORSHIP_PROVIDER: ${provider}`); diff --git a/src/server/lib/sponsorship/types.ts b/apps/api/src/lib/sponsorship/types.ts similarity index 93% rename from src/server/lib/sponsorship/types.ts rename to apps/api/src/lib/sponsorship/types.ts index 35e096c5..2883efeb 100644 --- a/src/server/lib/sponsorship/types.ts +++ b/apps/api/src/lib/sponsorship/types.ts @@ -1,4 +1,4 @@ -import type { SponsorshipTierId } from '@shared/lib/sponsorship'; +import type { SponsorshipTierId } from '@daoyou/shared/lib/sponsorship'; export type ProviderOrder = { provider: 'afdian'; diff --git a/src/server/lib/template/render.ts b/apps/api/src/lib/template/render.ts similarity index 100% rename from src/server/lib/template/render.ts rename to apps/api/src/lib/template/render.ts diff --git a/src/server/lib/tower/combatV6.ts b/apps/api/src/lib/tower/combatV6.ts similarity index 94% rename from src/server/lib/tower/combatV6.ts rename to apps/api/src/lib/tower/combatV6.ts index 8e53beef..4eac2c7f 100644 --- a/src/server/lib/tower/combatV6.ts +++ b/apps/api/src/lib/tower/combatV6.ts @@ -1,48 +1,48 @@ -import { automaticCommands } from '@shared/combat-v6/auto'; +import { automaticCommands } from '@daoyou/shared/combat-v6/auto'; import { combatV6Display, combatV6DisplayEvent, combatV6Playback, combatV6Units, visibleUnitNames, -} from '@shared/combat-v6/presentation'; -import { createCombatV6Replay } from '@shared/combat-v6/replay'; -import { liveReplayDelta } from '@shared/combat-v6/replay-timeline'; -import { publicUnitAppearances } from '@shared/combat-v6/unit-appearance'; -import type { CombatV6CommandGroup } from '@shared/contracts/combatV6'; +} from '@daoyou/shared/combat-v6/presentation'; +import { createCombatV6Replay } from '@daoyou/shared/combat-v6/replay'; +import { liveReplayDelta } from '@daoyou/shared/combat-v6/replay-timeline'; +import { publicUnitAppearances } from '@daoyou/shared/combat-v6/unit-appearance'; +import type { CombatV6CommandGroup } from '@daoyou/shared/contracts/combatV6'; import type { TowerReward, TowerSessionView, TowerView, -} from '@shared/contracts/combatV6Tower'; -import type { ResourceChange } from '@shared/contracts/resources'; -import { canDeployBeast } from '@shared/engine/combat-v6/beasts'; -import type { CombatV6TrainingPlayerInput } from '@shared/engine/combat-v6/encounter'; -import { combatCharacterLevel } from '@shared/engine/combat-v6/projection/character-level'; +} from '@daoyou/shared/contracts/combatV6Tower'; +import type { ResourceChange } from '@daoyou/shared/contracts/resources'; +import { canDeployBeast } from '@daoyou/shared/engine/combat-v6/beasts'; +import type { CombatV6TrainingPlayerInput } from '@daoyou/shared/engine/combat-v6/encounter'; +import { combatCharacterLevel } from '@daoyou/shared/engine/combat-v6/projection/character-level'; import { createTowerHost, TowerHost, type TowerBattleSnapshot, -} from '@shared/engine/combat-v6/tower/host'; -import { publishedTowerPreviews } from '@shared/engine/combat-v6/tower/published'; -import type { TowerBlessingId } from '@shared/lib/tower/blessings'; +} from '@daoyou/shared/engine/combat-v6/tower/host'; +import { publishedTowerPreviews } from '@daoyou/shared/engine/combat-v6/tower/published'; +import type { TowerBlessingId } from '@daoyou/shared/lib/tower/blessings'; import { buildTowerBlessingChoices, hashTowerSeed, isTowerRealmEligible, TOWER_MAX_FLOOR, TOWER_MIN_REALM, -} from '@shared/lib/tower/helpers'; -import { shouldExpireTowerRun } from '@shared/lib/tower/lifecycle'; +} from '@daoyou/shared/lib/tower/helpers'; +import { shouldExpireTowerRun } from '@daoyou/shared/lib/tower/lifecycle'; import { advanceTowerRewardWeek, towerRewards, TowerRewardSchema, -} from '@shared/lib/tower/reward-state'; -import { getTowerSeasonMeta } from '@shared/lib/tower/season'; -import { planTowerReward, towerRewardPreviews } from '@shared/rewards/tower'; -import type { CultivatorCondition } from '@shared/types/condition'; -import type { RealmType } from '@shared/types/constants'; +} from '@daoyou/shared/lib/tower/reward-state'; +import { getTowerSeasonMeta } from '@daoyou/shared/lib/tower/season'; +import { planTowerReward, towerRewardPreviews } from '@daoyou/shared/rewards/tower'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; +import type { RealmType } from '@daoyou/shared/types/constants'; import { eq, sql } from 'drizzle-orm'; import { randomUUID } from 'node:crypto'; import { db } from '../drizzle/db'; diff --git a/src/server/lib/tower/leaderboard.ts b/apps/api/src/lib/tower/leaderboard.ts similarity index 97% rename from src/server/lib/tower/leaderboard.ts rename to apps/api/src/lib/tower/leaderboard.ts index afc0a3ec..3a3ae8ee 100644 --- a/src/server/lib/tower/leaderboard.ts +++ b/apps/api/src/lib/tower/leaderboard.ts @@ -6,8 +6,8 @@ import { unpackTowerLeaderboardScore, type TowerLeaderboardEntry, type TowerWeeklyRecord, -} from '@shared/lib/tower'; -import type { RealmType } from '@shared/types/constants'; +} from '@daoyou/shared/lib/tower'; +import type { RealmType } from '@daoyou/shared/types/constants'; import { and, eq, inArray } from 'drizzle-orm'; function getTowerLeaderboardKey(seasonKey: string, realm: RealmType) { diff --git a/src/server/lib/tower/occupancy.ts b/apps/api/src/lib/tower/occupancy.ts similarity index 94% rename from src/server/lib/tower/occupancy.ts rename to apps/api/src/lib/tower/occupancy.ts index 6d14d8e6..9e28b0af 100644 --- a/src/server/lib/tower/occupancy.ts +++ b/apps/api/src/lib/tower/occupancy.ts @@ -1,7 +1,7 @@ import { towerRunOccupancy, type TowerLifecycleState, -} from '@shared/lib/tower/lifecycle'; +} from '@daoyou/shared/lib/tower/lifecycle'; import { redis } from '../redis'; import { parseRedisJson } from '../redis/json'; diff --git a/apps/api/src/mail/mail.controller.ts b/apps/api/src/mail/mail.controller.ts new file mode 100644 index 00000000..89dfe58a --- /dev/null +++ b/apps/api/src/mail/mail.controller.ts @@ -0,0 +1,128 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Post, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import { BeastError } from '@server/lib/services/combat-v6/BeastMutationGuard'; +import { PlayerCommandIdempotencyError } from '@server/lib/services/CommandExecutors'; +import { InventoryError } from '@server/lib/services/InventoryService'; +import { PlayerMailCommandError } from '@server/lib/services/PlayerMailApplicationService'; +import { PlayerMailServiceError } from '@server/lib/services/PlayerMailService'; +import { SendMailSchema, type SendMailRequest } from '@daoyou/shared/contracts/mail'; +import { JournalRequestSchema } from '@daoyou/shared/contracts/playerJournal'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { MailService } from './mail.service'; + +const MailIdSchema = z.object({ mailId: z.string() }); +const SendErrors = apiErrorFilter((error) => { + const lock = redisLockErrorResponse(error); + if (lock) return lock; + if (error instanceof z.ZodError) + return Response.json( + { error: '参数错误', details: error.issues }, + { status: 400 }, + ); + if ( + error instanceof InventoryError || + error instanceof PlayerCommandIdempotencyError + ) + return Response.json({ error: error.message }, { status: 409 }); + if (error instanceof PlayerMailServiceError) + return Response.json({ error: error.message }, { status: error.status }); + console.error('mail send api error:', error); + return Response.json({ error: '发送传音失败' }, { status: 500 }); +}); +const ClaimAllErrors = apiErrorFilter((error) => { + if (error instanceof BeastError) + return Response.json({ error: error.message }, { status: 409 }); + return redisLockErrorResponse(error) ?? undefined; +}); +const ClaimErrors = apiErrorFilter((error) => { + if (error instanceof BeastError) + return Response.json({ error: error.message }, { status: 409 }); + const lock = redisLockErrorResponse(error); + if (lock) return lock; + if (error instanceof PlayerMailCommandError) + return Response.json({ error: error.message }, { status: error.status }); +}); +const ReadErrors = apiErrorFilter((error) => { + if (error instanceof PlayerMailCommandError) + return Response.json({ error: error.message }, { status: error.status }); +}); + +@Controller('api/cultivator/mail') +@Access('active') +export class MailController { + constructor(@Inject(MailService) private readonly mail: MailService) {} + + @Get() + list( + @CurrentCultivator() actor: ActiveCultivatorRef, + @FirstQuery('page') page?: string, + @FirstQuery('pageSize') pageSize?: string, + ) { + return this.mail.list(actor.cultivatorId, page, pageSize); + } + + @Get('unread-count') + unreadCount(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.mail.unreadCount(actor.cultivatorId); + } + + @Post('send') + @HttpCode(200) + @UseFilters(SendErrors) + send( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(SendMailSchema)) input: SendMailRequest, + ) { + return this.mail.send(actor, input); + } + + @Post('claim') + @HttpCode(200) + @UseFilters(ClaimErrors) + claim( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(MailIdSchema)) input: z.infer, + ) { + return this.mail.claim(actor, input.mailId); + } + + @Post('claim-all') + @HttpCode(200) + @UseFilters(ClaimAllErrors) + claimAll( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody({ fallback: undefined }, new ZodPipe(JournalRequestSchema)) + input: z.infer, + ) { + return this.mail.claimAll(actor, input.requestId); + } + + @Post('read') + @HttpCode(200) + @UseFilters(ReadErrors) + read( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(MailIdSchema)) input: z.infer, + ) { + return this.mail.read(actor, input.mailId); + } + + @Post('read-all') + @HttpCode(200) + readAll(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.mail.readAll(actor); + } +} diff --git a/apps/api/src/mail/mail.module.ts b/apps/api/src/mail/mail.module.ts new file mode 100644 index 00000000..0206ed19 --- /dev/null +++ b/apps/api/src/mail/mail.module.ts @@ -0,0 +1,6 @@ +import { Module } from '@nestjs/common'; +import { MailController } from './mail.controller'; +import { MailService } from './mail.service'; + +@Module({ controllers: [MailController], providers: [MailService] }) +export class MailModule {} diff --git a/apps/api/src/mail/mail.service.ts b/apps/api/src/mail/mail.service.ts new file mode 100644 index 00000000..e8471a56 --- /dev/null +++ b/apps/api/src/mail/mail.service.ts @@ -0,0 +1,87 @@ +import { Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { getExecutor } from '@server/lib/drizzle/db'; +import { mails } from '@server/lib/drizzle/schema'; +import { publicMailAttachment } from '@server/lib/services/MailInventory'; +import type { MailAttachment } from '@server/lib/services/MailService'; +import { + claimAllCultivatorMail, + claimCultivatorMail, + markAllCultivatorMailRead, + markCultivatorMailRead, + sendCultivatorMail, +} from '@server/lib/services/PlayerMailApplicationService'; +import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; +import { scheduleSystemMailObservation } from '@server/lib/services/SystemMailService'; +import type { SendMailRequest } from '@daoyou/shared/contracts/mail'; +import { and, desc, eq, sql } from 'drizzle-orm'; + +@Injectable() +export class MailService { + async list(cultivatorId: string, pageValue?: string, pageSizeValue?: string) { + scheduleSystemMailObservation(cultivatorId, 'mailbox'); + const pageRaw = parseInt(pageValue || '1', 10); + const pageSizeRaw = parseInt(pageSizeValue || '20', 10); + const page = Number.isNaN(pageRaw) ? 1 : Math.max(1, pageRaw); + const pageSize = Number.isNaN(pageSizeRaw) + ? 20 + : Math.min(100, Math.max(1, pageSizeRaw)); + const rows = await getExecutor().query.mails.findMany({ + where: eq(mails.cultivatorId, cultivatorId), + orderBy: [desc(mails.createdAt)], + limit: pageSize + 1, + offset: (page - 1) * pageSize, + }); + const hasMore = rows.length > pageSize; + return { + mails: (hasMore ? rows.slice(0, pageSize) : rows).map((mail) => ({ + ...mail, + attachments: (Array.isArray(mail.attachments) + ? (mail.attachments as MailAttachment[]) + : [] + ).map(publicMailAttachment), + })), + pagination: { page, pageSize, hasMore }, + }; + } + + async unreadCount(cultivatorId: string) { + const [result] = await getExecutor() + .select({ count: sql`count(*)::int` }) + .from(mails) + .where( + and(eq(mails.cultivatorId, cultivatorId), eq(mails.isRead, false)), + ); + return { count: Number(result?.count ?? 0) }; + } + + async send(actor: ActiveCultivatorRef, input: SendMailRequest) { + return toPlayerStateMutationResponse( + await sendCultivatorMail({ actor, ...input }), + ); + } + + async claim(actor: ActiveCultivatorRef, mailId: string) { + return toPlayerStateMutationResponse( + await claimCultivatorMail({ actor, mailId }), + ); + } + + async claimAll(actor: ActiveCultivatorRef, requestId: string) { + return toPlayerStateMutationResponse( + await claimAllCultivatorMail({ actor, requestId }), + ); + } + + async read(actor: ActiveCultivatorRef, mailId: string) { + return toPlayerStateMutationResponse( + await markCultivatorMailRead({ actor, mailId }), + ); + } + + async readAll(actor: ActiveCultivatorRef) { + return toPlayerStateMutationResponse( + await markAllCultivatorMailRead({ actor }), + ); + } +} diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts new file mode 100644 index 00000000..cce2d21b --- /dev/null +++ b/apps/api/src/main.ts @@ -0,0 +1,92 @@ +import { NestFactory } from '@nestjs/core'; +import { + ExpressAdapter, + type NestExpressApplication, +} from '@nestjs/platform-express'; +import { closeDatabase } from '@server/lib/drizzle/db'; +import { shutdownMessageInfrastructure } from '@server/lib/mq/domainEventRegistry'; +import { closeRedisConnection } from '@server/lib/redis'; +import express from 'express'; +import 'reflect-metadata'; +import { runtimeConfig } from './config/runtime.config'; +import type { ConfigType } from '@nestjs/config'; +import { AppModule } from './app.module'; +import { ArenaRealtimeService } from './arena/arena-realtime.service'; +import { configureHttp } from './http/configure-http'; +import { RequestWorkService } from './http/request-work.service'; +import { RealtimeAdapter } from './realtime/realtime.adapter'; +import { RealtimeService } from './realtime/realtime.service'; + +// ExpressAdapter installs middleware in its constructor, creating the router. +// Configure matching before that first use so the API keeps Hono's strict paths. +const server = express(); +server.set('case sensitive routing', true); +server.set('strict routing', true); +let closing = false; +function startShutdownDeadline() { + if (closing) return; + closing = true; + setTimeout(() => { + console.error('[runtime] shutdown exceeded 60s; forcing process exit'); + process.exit(1); + }, 60_000).unref(); +} + +const shutdownSignals = ['SIGTERM', 'SIGINT'] as const; +let startupSignal: NodeJS.Signals | undefined; +function deferStartupShutdown(signal: NodeJS.Signals) { + startupSignal ??= signal; + startShutdownDeadline(); +} +// Serialize shutdown with bootstrap: Nest's HTTP init does not populate the +// application context initializationPromise awaited by its shutdown hooks. +for (const signal of shutdownSignals) process.on(signal, deferStartupShutdown); + +let app: NestExpressApplication | undefined; +try { + app = await NestFactory.create( + AppModule, + new ExpressAdapter(server), + { bodyParser: false, return503OnClosing: true, abortOnError: false }, + ); + configureHttp(app); + app.useWebSocketAdapter( + new RealtimeAdapter( + app, + app.get(RealtimeService), + app.get(ArenaRealtimeService), + app.get(RequestWorkService), + ), + ); + await app.init(); + if (!startupSignal) { + const config = app.get>(runtimeConfig.KEY); + await app.listen( + Number(config.PORT), + config.HOST, + ); + } + if (startupSignal) { + console.info(`[runtime] ${startupSignal} received during startup; closing`); + await app.close(); + process.exit(startupSignal === 'SIGINT' ? 130 : 143); + } + for (const signal of shutdownSignals) { + process.removeListener(signal, deferStartupShutdown); + process.once(signal, startShutdownDeadline); + } + app.enableShutdownHooks([...shutdownSignals]); +} catch (error) { + console.error('[runtime] startup failed', error); + startShutdownDeadline(); + try { + if (app) await app.close(); + else { + await shutdownMessageInfrastructure(); + await Promise.allSettled([closeDatabase(), closeRedisConnection()]); + } + } catch (cleanupError) { + console.error('[runtime] startup cleanup failed', cleanupError); + } + process.exit(1); +} diff --git a/apps/api/src/manuals/manuals.controller.ts b/apps/api/src/manuals/manuals.controller.ts new file mode 100644 index 00000000..ca835b31 --- /dev/null +++ b/apps/api/src/manuals/manuals.controller.ts @@ -0,0 +1,65 @@ +import { + Controller, + Get, + Header, + HttpCode, + Inject, + Post, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import { InventoryError } from '@server/lib/services/InventoryService'; +import { ManualActionSchema } from '@daoyou/shared/contracts/combatV6Manuals'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { ManualsService } from './manuals.service'; + +const ManualErrors = apiErrorFilter((error) => { + const lock = redisLockErrorResponse(error); + if (lock) return lock; + if (error instanceof z.ZodError) + return Response.json( + { success: false, error: '请求参数无效' }, + { status: 400 }, + ); + if (error instanceof InventoryError) + return Response.json( + { success: false, error: error.message }, + { status: 409 }, + ); + console.error('[manuals] request failed', error); + return Response.json( + { success: false, error: '请求未完成,请刷新核对道印与玉简' }, + { status: 500 }, + ); +}); + +@Controller('api/combat-v6/manuals') +@Access('active') +@UseFilters(ManualErrors) +export class ManualsController { + constructor( + @Inject(ManualsService) private readonly manuals: ManualsService, + ) {} + + @Get() + @Header('Cache-Control', 'no-store') + read(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.manuals.read(actor.cultivatorId); + } + + @Post() + @HttpCode(200) + @Header('Cache-Control', 'no-store') + mutate( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody({ fallback: undefined }, new ZodPipe(ManualActionSchema)) + action: z.infer, + ) { + return this.manuals.mutate(actor.cultivatorId, action); + } +} diff --git a/apps/api/src/manuals/manuals.module.ts b/apps/api/src/manuals/manuals.module.ts new file mode 100644 index 00000000..bb1e2fd5 --- /dev/null +++ b/apps/api/src/manuals/manuals.module.ts @@ -0,0 +1,6 @@ +import { Module } from '@nestjs/common'; +import { ManualsController } from './manuals.controller'; +import { ManualsService } from './manuals.service'; + +@Module({ controllers: [ManualsController], providers: [ManualsService] }) +export class ManualsModule {} diff --git a/apps/api/src/manuals/manuals.service.ts b/apps/api/src/manuals/manuals.service.ts new file mode 100644 index 00000000..5eb9d6cf --- /dev/null +++ b/apps/api/src/manuals/manuals.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { + mutateManuals, + readManuals, +} from '@server/lib/services/combat-v6/CombatV6ManualService'; +import type { ManualActionSchema } from '@daoyou/shared/contracts/combatV6Manuals'; +import type { z } from 'zod'; + +@Injectable() +export class ManualsService { + async read(owner: string) { + return { success: true, data: await readManuals(owner) }; + } + async mutate(owner: string, action: z.infer) { + return { success: true, ...(await mutateManuals(owner, action)) }; + } +} diff --git a/apps/api/src/market/market.controller.ts b/apps/api/src/market/market.controller.ts new file mode 100644 index 00000000..621b3087 --- /dev/null +++ b/apps/api/src/market/market.controller.ts @@ -0,0 +1,100 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Param, + Post, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import { PlayerCommandIdempotencyError } from '@server/lib/services/CommandExecutors'; +import { MarketRecycleError } from '@server/lib/services/MarketRecycleService'; +import { MarketServiceError } from '@server/lib/services/MarketService'; +import { MarketBuySchema } from '@daoyou/shared/contracts/market'; +import { RecycleRequestSchema } from '@daoyou/shared/contracts/recycle'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { MarketService } from './market.service'; + +const RecycleErrors = apiErrorFilter((error) => { + const lock = redisLockErrorResponse(error); + if (lock) return lock; + if (error instanceof z.ZodError) + return Response.json( + { error: error.issues[0]?.message ?? '参数格式错误' }, + { status: 400 }, + ); + if (error instanceof MarketRecycleError) + return Response.json({ error: error.message }, { status: error.status }); + return undefined; +}); +const MarketReadErrors = apiErrorFilter((error) => { + if (error instanceof MarketServiceError) + return Response.json({ error: error.message }, { status: error.status }); + console.error('Market node API error:', error); + return Response.json( + { error: 'Failed to fetch market listings' }, + { status: 500 }, + ); +}); +const MarketBuyErrors = apiErrorFilter((error) => { + const lock = redisLockErrorResponse(error); + if (lock) return lock; + if ( + error instanceof MarketServiceError || + error instanceof PlayerCommandIdempotencyError + ) + return Response.json({ error: error.message }, { status: error.status }); + if (error instanceof z.ZodError) + return Response.json( + { error: error.issues[0]?.message || '参数错误' }, + { status: 400 }, + ); + console.error('Market buy API error:', error); + return Response.json({ error: '购买失败' }, { status: 500 }); +}); + +@Controller('api/market') +@Access('active') +export class MarketController { + constructor(@Inject(MarketService) private readonly market: MarketService) {} + + @Post('recycle') + @HttpCode(200) + @UseFilters(RecycleErrors) + recycle( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(RecycleRequestSchema)) + input: z.infer, + ) { + return this.market.recycle(actor, input); + } + + @Get(':nodeId') + @UseFilters(MarketReadErrors) + list( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('nodeId') nodeId: string, + @FirstQuery('layer') layer?: string, + ) { + return this.market.list(actor, nodeId, layer); + } + + @Post(':nodeId/buy') + @HttpCode(200) + @UseFilters(MarketBuyErrors) + buy( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('nodeId') nodeId: string, + @JsonBody(new ZodPipe(MarketBuySchema)) + input: z.infer, + ) { + return this.market.buy(actor, nodeId, input); + } +} diff --git a/apps/api/src/market/market.module.ts b/apps/api/src/market/market.module.ts new file mode 100644 index 00000000..89a840d7 --- /dev/null +++ b/apps/api/src/market/market.module.ts @@ -0,0 +1,6 @@ +import { Module } from '@nestjs/common'; +import { MarketController } from './market.controller'; +import { MarketService } from './market.service'; + +@Module({ controllers: [MarketController], providers: [MarketService] }) +export class MarketModule {} diff --git a/apps/api/src/market/market.service.ts b/apps/api/src/market/market.service.ts new file mode 100644 index 00000000..4f2905af --- /dev/null +++ b/apps/api/src/market/market.service.ts @@ -0,0 +1,60 @@ +import { Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { + confirmBagRecycle, + previewBagRecycle, +} from '@server/lib/services/BagRecycleService'; +import { purchaseMarketItems } from '@server/lib/services/MarketApplicationService'; +import { + getMarketListings, + MarketServiceError, + resolveLayer, + resolveNodeId, +} from '@server/lib/services/MarketService'; +import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; +import { readCultivatorRealm } from '@server/lib/services/cultivator/CultivatorFactsReader'; +import { getPlayerPreHeavenFates } from '@server/lib/services/cultivator/CultivatorProfileRepository'; +import type { MarketBuyInput } from '@daoyou/shared/contracts/market'; +import type { RecycleRequestSchema } from '@daoyou/shared/contracts/recycle'; +import type { z } from 'zod'; + +@Injectable() +export class MarketService { + async list(actor: ActiveCultivatorRef, node: string, layerValue?: string) { + const nodeId = resolveNodeId(node); + const layer = resolveLayer(layerValue); + if (layer === 'black') + throw new MarketServiceError(410, '黑市已经移入暗巷,请从坊市入口前往'); + const [{ realm }, fates] = await Promise.all([ + readCultivatorRealm(actor.cultivatorId), + getPlayerPreHeavenFates(actor.userId, actor.cultivatorId), + ]); + return getMarketListings({ + nodeId, + layer, + userId: actor.userId, + cultivatorRealm: realm, + fates: fates ?? [], + }); + } + + async buy(actor: ActiveCultivatorRef, node: string, input: MarketBuyInput) { + return toPlayerStateMutationResponse( + await purchaseMarketItems({ actor, nodeId: resolveNodeId(node), input }), + ); + } + + async recycle( + actor: ActiveCultivatorRef, + input: z.infer, + ) { + if (input.phase === 'preview') + return { + success: true, + data: await previewBagRecycle(actor.cultivatorId, input.items), + }; + return toPlayerStateMutationResponse( + await confirmBagRecycle(actor, input.quoteId), + ); + } +} diff --git a/apps/api/src/player/journal.controller.ts b/apps/api/src/player/journal.controller.ts new file mode 100644 index 00000000..7e97646e --- /dev/null +++ b/apps/api/src/player/journal.controller.ts @@ -0,0 +1,33 @@ +import { Controller, Get, Header, Inject, UseFilters } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { PlayerJournalQuerySchema } from '@daoyou/shared/contracts/playerJournal'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { FirstQuery } from '../http/first-query'; +import { ZodPipe } from '../http/zod.pipe'; +import { PlayerService } from './player.service'; + +const JournalErrors = apiErrorFilter((error) => { + if (error instanceof z.ZodError) + return Response.json({ error: '日志查询参数无效' }, { status: 400 }); + console.error('修仙日志查询失败:', error); + return Response.json({ error: '修仙日志暂时无法读取' }, { status: 500 }); +}); + +@Controller('api/player-journal') +@Access('active') +@UseFilters(JournalErrors) +export class JournalController { + constructor(@Inject(PlayerService) private readonly player: PlayerService) {} + + @Get() + @Header('Cache-Control', 'no-store') + list( + @CurrentCultivator() actor: ActiveCultivatorRef, + @FirstQuery(new ZodPipe(PlayerJournalQuerySchema)) + query: z.infer, + ) { + return this.player.journal(actor.cultivatorId, query); + } +} diff --git a/apps/api/src/player/player.controller.ts b/apps/api/src/player/player.controller.ts new file mode 100644 index 00000000..f4b6bda0 --- /dev/null +++ b/apps/api/src/player/player.controller.ts @@ -0,0 +1,41 @@ +import { Controller, Get, Inject } from '@nestjs/common'; +import type { AuthUser } from '@server/lib/auth/types'; +import { RESOURCE_SCOPE_KINDS } from '@daoyou/shared/contracts/resources'; +import { z } from 'zod'; +import { CurrentUser } from '../auth/access'; +import { FirstQuery } from '../http/first-query'; +import { ZodPipe } from '../http/zod.pipe'; +import { PlayerService } from './player.service'; + +const ResourcesQuery = z.object({ keys: z.string().min(1).max(256) }); +const EventsQuery = z.object({ + after: z.coerce.number().int().nonnegative().default(0), + scopeKind: z.enum(RESOURCE_SCOPE_KINDS), + scopeId: z.string().min(1).max(128), +}); + +@Controller('api/player') +export class PlayerController { + constructor(@Inject(PlayerService) private readonly player: PlayerService) {} + + @Get('resources') + resources( + @CurrentUser() user: AuthUser, + @FirstQuery(new ZodPipe(ResourcesQuery)) + query: z.infer, + ) { + return this.player.resources(user.id, query.keys); + } + + @Get('resources/events') + events( + @CurrentUser() user: AuthUser, + @FirstQuery(new ZodPipe(EventsQuery)) query: z.infer, + ) { + return this.player.events( + user.id, + { kind: query.scopeKind, id: query.scopeId }, + query.after, + ); + } +} diff --git a/apps/api/src/player/player.module.ts b/apps/api/src/player/player.module.ts new file mode 100644 index 00000000..5ffa257c --- /dev/null +++ b/apps/api/src/player/player.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { JournalController } from './journal.controller'; +import { PlayerController } from './player.controller'; +import { PlayerService } from './player.service'; + +@Module({ + controllers: [PlayerController, JournalController], + providers: [PlayerService], +}) +export class PlayerModule {} diff --git a/apps/api/src/player/player.service.ts b/apps/api/src/player/player.service.ts new file mode 100644 index 00000000..892d7b89 --- /dev/null +++ b/apps/api/src/player/player.service.ts @@ -0,0 +1,115 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import { db } from '@server/lib/drizzle/db'; +import { cultivators, sectMemberships } from '@server/lib/drizzle/schema'; +import { listPlayerJournal } from '@server/lib/repositories/playerJournalRepository'; +import { + readResourceEventWindow, + RESOURCE_EVENT_PAGE_LIMIT, +} from '@server/lib/repositories/playerStateRepository'; +import { + parsePlayerResourceKeys, + readPlayerResourcesSnapshot, +} from '@server/lib/services/PlayerResourceReaderService'; +import type { + PlayerResourceEventsResponse, + PlayerResourcesResponse, +} from '@daoyou/shared/contracts/player'; +import { + requiresResourceEventReload, + type ResourceScope, +} from '@daoyou/shared/contracts/resources'; +import { and, eq } from 'drizzle-orm'; + +@Injectable() +export class PlayerService { + async journal( + cultivatorId: string, + query: Parameters[1], + ) { + return { + success: true, + data: await listPlayerJournal(cultivatorId, query), + }; + } + + async resources( + userId: string, + rawKeys: string, + ): Promise { + let keys; + try { + keys = parsePlayerResourceKeys(rawKeys); + } catch (error) { + throw new HttpException( + { + success: false, + error: error instanceof Error ? error.message : '玩家资源 keys 无效', + }, + 400, + ); + } + return { + success: true, + data: await readPlayerResourcesSnapshot({ userId, keys }), + }; + } + + async events( + userId: string, + scope: ResourceScope, + after: number, + ): Promise { + const active = await db.query.cultivators.findFirst({ + columns: { id: true }, + where: and( + eq(cultivators.userId, userId), + eq(cultivators.status, 'active'), + ), + }); + if ( + !(await this.canReadScope( + { userId, cultivatorId: active?.id ?? null }, + scope, + )) + ) { + throw new HttpException( + { success: false, error: '无权读取该资源作用域' }, + 403, + ); + } + const window = await readResourceEventWindow(scope, after); + return { + success: true, + data: { + after, + scope, + currentScopeVersion: window.currentScopeVersion, + earliestAvailableVersion: window.earliestAvailableVersion, + changes: window.changes.slice(0, RESOURCE_EVENT_PAGE_LIMIT), + requiresReload: + window.hasIncompatibleEvents || + requiresResourceEventReload(window, after, RESOURCE_EVENT_PAGE_LIMIT), + }, + }; + } + + private async canReadScope( + ref: { userId: string; cultivatorId: string | null }, + scope: ResourceScope, + ): Promise { + if (scope.kind === 'account') return scope.id === ref.userId; + if (scope.kind === 'cultivator') + return Boolean(ref.cultivatorId && scope.id === ref.cultivatorId); + if (scope.kind === 'global') return scope.id === 'global'; + if (!ref.cultivatorId) return false; + const membership = await db.query.sectMemberships.findFirst({ + columns: { id: true }, + where: and( + eq(sectMemberships.cultivatorId, ref.cultivatorId), + eq(sectMemberships.sectId, scope.id), + eq(sectMemberships.status, 'active'), + ), + }); + return Boolean(membership); + } +} diff --git a/src/server/prompts/alchemy-formula-analysis.md b/apps/api/src/prompts/alchemy-formula-analysis.md similarity index 100% rename from src/server/prompts/alchemy-formula-analysis.md rename to apps/api/src/prompts/alchemy-formula-analysis.md diff --git a/src/server/prompts/alchemy-improvised-copy.md b/apps/api/src/prompts/alchemy-improvised-copy.md similarity index 100% rename from src/server/prompts/alchemy-improvised-copy.md rename to apps/api/src/prompts/alchemy-improvised-copy.md diff --git a/src/server/prompts/alchemy-recipe-plan.md b/apps/api/src/prompts/alchemy-recipe-plan.md similarity index 100% rename from src/server/prompts/alchemy-recipe-plan.md rename to apps/api/src/prompts/alchemy-recipe-plan.md diff --git a/src/server/prompts/black-market-observations.md b/apps/api/src/prompts/black-market-observations.md similarity index 100% rename from src/server/prompts/black-market-observations.md rename to apps/api/src/prompts/black-market-observations.md diff --git a/src/server/prompts/black-market-perception.md b/apps/api/src/prompts/black-market-perception.md similarity index 100% rename from src/server/prompts/black-market-perception.md rename to apps/api/src/prompts/black-market-perception.md diff --git a/src/server/prompts/black-market-reply.md b/apps/api/src/prompts/black-market-reply.md similarity index 100% rename from src/server/prompts/black-market-reply.md rename to apps/api/src/prompts/black-market-reply.md diff --git a/src/server/prompts/black-market-turn.md b/apps/api/src/prompts/black-market-turn.md similarity index 100% rename from src/server/prompts/black-market-turn.md rename to apps/api/src/prompts/black-market-turn.md diff --git a/src/server/prompts/breakthrough-story.md b/apps/api/src/prompts/breakthrough-story.md similarity index 100% rename from src/server/prompts/breakthrough-story.md rename to apps/api/src/prompts/breakthrough-story.md diff --git a/src/server/prompts/character-generation.md b/apps/api/src/prompts/character-generation.md similarity index 100% rename from src/server/prompts/character-generation.md rename to apps/api/src/prompts/character-generation.md diff --git a/src/server/prompts/daily-divination.md b/apps/api/src/prompts/daily-divination.md similarity index 100% rename from src/server/prompts/daily-divination.md rename to apps/api/src/prompts/daily-divination.md diff --git a/src/server/prompts/divine-fortune.md b/apps/api/src/prompts/divine-fortune.md similarity index 100% rename from src/server/prompts/divine-fortune.md rename to apps/api/src/prompts/divine-fortune.md diff --git a/src/server/prompts/dungeon-round.md b/apps/api/src/prompts/dungeon-round.md similarity index 100% rename from src/server/prompts/dungeon-round.md rename to apps/api/src/prompts/dungeon-round.md diff --git a/src/server/prompts/dungeon-settlement.md b/apps/api/src/prompts/dungeon-settlement.md similarity index 100% rename from src/server/prompts/dungeon-settlement.md rename to apps/api/src/prompts/dungeon-settlement.md diff --git a/src/server/prompts/equipment-forge-naming.md b/apps/api/src/prompts/equipment-forge-naming.md similarity index 100% rename from src/server/prompts/equipment-forge-naming.md rename to apps/api/src/prompts/equipment-forge-naming.md diff --git a/src/server/prompts/fate-naming.md b/apps/api/src/prompts/fate-naming.md similarity index 100% rename from src/server/prompts/fate-naming.md rename to apps/api/src/prompts/fate-naming.md diff --git a/src/server/prompts/identity-reshape.md b/apps/api/src/prompts/identity-reshape.md similarity index 100% rename from src/server/prompts/identity-reshape.md rename to apps/api/src/prompts/identity-reshape.md diff --git a/src/server/prompts/lifespan-exhausted.md b/apps/api/src/prompts/lifespan-exhausted.md similarity index 100% rename from src/server/prompts/lifespan-exhausted.md rename to apps/api/src/prompts/lifespan-exhausted.md diff --git a/src/server/prompts/material-generation.md b/apps/api/src/prompts/material-generation.md similarity index 100% rename from src/server/prompts/material-generation.md rename to apps/api/src/prompts/material-generation.md diff --git a/src/server/prompts/spirit-field-finalization.md b/apps/api/src/prompts/spirit-field-finalization.md similarity index 100% rename from src/server/prompts/spirit-field-finalization.md rename to apps/api/src/prompts/spirit-field-finalization.md diff --git a/src/server/prompts/spirit-field-stage-judgment.md b/apps/api/src/prompts/spirit-field-stage-judgment.md similarity index 100% rename from src/server/prompts/spirit-field-stage-judgment.md rename to apps/api/src/prompts/spirit-field-stage-judgment.md diff --git a/src/server/prompts/spirit-seed-generation.md b/apps/api/src/prompts/spirit-seed-generation.md similarity index 100% rename from src/server/prompts/spirit-seed-generation.md rename to apps/api/src/prompts/spirit-seed-generation.md diff --git a/src/server/prompts/yield-story.md b/apps/api/src/prompts/yield-story.md similarity index 100% rename from src/server/prompts/yield-story.md rename to apps/api/src/prompts/yield-story.md diff --git a/apps/api/src/rankings/rankings-errors.ts b/apps/api/src/rankings/rankings-errors.ts new file mode 100644 index 00000000..6cff17d4 --- /dev/null +++ b/apps/api/src/rankings/rankings-errors.ts @@ -0,0 +1,68 @@ +import { getRuntimeEnvironment } from '@server/lib/config/environment'; +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import { InventoryError } from '@server/lib/services/InventoryService'; +import { CombatV6BuildError } from '@server/lib/services/combat-v6/CombatV6BuildService'; +import { RankingV6Error } from '@server/lib/services/combat-v6/CombatV6RankingService'; +import { z } from 'zod'; +import { apiErrorFilter } from '../http/error-filter'; + +export const RankingListErrors = apiErrorFilter((error) => { + console.error('获取排行榜 API 错误:', error); + return Response.json( + { + error: + getRuntimeEnvironment().NODE_ENV === 'development' && error instanceof Error + ? error.message + : '获取排行榜失败,请稍后重试', + }, + { status: 500 }, + ); +}); +export const ItemRankingErrors = apiErrorFilter((error) => { + console.error('获取排行榜失败:', error); + return Response.json( + { success: false, error: '获取排行榜失败' }, + { status: 500 }, + ); +}); +export const WealthRankingErrors = apiErrorFilter((error) => { + console.error('获取财富榜失败:', error); + return Response.json( + { success: false, error: '获取财富榜失败' }, + { status: 500 }, + ); +}); +export const RankingProbeErrors = apiErrorFilter((error) => { + console.error('神识查探错误:', error); + const message = + getRuntimeEnvironment().NODE_ENV === 'development' + ? error instanceof Error + ? error.message + : '神识查探失败' + : '神识查探失败,请稍后重试'; + return Response.json({ error: message }, { status: 500 }); +}); +export const RankingChallengeErrors = apiErrorFilter((error) => { + const lock = redisLockErrorResponse(error); + if (lock) return lock; + if (error instanceof z.ZodError) + return Response.json( + { success: false, error: '挑战参数无效' }, + { status: 400 }, + ); + if (error instanceof InventoryError) + return Response.json( + { success: false, error: '请先结束当前战斗与结算,再发起天骄榜挑战' }, + { status: 409 }, + ); + if (error instanceof RankingV6Error || error instanceof CombatV6BuildError) + return Response.json( + { success: false, error: error.message }, + { status: 409 }, + ); + console.error('[ranking-v6] challenge failed', error); + return Response.json( + { success: false, error: '挑战尚未完成,请使用原请求重试恢复' }, + { status: 500 }, + ); +}); diff --git a/apps/api/src/rankings/rankings.controller.ts b/apps/api/src/rankings/rankings.controller.ts new file mode 100644 index 00000000..f3bcb69b --- /dev/null +++ b/apps/api/src/rankings/rankings.controller.ts @@ -0,0 +1,99 @@ +import { + Controller, + Get, + Header, + HttpCode, + Inject, + Post, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { + RankingChallengeSchema, + type RankingChallengeRequest, +} from '@daoyou/shared/contracts/combatV6Ranking'; +import { Access, CurrentCultivator } from '../auth/access'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { + ItemRankingErrors, + RankingChallengeErrors, + RankingListErrors, + RankingProbeErrors, + WealthRankingErrors, +} from './rankings-errors'; +import { RankingsService } from './rankings.service'; + +// Hono's named query reads return the first value for repeated parameters. +function first(value: string | string[] | undefined) { + return Array.isArray(value) ? value[0] : value; +} + +@Controller('api/rankings') +@Access('active') +export class RankingsController { + constructor( + @Inject(RankingsService) private readonly rankings: RankingsService, + ) {} + + @Get() + @Access('public') + @UseFilters(RankingListErrors) + list(@FirstQuery('realm') realm: string | string[] | undefined) { + return this.rankings.list(first(realm)); + } + + @Get('items') + @Access('public') + @UseFilters(ItemRankingErrors) + items(@FirstQuery('type') type: string | string[] | undefined) { + return this.rankings.items(first(type)); + } + + @Get('wealth') + @Access('public') + @UseFilters(WealthRankingErrors) + wealth() { + return this.rankings.wealth(); + } + + @Get('my-rank') + myRank( + @CurrentCultivator() actor: ActiveCultivatorRef, + @FirstQuery('realm') realm: string | string[] | undefined, + ) { + return this.rankings.myRank(actor, first(realm)); + } + + @Post('probe') + @HttpCode(200) + @UseFilters(RankingProbeErrors) + probe(@JsonBody() input: { targetId?: unknown }) { + return this.rankings.probe(input); + } + + @Post(['challenge', 'challenge-battle', 'challenge-battle/v5']) + @HttpCode(410) + retired() { + return { success: false, error: '旧天骄榜战斗已停用,请刷新使用新版挑战' }; + } + + @Get('challenge/current') + @Header('Cache-Control', 'no-store') + current(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.rankings.current(actor); + } + + @Post('challenge-battle/v6') + @HttpCode(200) + @Header('Cache-Control', 'no-store') + @UseFilters(RankingChallengeErrors) + challenge( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(RankingChallengeSchema)) + input: RankingChallengeRequest, + ) { + return this.rankings.challenge(actor, input); + } +} diff --git a/apps/api/src/rankings/rankings.module.ts b/apps/api/src/rankings/rankings.module.ts new file mode 100644 index 00000000..8f8c53d1 --- /dev/null +++ b/apps/api/src/rankings/rankings.module.ts @@ -0,0 +1,6 @@ +import { Module } from '@nestjs/common'; +import { RankingsController } from './rankings.controller'; +import { RankingsService } from './rankings.service'; + +@Module({ controllers: [RankingsController], providers: [RankingsService] }) +export class RankingsModule {} diff --git a/apps/api/src/rankings/rankings.service.ts b/apps/api/src/rankings/rankings.service.ts new file mode 100644 index 00000000..cf089614 --- /dev/null +++ b/apps/api/src/rankings/rankings.service.ts @@ -0,0 +1,163 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { getExecutor } from '@server/lib/drizzle/db'; +import { cultivators, inventoryItems } from '@server/lib/drizzle/schema'; +import { + getCultivatorRank, + getRankingList, + getRemainingChallenges, +} from '@server/lib/redis/rankings'; +import { + pendingRanking, + runRankingChallenge, +} from '@server/lib/services/combat-v6/CombatV6RankingService'; +import { loadCultivatorInspectionData } from '@server/lib/services/cultivator/CultivatorCombatProjectionReader'; +import { readCultivatorRealm } from '@server/lib/services/cultivator/CultivatorFactsReader'; +import type { RankingChallengeRequest } from '@daoyou/shared/contracts/combatV6Ranking'; +import { ConsumableFactsSchema } from '@daoyou/shared/items/definitions/consumables'; +import { + QUALITY_VALUES, + REALM_VALUES, + type RealmType, +} from '@daoyou/shared/types/constants'; +import type { + ItemRankingEntry, + WealthRankingEntry, +} from '@daoyou/shared/types/rankings'; +import { and, desc, eq, inArray, sql } from 'drizzle-orm'; + +function parseRealm(raw: string | undefined): RealmType | null { + return REALM_VALUES.includes(raw as RealmType) ? (raw as RealmType) : null; +} + +@Injectable() +export class RankingsService { + async list(rawRealm: string | undefined) { + const realm = parseRealm(rawRealm) ?? '炼气'; + return { success: true, data: await getRankingList(realm), realm }; + } + async items(type: string | undefined) { + if (type && ['artifact', 'skill', 'technique'].includes(type)) + throw new HttpException( + { success: false, error: '旧装备、功法、神通榜单已停用' }, + 410, + ); + if (type !== 'elixir') + throw new HttpException({ success: false, error: '无效的榜单类型' }, 400); + const score = sql`(${inventoryItems.instanceData}->>'score')::double precision`; + const groupId = sql`concat('pill:', ${inventoryItems.cultivatorId}, ':', md5(${inventoryItems.instanceData}::text))`; + const rows = await getExecutor() + .select({ + id: groupId, + ownerName: cultivators.name, + facts: inventoryItems.instanceData, + quantity: sql`sum(${inventoryItems.quantity})::integer`, + }) + .from(inventoryItems) + .innerJoin(cultivators, eq(inventoryItems.cultivatorId, cultivators.id)) + .where( + and( + eq(inventoryItems.definitionId, 'consumable.v1'), + inArray(inventoryItems.location, ['bag', 'storage']), + sql`${inventoryItems.instanceData}->>'type' = '丹药'`, + sql`${inventoryItems.instanceData}->'spec'->>'kind' = 'pill'`, + inArray( + sql`${inventoryItems.instanceData}->>'quality'`, + QUALITY_VALUES.slice(2), + ), + ), + ) + .groupBy( + inventoryItems.cultivatorId, + cultivators.name, + inventoryItems.instanceData, + ) + .orderBy(desc(score), groupId) + .limit(100); + const items: ItemRankingEntry[] = rows.map((row, index) => { + const facts = ConsumableFactsSchema.parse(row.facts); + return { + id: row.id, + rank: index + 1, + name: facts.name, + itemType: 'elixir', + type: facts.type, + quality: facts.quality, + ownerName: row.ownerName, + score: facts.score, + description: facts.description, + quantity: row.quantity, + spec: facts.spec, + }; + }); + + return { + success: true, + data: items, + }; + } + async wealth() { + const limit = 100; + const rows = await getExecutor() + .select({ + id: cultivators.id, + name: cultivators.name, + title: cultivators.title, + realm: cultivators.realm, + realmStage: cultivators.realm_stage, + age: cultivators.age, + origin: cultivators.origin, + spiritStones: cultivators.spirit_stones, + }) + .from(cultivators) + .where(eq(cultivators.status, 'active')) + .orderBy(desc(cultivators.spirit_stones)) + .limit(limit); + + const items: WealthRankingEntry[] = rows.map((row, index) => ({ + id: row.id, + rank: index + 1, + rankingType: 'wealth', + name: row.name, + title: row.title, + realm: row.realm, + realm_stage: row.realmStage, + age: row.age, + origin: row.origin, + spiritStones: row.spiritStones, + })); + + return { + success: true, + data: items, + }; + } + async myRank(actor: ActiveCultivatorRef, rawRealm: string | undefined) { + const own = await readCultivatorRealm(actor.cultivatorId); + const realm = parseRealm(rawRealm) ?? own.realm; + const rank = await getCultivatorRank(realm, actor.cultivatorId); + const remainingChallenges = await getRemainingChallenges( + actor.cultivatorId, + ); + return { success: true, data: { rank, realm, remainingChallenges } }; + } + async probe(input: { targetId?: unknown }) { + const { targetId } = input; + if (!targetId || typeof targetId !== 'string') + throw new HttpException({ error: '请提供有效的目标角色ID' }, 400); + const inspection = await loadCultivatorInspectionData(targetId); + if (!inspection) + throw new HttpException({ error: '目标角色不存在或不可查探' }, 404); + return { success: true, data: { cultivator: inspection } }; + } + async current(actor: ActiveCultivatorRef) { + return { success: true, data: await pendingRanking(actor.cultivatorId) }; + } + async challenge(actor: ActiveCultivatorRef, input: RankingChallengeRequest) { + const data = await runRankingChallenge( + { userId: actor.userId, cultivatorId: actor.cultivatorId }, + input, + ); + return { success: true, data }; + } +} diff --git a/apps/api/src/realtime/realtime.adapter.ts b/apps/api/src/realtime/realtime.adapter.ts new file mode 100644 index 00000000..222e4b1d --- /dev/null +++ b/apps/api/src/realtime/realtime.adapter.ts @@ -0,0 +1,154 @@ +import type { INestApplicationContext } from '@nestjs/common'; +import { WsAdapter } from '@nestjs/platform-ws'; +import { fromNodeHeaders } from 'better-auth/node'; +import { + createServer, + STATUS_CODES, + type IncomingMessage, + type Server, +} from 'node:http'; +import type { Duplex } from 'node:stream'; +import type { ServerOptions, WebSocketServer } from 'ws'; +import { ArenaRealtimeService } from '../arena/arena-realtime.service'; +import { ARENA_SOCKET_PATH, arenaSocketRoute } from '../arena/arena-socket'; +import { RequestWorkService } from '../http/request-work.service'; +import { + verifyWebSocketRequest, + type HandshakeError, +} from '../http/websocket-request'; +import { RealtimeService } from './realtime.service'; + +export class RealtimeAdapter extends WsAdapter { + private readonly handshakeHeaders = new WeakMap(); + private readonly upgradeListeners = new Map< + number, + (request: IncomingMessage, socket: Duplex, head: Buffer) => void + >(); + + constructor( + app: INestApplicationContext, + private readonly realtime: RealtimeService, + private readonly arena: ArenaRealtimeService, + private readonly work: RequestWorkService, + ) { + super(app); + } + + override create( + port: number, + options: ServerOptions & { path?: string } = {}, + ): WebSocketServer { + const service = + options.path === ARENA_SOCKET_PATH ? this.arena : this.realtime; + const verifyClient: ServerOptions['verifyClient'] = (info, callback) => { + const done = this.work.begin(); + let headers = new Headers(); + const reject = (result: HandshakeError) => { + const body = JSON.stringify( + result.body ?? { success: false, error: result.message }, + ); + headers.set('Content-Type', 'application/json; charset=utf-8'); + headers.set('Content-Length', String(Buffer.byteLength(body))); + headers.set('Connection', 'close'); + const lines: string[] = []; + headers.forEach((value, key) => { + if (key !== 'set-cookie') lines.push(`${key}: ${value}`); + }); + for (const cookie of headers.getSetCookie()) + lines.push(`Set-Cookie: ${cookie}`); + // ws's rejection callback joins array headers with commas. Serialize + // validated Headers here so renewed cookies remain separate fields. + const socket = info.req.socket; + socket.once('finish', () => socket.destroy()); + socket.end( + `HTTP/1.1 ${result.status} ${STATUS_CODES[result.status]}\r\n${lines.join('\r\n')}\r\n\r\n${body}`, + ); + }; + const verify = async () => { + const admission = await verifyWebSocketRequest( + fromNodeHeaders(info.req.headers), + ); + headers = admission.headers ?? headers; + if ('status' in admission) return reject(admission); + const result = await service.verify(info.req); + result.headers?.forEach((value, key) => { + if (key !== 'set-cookie') headers.set(key, value); + }); + for (const cookie of result.headers?.getSetCookie() ?? []) + headers.append('Set-Cookie', cookie); + if ('status' in result) return reject(result); + this.handshakeHeaders.set(info.req, headers); + callback(true); + }; + void verify() + .catch((error: unknown) => { + console.error('[realtime] handshake failed', error); + reject({ status: 500, message: '服务器内部错误' }); + }) + .finally(done); + }; + const server = super.create(port, { + ...options, + verifyClient, + }) as WebSocketServer; + server.on('headers', (headers, request) => { + const responseHeaders = this.handshakeHeaders.get(request); + this.handshakeHeaders.delete(request); + responseHeaders?.forEach((value, key) => { + if (key !== 'set-cookie') headers.push(`${key}: ${value}`); + }); + for (const cookie of responseHeaders?.getSetCookie() ?? []) + headers.push(`Set-Cookie: ${cookie}`); + }); + return server; + } + + // WsAdapter only matches literal paths. Keep a single upgrade listener for + // both the general realtime channel and battle-specific participant/watch URLs. + protected override ensureHttpServerExists( + port: number, + httpServer: Server = createServer(), + ): Server | undefined { + if (this.httpServersRegistry.has(port)) return; + this.httpServersRegistry.set(port, httpServer); + const listener = ( + request: IncomingMessage, + socket: Duplex, + head: Buffer, + ) => { + try { + const pathname = new URL(request.url ?? '/', 'http://localhost') + .pathname; + const server = ( + this.wsServersRegistry.get(port) as + (WebSocketServer & { path: string })[] | undefined + )?.find((candidate) => + candidate.path === ARENA_SOCKET_PATH + ? arenaSocketRoute(pathname) !== null + : pathname === candidate.path, + ); + if (!server) { + socket.destroy(); + return; + } + server.handleUpgrade(request, socket, head, (ws) => + server.emit('connection', ws, request), + ); + } catch { + socket.end( + 'HTTP/1.1 400 Bad Request\r\nConnection: close\r\nContent-Length: 0\r\n\r\n', + ); + } + }; + httpServer.on('upgrade', listener); + this.upgradeListeners.set(port, listener); + return httpServer; + } + + override async dispose(): Promise { + for (const [port, listener] of this.upgradeListeners) + this.httpServersRegistry.get(port)?.off('upgrade', listener); + this.upgradeListeners.clear(); + await super.dispose(); + } +} diff --git a/apps/api/src/realtime/realtime.controller.ts b/apps/api/src/realtime/realtime.controller.ts new file mode 100644 index 00000000..1a30ccc4 --- /dev/null +++ b/apps/api/src/realtime/realtime.controller.ts @@ -0,0 +1,59 @@ +import { + Controller, + Get, + HttpCode, + HttpException, + Inject, + Injectable, + UseGuards, + type CanActivate, + type ExecutionContext, +} from '@nestjs/common'; +import { fromNodeHeaders } from 'better-auth/node'; +import type { Request, Response } from 'express'; +import { Access } from '../auth/access'; +import { RealtimeService } from './realtime.service'; + +@Injectable() +class RealtimeHttpGuard implements CanActivate { + constructor( + @Inject(RealtimeService) private readonly realtime: RealtimeService, + ) {} + + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const response = context.switchToHttp().getResponse(); + const result = await this.realtime.reserveConnection( + fromNodeHeaders(request.headers), + ); + try { + result.headers?.forEach((value, name) => { + if (name.toLowerCase() !== 'set-cookie') + response.setHeader(name, value); + }); + for (const cookie of result.headers?.getSetCookie() ?? []) + response.append('Set-Cookie', cookie); + if ('status' in result) + throw new HttpException( + { success: false, error: result.message }, + result.status, + ); + return true; + } finally { + // A plain HTTP request never owns a live WebSocket reservation. + if ('reservation' in result) result.reservation.release(); + } + } +} + +@Controller('api/realtime') +// This guard preserves the realtime route's origin-before-session order. +@Access('public') +@UseGuards(RealtimeHttpGuard) +export class RealtimeController { + @Get() + @HttpCode(404) + withoutUpgrade() { + return { success: false, error: '接口不存在' }; + } +} diff --git a/apps/api/src/realtime/realtime.gateway.ts b/apps/api/src/realtime/realtime.gateway.ts new file mode 100644 index 00000000..26896837 --- /dev/null +++ b/apps/api/src/realtime/realtime.gateway.ts @@ -0,0 +1,16 @@ +import { Inject } from '@nestjs/common'; +import { WebSocketGateway, type OnGatewayConnection } from '@nestjs/websockets'; +import type { IncomingMessage } from 'node:http'; +import type { WebSocket } from 'ws'; +import { RealtimeService } from './realtime.service'; + +@WebSocketGateway({ path: '/api/realtime', maxPayload: 2048 }) +export class RealtimeGateway implements OnGatewayConnection { + constructor( + @Inject(RealtimeService) private readonly realtime: RealtimeService, + ) {} + + handleConnection(socket: WebSocket, request: IncomingMessage): void { + this.realtime.connect(socket, request); + } +} diff --git a/apps/api/src/realtime/realtime.module.ts b/apps/api/src/realtime/realtime.module.ts new file mode 100644 index 00000000..a4182011 --- /dev/null +++ b/apps/api/src/realtime/realtime.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { AuthModule } from '../auth/auth.module'; +import { RealtimeController } from './realtime.controller'; +import { RealtimeGateway } from './realtime.gateway'; +import { RealtimeService } from './realtime.service'; + +@Module({ + imports: [AuthModule], + controllers: [RealtimeController], + providers: [RealtimeGateway, RealtimeService], + exports: [RealtimeService], +}) +export class RealtimeModule {} diff --git a/apps/api/src/realtime/realtime.service.ts b/apps/api/src/realtime/realtime.service.ts new file mode 100644 index 00000000..8dcb6a6b --- /dev/null +++ b/apps/api/src/realtime/realtime.service.ts @@ -0,0 +1,251 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { db } from '@server/lib/drizzle/db'; +import { cultivators, sectMemberships } from '@server/lib/drizzle/schema'; +import { isAllowedRealtimeOrigin } from '@server/lib/http/realtimeOrigin'; +import { getRequestIp } from '@server/lib/http/requestIp'; +import { subscribeArenaRoomChanges } from '@server/lib/services/arenaRoomBroadcaster'; +import { + recordRealtimeConnectionClose, + recordRealtimeConnectionHeartbeat, + recordRealtimeConnectionOpen, +} from '@server/lib/services/onlinePresenceService'; +import { subscribeResourceEvents } from '@server/lib/services/playerStateBroadcaster'; +import { subscribeSectChatMessages } from '@server/lib/services/sectChatBroadcaster'; +import { subscribeWorldChatMessages } from '@server/lib/services/worldChatBroadcaster'; +import { + REALTIME_CHANNELS, + type RealtimeChannel, + type RealtimeServerEvent, +} from '@daoyou/shared/contracts/realtime'; +import type { ResourceScope } from '@daoyou/shared/contracts/resources'; +import { fromNodeHeaders } from 'better-auth/node'; +import { and, eq } from 'drizzle-orm'; +import type { IncomingMessage } from 'node:http'; +import { WebSocket } from 'ws'; +import { SessionService } from '../auth/session.service'; + +type Reservation = { + userId: string; + cultivatorId: string | null; + sectId: string | null; + release: () => void; +}; + +type Verification = ( + { status: number; message: string } | { reservation: Reservation } +) & { headers?: Headers }; + +@Injectable() +export class RealtimeService { + private readonly users = new Map(); + private readonly cultivators = new Map(); + private readonly ips = new Map(); + private readonly pending = new WeakMap(); + + constructor( + @Inject(SessionService) private readonly sessions: SessionService, + ) {} + + async verify(request: IncomingMessage): Promise { + const headers = fromNodeHeaders(request.headers); + const result = await this.reserveConnection(headers); + if ('status' in result) return result; + const { reservation } = result; + if (request.socket.destroyed) { + reservation.release(); + return { status: 400, message: 'Connection closed' }; + } + this.pending.set(request, reservation); + request.socket.once('close', reservation.release); + return result; + } + + // HTTP fallback and upgrades share identity and quota checks. HTTP has already + // passed the API admission middleware and must not count that rate limit twice. + async reserveConnection(headers: Headers): Promise { + if (!isAllowedRealtimeOrigin(headers.get('origin'))) + return { status: 403, message: 'Forbidden origin' }; + const session = await this.sessions.getSession(headers); + const user = session.response?.user; + if (!user) + return { status: 401, message: '未授权访问', headers: session.headers }; + const active = await db.query.cultivators.findFirst({ + columns: { id: true }, + where: and( + eq(cultivators.userId, user.id), + eq(cultivators.status, 'active'), + ), + }); + const sect = active + ? await db.query.sectMemberships.findFirst({ + columns: { sectId: true }, + where: and( + eq(sectMemberships.cultivatorId, active.id), + eq(sectMemberships.status, 'active'), + ), + }) + : undefined; + const counters: [Map, string, number][] = [ + [this.users, user.id, 3], + [this.ips, getRequestIp(headers) ?? 'unknown', 40], + ...(active + ? [ + [this.cultivators, active.id, 3] as [ + Map, + string, + number, + ], + ] + : []), + ]; + if (counters.some(([map, key, max]) => (map.get(key) ?? 0) >= max)) { + return { status: 429, message: '实时连接过多', headers: session.headers }; + } + for (const [map, key] of counters) map.set(key, (map.get(key) ?? 0) + 1); + let released = false; + const reservation: Reservation = { + userId: user.id, + cultivatorId: active?.id ?? null, + sectId: sect?.sectId ?? null, + release: () => { + if (released) return; + released = true; + for (const [map, key] of counters) { + const next = (map.get(key) ?? 0) - 1; + if (next <= 0) map.delete(key); + else map.set(key, next); + } + }, + }; + return { reservation, headers: session.headers }; + } + + connect(socket: WebSocket, request: IncomingMessage): void { + const reservation = this.pending.get(request); + this.pending.delete(request); + if (!reservation) { + socket.close(1011, 'Missing connection identity'); + return; + } + const rawChannels = new URL( + request.url ?? '/', + 'http://localhost', + ).searchParams.get('channels'); + const selected = + rawChannels + ?.split(',') + .map((value) => value.trim()) + .filter((value): value is RealtimeChannel => + REALTIME_CHANNELS.some((channel) => channel === value), + ) ?? []; + const channels = selected.length + ? [...new Set(selected)] + : [...REALTIME_CHANNELS]; + const { cultivatorId } = reservation; + const resourceScopes: ResourceScope[] = [ + { kind: 'account', id: reservation.userId }, + ...(cultivatorId + ? [{ kind: 'cultivator', id: cultivatorId } as const] + : []), + ...(reservation.sectId + ? [{ kind: 'sect', id: reservation.sectId } as const] + : []), + ]; + let lastActivityAt = Date.now(); + let closed = false; + let heartbeat: ReturnType | undefined; + const unsubscribers: (() => void)[] = []; + const cleanup = () => { + if (closed) return; + closed = true; + if (heartbeat) clearInterval(heartbeat); + for (const unsubscribe of unsubscribers) { + try { + unsubscribe(); + } catch (error) { + console.warn('[realtime] unsubscribe failed', { + cultivatorId, + error, + }); + } + } + reservation.release(); + if (cultivatorId) recordRealtimeConnectionClose(cultivatorId); + }; + const close = (code: number, reason: string) => { + socket.close(code, reason); + cleanup(); + }; + const send = (event: RealtimeServerEvent) => { + if (closed || socket.readyState !== WebSocket.OPEN) return; + if (socket.bufferedAmount > 1_048_576) { + close(1013, 'backpressure limit'); + return; + } + socket.send(JSON.stringify(event), (error) => { + if (error) { + cleanup(); + socket.terminate(); + } + }); + }; + socket.once('close', cleanup); + socket.once('error', cleanup); + socket.on('message', (data, binary) => { + if (binary || data.toString().length > 512) { + close(1003, 'invalid client message'); + return; + } + lastActivityAt = Date.now(); + }); + if (cultivatorId) recordRealtimeConnectionOpen(cultivatorId); + try { + if (channels.includes('player-state')) { + unsubscribers.push( + subscribeResourceEvents(resourceScopes, (changes) => + send({ type: 'player-state.events', payload: { changes } }), + ), + ); + } + if (channels.includes('world-chat')) { + unsubscribers.push( + subscribeWorldChatMessages((message) => + send({ type: 'world-chat.message', payload: message }), + ), + ); + if (reservation.sectId) { + unsubscribers.push( + subscribeSectChatMessages(reservation.sectId, (message) => + send({ type: 'world-chat.message', payload: message }), + ), + ); + } + } + if (channels.includes('arena-room')) { + unsubscribers.push( + subscribeArenaRoomChanges(reservation.userId, (payload) => + send({ type: 'arena-room.changed', payload }), + ), + ); + } + heartbeat = setInterval(() => { + if (Date.now() - lastActivityAt > 50_000) { + close(4000, 'heartbeat timeout'); + return; + } + if (cultivatorId) recordRealtimeConnectionHeartbeat(cultivatorId); + send({ + type: 'ping', + payload: { serverTime: new Date().toISOString() }, + }); + }, 25_000); + send({ + type: 'ready', + payload: { cultivatorId, channels, resourceScopes }, + }); + } catch (error) { + console.error('[realtime] connection setup failed', error); + close(1011, 'Connection setup failed'); + } + } +} diff --git a/apps/api/src/reputation-shop/reputation-shop.controller.ts b/apps/api/src/reputation-shop/reputation-shop.controller.ts new file mode 100644 index 00000000..93b4a3bd --- /dev/null +++ b/apps/api/src/reputation-shop/reputation-shop.controller.ts @@ -0,0 +1,66 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Param, + Post, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import { PlayerCommandIdempotencyError } from '@server/lib/services/CommandExecutors'; +import { ReputationShopError } from '@server/lib/services/ReputationShopService'; +import { + ReputationShopBuyBodySchema, + ReputationShopBuyParamsSchema, +} from '@daoyou/shared/contracts/reputationShop'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { ReputationShopService } from './reputation-shop.service'; + +const ReputationBuyErrors = apiErrorFilter((error) => { + const lock = redisLockErrorResponse(error); + if (lock) return lock; + if ( + error instanceof ReputationShopError || + error instanceof PlayerCommandIdempotencyError + ) + return Response.json({ error: error.message }, { status: error.status }); + if (error instanceof z.ZodError) + return Response.json( + { error: '参数错误', details: error.flatten() }, + { status: 400 }, + ); + console.error('reputation shop buy error:', error); + return Response.json({ error: '兑换失败,请稍后再试' }, { status: 500 }); +}); + +@Controller('api/reputation-shop') +@Access('active') +export class ReputationShopController { + constructor( + @Inject(ReputationShopService) private readonly shop: ReputationShopService, + ) {} + + @Get() + list(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.shop.list(actor.cultivatorId); + } + + @Post(':id/buy') + @HttpCode(200) + @UseFilters(ReputationBuyErrors) + buy( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param(new ZodPipe(ReputationShopBuyParamsSchema)) + params: z.infer, + @JsonBody(new ZodPipe(ReputationShopBuyBodySchema)) + input: z.infer, + ) { + return this.shop.buy(actor, params.id, input.requestId); + } +} diff --git a/apps/api/src/reputation-shop/reputation-shop.module.ts b/apps/api/src/reputation-shop/reputation-shop.module.ts new file mode 100644 index 00000000..0348853a --- /dev/null +++ b/apps/api/src/reputation-shop/reputation-shop.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { ReputationShopController } from './reputation-shop.controller'; +import { ReputationShopService } from './reputation-shop.service'; + +@Module({ + controllers: [ReputationShopController], + providers: [ReputationShopService], +}) +export class ReputationShopModule {} diff --git a/apps/api/src/reputation-shop/reputation-shop.service.ts b/apps/api/src/reputation-shop/reputation-shop.service.ts new file mode 100644 index 00000000..f9383e5f --- /dev/null +++ b/apps/api/src/reputation-shop/reputation-shop.service.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { readCultivatorReputation } from '@server/lib/services/cultivator/CultivatorFactsReader'; +import { purchaseReputationShopItemCommand } from '@server/lib/services/ReputationShopApplicationService'; +import { listReputationShopItems } from '@server/lib/services/ReputationShopService'; +import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; + +@Injectable() +export class ReputationShopService { + async list(owner: string) { + const items = await listReputationShopItems({ + cultivatorId: owner, + userVisibleOnly: true, + }); + const { reputation } = await readCultivatorReputation(owner); + return { items, reputation }; + } + + async buy(actor: ActiveCultivatorRef, id: string, requestId: string) { + return toPlayerStateMutationResponse( + await purchaseReputationShopItemCommand({ + id, + requestId, + userId: actor.userId, + cultivatorId: actor.cultivatorId, + }), + ); + } +} diff --git a/apps/api/src/reshape/fate-reshape.controller.ts b/apps/api/src/reshape/fate-reshape.controller.ts new file mode 100644 index 00000000..a0dd01d4 --- /dev/null +++ b/apps/api/src/reshape/fate-reshape.controller.ts @@ -0,0 +1,78 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Post, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import { FateReshapeServiceError } from '@server/lib/services/FateReshapeService'; +import { Access, CurrentCultivator } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { JsonBody } from '../http/json-body'; +import { FateReshapeService } from './fate-reshape.service'; +function fateErrors(fallback: string, lockAware = false) { + return apiErrorFilter((error) => { + if (lockAware) { + const lock = redisLockErrorResponse(error); + if (lock) return lock; + } + return Response.json( + { + success: false, + error: error instanceof Error ? error.message : fallback, + }, + { status: error instanceof FateReshapeServiceError ? error.status : 400 }, + ); + }); +} +const ReadErrors = apiErrorFilter((error) => + Response.json( + { + success: false, + error: error instanceof Error ? error.message : '获取命格重塑状态失败', + }, + { status: 400 }, + ), +); +@Controller('api/fate-reshape') +@Access('active') +export class FateReshapeController { + constructor( + @Inject(FateReshapeService) private readonly fate: FateReshapeService, + ) {} + @Get('session') + @UseFilters(ReadErrors) + read(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.fate.read(actor); + } + @Post('session') + @HttpCode(200) + @UseFilters(fateErrors('开启命格重塑失败', true)) + start(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.fate.start(actor); + } + @Post('reroll') + @HttpCode(200) + @UseFilters(fateErrors('命格重抽失败')) + reroll(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.fate.reroll(actor); + } + @Post('confirm') + @HttpCode(200) + @UseFilters(fateErrors('确认命格重塑失败', true)) + confirm( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody() input: unknown, + ) { + return this.fate.confirm(actor, input); + } + @Post('abandon') + @HttpCode(200) + @UseFilters(fateErrors('放弃命格重塑失败')) + abandon(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.fate.abandon(actor); + } +} diff --git a/apps/api/src/reshape/fate-reshape.service.ts b/apps/api/src/reshape/fate-reshape.service.ts new file mode 100644 index 00000000..8d9bf269 --- /dev/null +++ b/apps/api/src/reshape/fate-reshape.service.ts @@ -0,0 +1,53 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { + confirmFateReshapeCommand, + startFateReshapeCommand, +} from '@server/lib/services/FateReshapeApplicationService'; +import { FateReshapeService as FateSessions } from '@server/lib/services/FateReshapeService'; +import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; +import { z } from 'zod'; +const ConfirmSchema = z.object({ + selectedIndices: z.array(z.number().int().nonnegative()).length(3), +}); +@Injectable() +export class FateReshapeService { + async read(actor: ActiveCultivatorRef) { + const [session, talismanCount] = await Promise.all([ + FateSessions.getSession(actor.cultivatorId), + FateSessions.getAvailableTalismanCount(actor.cultivatorId), + ]); + return { success: true, data: { session, talismanCount } }; + } + async start(actor: ActiveCultivatorRef) { + return toPlayerStateMutationResponse( + await startFateReshapeCommand({ + userId: actor.userId, + cultivatorId: actor.cultivatorId, + }), + ); + } + async reroll(actor: ActiveCultivatorRef) { + const session = await FateSessions.rerollSession(actor.cultivatorId); + return { success: true, data: { session } }; + } + async confirm(actor: ActiveCultivatorRef, input: unknown) { + const parsed = ConfirmSchema.safeParse(input); + if (!parsed.success) + throw new HttpException( + { success: false, error: '请求参数格式错误' }, + 400, + ); + return toPlayerStateMutationResponse( + await confirmFateReshapeCommand({ + userId: actor.userId, + cultivatorId: actor.cultivatorId, + selectedIndices: parsed.data.selectedIndices, + }), + ); + } + async abandon(actor: ActiveCultivatorRef) { + await FateSessions.abandonSession(actor.cultivatorId); + return { success: true }; + } +} diff --git a/apps/api/src/reshape/identity-reshape.controller.ts b/apps/api/src/reshape/identity-reshape.controller.ts new file mode 100644 index 00000000..eb0dd444 --- /dev/null +++ b/apps/api/src/reshape/identity-reshape.controller.ts @@ -0,0 +1,74 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Patch, + Post, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import { IdentityReshapeServiceError } from '@server/lib/services/IdentityReshapeService'; +import { Access, CurrentCultivator } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { JsonBody, JsonBodyParseError } from '../http/json-body'; +import { IdentityReshapeService } from './identity-reshape.service'; +const IdentityErrors = apiErrorFilter((error) => { + // These routes decoded JSON outside their legacy domain error handler. + if (error instanceof JsonBodyParseError) return undefined; + const lock = redisLockErrorResponse(error); + if (lock) return lock; + return Response.json( + { + success: false, + error: error instanceof Error ? error.message : '改天换地失败', + }, + { + status: error instanceof IdentityReshapeServiceError ? error.status : 400, + }, + ); +}); +@Controller('api/identity-reshape') +@Access('active') +@UseFilters(IdentityErrors) +export class IdentityReshapeController { + constructor( + @Inject(IdentityReshapeService) + private readonly identity: IdentityReshapeService, + ) {} + @Get('session') + read(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.identity.read(actor); + } + @Post('session') + @HttpCode(200) + start(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.identity.start(actor); + } + @Patch('session') + draft( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody() input: unknown, + ) { + return this.identity.draft(actor, input); + } + @Post('generate') + @HttpCode(200) + generate( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody() input: unknown, + ) { + return this.identity.generate(actor, input); + } + @Post('confirm') + @HttpCode(200) + confirm(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.identity.confirm(actor); + } + @Post('abandon') + @HttpCode(200) + abandon(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.identity.abandon(actor); + } +} diff --git a/apps/api/src/reshape/identity-reshape.service.ts b/apps/api/src/reshape/identity-reshape.service.ts new file mode 100644 index 00000000..ac059aad --- /dev/null +++ b/apps/api/src/reshape/identity-reshape.service.ts @@ -0,0 +1,99 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { + abandonIdentityReshape, + confirmIdentityReshape, + generateIdentityReshape, + getIdentityReshapeSession, + getIdentityReshapeTalismanCount, + saveIdentityReshapeDraft, + startIdentityReshape, +} from '@server/lib/services/IdentityReshapeService'; +import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; +import { + IDENTITY_RESHAPE_DESCRIPTION_MAX_LENGTH, + IDENTITY_RESHAPE_DESCRIPTION_MIN_LENGTH, +} from '@daoyou/shared/config/identityReshape'; +import { z } from 'zod'; +const AnswerSchema = z.object({ + questionId: z.string().min(1).max(80), + optionId: z.string().min(1).max(20), +}); +const DraftSchema = z.object({ + answers: z.array(AnswerSchema).max(3), + description: z.string().trim().max(IDENTITY_RESHAPE_DESCRIPTION_MAX_LENGTH), +}); +const GenerateSchema = DraftSchema.extend({ + answers: z.array(AnswerSchema).length(3), + description: z + .string() + .trim() + .min(IDENTITY_RESHAPE_DESCRIPTION_MIN_LENGTH) + .max(IDENTITY_RESHAPE_DESCRIPTION_MAX_LENGTH), +}); +@Injectable() +export class IdentityReshapeService { + async read(actor: ActiveCultivatorRef) { + const [session, talismanCount] = await Promise.all([ + getIdentityReshapeSession(actor.cultivatorId), + getIdentityReshapeTalismanCount(actor.cultivatorId), + ]); + return { success: true, data: { session, talismanCount } }; + } + async start(actor: ActiveCultivatorRef) { + const result = await startIdentityReshape({ + userId: actor.userId, + cultivatorId: actor.cultivatorId, + }); + if (result.committed) + return toPlayerStateMutationResponse({ + ...result.committed, + result: { + session: result.session, + talismanCount: result.talismanCount, + }, + }); + return { + success: true, + data: { session: result.session, talismanCount: result.talismanCount }, + }; + } + async draft(actor: ActiveCultivatorRef, input: unknown) { + const parsed = DraftSchema.safeParse(input); + if (!parsed.success) + throw new HttpException( + { success: false, error: '问答草稿格式错误' }, + 400, + ); + const session = await saveIdentityReshapeDraft({ + cultivatorId: actor.cultivatorId, + ...parsed.data, + }); + return { success: true, data: { session } }; + } + async generate(actor: ActiveCultivatorRef, input: unknown) { + const parsed = GenerateSchema.safeParse(input); + if (!parsed.success) + throw new HttpException( + { success: false, error: '请完成问答并填写身世描述' }, + 400, + ); + const session = await generateIdentityReshape({ + cultivatorId: actor.cultivatorId, + ...parsed.data, + }); + return { success: true, data: { session } }; + } + async confirm(actor: ActiveCultivatorRef) { + return toPlayerStateMutationResponse( + await confirmIdentityReshape({ + userId: actor.userId, + cultivatorId: actor.cultivatorId, + }), + ); + } + async abandon(actor: ActiveCultivatorRef) { + await abandonIdentityReshape(actor.cultivatorId); + return { success: true }; + } +} diff --git a/apps/api/src/reshape/reshape.module.ts b/apps/api/src/reshape/reshape.module.ts new file mode 100644 index 00000000..e587d895 --- /dev/null +++ b/apps/api/src/reshape/reshape.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { FateReshapeController } from './fate-reshape.controller'; +import { FateReshapeService } from './fate-reshape.service'; +import { IdentityReshapeController } from './identity-reshape.controller'; +import { IdentityReshapeService } from './identity-reshape.service'; +@Module({ + controllers: [FateReshapeController, IdentityReshapeController], + providers: [FateReshapeService, IdentityReshapeService], +}) +export class ReshapeModule {} diff --git a/apps/api/src/runtime/cron.service.ts b/apps/api/src/runtime/cron.service.ts new file mode 100644 index 00000000..7adce926 --- /dev/null +++ b/apps/api/src/runtime/cron.service.ts @@ -0,0 +1,42 @@ +import { runtimeConfig } from '../config/runtime.config'; +import type { ConfigType } from '@nestjs/config'; +import { Inject, Injectable } from '@nestjs/common'; +import { SchedulerRegistry } from '@nestjs/schedule'; +import { BACKGROUND_SCHEDULES } from '@server/lib/jobs/schedules'; +import { publishScheduledBackgroundCommand } from '@server/lib/mq/backgroundCommandPublisher'; +import { CronJob } from 'cron'; + +@Injectable() +export class CronService { + constructor( + @Inject(runtimeConfig.KEY) private readonly config: ConfigType, + @Inject(SchedulerRegistry) private readonly scheduler: SchedulerRegistry, + ) {} + + start(): void { + if (this.config.NODE_ENV !== 'production') return; + for (const { type, expression } of BACKGROUND_SCHEDULES) { + const job = CronJob.from({ + cronTime: expression, + onTick: async () => { + try { + await publishScheduledBackgroundCommand(type); + } catch (error) { + console.error(`[cron] publish ${type} failed`, error); + } + }, + start: false, + timeZone: 'UTC', + waitForCompletion: true, + }); + this.scheduler.addCronJob(type, job); + job.start(); + } + } + + async stop(): Promise { + await Promise.all( + [...this.scheduler.getCronJobs().values()].map((job) => job.stop()), + ); + } +} diff --git a/apps/api/src/runtime/internal-cron.controller.ts b/apps/api/src/runtime/internal-cron.controller.ts new file mode 100644 index 00000000..e0968a22 --- /dev/null +++ b/apps/api/src/runtime/internal-cron.controller.ts @@ -0,0 +1,60 @@ +import { Controller, Get, Inject, UseFilters, UseGuards } from '@nestjs/common'; +import { Access } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { InternalCronGuard } from './internal-cron.guard'; +import { InternalCronService } from './internal-cron.service'; +const CronErrors = apiErrorFilter(() => + Response.json( + { success: false, error: 'Cron job execution failed' }, + { status: 500 }, + ), +); +@Controller('internal/cron') +@Access('public') +@UseGuards(InternalCronGuard) +@UseFilters(CronErrors) +export class InternalCronController { + constructor( + @Inject(InternalCronService) private readonly jobs: InternalCronService, + ) {} + @Get('auction-expire') + auctionExpire() { + return this.jobs.auctionExpire(); + } + @Get('rank-rewards') + rankRewards() { + return this.jobs.rankRewards(); + } + @Get('market-refresh') + marketRefresh() { + return this.jobs.marketRefresh(); + } + @Get('resource-replay-cleanup') + resourceReplayCleanup() { + return this.jobs.resourceReplayCleanup(); + } + @Get('expired-data-cleanup') + expiredDataCleanup() { + return this.jobs.expiredDataCleanup(); + } + @Get('material-library-daily-generation') + materialLibraryGeneration() { + return this.jobs.materialLibraryGeneration(); + } + @Get('sponsorship-reconcile') + sponsorshipReconcile() { + return this.jobs.sponsorshipReconcile(); + } + @Get('sponsorship-deep-reconcile') + sponsorshipDeepReconcile() { + return this.jobs.sponsorshipDeepReconcile(); + } + @Get('sponsorship-cleanup') + sponsorshipCleanup() { + return this.jobs.sponsorshipCleanup(); + } + @Get('sponsorship-admin-digest') + sponsorshipAdminDigest() { + return this.jobs.sponsorshipAdminDigest(); + } +} diff --git a/apps/api/src/runtime/internal-cron.guard.ts b/apps/api/src/runtime/internal-cron.guard.ts new file mode 100644 index 00000000..d4fc1818 --- /dev/null +++ b/apps/api/src/runtime/internal-cron.guard.ts @@ -0,0 +1,29 @@ +import { runtimeConfig } from '../config/runtime.config'; +import type { ConfigType } from '@nestjs/config'; +import { + HttpException, + Inject, + Injectable, + type CanActivate, + type ExecutionContext, +} from '@nestjs/common'; +import type { Request } from 'express'; +@Injectable() +export class InternalCronGuard implements CanActivate { + constructor(@Inject(runtimeConfig.KEY) private readonly config: ConfigType) {} + canActivate(context: ExecutionContext): boolean { + const secret = this.config.CRON_SECRET; + if (!secret) { + if (this.config.NODE_ENV === 'production') + throw new HttpException( + { success: false, error: 'CRON_SECRET is required in production' }, + 500, + ); + return true; + } + const request = context.switchToHttp().getRequest(); + if (request.get('authorization') !== `Bearer ${secret}`) + throw new HttpException({ success: false, error: 'Unauthorized' }, 401); + return true; + } +} diff --git a/apps/api/src/runtime/internal-cron.service.ts b/apps/api/src/runtime/internal-cron.service.ts new file mode 100644 index 00000000..7502f3a7 --- /dev/null +++ b/apps/api/src/runtime/internal-cron.service.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@nestjs/common'; +import { + runAuctionExpireJob, + runExpiredDataCleanupJob, + runMarketRefreshCronJob, + runMaterialLibraryDailyGenerationJob, + runRankRewardsJob, + runResourceReplayCleanupJob, + runSponsorshipAdminDigestJob, + runSponsorshipCleanupJob, + runSponsorshipReconcileJob, +} from '@server/lib/jobs/internalCron'; +@Injectable() +export class InternalCronService { + auctionExpire() { + return runAuctionExpireJob(); + } + rankRewards() { + return runRankRewardsJob(); + } + marketRefresh() { + return runMarketRefreshCronJob(); + } + resourceReplayCleanup() { + return runResourceReplayCleanupJob(); + } + expiredDataCleanup() { + return runExpiredDataCleanupJob(); + } + materialLibraryGeneration() { + return runMaterialLibraryDailyGenerationJob(); + } + sponsorshipReconcile() { + return runSponsorshipReconcileJob(false); + } + sponsorshipDeepReconcile() { + return runSponsorshipReconcileJob(true); + } + sponsorshipCleanup() { + return runSponsorshipCleanupJob(); + } + sponsorshipAdminDigest() { + return runSponsorshipAdminDigestJob(); + } +} diff --git a/apps/api/src/runtime/runtime.module.ts b/apps/api/src/runtime/runtime.module.ts new file mode 100644 index 00000000..9870564e --- /dev/null +++ b/apps/api/src/runtime/runtime.module.ts @@ -0,0 +1,23 @@ +import { DatabaseModule } from '../database/database.module'; +import { Module } from '@nestjs/common'; +import { ScheduleModule } from '@nestjs/schedule'; +import { RequestWorkService } from '../http/request-work.service'; +import { CronService } from './cron.service'; +import { InternalCronController } from './internal-cron.controller'; +import { InternalCronGuard } from './internal-cron.guard'; +import { InternalCronService } from './internal-cron.service'; +import { RuntimeService } from './runtime.service'; + +@Module({ + imports: [DatabaseModule, ScheduleModule.forRoot()], + controllers: [InternalCronController], + providers: [ + RuntimeService, + RequestWorkService, + CronService, + InternalCronGuard, + InternalCronService, + ], + exports: [RequestWorkService], +}) +export class RuntimeModule {} diff --git a/apps/api/src/runtime/runtime.service.ts b/apps/api/src/runtime/runtime.service.ts new file mode 100644 index 00000000..b699d0f9 --- /dev/null +++ b/apps/api/src/runtime/runtime.service.ts @@ -0,0 +1,74 @@ +import { + Inject, + Injectable, + type BeforeApplicationShutdown, + type OnApplicationBootstrap, + type OnApplicationShutdown, +} from '@nestjs/common'; +import { HttpAdapterHost } from '@nestjs/core'; +import { DatabaseService } from '../database/database.service'; +import { + registerMessageInfrastructure, + shutdownMessageInfrastructure, +} from '@server/lib/mq/domainEventRegistry'; +import { closeRedisConnection } from '@server/lib/redis'; +import type { Server } from 'node:http'; +import { RequestWorkService } from '../http/request-work.service'; +import { CronService } from './cron.service'; + +@Injectable() +export class RuntimeService + implements + OnApplicationBootstrap, + BeforeApplicationShutdown, + OnApplicationShutdown +{ + private drainTimer?: ReturnType; + + constructor( + @Inject(DatabaseService) private readonly database: DatabaseService, + @Inject(CronService) private readonly cron: CronService, + @Inject(HttpAdapterHost) private readonly http: HttpAdapterHost, + @Inject(RequestWorkService) private readonly work: RequestWorkService, + ) {} + + async onApplicationBootstrap(): Promise { + await registerMessageInfrastructure(); + this.cron.start(); + } + + async beforeApplicationShutdown(): Promise { + console.info('[runtime] stopping scheduled work; draining HTTP requests'); + await this.cron.stop(); + this.drainTimer = setTimeout(() => { + console.warn( + '[runtime] HTTP drain exceeded 30s; closing remaining connections', + ); + const server: Server = this.http.httpAdapter.getHttpServer(); + server.closeAllConnections(); + }, 30_000); + this.drainTimer.unref(); + } + + async onApplicationShutdown(): Promise { + clearTimeout(this.drainTimer); + await this.work.drain(); + console.info( + '[runtime] request handlers drained; stopping message infrastructure', + ); + await shutdownMessageInfrastructure(); + console.info('[runtime] message infrastructure stopped'); + console.info('[runtime] closing database and Redis connections'); + const results = await Promise.allSettled([ + this.database.close(), + closeRedisConnection(), + ]); + const errors = results.filter((result) => result.status === 'rejected'); + if (errors.length) + throw new AggregateError( + errors.map((result) => result.reason), + 'Runtime cleanup failed', + ); + console.info('[runtime] shutdown complete'); + } +} diff --git a/apps/api/src/sects/sect-combat.controller.ts b/apps/api/src/sects/sect-combat.controller.ts new file mode 100644 index 00000000..fcb4826f --- /dev/null +++ b/apps/api/src/sects/sect-combat.controller.ts @@ -0,0 +1,94 @@ +import { + Controller, + Get, + Header, + HttpCode, + Inject, + Post, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import { InventoryError } from '@server/lib/services/InventoryService'; +import { CombatV6BuildError } from '@server/lib/services/combat-v6/CombatV6BuildService'; +import { + SectPathSelectionRequestSchema, + type SectPathSelectionRequest, +} from '@daoyou/shared/contracts/combatV6'; +import { SectV6ActionSchema } from '@daoyou/shared/contracts/combatV6Sect'; +import { SectV6RuleError } from '@daoyou/shared/engine/combat-v6/sect-progression'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { CombatErrors } from '../combat/combat-errors'; +import { apiErrorFilter } from '../http/error-filter'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { SectCombatService } from './sect-combat.service'; + +const ProgressionErrors = apiErrorFilter((error) => { + const lock = redisLockErrorResponse(error); + if (lock) return lock; + if (error instanceof z.ZodError) + return Response.json( + { success: false, error: '参数无效' }, + { status: 400 }, + ); + if ( + error instanceof InventoryError || + error instanceof SectV6RuleError || + error instanceof CombatV6BuildError + ) + return Response.json( + { success: false, error: error.message }, + { status: 409 }, + ); + console.error('[sect-v6]', error); + return Response.json( + { success: false, error: '请求未完成,请刷新核对传承与资源' }, + { status: 500 }, + ); +}); + +@Controller('api/combat-v6/sect') +@Access('active') +export class SectCombatController { + constructor( + @Inject(SectCombatService) private readonly sect: SectCombatService, + ) {} + + @Get('state') + @UseFilters(CombatErrors) + state(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.sect.state(actor.cultivatorId); + } + + @Post('path') + @HttpCode(200) + @UseFilters(CombatErrors) + selectPath( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(SectPathSelectionRequestSchema)) + input: SectPathSelectionRequest, + ) { + return this.sect.selectPath(actor, input); + } + + @Get() + @Header('Cache-Control', 'no-store') + @UseFilters(ProgressionErrors) + read(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.sect.read(actor.cultivatorId); + } + + @Post() + @HttpCode(200) + @Header('Cache-Control', 'no-store') + @UseFilters(ProgressionErrors) + mutate( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody({ fallback: undefined }, new ZodPipe(SectV6ActionSchema)) + action: z.infer, + ) { + return this.sect.mutate(actor.cultivatorId, action); + } +} diff --git a/apps/api/src/sects/sect-combat.service.ts b/apps/api/src/sects/sect-combat.service.ts new file mode 100644 index 00000000..434cb6a8 --- /dev/null +++ b/apps/api/src/sects/sect-combat.service.ts @@ -0,0 +1,40 @@ +import { Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; +import { readResourceWithMeta } from '@server/lib/services/ResourceReadService'; +import { + getSectCombatView, + selectInitialSectPath, +} from '@server/lib/services/combat-v6/CombatV6BuildService'; +import { + mutateSectV6, + readSectV6, +} from '@server/lib/services/combat-v6/CombatV6SectService'; +import type { SectPathSelectionRequest } from '@daoyou/shared/contracts/combatV6'; +import type { SectV6ActionSchema } from '@daoyou/shared/contracts/combatV6Sect'; +import type { z } from 'zod'; + +@Injectable() +export class SectCombatService { + state(owner: string) { + return readResourceWithMeta( + { kind: 'cultivator', id: owner }, + 'player.sect-combat', + (tx) => getSectCombatView(owner, tx), + ); + } + async selectPath( + actor: ActiveCultivatorRef, + input: SectPathSelectionRequest, + ) { + return toPlayerStateMutationResponse( + await selectInitialSectPath(actor, input), + ); + } + async read(owner: string) { + return { success: true, data: await readSectV6(owner) }; + } + async mutate(owner: string, action: z.infer) { + return { success: true, ...(await mutateSectV6(owner, action)) }; + } +} diff --git a/apps/api/src/sects/sect-errors.ts b/apps/api/src/sects/sect-errors.ts new file mode 100644 index 00000000..a85e7cd7 --- /dev/null +++ b/apps/api/src/sects/sect-errors.ts @@ -0,0 +1,36 @@ +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import { PlayerCommandIdempotencyError } from '@server/lib/services/CommandExecutors'; +import { InventoryError } from '@server/lib/services/InventoryService'; +import { SectError } from '@server/lib/services/SectError'; +import { SectShopError } from '@server/lib/services/SectShopService'; +import { apiErrorFilter } from '../http/error-filter'; + +export const SectErrors = apiErrorFilter((error) => { + const lock = redisLockErrorResponse(error); + if (lock) return lock; + if (error instanceof PlayerCommandIdempotencyError) + return Response.json( + { success: false, error: error.message, code: error.code }, + { status: 409 }, + ); + if (error instanceof SectError) + return Response.json( + { success: false, error: error.message, code: error.code }, + { status: error.status }, + ); + if (error instanceof SectShopError) + return Response.json( + { success: false, error: error.message }, + { status: error.status }, + ); + if (error instanceof InventoryError) + return Response.json( + { success: false, error: error.message }, + { status: 409 }, + ); + console.error('[sects]', error); + return Response.json( + { success: false, error: '宗门事务处理失败' }, + { status: 500 }, + ); +}); diff --git a/apps/api/src/sects/sect-idempotency.ts b/apps/api/src/sects/sect-idempotency.ts new file mode 100644 index 00000000..03573800 --- /dev/null +++ b/apps/api/src/sects/sect-idempotency.ts @@ -0,0 +1,41 @@ +import { SectError } from '@server/lib/services/SectError'; +import { SectIdempotencyKeySchema } from '@daoyou/shared/contracts/sect'; +import { createHash } from 'node:crypto'; + +export type SectCommandRequest = { + key: string | undefined; + method: string; + path: string; +}; + +function canonicalize(value: unknown): string { + if (Array.isArray(value)) return `[${value.map(canonicalize).join(',')}]`; + if (value && typeof value === 'object') + return `{${Object.entries(value as Record) + .sort(([left], [right]) => left.localeCompare(right)) + .map(([key, item]) => `${JSON.stringify(key)}:${canonicalize(item)}`) + .join(',')}}`; + return JSON.stringify(value); +} + +export function requireSectIdempotency( + request: SectCommandRequest, + source: string, + payload: unknown, +) { + const parsed = SectIdempotencyKeySchema.safeParse(request.key); + if (!parsed.success) + throw new SectError( + 'SECT_ORGANIZATION_INVALID', + '缺少有效的 Idempotency-Key 请求头', + 400, + ); + return { + key: parsed.data, + fingerprint: createHash('sha256') + .update( + `${source}:${request.method}:${request.path}:${canonicalize(payload)}`, + ) + .digest('hex'), + }; +} diff --git a/apps/api/src/sects/sect-organization.service.ts b/apps/api/src/sects/sect-organization.service.ts new file mode 100644 index 00000000..14d05c89 --- /dev/null +++ b/apps/api/src/sects/sect-organization.service.ts @@ -0,0 +1,301 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { getExecutor } from '@server/lib/drizzle/db'; +import { cultivators } from '@server/lib/drizzle/schema'; +import { findMembership } from '@server/lib/repositories/sectRepository'; +import type { CommittedCommand } from '@server/lib/services/CommandExecutors'; +import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; +import { + readResourceWithMeta, + readResourceWithResolvedScope, +} from '@server/lib/services/ResourceReadService'; +import { SectError } from '@server/lib/services/SectError'; +import { sectOrganizationFacade } from '@server/lib/services/sect-organization'; +import { + createPostgresSectConstructionQueryContext, + createPostgresSectEconomyContext, + createPostgresSectMembershipQueryContext, + createPostgresSectQueryContext, +} from '@server/lib/services/sect-organization/PostgresSectOrganizationAdapters'; +import { executeSectConstructionDonationCommand } from '@server/lib/services/sect-organization/SectConstructionCommand'; +import { + executeSectShopPurchaseCommand, + executeSectStipendClaimCommand, +} from '@server/lib/services/sect-organization/SectEconomyCommand'; +import { + executeSectJoinCommand, + executeSectPromotionCommand, +} from '@server/lib/services/sect-organization/SectMembershipCommand'; +import { executeSectTaskActionCommand } from '@server/lib/services/sect-organization/SectTaskCommand'; +import { previewSectTransfer } from '@server/lib/services/sect-organization/SectTransferApplicationService'; +import { executeSectTransferCommand } from '@server/lib/services/sect-organization/SectTransferCommand'; +import type { SectCommandArgs } from '@server/lib/services/sect-organization/commandSupport'; +import type { + SectDonationRequestSchema, + SectTaskActionRequestSchema, + SectTransferPreviewQuerySchema, + SectTransferRequestSchema, +} from '@daoyou/shared/contracts/sect'; +import { SectShopBuyParamsSchema } from '@daoyou/shared/contracts/sectShop'; +import { productionSectRuntime as runtime } from '@daoyou/shared/engine/sect/content'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; +import { eq } from 'drizzle-orm'; +import type { z } from 'zod'; +import { + requireSectIdempotency, + type SectCommandRequest, +} from './sect-idempotency'; + +@Injectable() +export class SectOrganizationService { + infrastructure(actor: ActiveCultivatorRef) { + return readResourceWithResolvedScope('sect.infrastructure', async (q) => { + const membership = await findMembership(actor.cultivatorId, q); + if (!membership) + throw new SectError('SECT_MEMBERSHIP_REQUIRED', '尚未拜入宗门', 404); + return { + scope: { kind: 'sect', id: membership.sectId }, + data: await sectOrganizationFacade.membership.getInfrastructureResource( + actor.cultivatorId, + createPostgresSectMembershipQueryContext({ q, runtime }), + ), + }; + }); + } + + async stipend(actor: ActiveCultivatorRef) { + const q = getExecutor(); + const cultivator = await q.query.cultivators.findFirst({ + columns: { id: true, realm: true }, + where: eq(cultivators.id, actor.cultivatorId), + }); + if (!cultivator) + throw new SectError('SECT_MEMBERSHIP_REQUIRED', '角色不存在', 404); + const data = await sectOrganizationFacade.membership.getStipendResource( + { id: cultivator.id, realm: cultivator.realm as RealmType }, + createPostgresSectMembershipQueryContext({ q, runtime }), + ); + return { success: true, data }; + } + + async promotionEvaluation(actor: ActiveCultivatorRef) { + const q = getExecutor(); + const cultivator = await q.query.cultivators.findFirst({ + columns: { id: true, realm: true, realm_stage: true }, + where: eq(cultivators.id, actor.cultivatorId), + }); + if (!cultivator) + throw new SectError('SECT_MEMBERSHIP_REQUIRED', '角色不存在', 404); + const data = + await sectOrganizationFacade.membership.getPromotionEvaluationResource( + { + id: cultivator.id, + realm: cultivator.realm as RealmType, + realm_stage: cultivator.realm_stage as RealmStage, + }, + createPostgresSectMembershipQueryContext({ q, runtime }), + ); + return { success: true, data }; + } + + tasks(actor: ActiveCultivatorRef) { + return readResourceWithMeta( + { kind: 'cultivator', id: actor.cultivatorId }, + 'sect.tasks', + (q) => + sectOrganizationFacade.tasks.queries.execute( + { cultivatorId: actor.cultivatorId }, + createPostgresSectQueryContext({ q, runtime }), + ), + ); + } + + async submissionCandidates(actor: ActiveCultivatorRef, taskId: string) { + if (!taskId || taskId.length > 64) + throw new HttpException({ success: false, error: '任务编号无效' }, 400); + return { + success: true, + data: await sectOrganizationFacade.tasks.submissions.execute( + { cultivatorId: actor.cultivatorId, taskId }, + createPostgresSectQueryContext({ q: getExecutor(), runtime }), + ), + }; + } + + shop(actor: ActiveCultivatorRef) { + return readResourceWithMeta( + { kind: 'cultivator', id: actor.cultivatorId }, + 'sect.shop', + (q) => + sectOrganizationFacade.economy.getShop( + actor.cultivatorId, + createPostgresSectEconomyContext({ q, runtime }), + ), + ); + } + + constructionMember(actor: ActiveCultivatorRef) { + return readResourceWithMeta( + { kind: 'cultivator', id: actor.cultivatorId }, + 'sect.construction-member', + (q) => + sectOrganizationFacade.construction.getConstructionMember( + actor.userId, + actor.cultivatorId, + createPostgresSectConstructionQueryContext({ q, runtime }), + ), + ); + } + + members( + actor: ActiveCultivatorRef, + query: { page: number; pageSize: number }, + ) { + return readResourceWithResolvedScope('sect.members', async (q) => { + const membership = await findMembership(actor.cultivatorId, q); + if (!membership) + throw new SectError('SECT_MEMBERSHIP_REQUIRED', '尚未拜入宗门', 404); + return { + scope: { kind: 'sect', id: membership.sectId }, + data: await sectOrganizationFacade.membership.listMembers( + actor.cultivatorId, + query.page, + query.pageSize, + createPostgresSectMembershipQueryContext({ q, runtime }), + ), + }; + }); + } + + async transferPreview( + actor: ActiveCultivatorRef, + query: z.infer, + ) { + return { + success: true, + data: await previewSectTransfer({ + cultivatorId: actor.cultivatorId, + ...query, + runtime, + q: getExecutor(), + }), + }; + } + + taskAction( + actor: ActiveCultivatorRef, + request: SectCommandRequest, + taskId: string, + actionKey: string, + body: z.infer, + ) { + if (taskId.length > 64 || actionKey.length > 64) + throw new HttpException( + { success: false, error: '任务操作编号无效' }, + 400, + ); + return this.mutate( + actor, + request, + 'sect_task_action', + { taskId, actionKey, input: body.input }, + (args) => + executeSectTaskActionCommand({ + ...args, + taskId, + actionKey, + requestId: request.key ?? '', + input: body.input, + }), + ); + } + + promote(actor: ActiveCultivatorRef, request: SectCommandRequest) { + return this.mutate( + actor, + request, + 'sect_promotion', + null, + executeSectPromotionCommand, + ); + } + + buy(actor: ActiveCultivatorRef, request: SectCommandRequest, id: string) { + const parsed = SectShopBuyParamsSchema.safeParse({ id }); + if (!parsed.success) + throw new HttpException({ success: false, error: '商品编号无效' }, 400); + return this.mutate( + actor, + request, + 'sect_shop_purchase', + parsed.data, + (args) => + executeSectShopPurchaseCommand({ ...args, itemId: parsed.data.id }), + ); + } + + donate( + actor: ActiveCultivatorRef, + request: SectCommandRequest, + body: z.infer, + ) { + return this.mutate( + actor, + request, + 'sect_construction_donate', + body, + (args) => executeSectConstructionDonationCommand({ ...args, ...body }), + ); + } + + claimStipend(actor: ActiveCultivatorRef, request: SectCommandRequest) { + return this.mutate( + actor, + request, + 'sect_stipend_claim', + null, + executeSectStipendClaimCommand, + ); + } + + transfer( + actor: ActiveCultivatorRef, + request: SectCommandRequest, + body: z.infer, + ) { + return this.mutate(actor, request, 'sect_transfer', body, (args) => + executeSectTransferCommand({ ...args, ...body }), + ); + } + + join( + actor: ActiveCultivatorRef, + request: SectCommandRequest, + sectId: string, + ) { + return this.mutate(actor, request, 'sect_join', { sectId }, (args) => + executeSectJoinCommand({ + ...args, + sectId, + admission: (q) => sectOrganizationFacade.admission(q, runtime), + }), + ); + } + + private async mutate( + actor: ActiveCultivatorRef, + request: SectCommandRequest, + source: string, + payload: unknown, + run: (args: SectCommandArgs) => Promise>, + ) { + const committed = await run({ + userId: actor.userId, + cultivatorId: actor.cultivatorId, + source, + idempotency: requireSectIdempotency(request, source, payload), + runtime, + }); + return toPlayerStateMutationResponse(committed); + } +} diff --git a/apps/api/src/sects/sect-social.controller.ts b/apps/api/src/sects/sect-social.controller.ts new file mode 100644 index 00000000..81eda61f --- /dev/null +++ b/apps/api/src/sects/sect-social.controller.ts @@ -0,0 +1,72 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Post, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { + SectChatListQuerySchema, + WorldChatCreateMessageSchema, + type SectChatListQuery, + type WorldChatCreateMessageRequest, +} from '@daoyou/shared/contracts/world-chat'; +import { Access, CurrentCultivator } from '../auth/access'; +import { RejectRetiredBattleSharePipe } from '../http/chat-input.pipe'; +import { apiErrorFilter } from '../http/error-filter'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { SectSocialService } from './sect-social.service'; + +const ContributionErrors = apiErrorFilter((error) => { + if (error instanceof Error && error.message === 'SECT_MEMBERSHIP_REQUIRED') + return Response.json( + { success: false, error: '尚未拜入宗门' }, + { status: 404 }, + ); + console.error('[sect-ranking] read failed', error); + return Response.json( + { success: false, error: '贡献榜读取失败' }, + { status: 500 }, + ); +}); + +@Controller('api/sects/current') +@Access('active') +export class SectSocialController { + constructor( + @Inject(SectSocialService) private readonly social: SectSocialService, + ) {} + + @Get('chat/messages') + list( + @CurrentCultivator() actor: ActiveCultivatorRef, + @FirstQuery(new ZodPipe(SectChatListQuerySchema, 'legacy-unhandled')) + query: SectChatListQuery, + ) { + return this.social.list(actor, query); + } + + @Post('chat/messages') + @HttpCode(200) + create( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody( + { fallback: undefined }, + new RejectRetiredBattleSharePipe(), + new ZodPipe(WorldChatCreateMessageSchema, 'legacy-unhandled'), + ) + body: WorldChatCreateMessageRequest, + ) { + return this.social.create(actor, body); + } + + @Get('contribution-ranking') + @UseFilters(ContributionErrors) + contributionRanking(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.social.contributionRanking(actor); + } +} diff --git a/apps/api/src/sects/sect-social.service.ts b/apps/api/src/sects/sect-social.service.ts new file mode 100644 index 00000000..3ab5dc0e --- /dev/null +++ b/apps/api/src/sects/sect-social.service.ts @@ -0,0 +1,157 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { getExecutor } from '@server/lib/drizzle/db'; +import { checkAndAcquireSectChatCooldown } from '@server/lib/redis/worldChatLimiter'; +import { + createSectChatMessage, + listSectChatMessages, +} from '@server/lib/repositories/sectChatRepository'; +import { + countSectMembersAboveLifetimeContribution, + findSectContributionRankingMember, + listTopSectContributionRanking, +} from '@server/lib/repositories/sectOrganizationRepository'; +import { findMembership } from '@server/lib/repositories/sectRepository'; +import { + ChatMessageApplicationError, + createCultivatorChatMessage, +} from '@server/lib/services/chatMessageApplication'; +import { readResourceWithResolvedScope } from '@server/lib/services/ResourceReadService'; +import type { + SectContributionRankingData, + SectContributionRankingEntry, +} from '@daoyou/shared/contracts/sect'; +import type { + SectChatListQuery, + WorldChatCreateMessageRequest, +} from '@daoyou/shared/contracts/world-chat'; +import type { SectDiscipleRank, SectOffice } from '@daoyou/shared/engine/sect'; + +@Injectable() +export class SectSocialService { + async list(actor: ActiveCultivatorRef, query: SectChatListQuery) { + const membership = await this.membership(actor); + const result = await listSectChatMessages({ + sectId: membership.sectId, + page: query.page, + pageSize: query.pageSize, + }); + return { + success: true, + data: result.messages, + pagination: { + page: query.page, + pageSize: query.pageSize, + hasMore: result.hasMore, + }, + }; + } + + async create( + actor: ActiveCultivatorRef, + request: WorldChatCreateMessageRequest, + ) { + // Membership lookup failures retain the global error contract, as before. + const membership = await this.membership(actor); + try { + const message = await createCultivatorChatMessage({ + request, + userId: actor.userId, + cultivatorId: actor.cultivatorId, + channel: 'sect', + sectId: membership.sectId, + acquireCooldown: checkAndAcquireSectChatCooldown, + persist: createSectChatMessage, + }); + return { success: true, data: message }; + } catch (error) { + if (error instanceof ChatMessageApplicationError) + throw new HttpException( + { + success: false, + error: error.message, + ...(error.remainingSeconds + ? { remainingSeconds: error.remainingSeconds } + : {}), + }, + error.status, + ); + console.error('[sect-chat] create message failed', error); + throw new HttpException( + { success: false, error: '发送失败,请稍后重试' }, + 500, + ); + } + } + + private async membership(actor: ActiveCultivatorRef) { + const membership = await findMembership(actor.cultivatorId, getExecutor()); + if (!membership) + throw new HttpException({ success: false, error: '尚未拜入宗门' }, 404); + return membership; + } + + contributionRanking(actor: ActiveCultivatorRef) { + return readResourceWithResolvedScope( + 'sect.contribution-ranking', + async (q) => { + const membership = await findMembership(actor.cultivatorId, q); + if (!membership) { + throw new Error('SECT_MEMBERSHIP_REQUIRED'); + } + const rows = await listTopSectContributionRanking(membership.sectId, q); + let previousContribution: number | undefined; + let rank = 0; + const entries = rows.map((row, index): SectContributionRankingEntry => { + if (row.lifetimeContribution !== previousContribution) { + rank = index + 1; + previousContribution = row.lifetimeContribution; + } + return { + rank, + cultivatorId: row.cultivatorId, + name: row.name, + discipleRank: row.discipleRank as SectDiscipleRank, + office: row.office as SectOffice, + contribution: row.lifetimeContribution, + }; + }); + let currentMember = entries.find( + (entry) => entry.cultivatorId === actor.cultivatorId, + ); + if (!currentMember) { + const currentRow = await findSectContributionRankingMember( + membership.sectId, + actor.cultivatorId, + q, + ); + if (!currentRow) { + throw new Error('SECT_MEMBERSHIP_REQUIRED'); + } + currentMember = { + rank: + (await countSectMembersAboveLifetimeContribution( + membership.sectId, + currentRow.lifetimeContribution, + q, + )) + 1, + cultivatorId: currentRow.cultivatorId, + name: currentRow.name, + discipleRank: currentRow.discipleRank as SectDiscipleRank, + office: currentRow.office as SectOffice, + contribution: currentRow.lifetimeContribution, + }; + } + return { + scope: { kind: 'sect', id: membership.sectId }, + data: { + metric: 'lifetime_contribution', + generatedAt: new Date().toISOString(), + entries: entries.slice(0, 20), + currentMember, + } satisfies SectContributionRankingData, + }; + }, + ); + } +} diff --git a/apps/api/src/sects/sects.controller.ts b/apps/api/src/sects/sects.controller.ts new file mode 100644 index 00000000..3dddc85f --- /dev/null +++ b/apps/api/src/sects/sects.controller.ts @@ -0,0 +1,225 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Param, + Post, + Put, + Req, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { + SectDonationRequestSchema, + SectMembersQuerySchema, + SectTaskActionRequestSchema, + SectTransferPreviewQuerySchema, + SectTransferRequestSchema, +} from '@daoyou/shared/contracts/sect'; +import type { Request } from 'express'; +import type { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { SectErrors } from './sect-errors'; +import type { SectCommandRequest } from './sect-idempotency'; +import { SectOrganizationService } from './sect-organization.service'; +import { SectsService } from './sects.service'; + +const retiredPaths = [ + 'current/methods/:methodId/train', + 'current/paths/:pathId/layers/:layerId/unlock', + 'current/paths/:pathId/activate', + 'current/paths/:pathId/meridian-loadouts/:slot', + 'current/paths/:pathId/meridian-loadouts/:slot/activate', + 'current/ability-loadout', + 'current/paths/:pathId/tactic', +]; +function commandRequest(request: Request): SectCommandRequest { + return { + key: request.get('Idempotency-Key'), + method: request.method, + path: request.path, + }; +} + +@Controller('api/sects') +@Access('active') +@UseFilters(SectErrors) +export class SectsController { + constructor( + @Inject(SectsService) private readonly sects: SectsService, + @Inject(SectOrganizationService) + private readonly organization: SectOrganizationService, + ) {} + + @Get('current/context') + context(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.sects.context(actor.cultivatorId); + } + + @Get('current/infrastructure') + infrastructure(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.organization.infrastructure(actor); + } + + @Get('current/stipend') + stipend(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.organization.stipend(actor); + } + + @Get('current/promotion-evaluation') + promotionEvaluation(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.organization.promotionEvaluation(actor); + } + + @Get('current/tasks') + tasks(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.organization.tasks(actor); + } + + @Get('current/tasks/:taskId/submission-candidates') + submissionCandidates( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('taskId') taskId: string, + ) { + return this.organization.submissionCandidates(actor, taskId); + } + + @Get('current/shop') + shop(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.organization.shop(actor); + } + + @Get('current/construction-member') + constructionMember(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.organization.constructionMember(actor); + } + + @Get('current/members') + members( + @CurrentCultivator() actor: ActiveCultivatorRef, + @FirstQuery(new ZodPipe(SectMembersQuerySchema, 'legacy-unhandled')) + query: z.infer, + ) { + return this.organization.members(actor, query); + } + + @Post('current/tasks/:taskId/actions/:actionKey') + @HttpCode(200) + taskAction( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Req() request: Request, + @Param('taskId') taskId: string, + @Param('actionKey') actionKey: string, + @JsonBody( + { fallback: undefined }, + new ZodPipe(SectTaskActionRequestSchema, 'legacy-unhandled'), + ) + body: z.infer, + ) { + return this.organization.taskAction( + actor, + commandRequest(request), + taskId, + actionKey, + body, + ); + } + + @Post('current/promotion') + @HttpCode(200) + promote( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Req() request: Request, + ) { + return this.organization.promote(actor, commandRequest(request)); + } + + @Post('current/shop/:id/buy') + @HttpCode(200) + buy( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Req() request: Request, + @Param('id') id: string, + ) { + return this.organization.buy(actor, commandRequest(request), id); + } + + @Post('current/construction/donate') + @HttpCode(200) + donate( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Req() request: Request, + @JsonBody( + { fallback: undefined }, + new ZodPipe(SectDonationRequestSchema, 'legacy-unhandled'), + ) + body: z.infer, + ) { + return this.organization.donate(actor, commandRequest(request), body); + } + + @Post('current/stipend/claim') + @HttpCode(200) + claimStipend( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Req() request: Request, + ) { + return this.organization.claimStipend(actor, commandRequest(request)); + } + + @Get('current/transfer/preview') + transferPreview( + @CurrentCultivator() actor: ActiveCultivatorRef, + @FirstQuery(new ZodPipe(SectTransferPreviewQuerySchema, 'legacy-unhandled')) + query: z.infer, + ) { + return this.organization.transferPreview(actor, query); + } + + @Post('current/transfer') + @HttpCode(200) + transfer( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Req() request: Request, + @JsonBody( + { fallback: undefined }, + new ZodPipe(SectTransferRequestSchema, 'legacy-unhandled'), + ) + body: z.infer, + ) { + return this.organization.transfer(actor, commandRequest(request), body); + } + + @Post(':sectId/join') + @HttpCode(200) + join( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Req() request: Request, + @Param('sectId') sectId: string, + ) { + return this.organization.join(actor, commandRequest(request), sectId); + } + + @Post(retiredPaths) + @HttpCode(410) + retiredPost() { + return this.retired(); + } + + @Put(retiredPaths) + @HttpCode(410) + retiredPut() { + return this.retired(); + } + + private retired() { + return { + success: false, + error: '旧宗门战斗养成已停用,请刷新使用新版心法与经脉', + }; + } +} diff --git a/apps/api/src/sects/sects.module.ts b/apps/api/src/sects/sects.module.ts new file mode 100644 index 00000000..2664ba17 --- /dev/null +++ b/apps/api/src/sects/sects.module.ts @@ -0,0 +1,19 @@ +import { Module } from '@nestjs/common'; +import { SectCombatController } from './sect-combat.controller'; +import { SectCombatService } from './sect-combat.service'; +import { SectOrganizationService } from './sect-organization.service'; +import { SectSocialController } from './sect-social.controller'; +import { SectSocialService } from './sect-social.service'; +import { SectsController } from './sects.controller'; +import { SectsService } from './sects.service'; + +@Module({ + controllers: [SectsController, SectCombatController, SectSocialController], + providers: [ + SectsService, + SectCombatService, + SectOrganizationService, + SectSocialService, + ], +}) +export class SectsModule {} diff --git a/apps/api/src/sects/sects.service.ts b/apps/api/src/sects/sects.service.ts new file mode 100644 index 00000000..1dba2c28 --- /dev/null +++ b/apps/api/src/sects/sects.service.ts @@ -0,0 +1,41 @@ +import { Injectable } from '@nestjs/common'; +import { findMembership } from '@server/lib/repositories/sectRepository'; +import { readResourceWithMeta } from '@server/lib/services/ResourceReadService'; +import { SectError } from '@server/lib/services/SectError'; +import type { SectContextData } from '@daoyou/shared/contracts/sect'; +import type { SectDiscipleRank } from '@daoyou/shared/engine/sect'; +import { productionSectRuntime } from '@daoyou/shared/engine/sect/content'; + +@Injectable() +export class SectsService { + context(cultivatorId: string) { + return readResourceWithMeta( + { kind: 'cultivator', id: cultivatorId }, + 'sect.membership', + async (q) => { + const membership = await findMembership(cultivatorId, q); + if (!membership) + throw new SectError('SECT_MEMBERSHIP_REQUIRED', '尚未拜入宗门', 404); + const organization = productionSectRuntime.registry.require( + membership.sectId, + ).organization; + return { + sectId: membership.sectId, + membershipId: membership.id, + status: membership.status as SectContextData['status'], + joinedAt: membership.joinedAt?.toISOString(), + discipleRank: + membership.discipleRank as SectContextData['discipleRank'], + contribution: membership.contribution, + lifetimeContribution: membership.lifetimeContribution, + office: membership.office as SectContextData['office'], + promotedAt: membership.promotedAt?.toISOString(), + permissions: organization.capabilities.snapshot( + membership.discipleRank as SectDiscipleRank, + ), + configVersion: membership.configVersion, + } satisfies SectContextData; + }, + ); + } +} diff --git a/apps/api/src/social/friends.controller.ts b/apps/api/src/social/friends.controller.ts new file mode 100644 index 00000000..46267d3f --- /dev/null +++ b/apps/api/src/social/friends.controller.ts @@ -0,0 +1,82 @@ +import { + Controller, + Delete, + Get, + HttpCode, + Inject, + Param, + Post, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { FriendServiceError } from '@server/lib/services/FriendService'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { FirstQuery } from '../http/first-query'; +import { ZodPipe } from '../http/zod.pipe'; +import { FriendsService } from './friends.service'; + +const TargetSchema = z.object({ cultivatorId: z.string().uuid() }); +const SearchSchema = z.object({ name: z.string().trim().min(1).max(100) }); +const friendErrors = (fallback: string, handleDomain = true) => + apiErrorFilter((error) => { + if (error instanceof z.ZodError) + return Response.json( + { error: '参数错误', details: error.issues }, + { status: 400 }, + ); + if (handleDomain && error instanceof FriendServiceError) + return Response.json({ error: error.message }, { status: error.status }); + console.error('Friend API error:', error); + return Response.json({ error: fallback }, { status: 500 }); + }); + +@Controller('api/friends') +@Access('active') +export class FriendsController { + constructor( + @Inject(FriendsService) private readonly friends: FriendsService, + ) {} + + @Get() + list(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.friends.list(actor.cultivatorId); + } + + @Get('search') + search( + @CurrentCultivator() actor: ActiveCultivatorRef, + @FirstQuery(new ZodPipe(SearchSchema)) query: z.infer, + ) { + return this.friends.search(actor.cultivatorId, query.name); + } + + @Get('invite/:cultivatorId') + @UseFilters(friendErrors('查询道友失败')) + invite( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param(new ZodPipe(TargetSchema)) params: z.infer, + ) { + return this.friends.invite(actor.cultivatorId, params.cultivatorId); + } + + @Post(':cultivatorId') + @HttpCode(200) + @UseFilters(friendErrors('添加道友失败')) + add( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param(new ZodPipe(TargetSchema)) params: z.infer, + ) { + return this.friends.add(actor.cultivatorId, params.cultivatorId); + } + + @Delete(':cultivatorId') + @UseFilters(friendErrors('移除道友失败', false)) + remove( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param(new ZodPipe(TargetSchema)) params: z.infer, + ) { + return this.friends.remove(actor.cultivatorId, params.cultivatorId); + } +} diff --git a/apps/api/src/social/friends.service.ts b/apps/api/src/social/friends.service.ts new file mode 100644 index 00000000..eed8366b --- /dev/null +++ b/apps/api/src/social/friends.service.ts @@ -0,0 +1,49 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import { acquireRedisCooldown } from '@server/lib/redis/cooldownLimiter'; +import { + addFriendPair, + getInviteTarget, + listFriends, + removeFriendPair, + searchActiveCultivatorsByExactName, +} from '@server/lib/services/FriendService'; +import { FRIEND_SEARCH_COOLDOWN_SECONDS } from '@daoyou/shared/config/socialConfig'; + +@Injectable() +export class FriendsService { + async list(cultivatorId: string) { + return { friends: await listFriends(cultivatorId) }; + } + + async search(cultivatorId: string, name: string) { + const cooldown = await acquireRedisCooldown({ + key: `friends:search:cooldown:${cultivatorId}`, + cooldownSeconds: FRIEND_SEARCH_COOLDOWN_SECONDS, + allowWhenRedisUnavailable: true, + }); + if (!cooldown.allowed) + throw new HttpException( + { error: `请 ${cooldown.remainingSeconds} 秒后再搜索` }, + 429, + ); + return { + results: await searchActiveCultivatorsByExactName(cultivatorId, name), + }; + } + + invite(cultivatorId: string, target: string) { + return getInviteTarget(cultivatorId, target); + } + + async add(cultivatorId: string, target: string) { + return { + friend: await addFriendPair(cultivatorId, target), + message: '已加入好友名录', + }; + } + + async remove(cultivatorId: string, target: string) { + await removeFriendPair(cultivatorId, target); + return { message: '已移出好友名录' }; + } +} diff --git a/apps/api/src/social/social.module.ts b/apps/api/src/social/social.module.ts new file mode 100644 index 00000000..8a5e0b3b --- /dev/null +++ b/apps/api/src/social/social.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { FriendsController } from './friends.controller'; +import { FriendsService } from './friends.service'; +import { WorldChatController } from './world-chat.controller'; +import { WorldChatService } from './world-chat.service'; + +@Module({ + controllers: [WorldChatController, FriendsController], + providers: [WorldChatService, FriendsService], +}) +export class SocialModule {} diff --git a/apps/api/src/social/world-chat.controller.ts b/apps/api/src/social/world-chat.controller.ts new file mode 100644 index 00000000..7d97fceb --- /dev/null +++ b/apps/api/src/social/world-chat.controller.ts @@ -0,0 +1,77 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Post, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { ChatMessageApplicationError } from '@server/lib/services/chatMessageApplication'; +import { + WorldChatCreateMessageSchema, + WorldChatListQuerySchema, + type WorldChatCreateMessageRequest, + type WorldChatListQuery, +} from '@daoyou/shared/contracts/world-chat'; +import { ZodError } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { RejectRetiredBattleSharePipe } from '../http/chat-input.pipe'; +import { apiErrorFilter } from '../http/error-filter'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { WorldChatService } from './world-chat.service'; + +const SendErrors = apiErrorFilter((error) => { + if (error instanceof ZodError) return undefined; + if (error instanceof ChatMessageApplicationError) { + return Response.json( + { + success: false, + error: error.message, + ...(error.remainingSeconds + ? { remainingSeconds: error.remainingSeconds } + : {}), + }, + { status: error.status }, + ); + } + console.error('Create world chat message error:', error); + return Response.json( + { success: false, error: '发送失败,请稍后重试' }, + { status: 500 }, + ); +}); + +@Controller('api/world-chat') +export class WorldChatController { + constructor( + @Inject(WorldChatService) private readonly chat: WorldChatService, + ) {} + + @Get('messages') + @Access('public') + list( + @FirstQuery(new ZodPipe(WorldChatListQuerySchema)) + query: WorldChatListQuery, + ) { + return this.chat.list(query); + } + + @Post('messages') + @HttpCode(200) + @Access('active') + @UseFilters(SendErrors) + create( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody( + { fallback: undefined }, + new RejectRetiredBattleSharePipe(), + new ZodPipe(WorldChatCreateMessageSchema), + ) + body: WorldChatCreateMessageRequest, + ) { + return this.chat.create(actor, body); + } +} diff --git a/apps/api/src/social/world-chat.service.ts b/apps/api/src/social/world-chat.service.ts new file mode 100644 index 00000000..cac8ec2f --- /dev/null +++ b/apps/api/src/social/world-chat.service.ts @@ -0,0 +1,53 @@ +import { Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { checkAndAcquireCooldown } from '@server/lib/redis/worldChatLimiter'; +import { + createMessage, + listLatestMessages, + listMessages, +} from '@server/lib/repositories/worldChatRepository'; +import { createCultivatorChatMessage } from '@server/lib/services/chatMessageApplication'; +import type { + WorldChatCreateMessageRequest, + WorldChatListQuery, +} from '@daoyou/shared/contracts/world-chat'; + +@Injectable() +export class WorldChatService { + async list({ channel, limit, page, pageSize }: WorldChatListQuery) { + if (limit) + return { success: true, data: await listLatestMessages(limit, channel) }; + const currentPage = page || 1; + const currentPageSize = pageSize || 20; + const result = await listMessages({ + channel, + page: currentPage, + pageSize: currentPageSize, + }); + return { + success: true, + data: result.messages, + pagination: { + page: currentPage, + pageSize: currentPageSize, + hasMore: result.hasMore, + }, + }; + } + + async create( + actor: ActiveCultivatorRef, + request: WorldChatCreateMessageRequest, + ) { + const message = await createCultivatorChatMessage({ + request, + userId: actor.userId, + cultivatorId: actor.cultivatorId, + channel: 'world', + sectId: null, + acquireCooldown: checkAndAcquireCooldown, + persist: (input) => createMessage({ ...input, channel: 'world' }), + }); + return { success: true, data: message }; + } +} diff --git a/apps/api/src/spirit-field/spirit-field.controller.ts b/apps/api/src/spirit-field/spirit-field.controller.ts new file mode 100644 index 00000000..5bfae049 --- /dev/null +++ b/apps/api/src/spirit-field/spirit-field.controller.ts @@ -0,0 +1,136 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Post, + Res, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import { PlayerCommandIdempotencyError } from '@server/lib/services/CommandExecutors'; +import { InventoryError } from '@server/lib/services/InventoryService'; +import { + QiInsufficientError, + QiServiceError, +} from '@server/lib/services/QiService'; +import { SpiritFieldServiceError } from '@server/lib/services/spirit-field/SpiritFieldService'; +import { + SpiritFieldCultivateRequestSchema, + SpiritFieldHarvestRequestSchema, + SpiritFieldSowRequestSchema, +} from '@daoyou/shared/contracts/spiritField'; +import type { Response as ExpressResponse } from 'express'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { JsonBody } from '../http/json-body'; +import { withRequestAbort } from '../http/request-abort'; +import { ZodPipe } from '../http/zod.pipe'; +import { SpiritFieldService } from './spirit-field.service'; + +const SpiritFieldErrors = apiErrorFilter((error) => { + const lock = redisLockErrorResponse(error); + if (lock) return lock; + if ( + error instanceof InventoryError || + error instanceof PlayerCommandIdempotencyError + ) + return Response.json( + { success: false, error: error.message }, + { status: 409 }, + ); + if (error instanceof z.ZodError) + return Response.json( + { + success: false, + error: error.issues[0]?.message || '参数错误', + details: error.issues, + }, + { status: 400 }, + ); + if (error instanceof SpiritFieldServiceError) + return Response.json( + { success: false, error: error.message }, + { status: error.status }, + ); + if (error instanceof QiInsufficientError) + return Response.json( + { + success: false, + error: error.code, + message: error.message, + required: error.required, + current: error.current, + action: error.action, + }, + { status: 409 }, + ); + if (error instanceof QiServiceError) + return Response.json( + { success: false, error: error.message }, + { status: error.status }, + ); + console.error('spirit field api error:', error); + return Response.json( + { success: false, error: '灵田灵机暂乱,请稍后再试' }, + { status: 500 }, + ); +}); + +@Controller('api/spirit-field') +@Access('active') +@UseFilters(SpiritFieldErrors) +export class SpiritFieldController { + constructor( + @Inject(SpiritFieldService) private readonly field: SpiritFieldService, + ) {} + + @Get() + read(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.field.read(actor); + } + + @Post('starter') + @HttpCode(200) + starter(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.field.starter(actor); + } + + @Post('sow') + @HttpCode(200) + sow( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(SpiritFieldSowRequestSchema)) + input: z.infer, + ) { + return this.field.sow(actor, input); + } + + @Post('cultivate') + @HttpCode(200) + cultivate( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(SpiritFieldCultivateRequestSchema)) + input: z.infer, + @Res({ passthrough: true }) response: ExpressResponse, + ) { + return withRequestAbort(response, (signal) => + this.field.cultivate(actor, input, signal), + ); + } + + @Post('harvest') + @HttpCode(200) + harvest( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(SpiritFieldHarvestRequestSchema)) + input: z.infer, + @Res({ passthrough: true }) response: ExpressResponse, + ) { + return withRequestAbort(response, (signal) => + this.field.harvest(actor, input, signal), + ); + } +} diff --git a/apps/api/src/spirit-field/spirit-field.module.ts b/apps/api/src/spirit-field/spirit-field.module.ts new file mode 100644 index 00000000..e91c37f5 --- /dev/null +++ b/apps/api/src/spirit-field/spirit-field.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { SpiritFieldController } from './spirit-field.controller'; +import { SpiritFieldService } from './spirit-field.service'; + +@Module({ + controllers: [SpiritFieldController], + providers: [SpiritFieldService], +}) +export class SpiritFieldModule {} diff --git a/apps/api/src/spirit-field/spirit-field.service.ts b/apps/api/src/spirit-field/spirit-field.service.ts new file mode 100644 index 00000000..c743308f --- /dev/null +++ b/apps/api/src/spirit-field/spirit-field.service.ts @@ -0,0 +1,52 @@ +import { Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; +import { + claimSpiritFieldStarterSeeds, + cultivateSpiritField, + getSpiritFieldSnapshot, + harvestSpiritField, + sowSpiritField, +} from '@server/lib/services/spirit-field/SpiritFieldService'; +import type { + SpiritFieldCultivateRequest, + SpiritFieldHarvestRequest, + SpiritFieldSowRequest, +} from '@daoyou/shared/contracts/spiritField'; + +@Injectable() +export class SpiritFieldService { + async read(actor: ActiveCultivatorRef) { + return { success: true, data: await getSpiritFieldSnapshot(actor) }; + } + + async starter(actor: ActiveCultivatorRef) { + return toPlayerStateMutationResponse( + await claimSpiritFieldStarterSeeds(actor), + ); + } + + async sow(actor: ActiveCultivatorRef, input: SpiritFieldSowRequest) { + return toPlayerStateMutationResponse(await sowSpiritField(actor, input)); + } + + async cultivate( + actor: ActiveCultivatorRef, + input: SpiritFieldCultivateRequest, + signal: AbortSignal, + ) { + return toPlayerStateMutationResponse( + await cultivateSpiritField(actor, input, signal), + ); + } + + async harvest( + actor: ActiveCultivatorRef, + input: SpiritFieldHarvestRequest, + signal: AbortSignal, + ) { + return toPlayerStateMutationResponse( + await harvestSpiritField(actor, input, signal), + ); + } +} diff --git a/apps/api/src/sponsorship/sponsorship.controller.ts b/apps/api/src/sponsorship/sponsorship.controller.ts new file mode 100644 index 00000000..fab791f7 --- /dev/null +++ b/apps/api/src/sponsorship/sponsorship.controller.ts @@ -0,0 +1,119 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Param, + Patch, + Post, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { SponsorshipApplicationError } from '@server/lib/services/SponsorshipApplicationService'; +import { + SponsorshipCheckoutRequestSchema, + SponsorshipClaimRequestSchema, + SponsorshipPublicQuerySchema, + SponsorshipVisibilityRequestSchema, + type SponsorshipCheckoutRequest, + type SponsorshipClaimRequest, +} from '@daoyou/shared/contracts/sponsorship'; +import { Access, CurrentCultivator } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { SponsorshipService } from './sponsorship.service'; +const SponsorshipErrors = apiErrorFilter((error) => + error instanceof SponsorshipApplicationError + ? Response.json( + { error: error.message, code: error.code }, + { status: error.status }, + ) + : undefined, +); +const WebhookErrors = apiErrorFilter((error) => { + console.warn('[sponsorship-webhook] rejected', { + error: error instanceof Error ? error.message : 'unknown', + }); + return Response.json({ ec: 400, em: 'invalid webhook' }, { status: 400 }); +}); +@Controller('api/sponsorship') +@Access('active') +export class SponsorshipController { + constructor( + @Inject(SponsorshipService) + private readonly sponsorship: SponsorshipService, + ) {} + @Post('providers/afdian/webhook') + @Access('public') + @HttpCode(200) + @UseFilters(WebhookErrors) + webhook(@JsonBody() input: unknown) { + return this.sponsorship.webhook(input); + } + @Get('config') + @Access('public') + config() { + return this.sponsorship.config(); + } + @Get('public') + @Access('public') + publicProfiles( + @FirstQuery(new ZodPipe(SponsorshipPublicQuerySchema, 'legacy-unhandled')) + query: { + page: number; + pageSize: number; + }, + ) { + return this.sponsorship.publicProfiles(query); + } + @Get('me') + merit(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.sponsorship.merit(actor); + } + @Patch('me/visibility') + visibility( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody( + { fallback: undefined }, + new ZodPipe(SponsorshipVisibilityRequestSchema, 'legacy-unhandled'), + ) + input: { isPublic: boolean }, + ) { + return this.sponsorship.visibility(actor, input.isPublic); + } + @Post('checkout-intents') + @HttpCode(201) + @UseFilters(SponsorshipErrors) + checkout( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody( + { fallback: undefined }, + new ZodPipe(SponsorshipCheckoutRequestSchema, 'legacy-unhandled'), + ) + input: SponsorshipCheckoutRequest, + ) { + return this.sponsorship.checkout(actor, input); + } + @Get('checkout-intents/:id') + checkoutStatus( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('id') id: string, + ) { + return this.sponsorship.checkoutStatus(actor, id); + } + @Post('claims') + @HttpCode(200) + @UseFilters(SponsorshipErrors) + claim( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody( + { fallback: undefined }, + new ZodPipe(SponsorshipClaimRequestSchema, 'legacy-unhandled'), + ) + input: SponsorshipClaimRequest, + ) { + return this.sponsorship.claim(actor, input); + } +} diff --git a/apps/api/src/sponsorship/sponsorship.module.ts b/apps/api/src/sponsorship/sponsorship.module.ts new file mode 100644 index 00000000..a53252e9 --- /dev/null +++ b/apps/api/src/sponsorship/sponsorship.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { SponsorshipController } from './sponsorship.controller'; +import { SponsorshipService } from './sponsorship.service'; +@Module({ + controllers: [SponsorshipController], + providers: [SponsorshipService], +}) +export class SponsorshipModule {} diff --git a/apps/api/src/sponsorship/sponsorship.service.ts b/apps/api/src/sponsorship/sponsorship.service.ts new file mode 100644 index 00000000..5403a287 --- /dev/null +++ b/apps/api/src/sponsorship/sponsorship.service.ts @@ -0,0 +1,62 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { + claimSponsorshipOrder, + createSponsorshipCheckoutIntent, + getCheckoutIntentStatus, + getCultivatorMerit, + getSponsorshipClientConfig, + listPublicMeritProfiles, + recordAfdianWebhook, + updateMeritVisibility, +} from '@server/lib/services/SponsorshipApplicationService'; +import type { + SponsorshipCheckoutRequest, + SponsorshipClaimRequest, +} from '@daoyou/shared/contracts/sponsorship'; +import { z } from 'zod'; +@Injectable() +export class SponsorshipService { + async webhook(payload: unknown) { + await recordAfdianWebhook(payload); + return { ec: 200, em: '' }; + } + config() { + return getSponsorshipClientConfig(); + } + publicProfiles(query: { page: number; pageSize: number }) { + return listPublicMeritProfiles(query.page, query.pageSize); + } + merit(actor: ActiveCultivatorRef) { + return getCultivatorMerit(actor.cultivatorId); + } + async visibility(actor: ActiveCultivatorRef, isPublic: boolean) { + const updated = await updateMeritVisibility(actor.cultivatorId, isPublic); + return { success: true, updated }; + } + checkout(actor: ActiveCultivatorRef, input: SponsorshipCheckoutRequest) { + return createSponsorshipCheckoutIntent({ + userId: actor.userId, + cultivatorId: actor.cultivatorId, + ...input, + }); + } + async checkoutStatus(actor: ActiveCultivatorRef, rawId: string) { + const id = z.uuid().safeParse(rawId); + if (!id.success) throw new HttpException({ error: '下单意图不存在' }, 404); + const status = await getCheckoutIntentStatus({ + id: id.data, + userId: actor.userId, + }); + if (!status) throw new HttpException({ error: '下单意图不存在' }, 404); + return status; + } + async claim(actor: ActiveCultivatorRef, input: SponsorshipClaimRequest) { + await claimSponsorshipOrder({ + userId: actor.userId, + cultivatorId: actor.cultivatorId, + ...input, + }); + return { success: true }; + } +} diff --git a/apps/api/src/story/story.controller.ts b/apps/api/src/story/story.controller.ts new file mode 100644 index 00000000..30f7c5f9 --- /dev/null +++ b/apps/api/src/story/story.controller.ts @@ -0,0 +1,94 @@ +import { + Controller, + Get, + HttpCode, + HttpException, + Inject, + Param, + Post, + UseFilters, + type PipeTransform, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { StoryService } from './story.service'; + +const CompleteSchema = z + .object({ outcome: z.string().trim().min(1).max(40) }) + .strict(); +const PerformanceErrors = apiErrorFilter((error) => { + const message = + error instanceof z.ZodError + ? error.issues[0]?.message || '演出结果无效' + : error instanceof Error + ? error.message + : '演出没能记下'; + return Response.json( + { error: message }, + { + status: + error instanceof z.ZodError + ? 400 + : message.includes('当前没有这场演出') + ? 409 + : 400, + }, + ); +}); +const GuideErrors = apiErrorFilter((error) => { + const message = error instanceof Error ? error.message : '这课没能记下'; + return Response.json( + { error: message }, + { + status: message.includes('当前没有这场教学') + ? 409 + : message.includes('没有这场教学') + ? 404 + : 400, + }, + ); +}); +class LessonIdPipe implements PipeTransform { + transform(value: string) { + if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value) || value.length > 80) + throw new HttpException({ error: '没有这场教学' }, 404); + return value; + } +} + +@Controller('api/story') +@Access('active') +export class StoryController { + constructor(@Inject(StoryService) private readonly story: StoryService) {} + + @Get() + read(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.story.read(actor); + } + + @Post('performances/:scriptId/complete') + @HttpCode(200) + @UseFilters(PerformanceErrors) + performance( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('scriptId') scriptId: string, + @JsonBody({ fallback: null }, new ZodPipe(CompleteSchema)) + input: z.infer, + ) { + return this.story.performance(actor, scriptId, input.outcome); + } + + @Post('guides/:lessonId/complete') + @HttpCode(200) + @UseFilters(GuideErrors) + guide( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('lessonId', new LessonIdPipe()) lessonId: string, + ) { + return this.story.guide(actor, lessonId); + } +} diff --git a/apps/api/src/story/story.module.ts b/apps/api/src/story/story.module.ts new file mode 100644 index 00000000..b2fae13f --- /dev/null +++ b/apps/api/src/story/story.module.ts @@ -0,0 +1,6 @@ +import { Module } from '@nestjs/common'; +import { StoryController } from './story.controller'; +import { StoryService } from './story.service'; + +@Module({ controllers: [StoryController], providers: [StoryService] }) +export class StoryModule {} diff --git a/apps/api/src/story/story.service.ts b/apps/api/src/story/story.service.ts new file mode 100644 index 00000000..f70abe64 --- /dev/null +++ b/apps/api/src/story/story.service.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; +import { readResourceWithMeta } from '@server/lib/services/ResourceReadService'; +import { + completeStoryGuideCommand, + completeStoryPerformanceCommand, +} from '@server/lib/services/StoryApplicationService'; +import { StoryService as StoryDomain } from '@server/lib/services/StoryService'; + +@Injectable() +export class StoryService { + read(actor: ActiveCultivatorRef) { + return readResourceWithMeta( + { kind: 'cultivator', id: actor.cultivatorId }, + 'player.story', + (tx) => StoryDomain.read(actor.cultivatorId, tx), + ); + } + + async performance( + actor: ActiveCultivatorRef, + scriptId: string, + outcome: string, + ) { + return toPlayerStateMutationResponse( + await completeStoryPerformanceCommand({ + userId: actor.userId, + cultivatorId: actor.cultivatorId, + scriptId, + outcome, + }), + ); + } + + async guide(actor: ActiveCultivatorRef, lessonId: string) { + return toPlayerStateMutationResponse( + await completeStoryGuideCommand({ + userId: actor.userId, + cultivatorId: actor.cultivatorId, + lessonId, + }), + ); + } +} diff --git a/apps/api/src/tasks/tasks.controller.ts b/apps/api/src/tasks/tasks.controller.ts new file mode 100644 index 00000000..cfcceba2 --- /dev/null +++ b/apps/api/src/tasks/tasks.controller.ts @@ -0,0 +1,116 @@ +import { + Controller, + Get, + HttpCode, + Inject, + Param, + Post, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { apiErrorFilter } from '../http/error-filter'; +import { FirstQuery } from '../http/first-query'; +import { ZodPipe } from '../http/zod.pipe'; +import { TasksService } from './tasks.service'; + +const ListSchema = z.object({ + status: z.enum(['active', 'completed']).optional(), +}); +const ListErrors = apiErrorFilter((error) => { + if (error instanceof z.ZodError) + return Response.json( + { error: error.issues[0]?.message || '查询参数错误' }, + { status: 400 }, + ); + console.error('获取任务列表失败:', error); + return Response.json( + { error: '获取任务列表失败,请稍后再试' }, + { status: 500 }, + ); +}); +const DetailErrors = apiErrorFilter((error) => { + console.error('获取任务详情失败:', error); + return Response.json( + { error: '获取任务详情失败,请稍后再试' }, + { status: 500 }, + ); +}); +const ChallengeErrors = apiErrorFilter((error) => { + const lock = redisLockErrorResponse(error); + if (lock) return lock; + const message = + error instanceof Error ? error.message : '试炼失败,请稍后再试'; + return Response.json( + { error: message }, + { + status: + message === '任务不存在' + ? 404 + : message.includes('没有可执行') + ? 409 + : 400, + }, + ); +}); +const ClaimErrors = apiErrorFilter((error) => { + const message = + error instanceof Error ? error.message : '领取奖励失败,请稍后再试'; + return Response.json( + { error: message }, + { + status: + message === '任务不存在' + ? 404 + : message.includes('尚未完成') || message.includes('已经领取') + ? 409 + : 400, + }, + ); +}); + +@Controller('api/tasks') +@Access('active') +export class TasksController { + constructor(@Inject(TasksService) private readonly tasks: TasksService) {} + + @Get() + @UseFilters(ListErrors) + list( + @CurrentCultivator() actor: ActiveCultivatorRef, + @FirstQuery(new ZodPipe(ListSchema)) query: z.infer, + ) { + return this.tasks.list(actor, query.status); + } + + @Get(':id') + @UseFilters(DetailErrors) + detail( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('id') id: string, + ) { + return this.tasks.detail(actor, id); + } + + @Post(':id/challenge') + @HttpCode(200) + @UseFilters(ChallengeErrors) + challenge( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('id', new ZodPipe(z.uuid())) id: string, + ) { + return this.tasks.challenge(actor, id); + } + + @Post(':id/claim-reward') + @HttpCode(200) + @UseFilters(ClaimErrors) + claim( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('id') id: string, + ) { + return this.tasks.claim(actor, id); + } +} diff --git a/apps/api/src/tasks/tasks.module.ts b/apps/api/src/tasks/tasks.module.ts new file mode 100644 index 00000000..f0a8f303 --- /dev/null +++ b/apps/api/src/tasks/tasks.module.ts @@ -0,0 +1,6 @@ +import { Module } from '@nestjs/common'; +import { TasksController } from './tasks.controller'; +import { TasksService } from './tasks.service'; + +@Module({ controllers: [TasksController], providers: [TasksService] }) +export class TasksModule {} diff --git a/apps/api/src/tasks/tasks.service.ts b/apps/api/src/tasks/tasks.service.ts new file mode 100644 index 00000000..74e16cd8 --- /dev/null +++ b/apps/api/src/tasks/tasks.service.ts @@ -0,0 +1,44 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; +import { readResourceWithMeta } from '@server/lib/services/ResourceReadService'; +import { claimTaskRewardCommand } from '@server/lib/services/TaskApplicationService'; +import { TaskService } from '@server/lib/services/TaskService'; +import { startBreakthroughBattle } from '@server/lib/services/combat-v6/CombatV6BreakthroughService'; + +@Injectable() +export class TasksService { + list(actor: ActiveCultivatorRef, status?: 'active' | 'completed') { + return readResourceWithMeta( + { kind: 'cultivator', id: actor.cultivatorId }, + 'player.tasks', + (tx) => TaskService.readCultivatorTasks(actor.cultivatorId, status, tx), + ); + } + + async detail(actor: ActiveCultivatorRef, id: string) { + const task = await TaskService.getCultivatorTask(actor.cultivatorId, id); + if (!task) throw new HttpException({ error: '任务不存在' }, 404); + return { success: true, data: { task } }; + } + + async challenge(actor: ActiveCultivatorRef, id: string) { + return { + success: true, + data: await startBreakthroughBattle( + { userId: actor.userId, cultivatorId: actor.cultivatorId }, + id, + ), + }; + } + + async claim(actor: ActiveCultivatorRef, taskId: string) { + return toPlayerStateMutationResponse( + await claimTaskRewardCommand({ + userId: actor.userId, + cultivatorId: actor.cultivatorId, + taskId, + }), + ); + } +} diff --git a/apps/api/src/tower/tower-errors.ts b/apps/api/src/tower/tower-errors.ts new file mode 100644 index 00000000..89ed3e40 --- /dev/null +++ b/apps/api/src/tower/tower-errors.ts @@ -0,0 +1,26 @@ +import { redisLockErrorResponse } from '@server/lib/http/errors'; +import { InventoryError } from '@server/lib/services/InventoryService'; +import { CombatV6BuildError } from '@server/lib/services/combat-v6/CombatV6BuildService'; +import { TowerV6Error } from '@server/lib/tower/combatV6'; +import { ZodError } from 'zod'; +import { apiErrorFilter } from '../http/error-filter'; + +function towerError(error: unknown): Response { + const lock = redisLockErrorResponse(error); + if (lock) return lock; + if (error instanceof ZodError) + return Response.json({ error: '幻境请求参数无效' }, { status: 400 }); + if ( + error instanceof TowerV6Error || + error instanceof InventoryError || + error instanceof CombatV6BuildError + ) + return Response.json({ error: error.message }, { status: 409 }); + console.error('[tower-v6] request failed', error); + return Response.json( + { error: '幻境操作失败,请刷新后重试' }, + { status: 500 }, + ); +} + +export const TowerErrors = apiErrorFilter(towerError); diff --git a/apps/api/src/tower/tower.controller.ts b/apps/api/src/tower/tower.controller.ts new file mode 100644 index 00000000..65098f95 --- /dev/null +++ b/apps/api/src/tower/tower.controller.ts @@ -0,0 +1,137 @@ +import { + All, + Controller, + Get, + HttpCode, + Inject, + Param, + Post, + Put, + UseFilters, +} from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { CombatAutoRequestSchema } from '@daoyou/shared/combat-v6/auto'; +import { + CombatV6TrainingCommandRequestSchema, + CombatV6TrainingEventsQuerySchema, + CombatV6TrainingRevisionRequestSchema, +} from '@daoyou/shared/contracts/combatV6'; +import { TOWER_BLESSING_IDS } from '@daoyou/shared/lib/tower/blessings'; +import { TOWER_ELIGIBLE_REALMS } from '@daoyou/shared/lib/tower/helpers'; +import { z } from 'zod'; +import { Access, CurrentCultivator } from '../auth/access'; +import { FirstQuery } from '../http/first-query'; +import { JsonBody } from '../http/json-body'; +import { ZodPipe } from '../http/zod.pipe'; +import { TowerErrors } from './tower-errors'; +import { TowerService } from './tower.service'; + +const ActionSchema = z.object({ + runId: z.uuid(), + revision: z.number().int().min(0), + action: z.enum(['battle', 'blessing', 'leave', 'complete']), + blessingId: z.enum(TOWER_BLESSING_IDS).optional(), +}); +const RealmSchema = z.enum(TOWER_ELIGIBLE_REALMS); + +@Controller('api/tower') +@Access('active') +@UseFilters(TowerErrors) +export class TowerController { + constructor(@Inject(TowerService) private readonly tower: TowerService) {} + + @Get('state') + state(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.tower.state(actor.cultivatorId); + } + + @Post('start') + @HttpCode(200) + start(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.tower.start(actor.cultivatorId); + } + + @Post('action') + @HttpCode(200) + action( + @CurrentCultivator() actor: ActiveCultivatorRef, + @JsonBody(new ZodPipe(ActionSchema)) input: z.infer, + ) { + return this.tower.action(actor, input); + } + + @Get('leaderboard') + leaderboard( + @CurrentCultivator() actor: ActiveCultivatorRef, + @FirstQuery('realm', new ZodPipe(RealmSchema)) + realm: z.infer, + ) { + return this.tower.leaderboard(actor.cultivatorId, realm); + } + + @Get('battle/sessions/current') + current(@CurrentCultivator() actor: ActiveCultivatorRef) { + return this.tower.current(actor.cultivatorId); + } + + @Get('battle/sessions/:id') + read( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('id', new ZodPipe(z.uuid())) id: string, + @FirstQuery(new ZodPipe(CombatV6TrainingEventsQuerySchema)) + query: z.infer, + ) { + return this.tower.read(actor.cultivatorId, id, query.afterEventSeq); + } + + @Put('battle/sessions/:id/commands/:unitId') + submit( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('id', new ZodPipe(z.uuid())) id: string, + @Param('unitId') unitId: string, + @JsonBody(new ZodPipe(CombatV6TrainingCommandRequestSchema)) + input: z.infer, + ) { + return this.tower.submit(actor, id, unitId, input); + } + + @Post('battle/sessions/:id/resolve') + @HttpCode(200) + resolve( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('id', new ZodPipe(z.uuid())) id: string, + @JsonBody(new ZodPipe(CombatV6TrainingRevisionRequestSchema)) + input: z.infer, + ) { + return this.tower.resolve(actor, id, input.expectedRevision); + } + + @Post('battle/sessions/:id/auto') + @HttpCode(200) + auto( + @CurrentCultivator() actor: ActiveCultivatorRef, + @Param('id', new ZodPipe(z.uuid())) id: string, + @JsonBody(new ZodPipe(CombatAutoRequestSchema)) + input: z.infer, + ) { + return this.tower.auto(actor, id, input.expectedRevision, input.round); + } + + @All(['battle', 'battle/*path']) + @HttpCode(410) + retiredBattle() { + return { error: '旧幻境战斗已下线,请刷新页面' }; + } + + @Post('reset') + @HttpCode(410) + retiredReset() { + return { error: '旧幻境接口已下线,请刷新页面' }; + } + + @Post('blessing/choose') + @HttpCode(410) + retiredBlessing() { + return { error: '旧幻境接口已下线,请刷新页面' }; + } +} diff --git a/apps/api/src/tower/tower.module.ts b/apps/api/src/tower/tower.module.ts new file mode 100644 index 00000000..5aa81d48 --- /dev/null +++ b/apps/api/src/tower/tower.module.ts @@ -0,0 +1,6 @@ +import { Module } from '@nestjs/common'; +import { TowerController } from './tower.controller'; +import { TowerService } from './tower.service'; + +@Module({ controllers: [TowerController], providers: [TowerService] }) +export class TowerModule {} diff --git a/apps/api/src/tower/tower.service.ts b/apps/api/src/tower/tower.service.ts new file mode 100644 index 00000000..76a3f98a --- /dev/null +++ b/apps/api/src/tower/tower.service.ts @@ -0,0 +1,106 @@ +import { HttpException, Injectable } from '@nestjs/common'; +import type { ActiveCultivatorRef } from '@server/lib/auth/types'; +import { + advanceTower, + changeTowerBattle, + completeTower, + getTowerBattle, + getTowerView, + startTower, +} from '@server/lib/tower/combatV6'; +import { getTowerLeaderboard } from '@server/lib/tower/leaderboard'; +import type { CombatV6TrainingCommandRequestSchema } from '@daoyou/shared/contracts/combatV6'; +import { getTowerSeasonMeta } from '@daoyou/shared/lib/tower/season'; +import type { z } from 'zod'; + +@Injectable() +export class TowerService { + async state(owner: string) { + return { success: true, data: await getTowerView(owner) }; + } + + async start(owner: string) { + return { success: true, data: await startTower(owner) }; + } + + async action( + actor: ActiveCultivatorRef, + input: Parameters[1], + ) { + const data = + input.action === 'complete' + ? await completeTower(actor, input) + : await advanceTower(actor, input); + return { success: true, data }; + } + + async leaderboard( + owner: string, + realm: Parameters[0]['realm'], + ) { + const season = getTowerSeasonMeta(); + return { + success: true, + data: await getTowerLeaderboard({ + seasonKey: season.seasonKey, + seasonEndAt: season.seasonEndsAt, + realm, + limit: 30, + selfCultivatorId: owner, + }), + }; + } + + async current(owner: string) { + return { success: true, data: await getTowerBattle(owner) }; + } + + async read(owner: string, id: string, after: number) { + const data = await getTowerBattle(owner, id, after); + if (!data) throw new HttpException({ error: '战斗不存在' }, 404); + return { success: true, data }; + } + + async submit( + actor: ActiveCultivatorRef, + id: string, + unitId: string, + input: z.infer, + ) { + return { + success: true, + data: await changeTowerBattle(actor, id, input.expectedRevision, { + unitId, + commands: input.commands, + }), + }; + } + + async auto( + actor: ActiveCultivatorRef, + id: string, + revision: number, + round: number, + ) { + try { + return await this.resolve(actor, id, revision, round); + } catch (error) { + throw new HttpException( + { error: error instanceof Error ? error.message : '自动指令提交失败' }, + 409, + ); + } + } + + async resolve( + actor: ActiveCultivatorRef, + id: string, + revision: number, + round?: number, + ) { + return { + success: true, + data: await changeTowerBattle(actor, id, revision, undefined, round), + }; + } +} diff --git a/src/server/types/markdown.d.ts b/apps/api/src/types/markdown.d.ts similarity index 100% rename from src/server/types/markdown.d.ts rename to apps/api/src/types/markdown.d.ts diff --git a/src/server/utils/aiClient.ts b/apps/api/src/utils/aiClient.ts similarity index 88% rename from src/server/utils/aiClient.ts rename to apps/api/src/utils/aiClient.ts index f86644c6..3aa97c78 100644 --- a/src/server/utils/aiClient.ts +++ b/apps/api/src/utils/aiClient.ts @@ -1,3 +1,11 @@ +import type { LlmByokConfig, LlmProviderId } from '@daoyou/shared/config/llm'; +import { + pickHighestWeightLlmRoute, + pickLlmRouteByUserHash, + resolveServerLlmRoutes, + type LlmRoute, +} from '@daoyou/shared/config/llmRouting'; +import { getRuntimeEnvironment } from '@server/lib/config/environment'; import { getCurrentContext } from '@server/lib/http/context'; import { recordLlmCallMetric } from '@server/lib/llm/metricsStore'; import { LLM_PROVIDERS } from '@server/lib/llm/providers'; @@ -7,17 +15,7 @@ import type { LlmSceneId, LlmStructuredFailureKind, } from '@server/lib/llm/types'; -import { - stableCompactStringify, - truncateText, -} from '@server/utils/llmPayload'; -import type { LlmByokConfig, LlmProviderId } from '@shared/config/llm'; -import { - pickHighestWeightLlmRoute, - pickLlmRouteByUserHash, - resolveServerLlmRoutes, - type LlmRoute, -} from '@shared/config/llmRouting'; +import { stableCompactStringify, truncateText } from '@server/utils/llmPayload'; import { generateText, JSONParseError, @@ -32,12 +30,12 @@ import { import { z } from 'zod'; const LLM_DEBUG_ENABLED = - process.env.LLM_DEBUG === 'true' || - process.env.LLM_DEBUG === '1' || - (process.env.LLM_DEBUG !== 'false' && - process.env.LLM_DEBUG !== '0' && - process.env.NODE_ENV !== 'production' && - process.env.NODE_ENV !== 'test'); + getRuntimeEnvironment().LLM_DEBUG === 'true' || + getRuntimeEnvironment().LLM_DEBUG === '1' || + (getRuntimeEnvironment().LLM_DEBUG !== 'false' && + getRuntimeEnvironment().LLM_DEBUG !== '0' && + getRuntimeEnvironment().NODE_ENV !== 'production' && + getRuntimeEnvironment().NODE_ENV !== 'test'); function logLlmDebug( label: string, @@ -55,43 +53,39 @@ function logLlmDebug( } function createLlmDebugFetch(sceneId: LlmSceneId, model: string): typeof fetch { - return Object.assign( - async ( - input: Parameters[0], - init?: Parameters[1], - ) => { - logLlmDebug( - 'REQUEST', - sceneId, - model, - typeof init?.body === 'string' ? init.body : String(init?.body ?? ''), - ); + return async ( + input: Parameters[0], + init?: Parameters[1], + ) => { + logLlmDebug( + 'REQUEST', + sceneId, + model, + typeof init?.body === 'string' ? init.body : String(init?.body ?? ''), + ); - try { - const response = await globalThis.fetch(input, init); - void response - .clone() - .text() - .then((body) => { - logLlmDebug( - `RESPONSE status=${response.status}`, - sceneId, - model, - body, - ); - }) - .catch((error) => { - logLlmDebug('RESPONSE_READ_ERROR', sceneId, model, String(error)); - }); - return response; - } catch (error) { - logLlmDebug('REQUEST_ERROR', sceneId, model, String(error)); - throw error; - } - }, - // Bun's fetch type requires this property; AI SDK only invokes the function. - { preconnect: () => undefined }, - ); + try { + const response = await globalThis.fetch(input, init); + void response + .clone() + .text() + .then((body) => { + logLlmDebug( + `RESPONSE status=${response.status}`, + sceneId, + model, + body, + ); + }) + .catch((error) => { + logLlmDebug('RESPONSE_READ_ERROR', sceneId, model, String(error)); + }); + return response; + } catch (error) { + logLlmDebug('REQUEST_ERROR', sceneId, model, String(error)); + throw error; + } + }; } const AI_MAX_CONCURRENT_REQUESTS = 64; @@ -100,7 +94,10 @@ let activeAiRequests = 0; let rejectedAiRequests = 0; export function getAiRuntimeStats() { - return { activeRequests: activeAiRequests, rejectedRequests: rejectedAiRequests }; + return { + activeRequests: activeAiRequests, + rejectedRequests: rejectedAiRequests, + }; } function startAiRequest(options: AiTextOptions) { @@ -138,7 +135,10 @@ function startAiRequest(options: AiTextOptions) { export async function generateAiText(options: AiTextOptions) { const request = startAiRequest(options); try { - return await generateAiTextInternal({ ...options, abortSignal: request.signal }); + return await generateAiTextInternal({ + ...options, + abortSignal: request.signal, + }); } finally { request.release(); } @@ -149,7 +149,10 @@ export async function generateAiObject( ) { const request = startAiRequest(options); try { - return await generateAiObjectInternal({ ...options, abortSignal: request.signal }); + return await generateAiObjectInternal({ + ...options, + abortSignal: request.signal, + }); } finally { request.release(); } @@ -160,7 +163,10 @@ export async function generateAiArray( ) { const request = startAiRequest(options); try { - return await generateAiArrayInternal({ ...options, abortSignal: request.signal }); + return await generateAiArrayInternal({ + ...options, + abortSignal: request.signal, + }); } finally { request.release(); } @@ -181,16 +187,20 @@ export interface AiTextOptions { reasoning?: AiReasoning; } -export interface AiObjectOptions - extends AiTextOptions { +export interface AiObjectOptions< + GENERATED, + RESULT = GENERATED, +> extends AiTextOptions { schema: z.ZodType; resultSchema?: z.ZodType; name?: string; description?: string; } -export interface AiArrayOptions - extends AiTextOptions { +export interface AiArrayOptions< + ELEMENT, + RESULT = ELEMENT[], +> extends AiTextOptions { elementSchema: z.ZodType; resultSchema?: z.ZodType; name?: string; @@ -225,27 +235,19 @@ type StructuredGenerationAttempt = { }; function getRequestConfig(): LlmByokConfig | undefined { - try { - return getCurrentContext().get('llmConfig'); - } catch { - return undefined; - } + return getCurrentContext()?.llmConfig; } function getRequestUserId(): string | undefined { - try { - return getCurrentContext().get('user')?.id; - } catch { - return undefined; - } + return getCurrentContext()?.user?.id; } function listConfiguredServerRoutes(): LlmRoute[] { return resolveServerLlmRoutes({ - providerSpec: process.env.LLM_PROVIDER, + providerSpec: getRuntimeEnvironment().LLM_PROVIDER, availableProviders: { - alibaba: Boolean(process.env.ALIBABA_API_KEY?.trim()), - deepseek: Boolean(process.env.DEEPSEEK_API_KEY?.trim()), + alibaba: Boolean(getRuntimeEnvironment().ALIBABA_API_KEY?.trim()), + deepseek: Boolean(getRuntimeEnvironment().DEEPSEEK_API_KEY?.trim()), }, }); } @@ -277,7 +279,7 @@ function resolveModel(sceneId: LlmSceneId): ResolvedModel { ? createLlmDebugFetch(sceneId, modelName) : undefined; const apiKey = - requestConfig?.apiKey ?? process.env[def.apiKeyEnv]?.trim(); + requestConfig?.apiKey ?? getRuntimeEnvironment()[def.apiKeyEnv]?.trim(); return { model: def.create({ apiKey, fetch: debugFetch })(modelName), diff --git a/src/server/utils/characterEngine.ts b/apps/api/src/utils/characterEngine.ts similarity index 55% rename from src/server/utils/characterEngine.ts rename to apps/api/src/utils/characterEngine.ts index 1f7f3b0e..fcd35ea5 100644 --- a/src/server/utils/characterEngine.ts +++ b/apps/api/src/utils/characterEngine.ts @@ -1,8 +1,8 @@ -import { CharacterGenerator } from '@shared/engine/cultivator/creation/CharacterGenerator'; -import type { Cultivator } from '@shared/types/cultivator'; +import { CharacterGenerator } from '@server/lib/generation/CharacterGenerator'; +import type { Cultivator } from '@daoyou/shared/types/cultivator'; /** - * @deprecated Use CharacterGenerator.generate() directly from @shared/engine/cultivator/creation/CharacterGenerator + * @deprecated Use CharacterGenerator.generate() directly from @server/lib/generation/CharacterGenerator */ export async function generateCultivatorFromAI( userInput: string, diff --git a/src/server/utils/divineFortune.ts b/apps/api/src/utils/divineFortune.ts similarity index 85% rename from src/server/utils/divineFortune.ts rename to apps/api/src/utils/divineFortune.ts index f4ba0c72..f87579d1 100644 --- a/src/server/utils/divineFortune.ts +++ b/apps/api/src/utils/divineFortune.ts @@ -3,7 +3,7 @@ import { renderPrompt } from '@server/lib/prompts'; export { getRandomFallbackFortune, type DivineFortune, -} from '@shared/lib/divineFortune'; +} from '@daoyou/shared/lib/divineFortune'; export function getDivineFortunePrompt(): [string, string] { const { system, user } = renderPrompt('divine-fortune'); diff --git a/src/server/utils/dungeonRewards.ts b/apps/api/src/utils/dungeonRewards.ts similarity index 99% rename from src/server/utils/dungeonRewards.ts rename to apps/api/src/utils/dungeonRewards.ts index f61ddd90..0fe38e78 100644 --- a/src/server/utils/dungeonRewards.ts +++ b/apps/api/src/utils/dungeonRewards.ts @@ -3,7 +3,7 @@ * 定义奖励评级、物品池、品质加成等常量 */ -import type { MaterialType, Quality } from '@shared/types/constants'; +import type { MaterialType, Quality } from '@daoyou/shared/types/constants'; /** * 奖励评级配置 diff --git a/src/server/utils/expGainSystem.ts b/apps/api/src/utils/expGainSystem.ts similarity index 92% rename from src/server/utils/expGainSystem.ts rename to apps/api/src/utils/expGainSystem.ts index 40ba823e..82599b0f 100644 --- a/src/server/utils/expGainSystem.ts +++ b/apps/api/src/utils/expGainSystem.ts @@ -1,18 +1,18 @@ -import type { CultivationProgress, Cultivator } from '@shared/types/cultivator'; -import { COMPREHENSION_INSIGHT_CAP } from '@shared/config/cultivationTuning'; +import type { CultivationProgress, Cultivator } from '@daoyou/shared/types/cultivator'; +import { COMPREHENSION_INSIGHT_CAP } from '@daoyou/shared/config/cultivationTuning'; import { calculateBattleExp, calculateSceneCultivationExp, -} from '@shared/engine/cultivation/ExpBudgetCalculator'; -import type { DailyTaskDifficulty } from '@shared/engine/cultivation/exp-gain-strategies/types'; -import { REALM_ORDER, type RealmType } from '@shared/types/constants'; +} from '@daoyou/shared/engine/cultivation/ExpBudgetCalculator'; +import type { DailyTaskDifficulty } from '@daoyou/shared/engine/cultivation/exp-gain-strategies/types'; +import { REALM_ORDER, type RealmType } from '@daoyou/shared/types/constants'; import { calculateExpProgress, canAttemptBreakthrough, getCultivationProgress, isBottleneckReached, syncBottleneckState, -} from './cultivationUtils'; +} from '@daoyou/shared/engine/cultivation/cultivationUtils'; /** * 修为增加来源 diff --git a/src/server/utils/llmPayload.ts b/apps/api/src/utils/llmPayload.ts similarity index 100% rename from src/server/utils/llmPayload.ts rename to apps/api/src/utils/llmPayload.ts diff --git a/src/server/utils/prompts.ts b/apps/api/src/utils/prompts.ts similarity index 95% rename from src/server/utils/prompts.ts rename to apps/api/src/utils/prompts.ts index af02d5ec..db7496b5 100644 --- a/src/server/utils/prompts.ts +++ b/apps/api/src/utils/prompts.ts @@ -1,8 +1,8 @@ import { renderPrompt } from '@server/lib/prompts'; -import type { RealmStage, RealmType } from '@shared/types/constants'; -import type { Attributes, Cultivator } from '@shared/types/cultivator'; -import { getAttributeInfo } from '@shared/lib/gameConceptDisplay'; -import type { BreakthroughModifiers } from './breakthroughCalculator'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; +import type { Attributes, Cultivator } from '@daoyou/shared/types/cultivator'; +import { getAttributeInfo } from '@daoyou/shared/lib/gameConceptDisplay'; +import type { BreakthroughModifiers } from '@daoyou/shared/engine/cultivation/breakthroughCalculator'; export type RetreatStoryCultivator = Pick< Cultivator, diff --git a/src/server/utils/rankingUtils.ts b/apps/api/src/utils/rankingUtils.ts similarity index 83% rename from src/server/utils/rankingUtils.ts rename to apps/api/src/utils/rankingUtils.ts index 845636c0..7e2c461a 100644 --- a/src/server/utils/rankingUtils.ts +++ b/apps/api/src/utils/rankingUtils.ts @@ -6,9 +6,9 @@ * 丹药评分保持原有规则;历史战斗产物直接保留存档评分。 */ -import { calculatePillScore } from '@shared/lib/pillScore'; -import { Quality } from '@shared/types/constants'; -import { Consumable } from '@shared/types/cultivator'; +import { calculatePillScore } from '@daoyou/shared/lib/pillScore'; +import { Quality } from '@daoyou/shared/types/constants'; +import { Consumable } from '@daoyou/shared/types/cultivator'; const QUALITY_SCORE_MAP: Record = { 凡品: 80, diff --git a/apps/api/tsconfig.build.json b/apps/api/tsconfig.build.json new file mode 100644 index 00000000..b7abb298 --- /dev/null +++ b/apps/api/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false + }, + "exclude": ["dist", "node_modules", "**/*.test.ts", "**/*.spec.ts"] +} diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json new file mode 100644 index 00000000..fc3fe14d --- /dev/null +++ b/apps/api/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "experimentalDecorators": true, + "outDir": "./dist", + "noEmit": true, + "types": ["node"], + "paths": { + "@server/*": ["./src/*"], + "@daoyou/shared/*": ["../../packages/shared/src/*"] + }, + "rootDir": "../.." + }, + "include": ["src/**/*.ts"], + "exclude": ["dist", "node_modules"] +} diff --git a/index.html b/apps/web/index.html similarity index 99% rename from index.html rename to apps/web/index.html index 559d3f53..5db8807e 100644 --- a/index.html +++ b/apps/web/index.html @@ -337,6 +337,6 @@ - + diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 00000000..401fe558 --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,39 @@ +{ + "name": "@daoyou/web", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "build": "tsc -b tsconfig.json && vite build", + "dev": "NODE_ENV=development node --env-file=../../env/local.env node_modules/vite/bin/vite.js", + "prd": "NODE_ENV=development node --env-file=../../env/staging.env node_modules/vite/bin/vite.js", + "typecheck": "tsc --noEmit -p tsconfig.json" + }, + "dependencies": { + "@better-auth/i18n": "^1.7.4", + "@better-fetch/fetch": "^1.3.1", + "@daoyou/shared": "workspace:*", + "altcha": "^3.2.2", + "better-auth": "^1.7.4", + "canvas-confetti": "^1.9.4", + "class-variance-authority": "^0.7.1", + "d3-format": "^3.1.2", + "phaser": "^4.2.1", + "phaser4-rex-plugins": "^4.2.0", + "react": "19.3.0", + "react-dom": "19.3.0", + "react-router": "^8.3.1", + "react-zoom-pan-pinch": "^4.2.0", + "zod": "^4.6.4" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.3.3", + "@types/canvas-confetti": "^1.9.0", + "@types/react": "19.3.0", + "@types/react-dom": "19.3.0", + "@vitejs/plugin-react": "6.1.1", + "graphology-types": "^0.24.8", + "tailwindcss": "^4.3.3", + "vite": "^8.3.0" + } +} diff --git a/public/_headers b/apps/web/public/_headers similarity index 100% rename from public/_headers rename to apps/web/public/_headers diff --git a/public/assets/app-boot/boot-logo.webp b/apps/web/public/assets/app-boot/boot-logo.webp similarity index 100% rename from public/assets/app-boot/boot-logo.webp rename to apps/web/public/assets/app-boot/boot-logo.webp diff --git a/public/assets/app-boot/boot-message.svg b/apps/web/public/assets/app-boot/boot-message.svg similarity index 100% rename from public/assets/app-boot/boot-message.svg rename to apps/web/public/assets/app-boot/boot-message.svg diff --git a/public/assets/app-boot/boot-motto.svg b/apps/web/public/assets/app-boot/boot-motto.svg similarity index 100% rename from public/assets/app-boot/boot-motto.svg rename to apps/web/public/assets/app-boot/boot-motto.svg diff --git a/public/assets/app-boot/boot-seal.webp b/apps/web/public/assets/app-boot/boot-seal.webp similarity index 100% rename from public/assets/app-boot/boot-seal.webp rename to apps/web/public/assets/app-boot/boot-seal.webp diff --git a/public/assets/app-boot/boot-title.svg b/apps/web/public/assets/app-boot/boot-title.svg similarity index 100% rename from public/assets/app-boot/boot-title.svg rename to apps/web/public/assets/app-boot/boot-title.svg diff --git a/public/assets/daoyou_logo.webp b/apps/web/public/assets/daoyou_logo.webp similarity index 100% rename from public/assets/daoyou_logo.webp rename to apps/web/public/assets/daoyou_logo.webp diff --git a/public/assets/divination/qiantai-mobile-v1.jpg b/apps/web/public/assets/divination/qiantai-mobile-v1.jpg similarity index 100% rename from public/assets/divination/qiantai-mobile-v1.jpg rename to apps/web/public/assets/divination/qiantai-mobile-v1.jpg diff --git a/public/assets/divination/qiantai-v1.jpg b/apps/web/public/assets/divination/qiantai-v1.jpg similarity index 100% rename from public/assets/divination/qiantai-v1.jpg rename to apps/web/public/assets/divination/qiantai-v1.jpg diff --git a/public/assets/game-controls/virtual-joystick-base.webp b/apps/web/public/assets/game-controls/virtual-joystick-base.webp similarity index 100% rename from public/assets/game-controls/virtual-joystick-base.webp rename to apps/web/public/assets/game-controls/virtual-joystick-base.webp diff --git a/public/assets/game-controls/virtual-joystick-thumb.webp b/apps/web/public/assets/game-controls/virtual-joystick-thumb.webp similarity index 100% rename from public/assets/game-controls/virtual-joystick-thumb.webp rename to apps/web/public/assets/game-controls/virtual-joystick-thumb.webp diff --git a/public/assets/icons/beast-fire-crow.webp b/apps/web/public/assets/icons/beast-fire-crow.webp similarity index 100% rename from public/assets/icons/beast-fire-crow.webp rename to apps/web/public/assets/icons/beast-fire-crow.webp diff --git a/public/assets/icons/beast-fusion-cauldron.png b/apps/web/public/assets/icons/beast-fusion-cauldron.png similarity index 100% rename from public/assets/icons/beast-fusion-cauldron.png rename to apps/web/public/assets/icons/beast-fusion-cauldron.png diff --git a/public/assets/icons/beast-ghost-lantern-butterfly.webp b/apps/web/public/assets/icons/beast-ghost-lantern-butterfly.webp similarity index 100% rename from public/assets/icons/beast-ghost-lantern-butterfly.webp rename to apps/web/public/assets/icons/beast-ghost-lantern-butterfly.webp diff --git a/public/assets/icons/beast-ink-jiao.webp b/apps/web/public/assets/icons/beast-ink-jiao.webp similarity index 100% rename from public/assets/icons/beast-ink-jiao.webp rename to apps/web/public/assets/icons/beast-ink-jiao.webp diff --git a/public/assets/icons/beast-mimi.webp b/apps/web/public/assets/icons/beast-mimi.webp similarity index 100% rename from public/assets/icons/beast-mimi.webp rename to apps/web/public/assets/icons/beast-mimi.webp diff --git a/public/assets/icons/beast-moon-marten.webp b/apps/web/public/assets/icons/beast-moon-marten.webp similarity index 100% rename from public/assets/icons/beast-moon-marten.webp rename to apps/web/public/assets/icons/beast-moon-marten.webp diff --git a/public/assets/icons/beast-nether-tiger.webp b/apps/web/public/assets/icons/beast-nether-tiger.webp similarity index 100% rename from public/assets/icons/beast-nether-tiger.webp rename to apps/web/public/assets/icons/beast-nether-tiger.webp diff --git a/public/assets/icons/beast-silverwing-mantis.webp b/apps/web/public/assets/icons/beast-silverwing-mantis.webp similarity index 100% rename from public/assets/icons/beast-silverwing-mantis.webp rename to apps/web/public/assets/icons/beast-silverwing-mantis.webp diff --git a/public/assets/icons/beast-six-eyed-ape.webp b/apps/web/public/assets/icons/beast-six-eyed-ape.webp similarity index 100% rename from public/assets/icons/beast-six-eyed-ape.webp rename to apps/web/public/assets/icons/beast-six-eyed-ape.webp diff --git a/public/assets/icons/beast-snake-neck-turtle.webp b/apps/web/public/assets/icons/beast-snake-neck-turtle.webp similarity index 100% rename from public/assets/icons/beast-snake-neck-turtle.webp rename to apps/web/public/assets/icons/beast-snake-neck-turtle.webp diff --git a/public/assets/icons/beast-snow-crane.webp b/apps/web/public/assets/icons/beast-snow-crane.webp similarity index 100% rename from public/assets/icons/beast-snow-crane.webp rename to apps/web/public/assets/icons/beast-snow-crane.webp diff --git a/public/assets/icons/beast-three-legged-golden-toad.webp b/apps/web/public/assets/icons/beast-three-legged-golden-toad.webp similarity index 100% rename from public/assets/icons/beast-three-legged-golden-toad.webp rename to apps/web/public/assets/icons/beast-three-legged-golden-toad.webp diff --git a/public/assets/icons/beast-thunder-peng.webp b/apps/web/public/assets/icons/beast-thunder-peng.webp similarity index 100% rename from public/assets/icons/beast-thunder-peng.webp rename to apps/web/public/assets/icons/beast-thunder-peng.webp diff --git a/public/assets/icons/beast-zheng.webp b/apps/web/public/assets/icons/beast-zheng.webp similarity index 100% rename from public/assets/icons/beast-zheng.webp rename to apps/web/public/assets/icons/beast-zheng.webp diff --git a/public/assets/icons/cultivator-female-avatar.png b/apps/web/public/assets/icons/cultivator-female-avatar.png similarity index 100% rename from public/assets/icons/cultivator-female-avatar.png rename to apps/web/public/assets/icons/cultivator-female-avatar.png diff --git a/public/assets/icons/cultivator-male-avatar.png b/apps/web/public/assets/icons/cultivator-male-avatar.png similarity index 100% rename from public/assets/icons/cultivator-male-avatar.png rename to apps/web/public/assets/icons/cultivator-male-avatar.png diff --git a/public/assets/icons/earthfire-furnace-ink.png b/apps/web/public/assets/icons/earthfire-furnace-ink.png similarity index 100% rename from public/assets/icons/earthfire-furnace-ink.png rename to apps/web/public/assets/icons/earthfire-furnace-ink.png diff --git a/public/assets/icons/map-dungeon.webp b/apps/web/public/assets/icons/map-dungeon.webp similarity index 100% rename from public/assets/icons/map-dungeon.webp rename to apps/web/public/assets/icons/map-dungeon.webp diff --git a/public/assets/icons/map-landmark.webp b/apps/web/public/assets/icons/map-landmark.webp similarity index 100% rename from public/assets/icons/map-landmark.webp rename to apps/web/public/assets/icons/map-landmark.webp diff --git a/public/assets/icons/map-market.webp b/apps/web/public/assets/icons/map-market.webp similarity index 100% rename from public/assets/icons/map-market.webp rename to apps/web/public/assets/icons/map-market.webp diff --git a/public/assets/icons/map-sect.webp b/apps/web/public/assets/icons/map-sect.webp similarity index 100% rename from public/assets/icons/map-sect.webp rename to apps/web/public/assets/icons/map-sect.webp diff --git a/public/assets/icons/map-wild.webp b/apps/web/public/assets/icons/map-wild.webp similarity index 100% rename from public/assets/icons/map-wild.webp rename to apps/web/public/assets/icons/map-wild.webp diff --git a/public/assets/icons/xuanfire-furnace-ink.png b/apps/web/public/assets/icons/xuanfire-furnace-ink.png similarity index 100% rename from public/assets/icons/xuanfire-furnace-ink.png rename to apps/web/public/assets/icons/xuanfire-furnace-ink.png diff --git a/public/assets/inscriptions/drawing-board.webp b/apps/web/public/assets/inscriptions/drawing-board.webp similarity index 100% rename from public/assets/inscriptions/drawing-board.webp rename to apps/web/public/assets/inscriptions/drawing-board.webp diff --git a/public/assets/inventory/cultivator-female-ink.webp b/apps/web/public/assets/inventory/cultivator-female-ink.webp similarity index 100% rename from public/assets/inventory/cultivator-female-ink.webp rename to apps/web/public/assets/inventory/cultivator-female-ink.webp diff --git a/public/assets/inventory/cultivator-male-ink.webp b/apps/web/public/assets/inventory/cultivator-male-ink.webp similarity index 100% rename from public/assets/inventory/cultivator-male-ink.webp rename to apps/web/public/assets/inventory/cultivator-male-ink.webp diff --git a/public/assets/light-grey-floral-motif.webp b/apps/web/public/assets/light-grey-floral-motif.webp similarity index 100% rename from public/assets/light-grey-floral-motif.webp rename to apps/web/public/assets/light-grey-floral-motif.webp diff --git a/public/assets/manuals/cultivator-female-meditation.webp b/apps/web/public/assets/manuals/cultivator-female-meditation.webp similarity index 100% rename from public/assets/manuals/cultivator-female-meditation.webp rename to apps/web/public/assets/manuals/cultivator-female-meditation.webp diff --git a/public/assets/manuals/cultivator-male-meditation.webp b/apps/web/public/assets/manuals/cultivator-male-meditation.webp similarity index 100% rename from public/assets/manuals/cultivator-male-meditation.webp rename to apps/web/public/assets/manuals/cultivator-male-meditation.webp diff --git a/public/assets/maps/dajin-region-v1.webp b/apps/web/public/assets/maps/dajin-region-v1.webp similarity index 100% rename from public/assets/maps/dajin-region-v1.webp rename to apps/web/public/assets/maps/dajin-region-v1.webp diff --git a/public/assets/maps/luanxinghai-region-v1.webp b/apps/web/public/assets/maps/luanxinghai-region-v1.webp similarity index 100% rename from public/assets/maps/luanxinghai-region-v1.webp rename to apps/web/public/assets/maps/luanxinghai-region-v1.webp diff --git a/public/assets/maps/mulan-region-v1.webp b/apps/web/public/assets/maps/mulan-region-v1.webp similarity index 100% rename from public/assets/maps/mulan-region-v1.webp rename to apps/web/public/assets/maps/mulan-region-v1.webp diff --git a/public/assets/maps/nanjiang-region-v1.webp b/apps/web/public/assets/maps/nanjiang-region-v1.webp similarity index 100% rename from public/assets/maps/nanjiang-region-v1.webp rename to apps/web/public/assets/maps/nanjiang-region-v1.webp diff --git a/public/assets/maps/northland-region-v1.webp b/apps/web/public/assets/maps/northland-region-v1.webp similarity index 100% rename from public/assets/maps/northland-region-v1.webp rename to apps/web/public/assets/maps/northland-region-v1.webp diff --git a/public/assets/maps/tiannan-region-v1.webp b/apps/web/public/assets/maps/tiannan-region-v1.webp similarity index 100% rename from public/assets/maps/tiannan-region-v1.webp rename to apps/web/public/assets/maps/tiannan-region-v1.webp diff --git a/public/assets/maps/world-overview-v1.webp b/apps/web/public/assets/maps/world-overview-v1.webp similarity index 100% rename from public/assets/maps/world-overview-v1.webp rename to apps/web/public/assets/maps/world-overview-v1.webp diff --git a/public/assets/paper.webp b/apps/web/public/assets/paper.webp similarity index 100% rename from public/assets/paper.webp rename to apps/web/public/assets/paper.webp diff --git a/public/assets/sect/jiujie-map.webp b/apps/web/public/assets/sect/jiujie-map.webp similarity index 100% rename from public/assets/sect/jiujie-map.webp rename to apps/web/public/assets/sect/jiujie-map.webp diff --git a/public/assets/sect/lingxiao-map.webp b/apps/web/public/assets/sect/lingxiao-map.webp similarity index 100% rename from public/assets/sect/lingxiao-map.webp rename to apps/web/public/assets/sect/lingxiao-map.webp diff --git a/public/assets/sect/mining/copper-ore.webp b/apps/web/public/assets/sect/mining/copper-ore.webp similarity index 100% rename from public/assets/sect/mining/copper-ore.webp rename to apps/web/public/assets/sect/mining/copper-ore.webp diff --git a/public/assets/sect/mining/dark-iron.webp b/apps/web/public/assets/sect/mining/dark-iron.webp similarity index 100% rename from public/assets/sect/mining/dark-iron.webp rename to apps/web/public/assets/sect/mining/dark-iron.webp diff --git a/public/assets/sect/mining/earth-essence.webp b/apps/web/public/assets/sect/mining/earth-essence.webp similarity index 100% rename from public/assets/sect/mining/earth-essence.webp rename to apps/web/public/assets/sect/mining/earth-essence.webp diff --git a/public/assets/sect/mining/explosive-barrel.webp b/apps/web/public/assets/sect/mining/explosive-barrel.webp similarity index 100% rename from public/assets/sect/mining/explosive-barrel.webp rename to apps/web/public/assets/sect/mining/explosive-barrel.webp diff --git a/public/assets/sect/mining/rope-cultivator.webp b/apps/web/public/assets/sect/mining/rope-cultivator.webp similarity index 100% rename from public/assets/sect/mining/rope-cultivator.webp rename to apps/web/public/assets/sect/mining/rope-cultivator.webp diff --git a/public/assets/sect/mining/spirit-crystal.webp b/apps/web/public/assets/sect/mining/spirit-crystal.webp similarity index 100% rename from public/assets/sect/mining/spirit-crystal.webp rename to apps/web/public/assets/sect/mining/spirit-crystal.webp diff --git a/public/assets/sect/mining/spirit-hook.webp b/apps/web/public/assets/sect/mining/spirit-hook.webp similarity index 100% rename from public/assets/sect/mining/spirit-hook.webp rename to apps/web/public/assets/sect/mining/spirit-hook.webp diff --git a/public/assets/sect/mining/spirit-vein-cavern.webp b/apps/web/public/assets/sect/mining/spirit-vein-cavern.webp similarity index 100% rename from public/assets/sect/mining/spirit-vein-cavern.webp rename to apps/web/public/assets/sect/mining/spirit-vein-cavern.webp diff --git a/public/assets/sect/onboarding/jiujie.webp b/apps/web/public/assets/sect/onboarding/jiujie.webp similarity index 100% rename from public/assets/sect/onboarding/jiujie.webp rename to apps/web/public/assets/sect/onboarding/jiujie.webp diff --git a/public/assets/sect/onboarding/lingxiao.webp b/apps/web/public/assets/sect/onboarding/lingxiao.webp similarity index 100% rename from public/assets/sect/onboarding/lingxiao.webp rename to apps/web/public/assets/sect/onboarding/lingxiao.webp diff --git a/public/assets/sect/onboarding/tianyan.webp b/apps/web/public/assets/sect/onboarding/tianyan.webp similarity index 100% rename from public/assets/sect/onboarding/tianyan.webp rename to apps/web/public/assets/sect/onboarding/tianyan.webp diff --git a/public/assets/sect/onboarding/wuxiang.webp b/apps/web/public/assets/sect/onboarding/wuxiang.webp similarity index 100% rename from public/assets/sect/onboarding/wuxiang.webp rename to apps/web/public/assets/sect/onboarding/wuxiang.webp diff --git a/public/assets/sect/onboarding/youdu.webp b/apps/web/public/assets/sect/onboarding/youdu.webp similarity index 100% rename from public/assets/sect/onboarding/youdu.webp rename to apps/web/public/assets/sect/onboarding/youdu.webp diff --git a/public/assets/sect/sweep/cloud-stair-courtyard.webp b/apps/web/public/assets/sect/sweep/cloud-stair-courtyard.webp similarity index 100% rename from public/assets/sect/sweep/cloud-stair-courtyard.webp rename to apps/web/public/assets/sect/sweep/cloud-stair-courtyard.webp diff --git a/public/assets/sect/sweep/sweep-atlas.webp b/apps/web/public/assets/sect/sweep/sweep-atlas.webp similarity index 100% rename from public/assets/sect/sweep/sweep-atlas.webp rename to apps/web/public/assets/sect/sweep/sweep-atlas.webp diff --git a/public/assets/sect/sweep/sweep-obstacles.webp b/apps/web/public/assets/sect/sweep/sweep-obstacles.webp similarity index 100% rename from public/assets/sect/sweep/sweep-obstacles.webp rename to apps/web/public/assets/sect/sweep/sweep-obstacles.webp diff --git a/public/assets/sect/tianyan-map.webp b/apps/web/public/assets/sect/tianyan-map.webp similarity index 100% rename from public/assets/sect/tianyan-map.webp rename to apps/web/public/assets/sect/tianyan-map.webp diff --git a/public/assets/sect/wuxiang-map.webp b/apps/web/public/assets/sect/wuxiang-map.webp similarity index 100% rename from public/assets/sect/wuxiang-map.webp rename to apps/web/public/assets/sect/wuxiang-map.webp diff --git a/public/assets/sect/youdu-map.webp b/apps/web/public/assets/sect/youdu-map.webp similarity index 100% rename from public/assets/sect/youdu-map.webp rename to apps/web/public/assets/sect/youdu-map.webp diff --git a/public/assets/sponsors/changyehudao.webp b/apps/web/public/assets/sponsors/changyehudao.webp similarity index 100% rename from public/assets/sponsors/changyehudao.webp rename to apps/web/public/assets/sponsors/changyehudao.webp diff --git a/public/assets/sponsors/gongzhengchangsheng.webp b/apps/web/public/assets/sponsors/gongzhengchangsheng.webp similarity index 100% rename from public/assets/sponsors/gongzhengchangsheng.webp rename to apps/web/public/assets/sponsors/gongzhengchangsheng.webp diff --git a/public/assets/sponsors/shanshuitongcheng.webp b/apps/web/public/assets/sponsors/shanshuitongcheng.webp similarity index 100% rename from public/assets/sponsors/shanshuitongcheng.webp rename to apps/web/public/assets/sponsors/shanshuitongcheng.webp diff --git a/public/assets/sponsors/yidengweiguang.webp b/apps/web/public/assets/sponsors/yidengweiguang.webp similarity index 100% rename from public/assets/sponsors/yidengweiguang.webp rename to apps/web/public/assets/sponsors/yidengweiguang.webp diff --git a/public/favicon.svg b/apps/web/public/favicon.svg similarity index 100% rename from public/favicon.svg rename to apps/web/public/favicon.svg diff --git a/public/icons/apple-touch-icon.png b/apps/web/public/icons/apple-touch-icon.png similarity index 100% rename from public/icons/apple-touch-icon.png rename to apps/web/public/icons/apple-touch-icon.png diff --git a/public/icons/icon-192.png b/apps/web/public/icons/icon-192.png similarity index 100% rename from public/icons/icon-192.png rename to apps/web/public/icons/icon-192.png diff --git a/public/icons/icon-512.png b/apps/web/public/icons/icon-512.png similarity index 100% rename from public/icons/icon-512.png rename to apps/web/public/icons/icon-512.png diff --git a/public/icons/icon-maskable-512.png b/apps/web/public/icons/icon-maskable-512.png similarity index 100% rename from public/icons/icon-maskable-512.png rename to apps/web/public/icons/icon-maskable-512.png diff --git a/public/licenses/text-filter/NOTICE.txt b/apps/web/public/licenses/text-filter/NOTICE.txt similarity index 93% rename from public/licenses/text-filter/NOTICE.txt rename to apps/web/public/licenses/text-filter/NOTICE.txt index de5f1667..d44aff0d 100644 --- a/public/licenses/text-filter/NOTICE.txt +++ b/apps/web/public/licenses/text-filter/NOTICE.txt @@ -1,7 +1,7 @@ Daoyou text-filter dictionary — modified community lexicon Modified on 2026-09-17. -The file src/server/config/text-filter.dictionary.json is a modified selection +The file apps/api/src/config/text-filter.dictionary.json is a modified selection from the following projects, merged with Daoyou's original 13 seed entries: 1. fwwdn/sensitive-stop-words diff --git a/public/licenses/text-filter/fwwdn-Apache-2.0.txt b/apps/web/public/licenses/text-filter/fwwdn-Apache-2.0.txt similarity index 100% rename from public/licenses/text-filter/fwwdn-Apache-2.0.txt rename to apps/web/public/licenses/text-filter/fwwdn-Apache-2.0.txt diff --git a/public/licenses/text-filter/konsheng-MIT.txt b/apps/web/public/licenses/text-filter/konsheng-MIT.txt similarity index 100% rename from public/licenses/text-filter/konsheng-MIT.txt rename to apps/web/public/licenses/text-filter/konsheng-MIT.txt diff --git a/public/manifest.webmanifest b/apps/web/public/manifest.webmanifest similarity index 100% rename from public/manifest.webmanifest rename to apps/web/public/manifest.webmanifest diff --git a/src/react-app/App.tsx b/apps/web/src/App.tsx similarity index 100% rename from src/react-app/App.tsx rename to apps/web/src/App.tsx diff --git a/src/react-app/assets/hono.svg b/apps/web/src/assets/hono.svg similarity index 100% rename from src/react-app/assets/hono.svg rename to apps/web/src/assets/hono.svg diff --git a/src/react-app/assets/react.svg b/apps/web/src/assets/react.svg similarity index 100% rename from src/react-app/assets/react.svg rename to apps/web/src/assets/react.svg diff --git a/src/react-app/components/auction/AuctionListingActions.tsx b/apps/web/src/components/auction/AuctionListingActions.tsx similarity index 94% rename from src/react-app/components/auction/AuctionListingActions.tsx rename to apps/web/src/components/auction/AuctionListingActions.tsx index 460adf26..23a69f7d 100644 --- a/src/react-app/components/auction/AuctionListingActions.tsx +++ b/apps/web/src/components/auction/AuctionListingActions.tsx @@ -3,9 +3,9 @@ import { AUCTION_MAX_PURCHASE_QUANTITY, AUCTION_MAX_TRANSACTION_TOTAL, calculateAuctionSettlement, -} from '@shared/config/auctionConfig'; -import type { AuctionListingView } from '@shared/contracts/auction'; -import { canDeployBeast } from '@shared/engine/combat-v6/beasts/projection'; +} from '@daoyou/shared/config/auctionConfig'; +import type { AuctionListingView } from '@daoyou/shared/contracts/auction'; +import { canDeployBeast } from '@daoyou/shared/engine/combat-v6/beasts/projection'; import { useState } from 'react'; export function AuctionListingActions({ diff --git a/src/react-app/components/auction/AuctionListings.tsx b/apps/web/src/components/auction/AuctionListings.tsx similarity index 98% rename from src/react-app/components/auction/AuctionListings.tsx rename to apps/web/src/components/auction/AuctionListings.tsx index a3882665..c040ef0a 100644 --- a/src/react-app/components/auction/AuctionListings.tsx +++ b/apps/web/src/components/auction/AuctionListings.tsx @@ -9,7 +9,7 @@ import { InkButton } from '@app/components/ui'; import type { AuctionAssetType, AuctionListingView, -} from '@shared/contracts/auction'; +} from '@daoyou/shared/contracts/auction'; import { useState, type ReactNode } from 'react'; type Actions = (listing: AuctionListingView, close: () => void) => ReactNode; diff --git a/src/react-app/components/auction/ListBeastModal.tsx b/apps/web/src/components/auction/ListBeastModal.tsx similarity index 96% rename from src/react-app/components/auction/ListBeastModal.tsx rename to apps/web/src/components/auction/ListBeastModal.tsx index e6db4b06..7cec3dc2 100644 --- a/src/react-app/components/auction/ListBeastModal.tsx +++ b/apps/web/src/components/auction/ListBeastModal.tsx @@ -7,11 +7,11 @@ import { useResourceMutation } from '@app/lib/resources/mutations'; import { AUCTION_MAX_UNIT_PRICE, calculateAuctionSettlement, -} from '@shared/config/auctionConfig'; -import { AuctionBeastListSchema } from '@shared/contracts/auction'; -import { beastAuctionBlockReason } from '@shared/contracts/beastTrade'; -import type { BeastManagementView } from '@shared/contracts/combatV6Beasts'; -import type { FriendCultivatorSummary } from '@shared/contracts/friends'; +} from '@daoyou/shared/config/auctionConfig'; +import { AuctionBeastListSchema } from '@daoyou/shared/contracts/auction'; +import { beastAuctionBlockReason } from '@daoyou/shared/contracts/beastTrade'; +import type { BeastManagementView } from '@daoyou/shared/contracts/combatV6Beasts'; +import type { FriendCultivatorSummary } from '@daoyou/shared/contracts/friends'; import { useEffect, useRef, useState } from 'react'; import { combatV6Request } from '../feature/combat-v6/request'; diff --git a/src/react-app/components/auction/ListItemModal.tsx b/apps/web/src/components/auction/ListItemModal.tsx similarity index 97% rename from src/react-app/components/auction/ListItemModal.tsx rename to apps/web/src/components/auction/ListItemModal.tsx index 52a122ef..73db701c 100644 --- a/src/react-app/components/auction/ListItemModal.tsx +++ b/apps/web/src/components/auction/ListItemModal.tsx @@ -13,14 +13,14 @@ import { import { InkDetailDrawer } from '@app/components/ui/InkDetailDrawer'; import { useInventoryBag } from '@app/lib/resources/bag'; import { useResourceMutation } from '@app/lib/resources/mutations'; -import { calculateAuctionSettlement } from '@shared/config/auctionConfig'; +import { calculateAuctionSettlement } from '@daoyou/shared/config/auctionConfig'; import { AuctionListSchema, auctionBlockReason, auctionItemPriceCap, -} from '@shared/contracts/auction'; -import type { FriendCultivatorSummary } from '@shared/contracts/friends'; -import type { InventoryView } from '@shared/contracts/inventory'; +} from '@daoyou/shared/contracts/auction'; +import type { FriendCultivatorSummary } from '@daoyou/shared/contracts/friends'; +import type { InventoryView } from '@daoyou/shared/contracts/inventory'; import { useEffect, useRef, useState } from 'react'; export function ListItemModal({ diff --git a/src/react-app/components/auth/AltchaCaptcha.tsx b/apps/web/src/components/auth/AltchaCaptcha.tsx similarity index 100% rename from src/react-app/components/auth/AltchaCaptcha.tsx rename to apps/web/src/components/auth/AltchaCaptcha.tsx diff --git a/src/react-app/components/auth/AuthCaptchaField.tsx b/apps/web/src/components/auth/AuthCaptchaField.tsx similarity index 100% rename from src/react-app/components/auth/AuthCaptchaField.tsx rename to apps/web/src/components/auth/AuthCaptchaField.tsx diff --git a/src/react-app/components/auth/AuthChoiceCard.tsx b/apps/web/src/components/auth/AuthChoiceCard.tsx similarity index 96% rename from src/react-app/components/auth/AuthChoiceCard.tsx rename to apps/web/src/components/auth/AuthChoiceCard.tsx index 06697b2e..beaa70d9 100644 --- a/src/react-app/components/auth/AuthChoiceCard.tsx +++ b/apps/web/src/components/auth/AuthChoiceCard.tsx @@ -1,5 +1,5 @@ import Link from '@app/components/router/AppLink'; -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; interface AuthChoiceCardProps { title: string; diff --git a/src/react-app/components/auth/AuthPageShell.tsx b/apps/web/src/components/auth/AuthPageShell.tsx similarity index 100% rename from src/react-app/components/auth/AuthPageShell.tsx rename to apps/web/src/components/auth/AuthPageShell.tsx diff --git a/src/react-app/components/auth/index.ts b/apps/web/src/components/auth/index.ts similarity index 100% rename from src/react-app/components/auth/index.ts rename to apps/web/src/components/auth/index.ts diff --git a/src/react-app/components/auth/useAuthFeedback.tsx b/apps/web/src/components/auth/useAuthFeedback.tsx similarity index 100% rename from src/react-app/components/auth/useAuthFeedback.tsx rename to apps/web/src/components/auth/useAuthFeedback.tsx diff --git a/src/react-app/components/auth/useCaptchaField.ts b/apps/web/src/components/auth/useCaptchaField.ts similarity index 100% rename from src/react-app/components/auth/useCaptchaField.ts rename to apps/web/src/components/auth/useCaptchaField.ts diff --git a/src/react-app/components/auth/utils.ts b/apps/web/src/components/auth/utils.ts similarity index 100% rename from src/react-app/components/auth/utils.ts rename to apps/web/src/components/auth/utils.ts diff --git a/src/react-app/components/cultivator/StatusCard.tsx b/apps/web/src/components/cultivator/StatusCard.tsx similarity index 91% rename from src/react-app/components/cultivator/StatusCard.tsx rename to apps/web/src/components/cultivator/StatusCard.tsx index f9dce06e..41e67ff4 100644 --- a/src/react-app/components/cultivator/StatusCard.tsx +++ b/apps/web/src/components/cultivator/StatusCard.tsx @@ -1,17 +1,17 @@ import { InkCard } from '@app/components/ui/InkCard'; -import { getConditionStatusTemplate } from '@shared/lib/conditionStatusRegistry'; +import { getConditionStatusTemplate } from '@daoyou/shared/lib/conditionStatusRegistry'; import { CULTIVATION_BOOST_STATUS_KEY, getCultivationBoostDisplayText, -} from '@shared/lib/cultivationBoost'; +} from '@daoyou/shared/lib/cultivationBoost'; import { BREAKTHROUGH_FOCUS_STATUS_KEY, CLEAR_MIND_STATUS_KEY, getBreakthroughFocusBonus, getProtectMeridiansReductionPercent, PROTECT_MERIDIANS_STATUS_KEY, -} from '@shared/lib/pillEffectScaling'; -import type { ConditionStatusInstance } from '@shared/types/condition'; +} from '@daoyou/shared/lib/pillEffectScaling'; +import type { ConditionStatusInstance } from '@daoyou/shared/types/condition'; interface StatusCardProps { buffs: ConditionStatusInstance[]; diff --git a/src/react-app/components/dungeon/DungeonProgressCard.tsx b/apps/web/src/components/dungeon/DungeonProgressCard.tsx similarity index 96% rename from src/react-app/components/dungeon/DungeonProgressCard.tsx rename to apps/web/src/components/dungeon/DungeonProgressCard.tsx index 36ce3302..8b6f418d 100644 --- a/src/react-app/components/dungeon/DungeonProgressCard.tsx +++ b/apps/web/src/components/dungeon/DungeonProgressCard.tsx @@ -1,5 +1,5 @@ import { InkSection } from '@app/components/layout'; -import type { DungeonState } from '@shared/lib/dungeon/types'; +import type { DungeonState } from '@daoyou/shared/lib/dungeon/types'; import { InkButton } from '../ui'; import { ResourceCostCard } from './ResourceCostCard'; diff --git a/src/react-app/components/dungeon/ResourceCostCard.tsx b/apps/web/src/components/dungeon/ResourceCostCard.tsx similarity index 97% rename from src/react-app/components/dungeon/ResourceCostCard.tsx rename to apps/web/src/components/dungeon/ResourceCostCard.tsx index ac6813a6..e62eb673 100644 --- a/src/react-app/components/dungeon/ResourceCostCard.tsx +++ b/apps/web/src/components/dungeon/ResourceCostCard.tsx @@ -4,11 +4,11 @@ import { formatDungeonCostName, formatDungeonCostValue, } from '@app/lib/dungeon/formatDungeonCost'; -import type { DungeonOptionCost } from '@shared/lib/dungeon/types'; +import type { DungeonOptionCost } from '@daoyou/shared/lib/dungeon/types'; import { getResourceDisplayName, getResourceIcon, -} from '@shared/lib/gameConceptDisplay'; +} from '@daoyou/shared/lib/gameConceptDisplay'; import { format } from 'd3-format'; interface ResourceCostCardProps { diff --git a/src/react-app/components/feature/alchemy/AlchemyBag.tsx b/apps/web/src/components/feature/alchemy/AlchemyBag.tsx similarity index 98% rename from src/react-app/components/feature/alchemy/AlchemyBag.tsx rename to apps/web/src/components/feature/alchemy/AlchemyBag.tsx index 7cbbb929..6aa538c2 100644 --- a/src/react-app/components/feature/alchemy/AlchemyBag.tsx +++ b/apps/web/src/components/feature/alchemy/AlchemyBag.tsx @@ -12,8 +12,8 @@ import { groupAlchemyBagMaterials, groupAlchemyStorageMaterials, type AlchemyBagMaterial, -} from '@shared/inventory/alchemy'; -import type { Material } from '@shared/types/cultivator'; +} from '@daoyou/shared/inventory/alchemy'; +import type { Material } from '@daoyou/shared/types/cultivator'; import { useEffect, useState } from 'react'; import { CraftInventoryPanel } from '../items/CraftInventoryPanel'; import { InventoryItems } from '../items/InventoryItems'; diff --git a/src/react-app/components/feature/alchemy/AlchemyCraftSessionProvider.tsx b/apps/web/src/components/feature/alchemy/AlchemyCraftSessionProvider.tsx similarity index 100% rename from src/react-app/components/feature/alchemy/AlchemyCraftSessionProvider.tsx rename to apps/web/src/components/feature/alchemy/AlchemyCraftSessionProvider.tsx diff --git a/src/react-app/components/feature/alchemy/AlchemyFurnace.tsx b/apps/web/src/components/feature/alchemy/AlchemyFurnace.tsx similarity index 98% rename from src/react-app/components/feature/alchemy/AlchemyFurnace.tsx rename to apps/web/src/components/feature/alchemy/AlchemyFurnace.tsx index dd6e798b..d0121b2a 100644 --- a/src/react-app/components/feature/alchemy/AlchemyFurnace.tsx +++ b/apps/web/src/components/feature/alchemy/AlchemyFurnace.tsx @@ -1,8 +1,8 @@ import { GameIcon } from '@app/components/ui/GameIcon'; import { InkButton } from '@app/components/ui/InkButton'; import { InkQuantityInput } from '@app/components/ui/InkQuantityInput'; -import { consumableFactsOf } from '@shared/items/definitions/consumables'; -import { cn } from '@shared/lib/cn'; +import { consumableFactsOf } from '@daoyou/shared/items/definitions/consumables'; +import { cn } from '@daoyou/shared/lib/cn'; import { FurnaceGatherEffect } from '../craft/FurnaceGatherEffect'; import { ItemSlot } from '../items/ItemSlot'; import { diff --git a/src/react-app/components/feature/alchemy/AlchemyRoomScene.tsx b/apps/web/src/components/feature/alchemy/AlchemyRoomScene.tsx similarity index 99% rename from src/react-app/components/feature/alchemy/AlchemyRoomScene.tsx rename to apps/web/src/components/feature/alchemy/AlchemyRoomScene.tsx index dea35103..a3776fca 100644 --- a/src/react-app/components/feature/alchemy/AlchemyRoomScene.tsx +++ b/apps/web/src/components/feature/alchemy/AlchemyRoomScene.tsx @@ -1,6 +1,6 @@ import { RoomView, type RoomActorView } from '@app/components/feature/room'; import { GameSceneFrame, GameSceneLoading } from '@app/components/game-shell'; -import type { AlchemyMode } from '@shared/types/consumable'; +import type { AlchemyMode } from '@daoyou/shared/types/consumable'; import { useCallback, useEffect, useLayoutEffect } from 'react'; import { useBlocker, useSearchParams } from 'react-router'; import { useAlchemyCraftSession } from './alchemyCraftContext'; diff --git a/src/react-app/components/feature/alchemy/AlchemyToolWorkspace.tsx b/apps/web/src/components/feature/alchemy/AlchemyToolWorkspace.tsx similarity index 100% rename from src/react-app/components/feature/alchemy/AlchemyToolWorkspace.tsx rename to apps/web/src/components/feature/alchemy/AlchemyToolWorkspace.tsx diff --git a/src/react-app/components/feature/alchemy/FormulaPickerModal.tsx b/apps/web/src/components/feature/alchemy/FormulaPickerModal.tsx similarity index 98% rename from src/react-app/components/feature/alchemy/FormulaPickerModal.tsx rename to apps/web/src/components/feature/alchemy/FormulaPickerModal.tsx index eefa28ab..caf8727a 100644 --- a/src/react-app/components/feature/alchemy/FormulaPickerModal.tsx +++ b/apps/web/src/components/feature/alchemy/FormulaPickerModal.tsx @@ -7,12 +7,12 @@ import { InkNotice, inkFieldVariants, } from '@app/components/ui'; -import { formatAlchemyPropertyVector } from '@shared/lib/alchemyProperties'; +import { formatAlchemyPropertyVector } from '@daoyou/shared/lib/alchemyProperties'; import { PILL_FAMILY_VALUES, type AlchemyFormula, type PillFamily, -} from '@shared/types/consumable'; +} from '@daoyou/shared/types/consumable'; import { useAlchemyFormulaLibrary } from './useAlchemyFormulaLibrary'; export function FormulaPickerModal({ diff --git a/src/react-app/components/feature/alchemy/alchemyCraftContext.ts b/apps/web/src/components/feature/alchemy/alchemyCraftContext.ts similarity index 100% rename from src/react-app/components/feature/alchemy/alchemyCraftContext.ts rename to apps/web/src/components/feature/alchemy/alchemyCraftContext.ts diff --git a/src/react-app/components/feature/alchemy/alchemyFacilities.ts b/apps/web/src/components/feature/alchemy/alchemyFacilities.ts similarity index 100% rename from src/react-app/components/feature/alchemy/alchemyFacilities.ts rename to apps/web/src/components/feature/alchemy/alchemyFacilities.ts diff --git a/src/react-app/components/feature/alchemy/alchemyPresentation.ts b/apps/web/src/components/feature/alchemy/alchemyPresentation.ts similarity index 96% rename from src/react-app/components/feature/alchemy/alchemyPresentation.ts rename to apps/web/src/components/feature/alchemy/alchemyPresentation.ts index 7b7079d3..e0e00944 100644 --- a/src/react-app/components/feature/alchemy/alchemyPresentation.ts +++ b/apps/web/src/components/feature/alchemy/alchemyPresentation.ts @@ -1,9 +1,9 @@ -import { getPillAppearanceLabel } from '@shared/lib/pillAppearance'; +import { getPillAppearanceLabel } from '@daoyou/shared/lib/pillAppearance'; import type { AlchemyBatchDisplayProfile, FormulaAnalysisResult, PillAppearanceGrade, -} from '@shared/types/consumable'; +} from '@daoyou/shared/types/consumable'; export function describeFireState({ preview, diff --git a/src/react-app/components/feature/alchemy/alchemyTypes.ts b/apps/web/src/components/feature/alchemy/alchemyTypes.ts similarity index 91% rename from src/react-app/components/feature/alchemy/alchemyTypes.ts rename to apps/web/src/components/feature/alchemy/alchemyTypes.ts index 4a2aca94..aeae3d84 100644 --- a/src/react-app/components/feature/alchemy/alchemyTypes.ts +++ b/apps/web/src/components/feature/alchemy/alchemyTypes.ts @@ -1,11 +1,11 @@ -import type { SectScenePresentation } from '@shared/engine/sect'; +import type { SectScenePresentation } from '@daoyou/shared/engine/sect'; import type { AlchemyFormula, AlchemyFormulaDiscoveryCandidate, AlchemyYieldDisplayProfile, FormulaAnalysisResult, -} from '@shared/types/consumable'; -import type { Consumable, Material } from '@shared/types/cultivator'; +} from '@daoyou/shared/types/consumable'; +import type { Consumable, Material } from '@daoyou/shared/types/cultivator'; export type AlchemyWorkspacePhase = 'preparing' | 'observing' | 'firing' | 'result'; diff --git a/src/react-app/components/feature/alchemy/facilities/AlchemyFacilityConversations.tsx b/apps/web/src/components/feature/alchemy/facilities/AlchemyFacilityConversations.tsx similarity index 100% rename from src/react-app/components/feature/alchemy/facilities/AlchemyFacilityConversations.tsx rename to apps/web/src/components/feature/alchemy/facilities/AlchemyFacilityConversations.tsx diff --git a/src/react-app/components/feature/alchemy/facilities/AlchemyGuideView.tsx b/apps/web/src/components/feature/alchemy/facilities/AlchemyGuideView.tsx similarity index 100% rename from src/react-app/components/feature/alchemy/facilities/AlchemyGuideView.tsx rename to apps/web/src/components/feature/alchemy/facilities/AlchemyGuideView.tsx diff --git a/src/react-app/components/feature/alchemy/facilities/AlchemyMaterialShelf.tsx b/apps/web/src/components/feature/alchemy/facilities/AlchemyMaterialShelf.tsx similarity index 75% rename from src/react-app/components/feature/alchemy/facilities/AlchemyMaterialShelf.tsx rename to apps/web/src/components/feature/alchemy/facilities/AlchemyMaterialShelf.tsx index 41fd2244..506231c3 100644 --- a/src/react-app/components/feature/alchemy/facilities/AlchemyMaterialShelf.tsx +++ b/apps/web/src/components/feature/alchemy/facilities/AlchemyMaterialShelf.tsx @@ -1,4 +1,4 @@ -import type { Material } from '@shared/types/cultivator'; +import type { Material } from '@daoyou/shared/types/cultivator'; import { AlchemyBag } from '../AlchemyBag'; export function AlchemyMaterialShelf({ onCarry, diff --git a/src/react-app/components/feature/alchemy/facilities/FormulaArchiveView.tsx b/apps/web/src/components/feature/alchemy/facilities/FormulaArchiveView.tsx similarity index 98% rename from src/react-app/components/feature/alchemy/facilities/FormulaArchiveView.tsx rename to apps/web/src/components/feature/alchemy/facilities/FormulaArchiveView.tsx index 83ae721f..4c4071e8 100644 --- a/src/react-app/components/feature/alchemy/facilities/FormulaArchiveView.tsx +++ b/apps/web/src/components/feature/alchemy/facilities/FormulaArchiveView.tsx @@ -6,12 +6,12 @@ import { InkNotice, inkFieldVariants, } from '@app/components/ui'; -import { formatAlchemyPropertyVector } from '@shared/lib/alchemyProperties'; +import { formatAlchemyPropertyVector } from '@daoyou/shared/lib/alchemyProperties'; import { PILL_FAMILY_VALUES, type AlchemyFormula, type PillFamily, -} from '@shared/types/consumable'; +} from '@daoyou/shared/types/consumable'; import { useState } from 'react'; import { AlchemyToolWorkspace } from '../AlchemyToolWorkspace'; import { useAlchemyCraftSession } from '../alchemyCraftContext'; diff --git a/src/react-app/components/feature/alchemy/facilities/FurnaceWorkspace.tsx b/apps/web/src/components/feature/alchemy/facilities/FurnaceWorkspace.tsx similarity index 98% rename from src/react-app/components/feature/alchemy/facilities/FurnaceWorkspace.tsx rename to apps/web/src/components/feature/alchemy/facilities/FurnaceWorkspace.tsx index 95966b58..66325008 100644 --- a/src/react-app/components/feature/alchemy/facilities/FurnaceWorkspace.tsx +++ b/apps/web/src/components/feature/alchemy/facilities/FurnaceWorkspace.tsx @@ -1,5 +1,5 @@ import { InkButton, InkDetailDrawer } from '@app/components/ui'; -import type { AlchemyMode } from '@shared/types/consumable'; +import type { AlchemyMode } from '@daoyou/shared/types/consumable'; import { useRef, useState } from 'react'; import { AlchemyBag } from '../AlchemyBag'; import { AlchemyFurnace } from '../AlchemyFurnace'; diff --git a/src/react-app/components/feature/alchemy/facilities/HerbCabinetView.tsx b/apps/web/src/components/feature/alchemy/facilities/HerbCabinetView.tsx similarity index 94% rename from src/react-app/components/feature/alchemy/facilities/HerbCabinetView.tsx rename to apps/web/src/components/feature/alchemy/facilities/HerbCabinetView.tsx index 4930123d..1741f9e5 100644 --- a/src/react-app/components/feature/alchemy/facilities/HerbCabinetView.tsx +++ b/apps/web/src/components/feature/alchemy/facilities/HerbCabinetView.tsx @@ -1,5 +1,5 @@ import { useInkUI } from '@app/components/providers/InkUIProvider'; -import type { Material } from '@shared/types/cultivator'; +import type { Material } from '@daoyou/shared/types/cultivator'; import { AlchemyToolWorkspace } from '../AlchemyToolWorkspace'; import { useAlchemyCraftSession } from '../alchemyCraftContext'; import { AlchemyMaterialShelf } from './AlchemyMaterialShelf'; diff --git a/src/react-app/components/feature/alchemy/index.ts b/apps/web/src/components/feature/alchemy/index.ts similarity index 100% rename from src/react-app/components/feature/alchemy/index.ts rename to apps/web/src/components/feature/alchemy/index.ts diff --git a/src/react-app/components/feature/alchemy/stages/FurnaceFiringStage.tsx b/apps/web/src/components/feature/alchemy/stages/FurnaceFiringStage.tsx similarity index 100% rename from src/react-app/components/feature/alchemy/stages/FurnaceFiringStage.tsx rename to apps/web/src/components/feature/alchemy/stages/FurnaceFiringStage.tsx diff --git a/src/react-app/components/feature/alchemy/stages/FurnaceHarvestStage.tsx b/apps/web/src/components/feature/alchemy/stages/FurnaceHarvestStage.tsx similarity index 96% rename from src/react-app/components/feature/alchemy/stages/FurnaceHarvestStage.tsx rename to apps/web/src/components/feature/alchemy/stages/FurnaceHarvestStage.tsx index 01789649..17881f04 100644 --- a/src/react-app/components/feature/alchemy/stages/FurnaceHarvestStage.tsx +++ b/apps/web/src/components/feature/alchemy/stages/FurnaceHarvestStage.tsx @@ -1,5 +1,5 @@ import { InkButton } from '@app/components/ui'; -import { consumableFactsOf } from '@shared/items/definitions/consumables'; +import { consumableFactsOf } from '@daoyou/shared/items/definitions/consumables'; import { ItemSlot } from '../../items/ItemSlot'; import { useAlchemyCraftSession } from '../alchemyCraftContext'; diff --git a/src/react-app/components/feature/alchemy/stages/FurnaceObservationStage.tsx b/apps/web/src/components/feature/alchemy/stages/FurnaceObservationStage.tsx similarity index 100% rename from src/react-app/components/feature/alchemy/stages/FurnaceObservationStage.tsx rename to apps/web/src/components/feature/alchemy/stages/FurnaceObservationStage.tsx diff --git a/src/react-app/components/feature/alchemy/stages/FurnacePreparationStage.tsx b/apps/web/src/components/feature/alchemy/stages/FurnacePreparationStage.tsx similarity index 100% rename from src/react-app/components/feature/alchemy/stages/FurnacePreparationStage.tsx rename to apps/web/src/components/feature/alchemy/stages/FurnacePreparationStage.tsx diff --git a/src/react-app/components/feature/alchemy/useAlchemyCraftSessionState.ts b/apps/web/src/components/feature/alchemy/useAlchemyCraftSessionState.ts similarity index 99% rename from src/react-app/components/feature/alchemy/useAlchemyCraftSessionState.ts rename to apps/web/src/components/feature/alchemy/useAlchemyCraftSessionState.ts index eb7ab5cb..103f431c 100644 --- a/src/react-app/components/feature/alchemy/useAlchemyCraftSessionState.ts +++ b/apps/web/src/components/feature/alchemy/useAlchemyCraftSessionState.ts @@ -10,9 +10,9 @@ import { import { ALCHEMY_INPUT_CONSTRAINTS, ALCHEMY_MAX_DOSE, -} from '@shared/config/alchemyInput'; -import type { AlchemyFormula, AlchemyMode } from '@shared/types/consumable'; -import type { Material } from '@shared/types/cultivator'; +} from '@daoyou/shared/config/alchemyInput'; +import type { AlchemyFormula, AlchemyMode } from '@daoyou/shared/types/consumable'; +import type { Material } from '@daoyou/shared/types/cultivator'; import { createElement, useCallback, diff --git a/src/react-app/components/feature/alchemy/useAlchemyFormulaLibrary.tsx b/apps/web/src/components/feature/alchemy/useAlchemyFormulaLibrary.tsx similarity index 98% rename from src/react-app/components/feature/alchemy/useAlchemyFormulaLibrary.tsx rename to apps/web/src/components/feature/alchemy/useAlchemyFormulaLibrary.tsx index 458403a3..96e56265 100644 --- a/src/react-app/components/feature/alchemy/useAlchemyFormulaLibrary.tsx +++ b/apps/web/src/components/feature/alchemy/useAlchemyFormulaLibrary.tsx @@ -1,5 +1,5 @@ import { useInkUI } from '@app/components/providers/InkUIProvider'; -import type { AlchemyFormula, PillFamily } from '@shared/types/consumable'; +import type { AlchemyFormula, PillFamily } from '@daoyou/shared/types/consumable'; import { useCallback, useEffect, useRef, useState } from 'react'; import type { FormulaPagination } from './alchemyTypes'; diff --git a/src/react-app/components/feature/app-boot/AppBootScreen.tsx b/apps/web/src/components/feature/app-boot/AppBootScreen.tsx similarity index 100% rename from src/react-app/components/feature/app-boot/AppBootScreen.tsx rename to apps/web/src/components/feature/app-boot/AppBootScreen.tsx diff --git a/src/react-app/components/feature/app-version/AppVersionNotifier.tsx b/apps/web/src/components/feature/app-version/AppVersionNotifier.tsx similarity index 100% rename from src/react-app/components/feature/app-version/AppVersionNotifier.tsx rename to apps/web/src/components/feature/app-version/AppVersionNotifier.tsx diff --git a/src/react-app/components/feature/attributes/AttributeAllocation.tsx b/apps/web/src/components/feature/attributes/AttributeAllocation.tsx similarity index 100% rename from src/react-app/components/feature/attributes/AttributeAllocation.tsx rename to apps/web/src/components/feature/attributes/AttributeAllocation.tsx diff --git a/src/react-app/components/feature/beasts/BeastIcon.tsx b/apps/web/src/components/feature/beasts/BeastIcon.tsx similarity index 84% rename from src/react-app/components/feature/beasts/BeastIcon.tsx rename to apps/web/src/components/feature/beasts/BeastIcon.tsx index 1da4fcbd..1aa97dc6 100644 --- a/src/react-app/components/feature/beasts/BeastIcon.tsx +++ b/apps/web/src/components/feature/beasts/BeastIcon.tsx @@ -1,5 +1,5 @@ import type { GameIconProps } from '@app/components/ui/GameIcon'; -import { BEAST_SPECIES } from '@shared/engine/combat-v6/beasts/content'; +import { BEAST_SPECIES } from '@daoyou/shared/engine/combat-v6/beasts/content'; import { BeastPortrait } from './BeastPortrait'; const speciesIcons = new Map( diff --git a/src/react-app/components/feature/beasts/BeastMutationTag.tsx b/apps/web/src/components/feature/beasts/BeastMutationTag.tsx similarity index 100% rename from src/react-app/components/feature/beasts/BeastMutationTag.tsx rename to apps/web/src/components/feature/beasts/BeastMutationTag.tsx diff --git a/src/react-app/components/feature/beasts/BeastPortrait.tsx b/apps/web/src/components/feature/beasts/BeastPortrait.tsx similarity index 91% rename from src/react-app/components/feature/beasts/BeastPortrait.tsx rename to apps/web/src/components/feature/beasts/BeastPortrait.tsx index 7689eaf0..1e8a0843 100644 --- a/src/react-app/components/feature/beasts/BeastPortrait.tsx +++ b/apps/web/src/components/feature/beasts/BeastPortrait.tsx @@ -1,5 +1,5 @@ import { GameIcon, type GameIconProps } from '@app/components/ui/GameIcon'; -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; /** Shared by owned beasts and frozen battle/replay appearances. */ export function BeastPortrait({ diff --git a/src/react-app/components/feature/beasts/BeastSkillGrid.tsx b/apps/web/src/components/feature/beasts/BeastSkillGrid.tsx similarity index 100% rename from src/react-app/components/feature/beasts/BeastSkillGrid.tsx rename to apps/web/src/components/feature/beasts/BeastSkillGrid.tsx diff --git a/src/react-app/components/feature/beasts/BeastSkillTile.tsx b/apps/web/src/components/feature/beasts/BeastSkillTile.tsx similarity index 96% rename from src/react-app/components/feature/beasts/BeastSkillTile.tsx rename to apps/web/src/components/feature/beasts/BeastSkillTile.tsx index ba60db67..8893acfe 100644 --- a/src/react-app/components/feature/beasts/BeastSkillTile.tsx +++ b/apps/web/src/components/feature/beasts/BeastSkillTile.tsx @@ -1,5 +1,5 @@ import { InkTooltip } from '@app/components/ui/InkTooltip'; -import { beastSkillPresentation } from '@shared/combat-v6/beast-skill-presentation'; +import { beastSkillPresentation } from '@daoyou/shared/combat-v6/beast-skill-presentation'; const styles = { normal: 'border-ink/20 bg-bgpaper text-ink hover:border-ink/40', diff --git a/src/react-app/components/feature/beasts/BeastTradeCard.tsx b/apps/web/src/components/feature/beasts/BeastTradeCard.tsx similarity index 87% rename from src/react-app/components/feature/beasts/BeastTradeCard.tsx rename to apps/web/src/components/feature/beasts/BeastTradeCard.tsx index baef8345..e32ad6e3 100644 --- a/src/react-app/components/feature/beasts/BeastTradeCard.tsx +++ b/apps/web/src/components/feature/beasts/BeastTradeCard.tsx @@ -1,7 +1,7 @@ -import { getLevelRealmStage } from '@shared/config/realmProgression'; -import type { BeastTradePreview } from '@shared/contracts/beastTrade'; -import { BEAST_SPECIES } from '@shared/engine/combat-v6/beasts/content'; -import { beastOriginName } from '@shared/engine/combat-v6/beasts/identity'; +import { getLevelRealmStage } from '@daoyou/shared/config/realmProgression'; +import type { BeastTradePreview } from '@daoyou/shared/contracts/beastTrade'; +import { BEAST_SPECIES } from '@daoyou/shared/engine/combat-v6/beasts/content'; +import { beastOriginName } from '@daoyou/shared/engine/combat-v6/beasts/identity'; import { BeastIcon } from './BeastIcon'; import { BeastMutationTag } from './BeastMutationTag'; diff --git a/src/react-app/components/feature/beasts/BeastTradePreview.tsx b/apps/web/src/components/feature/beasts/BeastTradePreview.tsx similarity index 91% rename from src/react-app/components/feature/beasts/BeastTradePreview.tsx rename to apps/web/src/components/feature/beasts/BeastTradePreview.tsx index cfaf45e9..d6030fa9 100644 --- a/src/react-app/components/feature/beasts/BeastTradePreview.tsx +++ b/apps/web/src/components/feature/beasts/BeastTradePreview.tsx @@ -1,12 +1,12 @@ import { InkModal } from '@app/components/layout'; -import type { BeastTradePreview as Preview } from '@shared/contracts/beastTrade'; +import type { BeastTradePreview as Preview } from '@daoyou/shared/contracts/beastTrade'; import { BEAST_PROGRESSION, BEAST_SPECIES, -} from '@shared/engine/combat-v6/beasts/content'; -import { beastOriginName } from '@shared/engine/combat-v6/beasts/identity'; -import { BEAST_ATTRIBUTE_NAMES } from '@shared/engine/combat-v6/beasts/progression'; -import { beastAttributes } from '@shared/engine/combat-v6/beasts/projection'; +} from '@daoyou/shared/engine/combat-v6/beasts/content'; +import { beastOriginName } from '@daoyou/shared/engine/combat-v6/beasts/identity'; +import { BEAST_ATTRIBUTE_NAMES } from '@daoyou/shared/engine/combat-v6/beasts/progression'; +import { beastAttributes } from '@daoyou/shared/engine/combat-v6/beasts/projection'; import { useState } from 'react'; import { BeastIcon } from './BeastIcon'; import { BeastMutationTag } from './BeastMutationTag'; diff --git a/src/react-app/components/feature/combat-v6/CombatV6Battle.tsx b/apps/web/src/components/feature/combat-v6/CombatV6Battle.tsx similarity index 97% rename from src/react-app/components/feature/combat-v6/CombatV6Battle.tsx rename to apps/web/src/components/feature/combat-v6/CombatV6Battle.tsx index 75d1d183..eb347f78 100644 --- a/src/react-app/components/feature/combat-v6/CombatV6Battle.tsx +++ b/apps/web/src/components/feature/combat-v6/CombatV6Battle.tsx @@ -1,7 +1,7 @@ import { GameIcon } from '@app/components/ui/GameIcon'; -import { AUTO_DELAY_MS } from '@shared/combat-v6/auto'; -import type { CombatV6TrainingCommandV1 } from '@shared/contracts/combatV6'; -import type { ArenaSessionView } from '@shared/contracts/combatV6Arena'; +import { AUTO_DELAY_MS } from '@daoyou/shared/combat-v6/auto'; +import type { CombatV6TrainingCommandV1 } from '@daoyou/shared/contracts/combatV6'; +import type { ArenaSessionView } from '@daoyou/shared/contracts/combatV6Arena'; import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'; import { Link } from 'react-router'; import { CombatV6Commands, type Choice } from './CombatV6Commands'; @@ -25,7 +25,7 @@ type Props = { playing: boolean; pending: boolean; onCommand: ( - commands: import('@shared/contracts/combatV6').CombatV6CommandGroup, + commands: import('@daoyou/shared/contracts/combatV6').CombatV6CommandGroup, ) => Promise; onResolve: () => Promise; onAuto: () => Promise; diff --git a/src/react-app/components/feature/combat-v6/CombatV6Commands.tsx b/apps/web/src/components/feature/combat-v6/CombatV6Commands.tsx similarity index 97% rename from src/react-app/components/feature/combat-v6/CombatV6Commands.tsx rename to apps/web/src/components/feature/combat-v6/CombatV6Commands.tsx index 79f5f09b..aad8dc13 100644 --- a/src/react-app/components/feature/combat-v6/CombatV6Commands.tsx +++ b/apps/web/src/components/feature/combat-v6/CombatV6Commands.tsx @@ -1,7 +1,7 @@ import { InkButton } from '@app/components/ui/InkButton'; -import type { CombatV6TrainingCommandV1 } from '@shared/contracts/combatV6'; -import type { ArenaSessionView } from '@shared/contracts/combatV6Arena'; -import { CAPTURE_SKILL_ID } from '@shared/engine/combat-v6/beasts/progression'; +import type { CombatV6TrainingCommandV1 } from '@daoyou/shared/contracts/combatV6'; +import type { ArenaSessionView } from '@daoyou/shared/contracts/combatV6Arena'; +import { CAPTURE_SKILL_ID } from '@daoyou/shared/engine/combat-v6/beasts/progression'; import { useState, type ReactNode } from 'react'; import { CombatV6PetChoice } from './CombatV6PetChoice'; import { CombatV6SkillChoice } from './CombatV6SkillChoice'; diff --git a/src/react-app/components/feature/combat-v6/CombatV6Details.tsx b/apps/web/src/components/feature/combat-v6/CombatV6Details.tsx similarity index 100% rename from src/react-app/components/feature/combat-v6/CombatV6Details.tsx rename to apps/web/src/components/feature/combat-v6/CombatV6Details.tsx diff --git a/src/react-app/components/feature/combat-v6/CombatV6HistoryList.tsx b/apps/web/src/components/feature/combat-v6/CombatV6HistoryList.tsx similarity index 97% rename from src/react-app/components/feature/combat-v6/CombatV6HistoryList.tsx rename to apps/web/src/components/feature/combat-v6/CombatV6HistoryList.tsx index eec7632d..3c25fa3a 100644 --- a/src/react-app/components/feature/combat-v6/CombatV6HistoryList.tsx +++ b/apps/web/src/components/feature/combat-v6/CombatV6HistoryList.tsx @@ -1,5 +1,5 @@ -import type { CombatV6HistoryItem } from '@shared/contracts/combatV6Replay'; -import { cn } from '@shared/lib/cn'; +import type { CombatV6HistoryItem } from '@daoyou/shared/contracts/combatV6Replay'; +import { cn } from '@daoyou/shared/lib/cn'; import { Link } from 'react-router'; import { combatV6HistorySources } from './presentation'; diff --git a/src/react-app/components/feature/combat-v6/CombatV6Log.tsx b/apps/web/src/components/feature/combat-v6/CombatV6Log.tsx similarity index 100% rename from src/react-app/components/feature/combat-v6/CombatV6Log.tsx rename to apps/web/src/components/feature/combat-v6/CombatV6Log.tsx diff --git a/src/react-app/components/feature/combat-v6/CombatV6Page.tsx b/apps/web/src/components/feature/combat-v6/CombatV6Page.tsx similarity index 100% rename from src/react-app/components/feature/combat-v6/CombatV6Page.tsx rename to apps/web/src/components/feature/combat-v6/CombatV6Page.tsx diff --git a/src/react-app/components/feature/combat-v6/CombatV6PetChoice.tsx b/apps/web/src/components/feature/combat-v6/CombatV6PetChoice.tsx similarity index 100% rename from src/react-app/components/feature/combat-v6/CombatV6PetChoice.tsx rename to apps/web/src/components/feature/combat-v6/CombatV6PetChoice.tsx diff --git a/src/react-app/components/feature/combat-v6/CombatV6ReplayPlayer.tsx b/apps/web/src/components/feature/combat-v6/CombatV6ReplayPlayer.tsx similarity index 97% rename from src/react-app/components/feature/combat-v6/CombatV6ReplayPlayer.tsx rename to apps/web/src/components/feature/combat-v6/CombatV6ReplayPlayer.tsx index f687e4e7..b174387d 100644 --- a/src/react-app/components/feature/combat-v6/CombatV6ReplayPlayer.tsx +++ b/apps/web/src/components/feature/combat-v6/CombatV6ReplayPlayer.tsx @@ -1,5 +1,5 @@ -import type { CombatV6ReplayView } from '@shared/combat-v6/replay'; -import { replaySeeker } from '@shared/combat-v6/replay-timeline'; +import type { CombatV6ReplayView } from '@daoyou/shared/combat-v6/replay'; +import { replaySeeker } from '@daoyou/shared/combat-v6/replay-timeline'; import { useCallback, useEffect, diff --git a/src/react-app/components/feature/combat-v6/CombatV6Roster.tsx b/apps/web/src/components/feature/combat-v6/CombatV6Roster.tsx similarity index 97% rename from src/react-app/components/feature/combat-v6/CombatV6Roster.tsx rename to apps/web/src/components/feature/combat-v6/CombatV6Roster.tsx index c664a1ad..be152f3f 100644 --- a/src/react-app/components/feature/combat-v6/CombatV6Roster.tsx +++ b/apps/web/src/components/feature/combat-v6/CombatV6Roster.tsx @@ -1,6 +1,6 @@ import { BeastPortrait } from '@app/components/feature/beasts/BeastPortrait'; -import type { CombatV6UnitAppearance } from '@shared/contracts/combatV6'; -import { DAO_RAGE_RESOURCE_ID } from '@shared/engine/combat-v6/equipment/special-ids'; +import type { CombatV6UnitAppearance } from '@daoyou/shared/contracts/combatV6'; +import { DAO_RAGE_RESOURCE_ID } from '@daoyou/shared/engine/combat-v6/equipment/special-ids'; import { memo, useMemo, type CSSProperties } from 'react'; import type { frameFeedback } from './presentation'; import type { CombatV6Unit } from './session'; diff --git a/src/react-app/components/feature/combat-v6/CombatV6SkillChoice.tsx b/apps/web/src/components/feature/combat-v6/CombatV6SkillChoice.tsx similarity index 97% rename from src/react-app/components/feature/combat-v6/CombatV6SkillChoice.tsx rename to apps/web/src/components/feature/combat-v6/CombatV6SkillChoice.tsx index 5578179f..6d15fdd1 100644 --- a/src/react-app/components/feature/combat-v6/CombatV6SkillChoice.tsx +++ b/apps/web/src/components/feature/combat-v6/CombatV6SkillChoice.tsx @@ -1,8 +1,8 @@ import { GameIcon } from '@app/components/ui/GameIcon'; import { InkButton } from '@app/components/ui/InkButton'; import { InkDetailDrawer } from '@app/components/ui/InkDetailDrawer'; -import { findBeastSkillPresentation } from '@shared/combat-v6/beast-skill-presentation'; -import type { CombatV6SkillCommandOption } from '@shared/engine/combat-v6/core/types'; +import { findBeastSkillPresentation } from '@daoyou/shared/combat-v6/beast-skill-presentation'; +import type { CombatV6SkillCommandOption } from '@daoyou/shared/engine/combat-v6/core/types'; import { useState, type ReactNode } from 'react'; import { reasonText, skillNeedsTarget } from './presentation'; import type { CombatV6Session } from './session'; diff --git a/src/react-app/components/feature/combat-v6/CombatV6Status.tsx b/apps/web/src/components/feature/combat-v6/CombatV6Status.tsx similarity index 96% rename from src/react-app/components/feature/combat-v6/CombatV6Status.tsx rename to apps/web/src/components/feature/combat-v6/CombatV6Status.tsx index e0b82453..7cae934c 100644 --- a/src/react-app/components/feature/combat-v6/CombatV6Status.tsx +++ b/apps/web/src/components/feature/combat-v6/CombatV6Status.tsx @@ -1,4 +1,4 @@ -import type { ArenaSessionView } from '@shared/contracts/combatV6Arena'; +import type { ArenaSessionView } from '@daoyou/shared/contracts/combatV6Arena'; import { useEffect, useState } from 'react'; /** Local clock drives presentation only; the host decides when commands lock. */ diff --git a/src/react-app/components/feature/combat-v6/combat-v6.css b/apps/web/src/components/feature/combat-v6/combat-v6.css similarity index 100% rename from src/react-app/components/feature/combat-v6/combat-v6.css rename to apps/web/src/components/feature/combat-v6/combat-v6.css diff --git a/src/react-app/components/feature/combat-v6/presentation.ts b/apps/web/src/components/feature/combat-v6/presentation.ts similarity index 75% rename from src/react-app/components/feature/combat-v6/presentation.ts rename to apps/web/src/components/feature/combat-v6/presentation.ts index fe6740de..b4acc2dd 100644 --- a/src/react-app/components/feature/combat-v6/presentation.ts +++ b/apps/web/src/components/feature/combat-v6/presentation.ts @@ -1,4 +1,4 @@ -import type { CombatV6SkillCommandOption } from '@shared/engine/combat-v6/core/types'; +import type { CombatV6SkillCommandOption } from '@daoyou/shared/engine/combat-v6/core/types'; export { appendBattleEntries, @@ -6,12 +6,12 @@ export { frameFeedback, reasonText, unitLabels, -} from '@shared/combat-v6/battle-log'; +} from '@daoyou/shared/combat-v6/battle-log'; export type { ActionEntry, BattleLog, LogLine, -} from '@shared/combat-v6/battle-log'; +} from '@daoyou/shared/combat-v6/battle-log'; export const combatV6HistorySources = { ranking: '天骄榜', diff --git a/src/react-app/components/feature/combat-v6/request.ts b/apps/web/src/components/feature/combat-v6/request.ts similarity index 100% rename from src/react-app/components/feature/combat-v6/request.ts rename to apps/web/src/components/feature/combat-v6/request.ts diff --git a/src/react-app/components/feature/combat-v6/session.ts b/apps/web/src/components/feature/combat-v6/session.ts similarity index 97% rename from src/react-app/components/feature/combat-v6/session.ts rename to apps/web/src/components/feature/combat-v6/session.ts index 0cfb3755..442538fb 100644 --- a/src/react-app/components/feature/combat-v6/session.ts +++ b/apps/web/src/components/feature/combat-v6/session.ts @@ -1,8 +1,8 @@ -import { applyUnitDelta, contiguousEvents } from '@shared/combat-v6/playback'; +import { applyUnitDelta, contiguousEvents } from '@daoyou/shared/combat-v6/playback'; import type { CombatV6DeltaFrameV1, CombatV6TrainingSessionViewV1, -} from '@shared/contracts/combatV6'; +} from '@daoyou/shared/contracts/combatV6'; import { appendBattleEntries, type BattleLog } from './presentation'; export type CombatV6Session = Omit< diff --git a/src/react-app/components/feature/combat-v6/useArenaV6Session.ts b/apps/web/src/components/feature/combat-v6/useArenaV6Session.ts similarity index 98% rename from src/react-app/components/feature/combat-v6/useArenaV6Session.ts rename to apps/web/src/components/feature/combat-v6/useArenaV6Session.ts index 66ac499c..074081c6 100644 --- a/src/react-app/components/feature/combat-v6/useArenaV6Session.ts +++ b/apps/web/src/components/feature/combat-v6/useArenaV6Session.ts @@ -3,7 +3,7 @@ import type { ArenaSessionView, ArenaSocketMessage, ArenaV6Submit, -} from '@shared/contracts/combatV6Arena'; +} from '@daoyou/shared/contracts/combatV6Arena'; import { useCallback, useEffect, useReducer, useRef, useState } from 'react'; import { combatV6Request, CombatV6RequestError, mutationBody } from './request'; import { emptySession, reduceSession } from './session'; @@ -215,7 +215,7 @@ export function useArenaV6Session(battleId: string, spectator = false) { ); const submit = useCallback( async ( - commands: import('@shared/contracts/combatV6').CombatV6CommandGroup, + commands: import('@daoyou/shared/contracts/combatV6').CombatV6CommandGroup, ) => { const session = latest.current; if (spectator || !session || !connected || retry) diff --git a/src/react-app/components/feature/combat-v6/useCombatV6Session.ts b/apps/web/src/components/feature/combat-v6/useCombatV6Session.ts similarity index 98% rename from src/react-app/components/feature/combat-v6/useCombatV6Session.ts rename to apps/web/src/components/feature/combat-v6/useCombatV6Session.ts index f3d55ac2..fe2d686f 100644 --- a/src/react-app/components/feature/combat-v6/useCombatV6Session.ts +++ b/apps/web/src/components/feature/combat-v6/useCombatV6Session.ts @@ -142,7 +142,7 @@ export function useCombatV6Session( ); const submit = useCallback( async ( - commands: import('@shared/contracts/combatV6').CombatV6CommandGroup, + commands: import('@daoyou/shared/contracts/combatV6').CombatV6CommandGroup, ) => { let completed = false; await run(async () => { diff --git a/src/react-app/components/feature/consumables/ConsumableDetailModal.tsx b/apps/web/src/components/feature/consumables/ConsumableDetailModal.tsx similarity index 74% rename from src/react-app/components/feature/consumables/ConsumableDetailModal.tsx rename to apps/web/src/components/feature/consumables/ConsumableDetailModal.tsx index 9a7ee050..a4337dee 100644 --- a/src/react-app/components/feature/consumables/ConsumableDetailModal.tsx +++ b/apps/web/src/components/feature/consumables/ConsumableDetailModal.tsx @@ -1,8 +1,8 @@ import { InkModal } from '@app/components/layout'; -import { consumableFactsOf } from '@shared/items/definitions/consumables'; -import type { CultivatorCondition } from '@shared/types/condition'; -import type { RealmType } from '@shared/types/constants'; -import type { Consumable } from '@shared/types/cultivator'; +import { consumableFactsOf } from '@daoyou/shared/items/definitions/consumables'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; +import type { RealmType } from '@daoyou/shared/types/constants'; +import type { Consumable } from '@daoyou/shared/types/cultivator'; import { ItemPreview } from '../items/ItemPreview'; interface ConsumableDetailModalProps { diff --git a/src/react-app/components/feature/consumables/ConsumableListCard.tsx b/apps/web/src/components/feature/consumables/ConsumableListCard.tsx similarity index 90% rename from src/react-app/components/feature/consumables/ConsumableListCard.tsx rename to apps/web/src/components/feature/consumables/ConsumableListCard.tsx index 22af8711..4d5b49a9 100644 --- a/src/react-app/components/feature/consumables/ConsumableListCard.tsx +++ b/apps/web/src/components/feature/consumables/ConsumableListCard.tsx @@ -5,12 +5,12 @@ import { isPillConsumable, isSpiritFruitConsumable, isTalismanConsumable, -} from '@shared/lib/consumables'; -import { CONSUMABLE_TYPE_DISPLAY_MAP } from '@shared/lib/gameConceptDisplay'; -import { calculatePillScore } from '@shared/lib/pillScore'; -import type { CultivatorCondition } from '@shared/types/condition'; -import type { RealmType } from '@shared/types/constants'; -import type { Consumable } from '@shared/types/cultivator'; +} from '@daoyou/shared/lib/consumables'; +import { CONSUMABLE_TYPE_DISPLAY_MAP } from '@daoyou/shared/lib/gameConceptDisplay'; +import { calculatePillScore } from '@daoyou/shared/lib/pillScore'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; +import type { RealmType } from '@daoyou/shared/types/constants'; +import type { Consumable } from '@daoyou/shared/types/cultivator'; import type { ReactNode } from 'react'; import { getConsumableListSummary } from './consumableListSummary'; import { PillAppearanceMark, PillKeywordLine } from './pillDisplayComponents'; diff --git a/src/react-app/components/feature/consumables/consumableListSummary.ts b/apps/web/src/components/feature/consumables/consumableListSummary.ts similarity index 70% rename from src/react-app/components/feature/consumables/consumableListSummary.ts rename to apps/web/src/components/feature/consumables/consumableListSummary.ts index 6e3da9fd..4176d572 100644 --- a/src/react-app/components/feature/consumables/consumableListSummary.ts +++ b/apps/web/src/components/feature/consumables/consumableListSummary.ts @@ -1,10 +1,10 @@ import { isPillConsumable, isSpiritFruitConsumable, -} from '@shared/lib/consumables'; -import type { CultivatorCondition } from '@shared/types/condition'; -import type { RealmType } from '@shared/types/constants'; -import type { Consumable } from '@shared/types/cultivator'; +} from '@daoyou/shared/lib/consumables'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; +import type { RealmType } from '@daoyou/shared/types/constants'; +import type { Consumable } from '@daoyou/shared/types/cultivator'; import { toPillDisplayModel, toSpiritFruitDisplayModel, diff --git a/src/react-app/components/feature/consumables/index.ts b/apps/web/src/components/feature/consumables/index.ts similarity index 100% rename from src/react-app/components/feature/consumables/index.ts rename to apps/web/src/components/feature/consumables/index.ts diff --git a/src/react-app/components/feature/consumables/pillDisplay.ts b/apps/web/src/components/feature/consumables/pillDisplay.ts similarity index 100% rename from src/react-app/components/feature/consumables/pillDisplay.ts rename to apps/web/src/components/feature/consumables/pillDisplay.ts diff --git a/src/react-app/components/feature/consumables/pillDisplayComponents.tsx b/apps/web/src/components/feature/consumables/pillDisplayComponents.tsx similarity index 96% rename from src/react-app/components/feature/consumables/pillDisplayComponents.tsx rename to apps/web/src/components/feature/consumables/pillDisplayComponents.tsx index 727de8f7..63cf139d 100644 --- a/src/react-app/components/feature/consumables/pillDisplayComponents.tsx +++ b/apps/web/src/components/feature/consumables/pillDisplayComponents.tsx @@ -3,8 +3,8 @@ import { getAffixUnderlineStyle, } from '@app/components/feature/products/affixPresentation'; import { InkBadge } from '@app/components/ui/InkBadge'; -import { getPillAppearanceColorClass } from '@shared/lib/pillAppearance'; -import { cn } from '@shared/lib/cn'; +import { getPillAppearanceColorClass } from '@daoyou/shared/lib/pillAppearance'; +import { cn } from '@daoyou/shared/lib/cn'; import type { PillDetailGroup, PillDisplayModel } from './pillDisplayModel'; type PillAppearanceDisplay = NonNullable; diff --git a/src/react-app/components/feature/consumables/pillDisplayModel.ts b/apps/web/src/components/feature/consumables/pillDisplayModel.ts similarity index 95% rename from src/react-app/components/feature/consumables/pillDisplayModel.ts rename to apps/web/src/components/feature/consumables/pillDisplayModel.ts index 2e26db92..c148c25d 100644 --- a/src/react-app/components/feature/consumables/pillDisplayModel.ts +++ b/apps/web/src/components/feature/consumables/pillDisplayModel.ts @@ -1,56 +1,56 @@ import { CULTIVATION_PILL_MAX_QUALITY_BY_REALM, getMinimumPillQualityByRealm, -} from '@shared/config/consumableSystem'; +} from '@daoyou/shared/config/consumableSystem'; import { BODY_CULTIVATION_REALM_REQUIREMENTS, getBodyCultivationThresholdByLevel, getBodyTrackKeyFromPath, isBodyCultivationTrackPath, isLegacyTemperingTrackPath, -} from '@shared/lib/bodyCultivation/config'; -import { normalizeBodyCultivationState } from '@shared/lib/bodyCultivation/normalize'; -import { getBodyCultivationSummary } from '@shared/lib/bodyCultivation/summary'; -import { getBreakthroughFocusPillLabel } from '@shared/lib/breakthroughPill'; -import { getConditionStatusTemplate } from '@shared/lib/conditionStatusRegistry'; +} from '@daoyou/shared/lib/bodyCultivation/config'; +import { normalizeBodyCultivationState } from '@daoyou/shared/lib/bodyCultivation/normalize'; +import { getBodyCultivationSummary } from '@daoyou/shared/lib/bodyCultivation/summary'; +import { getBreakthroughFocusPillLabel } from '@daoyou/shared/lib/breakthroughPill'; +import { getConditionStatusTemplate } from '@daoyou/shared/lib/conditionStatusRegistry'; import { CULTIVATION_BOOST_STATUS_KEY, getCultivationBoostDisplayText, -} from '@shared/lib/cultivationBoost'; +} from '@daoyou/shared/lib/cultivationBoost'; import { getGameConceptLabel, getResourceLabel, getResourceText, -} from '@shared/lib/gameConceptDisplay'; -import { getPillAppearanceLabel } from '@shared/lib/pillAppearance'; +} from '@daoyou/shared/lib/gameConceptDisplay'; +import { getPillAppearanceLabel } from '@daoyou/shared/lib/pillAppearance'; import { BREAKTHROUGH_FOCUS_STATUS_KEY, CLEAR_MIND_STATUS_KEY, getBreakthroughFocusBonus, getProtectMeridiansReductionPercent, PROTECT_MERIDIANS_STATUS_KEY, -} from '@shared/lib/pillEffectScaling'; +} from '@daoyou/shared/lib/pillEffectScaling'; import { getLongevityPillUsageLimit, getPillUsageKeywordLabel, getPillUsageRuleText, getPrimaryPillQuotaCategory, getRealmPillUsageLimit, -} from '@shared/lib/pillUsageText'; -import { getTrackConfig } from '@shared/lib/trackConfigRegistry'; +} from '@daoyou/shared/lib/pillUsageText'; +import { getTrackConfig } from '@daoyou/shared/lib/trackConfigRegistry'; import type { ConditionStatusKey, CultivatorCondition, -} from '@shared/types/condition'; -import { QUALITY_ORDER, type RealmType } from '@shared/types/constants'; +} from '@daoyou/shared/types/condition'; +import { QUALITY_ORDER, type RealmType } from '@daoyou/shared/types/constants'; import type { ConditionOperation, PillFamily, PillQuotaCategory, PillSpec, SpiritFruitSpec, -} from '@shared/types/consumable'; -import type { Consumable } from '@shared/types/cultivator'; +} from '@daoyou/shared/types/consumable'; +import type { Consumable } from '@daoyou/shared/types/cultivator'; interface PillDisplayOptions { realm?: RealmType; diff --git a/src/react-app/components/feature/consumables/talismanDisplay.ts b/apps/web/src/components/feature/consumables/talismanDisplay.ts similarity index 94% rename from src/react-app/components/feature/consumables/talismanDisplay.ts rename to apps/web/src/components/feature/consumables/talismanDisplay.ts index 2c716057..dfa4bea6 100644 --- a/src/react-app/components/feature/consumables/talismanDisplay.ts +++ b/apps/web/src/components/feature/consumables/talismanDisplay.ts @@ -2,27 +2,27 @@ import { ATTRIBUTE_RESET_TALISMAN_NAME, ATTRIBUTE_RESET_TALISMAN_SCENARIO, isAttributeResetTalismanScenario, -} from '@shared/config/attributeResetTalisman'; -import { IDENTITY_RESHAPE_SCENARIO } from '@shared/config/identityReshape'; +} from '@daoyou/shared/config/attributeResetTalisman'; +import { IDENTITY_RESHAPE_SCENARIO } from '@daoyou/shared/config/identityReshape'; import { QI_RESTORE_TALISMAN_SCENARIOS, isQiRestoreTalismanScenario, -} from '@shared/config/qiSystem'; +} from '@daoyou/shared/config/qiSystem'; import { SECT_MERIDIAN_RESET_TALISMAN_NAME, SECT_MERIDIAN_RESET_TALISMAN_SCENARIO, isSectMeridianResetTalismanScenario, -} from '@shared/config/sectMeridianResetTalisman'; +} from '@daoyou/shared/config/sectMeridianResetTalisman'; import { CHEAT_HEAVEN_TALISMAN_SCENARIO, isSectTransferTalismanScenario, -} from '@shared/config/sectTransferTalisman'; +} from '@daoyou/shared/config/sectTransferTalisman'; import { AUCTION_PRIVATE_LISTING_TALISMAN_SCENARIO, FRIEND_MAIL_TALISMAN_SCENARIO, -} from '@shared/config/socialConfig'; -import { isTalismanConsumable } from '@shared/lib/consumables'; -import type { Consumable } from '@shared/types/cultivator'; +} from '@daoyou/shared/config/socialConfig'; +import { isTalismanConsumable } from '@daoyou/shared/lib/consumables'; +import type { Consumable } from '@daoyou/shared/types/cultivator'; const TALISMAN_SCENARIO_LABELS: Record = { [ATTRIBUTE_RESET_TALISMAN_SCENARIO]: '根基属性重洗', diff --git a/src/react-app/components/feature/craft/FurnaceGatherEffect.tsx b/apps/web/src/components/feature/craft/FurnaceGatherEffect.tsx similarity index 100% rename from src/react-app/components/feature/craft/FurnaceGatherEffect.tsx rename to apps/web/src/components/feature/craft/FurnaceGatherEffect.tsx diff --git a/src/react-app/components/feature/cultivator-inspection/CultivatorAttributeTable.tsx b/apps/web/src/components/feature/cultivator-inspection/CultivatorAttributeTable.tsx similarity index 95% rename from src/react-app/components/feature/cultivator-inspection/CultivatorAttributeTable.tsx rename to apps/web/src/components/feature/cultivator-inspection/CultivatorAttributeTable.tsx index 24ed48ea..06d8cca4 100644 --- a/src/react-app/components/feature/cultivator-inspection/CultivatorAttributeTable.tsx +++ b/apps/web/src/components/feature/cultivator-inspection/CultivatorAttributeTable.tsx @@ -1,10 +1,10 @@ -import type { CultivatorInspectionData } from '@shared/contracts/player'; -import { cn } from '@shared/lib/cn'; +import type { CultivatorInspectionData } from '@daoyou/shared/contracts/player'; +import { cn } from '@daoyou/shared/lib/cn'; import { characterDisplayRows, formatCharacterAttributeValue as formatAttributeValue, formatCharacterAttributeModifier as formatModifier, -} from '@shared/lib/cultivatorDisplay'; +} from '@daoyou/shared/lib/cultivatorDisplay'; import { useMemo } from 'react'; function chunkPairs(items: T[]): T[][] { diff --git a/src/react-app/components/feature/cultivator-inspection/CultivatorFateSection.tsx b/apps/web/src/components/feature/cultivator-inspection/CultivatorFateSection.tsx similarity index 93% rename from src/react-app/components/feature/cultivator-inspection/CultivatorFateSection.tsx rename to apps/web/src/components/feature/cultivator-inspection/CultivatorFateSection.tsx index d45ec5e6..88c0f70c 100644 --- a/src/react-app/components/feature/cultivator-inspection/CultivatorFateSection.tsx +++ b/apps/web/src/components/feature/cultivator-inspection/CultivatorFateSection.tsx @@ -2,7 +2,7 @@ import { toFateDisplayModel } from '@app/components/feature/fates/FateDisplayAda import { FateEffectInlineList } from '@app/components/feature/fates/FateEffectInlineList'; import { InkList, InkNotice } from '@app/components/ui'; import { ItemCard } from '@app/components/ui/ItemCard'; -import type { CultivatorInspectionData } from '@shared/contracts/player'; +import type { CultivatorInspectionData } from '@daoyou/shared/contracts/player'; export function CultivatorFateSection({ cultivator, diff --git a/src/react-app/components/feature/cultivator-inspection/CultivatorInspectionModal.tsx b/apps/web/src/components/feature/cultivator-inspection/CultivatorInspectionModal.tsx similarity index 97% rename from src/react-app/components/feature/cultivator-inspection/CultivatorInspectionModal.tsx rename to apps/web/src/components/feature/cultivator-inspection/CultivatorInspectionModal.tsx index da036df7..7e362acf 100644 --- a/src/react-app/components/feature/cultivator-inspection/CultivatorInspectionModal.tsx +++ b/apps/web/src/components/feature/cultivator-inspection/CultivatorInspectionModal.tsx @@ -2,7 +2,7 @@ import { BodyCultivationInspectionSection } from '@app/components/feature/cultiv import { LingGenMini } from '@app/components/func/LingGen'; import { InkBadge } from '@app/components/ui'; import { InkDetailDrawer } from '@app/components/ui/InkDetailDrawer'; -import type { CultivatorInspectionData } from '@shared/contracts/player'; +import type { CultivatorInspectionData } from '@daoyou/shared/contracts/player'; import type { ReactNode } from 'react'; import { CultivatorAttributeTable } from './CultivatorAttributeTable'; import { CultivatorFateSection } from './CultivatorFateSection'; diff --git a/src/react-app/components/feature/cultivator-inspection/CultivatorLoadoutSections.tsx b/apps/web/src/components/feature/cultivator-inspection/CultivatorLoadoutSections.tsx similarity index 90% rename from src/react-app/components/feature/cultivator-inspection/CultivatorLoadoutSections.tsx rename to apps/web/src/components/feature/cultivator-inspection/CultivatorLoadoutSections.tsx index ab1ce7b3..7aca3eb2 100644 --- a/src/react-app/components/feature/cultivator-inspection/CultivatorLoadoutSections.tsx +++ b/apps/web/src/components/feature/cultivator-inspection/CultivatorLoadoutSections.tsx @@ -1,9 +1,9 @@ import { ItemSlot } from '@app/components/feature/items/ItemSlot'; import { InkNotice } from '@app/components/ui'; -import type { PublicCombatV6Build } from '@shared/combat-v6/public-build'; -import { DAO_EQUIPMENT_SLOTS } from '@shared/engine/combat-v6/equipment'; -import { itemDefinition } from '@shared/inventory'; -import { EQUIPMENT_SLOT_NAMES } from '@shared/items/definitions/equipment-blueprints'; +import type { PublicCombatV6Build } from '@daoyou/shared/combat-v6/public-build'; +import { DAO_EQUIPMENT_SLOTS } from '@daoyou/shared/engine/combat-v6/equipment'; +import { itemDefinition } from '@daoyou/shared/inventory'; +import { EQUIPMENT_SLOT_NAMES } from '@daoyou/shared/items/definitions/equipment-blueprints'; export function CultivatorLoadoutSections({ build, diff --git a/src/react-app/components/feature/cultivator-inspection/index.ts b/apps/web/src/components/feature/cultivator-inspection/index.ts similarity index 100% rename from src/react-app/components/feature/cultivator-inspection/index.ts rename to apps/web/src/components/feature/cultivator-inspection/index.ts diff --git a/src/react-app/components/feature/cultivator/BodyCultivationPanels.tsx b/apps/web/src/components/feature/cultivator/BodyCultivationPanels.tsx similarity index 98% rename from src/react-app/components/feature/cultivator/BodyCultivationPanels.tsx rename to apps/web/src/components/feature/cultivator/BodyCultivationPanels.tsx index 0e5eab2d..94d07b64 100644 --- a/src/react-app/components/feature/cultivator/BodyCultivationPanels.tsx +++ b/apps/web/src/components/feature/cultivator/BodyCultivationPanels.tsx @@ -8,10 +8,10 @@ import { getBodyCultivationSummary, type BodyCultivationSummary, type BodyCultivationTrackSummary, -} from '@shared/lib/bodyCultivation/summary'; -import { cn } from '@shared/lib/cn'; -import { type MarrowWashSummary } from '@shared/lib/marrowWash'; -import type { Cultivator } from '@shared/types/cultivator'; +} from '@daoyou/shared/lib/bodyCultivation/summary'; +import { cn } from '@daoyou/shared/lib/cn'; +import { type MarrowWashSummary } from '@daoyou/shared/lib/marrowWash'; +import type { Cultivator } from '@daoyou/shared/types/cultivator'; import { type ReactNode } from 'react'; import { MarrowWashPanel } from './MarrowWashPanel'; diff --git a/src/react-app/components/feature/cultivator/CharacterAttributesPanel.tsx b/apps/web/src/components/feature/cultivator/CharacterAttributesPanel.tsx similarity index 100% rename from src/react-app/components/feature/cultivator/CharacterAttributesPanel.tsx rename to apps/web/src/components/feature/cultivator/CharacterAttributesPanel.tsx diff --git a/src/react-app/components/feature/cultivator/CharacterPanelRedirect.tsx b/apps/web/src/components/feature/cultivator/CharacterPanelRedirect.tsx similarity index 100% rename from src/react-app/components/feature/cultivator/CharacterPanelRedirect.tsx rename to apps/web/src/components/feature/cultivator/CharacterPanelRedirect.tsx diff --git a/src/react-app/components/feature/cultivator/CharacterSheetRow.tsx b/apps/web/src/components/feature/cultivator/CharacterSheetRow.tsx similarity index 93% rename from src/react-app/components/feature/cultivator/CharacterSheetRow.tsx rename to apps/web/src/components/feature/cultivator/CharacterSheetRow.tsx index cc743447..271d42ec 100644 --- a/src/react-app/components/feature/cultivator/CharacterSheetRow.tsx +++ b/apps/web/src/components/feature/cultivator/CharacterSheetRow.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import type { ReactNode } from 'react'; /** All character-sheet values share this label column and text baseline. */ diff --git a/src/react-app/components/feature/cultivator/CultivatorBiography.tsx b/apps/web/src/components/feature/cultivator/CultivatorBiography.tsx similarity index 97% rename from src/react-app/components/feature/cultivator/CultivatorBiography.tsx rename to apps/web/src/components/feature/cultivator/CultivatorBiography.tsx index e2c12a17..ad98b863 100644 --- a/src/react-app/components/feature/cultivator/CultivatorBiography.tsx +++ b/apps/web/src/components/feature/cultivator/CultivatorBiography.tsx @@ -3,9 +3,9 @@ import { InkBadge, InkNotice } from '@app/components/ui'; import { GameIcon } from '@app/components/ui/GameIcon'; import { tierColorMap } from '@app/components/ui/inkBadgeTiers'; import { useCultivatorIdentity } from '@app/lib/resources/player'; -import { cn } from '@shared/lib/cn'; -import { getElementInfo } from '@shared/lib/gameConceptDisplay'; -import type { Cultivator } from '@shared/types/cultivator'; +import { cn } from '@daoyou/shared/lib/cn'; +import { getElementInfo } from '@daoyou/shared/lib/gameConceptDisplay'; +import type { Cultivator } from '@daoyou/shared/types/cultivator'; import { useState } from 'react'; import { CultivatorReincarnation } from './CultivatorReincarnation'; diff --git a/src/react-app/components/feature/cultivator/CultivatorIdentity.tsx b/apps/web/src/components/feature/cultivator/CultivatorIdentity.tsx similarity index 100% rename from src/react-app/components/feature/cultivator/CultivatorIdentity.tsx rename to apps/web/src/components/feature/cultivator/CultivatorIdentity.tsx diff --git a/src/react-app/components/feature/cultivator/CultivatorReincarnation.tsx b/apps/web/src/components/feature/cultivator/CultivatorReincarnation.tsx similarity index 100% rename from src/react-app/components/feature/cultivator/CultivatorReincarnation.tsx rename to apps/web/src/components/feature/cultivator/CultivatorReincarnation.tsx diff --git a/src/react-app/components/feature/cultivator/CultivatorStatsPanel.tsx b/apps/web/src/components/feature/cultivator/CultivatorStatsPanel.tsx similarity index 97% rename from src/react-app/components/feature/cultivator/CultivatorStatsPanel.tsx rename to apps/web/src/components/feature/cultivator/CultivatorStatsPanel.tsx index d89ac502..e41815be 100644 --- a/src/react-app/components/feature/cultivator/CultivatorStatsPanel.tsx +++ b/apps/web/src/components/feature/cultivator/CultivatorStatsPanel.tsx @@ -4,15 +4,15 @@ import { useInkUI } from '@app/components/providers/InkUIProvider'; import { InkButton, InkDetailDrawer, InkNotice } from '@app/components/ui'; import { useResourceMutation } from '@app/lib/resources/mutations'; import { useCultivatorProgress } from '@app/lib/resources/player'; -import { ATTRIBUTE_RESET_TALISMAN_NAME } from '@shared/config/attributeResetTalisman'; -import type { AttributePreviewData } from '@shared/contracts/characterAttributes'; -import type { CharacterPanelV1 } from '@shared/engine/combat-v6/projection'; +import { ATTRIBUTE_RESET_TALISMAN_NAME } from '@daoyou/shared/config/attributeResetTalisman'; +import type { AttributePreviewData } from '@daoyou/shared/contracts/characterAttributes'; +import type { CharacterPanelV1 } from '@daoyou/shared/engine/combat-v6/projection'; import { CHARACTER_ATTRIBUTE_LABELS, CHARACTER_PANEL_LABELS, formatCharacterAttributeValue, -} from '@shared/lib/cultivatorDisplay'; -import type { Attributes } from '@shared/types/cultivator'; +} from '@daoyou/shared/lib/cultivatorDisplay'; +import type { Attributes } from '@daoyou/shared/types/cultivator'; import { useEffect, useRef, useState } from 'react'; import { useSearchParams } from 'react-router'; import { diff --git a/src/react-app/components/feature/cultivator/CultivatorVitals.tsx b/apps/web/src/components/feature/cultivator/CultivatorVitals.tsx similarity index 97% rename from src/react-app/components/feature/cultivator/CultivatorVitals.tsx rename to apps/web/src/components/feature/cultivator/CultivatorVitals.tsx index 982e5feb..fcba044a 100644 --- a/src/react-app/components/feature/cultivator/CultivatorVitals.tsx +++ b/apps/web/src/components/feature/cultivator/CultivatorVitals.tsx @@ -1,6 +1,6 @@ import { InkButton, InkDetailDrawer } from '@app/components/ui'; -import { getPillToxicityStage } from '@shared/lib/condition'; -import { getConditionStatusTemplate } from '@shared/lib/conditionStatusRegistry'; +import { getPillToxicityStage } from '@daoyou/shared/lib/condition'; +import { getConditionStatusTemplate } from '@daoyou/shared/lib/conditionStatusRegistry'; import { useState } from 'react'; import { CharacterSheetRow } from './CharacterSheetRow'; import { diff --git a/src/react-app/components/feature/cultivator/MarrowWashPanel.tsx b/apps/web/src/components/feature/cultivator/MarrowWashPanel.tsx similarity index 97% rename from src/react-app/components/feature/cultivator/MarrowWashPanel.tsx rename to apps/web/src/components/feature/cultivator/MarrowWashPanel.tsx index fbe4966e..1b28267e 100644 --- a/src/react-app/components/feature/cultivator/MarrowWashPanel.tsx +++ b/apps/web/src/components/feature/cultivator/MarrowWashPanel.tsx @@ -11,12 +11,12 @@ import { useCultivatorCondition, useCultivatorIdentity, } from '@app/lib/resources/player'; -import type { PlayerIdentityCultivator } from '@shared/contracts/player'; -import { getElementInfo } from '@shared/lib/gameConceptDisplay'; +import type { PlayerIdentityCultivator } from '@daoyou/shared/contracts/player'; +import { getElementInfo } from '@daoyou/shared/lib/gameConceptDisplay'; import { MARROW_WASH_BREAKTHROUGH_QI_COST, getMarrowWashSummary, -} from '@shared/lib/marrowWash'; +} from '@daoyou/shared/lib/marrowWash'; import { useState } from 'react'; function RootStrengthList({ diff --git a/src/react-app/components/feature/cultivator/PersistentStatusesCard.tsx b/apps/web/src/components/feature/cultivator/PersistentStatusesCard.tsx similarity index 96% rename from src/react-app/components/feature/cultivator/PersistentStatusesCard.tsx rename to apps/web/src/components/feature/cultivator/PersistentStatusesCard.tsx index a874cefd..a29a89e1 100644 --- a/src/react-app/components/feature/cultivator/PersistentStatusesCard.tsx +++ b/apps/web/src/components/feature/cultivator/PersistentStatusesCard.tsx @@ -1,17 +1,17 @@ import { GameSceneSection } from '@app/components/game-shell/GameSceneSection'; import { InkButton } from '@app/components/ui'; import { useCultivatorProgress } from '@app/lib/resources/player'; -import { getBodyCultivationSummary } from '@shared/lib/bodyCultivation/summary'; -import { cn } from '@shared/lib/cn'; +import { getBodyCultivationSummary } from '@daoyou/shared/lib/bodyCultivation/summary'; +import { cn } from '@daoyou/shared/lib/cn'; import { getBreakthroughPenaltyPercent, getPillToxicityRecoveryMultiplier, getPillToxicityStage, isConditionStatusActive, -} from '@shared/lib/condition'; -import { evaluateFateContext } from '@shared/lib/fates'; -import { getAllTrackConfigs } from '@shared/lib/trackConfigRegistry'; -import type { ConditionTrackPath } from '@shared/types/condition'; +} from '@daoyou/shared/lib/condition'; +import { evaluateFateContext } from '@daoyou/shared/lib/fates'; +import { getAllTrackConfigs } from '@daoyou/shared/lib/trackConfigRegistry'; +import type { ConditionTrackPath } from '@daoyou/shared/types/condition'; import { useCultivatorDisplayProjection } from './useCultivatorDisplayProjection'; const TRACK_ORDER: ConditionTrackPath[] = [ diff --git a/src/react-app/components/feature/cultivator/TitleEditorModal.tsx b/apps/web/src/components/feature/cultivator/TitleEditorModal.tsx similarity index 100% rename from src/react-app/components/feature/cultivator/TitleEditorModal.tsx rename to apps/web/src/components/feature/cultivator/TitleEditorModal.tsx diff --git a/src/react-app/components/feature/cultivator/YieldCard.tsx b/apps/web/src/components/feature/cultivator/YieldCard.tsx similarity index 98% rename from src/react-app/components/feature/cultivator/YieldCard.tsx rename to apps/web/src/components/feature/cultivator/YieldCard.tsx index 046a8c8e..f1dded20 100644 --- a/src/react-app/components/feature/cultivator/YieldCard.tsx +++ b/apps/web/src/components/feature/cultivator/YieldCard.tsx @@ -6,8 +6,8 @@ import { useInkUI } from '@app/components/providers/InkUIProvider'; import { InkBadge } from '@app/components/ui/InkBadge'; import { InkButton } from '@app/components/ui/InkButton'; import { consumeResourceChanges } from '@app/lib/resources/mutations'; -import { GeneratedMaterial } from '@shared/engine/material/creation/types'; -import { getGameConceptInfo } from '@shared/lib/gameConceptDisplay'; +import { GeneratedMaterial } from '@daoyou/shared/engine/material/creation/types'; +import { getGameConceptInfo } from '@daoyou/shared/lib/gameConceptDisplay'; import { useEffect, useState } from 'react'; interface YieldCardProps { diff --git a/src/react-app/components/feature/cultivator/attributeAllocationControlLogic.ts b/apps/web/src/components/feature/cultivator/attributeAllocationControlLogic.ts similarity index 89% rename from src/react-app/components/feature/cultivator/attributeAllocationControlLogic.ts rename to apps/web/src/components/feature/cultivator/attributeAllocationControlLogic.ts index 4c373998..9c446b7e 100644 --- a/src/react-app/components/feature/cultivator/attributeAllocationControlLogic.ts +++ b/apps/web/src/components/feature/cultivator/attributeAllocationControlLogic.ts @@ -1,4 +1,4 @@ -import type { Attributes } from '@shared/types/cultivator'; +import type { Attributes } from '@daoyou/shared/types/cultivator'; export function createEmptyAttributeDraft(): Attributes { return { diff --git a/src/react-app/components/feature/cultivator/persistentStatusDetails.ts b/apps/web/src/components/feature/cultivator/persistentStatusDetails.ts similarity index 88% rename from src/react-app/components/feature/cultivator/persistentStatusDetails.ts rename to apps/web/src/components/feature/cultivator/persistentStatusDetails.ts index 11a33a9a..d800f05e 100644 --- a/src/react-app/components/feature/cultivator/persistentStatusDetails.ts +++ b/apps/web/src/components/feature/cultivator/persistentStatusDetails.ts @@ -2,25 +2,25 @@ import { getBreakthroughPenaltyPercent, getPillToxicityRecoveryMultiplier, getPillToxicityStage, -} from '@shared/lib/condition'; -import { evaluateFateContext } from '@shared/lib/fates'; -import { getConditionStatusTemplate } from '@shared/lib/conditionStatusRegistry'; +} from '@daoyou/shared/lib/condition'; +import { evaluateFateContext } from '@daoyou/shared/lib/fates'; +import { getConditionStatusTemplate } from '@daoyou/shared/lib/conditionStatusRegistry'; import { CULTIVATION_BOOST_STATUS_KEY, getCultivationBoostDisplayText, -} from '@shared/lib/cultivationBoost'; +} from '@daoyou/shared/lib/cultivationBoost'; import { BREAKTHROUGH_FOCUS_STATUS_KEY, CLEAR_MIND_STATUS_KEY, getBreakthroughFocusBonus, getProtectMeridiansReductionPercent, PROTECT_MERIDIANS_STATUS_KEY, -} from '@shared/lib/pillEffectScaling'; +} from '@daoyou/shared/lib/pillEffectScaling'; import type { ConditionStatusInstance, CultivatorCondition, -} from '@shared/types/condition'; -import type { PreHeavenFate } from '@shared/types/cultivator'; +} from '@daoyou/shared/types/condition'; +import type { PreHeavenFate } from '@daoyou/shared/types/cultivator'; export function getStatusEffectDetails( status: ConditionStatusInstance, diff --git a/src/react-app/components/feature/cultivator/useCultivatorDisplayProjection.ts b/apps/web/src/components/feature/cultivator/useCultivatorDisplayProjection.ts similarity index 92% rename from src/react-app/components/feature/cultivator/useCultivatorDisplayProjection.ts rename to apps/web/src/components/feature/cultivator/useCultivatorDisplayProjection.ts index 8384a4b5..53f2c8bb 100644 --- a/src/react-app/components/feature/cultivator/useCultivatorDisplayProjection.ts +++ b/apps/web/src/components/feature/cultivator/useCultivatorDisplayProjection.ts @@ -12,18 +12,18 @@ import { } from '@app/lib/resources/recoveryClock'; import { type CultivatorDisplayInput, -} from '@shared/lib/cultivatorDisplay'; -import type { CombatV6ResourceAuthority, CultivatorDisplaySnapshot } from '@shared/lib/cultivatorDisplay'; +} from '@daoyou/shared/lib/cultivatorDisplay'; +import type { CombatV6ResourceAuthority, CultivatorDisplaySnapshot } from '@daoyou/shared/lib/cultivatorDisplay'; import { getNextConditionStatusExpiryMs, isConditionStatusActive, projectNaturalRecoveryResources, type NaturalRecoveryProjection, -} from '@shared/lib/condition'; -import { evaluateFateContext } from '@shared/lib/fates'; -import type { PlayerIdentityCultivator } from '@shared/contracts/player'; -import type { CultivatorCondition } from '@shared/types/condition'; -import type { Cultivator } from '@shared/types/cultivator'; +} from '@daoyou/shared/lib/condition'; +import { evaluateFateContext } from '@daoyou/shared/lib/fates'; +import type { PlayerIdentityCultivator } from '@daoyou/shared/contracts/player'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; +import type { Cultivator } from '@daoyou/shared/types/cultivator'; import { useMemo } from 'react'; export type CultivatorDisplayProjectionInput = PlayerIdentityCultivator & diff --git a/src/react-app/components/feature/cultivator/useQiActionConfirm.tsx b/apps/web/src/components/feature/cultivator/useQiActionConfirm.tsx similarity index 100% rename from src/react-app/components/feature/cultivator/useQiActionConfirm.tsx rename to apps/web/src/components/feature/cultivator/useQiActionConfirm.tsx diff --git a/src/react-app/components/feature/cultivator/useQiState.ts b/apps/web/src/components/feature/cultivator/useQiState.ts similarity index 93% rename from src/react-app/components/feature/cultivator/useQiState.ts rename to apps/web/src/components/feature/cultivator/useQiState.ts index 66e9e4e4..cf660af2 100644 --- a/src/react-app/components/feature/cultivator/useQiState.ts +++ b/apps/web/src/components/feature/cultivator/useQiState.ts @@ -3,13 +3,13 @@ import { getEstimatedServerNowMs, useRecoveryClock, } from '@app/lib/resources/recoveryClock'; -import { QI_MAX } from '@shared/config/qiSystem'; -import type { QiState } from '@shared/contracts/qi'; +import { QI_MAX } from '@daoyou/shared/config/qiSystem'; +import type { QiState } from '@daoyou/shared/contracts/qi'; import { projectNaturalQiState, type NaturalQiProjection, type QiRecoveryStatus, -} from '@shared/lib/qi'; +} from '@daoyou/shared/lib/qi'; export interface QiRecoveryInfo { status: QiRecoveryStatus; diff --git a/src/react-app/components/feature/enlightenment/EnlightenmentRoom.tsx b/apps/web/src/components/feature/enlightenment/EnlightenmentRoom.tsx similarity index 98% rename from src/react-app/components/feature/enlightenment/EnlightenmentRoom.tsx rename to apps/web/src/components/feature/enlightenment/EnlightenmentRoom.tsx index e7a7cf5f..45e0fa89 100644 --- a/src/react-app/components/feature/enlightenment/EnlightenmentRoom.tsx +++ b/apps/web/src/components/feature/enlightenment/EnlightenmentRoom.tsx @@ -8,13 +8,13 @@ import { GameIcon } from '@app/components/ui/GameIcon'; import { GameImage } from '@app/components/ui/GameImage'; import { InkButton } from '@app/components/ui/InkButton'; import { InkDetailDrawer } from '@app/components/ui/InkDetailDrawer'; -import { CHARACTER_MANUALS_V1 } from '@shared/engine/combat-v6/manuals/content'; -import { cn } from '@shared/lib/cn'; +import { CHARACTER_MANUALS_V1 } from '@daoyou/shared/engine/combat-v6/manuals/content'; +import { cn } from '@daoyou/shared/lib/cn'; import { ENLIGHTENMENT_REALMS, enlightenmentQualityCap, -} from '@shared/manuals/enlightenment'; -import { REALM_VALUES } from '@shared/types/constants'; +} from '@daoyou/shared/manuals/enlightenment'; +import { REALM_VALUES } from '@daoyou/shared/types/constants'; import { useState } from 'react'; import { CraftInventoryPanel } from '../items/CraftInventoryPanel'; import { InventoryItems } from '../items/InventoryItems'; diff --git a/src/react-app/components/feature/enlightenment/useEnlightenmentSession.ts b/apps/web/src/components/feature/enlightenment/useEnlightenmentSession.ts similarity index 98% rename from src/react-app/components/feature/enlightenment/useEnlightenmentSession.ts rename to apps/web/src/components/feature/enlightenment/useEnlightenmentSession.ts index 8f7ab644..d4b34f0a 100644 --- a/src/react-app/components/feature/enlightenment/useEnlightenmentSession.ts +++ b/apps/web/src/components/feature/enlightenment/useEnlightenmentSession.ts @@ -11,13 +11,13 @@ import { type EnlightenmentRequest, type EnlightenmentResult, type EnlightenmentView, -} from '@shared/contracts/enlightenment'; -import type { InventoryView } from '@shared/contracts/inventory'; +} from '@daoyou/shared/contracts/enlightenment'; +import type { InventoryView } from '@daoyou/shared/contracts/inventory'; import { enlightenmentMaterialProblem, prepareEnlightenment, type EnlightenmentPreview, -} from '@shared/manuals/enlightenment'; +} from '@daoyou/shared/manuals/enlightenment'; import { useEffect, useRef, useState } from 'react'; import { combatV6Request, mutationBody } from '../combat-v6/request'; import type { InventoryFilter } from '../items/inventoryFilterModel'; diff --git a/src/react-app/components/feature/fates/FateDetailModal.tsx b/apps/web/src/components/feature/fates/FateDetailModal.tsx similarity index 92% rename from src/react-app/components/feature/fates/FateDetailModal.tsx rename to apps/web/src/components/feature/fates/FateDetailModal.tsx index b4028552..34cfca74 100644 --- a/src/react-app/components/feature/fates/FateDetailModal.tsx +++ b/apps/web/src/components/feature/fates/FateDetailModal.tsx @@ -3,7 +3,7 @@ import { } from '@app/components/feature/fates/FateDisplayAdapter'; import { FateEffectList } from '@app/components/feature/fates/FateEffectList'; import { InkBadge, ItemShowcaseModal } from '@app/components/ui'; -import type { PreHeavenFate } from '@shared/types/cultivator'; +import type { PreHeavenFate } from '@daoyou/shared/types/cultivator'; interface FateDetailModalProps { isOpen: boolean; diff --git a/src/react-app/components/feature/fates/FateDisplayAdapter.ts b/apps/web/src/components/feature/fates/FateDisplayAdapter.ts similarity index 94% rename from src/react-app/components/feature/fates/FateDisplayAdapter.ts rename to apps/web/src/components/feature/fates/FateDisplayAdapter.ts index 7e2cfc4a..ad7eabbf 100644 --- a/src/react-app/components/feature/fates/FateDisplayAdapter.ts +++ b/apps/web/src/components/feature/fates/FateDisplayAdapter.ts @@ -1,8 +1,8 @@ import type { FateEffectEntry, PreHeavenFate, -} from '@shared/types/cultivator'; -import type { Quality } from '@shared/types/constants'; +} from '@daoyou/shared/types/cultivator'; +import type { Quality } from '@daoyou/shared/types/constants'; export interface FateDetailGroup { key: string; diff --git a/src/react-app/components/feature/fates/FateEffectInlineList.tsx b/apps/web/src/components/feature/fates/FateEffectInlineList.tsx similarity index 100% rename from src/react-app/components/feature/fates/FateEffectInlineList.tsx rename to apps/web/src/components/feature/fates/FateEffectInlineList.tsx diff --git a/src/react-app/components/feature/fates/FateEffectList.tsx b/apps/web/src/components/feature/fates/FateEffectList.tsx similarity index 100% rename from src/react-app/components/feature/fates/FateEffectList.tsx rename to apps/web/src/components/feature/fates/FateEffectList.tsx diff --git a/src/react-app/components/feature/forging/ForgingFurnace.tsx b/apps/web/src/components/feature/forging/ForgingFurnace.tsx similarity index 99% rename from src/react-app/components/feature/forging/ForgingFurnace.tsx rename to apps/web/src/components/feature/forging/ForgingFurnace.tsx index 2ff59504..fbf497e5 100644 --- a/src/react-app/components/feature/forging/ForgingFurnace.tsx +++ b/apps/web/src/components/feature/forging/ForgingFurnace.tsx @@ -1,6 +1,6 @@ import { GameIcon } from '@app/components/ui/GameIcon'; import { InkButton } from '@app/components/ui/InkButton'; -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import { FurnaceGatherEffect } from '../craft/FurnaceGatherEffect'; import { ItemSlot } from '../items/ItemSlot'; import type { ForgingSession } from './useForgingSession'; diff --git a/src/react-app/components/feature/forging/ForgingInventory.tsx b/apps/web/src/components/feature/forging/ForgingInventory.tsx similarity index 100% rename from src/react-app/components/feature/forging/ForgingInventory.tsx rename to apps/web/src/components/feature/forging/ForgingInventory.tsx diff --git a/src/react-app/components/feature/forging/ForgingRoom.tsx b/apps/web/src/components/feature/forging/ForgingRoom.tsx similarity index 97% rename from src/react-app/components/feature/forging/ForgingRoom.tsx rename to apps/web/src/components/feature/forging/ForgingRoom.tsx index f848cffd..e081e42d 100644 --- a/src/react-app/components/feature/forging/ForgingRoom.tsx +++ b/apps/web/src/components/feature/forging/ForgingRoom.tsx @@ -10,18 +10,18 @@ import { InkButton } from '@app/components/ui/InkButton'; import { InkDetailDrawer } from '@app/components/ui/InkDetailDrawer'; import { InkInput } from '@app/components/ui/InkInput'; import { InkTooltip } from '@app/components/ui/InkTooltip'; -import { getLevelRealmStage } from '@shared/config/realmProgression'; +import { getLevelRealmStage } from '@daoyou/shared/config/realmProgression'; import { daoEquipmentBaseRange, daoEquipmentTemplateOf, -} from '@shared/engine/combat-v6/equipment/content'; -import { DAO_EQUIPMENT_FORGING } from '@shared/engine/combat-v6/equipment/forging-content'; +} from '@daoyou/shared/engine/combat-v6/equipment/content'; +import { DAO_EQUIPMENT_FORGING } from '@daoyou/shared/engine/combat-v6/equipment/forging-content'; import { DAO_WEAPONS, DAO_WEAPON_TYPES, -} from '@shared/engine/combat-v6/equipment/weapons'; -import { FORGE_INTENT_MAX_LENGTH } from '@shared/forging/narrative'; -import { EQUIPMENT_ATTRIBUTE_NAMES } from '@shared/inventory/equipment'; +} from '@daoyou/shared/engine/combat-v6/equipment/weapons'; +import { FORGE_INTENT_MAX_LENGTH } from '@daoyou/shared/forging/narrative'; +import { EQUIPMENT_ATTRIBUTE_NAMES } from '@daoyou/shared/inventory/equipment'; import { useEffect, useRef, useState, useSyncExternalStore } from 'react'; import { useBeforeUnload, useBlocker } from 'react-router'; import { ForgingFurnace } from './ForgingFurnace'; diff --git a/src/react-app/components/feature/forging/VaultWithdrawal.tsx b/apps/web/src/components/feature/forging/VaultWithdrawal.tsx similarity index 98% rename from src/react-app/components/feature/forging/VaultWithdrawal.tsx rename to apps/web/src/components/feature/forging/VaultWithdrawal.tsx index 1ce3eb5a..857dd568 100644 --- a/src/react-app/components/feature/forging/VaultWithdrawal.tsx +++ b/apps/web/src/components/feature/forging/VaultWithdrawal.tsx @@ -3,8 +3,8 @@ import { InkButton } from '@app/components/ui/InkButton'; import { InkDetailDrawer } from '@app/components/ui/InkDetailDrawer'; import { consumeResourceMutation } from '@app/lib/resources/mutations'; import { usePlayerSession } from '@app/lib/resources/player'; -import type { VaultView } from '@shared/contracts/forging'; -import { MATERIAL_TYPE_NAMES } from '@shared/items/definitions/materials'; +import type { VaultView } from '@daoyou/shared/contracts/forging'; +import { MATERIAL_TYPE_NAMES } from '@daoyou/shared/items/definitions/materials'; import { useEffect, useRef, useState } from 'react'; import { combatV6Request, mutationBody } from '../combat-v6/request'; diff --git a/src/react-app/components/feature/forging/useForgingSession.ts b/apps/web/src/components/feature/forging/useForgingSession.ts similarity index 92% rename from src/react-app/components/feature/forging/useForgingSession.ts rename to apps/web/src/components/feature/forging/useForgingSession.ts index 85d7e2d6..79beea31 100644 --- a/src/react-app/components/feature/forging/useForgingSession.ts +++ b/apps/web/src/components/feature/forging/useForgingSession.ts @@ -2,20 +2,20 @@ import type { InventoryFilter } from '@app/components/feature/items/inventoryFil import { useInventoryBag } from '@app/lib/resources/bag'; import { useCraftStorage } from '@app/lib/resources/craftStorage'; import { consumeResourceMutation } from '@app/lib/resources/mutations'; -import type { ForgeRequest, ForgeView } from '@shared/contracts/forging'; -import type { InventoryView } from '@shared/contracts/inventory'; +import type { ForgeRequest, ForgeView } from '@daoyou/shared/contracts/forging'; +import type { InventoryView } from '@daoyou/shared/contracts/inventory'; import { equipmentRealm, isOpenEquipmentLevel, -} from '@shared/engine/combat-v6/equipment/realm'; -import type { DaoEquipmentInstanceV1 } from '@shared/engine/combat-v6/equipment/types'; -import type { DaoWeaponType } from '@shared/engine/combat-v6/equipment/weapons'; -import { FORGE_INTENT_MAX_LENGTH } from '@shared/forging/narrative'; -import { forgingCost, forgingInputs } from '@shared/forging/rules'; -import { itemDefinition } from '@shared/inventory'; -import { FORGING_MATERIAL_TYPES } from '@shared/items/definitions/materials'; -import { materialFactsOf } from '@shared/items/material'; -import { QUALITY_ORDER } from '@shared/types/constants'; +} from '@daoyou/shared/engine/combat-v6/equipment/realm'; +import type { DaoEquipmentInstanceV1 } from '@daoyou/shared/engine/combat-v6/equipment/types'; +import type { DaoWeaponType } from '@daoyou/shared/engine/combat-v6/equipment/weapons'; +import { FORGE_INTENT_MAX_LENGTH } from '@daoyou/shared/forging/narrative'; +import { forgingCost, forgingInputs } from '@daoyou/shared/forging/rules'; +import { itemDefinition } from '@daoyou/shared/inventory'; +import { FORGING_MATERIAL_TYPES } from '@daoyou/shared/items/definitions/materials'; +import { materialFactsOf } from '@daoyou/shared/items/material'; +import { QUALITY_ORDER } from '@daoyou/shared/types/constants'; import { useEffect, useRef, useState } from 'react'; import { combatV6Request, mutationBody } from '../combat-v6/request'; diff --git a/src/react-app/components/feature/friends/FriendTargetModal.tsx b/apps/web/src/components/feature/friends/FriendTargetModal.tsx similarity index 98% rename from src/react-app/components/feature/friends/FriendTargetModal.tsx rename to apps/web/src/components/feature/friends/FriendTargetModal.tsx index baaede7c..d1eaf23e 100644 --- a/src/react-app/components/feature/friends/FriendTargetModal.tsx +++ b/apps/web/src/components/feature/friends/FriendTargetModal.tsx @@ -4,7 +4,7 @@ import { InkButton, InkNotice } from '@app/components/ui'; import type { FriendCultivatorSummary, FriendTargetResponse, -} from '@shared/contracts/friends'; +} from '@daoyou/shared/contracts/friends'; import { useEffect, useState } from 'react'; export interface FriendTargetModalProps { diff --git a/src/react-app/components/feature/friends/index.ts b/apps/web/src/components/feature/friends/index.ts similarity index 100% rename from src/react-app/components/feature/friends/index.ts rename to apps/web/src/components/feature/friends/index.ts diff --git a/src/react-app/components/feature/game-activity/GameActivityImmersiveGate.tsx b/apps/web/src/components/feature/game-activity/GameActivityImmersiveGate.tsx similarity index 100% rename from src/react-app/components/feature/game-activity/GameActivityImmersiveGate.tsx rename to apps/web/src/components/feature/game-activity/GameActivityImmersiveGate.tsx diff --git a/src/react-app/components/feature/game-activity/index.ts b/apps/web/src/components/feature/game-activity/index.ts similarity index 100% rename from src/react-app/components/feature/game-activity/index.ts rename to apps/web/src/components/feature/game-activity/index.ts diff --git a/src/react-app/components/feature/game-activity/useGameActivityViewport.ts b/apps/web/src/components/feature/game-activity/useGameActivityViewport.ts similarity index 100% rename from src/react-app/components/feature/game-activity/useGameActivityViewport.ts rename to apps/web/src/components/feature/game-activity/useGameActivityViewport.ts diff --git a/src/react-app/components/feature/game-activity/virtualJoystickAssets.ts b/apps/web/src/components/feature/game-activity/virtualJoystickAssets.ts similarity index 100% rename from src/react-app/components/feature/game-activity/virtualJoystickAssets.ts rename to apps/web/src/components/feature/game-activity/virtualJoystickAssets.ts diff --git a/src/react-app/components/feature/guide/GuideOverlay.tsx b/apps/web/src/components/feature/guide/GuideOverlay.tsx similarity index 98% rename from src/react-app/components/feature/guide/GuideOverlay.tsx rename to apps/web/src/components/feature/guide/GuideOverlay.tsx index f0939bad..9edeb22e 100644 --- a/src/react-app/components/feature/guide/GuideOverlay.tsx +++ b/apps/web/src/components/feature/guide/GuideOverlay.tsx @@ -2,15 +2,15 @@ import { InkButton } from '@app/components/ui'; import { consumeResourceMutation } from '@app/lib/resources/mutations'; import { usePlayerSession } from '@app/lib/resources/player'; import { useStory } from '@app/lib/story/useStory'; -import { getGuideLesson } from '@shared/guide/catalog'; +import { getGuideLesson } from '@daoyou/shared/guide/catalog'; import { advanceGuide, createGuideState, currentGuideStep, restoreGuideState, type GuideState, -} from '@shared/guide/interpreter'; -import type { GuideStep } from '@shared/guide/schema'; +} from '@daoyou/shared/guide/interpreter'; +import type { GuideStep } from '@daoyou/shared/guide/schema'; import { useCallback, useEffect, useRef, useState } from 'react'; type ShownStep = Extract; diff --git a/src/react-app/components/feature/home/CaveQuickGrid.tsx b/apps/web/src/components/feature/home/CaveQuickGrid.tsx similarity index 100% rename from src/react-app/components/feature/home/CaveQuickGrid.tsx rename to apps/web/src/components/feature/home/CaveQuickGrid.tsx diff --git a/src/react-app/components/feature/home/DailyDivinationEntry.tsx b/apps/web/src/components/feature/home/DailyDivinationEntry.tsx similarity index 97% rename from src/react-app/components/feature/home/DailyDivinationEntry.tsx rename to apps/web/src/components/feature/home/DailyDivinationEntry.tsx index ab0f2e2b..c04c8186 100644 --- a/src/react-app/components/feature/home/DailyDivinationEntry.tsx +++ b/apps/web/src/components/feature/home/DailyDivinationEntry.tsx @@ -1,7 +1,7 @@ import { GameIcon } from '@app/components/ui/GameIcon'; import { InkButton } from '@app/components/ui/InkButton'; import { getDivination } from '@app/lib/divinationApi'; -import type { DivinationView } from '@shared/contracts/divination'; +import type { DivinationView } from '@daoyou/shared/contracts/divination'; import { useEffect, useState } from 'react'; export function DailyDivinationEntry() { diff --git a/src/react-app/components/feature/home/HomeAside.tsx b/apps/web/src/components/feature/home/HomeAside.tsx similarity index 100% rename from src/react-app/components/feature/home/HomeAside.tsx rename to apps/web/src/components/feature/home/HomeAside.tsx diff --git a/src/react-app/components/feature/home/HomeUrgentRow.tsx b/apps/web/src/components/feature/home/HomeUrgentRow.tsx similarity index 95% rename from src/react-app/components/feature/home/HomeUrgentRow.tsx rename to apps/web/src/components/feature/home/HomeUrgentRow.tsx index 3edb2ba2..9ad1a4db 100644 --- a/src/react-app/components/feature/home/HomeUrgentRow.tsx +++ b/apps/web/src/components/feature/home/HomeUrgentRow.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import type { ReactNode } from 'react'; interface HomeUrgentRowProps { diff --git a/src/react-app/components/feature/hunts/HuntLobbyDrawer.tsx b/apps/web/src/components/feature/hunts/HuntLobbyDrawer.tsx similarity index 97% rename from src/react-app/components/feature/hunts/HuntLobbyDrawer.tsx rename to apps/web/src/components/feature/hunts/HuntLobbyDrawer.tsx index bed31f81..03aff82c 100644 --- a/src/react-app/components/feature/hunts/HuntLobbyDrawer.tsx +++ b/apps/web/src/components/feature/hunts/HuntLobbyDrawer.tsx @@ -2,10 +2,10 @@ import { GameIcon } from '@app/components/ui/GameIcon'; import { InkButton } from '@app/components/ui/InkButton'; import { InkDetailDrawer } from '@app/components/ui/InkDetailDrawer'; import { useCultivatorIdentity } from '@app/lib/resources/player'; -import type { HuntLobby, HuntTeam } from '@shared/contracts/hunts'; -import { HUNT_BOSSES, huntMapHref } from '@shared/hunts/config'; -import { huntRealmAllowed } from '@shared/hunts/rules'; -import { REALM_VALUES, type RealmType } from '@shared/types/constants'; +import type { HuntLobby, HuntTeam } from '@daoyou/shared/contracts/hunts'; +import { HUNT_BOSSES, huntMapHref } from '@daoyou/shared/hunts/config'; +import { huntRealmAllowed } from '@daoyou/shared/hunts/rules'; +import { REALM_VALUES, type RealmType } from '@daoyou/shared/types/constants'; import { useState } from 'react'; import { Link, useNavigate } from 'react-router'; import { HuntRewardPreview } from './HuntRewardPreview'; diff --git a/src/react-app/components/feature/hunts/HuntMapMarkers.tsx b/apps/web/src/components/feature/hunts/HuntMapMarkers.tsx similarity index 96% rename from src/react-app/components/feature/hunts/HuntMapMarkers.tsx rename to apps/web/src/components/feature/hunts/HuntMapMarkers.tsx index 693242db..a1da22d8 100644 --- a/src/react-app/components/feature/hunts/HuntMapMarkers.tsx +++ b/apps/web/src/components/feature/hunts/HuntMapMarkers.tsx @@ -1,5 +1,5 @@ import { GameIcon } from '@app/components/ui/GameIcon'; -import { HUNT_BOSSES, type HuntEvent } from '@shared/hunts/config'; +import { HUNT_BOSSES, type HuntEvent } from '@daoyou/shared/hunts/config'; export type HuntMapPoint = { id: string; x: number; y: number }; export function HuntMapMarkers({ events, diff --git a/src/react-app/components/feature/hunts/HuntResult.tsx b/apps/web/src/components/feature/hunts/HuntResult.tsx similarity index 95% rename from src/react-app/components/feature/hunts/HuntResult.tsx rename to apps/web/src/components/feature/hunts/HuntResult.tsx index fb7b9d67..5eb58588 100644 --- a/src/react-app/components/feature/hunts/HuntResult.tsx +++ b/apps/web/src/components/feature/hunts/HuntResult.tsx @@ -1,7 +1,7 @@ import { ItemSlot } from '@app/components/feature/items/ItemSlot'; import { InkModal } from '@app/components/layout/InkModal'; -import type { HuntBattleReward } from '@shared/contracts/hunts'; -import { dungeonRewardItemName } from '@shared/rewards/dungeon'; +import type { HuntBattleReward } from '@daoyou/shared/contracts/hunts'; +import { dungeonRewardItemName } from '@daoyou/shared/rewards/dungeon'; import { useState } from 'react'; import { Link } from 'react-router'; import { useHunts } from './useHunts'; diff --git a/src/react-app/components/feature/hunts/HuntRewardPreview.tsx b/apps/web/src/components/feature/hunts/HuntRewardPreview.tsx similarity index 90% rename from src/react-app/components/feature/hunts/HuntRewardPreview.tsx rename to apps/web/src/components/feature/hunts/HuntRewardPreview.tsx index 9d5b03ae..4c049150 100644 --- a/src/react-app/components/feature/hunts/HuntRewardPreview.tsx +++ b/apps/web/src/components/feature/hunts/HuntRewardPreview.tsx @@ -1,12 +1,12 @@ import { itemPresentation } from '@app/components/feature/items/itemPresentation'; import { ItemSlot } from '@app/components/feature/items/ItemSlot'; import { GameIcon } from '@app/components/ui/GameIcon'; -import { BEAST_SUPERIOR_BOOK_SKILL_IDS } from '@shared/engine/combat-v6/beasts/content'; -import { itemDefinition } from '@shared/inventory'; -import { getGameConceptInfo } from '@shared/lib/gameConceptDisplay'; -import { HUNT_DROP_POOL } from '@shared/rewards/hunt'; -import { HUNT_MATERIAL_QUALITY_CHANCE_BY_REALM } from '@shared/rewards/hunt-material-quality'; -import type { RealmType } from '@shared/types/constants'; +import { BEAST_SUPERIOR_BOOK_SKILL_IDS } from '@daoyou/shared/engine/combat-v6/beasts/content'; +import { itemDefinition } from '@daoyou/shared/inventory'; +import { getGameConceptInfo } from '@daoyou/shared/lib/gameConceptDisplay'; +import { HUNT_DROP_POOL } from '@daoyou/shared/rewards/hunt'; +import { HUNT_MATERIAL_QUALITY_CHANCE_BY_REALM } from '@daoyou/shared/rewards/hunt-material-quality'; +import type { RealmType } from '@daoyou/shared/types/constants'; import { useState } from 'react'; const bookEntries = HUNT_DROP_POOL.groups.find((g) => g.id === 'book')!.entries; diff --git a/src/react-app/components/feature/hunts/useHunts.ts b/apps/web/src/components/feature/hunts/useHunts.ts similarity index 100% rename from src/react-app/components/feature/hunts/useHunts.ts rename to apps/web/src/components/feature/hunts/useHunts.ts diff --git a/src/react-app/components/feature/inscriptions/InscriptionRoom.tsx b/apps/web/src/components/feature/inscriptions/InscriptionRoom.tsx similarity index 97% rename from src/react-app/components/feature/inscriptions/InscriptionRoom.tsx rename to apps/web/src/components/feature/inscriptions/InscriptionRoom.tsx index 1f126793..c81073d0 100644 --- a/src/react-app/components/feature/inscriptions/InscriptionRoom.tsx +++ b/apps/web/src/components/feature/inscriptions/InscriptionRoom.tsx @@ -10,12 +10,12 @@ import { InkButton } from '@app/components/ui/InkButton'; import { InkDetailDrawer } from '@app/components/ui/InkDetailDrawer'; import { InkQuantityInput } from '@app/components/ui/InkQuantityInput'; import { useCraftStorage } from '@app/lib/resources/craftStorage'; -import { MAX_CRAFT_MATERIAL_QUANTITY } from '@shared/config/itemQuantity'; -import type { InscriptionRequest } from '@shared/contracts/inscriptions'; -import type { InventoryView } from '@shared/contracts/inventory'; -import { daoFormationInscriptionOf } from '@shared/engine/combat-v6/equipment/content'; -import { daoFormationMaxLevel } from '@shared/engine/combat-v6/equipment/inscriptions'; -import type { DaoFormationInscriptionStateV1 } from '@shared/engine/combat-v6/equipment/types'; +import { MAX_CRAFT_MATERIAL_QUANTITY } from '@daoyou/shared/config/itemQuantity'; +import type { InscriptionRequest } from '@daoyou/shared/contracts/inscriptions'; +import type { InventoryView } from '@daoyou/shared/contracts/inventory'; +import { daoFormationInscriptionOf } from '@daoyou/shared/engine/combat-v6/equipment/content'; +import { daoFormationMaxLevel } from '@daoyou/shared/engine/combat-v6/equipment/inscriptions'; +import type { DaoFormationInscriptionStateV1 } from '@daoyou/shared/engine/combat-v6/equipment/types'; import { inscriptionMaterialProblem, prepareInscriptionDraw, @@ -24,14 +24,14 @@ import { type InscriptionDrawPreview, type InscriptionMaterialRef, type InscriptionRef, -} from '@shared/inscriptions/rules'; -import { itemDefinition, type ItemGrant } from '@shared/inventory'; +} from '@daoyou/shared/inscriptions/rules'; +import { itemDefinition, type ItemGrant } from '@daoyou/shared/inventory'; import { EQUIPMENT_ATTRIBUTE_NAMES, InventoryEquipmentSchema, -} from '@shared/inventory/equipment'; -import { inscriptionItemId } from '@shared/items/definitions/inscriptions'; -import { cn } from '@shared/lib/cn'; +} from '@daoyou/shared/inventory/equipment'; +import { inscriptionItemId } from '@daoyou/shared/items/definitions/inscriptions'; +import { cn } from '@daoyou/shared/lib/cn'; import { useState } from 'react'; import { CraftInventoryPanel } from '../items/CraftInventoryPanel'; import { InventoryItems } from '../items/InventoryItems'; diff --git a/src/react-app/components/feature/inscriptions/useInscriptionSession.ts b/apps/web/src/components/feature/inscriptions/useInscriptionSession.ts similarity index 98% rename from src/react-app/components/feature/inscriptions/useInscriptionSession.ts rename to apps/web/src/components/feature/inscriptions/useInscriptionSession.ts index e5e1df5d..f88a6b07 100644 --- a/src/react-app/components/feature/inscriptions/useInscriptionSession.ts +++ b/apps/web/src/components/feature/inscriptions/useInscriptionSession.ts @@ -9,7 +9,7 @@ import { type InscriptionRequest, type InscriptionResult, type InscriptionView, -} from '@shared/contracts/inscriptions'; +} from '@daoyou/shared/contracts/inscriptions'; import { useEffect, useRef, useState } from 'react'; import { combatV6Request, mutationBody } from '../combat-v6/request'; diff --git a/src/react-app/components/feature/item-shop/ItemExchangeShelf.tsx b/apps/web/src/components/feature/item-shop/ItemExchangeShelf.tsx similarity index 94% rename from src/react-app/components/feature/item-shop/ItemExchangeShelf.tsx rename to apps/web/src/components/feature/item-shop/ItemExchangeShelf.tsx index 86d6ad01..7c70eb0d 100644 --- a/src/react-app/components/feature/item-shop/ItemExchangeShelf.tsx +++ b/apps/web/src/components/feature/item-shop/ItemExchangeShelf.tsx @@ -4,9 +4,9 @@ import { } from '@app/components/feature/items/ItemSlot'; import { GameLoadingState } from '@app/components/game-shell'; import { InkButton, InkInput, InkNotice, InkSelect } from '@app/components/ui'; -import type { ItemExchangeShopItemView } from '@shared/contracts/itemExchangeShop'; -import { findItemDefinition } from '@shared/items/registry'; -import { getGameConceptInfo } from '@shared/lib/gameConceptDisplay'; +import type { ItemExchangeShopItemView } from '@daoyou/shared/contracts/itemExchangeShop'; +import { findItemDefinition } from '@daoyou/shared/items/registry'; +import { getGameConceptInfo } from '@daoyou/shared/lib/gameConceptDisplay'; import { useState } from 'react'; const kinds = { diff --git a/src/react-app/components/feature/items/CraftInventoryPanel.tsx b/apps/web/src/components/feature/items/CraftInventoryPanel.tsx similarity index 97% rename from src/react-app/components/feature/items/CraftInventoryPanel.tsx rename to apps/web/src/components/feature/items/CraftInventoryPanel.tsx index 3b7128d3..80c899f9 100644 --- a/src/react-app/components/feature/items/CraftInventoryPanel.tsx +++ b/apps/web/src/components/feature/items/CraftInventoryPanel.tsx @@ -1,4 +1,4 @@ -import type { InventoryView } from '@shared/contracts/inventory'; +import type { InventoryView } from '@daoyou/shared/contracts/inventory'; import type { ReactNode } from 'react'; import { InkButton } from '../../ui/InkButton'; import { InventoryFilters } from './InventoryFilters'; diff --git a/src/react-app/components/feature/items/InventoryFilters.tsx b/apps/web/src/components/feature/items/InventoryFilters.tsx similarity index 97% rename from src/react-app/components/feature/items/InventoryFilters.tsx rename to apps/web/src/components/feature/items/InventoryFilters.tsx index d1a77e7c..68819b3d 100644 --- a/src/react-app/components/feature/items/InventoryFilters.tsx +++ b/apps/web/src/components/feature/items/InventoryFilters.tsx @@ -1,16 +1,16 @@ import { InkModal } from '@app/components/layout/InkModal'; import { InkButton } from '@app/components/ui/InkButton'; import { InkDiscreteRange } from '@app/components/ui/InkDiscreteRange'; -import type { InventorySort } from '@shared/inventory/sorting'; +import type { InventorySort } from '@daoyou/shared/inventory/sorting'; import { INVENTORY_MATERIAL_TYPES, MATERIAL_TYPE_NAMES, -} from '@shared/items/definitions/materials'; +} from '@daoyou/shared/items/definitions/materials'; import { ELEMENT_VALUES, QUALITY_VALUES, type ElementType, -} from '@shared/types/constants'; +} from '@daoyou/shared/types/constants'; import { useState } from 'react'; import { defaultInventoryFilter, diff --git a/src/react-app/components/feature/items/InventoryHeader.tsx b/apps/web/src/components/feature/items/InventoryHeader.tsx similarity index 100% rename from src/react-app/components/feature/items/InventoryHeader.tsx rename to apps/web/src/components/feature/items/InventoryHeader.tsx diff --git a/src/react-app/components/feature/items/InventoryItems.tsx b/apps/web/src/components/feature/items/InventoryItems.tsx similarity index 92% rename from src/react-app/components/feature/items/InventoryItems.tsx rename to apps/web/src/components/feature/items/InventoryItems.tsx index c5809cb6..5feace3f 100644 --- a/src/react-app/components/feature/items/InventoryItems.tsx +++ b/apps/web/src/components/feature/items/InventoryItems.tsx @@ -1,5 +1,5 @@ -import type { InventoryView } from '@shared/contracts/inventory'; -import { BAG_CAPACITY } from '@shared/inventory'; +import type { InventoryView } from '@daoyou/shared/contracts/inventory'; +import { BAG_CAPACITY } from '@daoyou/shared/inventory'; import type { ComponentProps } from 'react'; import { InventoryGrid, ItemSlot } from './ItemSlot'; diff --git a/src/react-app/components/feature/items/ItemDetailModal.tsx b/apps/web/src/components/feature/items/ItemDetailModal.tsx similarity index 92% rename from src/react-app/components/feature/items/ItemDetailModal.tsx rename to apps/web/src/components/feature/items/ItemDetailModal.tsx index 550bcb18..46ee97c1 100644 --- a/src/react-app/components/feature/items/ItemDetailModal.tsx +++ b/apps/web/src/components/feature/items/ItemDetailModal.tsx @@ -6,15 +6,15 @@ import { } from '@app/components/feature/products'; import { InkModal } from '@app/components/layout'; import { ItemShowcaseModal } from '@app/components/ui/ItemShowcaseModal'; -import { seedFactsOf } from '@shared/items/definitions/seeds'; -import type { CultivatorCondition } from '@shared/types/condition'; -import type { RealmType } from '@shared/types/constants'; +import { seedFactsOf } from '@daoyou/shared/items/definitions/seeds'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; +import type { RealmType } from '@daoyou/shared/types/constants'; import type { Consumable, CultivationTechnique, Material, Skill, -} from '@shared/types/cultivator'; +} from '@daoyou/shared/types/cultivator'; import type { ItemDetailPayload } from './itemDetailPayload'; import { ItemPreview } from './ItemPreview'; diff --git a/src/react-app/components/feature/items/ItemPreview.tsx b/apps/web/src/components/feature/items/ItemPreview.tsx similarity index 100% rename from src/react-app/components/feature/items/ItemPreview.tsx rename to apps/web/src/components/feature/items/ItemPreview.tsx diff --git a/src/react-app/components/feature/items/ItemPreviewView.tsx b/apps/web/src/components/feature/items/ItemPreviewView.tsx similarity index 99% rename from src/react-app/components/feature/items/ItemPreviewView.tsx rename to apps/web/src/components/feature/items/ItemPreviewView.tsx index 9662b4cd..033699b7 100644 --- a/src/react-app/components/feature/items/ItemPreviewView.tsx +++ b/apps/web/src/components/feature/items/ItemPreviewView.tsx @@ -1,5 +1,5 @@ import { GameIcon } from '@app/components/ui/GameIcon'; -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import type { ReactNode } from 'react'; import type { HeaderEntry, diff --git a/src/react-app/components/feature/items/ItemSlot.tsx b/apps/web/src/components/feature/items/ItemSlot.tsx similarity index 99% rename from src/react-app/components/feature/items/ItemSlot.tsx rename to apps/web/src/components/feature/items/ItemSlot.tsx index 6bf454f3..1b851b94 100644 --- a/src/react-app/components/feature/items/ItemSlot.tsx +++ b/apps/web/src/components/feature/items/ItemSlot.tsx @@ -1,5 +1,5 @@ import { GameIcon } from '@app/components/ui/GameIcon'; -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import { useEffect, useLayoutEffect, diff --git a/src/react-app/components/feature/items/index.ts b/apps/web/src/components/feature/items/index.ts similarity index 100% rename from src/react-app/components/feature/items/index.ts rename to apps/web/src/components/feature/items/index.ts diff --git a/src/react-app/components/feature/items/inventoryFilterModel.ts b/apps/web/src/components/feature/items/inventoryFilterModel.ts similarity index 83% rename from src/react-app/components/feature/items/inventoryFilterModel.ts rename to apps/web/src/components/feature/items/inventoryFilterModel.ts index a3a3f4e6..0f0b830b 100644 --- a/src/react-app/components/feature/items/inventoryFilterModel.ts +++ b/apps/web/src/components/feature/items/inventoryFilterModel.ts @@ -1,15 +1,15 @@ -import type { InventoryView } from '@shared/contracts/inventory'; -import { itemDefinition } from '@shared/inventory'; -import { INVENTORY_KINDS } from '@shared/inventory/sorting'; +import type { InventoryView } from '@daoyou/shared/contracts/inventory'; +import { itemDefinition } from '@daoyou/shared/inventory'; +import { INVENTORY_KINDS } from '@daoyou/shared/inventory/sorting'; import { INVENTORY_MATERIAL_TYPES, MaterialFactsSchema, -} from '@shared/items/definitions/materials'; +} from '@daoyou/shared/items/definitions/materials'; import { QUALITY_VALUES, type ElementType, type Quality, -} from '@shared/types/constants'; +} from '@daoyou/shared/types/constants'; type Item = InventoryView['items'][number]; diff --git a/src/react-app/components/feature/items/itemDetailPayload.ts b/apps/web/src/components/feature/items/itemDetailPayload.ts similarity index 95% rename from src/react-app/components/feature/items/itemDetailPayload.ts rename to apps/web/src/components/feature/items/itemDetailPayload.ts index 263fb0a6..a34f2fa9 100644 --- a/src/react-app/components/feature/items/itemDetailPayload.ts +++ b/apps/web/src/components/feature/items/itemDetailPayload.ts @@ -4,7 +4,7 @@ import type { CultivationTechnique, Material, Skill, -} from '@shared/types/cultivator'; +} from '@daoyou/shared/types/cultivator'; export type ItemDetailPayload = | { diff --git a/src/react-app/components/feature/items/itemPresentation.ts b/apps/web/src/components/feature/items/itemPresentation.ts similarity index 100% rename from src/react-app/components/feature/items/itemPresentation.ts rename to apps/web/src/components/feature/items/itemPresentation.ts diff --git a/src/react-app/components/feature/items/itemPreviewModel.ts b/apps/web/src/components/feature/items/itemPreviewModel.ts similarity index 100% rename from src/react-app/components/feature/items/itemPreviewModel.ts rename to apps/web/src/components/feature/items/itemPreviewModel.ts diff --git a/src/react-app/components/feature/items/presentation/basic.ts b/apps/web/src/components/feature/items/presentation/basic.ts similarity index 87% rename from src/react-app/components/feature/items/presentation/basic.ts rename to apps/web/src/components/feature/items/presentation/basic.ts index 9afd5668..6ea2cd0e 100644 --- a/src/react-app/components/feature/items/presentation/basic.ts +++ b/apps/web/src/components/feature/items/presentation/basic.ts @@ -1,22 +1,22 @@ import { tierColorMap } from '@app/components/ui/inkBadgeTiers'; -import { beastSkillPresentation } from '@shared/combat-v6/beast-skill-presentation'; -import { getLevelRealmStage } from '@shared/config/realmProgression'; +import { beastSkillPresentation } from '@daoyou/shared/combat-v6/beast-skill-presentation'; +import { getLevelRealmStage } from '@daoyou/shared/config/realmProgression'; import { BEAST_SKILL_CONTENT, BEAST_SUPERIOR_BOOK_SKILL_IDS, -} from '@shared/engine/combat-v6/beasts/content'; -import { BEAST_REFINEMENT } from '@shared/engine/combat-v6/beasts/refinement-config'; -import { BEAST_REJUVENATION } from '@shared/items/definitions/beast-rejuvenation'; -import { CHARACTER_MANUALS_V1 } from '@shared/engine/combat-v6/manuals/content'; -import { manualEffectLines } from '@shared/engine/combat-v6/manuals/presentation'; -import { EQUIPMENT_SLOT_NAMES } from '@shared/items/definitions/equipment-blueprints'; -import { MATERIAL_TYPE_NAMES } from '@shared/items/definitions/materials'; +} from '@daoyou/shared/engine/combat-v6/beasts/content'; +import { BEAST_REFINEMENT } from '@daoyou/shared/engine/combat-v6/beasts/refinement-config'; +import { BEAST_REJUVENATION } from '@daoyou/shared/items/definitions/beast-rejuvenation'; +import { CHARACTER_MANUALS_V1 } from '@daoyou/shared/engine/combat-v6/manuals/content'; +import { manualEffectLines } from '@daoyou/shared/engine/combat-v6/manuals/presentation'; +import { EQUIPMENT_SLOT_NAMES } from '@daoyou/shared/items/definitions/equipment-blueprints'; +import { MATERIAL_TYPE_NAMES } from '@daoyou/shared/items/definitions/materials'; import { SeedFactsSchema, SeedPreviewFactsSchema, -} from '@shared/items/definitions/seeds'; -import { materialFactsOf } from '@shared/items/material'; -import { REALM_VALUES } from '@shared/types/constants'; +} from '@daoyou/shared/items/definitions/seeds'; +import { materialFactsOf } from '@daoyou/shared/items/material'; +import { REALM_VALUES } from '@daoyou/shared/types/constants'; import { field, lines, quantity } from './helpers'; import type { ItemAdapter } from './types'; diff --git a/src/react-app/components/feature/items/presentation/consumable.ts b/apps/web/src/components/feature/items/presentation/consumable.ts similarity index 96% rename from src/react-app/components/feature/items/presentation/consumable.ts rename to apps/web/src/components/feature/items/presentation/consumable.ts index ac83d20f..39cd1324 100644 --- a/src/react-app/components/feature/items/presentation/consumable.ts +++ b/apps/web/src/components/feature/items/presentation/consumable.ts @@ -1,5 +1,5 @@ import { tierColorMap } from '@app/components/ui/inkBadgeTiers'; -import { ConsumableFactsSchema } from '@shared/items/definitions/consumables'; +import { ConsumableFactsSchema } from '@daoyou/shared/items/definitions/consumables'; import { toPillDisplayModel, toSpiritFruitDisplayModel, diff --git a/src/react-app/components/feature/items/presentation/equipment.ts b/apps/web/src/components/feature/items/presentation/equipment.ts similarity index 84% rename from src/react-app/components/feature/items/presentation/equipment.ts rename to apps/web/src/components/feature/items/presentation/equipment.ts index a69b1247..6c09c7da 100644 --- a/src/react-app/components/feature/items/presentation/equipment.ts +++ b/apps/web/src/components/feature/items/presentation/equipment.ts @@ -1,19 +1,19 @@ import { tierColorMap } from '@app/components/ui/inkBadgeTiers'; -import { getLevelRealmStage } from '@shared/config/realmProgression'; -import { daoEquipmentRequiredLevel } from '@shared/engine/combat-v6/equipment/compiler'; -import { daoFormationInscriptionOf } from '@shared/engine/combat-v6/equipment/content'; -import { daoFormationMaxLevel } from '@shared/engine/combat-v6/equipment/inscriptions'; +import { getLevelRealmStage } from '@daoyou/shared/config/realmProgression'; +import { daoEquipmentRequiredLevel } from '@daoyou/shared/engine/combat-v6/equipment/compiler'; +import { daoFormationInscriptionOf } from '@daoyou/shared/engine/combat-v6/equipment/content'; +import { daoFormationMaxLevel } from '@daoyou/shared/engine/combat-v6/equipment/inscriptions'; import { DAO_EQUIPMENT_ARTS_V1, DAO_EQUIPMENT_ESSENCES_V1, -} from '@shared/engine/combat-v6/equipment/special-content'; +} from '@daoyou/shared/engine/combat-v6/equipment/special-content'; import { EQUIPMENT_ATTRIBUTE_NAMES, InventoryEquipmentSchema, -} from '@shared/inventory/equipment'; -import { EQUIPMENT_SLOT_NAMES } from '@shared/items/definitions/equipment-blueprints'; -import { DAO_WEAPONS, daoWeaponTypeOf } from '@shared/engine/combat-v6/equipment/weapons'; -import { CHARACTER_ATTRIBUTE_LABELS } from '@shared/lib/characterAttributeLabels'; +} from '@daoyou/shared/inventory/equipment'; +import { EQUIPMENT_SLOT_NAMES } from '@daoyou/shared/items/definitions/equipment-blueprints'; +import { DAO_WEAPONS, daoWeaponTypeOf } from '@daoyou/shared/engine/combat-v6/equipment/weapons'; +import { CHARACTER_ATTRIBUTE_LABELS } from '@daoyou/shared/lib/characterAttributeLabels'; import { field, lines } from './helpers'; import type { ItemAdapter, PreviewLine, PreviewSection } from './types'; const icons = { diff --git a/src/react-app/components/feature/items/presentation/helpers.ts b/apps/web/src/components/feature/items/presentation/helpers.ts similarity index 100% rename from src/react-app/components/feature/items/presentation/helpers.ts rename to apps/web/src/components/feature/items/presentation/helpers.ts diff --git a/src/react-app/components/feature/items/presentation/inscription.ts b/apps/web/src/components/feature/items/presentation/inscription.ts similarity index 70% rename from src/react-app/components/feature/items/presentation/inscription.ts rename to apps/web/src/components/feature/items/presentation/inscription.ts index 130fc164..baf5340d 100644 --- a/src/react-app/components/feature/items/presentation/inscription.ts +++ b/apps/web/src/components/feature/items/presentation/inscription.ts @@ -1,10 +1,10 @@ import { tierColorMap } from '@app/components/ui/inkBadgeTiers'; -import { getLevelRealmStage } from '@shared/config/realmProgression'; -import { daoFormationInscriptionOf } from '@shared/engine/combat-v6/equipment/content'; -import { daoFormationMaxLevel } from '@shared/engine/combat-v6/equipment/inscriptions'; -import { OPEN_EQUIPMENT_LEVELS } from '@shared/engine/combat-v6/equipment/realm'; -import { EQUIPMENT_ATTRIBUTE_NAMES } from '@shared/inventory/equipment'; -import { EQUIPMENT_SLOT_NAMES } from '@shared/items/definitions/equipment-blueprints'; +import { getLevelRealmStage } from '@daoyou/shared/config/realmProgression'; +import { daoFormationInscriptionOf } from '@daoyou/shared/engine/combat-v6/equipment/content'; +import { daoFormationMaxLevel } from '@daoyou/shared/engine/combat-v6/equipment/inscriptions'; +import { OPEN_EQUIPMENT_LEVELS } from '@daoyou/shared/engine/combat-v6/equipment/realm'; +import { EQUIPMENT_ATTRIBUTE_NAMES } from '@daoyou/shared/inventory/equipment'; +import { EQUIPMENT_SLOT_NAMES } from '@daoyou/shared/items/definitions/equipment-blueprints'; import { field, quantity } from './helpers'; import type { ItemAdapter } from './types'; diff --git a/src/react-app/components/feature/items/presentation/registry.ts b/apps/web/src/components/feature/items/presentation/registry.ts similarity index 88% rename from src/react-app/components/feature/items/presentation/registry.ts rename to apps/web/src/components/feature/items/presentation/registry.ts index dacea637..6e40358d 100644 --- a/src/react-app/components/feature/items/presentation/registry.ts +++ b/apps/web/src/components/feature/items/presentation/registry.ts @@ -1,5 +1,5 @@ -import { itemDefinition } from '@shared/inventory'; -import type { ItemDefinition } from '@shared/items/types'; +import { itemDefinition } from '@daoyou/shared/inventory'; +import type { ItemDefinition } from '@daoyou/shared/items/types'; import { beastBookAdapter, blueprintAdapter, diff --git a/src/react-app/components/feature/items/presentation/types.ts b/apps/web/src/components/feature/items/presentation/types.ts similarity index 86% rename from src/react-app/components/feature/items/presentation/types.ts rename to apps/web/src/components/feature/items/presentation/types.ts index d12a7b3f..8ad4c9ee 100644 --- a/src/react-app/components/feature/items/presentation/types.ts +++ b/apps/web/src/components/feature/items/presentation/types.ts @@ -1,7 +1,7 @@ -import type { InventoryView } from '@shared/contracts/inventory'; -import type { ItemDefinition } from '@shared/items/types'; -import type { CultivatorCondition } from '@shared/types/condition'; -import type { RealmType } from '@shared/types/constants'; +import type { InventoryView } from '@daoyou/shared/contracts/inventory'; +import type { ItemDefinition } from '@daoyou/shared/items/types'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; +import type { RealmType } from '@daoyou/shared/types/constants'; export type DisplayItem = Pick< InventoryView['items'][number], diff --git a/src/react-app/components/feature/journal/JournalEventContent.tsx b/apps/web/src/components/feature/journal/JournalEventContent.tsx similarity index 98% rename from src/react-app/components/feature/journal/JournalEventContent.tsx rename to apps/web/src/components/feature/journal/JournalEventContent.tsx index 620d4ce9..5d27b59c 100644 --- a/src/react-app/components/feature/journal/JournalEventContent.tsx +++ b/apps/web/src/components/feature/journal/JournalEventContent.tsx @@ -1,7 +1,7 @@ import { JOURNAL_ACTIVITIES, type PlayerJournalEvent, -} from '@shared/contracts/playerJournal'; +} from '@daoyou/shared/contracts/playerJournal'; import { Fragment } from 'react'; const resourceStyles = { diff --git a/src/react-app/components/feature/manuals/ManualDetail.tsx b/apps/web/src/components/feature/manuals/ManualDetail.tsx similarity index 94% rename from src/react-app/components/feature/manuals/ManualDetail.tsx rename to apps/web/src/components/feature/manuals/ManualDetail.tsx index d51d6dc9..88b70939 100644 --- a/src/react-app/components/feature/manuals/ManualDetail.tsx +++ b/apps/web/src/components/feature/manuals/ManualDetail.tsx @@ -3,19 +3,19 @@ import { useInventoryBag } from '@app/lib/resources/bag'; import type { ManualAction, ManualView, -} from '@shared/contracts/combatV6Manuals'; -import type { InventoryView } from '@shared/contracts/inventory'; -import { manualAttributeValue } from '@shared/engine/combat-v6/manuals/attributes'; -import { manualSlot } from '@shared/engine/combat-v6/manuals/compiler'; +} from '@daoyou/shared/contracts/combatV6Manuals'; +import type { InventoryView } from '@daoyou/shared/contracts/inventory'; +import { manualAttributeValue } from '@daoyou/shared/engine/combat-v6/manuals/attributes'; +import { manualSlot } from '@daoyou/shared/engine/combat-v6/manuals/compiler'; import { CHARACTER_MANUALS_V1, manualRule, -} from '@shared/engine/combat-v6/manuals/content'; -import { manualEffectLines } from '@shared/engine/combat-v6/manuals/presentation'; -import type { CharacterManualDefV1 } from '@shared/engine/combat-v6/manuals/types'; -import { itemDefinition } from '@shared/inventory'; -import { CHARACTER_ATTRIBUTE_LABELS } from '@shared/lib/characterAttributeLabels'; -import { manualJadeCost, previewManualAction } from '@shared/manuals/action'; +} from '@daoyou/shared/engine/combat-v6/manuals/content'; +import { manualEffectLines } from '@daoyou/shared/engine/combat-v6/manuals/presentation'; +import type { CharacterManualDefV1 } from '@daoyou/shared/engine/combat-v6/manuals/types'; +import { itemDefinition } from '@daoyou/shared/inventory'; +import { CHARACTER_ATTRIBUTE_LABELS } from '@daoyou/shared/lib/characterAttributeLabels'; +import { manualJadeCost, previewManualAction } from '@daoyou/shared/manuals/action'; import { ManualProgress } from './ManualProgress'; import { manualMechanismSummary } from './manualPresentation'; diff --git a/src/react-app/components/feature/manuals/ManualJadePicker.tsx b/apps/web/src/components/feature/manuals/ManualJadePicker.tsx similarity index 90% rename from src/react-app/components/feature/manuals/ManualJadePicker.tsx rename to apps/web/src/components/feature/manuals/ManualJadePicker.tsx index b631c35e..f3350431 100644 --- a/src/react-app/components/feature/manuals/ManualJadePicker.tsx +++ b/apps/web/src/components/feature/manuals/ManualJadePicker.tsx @@ -4,13 +4,13 @@ import { useCraftStorage } from '@app/lib/resources/craftStorage'; import type { ManualAction, ManualView, -} from '@shared/contracts/combatV6Manuals'; -import type { InventoryView } from '@shared/contracts/inventory'; -import { manualSlot } from '@shared/engine/combat-v6/manuals/compiler'; -import { CHARACTER_MANUALS_V1 } from '@shared/engine/combat-v6/manuals/content'; -import type { CharacterManualDefV1 } from '@shared/engine/combat-v6/manuals/types'; -import { itemDefinition } from '@shared/inventory'; -import { manualJadeCost, previewManualAction } from '@shared/manuals/action'; +} from '@daoyou/shared/contracts/combatV6Manuals'; +import type { InventoryView } from '@daoyou/shared/contracts/inventory'; +import { manualSlot } from '@daoyou/shared/engine/combat-v6/manuals/compiler'; +import { CHARACTER_MANUALS_V1 } from '@daoyou/shared/engine/combat-v6/manuals/content'; +import type { CharacterManualDefV1 } from '@daoyou/shared/engine/combat-v6/manuals/types'; +import { itemDefinition } from '@daoyou/shared/inventory'; +import { manualJadeCost, previewManualAction } from '@daoyou/shared/manuals/action'; import { useState } from 'react'; import { CraftInventoryPanel } from '../items/CraftInventoryPanel'; import { diff --git a/src/react-app/components/feature/manuals/ManualProgress.tsx b/apps/web/src/components/feature/manuals/ManualProgress.tsx similarity index 89% rename from src/react-app/components/feature/manuals/ManualProgress.tsx rename to apps/web/src/components/feature/manuals/ManualProgress.tsx index 54f404e5..86a84720 100644 --- a/src/react-app/components/feature/manuals/ManualProgress.tsx +++ b/apps/web/src/components/feature/manuals/ManualProgress.tsx @@ -1,5 +1,5 @@ -import { manualRule } from '@shared/engine/combat-v6/manuals/content'; -import type { CharacterManualDefV1 } from '@shared/engine/combat-v6/manuals/types'; +import { manualRule } from '@daoyou/shared/engine/combat-v6/manuals/content'; +import type { CharacterManualDefV1 } from '@daoyou/shared/engine/combat-v6/manuals/types'; import { Fragment } from 'react'; export function ManualProgress({ diff --git a/src/react-app/components/feature/manuals/ManualRealmSlot.tsx b/apps/web/src/components/feature/manuals/ManualRealmSlot.tsx similarity index 97% rename from src/react-app/components/feature/manuals/ManualRealmSlot.tsx rename to apps/web/src/components/feature/manuals/ManualRealmSlot.tsx index e51d0d3f..d35bafce 100644 --- a/src/react-app/components/feature/manuals/ManualRealmSlot.tsx +++ b/apps/web/src/components/feature/manuals/ManualRealmSlot.tsx @@ -1,7 +1,7 @@ import type { CharacterManualDefV1, CultivatorManualStateV1, -} from '@shared/engine/combat-v6/manuals/types'; +} from '@daoyou/shared/engine/combat-v6/manuals/types'; import { ManualProgress } from './ManualProgress'; export function ManualRealmSlot({ diff --git a/src/react-app/components/feature/manuals/ManualRoom.tsx b/apps/web/src/components/feature/manuals/ManualRoom.tsx similarity index 95% rename from src/react-app/components/feature/manuals/ManualRoom.tsx rename to apps/web/src/components/feature/manuals/ManualRoom.tsx index 2cc6b189..237f666c 100644 --- a/src/react-app/components/feature/manuals/ManualRoom.tsx +++ b/apps/web/src/components/feature/manuals/ManualRoom.tsx @@ -7,18 +7,18 @@ import { useCultivatorIdentity } from '@app/lib/resources/player'; import type { ManualAction, ManualView, -} from '@shared/contracts/combatV6Manuals'; -import type { InventoryView } from '@shared/contracts/inventory'; -import { manualAttributeValue } from '@shared/engine/combat-v6/manuals/attributes'; +} from '@daoyou/shared/contracts/combatV6Manuals'; +import type { InventoryView } from '@daoyou/shared/contracts/inventory'; +import { manualAttributeValue } from '@daoyou/shared/engine/combat-v6/manuals/attributes'; import { getManualSlotCount, MAX_MANUALS_PER_SLOT, -} from '@shared/engine/combat-v6/manuals/compiler'; -import { CHARACTER_MANUALS_V1 } from '@shared/engine/combat-v6/manuals/content'; -import { MANUAL_REALMS } from '@shared/engine/combat-v6/manuals/pack'; -import type { CharacterManualDefV1 } from '@shared/engine/combat-v6/manuals/types'; -import { itemDefinition } from '@shared/inventory'; -import { CHARACTER_ATTRIBUTE_LABELS } from '@shared/lib/characterAttributeLabels'; +} from '@daoyou/shared/engine/combat-v6/manuals/compiler'; +import { CHARACTER_MANUALS_V1 } from '@daoyou/shared/engine/combat-v6/manuals/content'; +import { MANUAL_REALMS } from '@daoyou/shared/engine/combat-v6/manuals/pack'; +import type { CharacterManualDefV1 } from '@daoyou/shared/engine/combat-v6/manuals/types'; +import { itemDefinition } from '@daoyou/shared/inventory'; +import { CHARACTER_ATTRIBUTE_LABELS } from '@daoyou/shared/lib/characterAttributeLabels'; import { useEffect, useRef, useState, useSyncExternalStore } from 'react'; import { useSearchParams } from 'react-router'; import { combatV6Request, mutationBody } from '../combat-v6/request'; diff --git a/src/react-app/components/feature/manuals/manualPresentation.ts b/apps/web/src/components/feature/manuals/manualPresentation.ts similarity index 86% rename from src/react-app/components/feature/manuals/manualPresentation.ts rename to apps/web/src/components/feature/manuals/manualPresentation.ts index d72fae87..b999ba52 100644 --- a/src/react-app/components/feature/manuals/manualPresentation.ts +++ b/apps/web/src/components/feature/manuals/manualPresentation.ts @@ -1,6 +1,6 @@ -import { manualMechanismValue } from '@shared/engine/combat-v6/manuals/mechanism'; -import { manualMechanismDescription } from '@shared/engine/combat-v6/manuals/presentation'; -import type { CharacterManualDefV1 } from '@shared/engine/combat-v6/manuals/types'; +import { manualMechanismValue } from '@daoyou/shared/engine/combat-v6/manuals/mechanism'; +import { manualMechanismDescription } from '@daoyou/shared/engine/combat-v6/manuals/presentation'; +import type { CharacterManualDefV1 } from '@daoyou/shared/engine/combat-v6/manuals/types'; export function manualMechanismSummary( manual: CharacterManualDefV1, diff --git a/src/react-app/components/feature/map/WildNodePreview.tsx b/apps/web/src/components/feature/map/WildNodePreview.tsx similarity index 81% rename from src/react-app/components/feature/map/WildNodePreview.tsx rename to apps/web/src/components/feature/map/WildNodePreview.tsx index c2c482c1..adb18f4d 100644 --- a/src/react-app/components/feature/map/WildNodePreview.tsx +++ b/apps/web/src/components/feature/map/WildNodePreview.tsx @@ -1,5 +1,5 @@ -import { BEAST_SPECIES } from '@shared/engine/combat-v6/beasts/content'; -import { getWildRegion } from '@shared/engine/combat-v6/wild/content'; +import { BEAST_SPECIES } from '@daoyou/shared/engine/combat-v6/beasts/content'; +import { getWildRegion } from '@daoyou/shared/engine/combat-v6/wild/content'; export function WildNodePreview({ nodeId }: { nodeId: string }) { const region = getWildRegion(nodeId); diff --git a/src/react-app/components/feature/map/mapActions.ts b/apps/web/src/components/feature/map/mapActions.ts similarity index 97% rename from src/react-app/components/feature/map/mapActions.ts rename to apps/web/src/components/feature/map/mapActions.ts index 4f9b2e19..4c180329 100644 --- a/src/react-app/components/feature/map/mapActions.ts +++ b/apps/web/src/components/feature/map/mapActions.ts @@ -1,5 +1,5 @@ import type { InkButton } from '@app/components/ui/InkButton'; -import { getMapNode } from '@shared/lib/game/mapSystem'; +import { getMapNode } from '@daoyou/shared/lib/game/mapSystem'; import type { ComponentProps } from 'react'; export interface MapNodeAction { diff --git a/src/react-app/components/feature/merit/MeritStamp.tsx b/apps/web/src/components/feature/merit/MeritStamp.tsx similarity index 86% rename from src/react-app/components/feature/merit/MeritStamp.tsx rename to apps/web/src/components/feature/merit/MeritStamp.tsx index 58a7db33..11da0e30 100644 --- a/src/react-app/components/feature/merit/MeritStamp.tsx +++ b/apps/web/src/components/feature/merit/MeritStamp.tsx @@ -1,7 +1,7 @@ import { GameImage } from '@app/components/ui/GameImage'; -import { cn } from '@shared/lib/cn'; -import type { SponsorshipTierId } from '@shared/lib/sponsorship'; -import { SPONSORSHIP_TIER_META } from '@shared/lib/sponsorship'; +import { cn } from '@daoyou/shared/lib/cn'; +import type { SponsorshipTierId } from '@daoyou/shared/lib/sponsorship'; +import { SPONSORSHIP_TIER_META } from '@daoyou/shared/lib/sponsorship'; type MeritStampPresentation = { stampSrc: string; diff --git a/src/react-app/components/feature/narrative/NarrativePerformanceLoading.tsx b/apps/web/src/components/feature/narrative/NarrativePerformanceLoading.tsx similarity index 100% rename from src/react-app/components/feature/narrative/NarrativePerformanceLoading.tsx rename to apps/web/src/components/feature/narrative/NarrativePerformanceLoading.tsx diff --git a/src/react-app/components/feature/narrative/NarrativePerformanceStage.tsx b/apps/web/src/components/feature/narrative/NarrativePerformanceStage.tsx similarity index 99% rename from src/react-app/components/feature/narrative/NarrativePerformanceStage.tsx rename to apps/web/src/components/feature/narrative/NarrativePerformanceStage.tsx index edfbdaf3..7fc1bc33 100644 --- a/src/react-app/components/feature/narrative/NarrativePerformanceStage.tsx +++ b/apps/web/src/components/feature/narrative/NarrativePerformanceStage.tsx @@ -5,7 +5,7 @@ import type { NarrativeAct, NarrativePerformanceScript, NarrativeTone, -} from '@shared/types/narrative'; +} from '@daoyou/shared/types/narrative'; import { useEffect, useState } from 'react'; import { advanceNarrativePerformance, diff --git a/src/react-app/components/feature/narrative/narrativePerformanceState.ts b/apps/web/src/components/feature/narrative/narrativePerformanceState.ts similarity index 100% rename from src/react-app/components/feature/narrative/narrativePerformanceState.ts rename to apps/web/src/components/feature/narrative/narrativePerformanceState.ts diff --git a/src/react-app/components/feature/performance/PerformancePlayer.tsx b/apps/web/src/components/feature/performance/PerformancePlayer.tsx similarity index 99% rename from src/react-app/components/feature/performance/PerformancePlayer.tsx rename to apps/web/src/components/feature/performance/PerformancePlayer.tsx index 29c653a6..d1818507 100644 --- a/src/react-app/components/feature/performance/PerformancePlayer.tsx +++ b/apps/web/src/components/feature/performance/PerformancePlayer.tsx @@ -1,18 +1,18 @@ import { InkButton, InkChoiceButton } from '@app/components/ui'; import { GameImage } from '@app/components/ui/GameImage'; import { useTypewriter } from '@app/lib/hooks/useTypewriter'; -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import { currentPerformanceCue, createPerformanceState, reducePerformance, type PerformanceLogEntry, type PerformanceState, -} from '@shared/performance/interpreter'; +} from '@daoyou/shared/performance/interpreter'; import type { PerformanceContext, PerformanceScript, -} from '@shared/performance/schema'; +} from '@daoyou/shared/performance/schema'; import { useEffect, useRef, useState } from 'react'; const choiceMarks = ['一', '二', '三', '四'] as const; diff --git a/src/react-app/components/feature/products/AbilityDetailModal.tsx b/apps/web/src/components/feature/products/AbilityDetailModal.tsx similarity index 100% rename from src/react-app/components/feature/products/AbilityDetailModal.tsx rename to apps/web/src/components/feature/products/AbilityDetailModal.tsx diff --git a/src/react-app/components/feature/products/AbilityListCard.tsx b/apps/web/src/components/feature/products/AbilityListCard.tsx similarity index 95% rename from src/react-app/components/feature/products/AbilityListCard.tsx rename to apps/web/src/components/feature/products/AbilityListCard.tsx index 1c584f7a..dd2c02c1 100644 --- a/src/react-app/components/feature/products/AbilityListCard.tsx +++ b/apps/web/src/components/feature/products/AbilityListCard.tsx @@ -1,4 +1,4 @@ -import { getGameConceptIcon } from '@shared/lib/gameConceptDisplay'; +import { getGameConceptIcon } from '@daoyou/shared/lib/gameConceptDisplay'; import type { ReactNode } from 'react'; import type { ProductDisplayModel } from './abilityDisplay'; import { AffixInlineList } from './AffixInlineList'; diff --git a/src/react-app/components/feature/products/AffixChip.tsx b/apps/web/src/components/feature/products/AffixChip.tsx similarity index 100% rename from src/react-app/components/feature/products/AffixChip.tsx rename to apps/web/src/components/feature/products/AffixChip.tsx diff --git a/src/react-app/components/feature/products/AffixInlineList.tsx b/apps/web/src/components/feature/products/AffixInlineList.tsx similarity index 97% rename from src/react-app/components/feature/products/AffixInlineList.tsx rename to apps/web/src/components/feature/products/AffixInlineList.tsx index e91c0f7e..bd7367ea 100644 --- a/src/react-app/components/feature/products/AffixInlineList.tsx +++ b/apps/web/src/components/feature/products/AffixInlineList.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import type { AffixView } from './abilityDisplay'; import { getAffixToneStyle, diff --git a/src/react-app/components/feature/products/ArtifactListCard.tsx b/apps/web/src/components/feature/products/ArtifactListCard.tsx similarity index 90% rename from src/react-app/components/feature/products/ArtifactListCard.tsx rename to apps/web/src/components/feature/products/ArtifactListCard.tsx index 3bdeebfd..f73d9a6b 100644 --- a/src/react-app/components/feature/products/ArtifactListCard.tsx +++ b/apps/web/src/components/feature/products/ArtifactListCard.tsx @@ -1,5 +1,5 @@ -import type { Artifact } from '@shared/types/cultivator'; -import { getEquipmentSlotInfo } from '@shared/lib/gameConceptDisplay'; +import type { Artifact } from '@daoyou/shared/types/cultivator'; +import { getEquipmentSlotInfo } from '@daoyou/shared/lib/gameConceptDisplay'; import type { ReactNode } from 'react'; import { AffixInlineList } from './AffixInlineList'; import { diff --git a/src/react-app/components/feature/products/AttributeModifierList.tsx b/apps/web/src/components/feature/products/AttributeModifierList.tsx similarity index 100% rename from src/react-app/components/feature/products/AttributeModifierList.tsx rename to apps/web/src/components/feature/products/AttributeModifierList.tsx diff --git a/src/react-app/components/feature/products/LegacyProductList.tsx b/apps/web/src/components/feature/products/LegacyProductList.tsx similarity index 98% rename from src/react-app/components/feature/products/LegacyProductList.tsx rename to apps/web/src/components/feature/products/LegacyProductList.tsx index 68fb556e..45b70b50 100644 --- a/src/react-app/components/feature/products/LegacyProductList.tsx +++ b/apps/web/src/components/feature/products/LegacyProductList.tsx @@ -1,7 +1,7 @@ import { GameSceneFrame } from '@app/components/game-shell'; import { InkButton } from '@app/components/ui'; import { usePlayerSession } from '@app/lib/resources/player'; -import type { LegacyProductType } from '@shared/legacy/products'; +import type { LegacyProductType } from '@daoyou/shared/legacy/products'; import { useEffect, useState } from 'react'; import { AbilityDetailModal } from './AbilityDetailModal'; import { AbilityListCard } from './AbilityListCard'; diff --git a/src/react-app/components/feature/products/ProductListRow.tsx b/apps/web/src/components/feature/products/ProductListRow.tsx similarity index 95% rename from src/react-app/components/feature/products/ProductListRow.tsx rename to apps/web/src/components/feature/products/ProductListRow.tsx index c03adf45..48b3d419 100644 --- a/src/react-app/components/feature/products/ProductListRow.tsx +++ b/apps/web/src/components/feature/products/ProductListRow.tsx @@ -1,7 +1,7 @@ import { InkBadge } from '@app/components/ui/InkBadge'; -import { getElementInfo } from '@shared/lib/gameConceptDisplay'; -import { cn } from '@shared/lib/cn'; -import type { ElementType, Quality } from '@shared/types/constants'; +import { getElementInfo } from '@daoyou/shared/lib/gameConceptDisplay'; +import { cn } from '@daoyou/shared/lib/cn'; +import type { ElementType, Quality } from '@daoyou/shared/types/constants'; import type { ReactNode } from 'react'; import { getScoreMark } from './scoreMeta'; diff --git a/src/react-app/components/feature/products/abilityDisplay.ts b/apps/web/src/components/feature/products/abilityDisplay.ts similarity index 95% rename from src/react-app/components/feature/products/abilityDisplay.ts rename to apps/web/src/components/feature/products/abilityDisplay.ts index 4dafaadb..4aba8a57 100644 --- a/src/react-app/components/feature/products/abilityDisplay.ts +++ b/apps/web/src/components/feature/products/abilityDisplay.ts @@ -3,12 +3,12 @@ import { legacyRecord, legacyText, type LegacyAttributeModifier, -} from '@shared/legacy/products'; +} from '@daoyou/shared/legacy/products'; import { getAttributeInfo, type AttributeKey, -} from '@shared/lib/gameConceptDisplay'; -import type { ElementType, Quality } from '@shared/types/constants'; +} from '@daoyou/shared/lib/gameConceptDisplay'; +import type { ElementType, Quality } from '@daoyou/shared/types/constants'; export type AffixRarityTone = 'muted' | 'info' | 'rare' | 'legendary'; export interface AffixView { diff --git a/src/react-app/components/feature/products/affixPresentation.ts b/apps/web/src/components/feature/products/affixPresentation.ts similarity index 100% rename from src/react-app/components/feature/products/affixPresentation.ts rename to apps/web/src/components/feature/products/affixPresentation.ts diff --git a/src/react-app/components/feature/products/index.ts b/apps/web/src/components/feature/products/index.ts similarity index 100% rename from src/react-app/components/feature/products/index.ts rename to apps/web/src/components/feature/products/index.ts diff --git a/src/react-app/components/feature/products/productShowcase.tsx b/apps/web/src/components/feature/products/productShowcase.tsx similarity index 97% rename from src/react-app/components/feature/products/productShowcase.tsx rename to apps/web/src/components/feature/products/productShowcase.tsx index f2e69b5e..22686365 100644 --- a/src/react-app/components/feature/products/productShowcase.tsx +++ b/apps/web/src/components/feature/products/productShowcase.tsx @@ -3,8 +3,8 @@ import type { ItemShowcaseModalProps } from '@app/components/ui/ItemShowcaseModa import { getEquipmentSlotInfo, getGameConceptInfo, -} from '@shared/lib/gameConceptDisplay'; -import type { EquipmentSlot } from '@shared/types/constants'; +} from '@daoyou/shared/lib/gameConceptDisplay'; +import type { EquipmentSlot } from '@daoyou/shared/types/constants'; import { AffixChip } from './AffixChip'; import { type ProductDisplayModel } from './abilityDisplay'; import { getScoreMark } from './scoreMeta'; diff --git a/src/react-app/components/feature/products/scoreMeta.tsx b/apps/web/src/components/feature/products/scoreMeta.tsx similarity index 100% rename from src/react-app/components/feature/products/scoreMeta.tsx rename to apps/web/src/components/feature/products/scoreMeta.tsx diff --git a/src/react-app/components/feature/ranking/RankingListItem.tsx b/apps/web/src/components/feature/ranking/RankingListItem.tsx similarity index 96% rename from src/react-app/components/feature/ranking/RankingListItem.tsx rename to apps/web/src/components/feature/ranking/RankingListItem.tsx index 1188aa39..2d05d1e1 100644 --- a/src/react-app/components/feature/ranking/RankingListItem.tsx +++ b/apps/web/src/components/feature/ranking/RankingListItem.tsx @@ -3,22 +3,22 @@ import { toPillDisplayModel, } from '@app/components/feature/consumables'; import { InkBadge, InkButton, type Tier } from '@app/components/ui'; -import { cn } from '@shared/lib/cn'; -import { isPillSpec } from '@shared/lib/consumables'; -import { CONSUMABLE_TYPE_DISPLAY_MAP } from '@shared/lib/gameConceptDisplay'; +import { cn } from '@daoyou/shared/lib/cn'; +import { isPillSpec } from '@daoyou/shared/lib/consumables'; +import { CONSUMABLE_TYPE_DISPLAY_MAP } from '@daoyou/shared/lib/gameConceptDisplay'; import { formatCompactGameNumber, formatFullGameNumber, -} from '@shared/lib/numberFormat'; -import type { RealmType } from '@shared/types/constants'; -import type { PillSpec } from '@shared/types/consumable'; -import type { Consumable } from '@shared/types/cultivator'; +} from '@daoyou/shared/lib/numberFormat'; +import type { RealmType } from '@daoyou/shared/types/constants'; +import type { PillSpec } from '@daoyou/shared/types/consumable'; +import type { Consumable } from '@daoyou/shared/types/cultivator'; import type { BattleRankingItem, ItemRankingEntry, RankingsDisplayItem, WealthRankingEntry, -} from '@shared/types/rankings'; +} from '@daoyou/shared/types/rankings'; import { memo } from 'react'; interface BattleRankingCardProps { diff --git a/src/react-app/components/feature/ranking/RecentBattles.tsx b/apps/web/src/components/feature/ranking/RecentBattles.tsx similarity index 95% rename from src/react-app/components/feature/ranking/RecentBattles.tsx rename to apps/web/src/components/feature/ranking/RecentBattles.tsx index 38ead97a..11837351 100644 --- a/src/react-app/components/feature/ranking/RecentBattles.tsx +++ b/apps/web/src/components/feature/ranking/RecentBattles.tsx @@ -4,7 +4,7 @@ import { GameLoadingState } from '@app/components/game-shell/GameLoadingState'; import { InkButton } from '@app/components/ui/InkButton'; import { InkNotice } from '@app/components/ui/InkNotice'; import { usePlayerSession } from '@app/lib/resources/player'; -import type { CombatV6HistoryPage } from '@shared/contracts/combatV6Replay'; +import type { CombatV6HistoryPage } from '@daoyou/shared/contracts/combatV6Replay'; import { useEffect, useState } from 'react'; export function RecentBattles() { diff --git a/src/react-app/components/feature/retreat/BreakthroughChanceDetails.tsx b/apps/web/src/components/feature/retreat/BreakthroughChanceDetails.tsx similarity index 97% rename from src/react-app/components/feature/retreat/BreakthroughChanceDetails.tsx rename to apps/web/src/components/feature/retreat/BreakthroughChanceDetails.tsx index 78e105f5..dad71c19 100644 --- a/src/react-app/components/feature/retreat/BreakthroughChanceDetails.tsx +++ b/apps/web/src/components/feature/retreat/BreakthroughChanceDetails.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/utils'; +import { cn } from '@daoyou/shared/lib/utils'; import { format } from 'd3-format'; import type { BreakthroughChancePresentation } from './breakthroughChancePresentation'; diff --git a/src/react-app/components/feature/retreat/BreakthroughConfirmModal.tsx b/apps/web/src/components/feature/retreat/BreakthroughConfirmModal.tsx similarity index 100% rename from src/react-app/components/feature/retreat/BreakthroughConfirmModal.tsx rename to apps/web/src/components/feature/retreat/BreakthroughConfirmModal.tsx diff --git a/src/react-app/components/feature/retreat/RetreatResultModal.tsx b/apps/web/src/components/feature/retreat/RetreatResultModal.tsx similarity index 95% rename from src/react-app/components/feature/retreat/RetreatResultModal.tsx rename to apps/web/src/components/feature/retreat/RetreatResultModal.tsx index 0755b64e..93a41503 100644 --- a/src/react-app/components/feature/retreat/RetreatResultModal.tsx +++ b/apps/web/src/components/feature/retreat/RetreatResultModal.tsx @@ -1,13 +1,13 @@ import { InkModal } from '@app/components/layout'; import { InkButton, InkIdentifyCelebration } from '@app/components/ui'; -import type { RetreatResultData } from '@shared/contracts/retreat'; +import type { RetreatResultData } from '@daoyou/shared/contracts/retreat'; import type { BreakthroughResult, CultivationResult, -} from '@shared/engine/cultivation/CultivationEngine'; -import { getGameConceptLabel } from '@shared/lib/gameConceptDisplay'; -import type { Attributes } from '@shared/types/cultivator'; -import { getAttributeInfo } from '@shared/lib/gameConceptDisplay'; +} from '@daoyou/shared/engine/cultivation/CultivationEngine'; +import { getGameConceptLabel } from '@daoyou/shared/lib/gameConceptDisplay'; +import type { Attributes } from '@daoyou/shared/types/cultivator'; +import { getAttributeInfo } from '@daoyou/shared/lib/gameConceptDisplay'; import { format } from 'd3-format'; import { useMemo } from 'react'; import { BreakthroughChanceDetails } from './BreakthroughChanceDetails'; diff --git a/src/react-app/components/feature/retreat/RetreatView.tsx b/apps/web/src/components/feature/retreat/RetreatView.tsx similarity index 98% rename from src/react-app/components/feature/retreat/RetreatView.tsx rename to apps/web/src/components/feature/retreat/RetreatView.tsx index e9bf3034..82961360 100644 --- a/src/react-app/components/feature/retreat/RetreatView.tsx +++ b/apps/web/src/components/feature/retreat/RetreatView.tsx @@ -6,12 +6,12 @@ import { GameSceneTabs, } from '@app/components/game-shell'; import { InkButton, InkInput, InkNotice } from '@app/components/ui'; -import { COMPREHENSION_INSIGHT_CAP } from '@shared/config/cultivationTuning'; -import { QI_ACTION_COSTS } from '@shared/config/qiSystem'; -import { getGameConceptLabel } from '@shared/lib/gameConceptDisplay'; -import type { TaskInstance } from '@shared/types/task'; +import { COMPREHENSION_INSIGHT_CAP } from '@daoyou/shared/config/cultivationTuning'; +import { QI_ACTION_COSTS } from '@daoyou/shared/config/qiSystem'; +import { getGameConceptLabel } from '@daoyou/shared/lib/gameConceptDisplay'; +import type { TaskInstance } from '@daoyou/shared/types/task'; -import { cn } from '@shared/lib/utils'; +import { cn } from '@daoyou/shared/lib/utils'; import { useState } from 'react'; import { useNavigate } from 'react-router'; import { BreakthroughChanceDetails } from './BreakthroughChanceDetails'; @@ -441,7 +441,7 @@ export type RetreatViewProps = { facilityLevel: number; experienceBonusPercent: number; facilityLabel: string; - scene: import('@shared/engine/sect').SectScenePresentation; + scene: import('@daoyou/shared/engine/sect').SectScenePresentation; onExit?(): void; }; }; diff --git a/src/react-app/components/feature/retreat/breakthroughChancePresentation.ts b/apps/web/src/components/feature/retreat/breakthroughChancePresentation.ts similarity index 96% rename from src/react-app/components/feature/retreat/breakthroughChancePresentation.ts rename to apps/web/src/components/feature/retreat/breakthroughChancePresentation.ts index 260d7987..d7a25a0a 100644 --- a/src/react-app/components/feature/retreat/breakthroughChancePresentation.ts +++ b/apps/web/src/components/feature/retreat/breakthroughChancePresentation.ts @@ -1,4 +1,4 @@ -import type { BreakthroughModifiers } from '@server/utils/breakthroughCalculator'; +import type { BreakthroughModifiers } from '@daoyou/shared/engine/cultivation/breakthroughCalculator'; import { format } from 'd3-format'; export type BreakthroughFactorTone = 'positive' | 'warning' | 'neutral'; diff --git a/src/react-app/components/feature/retreat/retreatEfficiency.ts b/apps/web/src/components/feature/retreat/retreatEfficiency.ts similarity index 90% rename from src/react-app/components/feature/retreat/retreatEfficiency.ts rename to apps/web/src/components/feature/retreat/retreatEfficiency.ts index ed843f41..b89903a9 100644 --- a/src/react-app/components/feature/retreat/retreatEfficiency.ts +++ b/apps/web/src/components/feature/retreat/retreatEfficiency.ts @@ -1,16 +1,16 @@ -import { getRetreatQiCost } from '@shared/config/qiSystem'; -import { BOTTLENECK_THRESHOLD } from '@shared/config/cultivationTuning'; +import { getRetreatQiCost } from '@daoyou/shared/config/qiSystem'; +import { BOTTLENECK_THRESHOLD } from '@daoyou/shared/config/cultivationTuning'; import { getActiveCultivationBoostStatus, getCultivationBoostPercent, -} from '@shared/lib/cultivationBoost'; -import { evaluateFateContext } from '@shared/lib/fates'; +} from '@daoyou/shared/lib/cultivationBoost'; +import { evaluateFateContext } from '@daoyou/shared/lib/fates'; import { getBreakthroughFocusBonus, getProtectMeridiansReductionPercent, -} from '@shared/lib/pillEffectScaling'; -import { isConditionStatusActive } from '@shared/lib/condition'; -import type { Cultivator } from '@shared/types/cultivator'; +} from '@daoyou/shared/lib/pillEffectScaling'; +import { isConditionStatusActive } from '@daoyou/shared/lib/condition'; +import type { Cultivator } from '@daoyou/shared/types/cultivator'; type RetreatEfficiencyCultivator = Pick< Cultivator, diff --git a/src/react-app/components/feature/retreat/retreatStream.ts b/apps/web/src/components/feature/retreat/retreatStream.ts similarity index 97% rename from src/react-app/components/feature/retreat/retreatStream.ts rename to apps/web/src/components/feature/retreat/retreatStream.ts index d7dc3af0..f97b0ca2 100644 --- a/src/react-app/components/feature/retreat/retreatStream.ts +++ b/apps/web/src/components/feature/retreat/retreatStream.ts @@ -1,8 +1,8 @@ import type { RetreatResultData, RetreatStreamEvent, -} from '@shared/contracts/retreat'; -import type { PlayerResourceMutationMeta } from '@shared/contracts/player'; +} from '@daoyou/shared/contracts/retreat'; +import type { PlayerResourceMutationMeta } from '@daoyou/shared/contracts/player'; export interface RetreatCultivatorSnapshot { name: string; diff --git a/src/react-app/components/feature/retreat/useRetreatViewModel.tsx b/apps/web/src/components/feature/retreat/useRetreatViewModel.tsx similarity index 97% rename from src/react-app/components/feature/retreat/useRetreatViewModel.tsx rename to apps/web/src/components/feature/retreat/useRetreatViewModel.tsx index c37b6d0e..c1170a56 100644 --- a/src/react-app/components/feature/retreat/useRetreatViewModel.tsx +++ b/apps/web/src/components/feature/retreat/useRetreatViewModel.tsx @@ -10,20 +10,20 @@ import { useCultivatorProgress, usePlayerSession, } from '@app/lib/resources/player'; -import type { Cultivator } from '@shared/types/cultivator'; +import type { Cultivator } from '@daoyou/shared/types/cultivator'; import { useTaskList } from '@app/lib/hooks/useTaskList'; import { useResourceMutation } from '@app/lib/resources/mutations'; import { findCurrentMajorBreakthroughTask } from '@app/lib/tasks/taskClient'; import { calculateBreakthroughChance, getNextStage, -} from '@server/utils/breakthroughCalculator'; +} from '@daoyou/shared/engine/cultivation/breakthroughCalculator'; import type { RetreatAction, RetreatResultData, -} from '@shared/contracts/retreat'; -import type { TaskInstance } from '@shared/types/task'; -import { getRetreatQiCost } from '@shared/config/qiSystem'; +} from '@daoyou/shared/contracts/retreat'; +import type { TaskInstance } from '@daoyou/shared/types/task'; +import { getRetreatQiCost } from '@daoyou/shared/config/qiSystem'; import { useCallback, useMemo, useRef, useState } from 'react'; import { useNavigate } from 'react-router'; import { diff --git a/src/react-app/components/feature/room/NpcConversation.tsx b/apps/web/src/components/feature/room/NpcConversation.tsx similarity index 99% rename from src/react-app/components/feature/room/NpcConversation.tsx rename to apps/web/src/components/feature/room/NpcConversation.tsx index eeb8e57a..7ac66ba7 100644 --- a/src/react-app/components/feature/room/NpcConversation.tsx +++ b/apps/web/src/components/feature/room/NpcConversation.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import { useEffect, useRef, type ReactNode } from 'react'; import type { RoomActorAppearance } from './RoomView'; diff --git a/src/react-app/components/feature/room/RoomView.tsx b/apps/web/src/components/feature/room/RoomView.tsx similarity index 99% rename from src/react-app/components/feature/room/RoomView.tsx rename to apps/web/src/components/feature/room/RoomView.tsx index 3fb4f028..7ccb466b 100644 --- a/src/react-app/components/feature/room/RoomView.tsx +++ b/apps/web/src/components/feature/room/RoomView.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import { useEffect, useRef, type ReactNode } from 'react'; export type RoomActorStatusTone = 'neutral' | 'active' | 'attention' | 'muted'; diff --git a/src/react-app/components/feature/room/index.ts b/apps/web/src/components/feature/room/index.ts similarity index 100% rename from src/react-app/components/feature/room/index.ts rename to apps/web/src/components/feature/room/index.ts diff --git a/src/react-app/components/feature/room/useConversationSession.ts b/apps/web/src/components/feature/room/useConversationSession.ts similarity index 100% rename from src/react-app/components/feature/room/useConversationSession.ts rename to apps/web/src/components/feature/room/useConversationSession.ts diff --git a/src/react-app/components/feature/sect-v6/CostText.tsx b/apps/web/src/components/feature/sect-v6/CostText.tsx similarity index 79% rename from src/react-app/components/feature/sect-v6/CostText.tsx rename to apps/web/src/components/feature/sect-v6/CostText.tsx index 9cd9bda5..be837142 100644 --- a/src/react-app/components/feature/sect-v6/CostText.tsx +++ b/apps/web/src/components/feature/sect-v6/CostText.tsx @@ -1,4 +1,4 @@ -import type { SectV6Cost } from '@shared/contracts/combatV6Sect'; +import type { SectV6Cost } from '@daoyou/shared/contracts/combatV6Sect'; export function CostText({ cost }: { cost: SectV6Cost }) { return (

diff --git a/src/react-app/components/feature/sect-v6/MeridianEditor.tsx b/apps/web/src/components/feature/sect-v6/MeridianEditor.tsx similarity index 97% rename from src/react-app/components/feature/sect-v6/MeridianEditor.tsx rename to apps/web/src/components/feature/sect-v6/MeridianEditor.tsx index 6e806478..2854847c 100644 --- a/src/react-app/components/feature/sect-v6/MeridianEditor.tsx +++ b/apps/web/src/components/feature/sect-v6/MeridianEditor.tsx @@ -1,16 +1,16 @@ -import { canSelectMeridianNode, connectedMeridianSelection, meridianNodesConnect, toggleMeridianNode } from '@shared/engine/combat-v6/content/meridian-selection'; +import { canSelectMeridianNode, connectedMeridianSelection, meridianNodesConnect, toggleMeridianNode } from '@daoyou/shared/engine/combat-v6/content/meridian-selection'; import { InkButton } from '@app/components/ui/InkButton'; import { InkDetailDrawer } from '@app/components/ui/InkDetailDrawer'; -import { getLevelRealmStage } from '@shared/config/realmProgression'; +import { getLevelRealmStage } from '@daoyou/shared/config/realmProgression'; import { COMBAT_V6_SECT_DEFINITIONS, type MeridianNodeDefV6, -} from '@shared/engine/combat-v6/content'; +} from '@daoyou/shared/engine/combat-v6/content'; import { MERIDIAN_LEVELS, meridianUnlockCost, -} from '@shared/engine/combat-v6/sect-progression'; -import { SECT_PANEL_LABELS } from '@shared/engine/combat-v6/sect-progression/presentation'; +} from '@daoyou/shared/engine/combat-v6/sect-progression'; +import { SECT_PANEL_LABELS } from '@daoyou/shared/engine/combat-v6/sect-progression/presentation'; import { useState, useSyncExternalStore } from 'react'; import { useBeforeUnload, useBlocker } from 'react-router'; import { diff --git a/src/react-app/components/feature/sect-v6/MethodsWorkbench.tsx b/apps/web/src/components/feature/sect-v6/MethodsWorkbench.tsx similarity index 97% rename from src/react-app/components/feature/sect-v6/MethodsWorkbench.tsx rename to apps/web/src/components/feature/sect-v6/MethodsWorkbench.tsx index 30bfc29b..51ea2dc8 100644 --- a/src/react-app/components/feature/sect-v6/MethodsWorkbench.tsx +++ b/apps/web/src/components/feature/sect-v6/MethodsWorkbench.tsx @@ -1,13 +1,13 @@ import { InkButton } from '@app/components/ui/InkButton'; -import { COMBAT_V6_SECT_DEFINITIONS } from '@shared/engine/combat-v6/content'; +import { COMBAT_V6_SECT_DEFINITIONS } from '@daoyou/shared/engine/combat-v6/content'; import { methodLevelCap, methodTrainingCost, -} from '@shared/engine/combat-v6/sect-progression'; +} from '@daoyou/shared/engine/combat-v6/sect-progression'; import { SECT_PANEL_LABELS, sectSkillCatalog, -} from '@shared/engine/combat-v6/sect-progression/presentation'; +} from '@daoyou/shared/engine/combat-v6/sect-progression/presentation'; import { useMemo, useState } from 'react'; import { actionProblem, diff --git a/src/react-app/components/feature/sect-v6/SectWorkspace.tsx b/apps/web/src/components/feature/sect-v6/SectWorkspace.tsx similarity index 96% rename from src/react-app/components/feature/sect-v6/SectWorkspace.tsx rename to apps/web/src/components/feature/sect-v6/SectWorkspace.tsx index ffbdb359..265e73f1 100644 --- a/src/react-app/components/feature/sect-v6/SectWorkspace.tsx +++ b/apps/web/src/components/feature/sect-v6/SectWorkspace.tsx @@ -1,8 +1,8 @@ import { useInkUI } from '@app/components/providers/InkUIProvider'; import { InkButton } from '@app/components/ui/InkButton'; import { consumeResourceMutation } from '@app/lib/resources/mutations'; -import type { SectV6Action, SectV6View } from '@shared/contracts/combatV6Sect'; -import { sectSkillCatalog } from '@shared/engine/combat-v6/sect-progression/presentation'; +import type { SectV6Action, SectV6View } from '@daoyou/shared/contracts/combatV6Sect'; +import { sectSkillCatalog } from '@daoyou/shared/engine/combat-v6/sect-progression/presentation'; import { useEffect, useRef, useState } from 'react'; import { combatV6Request, mutationBody } from '../combat-v6/request'; import { MeridianEditor } from './MeridianEditor'; diff --git a/src/react-app/components/feature/sect-v6/actions.tsx b/apps/web/src/components/feature/sect-v6/actions.tsx similarity index 83% rename from src/react-app/components/feature/sect-v6/actions.tsx rename to apps/web/src/components/feature/sect-v6/actions.tsx index 4f72471c..352d72d2 100644 --- a/src/react-app/components/feature/sect-v6/actions.tsx +++ b/apps/web/src/components/feature/sect-v6/actions.tsx @@ -1,5 +1,5 @@ -import type { SectV6Action, SectV6View } from '@shared/contracts/combatV6Sect'; -import { sectV6Change } from '@shared/engine/combat-v6/sect-progression'; +import type { SectV6Action, SectV6View } from '@daoyou/shared/contracts/combatV6Sect'; +import { sectV6Change } from '@daoyou/shared/engine/combat-v6/sect-progression'; export type SectWorkspaceProps = { view: SectV6View; pending: boolean; diff --git a/src/react-app/components/feature/sect/SectIdentity.tsx b/apps/web/src/components/feature/sect/SectIdentity.tsx similarity index 96% rename from src/react-app/components/feature/sect/SectIdentity.tsx rename to apps/web/src/components/feature/sect/SectIdentity.tsx index da312208..110e0383 100644 --- a/src/react-app/components/feature/sect/SectIdentity.tsx +++ b/apps/web/src/components/feature/sect/SectIdentity.tsx @@ -3,9 +3,9 @@ import { InkButton, InkNotice } from '@app/components/ui'; import type { SectContextData, SectPromotionEvaluationData, -} from '@shared/contracts/sect'; -import { SECT_RANK_LABELS } from '@shared/engine/sect'; -import { cn } from '@shared/lib/cn'; +} from '@daoyou/shared/contracts/sect'; +import { SECT_RANK_LABELS } from '@daoyou/shared/engine/sect'; +import { cn } from '@daoyou/shared/lib/cn'; import type { ReactNode } from 'react'; import { getSectIdentityLabels } from './sectIdentityDisplay'; import { diff --git a/src/react-app/components/feature/sect/SectMap.tsx b/apps/web/src/components/feature/sect/SectMap.tsx similarity index 99% rename from src/react-app/components/feature/sect/SectMap.tsx rename to apps/web/src/components/feature/sect/SectMap.tsx index 5497ccb4..4a2ceb88 100644 --- a/src/react-app/components/feature/sect/SectMap.tsx +++ b/apps/web/src/components/feature/sect/SectMap.tsx @@ -7,8 +7,8 @@ import type { SectMapHotspot, SectPermissionState, SectSceneKey, -} from '@shared/engine/sect'; -import { cn } from '@shared/lib/cn'; +} from '@daoyou/shared/engine/sect'; +import { cn } from '@daoyou/shared/lib/cn'; import { useCallback, useRef, diff --git a/src/react-app/components/feature/sect/SectTaskActionRenderer.tsx b/apps/web/src/components/feature/sect/SectTaskActionRenderer.tsx similarity index 100% rename from src/react-app/components/feature/sect/SectTaskActionRenderer.tsx rename to apps/web/src/components/feature/sect/SectTaskActionRenderer.tsx diff --git a/src/react-app/components/feature/sect/SectTaskActions.tsx b/apps/web/src/components/feature/sect/SectTaskActions.tsx similarity index 99% rename from src/react-app/components/feature/sect/SectTaskActions.tsx rename to apps/web/src/components/feature/sect/SectTaskActions.tsx index ba71fef0..30c8932c 100644 --- a/src/react-app/components/feature/sect/SectTaskActions.tsx +++ b/apps/web/src/components/feature/sect/SectTaskActions.tsx @@ -1,6 +1,6 @@ import { useInkUI } from '@app/components/providers/InkUIProvider'; import { InkButton } from '@app/components/ui'; -import type { SectTaskViewData } from '@shared/contracts/sect'; +import type { SectTaskViewData } from '@daoyou/shared/contracts/sect'; import { useEffect, useState } from 'react'; import { createSectRoomNpcHref } from './sectRoomNavigation'; import { diff --git a/src/react-app/components/feature/sect/SectTaskActivityEntry.tsx b/apps/web/src/components/feature/sect/SectTaskActivityEntry.tsx similarity index 100% rename from src/react-app/components/feature/sect/SectTaskActivityEntry.tsx rename to apps/web/src/components/feature/sect/SectTaskActivityEntry.tsx diff --git a/src/react-app/components/feature/sect/SectTaskInteractionProvider.tsx b/apps/web/src/components/feature/sect/SectTaskInteractionProvider.tsx similarity index 98% rename from src/react-app/components/feature/sect/SectTaskInteractionProvider.tsx rename to apps/web/src/components/feature/sect/SectTaskInteractionProvider.tsx index f523a240..6a887e14 100644 --- a/src/react-app/components/feature/sect/SectTaskInteractionProvider.tsx +++ b/apps/web/src/components/feature/sect/SectTaskInteractionProvider.tsx @@ -4,7 +4,7 @@ import { useResourceMutation } from '@app/lib/resources/mutations'; import type { SectTaskActionData, SectTaskViewData, -} from '@shared/contracts/sect'; +} from '@daoyou/shared/contracts/sect'; import { createContext, useCallback, diff --git a/src/react-app/components/feature/sect/SectTaskOutcomeRenderers.tsx b/apps/web/src/components/feature/sect/SectTaskOutcomeRenderers.tsx similarity index 98% rename from src/react-app/components/feature/sect/SectTaskOutcomeRenderers.tsx rename to apps/web/src/components/feature/sect/SectTaskOutcomeRenderers.tsx index a5588690..3e5f5a7b 100644 --- a/src/react-app/components/feature/sect/SectTaskOutcomeRenderers.tsx +++ b/apps/web/src/components/feature/sect/SectTaskOutcomeRenderers.tsx @@ -2,7 +2,7 @@ import { InkButton, InkDialog, InkNotice } from '@app/components/ui'; import type { SectOutcomeRendererProps } from '@app/lib/sect/presentation/core/registry'; import type { SectTaskRewardReceipt, -} from '@shared/contracts/sect'; +} from '@daoyou/shared/contracts/sect'; import { createSectRoomNpcHref } from './sectRoomNavigation'; import { useSectTaskInteraction } from './SectTaskInteractionProvider'; diff --git a/src/react-app/components/feature/sect/SectTaskSubmissionDialog.tsx b/apps/web/src/components/feature/sect/SectTaskSubmissionDialog.tsx similarity index 98% rename from src/react-app/components/feature/sect/SectTaskSubmissionDialog.tsx rename to apps/web/src/components/feature/sect/SectTaskSubmissionDialog.tsx index a0e6a8a5..54f8a168 100644 --- a/src/react-app/components/feature/sect/SectTaskSubmissionDialog.tsx +++ b/apps/web/src/components/feature/sect/SectTaskSubmissionDialog.tsx @@ -12,12 +12,12 @@ import { InkDetailDrawer } from '@app/components/ui/InkDetailDrawer'; import { useInventoryBag } from '@app/lib/resources/bag'; import { useCraftStorage } from '@app/lib/resources/craftStorage'; import { fetchSectSubmissionCandidates } from '@app/lib/sect/sectClient'; -import type { InventoryView } from '@shared/contracts/inventory'; +import type { InventoryView } from '@daoyou/shared/contracts/inventory'; import type { SectSubmissionCandidatesData, SectTaskViewData, -} from '@shared/contracts/sect'; -import { describeSectDeliveryRequirement } from '@shared/engine/sect'; +} from '@daoyou/shared/contracts/sect'; +import { describeSectDeliveryRequirement } from '@daoyou/shared/engine/sect'; import { useCallback, useEffect, useRef, useState } from 'react'; import type { SectTaskViewAction } from './SectTaskActions'; import { useSectTaskInteraction } from './SectTaskInteractionProvider'; diff --git a/src/react-app/components/feature/sect/room/SectFacilityStatusConversation.tsx b/apps/web/src/components/feature/sect/room/SectFacilityStatusConversation.tsx similarity index 98% rename from src/react-app/components/feature/sect/room/SectFacilityStatusConversation.tsx rename to apps/web/src/components/feature/sect/room/SectFacilityStatusConversation.tsx index d0f77c38..ba7996bc 100644 --- a/src/react-app/components/feature/sect/room/SectFacilityStatusConversation.tsx +++ b/apps/web/src/components/feature/sect/room/SectFacilityStatusConversation.tsx @@ -12,7 +12,7 @@ import { import { describeSectFacilityStatus, type SectFacilityDialogueEmphasis, -} from '@shared/engine/sect'; +} from '@daoyou/shared/engine/sect'; import { useMemo } from 'react'; import type { SectNpcConversationRendererProps } from './SectNpcConversationRegistry'; diff --git a/src/react-app/components/feature/sect/room/SectFacilityWorkspaceConversation.tsx b/apps/web/src/components/feature/sect/room/SectFacilityWorkspaceConversation.tsx similarity index 97% rename from src/react-app/components/feature/sect/room/SectFacilityWorkspaceConversation.tsx rename to apps/web/src/components/feature/sect/room/SectFacilityWorkspaceConversation.tsx index ed7a7865..9c62d123 100644 --- a/src/react-app/components/feature/sect/room/SectFacilityWorkspaceConversation.tsx +++ b/apps/web/src/components/feature/sect/room/SectFacilityWorkspaceConversation.tsx @@ -10,7 +10,7 @@ import { useSectInfrastructureQuery, } from '@app/components/feature/sect/sectResources'; import { createSectRoomNpcHref } from '@app/components/feature/sect/sectRoomNavigation'; -import { describeSectFacilityStatus } from '@shared/engine/sect'; +import { describeSectFacilityStatus } from '@daoyou/shared/engine/sect'; import { useState } from 'react'; import { useNavigate } from 'react-router'; import type { SectNpcConversationRendererProps } from './SectNpcConversationRegistry'; diff --git a/src/react-app/components/feature/sect/room/SectManagedRoom.tsx b/apps/web/src/components/feature/sect/room/SectManagedRoom.tsx similarity index 98% rename from src/react-app/components/feature/sect/room/SectManagedRoom.tsx rename to apps/web/src/components/feature/sect/room/SectManagedRoom.tsx index bd8af13a..a1dc86a2 100644 --- a/src/react-app/components/feature/sect/room/SectManagedRoom.tsx +++ b/apps/web/src/components/feature/sect/room/SectManagedRoom.tsx @@ -3,7 +3,7 @@ import { RoomView, type RoomActorView, } from '@app/components/feature/room'; -import type { SectRoomDefinition } from '@shared/engine/sect'; +import type { SectRoomDefinition } from '@daoyou/shared/engine/sect'; import { createElement, useMemo, useState } from 'react'; import type { SectNpcConversationRegistry } from './SectNpcConversationRegistry'; diff --git a/src/react-app/components/feature/sect/room/SectNpcConversationRegistry.ts b/apps/web/src/components/feature/sect/room/SectNpcConversationRegistry.ts similarity index 97% rename from src/react-app/components/feature/sect/room/SectNpcConversationRegistry.ts rename to apps/web/src/components/feature/sect/room/SectNpcConversationRegistry.ts index 7e68159f..a3a3199e 100644 --- a/src/react-app/components/feature/sect/room/SectNpcConversationRegistry.ts +++ b/apps/web/src/components/feature/sect/room/SectNpcConversationRegistry.ts @@ -1,7 +1,7 @@ import type { SectRoomActorDefinition, SectRoomDefinition, -} from '@shared/engine/sect'; +} from '@daoyou/shared/engine/sect'; import type { ComponentType } from 'react'; export interface SectNpcConversationRendererProps { diff --git a/src/react-app/components/feature/sect/room/SectRoutedRoom.tsx b/apps/web/src/components/feature/sect/room/SectRoutedRoom.tsx similarity index 100% rename from src/react-app/components/feature/sect/room/SectRoutedRoom.tsx rename to apps/web/src/components/feature/sect/room/SectRoutedRoom.tsx diff --git a/src/react-app/components/feature/sect/room/SectTaskLocationConversation.tsx b/apps/web/src/components/feature/sect/room/SectTaskLocationConversation.tsx similarity index 100% rename from src/react-app/components/feature/sect/room/SectTaskLocationConversation.tsx rename to apps/web/src/components/feature/sect/room/SectTaskLocationConversation.tsx diff --git a/src/react-app/components/feature/sect/room/index.ts b/apps/web/src/components/feature/sect/room/index.ts similarity index 100% rename from src/react-app/components/feature/sect/room/index.ts rename to apps/web/src/components/feature/sect/room/index.ts diff --git a/src/react-app/components/feature/sect/room/useSectRoomRouteSelection.ts b/apps/web/src/components/feature/sect/room/useSectRoomRouteSelection.ts similarity index 93% rename from src/react-app/components/feature/sect/room/useSectRoomRouteSelection.ts rename to apps/web/src/components/feature/sect/room/useSectRoomRouteSelection.ts index 0ae7512e..edf281fb 100644 --- a/src/react-app/components/feature/sect/room/useSectRoomRouteSelection.ts +++ b/apps/web/src/components/feature/sect/room/useSectRoomRouteSelection.ts @@ -1,5 +1,5 @@ import { SECT_ROOM_NPC_QUERY_KEY } from '@app/components/feature/sect/sectRoomNavigation'; -import type { SectRoomDefinition } from '@shared/engine/sect'; +import type { SectRoomDefinition } from '@daoyou/shared/engine/sect'; import { useCallback, useMemo } from 'react'; import { useSearchParams } from 'react-router'; diff --git a/src/react-app/components/feature/sect/sectIdentityDisplay.ts b/apps/web/src/components/feature/sect/sectIdentityDisplay.ts similarity index 82% rename from src/react-app/components/feature/sect/sectIdentityDisplay.ts rename to apps/web/src/components/feature/sect/sectIdentityDisplay.ts index 68d2b2ef..3c44fc2b 100644 --- a/src/react-app/components/feature/sect/sectIdentityDisplay.ts +++ b/apps/web/src/components/feature/sect/sectIdentityDisplay.ts @@ -1,8 +1,8 @@ -import type { SectContextData } from '@shared/contracts/sect'; +import type { SectContextData } from '@daoyou/shared/contracts/sect'; import { SECT_RANK_LABELS, type SectOffice, -} from '@shared/engine/sect'; +} from '@daoyou/shared/engine/sect'; import { getSectDefinition } from './sectResources'; const SECT_OFFICE_LABELS: Record = { diff --git a/src/react-app/components/feature/sect/sectMapHitTest.ts b/apps/web/src/components/feature/sect/sectMapHitTest.ts similarity index 91% rename from src/react-app/components/feature/sect/sectMapHitTest.ts rename to apps/web/src/components/feature/sect/sectMapHitTest.ts index 97596da6..dfbb7b63 100644 --- a/src/react-app/components/feature/sect/sectMapHitTest.ts +++ b/apps/web/src/components/feature/sect/sectMapHitTest.ts @@ -1,4 +1,4 @@ -import type { SectMapHotspot } from '@shared/engine/sect'; +import type { SectMapHotspot } from '@daoyou/shared/engine/sect'; export function resolveClosestSectMapHotspot( hotspots: readonly SectMapHotspot[], diff --git a/src/react-app/components/feature/sect/sectMapState.ts b/apps/web/src/components/feature/sect/sectMapState.ts similarity index 96% rename from src/react-app/components/feature/sect/sectMapState.ts rename to apps/web/src/components/feature/sect/sectMapState.ts index 0723a7c3..24f0357a 100644 --- a/src/react-app/components/feature/sect/sectMapState.ts +++ b/apps/web/src/components/feature/sect/sectMapState.ts @@ -2,7 +2,7 @@ import type { SectFacilityState, SectMapHotspot, SectPermissionState, -} from '@shared/engine/sect'; +} from '@daoyou/shared/engine/sect'; export type SectMapMode = 'member' | 'visitor'; diff --git a/src/react-app/components/feature/sect/sectResources.ts b/apps/web/src/components/feature/sect/sectResources.ts similarity index 98% rename from src/react-app/components/feature/sect/sectResources.ts rename to apps/web/src/components/feature/sect/sectResources.ts index 0329ace0..d34208e9 100644 --- a/src/react-app/components/feature/sect/sectResources.ts +++ b/apps/web/src/components/feature/sect/sectResources.ts @@ -18,14 +18,14 @@ import { getSectPresentation } from '@app/lib/sect/sectPresentation'; import { SectPromotionEvaluationDataSchema, SectStipendDataSchema, -} from '@shared/contracts/sect'; +} from '@daoyou/shared/contracts/sect'; import { resolveSectBenefitSnapshot, resolveSectPresentation, type CultivatorSectState, type ResolvedSectPresentation, -} from '@shared/engine/sect'; -import { productionSectRuntime } from '@shared/engine/sect/content'; +} from '@daoyou/shared/engine/sect'; +import { productionSectRuntime } from '@daoyou/shared/engine/sect/content'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import type { z } from 'zod'; diff --git a/src/react-app/components/feature/sect/sectRoomNavigation.ts b/apps/web/src/components/feature/sect/sectRoomNavigation.ts similarity index 100% rename from src/react-app/components/feature/sect/sectRoomNavigation.ts rename to apps/web/src/components/feature/sect/sectRoomNavigation.ts diff --git a/src/react-app/components/feature/sect/sectTaskActionRegistry.ts b/apps/web/src/components/feature/sect/sectTaskActionRegistry.ts similarity index 100% rename from src/react-app/components/feature/sect/sectTaskActionRegistry.ts rename to apps/web/src/components/feature/sect/sectTaskActionRegistry.ts diff --git a/src/react-app/components/feature/sect/sectTaskActivityLocations.ts b/apps/web/src/components/feature/sect/sectTaskActivityLocations.ts similarity index 97% rename from src/react-app/components/feature/sect/sectTaskActivityLocations.ts rename to apps/web/src/components/feature/sect/sectTaskActivityLocations.ts index 341ed1ca..de233bc6 100644 --- a/src/react-app/components/feature/sect/sectTaskActivityLocations.ts +++ b/apps/web/src/components/feature/sect/sectTaskActivityLocations.ts @@ -1,4 +1,4 @@ -import type { SectTaskViewData } from '@shared/contracts/sect'; +import type { SectTaskViewData } from '@daoyou/shared/contracts/sect'; import { createSectRoomNpcHref } from './sectRoomNavigation'; export type SectTaskActivityLocationKey = diff --git a/src/react-app/components/feature/sect/sectTaskOutcomeRegistry.ts b/apps/web/src/components/feature/sect/sectTaskOutcomeRegistry.ts similarity index 94% rename from src/react-app/components/feature/sect/sectTaskOutcomeRegistry.ts rename to apps/web/src/components/feature/sect/sectTaskOutcomeRegistry.ts index b364ace8..ea081c22 100644 --- a/src/react-app/components/feature/sect/sectTaskOutcomeRegistry.ts +++ b/apps/web/src/components/feature/sect/sectTaskOutcomeRegistry.ts @@ -1,5 +1,5 @@ import { sectTaskRendererRegistry } from '@app/lib/sect/presentation/compositionRoot'; -import type { SectTaskActionOutcome } from '@shared/contracts/sect'; +import type { SectTaskActionOutcome } from '@daoyou/shared/contracts/sect'; import { createElement } from 'react'; import type { ZodType } from 'zod'; diff --git a/src/react-app/components/feature/sect/useSectIdentityDialog.tsx b/apps/web/src/components/feature/sect/useSectIdentityDialog.tsx similarity index 100% rename from src/react-app/components/feature/sect/useSectIdentityDialog.tsx rename to apps/web/src/components/feature/sect/useSectIdentityDialog.tsx diff --git a/src/react-app/components/feature/tasks/BreakthroughTaskCard.tsx b/apps/web/src/components/feature/tasks/BreakthroughTaskCard.tsx similarity index 97% rename from src/react-app/components/feature/tasks/BreakthroughTaskCard.tsx rename to apps/web/src/components/feature/tasks/BreakthroughTaskCard.tsx index 78b01b8e..500403d7 100644 --- a/src/react-app/components/feature/tasks/BreakthroughTaskCard.tsx +++ b/apps/web/src/components/feature/tasks/BreakthroughTaskCard.tsx @@ -1,6 +1,6 @@ import { InkButton } from '@app/components/ui/InkButton'; -import { cn } from '@shared/lib/cn'; -import type { TaskInstance } from '@shared/types/task'; +import { cn } from '@daoyou/shared/lib/cn'; +import type { TaskInstance } from '@daoyou/shared/types/task'; import { TaskObjectiveRow } from './TaskObjectiveRow'; function StatusPill({ diff --git a/src/react-app/components/feature/tasks/TaskObjectiveRow.tsx b/apps/web/src/components/feature/tasks/TaskObjectiveRow.tsx similarity index 88% rename from src/react-app/components/feature/tasks/TaskObjectiveRow.tsx rename to apps/web/src/components/feature/tasks/TaskObjectiveRow.tsx index 780e7cbd..d2d0971e 100644 --- a/src/react-app/components/feature/tasks/TaskObjectiveRow.tsx +++ b/apps/web/src/components/feature/tasks/TaskObjectiveRow.tsx @@ -1,5 +1,5 @@ -import { cn } from '@shared/lib/cn'; -import type { TaskObjectiveProgress } from '@shared/types/task'; +import { cn } from '@daoyou/shared/lib/cn'; +import type { TaskObjectiveProgress } from '@daoyou/shared/types/task'; export function TaskObjectiveRow({ objective, diff --git a/src/react-app/components/feature/world-chat/WorldChatChannel.tsx b/apps/web/src/components/feature/world-chat/WorldChatChannel.tsx similarity index 99% rename from src/react-app/components/feature/world-chat/WorldChatChannel.tsx rename to apps/web/src/components/feature/world-chat/WorldChatChannel.tsx index 762d052e..0e46d3fe 100644 --- a/src/react-app/components/feature/world-chat/WorldChatChannel.tsx +++ b/apps/web/src/components/feature/world-chat/WorldChatChannel.tsx @@ -5,7 +5,7 @@ import { InkButton } from '@app/components/ui/InkButton'; import { InkInput } from '@app/components/ui/InkInput'; import { InkNotice } from '@app/components/ui/InkNotice'; import { InkTabs } from '@app/components/ui/InkTabs'; -import type { WorldChatChannel as WorldChatViewChannel } from '@shared/types/world-chat'; +import type { WorldChatChannel as WorldChatViewChannel } from '@daoyou/shared/types/world-chat'; import { useEffect, useMemo, useRef, useState } from 'react'; import { WorldChatMessageItem } from './WorldChatMessageItem'; import { WorldChatShowcaseDialog } from './WorldChatShowcaseDialog'; diff --git a/src/react-app/components/feature/world-chat/WorldChatFeedProvider.tsx b/apps/web/src/components/feature/world-chat/WorldChatFeedProvider.tsx similarity index 99% rename from src/react-app/components/feature/world-chat/WorldChatFeedProvider.tsx rename to apps/web/src/components/feature/world-chat/WorldChatFeedProvider.tsx index 73086b8c..07a412ee 100644 --- a/src/react-app/components/feature/world-chat/WorldChatFeedProvider.tsx +++ b/apps/web/src/components/feature/world-chat/WorldChatFeedProvider.tsx @@ -4,7 +4,7 @@ import { usePlayerSession } from '@app/lib/resources/player'; import type { WorldChatChannel, WorldChatMessageDTO, -} from '@shared/types/world-chat'; +} from '@daoyou/shared/types/world-chat'; import { useCallback, useEffect, diff --git a/src/react-app/components/feature/world-chat/WorldChatMessageItem.tsx b/apps/web/src/components/feature/world-chat/WorldChatMessageItem.tsx similarity index 96% rename from src/react-app/components/feature/world-chat/WorldChatMessageItem.tsx rename to apps/web/src/components/feature/world-chat/WorldChatMessageItem.tsx index 4df4c40f..e08be9ba 100644 --- a/src/react-app/components/feature/world-chat/WorldChatMessageItem.tsx +++ b/apps/web/src/components/feature/world-chat/WorldChatMessageItem.tsx @@ -1,4 +1,4 @@ -import { huntEventById, huntMapHref } from '@shared/hunts/config'; +import { huntEventById, huntMapHref } from '@daoyou/shared/hunts/config'; import { BeastTradeDetails } from '@app/components/feature/beasts/BeastTradePreview'; import { itemPresentation } from '@app/components/feature/items/itemPresentation'; import { ItemPreview } from '@app/components/feature/items/ItemPreview'; @@ -6,13 +6,13 @@ import { InkModal } from '@app/components/layout'; import type { Tier } from '@app/components/ui/InkBadge'; import { InkBadge } from '@app/components/ui/InkBadge'; import { useCultivatorIdentity } from '@app/lib/resources/player'; -import { BeastTradePreviewSchema } from '@shared/contracts/beastTrade'; -import { isInventoryShowcase } from '@shared/items/showcase'; -import { cn } from '@shared/lib/cn'; +import { BeastTradePreviewSchema } from '@daoyou/shared/contracts/beastTrade'; +import { isInventoryShowcase } from '@daoyou/shared/items/showcase'; +import { cn } from '@daoyou/shared/lib/cn'; import type { WorldChatBattleShowcasePayload, WorldChatMessageDTO, -} from '@shared/types/world-chat'; +} from '@daoyou/shared/types/world-chat'; import { useMemo, useState } from 'react'; import { Link } from 'react-router'; diff --git a/src/react-app/components/feature/world-chat/WorldChatPreviewBar.tsx b/apps/web/src/components/feature/world-chat/WorldChatPreviewBar.tsx similarity index 100% rename from src/react-app/components/feature/world-chat/WorldChatPreviewBar.tsx rename to apps/web/src/components/feature/world-chat/WorldChatPreviewBar.tsx diff --git a/src/react-app/components/feature/world-chat/WorldChatShowcaseDialog.tsx b/apps/web/src/components/feature/world-chat/WorldChatShowcaseDialog.tsx similarity index 97% rename from src/react-app/components/feature/world-chat/WorldChatShowcaseDialog.tsx rename to apps/web/src/components/feature/world-chat/WorldChatShowcaseDialog.tsx index 62b32db7..72e108cb 100644 --- a/src/react-app/components/feature/world-chat/WorldChatShowcaseDialog.tsx +++ b/apps/web/src/components/feature/world-chat/WorldChatShowcaseDialog.tsx @@ -7,9 +7,9 @@ import { InkModal } from '@app/components/layout'; import { InkButton, InkInput, InkNotice } from '@app/components/ui'; import { InkDetailDrawer } from '@app/components/ui/InkDetailDrawer'; import { useInventoryBag } from '@app/lib/resources/bag'; -import type { BeastManagementView } from '@shared/contracts/combatV6Beasts'; -import type { InventoryView } from '@shared/contracts/inventory'; -import type { SummonedBeast } from '@shared/engine/combat-v6/beasts'; +import type { BeastManagementView } from '@daoyou/shared/contracts/combatV6Beasts'; +import type { InventoryView } from '@daoyou/shared/contracts/inventory'; +import type { SummonedBeast } from '@daoyou/shared/engine/combat-v6/beasts'; import { useEffect, useRef, useState } from 'react'; import type { SendWorldChatShowcaseInput } from './worldChatFeedContext'; diff --git a/src/react-app/components/feature/world-chat/useWorldChatFeedModel.ts b/apps/web/src/components/feature/world-chat/useWorldChatFeedModel.ts similarity index 100% rename from src/react-app/components/feature/world-chat/useWorldChatFeedModel.ts rename to apps/web/src/components/feature/world-chat/useWorldChatFeedModel.ts diff --git a/src/react-app/components/feature/world-chat/worldChatFeedContext.ts b/apps/web/src/components/feature/world-chat/worldChatFeedContext.ts similarity index 95% rename from src/react-app/components/feature/world-chat/worldChatFeedContext.ts rename to apps/web/src/components/feature/world-chat/worldChatFeedContext.ts index 81273e8f..b6e4b66d 100644 --- a/src/react-app/components/feature/world-chat/worldChatFeedContext.ts +++ b/apps/web/src/components/feature/world-chat/worldChatFeedContext.ts @@ -1,7 +1,7 @@ import type { WorldChatChannel, WorldChatMessageDTO, -} from '@shared/types/world-chat'; +} from '@daoyou/shared/types/world-chat'; import { createContext } from 'react'; export type SendWorldChatShowcaseInput = diff --git a/src/react-app/components/feature/world-chat/worldChatFeedHelpers.ts b/apps/web/src/components/feature/world-chat/worldChatFeedHelpers.ts similarity index 96% rename from src/react-app/components/feature/world-chat/worldChatFeedHelpers.ts rename to apps/web/src/components/feature/world-chat/worldChatFeedHelpers.ts index 257ab2f0..4b60b3fc 100644 --- a/src/react-app/components/feature/world-chat/worldChatFeedHelpers.ts +++ b/apps/web/src/components/feature/world-chat/worldChatFeedHelpers.ts @@ -1,7 +1,7 @@ import type { WorldChatChannel, WorldChatMessageDTO, -} from '@shared/types/world-chat'; +} from '@daoyou/shared/types/world-chat'; export const PAGE_SIZE = 20; diff --git a/src/react-app/components/feature/world-chat/worldChatSummary.ts b/apps/web/src/components/feature/world-chat/worldChatSummary.ts similarity index 96% rename from src/react-app/components/feature/world-chat/worldChatSummary.ts rename to apps/web/src/components/feature/world-chat/worldChatSummary.ts index 383f62a7..3af11951 100644 --- a/src/react-app/components/feature/world-chat/worldChatSummary.ts +++ b/apps/web/src/components/feature/world-chat/worldChatSummary.ts @@ -1,8 +1,8 @@ -import { isInventoryShowcase } from '@shared/items/showcase'; +import { isInventoryShowcase } from '@daoyou/shared/items/showcase'; import type { WorldChatBattleShowcasePayload, WorldChatMessageDTO, -} from '@shared/types/world-chat'; +} from '@daoyou/shared/types/world-chat'; function isTextPayload( payload: WorldChatMessageDTO['payload'], diff --git a/src/react-app/components/func/LingGen.tsx b/apps/web/src/components/func/LingGen.tsx similarity index 95% rename from src/react-app/components/func/LingGen.tsx rename to apps/web/src/components/func/LingGen.tsx index 37a786fc..9aaac02d 100644 --- a/src/react-app/components/func/LingGen.tsx +++ b/apps/web/src/components/func/LingGen.tsx @@ -3,9 +3,9 @@ import { InkSection } from '@app/components/layout'; import { InkBadge } from '@app/components/ui/InkBadge'; import { InkButton } from '@app/components/ui/InkButton'; import { InkList, InkListItem } from '@app/components/ui/InkList'; -import { ELEMENT_VALUES } from '@shared/types/constants'; -import type { SpiritualRoot } from '@shared/types/cultivator'; -import { getElementInfo } from '@shared/lib/gameConceptDisplay'; +import { ELEMENT_VALUES } from '@daoyou/shared/types/constants'; +import type { SpiritualRoot } from '@daoyou/shared/types/cultivator'; +import { getElementInfo } from '@daoyou/shared/lib/gameConceptDisplay'; import { ReactNode } from 'react'; import { useInkUI } from '../providers/InkUIProvider'; diff --git a/src/react-app/components/func/index.ts b/apps/web/src/components/func/index.ts similarity index 100% rename from src/react-app/components/func/index.ts rename to apps/web/src/components/func/index.ts diff --git a/src/react-app/components/game-shell/CultivatorOverviewPanel.tsx b/apps/web/src/components/game-shell/CultivatorOverviewPanel.tsx similarity index 100% rename from src/react-app/components/game-shell/CultivatorOverviewPanel.tsx rename to apps/web/src/components/game-shell/CultivatorOverviewPanel.tsx diff --git a/src/react-app/components/game-shell/GameBottomDock.tsx b/apps/web/src/components/game-shell/GameBottomDock.tsx similarity index 99% rename from src/react-app/components/game-shell/GameBottomDock.tsx rename to apps/web/src/components/game-shell/GameBottomDock.tsx index 5c268c28..1f19312b 100644 --- a/src/react-app/components/game-shell/GameBottomDock.tsx +++ b/apps/web/src/components/game-shell/GameBottomDock.tsx @@ -1,6 +1,6 @@ import Link from '@app/components/router/AppLink'; import { InkDetailDrawer } from '@app/components/ui/InkDetailDrawer'; -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import { getCoreDockItemBadge, shouldShowGameDockBadge, diff --git a/src/react-app/components/game-shell/GameImmersiveLoading.tsx b/apps/web/src/components/game-shell/GameImmersiveLoading.tsx similarity index 100% rename from src/react-app/components/game-shell/GameImmersiveLoading.tsx rename to apps/web/src/components/game-shell/GameImmersiveLoading.tsx diff --git a/src/react-app/components/game-shell/GameLoadingState.tsx b/apps/web/src/components/game-shell/GameLoadingState.tsx similarity index 97% rename from src/react-app/components/game-shell/GameLoadingState.tsx rename to apps/web/src/components/game-shell/GameLoadingState.tsx index 528c599b..1a448c7a 100644 --- a/src/react-app/components/game-shell/GameLoadingState.tsx +++ b/apps/web/src/components/game-shell/GameLoadingState.tsx @@ -1,5 +1,5 @@ import { InkLoadingBar } from '@app/components/ui/InkLoadingBar'; -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import type { ReactNode } from 'react'; export interface GameLoadingStateProps { diff --git a/src/react-app/components/game-shell/GameSceneFrame.tsx b/apps/web/src/components/game-shell/GameSceneFrame.tsx similarity index 99% rename from src/react-app/components/game-shell/GameSceneFrame.tsx rename to apps/web/src/components/game-shell/GameSceneFrame.tsx index bf1a07d9..91816aa1 100644 --- a/src/react-app/components/game-shell/GameSceneFrame.tsx +++ b/apps/web/src/components/game-shell/GameSceneFrame.tsx @@ -1,6 +1,6 @@ import { InkButton } from '@app/components/ui'; import { resolveGameScene } from '@app/lib/router/routeTitle'; -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import type { ReactNode } from 'react'; import { useStory } from '@app/lib/story/useStory'; import { useMatches } from 'react-router'; diff --git a/src/react-app/components/game-shell/GameSceneSection.tsx b/apps/web/src/components/game-shell/GameSceneSection.tsx similarity index 98% rename from src/react-app/components/game-shell/GameSceneSection.tsx rename to apps/web/src/components/game-shell/GameSceneSection.tsx index c731a6df..7fd4329a 100644 --- a/src/react-app/components/game-shell/GameSceneSection.tsx +++ b/apps/web/src/components/game-shell/GameSceneSection.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import type { ReactNode } from 'react'; import { useInkUI } from '../providers/InkUIProvider'; diff --git a/src/react-app/components/game-shell/GameSceneTabs.tsx b/apps/web/src/components/game-shell/GameSceneTabs.tsx similarity index 96% rename from src/react-app/components/game-shell/GameSceneTabs.tsx rename to apps/web/src/components/game-shell/GameSceneTabs.tsx index 6e8fa165..2e1793b6 100644 --- a/src/react-app/components/game-shell/GameSceneTabs.tsx +++ b/apps/web/src/components/game-shell/GameSceneTabs.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import { InkHorizontalScroll } from '@app/components/ui/InkHorizontalScroll'; import type { ReactNode } from 'react'; diff --git a/src/react-app/components/game-shell/GameTopHud.tsx b/apps/web/src/components/game-shell/GameTopHud.tsx similarity index 99% rename from src/react-app/components/game-shell/GameTopHud.tsx rename to apps/web/src/components/game-shell/GameTopHud.tsx index 56b8d019..ba8bdb5c 100644 --- a/src/react-app/components/game-shell/GameTopHud.tsx +++ b/apps/web/src/components/game-shell/GameTopHud.tsx @@ -17,7 +17,7 @@ import { COMPREHENSION_INSIGHT_CAP, NORMAL_BREAKTHROUGH_THRESHOLD, PERFECT_BREAKTHROUGH_INSIGHT, -} from '@shared/config/cultivationTuning'; +} from '@daoyou/shared/config/cultivationTuning'; import { QI_ACTION_COSTS, QI_DAILY_RESTORE_ITEM_LIMIT, @@ -25,10 +25,10 @@ import { QI_NATURAL_RESTORE_INTERVAL_MS, QI_NATURAL_RESTORE_PER_INTERVAL, QI_OVERFLOW_MAX, -} from '@shared/config/qiSystem'; -import { cn } from '@shared/lib/cn'; -import { getGameConceptInfo } from '@shared/lib/gameConceptDisplay'; -import type { SponsorshipTierId } from '@shared/lib/sponsorship'; +} from '@daoyou/shared/config/qiSystem'; +import { cn } from '@daoyou/shared/lib/cn'; +import { getGameConceptInfo } from '@daoyou/shared/lib/gameConceptDisplay'; +import type { SponsorshipTierId } from '@daoyou/shared/lib/sponsorship'; import type { ReactNode } from 'react'; import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router'; diff --git a/src/react-app/components/game-shell/RealtimeConnectionToasts.tsx b/apps/web/src/components/game-shell/RealtimeConnectionToasts.tsx similarity index 97% rename from src/react-app/components/game-shell/RealtimeConnectionToasts.tsx rename to apps/web/src/components/game-shell/RealtimeConnectionToasts.tsx index fd6be566..fe64f69f 100644 --- a/src/react-app/components/game-shell/RealtimeConnectionToasts.tsx +++ b/apps/web/src/components/game-shell/RealtimeConnectionToasts.tsx @@ -1,6 +1,6 @@ import { useInkUI } from '@app/components/providers/InkUIProvider'; import { realtimeClient } from '@app/lib/realtime/realtimeClient'; -import type { RealtimeChannel } from '@shared/contracts/realtime'; +import type { RealtimeChannel } from '@daoyou/shared/contracts/realtime'; import { useEffect, useRef } from 'react'; import { REALTIME_CHANNEL_META } from './realtimeStatusView'; diff --git a/src/react-app/components/game-shell/gameBottomDockBadge.ts b/apps/web/src/components/game-shell/gameBottomDockBadge.ts similarity index 100% rename from src/react-app/components/game-shell/gameBottomDockBadge.ts rename to apps/web/src/components/game-shell/gameBottomDockBadge.ts diff --git a/src/react-app/components/game-shell/gameNavigation.ts b/apps/web/src/components/game-shell/gameNavigation.ts similarity index 100% rename from src/react-app/components/game-shell/gameNavigation.ts rename to apps/web/src/components/game-shell/gameNavigation.ts diff --git a/src/react-app/components/game-shell/gameSceneFrameHeader.ts b/apps/web/src/components/game-shell/gameSceneFrameHeader.ts similarity index 100% rename from src/react-app/components/game-shell/gameSceneFrameHeader.ts rename to apps/web/src/components/game-shell/gameSceneFrameHeader.ts diff --git a/src/react-app/components/game-shell/index.ts b/apps/web/src/components/game-shell/index.ts similarity index 100% rename from src/react-app/components/game-shell/index.ts rename to apps/web/src/components/game-shell/index.ts diff --git a/src/react-app/components/game-shell/realtimeStatusView.ts b/apps/web/src/components/game-shell/realtimeStatusView.ts similarity index 94% rename from src/react-app/components/game-shell/realtimeStatusView.ts rename to apps/web/src/components/game-shell/realtimeStatusView.ts index 8821fef2..c73ac4e1 100644 --- a/src/react-app/components/game-shell/realtimeStatusView.ts +++ b/apps/web/src/components/game-shell/realtimeStatusView.ts @@ -1,5 +1,5 @@ import type { RealtimeChannelStatus } from '@app/lib/realtime/realtimeClient'; -import type { RealtimeChannel } from '@shared/contracts/realtime'; +import type { RealtimeChannel } from '@daoyou/shared/contracts/realtime'; export const REALTIME_CHANNEL_META: Record = { 'world-chat': { label: '聊天服务器' }, diff --git a/src/react-app/components/game-shell/useCombatActivityNotice.ts b/apps/web/src/components/game-shell/useCombatActivityNotice.ts similarity index 96% rename from src/react-app/components/game-shell/useCombatActivityNotice.ts rename to apps/web/src/components/game-shell/useCombatActivityNotice.ts index 47a2866c..8da60381 100644 --- a/src/react-app/components/game-shell/useCombatActivityNotice.ts +++ b/apps/web/src/components/game-shell/useCombatActivityNotice.ts @@ -1,7 +1,7 @@ import { CombatActivityNoticeSchema, type CombatActivityNotice, -} from '@shared/contracts/combatActivity'; +} from '@daoyou/shared/contracts/combatActivity'; import { useEffect, useState } from 'react'; const POLL_MS = 8_000; diff --git a/src/react-app/components/game-shell/useGameHudModel.ts b/apps/web/src/components/game-shell/useGameHudModel.ts similarity index 94% rename from src/react-app/components/game-shell/useGameHudModel.ts rename to apps/web/src/components/game-shell/useGameHudModel.ts index 53de991a..5e64b7dc 100644 --- a/src/react-app/components/game-shell/useGameHudModel.ts +++ b/apps/web/src/components/game-shell/useGameHudModel.ts @@ -11,32 +11,32 @@ import { import { type CultivatorDisplayInput, type CultivatorDisplaySnapshot, -} from '@shared/lib/cultivatorDisplay'; -import type { Cultivator } from '@shared/types/cultivator'; +} from '@daoyou/shared/lib/cultivatorDisplay'; +import type { Cultivator } from '@daoyou/shared/types/cultivator'; import { BOTTLENECK_THRESHOLD, COMPREHENSION_INSIGHT_CAP, -} from '@shared/config/cultivationTuning'; +} from '@daoyou/shared/config/cultivationTuning'; import { getBodyCultivationSummary, type BodyCultivationSummary, -} from '@shared/lib/bodyCultivation/summary'; +} from '@daoyou/shared/lib/bodyCultivation/summary'; import { getPillToxicityStage, isConditionStatusActive, -} from '@shared/lib/condition'; -import { getConditionStatusTemplate } from '@shared/lib/conditionStatusRegistry'; +} from '@daoyou/shared/lib/condition'; +import { getConditionStatusTemplate } from '@daoyou/shared/lib/conditionStatusRegistry'; import { getGameConceptLabel, getResourceLabel, getResourceText, -} from '@shared/lib/gameConceptDisplay'; +} from '@daoyou/shared/lib/gameConceptDisplay'; import { getMarrowWashSummary, type MarrowWashSummary, -} from '@shared/lib/marrowWash'; -import type { ConditionStatusKey } from '@shared/types/condition'; -import { RealmType } from '@shared/types/constants'; +} from '@daoyou/shared/lib/marrowWash'; +import type { ConditionStatusKey } from '@daoyou/shared/types/condition'; +import { RealmType } from '@daoyou/shared/types/constants'; export interface GameHudMetric { key: 'hp' | 'mp' | 'cultivation' | 'insight'; diff --git a/src/react-app/components/layout/InkModal.tsx b/apps/web/src/components/layout/InkModal.tsx similarity index 98% rename from src/react-app/components/layout/InkModal.tsx rename to apps/web/src/components/layout/InkModal.tsx index a3a2de69..c2cd25f2 100644 --- a/src/react-app/components/layout/InkModal.tsx +++ b/apps/web/src/components/layout/InkModal.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import { type ReactNode, useEffect, useState } from 'react'; import { createPortal } from 'react-dom'; diff --git a/src/react-app/components/layout/InkPageShell.tsx b/apps/web/src/components/layout/InkPageShell.tsx similarity index 98% rename from src/react-app/components/layout/InkPageShell.tsx rename to apps/web/src/components/layout/InkPageShell.tsx index 695ac89e..433f7dd7 100644 --- a/src/react-app/components/layout/InkPageShell.tsx +++ b/apps/web/src/components/layout/InkPageShell.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import Link from '@app/components/router/AppLink'; import type { ReactNode } from 'react'; import { InkNav } from '../ui/InkNav'; diff --git a/src/react-app/components/layout/InkSection.tsx b/apps/web/src/components/layout/InkSection.tsx similarity index 94% rename from src/react-app/components/layout/InkSection.tsx rename to apps/web/src/components/layout/InkSection.tsx index 04846eba..801df8cc 100644 --- a/src/react-app/components/layout/InkSection.tsx +++ b/apps/web/src/components/layout/InkSection.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import type { ReactNode } from 'react'; export interface InkSectionProps { diff --git a/src/react-app/components/layout/index.ts b/apps/web/src/components/layout/index.ts similarity index 100% rename from src/react-app/components/layout/index.ts rename to apps/web/src/components/layout/index.ts diff --git a/src/react-app/components/mail/MailAttachmentSlot.tsx b/apps/web/src/components/mail/MailAttachmentSlot.tsx similarity index 91% rename from src/react-app/components/mail/MailAttachmentSlot.tsx rename to apps/web/src/components/mail/MailAttachmentSlot.tsx index aeddd6dc..0bbab0fd 100644 --- a/src/react-app/components/mail/MailAttachmentSlot.tsx +++ b/apps/web/src/components/mail/MailAttachmentSlot.tsx @@ -3,9 +3,9 @@ import { ItemPreviewView } from '@app/components/feature/items/ItemPreviewView'; import { ItemSlot, PreviewSlot } from '@app/components/feature/items/ItemSlot'; import type { ItemPreviewModel } from '@app/components/feature/items/presentation/types'; import { tierColorMap } from '@app/components/ui/inkBadgeTiers'; -import { BEAST_SPECIES } from '@shared/engine/combat-v6/beasts/content'; -import { getGameConceptIcon } from '@shared/lib/gameConceptDisplay'; -import type { MailAttachment } from '@shared/types/mail'; +import { BEAST_SPECIES } from '@daoyou/shared/engine/combat-v6/beasts/content'; +import { getGameConceptIcon } from '@daoyou/shared/lib/gameConceptDisplay'; +import type { MailAttachment } from '@daoyou/shared/types/mail'; export function MailAttachmentSlot({ attachment, diff --git a/src/react-app/components/mail/MailComposer.tsx b/apps/web/src/components/mail/MailComposer.tsx similarity index 97% rename from src/react-app/components/mail/MailComposer.tsx rename to apps/web/src/components/mail/MailComposer.tsx index 5e75e0fa..476788e1 100644 --- a/src/react-app/components/mail/MailComposer.tsx +++ b/apps/web/src/components/mail/MailComposer.tsx @@ -13,9 +13,9 @@ import { import { InkDetailDrawer } from '@app/components/ui/InkDetailDrawer'; import { useInventoryBag } from '@app/lib/resources/bag'; import { useResourceMutation } from '@app/lib/resources/mutations'; -import type { FriendCultivatorSummary } from '@shared/contracts/friends'; -import type { InventoryView } from '@shared/contracts/inventory'; -import { mailGiftBlockReason, SendMailSchema } from '@shared/contracts/mail'; +import type { FriendCultivatorSummary } from '@daoyou/shared/contracts/friends'; +import type { InventoryView } from '@daoyou/shared/contracts/inventory'; +import { mailGiftBlockReason, SendMailSchema } from '@daoyou/shared/contracts/mail'; import { useRef, useState } from 'react'; export function MailComposer({ diff --git a/src/react-app/components/mail/MailDetailModal.tsx b/apps/web/src/components/mail/MailDetailModal.tsx similarity index 98% rename from src/react-app/components/mail/MailDetailModal.tsx rename to apps/web/src/components/mail/MailDetailModal.tsx index 35af537d..ddbc77d6 100644 --- a/src/react-app/components/mail/MailDetailModal.tsx +++ b/apps/web/src/components/mail/MailDetailModal.tsx @@ -3,7 +3,7 @@ import { useInkUI } from '@app/components/providers/InkUIProvider'; import { InkButton } from '@app/components/ui/InkButton'; import { InkNotice } from '@app/components/ui/InkNotice'; import { useResourceMutation } from '@app/lib/resources/mutations'; -import { mailLocationText } from '@shared/contracts/mail'; +import { mailLocationText } from '@daoyou/shared/contracts/mail'; import { useState } from 'react'; import { MailAttachmentSlot } from './MailAttachmentSlot'; import { Mail } from './MailList'; diff --git a/src/react-app/components/mail/MailList.tsx b/apps/web/src/components/mail/MailList.tsx similarity index 97% rename from src/react-app/components/mail/MailList.tsx rename to apps/web/src/components/mail/MailList.tsx index 27ccfec8..092cd114 100644 --- a/src/react-app/components/mail/MailList.tsx +++ b/apps/web/src/components/mail/MailList.tsx @@ -1,6 +1,6 @@ import { InkListItem } from '@app/components/ui/InkList'; import { InkNotice } from '@app/components/ui/InkNotice'; -import type { MailAttachment } from '@shared/types/mail'; +import type { MailAttachment } from '@daoyou/shared/types/mail'; // Define local interface to match API response/schema export interface Mail { diff --git a/src/react-app/components/providers/InkUIProvider.ts b/apps/web/src/components/providers/InkUIProvider.ts similarity index 100% rename from src/react-app/components/providers/InkUIProvider.ts rename to apps/web/src/components/providers/InkUIProvider.ts diff --git a/src/react-app/components/providers/InkUIProviderRoot.tsx b/apps/web/src/components/providers/InkUIProviderRoot.tsx similarity index 100% rename from src/react-app/components/providers/InkUIProviderRoot.tsx rename to apps/web/src/components/providers/InkUIProviderRoot.tsx diff --git a/src/react-app/components/providers/PwaInstallProvider.ts b/apps/web/src/components/providers/PwaInstallProvider.ts similarity index 100% rename from src/react-app/components/providers/PwaInstallProvider.ts rename to apps/web/src/components/providers/PwaInstallProvider.ts diff --git a/src/react-app/components/providers/PwaInstallProviderRoot.tsx b/apps/web/src/components/providers/PwaInstallProviderRoot.tsx similarity index 100% rename from src/react-app/components/providers/PwaInstallProviderRoot.tsx rename to apps/web/src/components/providers/PwaInstallProviderRoot.tsx diff --git a/src/react-app/components/providers/inkUIContext.ts b/apps/web/src/components/providers/inkUIContext.ts similarity index 100% rename from src/react-app/components/providers/inkUIContext.ts rename to apps/web/src/components/providers/inkUIContext.ts diff --git a/src/react-app/components/providers/pwaInstallContext.ts b/apps/web/src/components/providers/pwaInstallContext.ts similarity index 100% rename from src/react-app/components/providers/pwaInstallContext.ts rename to apps/web/src/components/providers/pwaInstallContext.ts diff --git a/src/react-app/components/providers/useInkUI.ts b/apps/web/src/components/providers/useInkUI.ts similarity index 100% rename from src/react-app/components/providers/useInkUI.ts rename to apps/web/src/components/providers/useInkUI.ts diff --git a/src/react-app/components/providers/usePwaInstall.ts b/apps/web/src/components/providers/usePwaInstall.ts similarity index 100% rename from src/react-app/components/providers/usePwaInstall.ts rename to apps/web/src/components/providers/usePwaInstall.ts diff --git a/src/react-app/components/router/AppLink.tsx b/apps/web/src/components/router/AppLink.tsx similarity index 100% rename from src/react-app/components/router/AppLink.tsx rename to apps/web/src/components/router/AppLink.tsx diff --git a/src/react-app/components/router/RouteDocumentTitle.tsx b/apps/web/src/components/router/RouteDocumentTitle.tsx similarity index 100% rename from src/react-app/components/router/RouteDocumentTitle.tsx rename to apps/web/src/components/router/RouteDocumentTitle.tsx diff --git a/src/react-app/components/ui/GameIcon.tsx b/apps/web/src/components/ui/GameIcon.tsx similarity index 97% rename from src/react-app/components/ui/GameIcon.tsx rename to apps/web/src/components/ui/GameIcon.tsx index 17facfab..65f4a1c0 100644 --- a/src/react-app/components/ui/GameIcon.tsx +++ b/apps/web/src/components/ui/GameIcon.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import { GameArtwork, GameImage, type GameImagePurpose } from './GameImage'; import { iconRegistry } from './icons/registry'; diff --git a/src/react-app/components/ui/GameImage.tsx b/apps/web/src/components/ui/GameImage.tsx similarity index 100% rename from src/react-app/components/ui/GameImage.tsx rename to apps/web/src/components/ui/GameImage.tsx diff --git a/src/react-app/components/ui/InkActionGroup.tsx b/apps/web/src/components/ui/InkActionGroup.tsx similarity index 94% rename from src/react-app/components/ui/InkActionGroup.tsx rename to apps/web/src/components/ui/InkActionGroup.tsx index 4231fcec..00b36685 100644 --- a/src/react-app/components/ui/InkActionGroup.tsx +++ b/apps/web/src/components/ui/InkActionGroup.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import { cva, type VariantProps } from 'class-variance-authority'; import type { ReactNode } from 'react'; diff --git a/src/react-app/components/ui/InkBadge.ts b/apps/web/src/components/ui/InkBadge.ts similarity index 100% rename from src/react-app/components/ui/InkBadge.ts rename to apps/web/src/components/ui/InkBadge.ts diff --git a/src/react-app/components/ui/InkBadgeView.tsx b/apps/web/src/components/ui/InkBadgeView.tsx similarity index 97% rename from src/react-app/components/ui/InkBadgeView.tsx rename to apps/web/src/components/ui/InkBadgeView.tsx index 7de28c52..c9c4ddef 100644 --- a/src/react-app/components/ui/InkBadgeView.tsx +++ b/apps/web/src/components/ui/InkBadgeView.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import type { ReactNode } from 'react'; import { tierColorMap, type Tier } from './inkBadgeTiers'; diff --git a/src/react-app/components/ui/InkButton.tsx b/apps/web/src/components/ui/InkButton.tsx similarity index 98% rename from src/react-app/components/ui/InkButton.tsx rename to apps/web/src/components/ui/InkButton.tsx index 7f16bb2a..7a1824bf 100644 --- a/src/react-app/components/ui/InkButton.tsx +++ b/apps/web/src/components/ui/InkButton.tsx @@ -1,5 +1,5 @@ import Link from '@app/components/router/AppLink'; -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import { cva, type VariantProps } from 'class-variance-authority'; import type { ReactNode } from 'react'; diff --git a/src/react-app/components/ui/InkCard.tsx b/apps/web/src/components/ui/InkCard.tsx similarity index 96% rename from src/react-app/components/ui/InkCard.tsx rename to apps/web/src/components/ui/InkCard.tsx index e16a5f05..0854340f 100644 --- a/src/react-app/components/ui/InkCard.tsx +++ b/apps/web/src/components/ui/InkCard.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import { cva, type VariantProps } from 'class-variance-authority'; import type { ReactNode } from 'react'; diff --git a/src/react-app/components/ui/InkChoiceButton.tsx b/apps/web/src/components/ui/InkChoiceButton.tsx similarity index 97% rename from src/react-app/components/ui/InkChoiceButton.tsx rename to apps/web/src/components/ui/InkChoiceButton.tsx index a0be7941..7a0c6c98 100644 --- a/src/react-app/components/ui/InkChoiceButton.tsx +++ b/apps/web/src/components/ui/InkChoiceButton.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import { cva, type VariantProps } from 'class-variance-authority'; import type { ReactNode } from 'react'; diff --git a/src/react-app/components/ui/InkDetailDrawer.tsx b/apps/web/src/components/ui/InkDetailDrawer.tsx similarity index 99% rename from src/react-app/components/ui/InkDetailDrawer.tsx rename to apps/web/src/components/ui/InkDetailDrawer.tsx index 24571876..f390b996 100644 --- a/src/react-app/components/ui/InkDetailDrawer.tsx +++ b/apps/web/src/components/ui/InkDetailDrawer.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import { useEffect, useId, diff --git a/src/react-app/components/ui/InkDialog.tsx b/apps/web/src/components/ui/InkDialog.tsx similarity index 98% rename from src/react-app/components/ui/InkDialog.tsx rename to apps/web/src/components/ui/InkDialog.tsx index 440bcdde..41358d7f 100644 --- a/src/react-app/components/ui/InkDialog.tsx +++ b/apps/web/src/components/ui/InkDialog.tsx @@ -1,5 +1,5 @@ import { InkModal } from '@app/components/layout/InkModal'; -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import type { ReactNode } from 'react'; import { useState } from 'react'; import { InkButton } from './InkButton'; diff --git a/src/react-app/components/ui/InkDiscreteRange.tsx b/apps/web/src/components/ui/InkDiscreteRange.tsx similarity index 98% rename from src/react-app/components/ui/InkDiscreteRange.tsx rename to apps/web/src/components/ui/InkDiscreteRange.tsx index 37a66395..020f62c4 100644 --- a/src/react-app/components/ui/InkDiscreteRange.tsx +++ b/apps/web/src/components/ui/InkDiscreteRange.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; const sliderClass = 'pointer-events-none absolute inset-0 h-11 w-full appearance-none bg-transparent [&::-webkit-slider-runnable-track]:bg-transparent [&::-moz-range-track]:bg-transparent [&::-webkit-slider-thumb]:pointer-events-auto [&::-webkit-slider-thumb]:size-6 [&::-webkit-slider-thumb]:cursor-grab [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-crimson [&::-webkit-slider-thumb]:bg-bgpaper [&::-webkit-slider-thumb]:shadow-sm [&::-moz-range-thumb]:pointer-events-auto [&::-moz-range-thumb]:size-6 [&::-moz-range-thumb]:cursor-grab [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-crimson [&::-moz-range-thumb]:bg-bgpaper [&::-moz-range-thumb]:shadow-sm focus-visible:outline-none focus-visible:[&::-webkit-slider-thumb]:ring-2 focus-visible:[&::-webkit-slider-thumb]:ring-crimson/40 focus-visible:[&::-moz-range-thumb]:ring-2 focus-visible:[&::-moz-range-thumb]:ring-crimson/40'; diff --git a/src/react-app/components/ui/InkDivider.tsx b/apps/web/src/components/ui/InkDivider.tsx similarity index 94% rename from src/react-app/components/ui/InkDivider.tsx rename to apps/web/src/components/ui/InkDivider.tsx index f2b5d653..4cf1536c 100644 --- a/src/react-app/components/ui/InkDivider.tsx +++ b/apps/web/src/components/ui/InkDivider.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; export interface InkDividerProps { variant?: 'line' | 'symbol'; diff --git a/src/react-app/components/ui/InkHorizontalScroll.tsx b/apps/web/src/components/ui/InkHorizontalScroll.tsx similarity index 98% rename from src/react-app/components/ui/InkHorizontalScroll.tsx rename to apps/web/src/components/ui/InkHorizontalScroll.tsx index 68415a6e..3eced873 100644 --- a/src/react-app/components/ui/InkHorizontalScroll.tsx +++ b/apps/web/src/components/ui/InkHorizontalScroll.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import { useCallback, useEffect, diff --git a/src/react-app/components/ui/InkIdentifyCelebration.tsx b/apps/web/src/components/ui/InkIdentifyCelebration.tsx similarity index 100% rename from src/react-app/components/ui/InkIdentifyCelebration.tsx rename to apps/web/src/components/ui/InkIdentifyCelebration.tsx diff --git a/src/react-app/components/ui/InkInput.tsx b/apps/web/src/components/ui/InkInput.tsx similarity index 98% rename from src/react-app/components/ui/InkInput.tsx rename to apps/web/src/components/ui/InkInput.tsx index d8b8b888..c7a01cda 100644 --- a/src/react-app/components/ui/InkInput.tsx +++ b/apps/web/src/components/ui/InkInput.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import type { ChangeEvent, KeyboardEvent } from 'react'; import type { HTMLInputTypeAttribute } from 'react'; import { diff --git a/src/react-app/components/ui/InkLink.tsx b/apps/web/src/components/ui/InkLink.tsx similarity index 93% rename from src/react-app/components/ui/InkLink.tsx rename to apps/web/src/components/ui/InkLink.tsx index e37bf7a2..4d01eba6 100644 --- a/src/react-app/components/ui/InkLink.tsx +++ b/apps/web/src/components/ui/InkLink.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import Link from '@app/components/router/AppLink'; import type { ReactNode } from 'react'; diff --git a/src/react-app/components/ui/InkList.tsx b/apps/web/src/components/ui/InkList.tsx similarity index 97% rename from src/react-app/components/ui/InkList.tsx rename to apps/web/src/components/ui/InkList.tsx index 0cb9a58f..5b75879b 100644 --- a/src/react-app/components/ui/InkList.tsx +++ b/apps/web/src/components/ui/InkList.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import type { ReactNode } from 'react'; // ============ InkList ============ diff --git a/src/react-app/components/ui/InkLoadingBar.tsx b/apps/web/src/components/ui/InkLoadingBar.tsx similarity index 93% rename from src/react-app/components/ui/InkLoadingBar.tsx rename to apps/web/src/components/ui/InkLoadingBar.tsx index 4b3d54e8..811dce6b 100644 --- a/src/react-app/components/ui/InkLoadingBar.tsx +++ b/apps/web/src/components/ui/InkLoadingBar.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; export interface InkLoadingBarProps { tone?: 'ink' | 'inverse' | 'accent'; diff --git a/src/react-app/components/ui/InkNav.tsx b/apps/web/src/components/ui/InkNav.tsx similarity index 100% rename from src/react-app/components/ui/InkNav.tsx rename to apps/web/src/components/ui/InkNav.tsx diff --git a/src/react-app/components/ui/InkNotice.tsx b/apps/web/src/components/ui/InkNotice.tsx similarity index 94% rename from src/react-app/components/ui/InkNotice.tsx rename to apps/web/src/components/ui/InkNotice.tsx index 9fee3926..0ba7b9f9 100644 --- a/src/react-app/components/ui/InkNotice.tsx +++ b/apps/web/src/components/ui/InkNotice.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import { cva, type VariantProps } from 'class-variance-authority'; import type { ReactNode } from 'react'; diff --git a/src/react-app/components/ui/InkQuantityInput.tsx b/apps/web/src/components/ui/InkQuantityInput.tsx similarity index 100% rename from src/react-app/components/ui/InkQuantityInput.tsx rename to apps/web/src/components/ui/InkQuantityInput.tsx diff --git a/src/react-app/components/ui/InkSelect.tsx b/apps/web/src/components/ui/InkSelect.tsx similarity index 96% rename from src/react-app/components/ui/InkSelect.tsx rename to apps/web/src/components/ui/InkSelect.tsx index 21e25706..604523c7 100644 --- a/src/react-app/components/ui/InkSelect.tsx +++ b/apps/web/src/components/ui/InkSelect.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import type { ChangeEvent, ReactNode } from 'react'; import { diff --git a/src/react-app/components/ui/InkStatRow.tsx b/apps/web/src/components/ui/InkStatRow.tsx similarity index 96% rename from src/react-app/components/ui/InkStatRow.tsx rename to apps/web/src/components/ui/InkStatRow.tsx index 75c8e4eb..aaee4b03 100644 --- a/src/react-app/components/ui/InkStatRow.tsx +++ b/apps/web/src/components/ui/InkStatRow.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import type { ReactNode } from 'react'; export interface InkStatRowProps { diff --git a/src/react-app/components/ui/InkStatusBar.tsx b/apps/web/src/components/ui/InkStatusBar.tsx similarity index 95% rename from src/react-app/components/ui/InkStatusBar.tsx rename to apps/web/src/components/ui/InkStatusBar.tsx index 68f7c7e0..80ae429a 100644 --- a/src/react-app/components/ui/InkStatusBar.tsx +++ b/apps/web/src/components/ui/InkStatusBar.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; export interface InkStatusDatum { label: string; diff --git a/src/react-app/components/ui/InkSwitch.tsx b/apps/web/src/components/ui/InkSwitch.tsx similarity index 96% rename from src/react-app/components/ui/InkSwitch.tsx rename to apps/web/src/components/ui/InkSwitch.tsx index 9d421f6c..1d2bc067 100644 --- a/src/react-app/components/ui/InkSwitch.tsx +++ b/apps/web/src/components/ui/InkSwitch.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; export interface InkSwitchProps { checked: boolean; diff --git a/src/react-app/components/ui/InkTabs.tsx b/apps/web/src/components/ui/InkTabs.tsx similarity index 98% rename from src/react-app/components/ui/InkTabs.tsx rename to apps/web/src/components/ui/InkTabs.tsx index 5fad2263..655e1291 100644 --- a/src/react-app/components/ui/InkTabs.tsx +++ b/apps/web/src/components/ui/InkTabs.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import { useCallback, useEffect, diff --git a/src/react-app/components/ui/InkTag.tsx b/apps/web/src/components/ui/InkTag.tsx similarity index 96% rename from src/react-app/components/ui/InkTag.tsx rename to apps/web/src/components/ui/InkTag.tsx index 8bbdf510..7004ccdf 100644 --- a/src/react-app/components/ui/InkTag.tsx +++ b/apps/web/src/components/ui/InkTag.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import { cva, type VariantProps } from 'class-variance-authority'; import type { ReactNode } from 'react'; diff --git a/src/react-app/components/ui/InkToast.tsx b/apps/web/src/components/ui/InkToast.tsx similarity index 98% rename from src/react-app/components/ui/InkToast.tsx rename to apps/web/src/components/ui/InkToast.tsx index 5ff0fa62..871a8edc 100644 --- a/src/react-app/components/ui/InkToast.tsx +++ b/apps/web/src/components/ui/InkToast.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import { InkButton } from './InkButton'; // ============ Toast Types ============ diff --git a/src/react-app/components/ui/InkTooltip.tsx b/apps/web/src/components/ui/InkTooltip.tsx similarity index 100% rename from src/react-app/components/ui/InkTooltip.tsx rename to apps/web/src/components/ui/InkTooltip.tsx diff --git a/src/react-app/components/ui/ItemCard.tsx b/apps/web/src/components/ui/ItemCard.tsx similarity index 96% rename from src/react-app/components/ui/ItemCard.tsx rename to apps/web/src/components/ui/ItemCard.tsx index b84cc1e5..222bcbb0 100644 --- a/src/react-app/components/ui/ItemCard.tsx +++ b/apps/web/src/components/ui/ItemCard.tsx @@ -5,7 +5,7 @@ * 只做标题 / 徽标 / 描述 / 元信息 / 操作按钮的排版。 */ -import type { Quality } from '@shared/types/constants'; +import type { Quality } from '@daoyou/shared/types/constants'; import type { ReactNode } from 'react'; import { InkBadge } from './InkBadge'; import { InkListItem } from './InkList'; diff --git a/src/react-app/components/ui/ItemShowcaseModal.tsx b/apps/web/src/components/ui/ItemShowcaseModal.tsx similarity index 100% rename from src/react-app/components/ui/ItemShowcaseModal.tsx rename to apps/web/src/components/ui/ItemShowcaseModal.tsx diff --git a/src/react-app/components/ui/ScoreMark.tsx b/apps/web/src/components/ui/ScoreMark.tsx similarity index 85% rename from src/react-app/components/ui/ScoreMark.tsx rename to apps/web/src/components/ui/ScoreMark.tsx index c65a4f78..14d07fdd 100644 --- a/src/react-app/components/ui/ScoreMark.tsx +++ b/apps/web/src/components/ui/ScoreMark.tsx @@ -1,5 +1,5 @@ -import { cn } from '@shared/lib/cn'; -import { formatScore } from '@shared/lib/scoreFormat'; +import { cn } from '@daoyou/shared/lib/cn'; +import { formatScore } from '@daoyou/shared/lib/scoreFormat'; export function ScoreMark({ score, diff --git a/src/react-app/components/ui/TypewriterText.tsx b/apps/web/src/components/ui/TypewriterText.tsx similarity index 96% rename from src/react-app/components/ui/TypewriterText.tsx rename to apps/web/src/components/ui/TypewriterText.tsx index ab410482..0a079505 100644 --- a/src/react-app/components/ui/TypewriterText.tsx +++ b/apps/web/src/components/ui/TypewriterText.tsx @@ -1,5 +1,5 @@ import { TypewriterOptions, useTypewriter } from '@app/lib/hooks/useTypewriter'; -import { cn } from '@shared/lib/utils'; +import { cn } from '@daoyou/shared/lib/utils'; interface TypewriterTextProps extends Omit { className?: string; diff --git a/src/react-app/components/ui/icons/registry.ts b/apps/web/src/components/ui/icons/registry.ts similarity index 100% rename from src/react-app/components/ui/icons/registry.ts rename to apps/web/src/components/ui/icons/registry.ts diff --git a/src/react-app/components/ui/index.ts b/apps/web/src/components/ui/index.ts similarity index 100% rename from src/react-app/components/ui/index.ts rename to apps/web/src/components/ui/index.ts diff --git a/src/react-app/components/ui/inkBadgeTiers.ts b/apps/web/src/components/ui/inkBadgeTiers.ts similarity index 92% rename from src/react-app/components/ui/inkBadgeTiers.ts rename to apps/web/src/components/ui/inkBadgeTiers.ts index bf7412d4..066d65ef 100644 --- a/src/react-app/components/ui/inkBadgeTiers.ts +++ b/apps/web/src/components/ui/inkBadgeTiers.ts @@ -3,8 +3,8 @@ import { RealmType, SkillGrade, SpiritualRootGrade, -} from '@shared/types/constants'; -import type { DungeonDifficultyTier } from '@shared/lib/game/mapSystem'; +} from '@daoyou/shared/types/constants'; +import type { DungeonDifficultyTier } from '@daoyou/shared/lib/game/mapSystem'; /** * 品阶类型:品质、灵根等级、技能等级、境界 diff --git a/src/react-app/components/ui/inkFieldStyles.ts b/apps/web/src/components/ui/inkFieldStyles.ts similarity index 100% rename from src/react-app/components/ui/inkFieldStyles.ts rename to apps/web/src/components/ui/inkFieldStyles.ts diff --git a/src/react-app/index.css b/apps/web/src/index.css similarity index 100% rename from src/react-app/index.css rename to apps/web/src/index.css diff --git a/src/react-app/layouts/SpecialSceneProvider.tsx b/apps/web/src/layouts/SpecialSceneProvider.tsx similarity index 100% rename from src/react-app/layouts/SpecialSceneProvider.tsx rename to apps/web/src/layouts/SpecialSceneProvider.tsx diff --git a/src/react-app/layouts/combat-v6-layout.tsx b/apps/web/src/layouts/combat-v6-layout.tsx similarity index 100% rename from src/react-app/layouts/combat-v6-layout.tsx rename to apps/web/src/layouts/combat-v6-layout.tsx diff --git a/src/react-app/layouts/game-layout.tsx b/apps/web/src/layouts/game-layout.tsx similarity index 100% rename from src/react-app/layouts/game-layout.tsx rename to apps/web/src/layouts/game-layout.tsx diff --git a/src/react-app/layouts/special-scene.ts b/apps/web/src/layouts/special-scene.ts similarity index 100% rename from src/react-app/layouts/special-scene.ts rename to apps/web/src/layouts/special-scene.ts diff --git a/src/react-app/layouts/specialSceneContext.ts b/apps/web/src/layouts/specialSceneContext.ts similarity index 100% rename from src/react-app/layouts/specialSceneContext.ts rename to apps/web/src/layouts/specialSceneContext.ts diff --git a/src/react-app/layouts/useSpecialSceneBack.ts b/apps/web/src/layouts/useSpecialSceneBack.ts similarity index 100% rename from src/react-app/layouts/useSpecialSceneBack.ts rename to apps/web/src/layouts/useSpecialSceneBack.ts diff --git a/src/react-app/lib/api/url.ts b/apps/web/src/lib/api/url.ts similarity index 92% rename from src/react-app/lib/api/url.ts rename to apps/web/src/lib/api/url.ts index d0ec7e6e..c029941d 100644 --- a/src/react-app/lib/api/url.ts +++ b/apps/web/src/lib/api/url.ts @@ -1,5 +1,5 @@ import { clientEnv } from '@app/lib/env'; -import type { RealtimeChannel } from '@shared/contracts/realtime'; +import type { RealtimeChannel } from '@daoyou/shared/contracts/realtime'; export function resolveApiUrl(input: string) { if (!input.startsWith('/api/')) { diff --git a/src/react-app/lib/appVersion.ts b/apps/web/src/lib/appVersion.ts similarity index 100% rename from src/react-app/lib/appVersion.ts rename to apps/web/src/lib/appVersion.ts diff --git a/src/react-app/lib/auth/AuthProvider.tsx b/apps/web/src/lib/auth/AuthProvider.tsx similarity index 100% rename from src/react-app/lib/auth/AuthProvider.tsx rename to apps/web/src/lib/auth/AuthProvider.tsx diff --git a/src/react-app/lib/auth/accountDeletion.ts b/apps/web/src/lib/auth/accountDeletion.ts similarity index 100% rename from src/react-app/lib/auth/accountDeletion.ts rename to apps/web/src/lib/auth/accountDeletion.ts diff --git a/src/react-app/lib/auth/authContext.ts b/apps/web/src/lib/auth/authContext.ts similarity index 100% rename from src/react-app/lib/auth/authContext.ts rename to apps/web/src/lib/auth/authContext.ts diff --git a/src/react-app/lib/auth/authState.ts b/apps/web/src/lib/auth/authState.ts similarity index 100% rename from src/react-app/lib/auth/authState.ts rename to apps/web/src/lib/auth/authState.ts diff --git a/src/react-app/lib/auth/client.ts b/apps/web/src/lib/auth/client.ts similarity index 100% rename from src/react-app/lib/auth/client.ts rename to apps/web/src/lib/auth/client.ts diff --git a/src/react-app/lib/auth/useAuth.ts b/apps/web/src/lib/auth/useAuth.ts similarity index 100% rename from src/react-app/lib/auth/useAuth.ts rename to apps/web/src/lib/auth/useAuth.ts diff --git a/src/react-app/lib/client/requestCache.ts b/apps/web/src/lib/client/requestCache.ts similarity index 100% rename from src/react-app/lib/client/requestCache.ts rename to apps/web/src/lib/client/requestCache.ts diff --git a/src/react-app/lib/divinationApi.ts b/apps/web/src/lib/divinationApi.ts similarity index 95% rename from src/react-app/lib/divinationApi.ts rename to apps/web/src/lib/divinationApi.ts index 88c8bed1..0c2b4b0c 100644 --- a/src/react-app/lib/divinationApi.ts +++ b/apps/web/src/lib/divinationApi.ts @@ -2,8 +2,8 @@ import type { DivinationRecord, DivinationStreamEvent, DivinationView, -} from '@shared/contracts/divination'; -import type { DivinationDirection } from '@shared/lib/divination'; +} from '@daoyou/shared/contracts/divination'; +import type { DivinationDirection } from '@daoyou/shared/lib/divination'; async function readJson(response: Response): Promise { const body = await response.json(); diff --git a/src/react-app/lib/dungeon/formatDungeonCost.ts b/apps/web/src/lib/dungeon/formatDungeonCost.ts similarity index 88% rename from src/react-app/lib/dungeon/formatDungeonCost.ts rename to apps/web/src/lib/dungeon/formatDungeonCost.ts index 29f4acfd..5d750aed 100644 --- a/src/react-app/lib/dungeon/formatDungeonCost.ts +++ b/apps/web/src/lib/dungeon/formatDungeonCost.ts @@ -1,10 +1,10 @@ -import type { DungeonOptionCost } from '@shared/lib/dungeon/types'; -import type { MaterialType } from '@shared/types/constants'; +import type { DungeonOptionCost } from '@daoyou/shared/lib/dungeon/types'; +import type { MaterialType } from '@daoyou/shared/types/constants'; import { getMaterialTypeLabel, getResourceTypeLabel, -} from '@shared/lib/gameConceptDisplay'; -import { getResourceDisplayName } from '@shared/lib/gameConceptDisplay'; +} from '@daoyou/shared/lib/gameConceptDisplay'; +import { getResourceDisplayName } from '@daoyou/shared/lib/gameConceptDisplay'; function formatMaterialCostName(cost: DungeonOptionCost) { if (cost.name) { diff --git a/src/react-app/lib/env.ts b/apps/web/src/lib/env.ts similarity index 100% rename from src/react-app/lib/env.ts rename to apps/web/src/lib/env.ts diff --git a/src/react-app/lib/game-setting.ts b/apps/web/src/lib/game-setting.ts similarity index 100% rename from src/react-app/lib/game-setting.ts rename to apps/web/src/lib/game-setting.ts diff --git a/src/react-app/lib/gameActivityImmersive.ts b/apps/web/src/lib/gameActivityImmersive.ts similarity index 100% rename from src/react-app/lib/gameActivityImmersive.ts rename to apps/web/src/lib/gameActivityImmersive.ts diff --git a/src/react-app/lib/hooks/dungeon/useDungeonActions.ts b/apps/web/src/lib/hooks/dungeon/useDungeonActions.ts similarity index 96% rename from src/react-app/lib/hooks/dungeon/useDungeonActions.ts rename to apps/web/src/lib/hooks/dungeon/useDungeonActions.ts index 85c533dd..ffbc1b98 100644 --- a/src/react-app/lib/hooks/dungeon/useDungeonActions.ts +++ b/apps/web/src/lib/hooks/dungeon/useDungeonActions.ts @@ -1,11 +1,11 @@ import { useInkUI } from '@app/components/providers/InkUIProvider'; import { consumeResourceMutation } from '@app/lib/resources/mutations'; -import type { DungeonMaterialSelection } from '@shared/contracts/combatV6Dungeon'; +import type { DungeonMaterialSelection } from '@daoyou/shared/contracts/combatV6Dungeon'; import type { DungeonOption, DungeonRecoverAction, DungeonState, -} from '@shared/lib/dungeon/types'; +} from '@daoyou/shared/lib/dungeon/types'; import { useRef, useState } from 'react'; export function useDungeonActions( diff --git a/src/react-app/lib/hooks/dungeon/useDungeonLimit.ts b/apps/web/src/lib/hooks/dungeon/useDungeonLimit.ts similarity index 100% rename from src/react-app/lib/hooks/dungeon/useDungeonLimit.ts rename to apps/web/src/lib/hooks/dungeon/useDungeonLimit.ts diff --git a/src/react-app/lib/hooks/dungeon/useDungeonState.ts b/apps/web/src/lib/hooks/dungeon/useDungeonState.ts similarity index 97% rename from src/react-app/lib/hooks/dungeon/useDungeonState.ts rename to apps/web/src/lib/hooks/dungeon/useDungeonState.ts index 77e5ebbf..9775ea90 100644 --- a/src/react-app/lib/hooks/dungeon/useDungeonState.ts +++ b/apps/web/src/lib/hooks/dungeon/useDungeonState.ts @@ -1,4 +1,4 @@ -import type { DungeonState } from '@shared/lib/dungeon/types'; +import type { DungeonState } from '@daoyou/shared/lib/dungeon/types'; import { useCallback, useEffect, useRef, useState } from 'react'; export function useDungeonState(cultivatorId: string | undefined) { diff --git a/src/react-app/lib/hooks/dungeon/useDungeonViewModel.ts b/apps/web/src/lib/hooks/dungeon/useDungeonViewModel.ts similarity index 96% rename from src/react-app/lib/hooks/dungeon/useDungeonViewModel.ts rename to apps/web/src/lib/hooks/dungeon/useDungeonViewModel.ts index eb204598..19ae87f2 100644 --- a/src/react-app/lib/hooks/dungeon/useDungeonViewModel.ts +++ b/apps/web/src/lib/hooks/dungeon/useDungeonViewModel.ts @@ -1,15 +1,15 @@ import { useQiActionConfirm } from '@app/components/feature/cultivator/useQiActionConfirm'; import { BattleCallbackData } from '@app/routes/game/dungeon/components/DungeonBattle'; -import { QI_ACTION_COSTS } from '@shared/config/qiSystem'; -import type { DungeonMaterialSelection } from '@shared/contracts/combatV6Dungeon'; -import type { ResourceOperation } from '@shared/engine/resource/types'; +import { QI_ACTION_COSTS } from '@daoyou/shared/config/qiSystem'; +import type { DungeonMaterialSelection } from '@daoyou/shared/contracts/combatV6Dungeon'; +import type { ResourceOperation } from '@daoyou/shared/engine/resource/types'; import type { DungeonOption, DungeonRecoverAction, DungeonRound, DungeonSettlement, DungeonState, -} from '@shared/lib/dungeon/types'; +} from '@daoyou/shared/lib/dungeon/types'; import { useMemo } from 'react'; import { useDungeonActions } from './useDungeonActions'; import { useDungeonState } from './useDungeonState'; diff --git a/src/react-app/lib/hooks/useTaskList.ts b/apps/web/src/lib/hooks/useTaskList.ts similarity index 90% rename from src/react-app/lib/hooks/useTaskList.ts rename to apps/web/src/lib/hooks/useTaskList.ts index 6666a3b9..94e66d6d 100644 --- a/src/react-app/lib/hooks/useTaskList.ts +++ b/apps/web/src/lib/hooks/useTaskList.ts @@ -3,7 +3,7 @@ import { type PlayerTasksParams, } from '@app/lib/resources/definitions'; import { useResource } from '@app/lib/resources/hooks'; -import type { TaskStatus } from '@shared/types/task'; +import type { TaskStatus } from '@daoyou/shared/types/task'; import { useMemo } from 'react'; export function useTaskList( diff --git a/src/react-app/lib/hooks/useTypewriter.ts b/apps/web/src/lib/hooks/useTypewriter.ts similarity index 100% rename from src/react-app/lib/hooks/useTypewriter.ts rename to apps/web/src/lib/hooks/useTypewriter.ts diff --git a/src/react-app/lib/llmConfig.ts b/apps/web/src/lib/llmConfig.ts similarity index 95% rename from src/react-app/lib/llmConfig.ts rename to apps/web/src/lib/llmConfig.ts index dd5411a5..4eaef89c 100644 --- a/src/react-app/lib/llmConfig.ts +++ b/apps/web/src/lib/llmConfig.ts @@ -1,7 +1,7 @@ import { LlmByokConfigSchema, type LlmByokConfig, -} from '@shared/config/llm'; +} from '@daoyou/shared/config/llm'; export const LLM_STORAGE_KEY = 'daoyou_llm_config'; diff --git a/src/react-app/lib/pendingCommandRequest.ts b/apps/web/src/lib/pendingCommandRequest.ts similarity index 100% rename from src/react-app/lib/pendingCommandRequest.ts rename to apps/web/src/lib/pendingCommandRequest.ts diff --git a/src/react-app/lib/player/PlayerProvider.ts b/apps/web/src/lib/player/PlayerProvider.ts similarity index 100% rename from src/react-app/lib/player/PlayerProvider.ts rename to apps/web/src/lib/player/PlayerProvider.ts diff --git a/src/react-app/lib/player/PlayerProviderRoot.tsx b/apps/web/src/lib/player/PlayerProviderRoot.tsx similarity index 100% rename from src/react-app/lib/player/PlayerProviderRoot.tsx rename to apps/web/src/lib/player/PlayerProviderRoot.tsx diff --git a/src/react-app/lib/pwaInstall.ts b/apps/web/src/lib/pwaInstall.ts similarity index 100% rename from src/react-app/lib/pwaInstall.ts rename to apps/web/src/lib/pwaInstall.ts diff --git a/src/react-app/lib/realtime/realtimeClient.ts b/apps/web/src/lib/realtime/realtimeClient.ts similarity index 99% rename from src/react-app/lib/realtime/realtimeClient.ts rename to apps/web/src/lib/realtime/realtimeClient.ts index 0fcb6367..7a2ab16b 100644 --- a/src/react-app/lib/realtime/realtimeClient.ts +++ b/apps/web/src/lib/realtime/realtimeClient.ts @@ -3,7 +3,7 @@ import type { RealtimeChannel, RealtimeServerEvent, RealtimeServerEventType, -} from '@shared/contracts/realtime'; +} from '@daoyou/shared/contracts/realtime'; export type RealtimeConnectionState = | 'idle' diff --git a/src/react-app/lib/resources/bag.ts b/apps/web/src/lib/resources/bag.ts similarity index 100% rename from src/react-app/lib/resources/bag.ts rename to apps/web/src/lib/resources/bag.ts diff --git a/src/react-app/lib/resources/craftStorage.ts b/apps/web/src/lib/resources/craftStorage.ts similarity index 97% rename from src/react-app/lib/resources/craftStorage.ts rename to apps/web/src/lib/resources/craftStorage.ts index 1d124507..41167792 100644 --- a/src/react-app/lib/resources/craftStorage.ts +++ b/apps/web/src/lib/resources/craftStorage.ts @@ -1,7 +1,7 @@ import { combatV6Request } from '@app/components/feature/combat-v6/request'; import type { InventoryFilter } from '@app/components/feature/items/inventoryFilterModel'; import { usePlayerSession } from '@app/lib/resources/player'; -import type { InventoryView } from '@shared/contracts/inventory'; +import type { InventoryView } from '@daoyou/shared/contracts/inventory'; import { useCallback, useEffect, useState } from 'react'; export function useCraftStorage(filter: InventoryFilter, enabled: boolean) { diff --git a/src/react-app/lib/resources/definitionCore.ts b/apps/web/src/lib/resources/definitionCore.ts similarity index 98% rename from src/react-app/lib/resources/definitionCore.ts rename to apps/web/src/lib/resources/definitionCore.ts index 3b04d326..022b7227 100644 --- a/src/react-app/lib/resources/definitionCore.ts +++ b/apps/web/src/lib/resources/definitionCore.ts @@ -8,7 +8,7 @@ import { type ResourceScope, type ResourceScopeKind, type ResourceTopic, -} from '@shared/contracts/resources'; +} from '@daoyou/shared/contracts/resources'; import type { ResourceReducerResult, ResourceRuntimeScopes, diff --git a/src/react-app/lib/resources/definitions.ts b/apps/web/src/lib/resources/definitions.ts similarity index 100% rename from src/react-app/lib/resources/definitions.ts rename to apps/web/src/lib/resources/definitions.ts diff --git a/src/react-app/lib/resources/hooks.ts b/apps/web/src/lib/resources/hooks.ts similarity index 98% rename from src/react-app/lib/resources/hooks.ts rename to apps/web/src/lib/resources/hooks.ts index 9c9002d6..a7013a03 100644 --- a/src/react-app/lib/resources/hooks.ts +++ b/apps/web/src/lib/resources/hooks.ts @@ -7,7 +7,7 @@ import type { ResourceCacheKey, ResourceDataMap, ResourceTopic, -} from '@shared/contracts/resources'; +} from '@daoyou/shared/contracts/resources'; import { useCallback, useMemo, useSyncExternalStore } from 'react'; const unavailableSnapshot: ResourceSnapshot = { diff --git a/src/react-app/lib/resources/inventory.ts b/apps/web/src/lib/resources/inventory.ts similarity index 100% rename from src/react-app/lib/resources/inventory.ts rename to apps/web/src/lib/resources/inventory.ts diff --git a/src/react-app/lib/resources/inventoryDefinitions.ts b/apps/web/src/lib/resources/inventoryDefinitions.ts similarity index 97% rename from src/react-app/lib/resources/inventoryDefinitions.ts rename to apps/web/src/lib/resources/inventoryDefinitions.ts index 68c85359..de174bb6 100644 --- a/src/react-app/lib/resources/inventoryDefinitions.ts +++ b/apps/web/src/lib/resources/inventoryDefinitions.ts @@ -1,12 +1,12 @@ import { reduceInventoryResourcePage, type ResourceTopic, -} from '@shared/contracts/resources'; +} from '@daoyou/shared/contracts/resources'; import type { ElementType, MaterialType, Quality, -} from '@shared/types/constants'; +} from '@daoyou/shared/types/constants'; import { loadResourceEndpoint, resolveTopicScope } from './definitionCore'; import type { ResourceDefinition } from './store'; diff --git a/src/react-app/lib/resources/mutations.ts b/apps/web/src/lib/resources/mutations.ts similarity index 83% rename from src/react-app/lib/resources/mutations.ts rename to apps/web/src/lib/resources/mutations.ts index ee736c08..126a1616 100644 --- a/src/react-app/lib/resources/mutations.ts +++ b/apps/web/src/lib/resources/mutations.ts @@ -1,10 +1,10 @@ import { resourceStore } from '@app/lib/resources/store'; -import type { ApiFailure } from '@shared/contracts/http'; +import type { ApiFailure } from '@daoyou/shared/contracts/http'; import type { PlayerResourceMutationMeta, PlayerStateMutationResponse, -} from '@shared/contracts/player'; -import type { ResourceChange } from '@shared/contracts/resources'; +} from '@daoyou/shared/contracts/player'; +import type { ResourceChange } from '@daoyou/shared/contracts/resources'; import { useMemo } from 'react'; export async function consumeResourceMutation( diff --git a/src/react-app/lib/resources/player.ts b/apps/web/src/lib/resources/player.ts similarity index 100% rename from src/react-app/lib/resources/player.ts rename to apps/web/src/lib/resources/player.ts diff --git a/src/react-app/lib/resources/playerDefinitions.ts b/apps/web/src/lib/resources/playerDefinitions.ts similarity index 98% rename from src/react-app/lib/resources/playerDefinitions.ts rename to apps/web/src/lib/resources/playerDefinitions.ts index 053dd6b0..19a8f2d7 100644 --- a/src/react-app/lib/resources/playerDefinitions.ts +++ b/apps/web/src/lib/resources/playerDefinitions.ts @@ -2,14 +2,14 @@ import type { PlayerResourceKey, PlayerResourceMap, PlayerResourcesResponse, -} from '@shared/contracts/player'; +} from '@daoyou/shared/contracts/player'; import { reduceTaskResourceList, RESOURCE_DATA_SCHEMAS, type ResourceDataMap, type ResourceScope, -} from '@shared/contracts/resources'; -import type { TaskStatus } from '@shared/types/task'; +} from '@daoyou/shared/contracts/resources'; +import type { TaskStatus } from '@daoyou/shared/types/task'; import { defaultResourceReducer, loadResourceEndpoint, diff --git a/src/react-app/lib/resources/recoveryClock.ts b/apps/web/src/lib/resources/recoveryClock.ts similarity index 100% rename from src/react-app/lib/resources/recoveryClock.ts rename to apps/web/src/lib/resources/recoveryClock.ts diff --git a/src/react-app/lib/resources/sectDefinitions.ts b/apps/web/src/lib/resources/sectDefinitions.ts similarity index 96% rename from src/react-app/lib/resources/sectDefinitions.ts rename to apps/web/src/lib/resources/sectDefinitions.ts index 7951db24..df6c3990 100644 --- a/src/react-app/lib/resources/sectDefinitions.ts +++ b/apps/web/src/lib/resources/sectDefinitions.ts @@ -1,4 +1,4 @@ -import type { ResourceTopic } from '@shared/contracts/resources'; +import type { ResourceTopic } from '@daoyou/shared/contracts/resources'; import { defaultResourceReducer, loadResourceEndpoint, diff --git a/src/react-app/lib/resources/store.ts b/apps/web/src/lib/resources/store.ts similarity index 99% rename from src/react-app/lib/resources/store.ts rename to apps/web/src/lib/resources/store.ts index 1a42bb80..a2be12c6 100644 --- a/src/react-app/lib/resources/store.ts +++ b/apps/web/src/lib/resources/store.ts @@ -1,9 +1,9 @@ import { realtimeClient } from '@app/lib/realtime/realtimeClient'; -import type { ApiFailure } from '@shared/contracts/http'; +import type { ApiFailure } from '@daoyou/shared/contracts/http'; import type { PlayerResourceEventsResponse, PlayerStateMutationResponse, -} from '@shared/contracts/player'; +} from '@daoyou/shared/contracts/player'; import { advanceContiguousResourceCursor, createResourceCacheKey, @@ -16,7 +16,7 @@ import { type ResourceScope, type ResourceScopeKind, type ResourceTopic, -} from '@shared/contracts/resources'; +} from '@daoyou/shared/contracts/resources'; export type ResourceStatus = 'idle' | 'loading' | 'ready' | 'stale' | 'error'; diff --git a/src/react-app/lib/router/gameShellRegistry.ts b/apps/web/src/lib/router/gameShellRegistry.ts similarity index 100% rename from src/react-app/lib/router/gameShellRegistry.ts rename to apps/web/src/lib/router/gameShellRegistry.ts diff --git a/src/react-app/lib/router/lazyRoute.ts b/apps/web/src/lib/router/lazyRoute.ts similarity index 100% rename from src/react-app/lib/router/lazyRoute.ts rename to apps/web/src/lib/router/lazyRoute.ts diff --git a/src/react-app/lib/router/loaders.ts b/apps/web/src/lib/router/loaders.ts similarity index 100% rename from src/react-app/lib/router/loaders.ts rename to apps/web/src/lib/router/loaders.ts diff --git a/src/react-app/lib/router/mapNavigation.ts b/apps/web/src/lib/router/mapNavigation.ts similarity index 100% rename from src/react-app/lib/router/mapNavigation.ts rename to apps/web/src/lib/router/mapNavigation.ts diff --git a/src/react-app/lib/router/routeData.ts b/apps/web/src/lib/router/routeData.ts similarity index 100% rename from src/react-app/lib/router/routeData.ts rename to apps/web/src/lib/router/routeData.ts diff --git a/src/react-app/lib/router/routeTitle.ts b/apps/web/src/lib/router/routeTitle.ts similarity index 100% rename from src/react-app/lib/router/routeTitle.ts rename to apps/web/src/lib/router/routeTitle.ts diff --git a/src/react-app/lib/sect/presentation/compositionRoot.ts b/apps/web/src/lib/sect/presentation/compositionRoot.ts similarity index 88% rename from src/react-app/lib/sect/presentation/compositionRoot.ts rename to apps/web/src/lib/sect/presentation/compositionRoot.ts index 4d66a83a..714faaaf 100644 --- a/src/react-app/lib/sect/presentation/compositionRoot.ts +++ b/apps/web/src/lib/sect/presentation/compositionRoot.ts @@ -1,4 +1,4 @@ -import { PRODUCTION_SECT_IDS } from '@shared/engine/sect/content/productionRuntime'; +import { PRODUCTION_SECT_IDS } from '@daoyou/shared/engine/sect/content/productionRuntime'; import { CORE_SECT_TASK_RENDERER_PLUGIN } from './core/module'; import { SectTaskRendererRegistry, diff --git a/src/react-app/lib/sect/presentation/core/module.tsx b/apps/web/src/lib/sect/presentation/core/module.tsx similarity index 99% rename from src/react-app/lib/sect/presentation/core/module.tsx rename to apps/web/src/lib/sect/presentation/core/module.tsx index dccf2622..1a8c7d71 100644 --- a/src/react-app/lib/sect/presentation/core/module.tsx +++ b/apps/web/src/lib/sect/presentation/core/module.tsx @@ -19,7 +19,7 @@ import type { SectMiningSessionData, SectSweepSessionData, SectTaskRewardReceipt, -} from '@shared/contracts/sect'; +} from '@daoyou/shared/contracts/sect'; import { z } from 'zod'; import type { DecodedSectTaskOutcome, diff --git a/src/react-app/lib/sect/presentation/core/registry.ts b/apps/web/src/lib/sect/presentation/core/registry.ts similarity index 98% rename from src/react-app/lib/sect/presentation/core/registry.ts rename to apps/web/src/lib/sect/presentation/core/registry.ts index dc5a5bcd..7a6155a4 100644 --- a/src/react-app/lib/sect/presentation/core/registry.ts +++ b/apps/web/src/lib/sect/presentation/core/registry.ts @@ -2,7 +2,7 @@ import type { SectTaskActionRendererProps } from '@app/components/feature/sect/S import type { SectTaskActionOutcome, SectTaskViewData, -} from '@shared/contracts/sect'; +} from '@daoyou/shared/contracts/sect'; import type { ComponentType } from 'react'; import type { ZodType } from 'zod'; diff --git a/src/react-app/lib/sect/sectClient.ts b/apps/web/src/lib/sect/sectClient.ts similarity index 97% rename from src/react-app/lib/sect/sectClient.ts rename to apps/web/src/lib/sect/sectClient.ts index 893b1de9..035eb9e3 100644 --- a/src/react-app/lib/sect/sectClient.ts +++ b/apps/web/src/lib/sect/sectClient.ts @@ -2,7 +2,7 @@ import { consumeResourceMutation } from '@app/lib/resources/mutations'; import type { SectSubmissionCandidatesData, SectTaskActionData, -} from '@shared/contracts/sect'; +} from '@daoyou/shared/contracts/sect'; const taskBattleRequests = new Map>(); diff --git a/src/react-app/lib/sect/sectPresentation.ts b/apps/web/src/lib/sect/sectPresentation.ts similarity index 83% rename from src/react-app/lib/sect/sectPresentation.ts rename to apps/web/src/lib/sect/sectPresentation.ts index 5d0d8a0f..a4df1fb7 100644 --- a/src/react-app/lib/sect/sectPresentation.ts +++ b/apps/web/src/lib/sect/sectPresentation.ts @@ -1,15 +1,15 @@ -import type { SectFacilityEffectSnapshot } from '@shared/engine/sect'; +import type { SectFacilityEffectSnapshot } from '@daoyou/shared/engine/sect'; import { PRODUCTION_SECT_IDS, PRODUCTION_SECT_PRESENTATIONS, -} from '@shared/engine/sect/content'; +} from '@daoyou/shared/engine/sect/content'; export type { ResolvedSectPresentation, SectMapHotspot, SectPresentationTheme, SectSceneKey, -} from '@shared/engine/sect'; +} from '@daoyou/shared/engine/sect'; export function getSectPresentation(sectId: string) { if (!PRODUCTION_SECT_IDS.includes(sectId)) { diff --git a/src/react-app/lib/story/useStory.ts b/apps/web/src/lib/story/useStory.ts similarity index 100% rename from src/react-app/lib/story/useStory.ts rename to apps/web/src/lib/story/useStory.ts diff --git a/src/react-app/lib/tasks/taskClient.ts b/apps/web/src/lib/tasks/taskClient.ts similarity index 89% rename from src/react-app/lib/tasks/taskClient.ts rename to apps/web/src/lib/tasks/taskClient.ts index d22831ff..1ad0d958 100644 --- a/src/react-app/lib/tasks/taskClient.ts +++ b/apps/web/src/lib/tasks/taskClient.ts @@ -2,10 +2,10 @@ import { consumeResourceMutation } from '@app/lib/resources/mutations'; import type { TaskChallengeResponse, TaskRewardClaimResponse, -} from '@shared/contracts/task'; -import { getNextMajorRealm } from '@shared/lib/breakthroughPill'; -import type { Cultivator } from '@shared/types/cultivator'; -import type { TaskInstance } from '@shared/types/task'; +} from '@daoyou/shared/contracts/task'; +import { getNextMajorRealm } from '@daoyou/shared/lib/breakthroughPill'; +import type { Cultivator } from '@daoyou/shared/types/cultivator'; +import type { TaskInstance } from '@daoyou/shared/types/task'; async function readJsonOrThrow(response: Response): Promise { const payload = await response.json(); diff --git a/src/react-app/main.tsx b/apps/web/src/main.tsx similarity index 100% rename from src/react-app/main.tsx rename to apps/web/src/main.tsx diff --git a/src/react-app/router.tsx b/apps/web/src/router.tsx similarity index 100% rename from src/react-app/router.tsx rename to apps/web/src/router.tsx diff --git a/src/react-app/routes/admin/_components/AdminDialog.tsx b/apps/web/src/routes/admin/_components/AdminDialog.tsx similarity index 98% rename from src/react-app/routes/admin/_components/AdminDialog.tsx rename to apps/web/src/routes/admin/_components/AdminDialog.tsx index ca829232..b275eb1d 100644 --- a/src/react-app/routes/admin/_components/AdminDialog.tsx +++ b/apps/web/src/routes/admin/_components/AdminDialog.tsx @@ -1,5 +1,5 @@ import { InkButton } from '@app/components/ui/InkButton'; -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import { useEffect, useId, useRef, type ReactNode } from 'react'; import { createPortal } from 'react-dom'; diff --git a/src/react-app/routes/admin/_components/AdminPage.tsx b/apps/web/src/routes/admin/_components/AdminPage.tsx similarity index 100% rename from src/react-app/routes/admin/_components/AdminPage.tsx rename to apps/web/src/routes/admin/_components/AdminPage.tsx diff --git a/src/react-app/routes/admin/_components/AdminShell.tsx b/apps/web/src/routes/admin/_components/AdminShell.tsx similarity index 98% rename from src/react-app/routes/admin/_components/AdminShell.tsx rename to apps/web/src/routes/admin/_components/AdminShell.tsx index c078952e..fa72e090 100644 --- a/src/react-app/routes/admin/_components/AdminShell.tsx +++ b/apps/web/src/routes/admin/_components/AdminShell.tsx @@ -1,5 +1,5 @@ import Link from '@app/components/router/AppLink'; -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import type { ReactNode } from 'react'; import { useLocation } from 'react-router'; import { adminNavItems } from '../_config/nav'; diff --git a/src/react-app/routes/admin/_components/ItemExchangeShopAdminPage.tsx b/apps/web/src/routes/admin/_components/ItemExchangeShopAdminPage.tsx similarity index 98% rename from src/react-app/routes/admin/_components/ItemExchangeShopAdminPage.tsx rename to apps/web/src/routes/admin/_components/ItemExchangeShopAdminPage.tsx index 36ad6ae3..680bede1 100644 --- a/src/react-app/routes/admin/_components/ItemExchangeShopAdminPage.tsx +++ b/apps/web/src/routes/admin/_components/ItemExchangeShopAdminPage.tsx @@ -4,14 +4,14 @@ import { InkButton, InkInput, InkNotice, InkSelect } from '@app/components/ui'; import { RewardItemSchema, rewardDisplayItem, -} from '@shared/contracts/adminRewards'; +} from '@daoyou/shared/contracts/adminRewards'; import { ITEM_EXCHANGE_SHOP_MAX_PRICE, ItemExchangeShopItemMutationSchema, type ItemExchangeShopItemMutation, type ItemExchangeShopItemView, -} from '@shared/contracts/itemExchangeShop'; -import type { ItemGrant } from '@shared/inventory'; +} from '@daoyou/shared/contracts/itemExchangeShop'; +import type { ItemGrant } from '@daoyou/shared/inventory'; import { useCallback, useEffect, useState } from 'react'; import { AdminDialog } from './AdminDialog'; import { AdminPageHeader } from './AdminPage'; diff --git a/src/react-app/routes/admin/_components/RewardItemPicker.tsx b/apps/web/src/routes/admin/_components/RewardItemPicker.tsx similarity index 95% rename from src/react-app/routes/admin/_components/RewardItemPicker.tsx rename to apps/web/src/routes/admin/_components/RewardItemPicker.tsx index d4fad84d..0f9af165 100644 --- a/src/react-app/routes/admin/_components/RewardItemPicker.tsx +++ b/apps/web/src/routes/admin/_components/RewardItemPicker.tsx @@ -3,27 +3,27 @@ import { ItemSlot, } from '@app/components/feature/items/ItemSlot'; import { InkButton, InkInput, InkNotice, InkSelect } from '@app/components/ui'; -import { TALISMAN_SCENARIO_OPTIONS } from '@shared/config/talismanScenarios'; +import { TALISMAN_SCENARIO_OPTIONS } from '@daoyou/shared/config/talismanScenarios'; import { rewardDisplayItem, RewardItemSchema, -} from '@shared/contracts/adminRewards'; -import { DAO_EQUIPMENT_TEMPLATES_V1 } from '@shared/engine/combat-v6/equipment/content'; +} from '@daoyou/shared/contracts/adminRewards'; +import { DAO_EQUIPMENT_TEMPLATES_V1 } from '@daoyou/shared/engine/combat-v6/equipment/content'; import { equipmentRealm, OPEN_EQUIPMENT_LEVELS, -} from '@shared/engine/combat-v6/equipment/realm'; -import { DAO_WEAPONS } from '@shared/engine/combat-v6/equipment/weapons'; -import type { ItemGrant } from '@shared/inventory'; +} from '@daoyou/shared/engine/combat-v6/equipment/realm'; +import { DAO_WEAPONS } from '@daoyou/shared/engine/combat-v6/equipment/weapons'; +import type { ItemGrant } from '@daoyou/shared/inventory'; import { INVENTORY_MATERIAL_TYPES, MATERIAL_TYPE_NAMES, -} from '@shared/items/definitions/materials'; -import { libraryMaterialGrant } from '@shared/items/libraryMaterialGrant'; -import { ITEM_DEFINITIONS } from '@shared/items/registry'; -import { ALCHEMY_PROPERTY_LABELS } from '@shared/lib/alchemyProperties'; -import type { ItemLibraryEntry } from '@shared/lib/itemLibrary'; -import { QUALITY_VALUES } from '@shared/types/constants'; +} from '@daoyou/shared/items/definitions/materials'; +import { libraryMaterialGrant } from '@daoyou/shared/items/libraryMaterialGrant'; +import { ITEM_DEFINITIONS } from '@daoyou/shared/items/registry'; +import { ALCHEMY_PROPERTY_LABELS } from '@daoyou/shared/lib/alchemyProperties'; +import type { ItemLibraryEntry } from '@daoyou/shared/lib/itemLibrary'; +import { QUALITY_VALUES } from '@daoyou/shared/types/constants'; import { useEffect, useState } from 'react'; import { AdminDialog } from './AdminDialog'; diff --git a/src/react-app/routes/admin/_components/RewardSelectionEditor.helpers.ts b/apps/web/src/routes/admin/_components/RewardSelectionEditor.helpers.ts similarity index 90% rename from src/react-app/routes/admin/_components/RewardSelectionEditor.helpers.ts rename to apps/web/src/routes/admin/_components/RewardSelectionEditor.helpers.ts index 86bfe28a..7cd9b04a 100644 --- a/src/react-app/routes/admin/_components/RewardSelectionEditor.helpers.ts +++ b/apps/web/src/routes/admin/_components/RewardSelectionEditor.helpers.ts @@ -1,5 +1,5 @@ -import { RewardSelectionsSchema } from '@shared/contracts/adminRewards'; -import type { ItemGrant } from '@shared/inventory'; +import { RewardSelectionsSchema } from '@daoyou/shared/contracts/adminRewards'; +import type { ItemGrant } from '@daoyou/shared/inventory'; export type RewardSelectionDraft = | { type: 'spirit_stones' | 'reputation'; quantity: string } diff --git a/src/react-app/routes/admin/_components/RewardSelectionEditor.tsx b/apps/web/src/routes/admin/_components/RewardSelectionEditor.tsx similarity index 98% rename from src/react-app/routes/admin/_components/RewardSelectionEditor.tsx rename to apps/web/src/routes/admin/_components/RewardSelectionEditor.tsx index c781ad46..ebb12769 100644 --- a/src/react-app/routes/admin/_components/RewardSelectionEditor.tsx +++ b/apps/web/src/routes/admin/_components/RewardSelectionEditor.tsx @@ -3,7 +3,7 @@ import { ItemSlot, } from '@app/components/feature/items/ItemSlot'; import { InkButton, InkInput } from '@app/components/ui'; -import { rewardDisplayItem } from '@shared/contracts/adminRewards'; +import { rewardDisplayItem } from '@daoyou/shared/contracts/adminRewards'; import { RewardItemPicker } from './RewardItemPicker'; import { createReputationDraft, diff --git a/src/react-app/routes/admin/_config/nav.ts b/apps/web/src/routes/admin/_config/nav.ts similarity index 100% rename from src/react-app/routes/admin/_config/nav.ts rename to apps/web/src/routes/admin/_config/nav.ts diff --git a/src/react-app/routes/admin/accounts/route.tsx b/apps/web/src/routes/admin/accounts/route.tsx similarity index 99% rename from src/react-app/routes/admin/accounts/route.tsx rename to apps/web/src/routes/admin/accounts/route.tsx index 9f2c7576..a66fd89f 100644 --- a/src/react-app/routes/admin/accounts/route.tsx +++ b/apps/web/src/routes/admin/accounts/route.tsx @@ -7,7 +7,7 @@ import type { AdminAccountListItem, AdminAccountListResponse, AdminAccountModerationResponse, -} from '@shared/contracts/adminAccounts'; +} from '@daoyou/shared/contracts/adminAccounts'; import { Fragment, useCallback, diff --git a/src/react-app/routes/admin/announcement/route.tsx b/apps/web/src/routes/admin/announcement/route.tsx similarity index 100% rename from src/react-app/routes/admin/announcement/route.tsx rename to apps/web/src/routes/admin/announcement/route.tsx diff --git a/src/react-app/routes/admin/broadcast/game-mail/SystemMailEditor.tsx b/apps/web/src/routes/admin/broadcast/game-mail/SystemMailEditor.tsx similarity index 98% rename from src/react-app/routes/admin/broadcast/game-mail/SystemMailEditor.tsx rename to apps/web/src/routes/admin/broadcast/game-mail/SystemMailEditor.tsx index 42e0ef05..c802f06a 100644 --- a/src/react-app/routes/admin/broadcast/game-mail/SystemMailEditor.tsx +++ b/apps/web/src/routes/admin/broadcast/game-mail/SystemMailEditor.tsx @@ -6,13 +6,13 @@ import { type MailRealm, type SystemMailCampaign, type SystemMailInput, -} from '@shared/contracts/systemMail'; +} from '@daoyou/shared/contracts/systemMail'; import { SPONSORSHIP_TIER_IDS, SPONSORSHIP_TIER_META, type SponsorshipTierId, -} from '@shared/lib/sponsorship'; -import { REALM_STAGE_VALUES, REALM_VALUES } from '@shared/types/constants'; +} from '@daoyou/shared/lib/sponsorship'; +import { REALM_STAGE_VALUES, REALM_VALUES } from '@daoyou/shared/types/constants'; import { useRef, useState } from 'react'; import { AdminDialog } from '../../_components/AdminDialog'; import { AdminSteps } from '../../_components/AdminPage'; diff --git a/src/react-app/routes/admin/broadcast/game-mail/route.tsx b/apps/web/src/routes/admin/broadcast/game-mail/route.tsx similarity index 99% rename from src/react-app/routes/admin/broadcast/game-mail/route.tsx rename to apps/web/src/routes/admin/broadcast/game-mail/route.tsx index 4f3cdcd5..cc628f8d 100644 --- a/src/react-app/routes/admin/broadcast/game-mail/route.tsx +++ b/apps/web/src/routes/admin/broadcast/game-mail/route.tsx @@ -6,7 +6,7 @@ import { systemMailStatusLabel, type SystemMailCampaign, type SystemMailListItem, -} from '@shared/contracts/systemMail'; +} from '@daoyou/shared/contracts/systemMail'; import { useEffect, useState } from 'react'; import { AdminDialog } from '../../_components/AdminDialog'; import { AdminPageHeader } from '../../_components/AdminPage'; diff --git a/src/react-app/routes/admin/broadcast/game-mail/systemMailUi.ts b/apps/web/src/routes/admin/broadcast/game-mail/systemMailUi.ts similarity index 94% rename from src/react-app/routes/admin/broadcast/game-mail/systemMailUi.ts rename to apps/web/src/routes/admin/broadcast/game-mail/systemMailUi.ts index f0f4f66e..ae94d9d5 100644 --- a/src/react-app/routes/admin/broadcast/game-mail/systemMailUi.ts +++ b/apps/web/src/routes/admin/broadcast/game-mail/systemMailUi.ts @@ -1,4 +1,4 @@ -import type { RewardSelection } from '@shared/contracts/adminRewards'; +import type { RewardSelection } from '@daoyou/shared/contracts/adminRewards'; import type { RewardSelectionDraft } from '../../_components/RewardSelectionEditor.helpers'; export function rewardDrafts( diff --git a/src/react-app/routes/admin/community-qrcode/route.tsx b/apps/web/src/routes/admin/community-qrcode/route.tsx similarity index 100% rename from src/react-app/routes/admin/community-qrcode/route.tsx rename to apps/web/src/routes/admin/community-qrcode/route.tsx diff --git a/src/react-app/routes/admin/feedback/_components/FeedbackTable.tsx b/apps/web/src/routes/admin/feedback/_components/FeedbackTable.tsx similarity index 99% rename from src/react-app/routes/admin/feedback/_components/FeedbackTable.tsx rename to apps/web/src/routes/admin/feedback/_components/FeedbackTable.tsx index 6f827c1f..f8af2323 100644 --- a/src/react-app/routes/admin/feedback/_components/FeedbackTable.tsx +++ b/apps/web/src/routes/admin/feedback/_components/FeedbackTable.tsx @@ -1,6 +1,6 @@ import { InkButton, InkSelect, inkFieldVariants } from '@app/components/ui'; import { useInkUI } from '@app/components/providers/InkUIProvider'; -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import { useEffect, useState } from 'react'; type FeedbackType = 'bug' | 'feature' | 'balance' | 'other'; diff --git a/src/react-app/routes/admin/feedback/route.tsx b/apps/web/src/routes/admin/feedback/route.tsx similarity index 100% rename from src/react-app/routes/admin/feedback/route.tsx rename to apps/web/src/routes/admin/feedback/route.tsx diff --git a/src/react-app/routes/admin/item-library/itemLibraryEditor.helpers.ts b/apps/web/src/routes/admin/item-library/itemLibraryEditor.helpers.ts similarity index 95% rename from src/react-app/routes/admin/item-library/itemLibraryEditor.helpers.ts rename to apps/web/src/routes/admin/item-library/itemLibraryEditor.helpers.ts index bca74505..dfc078ff 100644 --- a/src/react-app/routes/admin/item-library/itemLibraryEditor.helpers.ts +++ b/apps/web/src/routes/admin/item-library/itemLibraryEditor.helpers.ts @@ -1,12 +1,12 @@ import type { CreateItemLibraryEntry, ItemLibraryEntry, -} from '@shared/lib/itemLibrary'; +} from '@daoyou/shared/lib/itemLibrary'; import type { ElementType, MaterialType, Quality, -} from '@shared/types/constants'; +} from '@daoyou/shared/types/constants'; export interface ItemLibraryDraft { rowId: string; diff --git a/src/react-app/routes/admin/item-library/route.tsx b/apps/web/src/routes/admin/item-library/route.tsx similarity index 98% rename from src/react-app/routes/admin/item-library/route.tsx rename to apps/web/src/routes/admin/item-library/route.tsx index a528bea9..52f25461 100644 --- a/src/react-app/routes/admin/item-library/route.tsx +++ b/apps/web/src/routes/admin/item-library/route.tsx @@ -4,25 +4,25 @@ import { } from '@app/components/feature/items/ItemSlot'; import { useInkUI } from '@app/components/providers/InkUIProvider'; import { InkButton, InkInput, InkNotice, InkSelect } from '@app/components/ui'; -import { rewardDisplayItem } from '@shared/contracts/adminRewards'; +import { rewardDisplayItem } from '@daoyou/shared/contracts/adminRewards'; import { INVENTORY_MATERIAL_TYPES, MATERIAL_TYPE_NAMES, -} from '@shared/items/definitions/materials'; -import { libraryMaterialGrant } from '@shared/items/libraryMaterialGrant'; +} from '@daoyou/shared/items/definitions/materials'; +import { libraryMaterialGrant } from '@daoyou/shared/items/libraryMaterialGrant'; import { DEFAULT_ITEM_LIBRARY_DAILY_MATERIAL_GENERATION_SETTINGS, type ItemLibraryDailyMaterialGenerationSettings, -} from '@shared/lib/constants/appSettings'; +} from '@daoyou/shared/lib/constants/appSettings'; import type { CreateItemLibraryEntry, ItemLibraryEntry, -} from '@shared/lib/itemLibrary'; +} from '@daoyou/shared/lib/itemLibrary'; import { ELEMENT_VALUES, MATERIAL_TYPE_VALUES, QUALITY_VALUES, -} from '@shared/types/constants'; +} from '@daoyou/shared/types/constants'; import { useCallback, useEffect, useState } from 'react'; import { AdminDialog } from '../_components/AdminDialog'; import { AdminPageHeader } from '../_components/AdminPage'; diff --git a/src/react-app/routes/admin/layout.tsx b/apps/web/src/routes/admin/layout.tsx similarity index 100% rename from src/react-app/routes/admin/layout.tsx rename to apps/web/src/routes/admin/layout.tsx diff --git a/src/react-app/routes/admin/llm-metrics/route.tsx b/apps/web/src/routes/admin/llm-metrics/route.tsx similarity index 100% rename from src/react-app/routes/admin/llm-metrics/route.tsx rename to apps/web/src/routes/admin/llm-metrics/route.tsx diff --git a/src/react-app/routes/admin/manual-migration/route.tsx b/apps/web/src/routes/admin/manual-migration/route.tsx similarity index 98% rename from src/react-app/routes/admin/manual-migration/route.tsx rename to apps/web/src/routes/admin/manual-migration/route.tsx index 31ec86b0..4fa9205e 100644 --- a/src/react-app/routes/admin/manual-migration/route.tsx +++ b/apps/web/src/routes/admin/manual-migration/route.tsx @@ -1,6 +1,6 @@ import { combatV6Request } from '@app/components/feature/combat-v6/request'; import { InkButton } from '@app/components/ui/InkButton'; -import type { ManualMigrationAdminView } from '@shared/contracts/manualMigration'; +import type { ManualMigrationAdminView } from '@daoyou/shared/contracts/manualMigration'; import { useEffect, useRef, useState } from 'react'; import { AdminPageHeader } from '../_components/AdminPage'; const endpoint = '/api/admin/manual-migration'; diff --git a/src/react-app/routes/admin/online-users/route.tsx b/apps/web/src/routes/admin/online-users/route.tsx similarity index 98% rename from src/react-app/routes/admin/online-users/route.tsx rename to apps/web/src/routes/admin/online-users/route.tsx index 76513c2b..259b2ec1 100644 --- a/src/react-app/routes/admin/online-users/route.tsx +++ b/apps/web/src/routes/admin/online-users/route.tsx @@ -3,7 +3,7 @@ import { InkButton } from '@app/components/ui'; import type { AdminOnlineUsersResponse, AdminOnlineUsersSnapshot, -} from '@shared/contracts/adminOnlineUsers'; +} from '@daoyou/shared/contracts/adminOnlineUsers'; import { useCallback, useEffect, useRef, useState } from 'react'; const REFRESH_INTERVAL_MS = 30_000; diff --git a/src/react-app/routes/admin/redeem-codes/_components/RedeemCodeCreateForm.tsx b/apps/web/src/routes/admin/redeem-codes/_components/RedeemCodeCreateForm.tsx similarity index 100% rename from src/react-app/routes/admin/redeem-codes/_components/RedeemCodeCreateForm.tsx rename to apps/web/src/routes/admin/redeem-codes/_components/RedeemCodeCreateForm.tsx diff --git a/src/react-app/routes/admin/redeem-codes/_components/RedeemCodesTable.tsx b/apps/web/src/routes/admin/redeem-codes/_components/RedeemCodesTable.tsx similarity index 99% rename from src/react-app/routes/admin/redeem-codes/_components/RedeemCodesTable.tsx rename to apps/web/src/routes/admin/redeem-codes/_components/RedeemCodesTable.tsx index 50cd130d..aeb2668f 100644 --- a/src/react-app/routes/admin/redeem-codes/_components/RedeemCodesTable.tsx +++ b/apps/web/src/routes/admin/redeem-codes/_components/RedeemCodesTable.tsx @@ -1,6 +1,6 @@ import { useInkUI } from '@app/components/providers/InkUIProvider'; import { InkButton, InkInput, InkNotice, InkSelect } from '@app/components/ui'; -import { MailAttachmentsSchema } from '@shared/lib/itemLibrary'; +import { MailAttachmentsSchema } from '@daoyou/shared/lib/itemLibrary'; import { useEffect, useState } from 'react'; import { AdminDialog } from '../../_components/AdminDialog'; import { RewardSelectionPreview } from '../../_components/RewardSelectionEditor'; diff --git a/src/react-app/routes/admin/redeem-codes/new/route.tsx b/apps/web/src/routes/admin/redeem-codes/new/route.tsx similarity index 100% rename from src/react-app/routes/admin/redeem-codes/new/route.tsx rename to apps/web/src/routes/admin/redeem-codes/new/route.tsx diff --git a/src/react-app/routes/admin/redeem-codes/route.tsx b/apps/web/src/routes/admin/redeem-codes/route.tsx similarity index 100% rename from src/react-app/routes/admin/redeem-codes/route.tsx rename to apps/web/src/routes/admin/redeem-codes/route.tsx diff --git a/src/react-app/routes/admin/reputation-shop/route.tsx b/apps/web/src/routes/admin/reputation-shop/route.tsx similarity index 100% rename from src/react-app/routes/admin/reputation-shop/route.tsx rename to apps/web/src/routes/admin/reputation-shop/route.tsx diff --git a/src/react-app/routes/admin/route.tsx b/apps/web/src/routes/admin/route.tsx similarity index 100% rename from src/react-app/routes/admin/route.tsx rename to apps/web/src/routes/admin/route.tsx diff --git a/src/react-app/routes/admin/sect-shop/route.tsx b/apps/web/src/routes/admin/sect-shop/route.tsx similarity index 100% rename from src/react-app/routes/admin/sect-shop/route.tsx rename to apps/web/src/routes/admin/sect-shop/route.tsx diff --git a/src/react-app/routes/admin/sponsorship/route.tsx b/apps/web/src/routes/admin/sponsorship/route.tsx similarity index 99% rename from src/react-app/routes/admin/sponsorship/route.tsx rename to apps/web/src/routes/admin/sponsorship/route.tsx index 97aee9bc..b81fd383 100644 --- a/src/react-app/routes/admin/sponsorship/route.tsx +++ b/apps/web/src/routes/admin/sponsorship/route.tsx @@ -3,7 +3,7 @@ import { SPONSORSHIP_TIER_IDS, SPONSORSHIP_TIER_META, type SponsorshipTierId, -} from '@shared/lib/sponsorship'; +} from '@daoyou/shared/lib/sponsorship'; import { useCallback, useEffect, useState } from 'react'; type TierConfig = { planId: string; minimumAmountFen: number }; diff --git a/src/react-app/routes/admin/tower-enemy-sets/route.tsx b/apps/web/src/routes/admin/tower-enemy-sets/route.tsx similarity index 98% rename from src/react-app/routes/admin/tower-enemy-sets/route.tsx rename to apps/web/src/routes/admin/tower-enemy-sets/route.tsx index e1367ae2..1202b42c 100644 --- a/src/react-app/routes/admin/tower-enemy-sets/route.tsx +++ b/apps/web/src/routes/admin/tower-enemy-sets/route.tsx @@ -2,11 +2,11 @@ import { GameIcon } from '@app/components/ui/GameIcon'; import { InkButton } from '@app/components/ui/InkButton'; import { InkNotice } from '@app/components/ui/InkNotice'; import { InkSelect } from '@app/components/ui/InkSelect'; -import type { AdminTowerView } from '@shared/contracts/adminTower'; +import type { AdminTowerView } from '@daoyou/shared/contracts/adminTower'; import { TOWER_ELIGIBLE_REALMS, TOWER_MIN_REALM, -} from '@shared/lib/tower/helpers'; +} from '@daoyou/shared/lib/tower/helpers'; import { useEffect, useState } from 'react'; const kindLabels = { normal: '普通', elite: '精英', boss: '首领' }; diff --git a/src/react-app/routes/auth/layout.tsx b/apps/web/src/routes/auth/layout.tsx similarity index 100% rename from src/react-app/routes/auth/layout.tsx rename to apps/web/src/routes/auth/layout.tsx diff --git a/src/react-app/routes/battle-replay/route.tsx b/apps/web/src/routes/battle-replay/route.tsx similarity index 100% rename from src/react-app/routes/battle-replay/route.tsx rename to apps/web/src/routes/battle-replay/route.tsx diff --git a/src/react-app/routes/combat-replay/route.tsx b/apps/web/src/routes/combat-replay/route.tsx similarity index 95% rename from src/react-app/routes/combat-replay/route.tsx rename to apps/web/src/routes/combat-replay/route.tsx index 15be7776..a46f8791 100644 --- a/src/react-app/routes/combat-replay/route.tsx +++ b/apps/web/src/routes/combat-replay/route.tsx @@ -1,7 +1,7 @@ import { CombatV6Page } from '@app/components/feature/combat-v6/CombatV6Page'; import { CombatV6ReplayPlayer } from '@app/components/feature/combat-v6/CombatV6ReplayPlayer'; import { combatV6Request } from '@app/components/feature/combat-v6/request'; -import type { CombatV6ReplayView } from '@shared/combat-v6/replay'; +import type { CombatV6ReplayView } from '@daoyou/shared/combat-v6/replay'; import { useEffect, useState } from 'react'; import { Link, useParams } from 'react-router'; diff --git a/src/react-app/routes/forgot-password/route.tsx b/apps/web/src/routes/forgot-password/route.tsx similarity index 100% rename from src/react-app/routes/forgot-password/route.tsx rename to apps/web/src/routes/forgot-password/route.tsx diff --git a/src/react-app/routes/game/arena/route.tsx b/apps/web/src/routes/game/arena/route.tsx similarity index 99% rename from src/react-app/routes/game/arena/route.tsx rename to apps/web/src/routes/game/arena/route.tsx index 5c454981..044cac4d 100644 --- a/src/react-app/routes/game/arena/route.tsx +++ b/apps/web/src/routes/game/arena/route.tsx @@ -12,13 +12,13 @@ import type { ArenaRoomV1, ArenaStartResponseV1, ArenaTeamIdV1, -} from '@shared/contracts/arena'; +} from '@daoyou/shared/contracts/arena'; import { ARENA_ROOM_MAX_SEATS_PER_TEAM, allArenaSeatsReady, hasBothArenaTeams, isArenaRoomActive, -} from '@shared/contracts/arena'; +} from '@daoyou/shared/contracts/arena'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useNavigate } from 'react-router'; diff --git a/src/react-app/routes/game/artifact-migration/route.tsx b/apps/web/src/routes/game/artifact-migration/route.tsx similarity index 97% rename from src/react-app/routes/game/artifact-migration/route.tsx rename to apps/web/src/routes/game/artifact-migration/route.tsx index fb8d7b09..f77886a3 100644 --- a/src/react-app/routes/game/artifact-migration/route.tsx +++ b/apps/web/src/routes/game/artifact-migration/route.tsx @@ -12,21 +12,21 @@ import type { ArtifactMigrationResult, ArtifactMigrationSource, ArtifactMigrationView, -} from '@shared/contracts/artifactMigration'; +} from '@daoyou/shared/contracts/artifactMigration'; import { DAO_EQUIPMENT_SLOTS, type DaoEquipmentSlot, -} from '@shared/engine/combat-v6/equipment/types'; +} from '@daoyou/shared/engine/combat-v6/equipment/types'; import { DAO_WEAPONS, DAO_WEAPON_TYPES, type DaoWeaponType, -} from '@shared/engine/combat-v6/equipment/weapons'; -import type { ItemGrant } from '@shared/inventory'; +} from '@daoyou/shared/engine/combat-v6/equipment/weapons'; +import type { ItemGrant } from '@daoyou/shared/inventory'; import { BLUEPRINTS, EQUIPMENT_SLOT_NAMES, -} from '@shared/items/definitions/equipment-blueprints'; +} from '@daoyou/shared/items/definitions/equipment-blueprints'; import { useCallback, useEffect, useRef, useState } from 'react'; const endpoint = '/api/artifact-migration'; diff --git a/src/react-app/routes/game/auction/route.tsx b/apps/web/src/routes/game/auction/route.tsx similarity index 97% rename from src/react-app/routes/game/auction/route.tsx rename to apps/web/src/routes/game/auction/route.tsx index 8dfcefc6..42eaa9c8 100644 --- a/src/react-app/routes/game/auction/route.tsx +++ b/apps/web/src/routes/game/auction/route.tsx @@ -27,20 +27,20 @@ import { AUCTION_MAX_PURCHASE_QUANTITY, AUCTION_MAX_TRANSACTION_TOTAL, calculateAuctionSettlement, -} from '@shared/config/auctionConfig'; -import { getRealmStageLevel } from '@shared/config/realmProgression'; +} from '@daoyou/shared/config/auctionConfig'; +import { getRealmStageLevel } from '@daoyou/shared/config/realmProgression'; import { AUCTION_ITEM_TYPES, AUCTION_TYPE_NAMES, type AuctionAssetType, type AuctionItemType, type AuctionListingView, -} from '@shared/contracts/auction'; -import { BEAST_SPECIES } from '@shared/engine/combat-v6/beasts/content'; -import { canDeployBeast } from '@shared/engine/combat-v6/beasts/projection'; -import { EQUIPMENT_SLOT_NAMES } from '@shared/items/definitions/equipment-blueprints'; -import { MATERIAL_TYPE_NAMES } from '@shared/items/definitions/materials'; -import { QUALITY_VALUES } from '@shared/types/constants'; +} from '@daoyou/shared/contracts/auction'; +import { BEAST_SPECIES } from '@daoyou/shared/engine/combat-v6/beasts/content'; +import { canDeployBeast } from '@daoyou/shared/engine/combat-v6/beasts/projection'; +import { EQUIPMENT_SLOT_NAMES } from '@daoyou/shared/items/definitions/equipment-blueprints'; +import { MATERIAL_TYPE_NAMES } from '@daoyou/shared/items/definitions/materials'; +import { QUALITY_VALUES } from '@daoyou/shared/types/constants'; import { useEffect, useRef, useState } from 'react'; import { useSearchParams } from 'react-router'; diff --git a/src/react-app/routes/game/battle/challenge/route.tsx b/apps/web/src/routes/game/battle/challenge/route.tsx similarity index 95% rename from src/react-app/routes/game/battle/challenge/route.tsx rename to apps/web/src/routes/game/battle/challenge/route.tsx index 696521e1..4688aabc 100644 --- a/src/react-app/routes/game/battle/challenge/route.tsx +++ b/apps/web/src/routes/game/battle/challenge/route.tsx @@ -5,13 +5,13 @@ import { mutationBody, } from '@app/components/feature/combat-v6/request'; import { usePlayerSession } from '@app/lib/resources/player'; -import { MAX_DAILY_RANKING_CHALLENGES } from '@shared/combat-v6/ranking'; -import type { CombatV6ReplayView } from '@shared/combat-v6/replay'; +import { MAX_DAILY_RANKING_CHALLENGES } from '@daoyou/shared/combat-v6/ranking'; +import type { CombatV6ReplayView } from '@daoyou/shared/combat-v6/replay'; import { RankingChallengeSchema, type RankingChallengeRequest, type RankingChallengeResult, -} from '@shared/contracts/combatV6Ranking'; +} from '@daoyou/shared/contracts/combatV6Ranking'; import { useEffect, useState } from 'react'; import { Link, useSearchParams } from 'react-router'; diff --git a/src/react-app/routes/game/battle/detail/route.tsx b/apps/web/src/routes/game/battle/detail/route.tsx similarity index 97% rename from src/react-app/routes/game/battle/detail/route.tsx rename to apps/web/src/routes/game/battle/detail/route.tsx index 9ee41066..4a8ecad8 100644 --- a/src/react-app/routes/game/battle/detail/route.tsx +++ b/apps/web/src/routes/game/battle/detail/route.tsx @@ -2,7 +2,7 @@ import { CombatV6Page } from '@app/components/feature/combat-v6/CombatV6Page'; import { CombatV6ReplayPlayer } from '@app/components/feature/combat-v6/CombatV6ReplayPlayer'; import { combatV6Request } from '@app/components/feature/combat-v6/request'; import { usePlayerSession } from '@app/lib/resources/player'; -import type { CombatV6ReplayView } from '@shared/combat-v6/replay'; +import type { CombatV6ReplayView } from '@daoyou/shared/combat-v6/replay'; import { useEffect, useState } from 'react'; import { useParams } from 'react-router'; diff --git a/src/react-app/routes/game/battle/history/route.tsx b/apps/web/src/routes/game/battle/history/route.tsx similarity index 97% rename from src/react-app/routes/game/battle/history/route.tsx rename to apps/web/src/routes/game/battle/history/route.tsx index 75547dc0..9755cb36 100644 --- a/src/react-app/routes/game/battle/history/route.tsx +++ b/apps/web/src/routes/game/battle/history/route.tsx @@ -8,7 +8,7 @@ import { } from '@app/components/game-shell'; import { InkButton, InkNotice } from '@app/components/ui'; import { usePlayerSession } from '@app/lib/resources/player'; -import type { CombatV6HistoryPage } from '@shared/contracts/combatV6Replay'; +import type { CombatV6HistoryPage } from '@daoyou/shared/contracts/combatV6Replay'; import { useEffect, useState } from 'react'; import { useSearchParams } from 'react-router'; diff --git a/src/react-app/routes/game/beast-room/route.tsx b/apps/web/src/routes/game/beast-room/route.tsx similarity index 100% rename from src/react-app/routes/game/beast-room/route.tsx rename to apps/web/src/routes/game/beast-room/route.tsx diff --git a/src/react-app/routes/game/beasts/BeastActionDrawer.tsx b/apps/web/src/routes/game/beasts/BeastActionDrawer.tsx similarity index 92% rename from src/react-app/routes/game/beasts/BeastActionDrawer.tsx rename to apps/web/src/routes/game/beasts/BeastActionDrawer.tsx index 47ed13a1..44e494ba 100644 --- a/src/react-app/routes/game/beasts/BeastActionDrawer.tsx +++ b/apps/web/src/routes/game/beasts/BeastActionDrawer.tsx @@ -1,7 +1,7 @@ import { InkModal } from '@app/components/layout/InkModal'; import { InkButton } from '@app/components/ui/InkButton'; -import type { SummonedBeast } from '@shared/engine/combat-v6/beasts'; -import { beastRestCost } from '@shared/engine/combat-v6/beasts/progression'; +import type { SummonedBeast } from '@daoyou/shared/engine/combat-v6/beasts'; +import { beastRestCost } from '@daoyou/shared/engine/combat-v6/beasts/progression'; export type BeastAction = 'release' | 'rest'; const labels = { release: '放生', rest: '休养' }; diff --git a/src/react-app/routes/game/beasts/BeastBookDrawer.tsx b/apps/web/src/routes/game/beasts/BeastBookDrawer.tsx similarity index 96% rename from src/react-app/routes/game/beasts/BeastBookDrawer.tsx rename to apps/web/src/routes/game/beasts/BeastBookDrawer.tsx index 333d20e1..1b2f3d7a 100644 --- a/src/react-app/routes/game/beasts/BeastBookDrawer.tsx +++ b/apps/web/src/routes/game/beasts/BeastBookDrawer.tsx @@ -17,15 +17,15 @@ import { InkQuantityInput } from '@app/components/ui/InkQuantityInput'; import { useInventoryBag } from '@app/lib/resources/bag'; import { useCraftStorage } from '@app/lib/resources/craftStorage'; import { consumeResourceMutation } from '@app/lib/resources/mutations'; -import { beastSkillPresentation } from '@shared/combat-v6/beast-skill-presentation'; -import type { BeastManagementView } from '@shared/contracts/combatV6Beasts'; -import type { InventoryView } from '@shared/contracts/inventory'; -import { previewBeastFeeding } from '@shared/engine/combat-v6/beasts/feeding'; -import { beastRefinementReason } from '@shared/engine/combat-v6/beasts/refinement'; -import { BEAST_REFINEMENT } from '@shared/engine/combat-v6/beasts/refinement-config'; -import { itemDefinition } from '@shared/inventory'; -import { BEAST_REJUVENATION } from '@shared/items/definitions/beast-rejuvenation'; -import { ConsumableFactsSchema } from '@shared/items/definitions/consumables'; +import { beastSkillPresentation } from '@daoyou/shared/combat-v6/beast-skill-presentation'; +import type { BeastManagementView } from '@daoyou/shared/contracts/combatV6Beasts'; +import type { InventoryView } from '@daoyou/shared/contracts/inventory'; +import { previewBeastFeeding } from '@daoyou/shared/engine/combat-v6/beasts/feeding'; +import { beastRefinementReason } from '@daoyou/shared/engine/combat-v6/beasts/refinement'; +import { BEAST_REFINEMENT } from '@daoyou/shared/engine/combat-v6/beasts/refinement-config'; +import { itemDefinition } from '@daoyou/shared/inventory'; +import { BEAST_REJUVENATION } from '@daoyou/shared/items/definitions/beast-rejuvenation'; +import { ConsumableFactsSchema } from '@daoyou/shared/items/definitions/consumables'; import { useEffect, useRef, useState } from 'react'; export function BeastBookDrawer({ diff --git a/src/react-app/routes/game/beasts/BeastPanel.tsx b/apps/web/src/routes/game/beasts/BeastPanel.tsx similarity index 97% rename from src/react-app/routes/game/beasts/BeastPanel.tsx rename to apps/web/src/routes/game/beasts/BeastPanel.tsx index 3340adc5..8bdf1430 100644 --- a/src/react-app/routes/game/beasts/BeastPanel.tsx +++ b/apps/web/src/routes/game/beasts/BeastPanel.tsx @@ -7,20 +7,20 @@ import { InkBadge } from '@app/components/ui/InkBadge'; import { InkButton } from '@app/components/ui/InkButton'; import { InkTag } from '@app/components/ui/InkTag'; import { InkTooltip } from '@app/components/ui/InkTooltip'; -import { getLevelRealmStage } from '@shared/config/realmProgression'; +import { getLevelRealmStage } from '@daoyou/shared/config/realmProgression'; import { BEAST_SPECIES, beastPanel, canDeployBeast, type SummonedBeast, -} from '@shared/engine/combat-v6/beasts'; -import { BEAST_PROGRESSION } from '@shared/engine/combat-v6/beasts/content'; +} from '@daoyou/shared/engine/combat-v6/beasts'; +import { BEAST_PROGRESSION } from '@daoyou/shared/engine/combat-v6/beasts/content'; import { allocateBeast, BEAST_ATTRIBUTE_NAMES, nextBeastExp, -} from '@shared/engine/combat-v6/beasts/progression'; -import { beastAttributes } from '@shared/engine/combat-v6/beasts/projection'; +} from '@daoyou/shared/engine/combat-v6/beasts/progression'; +import { beastAttributes } from '@daoyou/shared/engine/combat-v6/beasts/projection'; import { useState } from 'react'; import type { BeastAction } from './BeastActionDrawer'; diff --git a/src/react-app/routes/game/beasts/BeastRenameModal.tsx b/apps/web/src/routes/game/beasts/BeastRenameModal.tsx similarity index 97% rename from src/react-app/routes/game/beasts/BeastRenameModal.tsx rename to apps/web/src/routes/game/beasts/BeastRenameModal.tsx index e410fe83..a1f0676d 100644 --- a/src/react-app/routes/game/beasts/BeastRenameModal.tsx +++ b/apps/web/src/routes/game/beasts/BeastRenameModal.tsx @@ -4,7 +4,7 @@ import { InkInput } from '@app/components/ui/InkInput'; import { BEAST_NAME_MAX_LENGTH, BeastNameSchema, -} from '@shared/contracts/combatV6Beasts'; +} from '@daoyou/shared/contracts/combatV6Beasts'; import { useState } from 'react'; export function BeastRenameModal({ diff --git a/src/react-app/routes/game/beasts/BeastRosterScroll.tsx b/apps/web/src/routes/game/beasts/BeastRosterScroll.tsx similarity index 100% rename from src/react-app/routes/game/beasts/BeastRosterScroll.tsx rename to apps/web/src/routes/game/beasts/BeastRosterScroll.tsx diff --git a/src/react-app/routes/game/beasts/codex/route.tsx b/apps/web/src/routes/game/beasts/codex/route.tsx similarity index 96% rename from src/react-app/routes/game/beasts/codex/route.tsx rename to apps/web/src/routes/game/beasts/codex/route.tsx index 1605081e..de21ee7a 100644 --- a/src/react-app/routes/game/beasts/codex/route.tsx +++ b/apps/web/src/routes/game/beasts/codex/route.tsx @@ -4,14 +4,14 @@ import { GameSceneFrame } from '@app/components/game-shell/GameSceneFrame'; import { GameSceneTabs } from '@app/components/game-shell/GameSceneTabs'; import { InkButton } from '@app/components/ui/InkButton'; import { InkTooltip } from '@app/components/ui/InkTooltip'; -import { getLevelRealmStage } from '@shared/config/realmProgression'; +import { getLevelRealmStage } from '@daoyou/shared/config/realmProgression'; import { listBeastCodex, type BeastCodexEntry, type BeastCodexHabitat, -} from '@shared/engine/combat-v6/beasts/codex'; -import { getAtlasRegion } from '@shared/lib/game/mapAtlas'; -import { getMapNode } from '@shared/lib/game/mapSystem'; +} from '@daoyou/shared/engine/combat-v6/beasts/codex'; +import { getAtlasRegion } from '@daoyou/shared/lib/game/mapAtlas'; +import { getMapNode } from '@daoyou/shared/lib/game/mapSystem'; import { useMemo, useState } from 'react'; import { useNavigate, useSearchParams } from 'react-router'; import { BeastRosterScroll } from '../BeastRosterScroll'; diff --git a/src/react-app/routes/game/beasts/fusion/FusionBeastPanel.tsx b/apps/web/src/routes/game/beasts/fusion/FusionBeastPanel.tsx similarity index 98% rename from src/react-app/routes/game/beasts/fusion/FusionBeastPanel.tsx rename to apps/web/src/routes/game/beasts/fusion/FusionBeastPanel.tsx index 607f50fb..608258cd 100644 --- a/src/react-app/routes/game/beasts/fusion/FusionBeastPanel.tsx +++ b/apps/web/src/routes/game/beasts/fusion/FusionBeastPanel.tsx @@ -2,7 +2,7 @@ import { BeastIcon } from '@app/components/feature/beasts/BeastIcon'; import { BeastSkillTile } from '@app/components/feature/beasts/BeastSkillTile'; import { InkButton } from '@app/components/ui/InkButton'; import { InkTag } from '@app/components/ui/InkTag'; -import type { SummonedBeast } from '@shared/engine/combat-v6/beasts'; +import type { SummonedBeast } from '@daoyou/shared/engine/combat-v6/beasts'; export function FusionIdentityTag({ beast }: { beast: SummonedBeast }) { return ( diff --git a/src/react-app/routes/game/beasts/fusion/FusionPreview.tsx b/apps/web/src/routes/game/beasts/fusion/FusionPreview.tsx similarity index 96% rename from src/react-app/routes/game/beasts/fusion/FusionPreview.tsx rename to apps/web/src/routes/game/beasts/fusion/FusionPreview.tsx index a798d70b..f2100a5d 100644 --- a/src/react-app/routes/game/beasts/fusion/FusionPreview.tsx +++ b/apps/web/src/routes/game/beasts/fusion/FusionPreview.tsx @@ -4,8 +4,8 @@ import { InkDetailDrawer } from '@app/components/ui/InkDetailDrawer'; import { BEAST_SPECIES, type SummonedBeast, -} from '@shared/engine/combat-v6/beasts'; -import { fusionPreview } from '@shared/engine/combat-v6/beasts/fusion'; +} from '@daoyou/shared/engine/combat-v6/beasts'; +import { fusionPreview } from '@daoyou/shared/engine/combat-v6/beasts/fusion'; export function FusionPreview({ first, diff --git a/src/react-app/routes/game/beasts/fusion/route.tsx b/apps/web/src/routes/game/beasts/fusion/route.tsx similarity index 98% rename from src/react-app/routes/game/beasts/fusion/route.tsx rename to apps/web/src/routes/game/beasts/fusion/route.tsx index 60957d33..a84d93aa 100644 --- a/src/react-app/routes/game/beasts/fusion/route.tsx +++ b/apps/web/src/routes/game/beasts/fusion/route.tsx @@ -15,13 +15,13 @@ import { type BeastFusionRequest, type BeastFusionResponse, type BeastManagementView, -} from '@shared/contracts/combatV6Beasts'; -import type { SummonedBeast } from '@shared/engine/combat-v6/beasts'; +} from '@daoyou/shared/contracts/combatV6Beasts'; +import type { SummonedBeast } from '@daoyou/shared/engine/combat-v6/beasts'; import { beastFusionMaterialReason, beastFusionReason, -} from '@shared/engine/combat-v6/beasts/fusion'; -import { BEAST_FUSION } from '@shared/engine/combat-v6/beasts/fusion-config'; +} from '@daoyou/shared/engine/combat-v6/beasts/fusion'; +import { BEAST_FUSION } from '@daoyou/shared/engine/combat-v6/beasts/fusion-config'; import { useEffect, useRef, useState } from 'react'; import { useNavigate } from 'react-router'; import { FusionBeastPanel, FusionIdentityTag } from './FusionBeastPanel'; diff --git a/src/react-app/routes/game/beasts/route.tsx b/apps/web/src/routes/game/beasts/route.tsx similarity index 98% rename from src/react-app/routes/game/beasts/route.tsx rename to apps/web/src/routes/game/beasts/route.tsx index 35b5b2a0..bf3d0d9b 100644 --- a/src/react-app/routes/game/beasts/route.tsx +++ b/apps/web/src/routes/game/beasts/route.tsx @@ -10,10 +10,10 @@ import { useInkUI } from '@app/components/providers/InkUIProvider'; import { InkBadge } from '@app/components/ui/InkBadge'; import { InkButton } from '@app/components/ui/InkButton'; import { InkDetailDrawer } from '@app/components/ui/InkDetailDrawer'; -import type { BeastManagementView } from '@shared/contracts/combatV6Beasts'; -import { BEAST_STARTER_SPECIES } from '@shared/engine/combat-v6/beasts'; -import { BEAST_GENERATION } from '@shared/engine/combat-v6/beasts/content'; -import { BEAST_CAPACITY } from '@shared/engine/combat-v6/beasts/progression'; +import type { BeastManagementView } from '@daoyou/shared/contracts/combatV6Beasts'; +import { BEAST_STARTER_SPECIES } from '@daoyou/shared/engine/combat-v6/beasts'; +import { BEAST_GENERATION } from '@daoyou/shared/engine/combat-v6/beasts/content'; +import { BEAST_CAPACITY } from '@daoyou/shared/engine/combat-v6/beasts/progression'; import { useEffect, useRef, useState } from 'react'; import { useSearchParams } from 'react-router'; import { BeastActionDrawer, type BeastAction } from './BeastActionDrawer'; diff --git a/src/react-app/routes/game/black-market/BlackMarketConversation.tsx b/apps/web/src/routes/game/black-market/BlackMarketConversation.tsx similarity index 98% rename from src/react-app/routes/game/black-market/BlackMarketConversation.tsx rename to apps/web/src/routes/game/black-market/BlackMarketConversation.tsx index e6fd7a72..00702ef7 100644 --- a/src/react-app/routes/game/black-market/BlackMarketConversation.tsx +++ b/apps/web/src/routes/game/black-market/BlackMarketConversation.tsx @@ -6,12 +6,12 @@ import { type InkDialogState, InkNotice, } from '@app/components/ui'; -import { normalizeBlackMarketPlayerBody } from '@shared/lib/blackMarketMessages'; -import { getGameConceptInfo } from '@shared/lib/gameConceptDisplay'; +import { normalizeBlackMarketPlayerBody } from '@daoyou/shared/lib/blackMarketMessages'; +import { getGameConceptInfo } from '@daoyou/shared/lib/gameConceptDisplay'; import type { BlackMarketNpcSummary, BlackMarketSessionView, -} from '@shared/types/blackMarket'; +} from '@daoyou/shared/types/blackMarket'; import { useState } from 'react'; const SPIRIT_STONES = getGameConceptInfo('spirit_stones'); diff --git a/src/react-app/routes/game/black-market/BlackMarketEntryPreview.tsx b/apps/web/src/routes/game/black-market/BlackMarketEntryPreview.tsx similarity index 98% rename from src/react-app/routes/game/black-market/BlackMarketEntryPreview.tsx rename to apps/web/src/routes/game/black-market/BlackMarketEntryPreview.tsx index 1893638f..fea90866 100644 --- a/src/react-app/routes/game/black-market/BlackMarketEntryPreview.tsx +++ b/apps/web/src/routes/game/black-market/BlackMarketEntryPreview.tsx @@ -3,7 +3,7 @@ import { InkButton, InkNotice } from '@app/components/ui'; import type { BlackMarketNpcSummary, BlackMarketNpcStatus, -} from '@shared/types/blackMarket'; +} from '@daoyou/shared/types/blackMarket'; const OPENING_STEPS = [ '你掀帘走近摊位……', diff --git a/src/react-app/routes/game/black-market/BlackMarketReveal.tsx b/apps/web/src/routes/game/black-market/BlackMarketReveal.tsx similarity index 96% rename from src/react-app/routes/game/black-market/BlackMarketReveal.tsx rename to apps/web/src/routes/game/black-market/BlackMarketReveal.tsx index a60a9206..a0e268c9 100644 --- a/src/react-app/routes/game/black-market/BlackMarketReveal.tsx +++ b/apps/web/src/routes/game/black-market/BlackMarketReveal.tsx @@ -3,8 +3,8 @@ import { InkButton, InkIdentifyCelebration, } from '@app/components/ui'; -import { getMaterialTypeInfo } from '@shared/lib/gameConceptDisplay'; -import type { BlackMarketReveal } from '@shared/types/blackMarket'; +import { getMaterialTypeInfo } from '@daoyou/shared/lib/gameConceptDisplay'; +import type { BlackMarketReveal } from '@daoyou/shared/types/blackMarket'; const ratingTone: Record = { 血亏: 'text-crimson', diff --git a/src/react-app/routes/game/black-market/BlackMarketRoom.tsx b/apps/web/src/routes/game/black-market/BlackMarketRoom.tsx similarity index 97% rename from src/react-app/routes/game/black-market/BlackMarketRoom.tsx rename to apps/web/src/routes/game/black-market/BlackMarketRoom.tsx index d5c02c75..ed88be02 100644 --- a/src/react-app/routes/game/black-market/BlackMarketRoom.tsx +++ b/apps/web/src/routes/game/black-market/BlackMarketRoom.tsx @@ -2,7 +2,7 @@ import { RoomView } from '@app/components/feature/room'; import type { BlackMarketNpcId, BlackMarketOverview, -} from '@shared/types/blackMarket'; +} from '@daoyou/shared/types/blackMarket'; import type { ReactNode } from 'react'; export function BlackMarketRoom({ diff --git a/src/react-app/routes/game/black-market/blackMarketApi.ts b/apps/web/src/routes/game/black-market/blackMarketApi.ts similarity index 98% rename from src/react-app/routes/game/black-market/blackMarketApi.ts rename to apps/web/src/routes/game/black-market/blackMarketApi.ts index 025576d2..9c79afdd 100644 --- a/src/react-app/routes/game/black-market/blackMarketApi.ts +++ b/apps/web/src/routes/game/black-market/blackMarketApi.ts @@ -5,7 +5,7 @@ import type { BlackMarketNpcId, BlackMarketOverview, BlackMarketSessionView, -} from '@shared/types/blackMarket'; +} from '@daoyou/shared/types/blackMarket'; async function readJson(response: Response): Promise { const payload = await response.json(); diff --git a/src/react-app/routes/game/black-market/route.tsx b/apps/web/src/routes/game/black-market/route.tsx similarity index 99% rename from src/react-app/routes/game/black-market/route.tsx rename to apps/web/src/routes/game/black-market/route.tsx index fbcc20ba..5e126119 100644 --- a/src/react-app/routes/game/black-market/route.tsx +++ b/apps/web/src/routes/game/black-market/route.tsx @@ -16,7 +16,7 @@ import type { BlackMarketOpenResult, BlackMarketOverview, BlackMarketSessionView, -} from '@shared/types/blackMarket'; +} from '@daoyou/shared/types/blackMarket'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useSearchParams } from 'react-router'; import { BlackMarketConversation } from './BlackMarketConversation'; diff --git a/src/react-app/routes/game/body-cultivation/breakthrough/route.tsx b/apps/web/src/routes/game/body-cultivation/breakthrough/route.tsx similarity index 94% rename from src/react-app/routes/game/body-cultivation/breakthrough/route.tsx rename to apps/web/src/routes/game/body-cultivation/breakthrough/route.tsx index 4354868e..cd7d8167 100644 --- a/src/react-app/routes/game/body-cultivation/breakthrough/route.tsx +++ b/apps/web/src/routes/game/body-cultivation/breakthrough/route.tsx @@ -16,10 +16,10 @@ import { useCultivatorCondition, useCultivatorIdentity, } from '@app/lib/resources/player'; -import type { BodyCultivationBreakthroughResultData } from '@shared/contracts/bodyCultivation'; -import { previewBodyCultivationRealmBreakthrough } from '@shared/lib/bodyCultivation/breakthrough'; -import { BODY_REALM_LABELS } from '@shared/lib/bodyCultivation/config'; -import { cn } from '@shared/lib/cn'; +import type { BodyCultivationBreakthroughResultData } from '@daoyou/shared/contracts/bodyCultivation'; +import { previewBodyCultivationRealmBreakthrough } from '@daoyou/shared/lib/bodyCultivation/breakthrough'; +import { BODY_REALM_LABELS } from '@daoyou/shared/lib/bodyCultivation/config'; +import { cn } from '@daoyou/shared/lib/cn'; import { useState } from 'react'; import { useNavigate } from 'react-router'; diff --git a/src/react-app/routes/game/body-cultivation/route.tsx b/apps/web/src/routes/game/body-cultivation/route.tsx similarity index 100% rename from src/react-app/routes/game/body-cultivation/route.tsx rename to apps/web/src/routes/game/body-cultivation/route.tsx diff --git a/src/react-app/routes/game/combat-v6/arena/route.tsx b/apps/web/src/routes/game/combat-v6/arena/route.tsx similarity index 97% rename from src/react-app/routes/game/combat-v6/arena/route.tsx rename to apps/web/src/routes/game/combat-v6/arena/route.tsx index 1a8115de..94292eac 100644 --- a/src/react-app/routes/game/combat-v6/arena/route.tsx +++ b/apps/web/src/routes/game/combat-v6/arena/route.tsx @@ -2,7 +2,7 @@ import { CombatV6Battle } from '@app/components/feature/combat-v6/CombatV6Battle import { CombatV6Page } from '@app/components/feature/combat-v6/CombatV6Page'; import { useArenaV6Session } from '@app/components/feature/combat-v6/useArenaV6Session'; import { HuntResult } from '@app/components/feature/hunts/HuntResult'; -import { HUNT_BOSSES, huntMapHref } from '@shared/hunts/config'; +import { HUNT_BOSSES, huntMapHref } from '@daoyou/shared/hunts/config'; import { useState } from 'react'; import { useNavigate, useParams, useSearchParams } from 'react-router'; diff --git a/src/react-app/routes/game/community/route.tsx b/apps/web/src/routes/game/community/route.tsx similarity index 100% rename from src/react-app/routes/game/community/route.tsx rename to apps/web/src/routes/game/community/route.tsx diff --git a/src/react-app/routes/game/components/HomeView.tsx b/apps/web/src/routes/game/components/HomeView.tsx similarity index 97% rename from src/react-app/routes/game/components/HomeView.tsx rename to apps/web/src/routes/game/components/HomeView.tsx index 1f8b0a84..3cdab592 100644 --- a/src/react-app/routes/game/components/HomeView.tsx +++ b/apps/web/src/routes/game/components/HomeView.tsx @@ -13,13 +13,13 @@ import { useTaskList } from '@app/lib/hooks/useTaskList'; import { useCultivatorProgress } from '@app/lib/resources/player'; import { useStory } from '@app/lib/story/useStory'; import { findCurrentMajorBreakthroughTask } from '@app/lib/tasks/taskClient'; -import { getBodyCultivationSummary } from '@shared/lib/bodyCultivation/summary'; -import { getNextMajorRealm } from '@shared/lib/breakthroughPill'; +import { getBodyCultivationSummary } from '@daoyou/shared/lib/bodyCultivation/summary'; +import { getNextMajorRealm } from '@daoyou/shared/lib/breakthroughPill'; import { getPillToxicityStage, isConditionStatusActive, -} from '@shared/lib/condition'; -import { getConditionStatusTemplate } from '@shared/lib/conditionStatusRegistry'; +} from '@daoyou/shared/lib/condition'; +import { getConditionStatusTemplate } from '@daoyou/shared/lib/conditionStatusRegistry'; import { useEffect, useMemo, useState, type ReactNode } from 'react'; function calculateYieldHours(lastYieldAt: Date | string | undefined) { diff --git a/src/react-app/routes/game/craft/alchemy/route.tsx b/apps/web/src/routes/game/craft/alchemy/route.tsx similarity index 100% rename from src/react-app/routes/game/craft/alchemy/route.tsx rename to apps/web/src/routes/game/craft/alchemy/route.tsx diff --git a/src/react-app/routes/game/craft/refine/route.tsx b/apps/web/src/routes/game/craft/refine/route.tsx similarity index 100% rename from src/react-app/routes/game/craft/refine/route.tsx rename to apps/web/src/routes/game/craft/refine/route.tsx diff --git a/src/react-app/routes/game/create/route.tsx b/apps/web/src/routes/game/create/route.tsx similarity index 99% rename from src/react-app/routes/game/create/route.tsx rename to apps/web/src/routes/game/create/route.tsx index 2c4d8180..a820d7d2 100644 --- a/src/react-app/routes/game/create/route.tsx +++ b/apps/web/src/routes/game/create/route.tsx @@ -25,19 +25,19 @@ import { type CharacterGenerationQuota, type CharacterGenerationQuotaResponse, type GenerateCharacterResponse, -} from '@shared/contracts/character-generation'; -import { cn } from '@shared/lib/cn'; +} from '@daoyou/shared/contracts/character-generation'; +import { cn } from '@daoyou/shared/lib/cn'; import { characterDisplayRows, formatCharacterAttributeValue as formatAttributeValue, formatCharacterAttributeModifier as formatModifier, projectCharacterDisplay, -} from '@shared/lib/cultivatorDisplay'; +} from '@daoyou/shared/lib/cultivatorDisplay'; import { getGameConceptIcon, getResourceLabel, -} from '@shared/lib/gameConceptDisplay'; -import type { Cultivator } from '@shared/types/cultivator'; +} from '@daoyou/shared/lib/gameConceptDisplay'; +import type { Cultivator } from '@daoyou/shared/types/cultivator'; import { useEffect, useMemo, useState } from 'react'; import { useNavigate } from 'react-router'; diff --git a/src/react-app/routes/game/cultivator/attributes/route.tsx b/apps/web/src/routes/game/cultivator/attributes/route.tsx similarity index 100% rename from src/react-app/routes/game/cultivator/attributes/route.tsx rename to apps/web/src/routes/game/cultivator/attributes/route.tsx diff --git a/src/react-app/routes/game/cultivator/route.tsx b/apps/web/src/routes/game/cultivator/route.tsx similarity index 100% rename from src/react-app/routes/game/cultivator/route.tsx rename to apps/web/src/routes/game/cultivator/route.tsx diff --git a/src/react-app/routes/game/divination/DivinationPhaserRuntime.ts b/apps/web/src/routes/game/divination/DivinationPhaserRuntime.ts similarity index 98% rename from src/react-app/routes/game/divination/DivinationPhaserRuntime.ts rename to apps/web/src/routes/game/divination/DivinationPhaserRuntime.ts index bf95e05b..f24d7fe6 100644 --- a/src/react-app/routes/game/divination/DivinationPhaserRuntime.ts +++ b/apps/web/src/routes/game/divination/DivinationPhaserRuntime.ts @@ -1,4 +1,4 @@ -import type { DivinationDice } from '@shared/lib/divination'; +import type { DivinationDice } from '@daoyou/shared/lib/divination'; import * as Phaser from 'phaser'; export interface DivinationController { diff --git a/src/react-app/routes/game/divination/route.tsx b/apps/web/src/routes/game/divination/route.tsx similarity index 99% rename from src/react-app/routes/game/divination/route.tsx rename to apps/web/src/routes/game/divination/route.tsx index e1fdaf0c..98b9b014 100644 --- a/src/react-app/routes/game/divination/route.tsx +++ b/apps/web/src/routes/game/divination/route.tsx @@ -11,11 +11,11 @@ import { usePlayerSession } from '@app/lib/resources/player'; import type { DivinationRecord, DivinationView, -} from '@shared/contracts/divination'; +} from '@daoyou/shared/contracts/divination'; import { DIVINATION_DIRECTIONS, type DivinationDirection, -} from '@shared/lib/divination'; +} from '@daoyou/shared/lib/divination'; import { useCallback, useEffect, diff --git a/src/react-app/routes/game/dungeon/MapNodeCard.tsx b/apps/web/src/routes/game/dungeon/MapNodeCard.tsx similarity index 93% rename from src/react-app/routes/game/dungeon/MapNodeCard.tsx rename to apps/web/src/routes/game/dungeon/MapNodeCard.tsx index 7614ffe3..4aec9d2e 100644 --- a/src/react-app/routes/game/dungeon/MapNodeCard.tsx +++ b/apps/web/src/routes/game/dungeon/MapNodeCard.tsx @@ -7,9 +7,9 @@ import { canChallengeDungeonRealm, resolveDungeonMapConfig, type MapNodeInfo, -} from '@shared/lib/game/mapSystem'; -import { cn } from '@shared/lib/cn'; -import type { RealmType } from '@shared/types/constants'; +} from '@daoyou/shared/lib/game/mapSystem'; +import { cn } from '@daoyou/shared/lib/cn'; +import type { RealmType } from '@daoyou/shared/types/constants'; export function MapNodeCard({ node, diff --git a/src/react-app/routes/game/dungeon/components/BattlePreparation.tsx b/apps/web/src/routes/game/dungeon/components/BattlePreparation.tsx similarity index 95% rename from src/react-app/routes/game/dungeon/components/BattlePreparation.tsx rename to apps/web/src/routes/game/dungeon/components/BattlePreparation.tsx index 943faa0e..772094a4 100644 --- a/src/react-app/routes/game/dungeon/components/BattlePreparation.tsx +++ b/apps/web/src/routes/game/dungeon/components/BattlePreparation.tsx @@ -1,5 +1,5 @@ import { InkButton } from '@app/components/ui/InkButton'; -import type { DungeonEncounterView } from '@shared/contracts/combatV6Dungeon'; +import type { DungeonEncounterView } from '@daoyou/shared/contracts/combatV6Dungeon'; export function BattlePreparation({ encounter, diff --git a/src/react-app/routes/game/dungeon/components/DungeonBattle.tsx b/apps/web/src/routes/game/dungeon/components/DungeonBattle.tsx similarity index 89% rename from src/react-app/routes/game/dungeon/components/DungeonBattle.tsx rename to apps/web/src/routes/game/dungeon/components/DungeonBattle.tsx index 94c0e596..bad40a57 100644 --- a/src/react-app/routes/game/dungeon/components/DungeonBattle.tsx +++ b/apps/web/src/routes/game/dungeon/components/DungeonBattle.tsx @@ -3,14 +3,14 @@ import { CombatV6Page } from '@app/components/feature/combat-v6/CombatV6Page'; import { useCombatV6Session } from '@app/components/feature/combat-v6/useCombatV6Session'; import { InkButton } from '@app/components/ui/InkButton'; import { consumeResourceMutation } from '@app/lib/resources/mutations'; -import type { DungeonSessionView } from '@shared/contracts/combatV6Dungeon'; -import type { ResourceOperation } from '@shared/engine/resource/types'; +import type { DungeonSessionView } from '@daoyou/shared/contracts/combatV6Dungeon'; +import type { ResourceOperation } from '@daoyou/shared/engine/resource/types'; import type { DungeonRound, DungeonSettlement, DungeonState, -} from '@shared/lib/dungeon/types'; -import type { Cultivator } from '@shared/types/cultivator'; +} from '@daoyou/shared/lib/dungeon/types'; +import type { Cultivator } from '@daoyou/shared/types/cultivator'; export interface BattleCallbackData { isFinished: boolean; diff --git a/src/react-app/routes/game/dungeon/components/DungeonExploring.tsx b/apps/web/src/routes/game/dungeon/components/DungeonExploring.tsx similarity index 96% rename from src/react-app/routes/game/dungeon/components/DungeonExploring.tsx rename to apps/web/src/routes/game/dungeon/components/DungeonExploring.tsx index c8c06eff..62636325 100644 --- a/src/react-app/routes/game/dungeon/components/DungeonExploring.tsx +++ b/apps/web/src/routes/game/dungeon/components/DungeonExploring.tsx @@ -8,15 +8,15 @@ import { formatDungeonCostName, formatDungeonCostValue, } from '@app/lib/dungeon/formatDungeonCost'; -import type { DungeonMaterialSelection } from '@shared/contracts/combatV6Dungeon'; +import type { DungeonMaterialSelection } from '@daoyou/shared/contracts/combatV6Dungeon'; import type { DungeonOption, DungeonOptionCost, DungeonRound, DungeonState, -} from '@shared/lib/dungeon/types'; -import { getResourceIcon } from '@shared/lib/gameConceptDisplay'; -import type { Cultivator } from '@shared/types/cultivator'; +} from '@daoyou/shared/lib/dungeon/types'; +import { getResourceIcon } from '@daoyou/shared/lib/gameConceptDisplay'; +import type { Cultivator } from '@daoyou/shared/types/cultivator'; import { useState } from 'react'; import { DungeonMaterialSubmission } from './DungeonMaterialSubmission'; import type { DungeonDisplayResources } from './DungeonRunPanel'; diff --git a/src/react-app/routes/game/dungeon/components/DungeonLooting.tsx b/apps/web/src/routes/game/dungeon/components/DungeonLooting.tsx similarity index 95% rename from src/react-app/routes/game/dungeon/components/DungeonLooting.tsx rename to apps/web/src/routes/game/dungeon/components/DungeonLooting.tsx index 2510e541..0bf8bd64 100644 --- a/src/react-app/routes/game/dungeon/components/DungeonLooting.tsx +++ b/apps/web/src/routes/game/dungeon/components/DungeonLooting.tsx @@ -1,8 +1,8 @@ import { InkSection } from '@app/components/layout'; import { InkButton } from '@app/components/ui/InkButton'; import { InkCard } from '@app/components/ui/InkCard'; -import type { DungeonState } from '@shared/lib/dungeon/types'; -import type { Cultivator } from '@shared/types/cultivator'; +import type { DungeonState } from '@daoyou/shared/lib/dungeon/types'; +import type { Cultivator } from '@daoyou/shared/types/cultivator'; import type { DungeonDisplayResources } from './DungeonRunPanel'; import { DungeonRunPanel } from './DungeonRunPanel'; diff --git a/src/react-app/routes/game/dungeon/components/DungeonMapSelector.tsx b/apps/web/src/routes/game/dungeon/components/DungeonMapSelector.tsx similarity index 91% rename from src/react-app/routes/game/dungeon/components/DungeonMapSelector.tsx rename to apps/web/src/routes/game/dungeon/components/DungeonMapSelector.tsx index 4e27be20..38601638 100644 --- a/src/react-app/routes/game/dungeon/components/DungeonMapSelector.tsx +++ b/apps/web/src/routes/game/dungeon/components/DungeonMapSelector.tsx @@ -1,9 +1,9 @@ import { InkButton } from '@app/components/ui/InkButton'; import { InkCard } from '@app/components/ui/InkCard'; import { InkNotice } from '@app/components/ui/InkNotice'; -import { dungeonReadiness } from '@shared/lib/dungeon/readiness'; -import { MapNodeInfo } from '@shared/lib/game/mapSystem'; -import type { RealmType } from '@shared/types/constants'; +import { dungeonReadiness } from '@daoyou/shared/lib/dungeon/readiness'; +import { MapNodeInfo } from '@daoyou/shared/lib/game/mapSystem'; +import type { RealmType } from '@daoyou/shared/types/constants'; import { MapNodeCard } from '../MapNodeCard'; type NoviceDungeonReadiness = ReturnType; diff --git a/src/react-app/routes/game/dungeon/components/DungeonMaterialSubmission.tsx b/apps/web/src/routes/game/dungeon/components/DungeonMaterialSubmission.tsx similarity index 96% rename from src/react-app/routes/game/dungeon/components/DungeonMaterialSubmission.tsx rename to apps/web/src/routes/game/dungeon/components/DungeonMaterialSubmission.tsx index 51258cfb..446f5f0a 100644 --- a/src/react-app/routes/game/dungeon/components/DungeonMaterialSubmission.tsx +++ b/apps/web/src/routes/game/dungeon/components/DungeonMaterialSubmission.tsx @@ -8,13 +8,13 @@ import { formatDungeonCostValue, } from '@app/lib/dungeon/formatDungeonCost'; import { useInventoryBag } from '@app/lib/resources/bag'; -import type { DungeonMaterialSelection } from '@shared/contracts/combatV6Dungeon'; -import type { InventoryView } from '@shared/contracts/inventory'; +import type { DungeonMaterialSelection } from '@daoyou/shared/contracts/combatV6Dungeon'; +import type { InventoryView } from '@daoyou/shared/contracts/inventory'; import { consumeDungeonMaterials, dungeonMaterialMatches, -} from '@shared/lib/dungeon/materialCosts'; -import type { DungeonOption } from '@shared/lib/dungeon/types'; +} from '@daoyou/shared/lib/dungeon/materialCosts'; +import type { DungeonOption } from '@daoyou/shared/lib/dungeon/types'; import { useState } from 'react'; export function DungeonMaterialSubmission({ diff --git a/src/react-app/routes/game/dungeon/components/DungeonRunPanel.tsx b/apps/web/src/routes/game/dungeon/components/DungeonRunPanel.tsx similarity index 93% rename from src/react-app/routes/game/dungeon/components/DungeonRunPanel.tsx rename to apps/web/src/routes/game/dungeon/components/DungeonRunPanel.tsx index 741508ab..ea87137c 100644 --- a/src/react-app/routes/game/dungeon/components/DungeonRunPanel.tsx +++ b/apps/web/src/routes/game/dungeon/components/DungeonRunPanel.tsx @@ -6,12 +6,12 @@ import { InkDetailDrawer } from '@app/components/ui/InkDetailDrawer'; import { useInventoryBag } from '@app/lib/resources/bag'; import { useResourceMutation } from '@app/lib/resources/mutations'; import { useCultivatorCondition } from '@app/lib/resources/player'; -import type { InventoryView } from '@shared/contracts/inventory'; -import { ConsumableFactsSchema } from '@shared/items/definitions/consumables'; -import { canUseDungeonRecoveryPill } from '@shared/lib/dungeon/rest'; -import type { DungeonState } from '@shared/lib/dungeon/types'; -import { dungeonRewardItemName } from '@shared/rewards/dungeon'; -import type { Cultivator } from '@shared/types/cultivator'; +import type { InventoryView } from '@daoyou/shared/contracts/inventory'; +import { ConsumableFactsSchema } from '@daoyou/shared/items/definitions/consumables'; +import { canUseDungeonRecoveryPill } from '@daoyou/shared/lib/dungeon/rest'; +import type { DungeonState } from '@daoyou/shared/lib/dungeon/types'; +import { dungeonRewardItemName } from '@daoyou/shared/rewards/dungeon'; +import type { Cultivator } from '@daoyou/shared/types/cultivator'; import { useRef, useState } from 'react'; export interface DungeonDisplayResources { diff --git a/src/react-app/routes/game/dungeon/components/DungeonSettlement.tsx b/apps/web/src/routes/game/dungeon/components/DungeonSettlement.tsx similarity index 94% rename from src/react-app/routes/game/dungeon/components/DungeonSettlement.tsx rename to apps/web/src/routes/game/dungeon/components/DungeonSettlement.tsx index 3f8856a1..7873f6bb 100644 --- a/src/react-app/routes/game/dungeon/components/DungeonSettlement.tsx +++ b/apps/web/src/routes/game/dungeon/components/DungeonSettlement.tsx @@ -3,10 +3,10 @@ import type { DisplayItem } from '@app/components/feature/items/itemPresentation import { InkButton } from '@app/components/ui/InkButton'; import { InkCard } from '@app/components/ui/InkCard'; import { InkTag } from '@app/components/ui/InkTag'; -import type { ResourceOperation } from '@shared/engine/resource/types'; -import type { DungeonSettlement as DungeonSettlementType } from '@shared/lib/dungeon/types'; -import { getResourceTypeInfo } from '@shared/lib/gameConceptDisplay'; -import { dungeonRewardItemName } from '@shared/rewards/dungeon'; +import type { ResourceOperation } from '@daoyou/shared/engine/resource/types'; +import type { DungeonSettlement as DungeonSettlementType } from '@daoyou/shared/lib/dungeon/types'; +import { getResourceTypeInfo } from '@daoyou/shared/lib/gameConceptDisplay'; +import { dungeonRewardItemName } from '@daoyou/shared/rewards/dungeon'; interface DungeonSettlementProps { settlement: DungeonSettlementType | undefined; diff --git a/src/react-app/routes/game/dungeon/components/DungeonViewRenderer.tsx b/apps/web/src/routes/game/dungeon/components/DungeonViewRenderer.tsx similarity index 96% rename from src/react-app/routes/game/dungeon/components/DungeonViewRenderer.tsx rename to apps/web/src/routes/game/dungeon/components/DungeonViewRenderer.tsx index 7b29e376..cf30d1cb 100644 --- a/src/react-app/routes/game/dungeon/components/DungeonViewRenderer.tsx +++ b/apps/web/src/routes/game/dungeon/components/DungeonViewRenderer.tsx @@ -4,21 +4,21 @@ import { InkButton } from '@app/components/ui/InkButton'; import { InkCard } from '@app/components/ui/InkCard'; import { InkNotice } from '@app/components/ui/InkNotice'; import { DungeonViewState } from '@app/lib/hooks/dungeon/useDungeonViewModel'; -import type { DungeonMaterialSelection } from '@shared/contracts/combatV6Dungeon'; -import { isConditionStatusActive } from '@shared/lib/condition'; -import { getConditionStatusTemplate } from '@shared/lib/conditionStatusRegistry'; -import { dungeonReadiness } from '@shared/lib/dungeon/readiness'; +import type { DungeonMaterialSelection } from '@daoyou/shared/contracts/combatV6Dungeon'; +import { isConditionStatusActive } from '@daoyou/shared/lib/condition'; +import { getConditionStatusTemplate } from '@daoyou/shared/lib/conditionStatusRegistry'; +import { dungeonReadiness } from '@daoyou/shared/lib/dungeon/readiness'; import type { DungeonOption, DungeonRecoverAction, DungeonState, -} from '@shared/lib/dungeon/types'; +} from '@daoyou/shared/lib/dungeon/types'; import { canChallengeDungeonRealm, getMapNode, -} from '@shared/lib/game/mapSystem'; -import type { Cultivator } from '@shared/types/cultivator'; -import type { TaskInstance } from '@shared/types/task'; +} from '@daoyou/shared/lib/game/mapSystem'; +import type { Cultivator } from '@daoyou/shared/types/cultivator'; +import type { TaskInstance } from '@daoyou/shared/types/task'; import { DungeonSceneScreen } from '../dungeonScene'; import { resolveDungeonSceneDescriptor, diff --git a/src/react-app/routes/game/dungeon/dungeonScene.tsx b/apps/web/src/routes/game/dungeon/dungeonScene.tsx similarity index 98% rename from src/react-app/routes/game/dungeon/dungeonScene.tsx rename to apps/web/src/routes/game/dungeon/dungeonScene.tsx index de6a8bcc..a27cabf0 100644 --- a/src/react-app/routes/game/dungeon/dungeonScene.tsx +++ b/apps/web/src/routes/game/dungeon/dungeonScene.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import { useMemo, useState, type CSSProperties, type ReactNode } from 'react'; import { DungeonSceneContext, diff --git a/src/react-app/routes/game/dungeon/dungeonSceneContext.ts b/apps/web/src/routes/game/dungeon/dungeonSceneContext.ts similarity index 100% rename from src/react-app/routes/game/dungeon/dungeonSceneContext.ts rename to apps/web/src/routes/game/dungeon/dungeonSceneContext.ts diff --git a/src/react-app/routes/game/dungeon/dungeonSceneRegistry.ts b/apps/web/src/routes/game/dungeon/dungeonSceneRegistry.ts similarity index 100% rename from src/react-app/routes/game/dungeon/dungeonSceneRegistry.ts rename to apps/web/src/routes/game/dungeon/dungeonSceneRegistry.ts diff --git a/src/react-app/routes/game/dungeon/history/route.tsx b/apps/web/src/routes/game/dungeon/history/route.tsx similarity index 99% rename from src/react-app/routes/game/dungeon/history/route.tsx rename to apps/web/src/routes/game/dungeon/history/route.tsx index d18ebbc7..cf5ad7a9 100644 --- a/src/react-app/routes/game/dungeon/history/route.tsx +++ b/apps/web/src/routes/game/dungeon/history/route.tsx @@ -8,7 +8,7 @@ import { InkButton } from '@app/components/ui/InkButton'; import { InkCard } from '@app/components/ui/InkCard'; import { InkDetailDrawer } from '@app/components/ui/InkDetailDrawer'; import { InkList, InkListItem } from '@app/components/ui/InkList'; -import { getResourceTypeLabel } from '@shared/lib/gameConceptDisplay'; +import { getResourceTypeLabel } from '@daoyou/shared/lib/gameConceptDisplay'; import { useEffect, useState } from 'react'; /** diff --git a/src/react-app/routes/game/dungeon/route.tsx b/apps/web/src/routes/game/dungeon/route.tsx similarity index 100% rename from src/react-app/routes/game/dungeon/route.tsx rename to apps/web/src/routes/game/dungeon/route.tsx diff --git a/src/react-app/routes/game/enlightenment/gongfa/route.tsx b/apps/web/src/routes/game/enlightenment/gongfa/route.tsx similarity index 100% rename from src/react-app/routes/game/enlightenment/gongfa/route.tsx rename to apps/web/src/routes/game/enlightenment/gongfa/route.tsx diff --git a/src/react-app/routes/game/enlightenment/replace/route.tsx b/apps/web/src/routes/game/enlightenment/replace/route.tsx similarity index 100% rename from src/react-app/routes/game/enlightenment/replace/route.tsx rename to apps/web/src/routes/game/enlightenment/replace/route.tsx diff --git a/src/react-app/routes/game/enlightenment/route.tsx b/apps/web/src/routes/game/enlightenment/route.tsx similarity index 100% rename from src/react-app/routes/game/enlightenment/route.tsx rename to apps/web/src/routes/game/enlightenment/route.tsx diff --git a/src/react-app/routes/game/enlightenment/skill/route.tsx b/apps/web/src/routes/game/enlightenment/skill/route.tsx similarity index 100% rename from src/react-app/routes/game/enlightenment/skill/route.tsx rename to apps/web/src/routes/game/enlightenment/skill/route.tsx diff --git a/src/react-app/routes/game/fate-reshape/route.tsx b/apps/web/src/routes/game/fate-reshape/route.tsx similarity index 99% rename from src/react-app/routes/game/fate-reshape/route.tsx rename to apps/web/src/routes/game/fate-reshape/route.tsx index 28d8d726..219b056a 100644 --- a/src/react-app/routes/game/fate-reshape/route.tsx +++ b/apps/web/src/routes/game/fate-reshape/route.tsx @@ -23,8 +23,8 @@ import { useCultivatorIdentity, usePlayerSession, } from '@app/lib/resources/player'; -import type { PreHeavenFate } from '@shared/types/cultivator'; -import type { FateReshapeSessionDTO } from '@shared/types/fateReshape'; +import type { PreHeavenFate } from '@daoyou/shared/types/cultivator'; +import type { FateReshapeSessionDTO } from '@daoyou/shared/types/fateReshape'; import { useCallback, useEffect, useMemo, useState } from 'react'; type SessionResponse = { diff --git a/src/react-app/routes/game/identity-reshape/route.tsx b/apps/web/src/routes/game/identity-reshape/route.tsx similarity index 99% rename from src/react-app/routes/game/identity-reshape/route.tsx rename to apps/web/src/routes/game/identity-reshape/route.tsx index 58907ce0..b1af0715 100644 --- a/src/react-app/routes/game/identity-reshape/route.tsx +++ b/apps/web/src/routes/game/identity-reshape/route.tsx @@ -9,12 +9,12 @@ import { IDENTITY_RESHAPE_DESCRIPTION_MAX_LENGTH, IDENTITY_RESHAPE_DESCRIPTION_MIN_LENGTH, IDENTITY_RESHAPE_TALISMAN_NAME, -} from '@shared/config/identityReshape'; -import type { PlayerResourceMutationMeta } from '@shared/contracts/player'; +} from '@daoyou/shared/config/identityReshape'; +import type { PlayerResourceMutationMeta } from '@daoyou/shared/contracts/player'; import type { IdentityReshapeAnswer, IdentityReshapeSessionDTO, -} from '@shared/types/identityReshape'; +} from '@daoyou/shared/types/identityReshape'; import { useCallback, useEffect, diff --git a/src/react-app/routes/game/inn/route.tsx b/apps/web/src/routes/game/inn/route.tsx similarity index 98% rename from src/react-app/routes/game/inn/route.tsx rename to apps/web/src/routes/game/inn/route.tsx index d9f16c78..6e34ca81 100644 --- a/src/react-app/routes/game/inn/route.tsx +++ b/apps/web/src/routes/game/inn/route.tsx @@ -14,12 +14,12 @@ import { import { calculateInnRecoveryLossRange, calculateInnRecoverySpiritStoneCost, -} from '@shared/config/innRecovery'; -import { isConditionStatusActive } from '@shared/lib/condition'; +} from '@daoyou/shared/config/innRecovery'; +import { isConditionStatusActive } from '@daoyou/shared/lib/condition'; import { evaluateFateContext, getInnSpiritStoneMultiplier, -} from '@shared/lib/fates'; +} from '@daoyou/shared/lib/fates'; import { useState } from 'react'; type InnRecoveryResponse = { diff --git a/src/react-app/routes/game/inscriptions/route.tsx b/apps/web/src/routes/game/inscriptions/route.tsx similarity index 100% rename from src/react-app/routes/game/inscriptions/route.tsx rename to apps/web/src/routes/game/inscriptions/route.tsx diff --git a/src/react-app/routes/game/inventory/EquipmentRack.tsx b/apps/web/src/routes/game/inventory/EquipmentRack.tsx similarity index 93% rename from src/react-app/routes/game/inventory/EquipmentRack.tsx rename to apps/web/src/routes/game/inventory/EquipmentRack.tsx index 76d120c0..457c287e 100644 --- a/src/react-app/routes/game/inventory/EquipmentRack.tsx +++ b/apps/web/src/routes/game/inventory/EquipmentRack.tsx @@ -2,14 +2,14 @@ import { ItemSlot } from '@app/components/feature/items/ItemSlot'; import { GameIcon } from '@app/components/ui/GameIcon'; import { GameImage } from '@app/components/ui/GameImage'; import { InkButton } from '@app/components/ui/InkButton'; -import type { InventoryView } from '@shared/contracts/inventory'; -import { compileDaoEquipmentSpecialLoadoutV1 } from '@shared/engine/combat-v6/equipment/compiler'; +import type { InventoryView } from '@daoyou/shared/contracts/inventory'; +import { compileDaoEquipmentSpecialLoadoutV1 } from '@daoyou/shared/engine/combat-v6/equipment/compiler'; import type { DaoEquipmentInstanceV1, DaoEquipmentLoadoutV1, DaoEquipmentSlot, -} from '@shared/engine/combat-v6/equipment/types'; -import { EQUIPMENT_SLOT_NAMES } from '@shared/items/definitions/equipment-blueprints'; +} from '@daoyou/shared/engine/combat-v6/equipment/types'; +import { EQUIPMENT_SLOT_NAMES } from '@daoyou/shared/items/definitions/equipment-blueprints'; type Item = InventoryView['items'][number]; const columns: DaoEquipmentSlot[][] = [ diff --git a/src/react-app/routes/game/inventory/InventoryV6.tsx b/apps/web/src/routes/game/inventory/InventoryV6.tsx similarity index 97% rename from src/react-app/routes/game/inventory/InventoryV6.tsx rename to apps/web/src/routes/game/inventory/InventoryV6.tsx index bca723ae..f47261d1 100644 --- a/src/react-app/routes/game/inventory/InventoryV6.tsx +++ b/apps/web/src/routes/game/inventory/InventoryV6.tsx @@ -27,19 +27,19 @@ import { useCultivatorIdentity } from '@app/lib/resources/player'; import type { InventoryAction, InventoryView, -} from '@shared/contracts/inventory'; +} from '@daoyou/shared/contracts/inventory'; import type { DaoEquipmentInstanceV1, DaoEquipmentSlot, -} from '@shared/engine/combat-v6/equipment/types'; -import { combatCharacterLevel } from '@shared/engine/combat-v6/projection/character-level'; -import { BAG_CAPACITY, itemDefinition } from '@shared/inventory'; +} from '@daoyou/shared/engine/combat-v6/equipment/types'; +import { combatCharacterLevel } from '@daoyou/shared/engine/combat-v6/projection/character-level'; +import { BAG_CAPACITY, itemDefinition } from '@daoyou/shared/inventory'; import { sortInventoryItems, type InventorySort, -} from '@shared/inventory/sorting'; -import { ConsumableFactsSchema } from '@shared/items/definitions/consumables'; -import { EQUIPMENT_SLOT_NAMES } from '@shared/items/definitions/equipment-blueprints'; +} from '@daoyou/shared/inventory/sorting'; +import { ConsumableFactsSchema } from '@daoyou/shared/items/definitions/consumables'; +import { EQUIPMENT_SLOT_NAMES } from '@daoyou/shared/items/definitions/equipment-blueprints'; import { useEffect, useRef, useState } from 'react'; import { useLocation, useNavigate, useSearchParams } from 'react-router'; import { EquipmentAction, EquipmentRack } from './EquipmentRack'; diff --git a/src/react-app/routes/game/inventory/components/InventoryView.tsx b/apps/web/src/routes/game/inventory/components/InventoryView.tsx similarity index 98% rename from src/react-app/routes/game/inventory/components/InventoryView.tsx rename to apps/web/src/routes/game/inventory/components/InventoryView.tsx index 67d985d7..a08142e3 100644 --- a/src/react-app/routes/game/inventory/components/InventoryView.tsx +++ b/apps/web/src/routes/game/inventory/components/InventoryView.tsx @@ -5,7 +5,7 @@ import { GameSceneFrame } from '@app/components/game-shell'; import { InkButton } from '@app/components/ui/InkButton'; import { InkDetailDrawer } from '@app/components/ui/InkDetailDrawer'; import { useInventoryBag } from '@app/lib/resources/bag'; -import { BAG_CAPACITY } from '@shared/inventory'; +import { BAG_CAPACITY } from '@daoyou/shared/inventory'; import { useState, useSyncExternalStore } from 'react'; const compactQuery = '(max-width: 767px)'; diff --git a/src/react-app/routes/game/inventory/route.tsx b/apps/web/src/routes/game/inventory/route.tsx similarity index 100% rename from src/react-app/routes/game/inventory/route.tsx rename to apps/web/src/routes/game/inventory/route.tsx diff --git a/src/react-app/routes/game/journal/route.tsx b/apps/web/src/routes/game/journal/route.tsx similarity index 98% rename from src/react-app/routes/game/journal/route.tsx rename to apps/web/src/routes/game/journal/route.tsx index 7a40451b..63a038d6 100644 --- a/src/react-app/routes/game/journal/route.tsx +++ b/apps/web/src/routes/game/journal/route.tsx @@ -10,7 +10,7 @@ import { usePlayerSession } from '@app/lib/resources/player'; import type { JournalCursor, PlayerJournalPage, -} from '@shared/contracts/playerJournal'; +} from '@daoyou/shared/contracts/playerJournal'; import { useEffect, useState } from 'react'; import { useSearchParams } from 'react-router'; diff --git a/src/react-app/routes/game/layout.tsx b/apps/web/src/routes/game/layout.tsx similarity index 100% rename from src/react-app/routes/game/layout.tsx rename to apps/web/src/routes/game/layout.tsx diff --git a/src/react-app/routes/game/mail/route.tsx b/apps/web/src/routes/game/mail/route.tsx similarity index 99% rename from src/react-app/routes/game/mail/route.tsx rename to apps/web/src/routes/game/mail/route.tsx index a7467cb9..7d1deab4 100644 --- a/src/react-app/routes/game/mail/route.tsx +++ b/apps/web/src/routes/game/mail/route.tsx @@ -17,13 +17,13 @@ import { InkNotice } from '@app/components/ui/InkNotice'; import { realtimeClient } from '@app/lib/realtime/realtimeClient'; import { useResourceMutation } from '@app/lib/resources/mutations'; import { usePlayerSession } from '@app/lib/resources/player'; -import { MAX_FRIENDS_PER_CULTIVATOR } from '@shared/config/socialConfig'; +import { MAX_FRIENDS_PER_CULTIVATOR } from '@daoyou/shared/config/socialConfig'; import type { FriendCultivatorSummary, FriendSearchResponse, FriendSearchResult, -} from '@shared/contracts/friends'; -import { mailLocationText } from '@shared/contracts/mail'; +} from '@daoyou/shared/contracts/friends'; +import { mailLocationText } from '@daoyou/shared/contracts/mail'; import { useCallback, useEffect, useRef, useState } from 'react'; import { useSearchParams } from 'react-router'; diff --git a/src/react-app/routes/game/manual-migration/route.tsx b/apps/web/src/routes/game/manual-migration/route.tsx similarity index 98% rename from src/react-app/routes/game/manual-migration/route.tsx rename to apps/web/src/routes/game/manual-migration/route.tsx index be6b5b1c..f9015ab7 100644 --- a/src/react-app/routes/game/manual-migration/route.tsx +++ b/apps/web/src/routes/game/manual-migration/route.tsx @@ -12,9 +12,9 @@ import { usePlayerSession } from '@app/lib/resources/player'; import type { ManualMigrationResult, ManualMigrationView, -} from '@shared/contracts/manualMigration'; -import type { ItemGrant } from '@shared/inventory'; -import type { Quality } from '@shared/types/constants'; +} from '@daoyou/shared/contracts/manualMigration'; +import type { ItemGrant } from '@daoyou/shared/inventory'; +import type { Quality } from '@daoyou/shared/types/constants'; import { useCallback, useEffect, useRef, useState } from 'react'; const endpoint = '/api/manual-migration'; diff --git a/src/react-app/routes/game/map-v2/AtlasMobileToolbar.tsx b/apps/web/src/routes/game/map-v2/AtlasMobileToolbar.tsx similarity index 98% rename from src/react-app/routes/game/map-v2/AtlasMobileToolbar.tsx rename to apps/web/src/routes/game/map-v2/AtlasMobileToolbar.tsx index c720a505..5d0ee859 100644 --- a/src/react-app/routes/game/map-v2/AtlasMobileToolbar.tsx +++ b/apps/web/src/routes/game/map-v2/AtlasMobileToolbar.tsx @@ -1,10 +1,10 @@ import { GameIcon } from '@app/components/ui/GameIcon'; import { InkDetailDrawer } from '@app/components/ui/InkDetailDrawer'; -import { getAtlasRegion } from '@shared/lib/game/mapAtlas'; +import { getAtlasRegion } from '@daoyou/shared/lib/game/mapAtlas'; import { ATLAS_CATEGORY_IDS, getAtlasCategory, -} from '@shared/lib/game/mapAtlasCategories'; +} from '@daoyou/shared/lib/game/mapAtlasCategories'; import { useEffect, useRef, useState } from 'react'; import { ATLAS_CATEGORY_STYLE } from './atlasMarkerStyle'; import type { AtlasToolbarProps } from './AtlasToolbar'; diff --git a/src/react-app/routes/game/map-v2/AtlasNodeKinds.tsx b/apps/web/src/routes/game/map-v2/AtlasNodeKinds.tsx similarity index 74% rename from src/react-app/routes/game/map-v2/AtlasNodeKinds.tsx rename to apps/web/src/routes/game/map-v2/AtlasNodeKinds.tsx index 99e554e0..985b0ceb 100644 --- a/src/react-app/routes/game/map-v2/AtlasNodeKinds.tsx +++ b/apps/web/src/routes/game/map-v2/AtlasNodeKinds.tsx @@ -1,6 +1,6 @@ import { GameIcon } from '@app/components/ui/GameIcon'; -import { getAtlasCategory } from '@shared/lib/game/mapAtlasCategories'; -import type { WorldMapLocation } from '@shared/lib/game/mapSystem'; +import { getAtlasCategory } from '@daoyou/shared/lib/game/mapAtlasCategories'; +import type { WorldMapLocation } from '@daoyou/shared/lib/game/mapSystem'; import { ATLAS_CATEGORY_STYLE } from './atlasMarkerStyle'; export function AtlasNodeKinds({ location }: { location: WorldMapLocation }) { diff --git a/src/react-app/routes/game/map-v2/AtlasNodePanel.tsx b/apps/web/src/routes/game/map-v2/AtlasNodePanel.tsx similarity index 95% rename from src/react-app/routes/game/map-v2/AtlasNodePanel.tsx rename to apps/web/src/routes/game/map-v2/AtlasNodePanel.tsx index c09669b1..599f78c7 100644 --- a/src/react-app/routes/game/map-v2/AtlasNodePanel.tsx +++ b/apps/web/src/routes/game/map-v2/AtlasNodePanel.tsx @@ -1,11 +1,11 @@ import type { MapNodeAction } from '@app/components/feature/map/mapActions'; import { WildNodePreview } from '@app/components/feature/map/WildNodePreview'; import { InkButton } from '@app/components/ui/InkButton'; -import { getAtlasCategory } from '@shared/lib/game/mapAtlasCategories'; +import { getAtlasCategory } from '@daoyou/shared/lib/game/mapAtlasCategories'; import { resolveDungeonMapConfig, type WorldMapLocation, -} from '@shared/lib/game/mapSystem'; +} from '@daoyou/shared/lib/game/mapSystem'; import { AtlasNodeKinds } from './AtlasNodeKinds'; /** One nonmodal panel owns both location information and its single gameplay entry. */ diff --git a/src/react-app/routes/game/map-v2/AtlasPhaserRuntime.ts b/apps/web/src/routes/game/map-v2/AtlasPhaserRuntime.ts similarity index 99% rename from src/react-app/routes/game/map-v2/AtlasPhaserRuntime.ts rename to apps/web/src/routes/game/map-v2/AtlasPhaserRuntime.ts index 8996f48a..9741d76f 100644 --- a/src/react-app/routes/game/map-v2/AtlasPhaserRuntime.ts +++ b/apps/web/src/routes/game/map-v2/AtlasPhaserRuntime.ts @@ -1,4 +1,4 @@ -import type { HuntEvent } from '@shared/hunts/config'; +import type { HuntEvent } from '@daoyou/shared/hunts/config'; import type { HuntMapPoint } from '@app/components/feature/hunts/HuntMapMarkers'; import { GameIcon } from '@app/components/ui/GameIcon'; import { @@ -7,12 +7,12 @@ import { getAtlasLocations, hasAtlasMap, type AtlasPoint, -} from '@shared/lib/game/mapAtlas'; +} from '@daoyou/shared/lib/game/mapAtlas'; import { getAtlasCategory, getAtlasShortName, type AtlasCategory, -} from '@shared/lib/game/mapAtlasCategories'; +} from '@daoyou/shared/lib/game/mapAtlasCategories'; import * as Phaser from 'phaser'; import { ATLAS_CATEGORY_STYLE } from './atlasMarkerStyle'; diff --git a/src/react-app/routes/game/map-v2/AtlasTextMap.tsx b/apps/web/src/routes/game/map-v2/AtlasTextMap.tsx similarity index 96% rename from src/react-app/routes/game/map-v2/AtlasTextMap.tsx rename to apps/web/src/routes/game/map-v2/AtlasTextMap.tsx index d0f94cbe..67b8a552 100644 --- a/src/react-app/routes/game/map-v2/AtlasTextMap.tsx +++ b/apps/web/src/routes/game/map-v2/AtlasTextMap.tsx @@ -1,11 +1,11 @@ import type { MapNodeAction } from '@app/components/feature/map/mapActions'; import { GameIcon } from '@app/components/ui/GameIcon'; -import { ATLAS_REGIONS, hasAtlasMap } from '@shared/lib/game/mapAtlas'; +import { ATLAS_REGIONS, hasAtlasMap } from '@daoyou/shared/lib/game/mapAtlas'; import { ATLAS_CATEGORY_IDS, getAtlasCategory, -} from '@shared/lib/game/mapAtlasCategories'; -import type { WorldMapLocation } from '@shared/lib/game/mapSystem'; +} from '@daoyou/shared/lib/game/mapAtlasCategories'; +import type { WorldMapLocation } from '@daoyou/shared/lib/game/mapSystem'; import { useEffect, useRef } from 'react'; import { AtlasNodePanel } from './AtlasNodePanel'; import { ATLAS_CATEGORY_STYLE } from './atlasMarkerStyle'; diff --git a/src/react-app/routes/game/map-v2/AtlasToolbar.tsx b/apps/web/src/routes/game/map-v2/AtlasToolbar.tsx similarity index 98% rename from src/react-app/routes/game/map-v2/AtlasToolbar.tsx rename to apps/web/src/routes/game/map-v2/AtlasToolbar.tsx index e4578c60..ae6b8ce9 100644 --- a/src/react-app/routes/game/map-v2/AtlasToolbar.tsx +++ b/apps/web/src/routes/game/map-v2/AtlasToolbar.tsx @@ -1,13 +1,13 @@ import { GameIcon } from '@app/components/ui/GameIcon'; import type { GameSettings } from '@app/lib/game-setting'; -import { cn } from '@shared/lib/cn'; -import { getAtlasRegion } from '@shared/lib/game/mapAtlas'; +import { cn } from '@daoyou/shared/lib/cn'; +import { getAtlasRegion } from '@daoyou/shared/lib/game/mapAtlas'; import { ATLAS_CATEGORY_IDS, getAtlasCategory, type AtlasCategory, -} from '@shared/lib/game/mapAtlasCategories'; -import type { WorldMapLocation } from '@shared/lib/game/mapSystem'; +} from '@daoyou/shared/lib/game/mapAtlasCategories'; +import type { WorldMapLocation } from '@daoyou/shared/lib/game/mapSystem'; import { useEffect, useEffectEvent, diff --git a/src/react-app/routes/game/map-v2/atlasMarkerStyle.ts b/apps/web/src/routes/game/map-v2/atlasMarkerStyle.ts similarity index 92% rename from src/react-app/routes/game/map-v2/atlasMarkerStyle.ts rename to apps/web/src/routes/game/map-v2/atlasMarkerStyle.ts index 0591822f..8dcb6317 100644 --- a/src/react-app/routes/game/map-v2/atlasMarkerStyle.ts +++ b/apps/web/src/routes/game/map-v2/atlasMarkerStyle.ts @@ -1,4 +1,4 @@ -import type { AtlasCategory } from '@shared/lib/game/mapAtlasCategories'; +import type { AtlasCategory } from '@daoyou/shared/lib/game/mapAtlasCategories'; export const ATLAS_CATEGORY_STYLE = { wild: { diff --git a/src/react-app/routes/game/map-v2/route.tsx b/apps/web/src/routes/game/map-v2/route.tsx similarity index 98% rename from src/react-app/routes/game/map-v2/route.tsx rename to apps/web/src/routes/game/map-v2/route.tsx index c2ae993b..b667412a 100644 --- a/src/react-app/routes/game/map-v2/route.tsx +++ b/apps/web/src/routes/game/map-v2/route.tsx @@ -4,7 +4,7 @@ import { type HuntMapPoint, } from '@app/components/feature/hunts/HuntMapMarkers'; import { useHunts } from '@app/components/feature/hunts/useHunts'; -import { huntMapHref, type HuntEvent } from '@shared/hunts/config'; +import { huntMapHref, type HuntEvent } from '@daoyou/shared/hunts/config'; import { buildNodeActions, buildSectLandmarkActions, @@ -20,13 +20,13 @@ import { getAtlasLocations, getAtlasRegion, hasAtlasMap, -} from '@shared/lib/game/mapAtlas'; +} from '@daoyou/shared/lib/game/mapAtlas'; import { matchesAtlasCategories, parseAtlasCategories, type AtlasCategory, -} from '@shared/lib/game/mapAtlasCategories'; -import { getWorldMapLocation } from '@shared/lib/game/mapSystem'; +} from '@daoyou/shared/lib/game/mapAtlasCategories'; +import { getWorldMapLocation } from '@daoyou/shared/lib/game/mapSystem'; import { useEffect, useEffectEvent, diff --git a/src/react-app/routes/game/market/recycle/route.tsx b/apps/web/src/routes/game/market/recycle/route.tsx similarity index 98% rename from src/react-app/routes/game/market/recycle/route.tsx rename to apps/web/src/routes/game/market/recycle/route.tsx index 4b9300e3..7db80072 100644 --- a/src/react-app/routes/game/market/recycle/route.tsx +++ b/apps/web/src/routes/game/market/recycle/route.tsx @@ -9,26 +9,26 @@ import { inventoryBagResource, useInventoryBag } from '@app/lib/resources/bag'; import { consumeResourceMutation } from '@app/lib/resources/mutations'; import { usePlayerSession } from '@app/lib/resources/player'; import { resourceStore } from '@app/lib/resources/store'; -import type { InventoryView } from '@shared/contracts/inventory'; +import type { InventoryView } from '@daoyou/shared/contracts/inventory'; import type { RecycleQuote, RecycleResult, RecycleSelection, -} from '@shared/contracts/recycle'; -import { recycleBlockingReason } from '@shared/inventory/recycle'; -import { ConsumableFactsSchema } from '@shared/items/definitions/consumables'; +} from '@daoyou/shared/contracts/recycle'; +import { recycleBlockingReason } from '@daoyou/shared/inventory/recycle'; +import { ConsumableFactsSchema } from '@daoyou/shared/items/definitions/consumables'; import { INVENTORY_MATERIAL_TYPES, MATERIAL_TYPE_NAMES, -} from '@shared/items/definitions/materials'; -import { SeedFactsSchema } from '@shared/items/definitions/seeds'; -import { materialFactsOf } from '@shared/items/material'; -import { findItemDefinition } from '@shared/items/registry'; +} from '@daoyou/shared/items/definitions/materials'; +import { SeedFactsSchema } from '@daoyou/shared/items/definitions/seeds'; +import { materialFactsOf } from '@daoyou/shared/items/material'; +import { findItemDefinition } from '@daoyou/shared/items/registry'; import { QUALITY_ORDER, QUALITY_VALUES, type Quality, -} from '@shared/types/constants'; +} from '@daoyou/shared/types/constants'; import { useEffect, useRef, useState } from 'react'; type Item = InventoryView['items'][number]; diff --git a/src/react-app/routes/game/market/route.tsx b/apps/web/src/routes/game/market/route.tsx similarity index 98% rename from src/react-app/routes/game/market/route.tsx rename to apps/web/src/routes/game/market/route.tsx index 4450be58..cd86764d 100644 --- a/src/react-app/routes/game/market/route.tsx +++ b/apps/web/src/routes/game/market/route.tsx @@ -17,13 +17,13 @@ import { import type { MarketBuyInput, MarketPurchaseResult, -} from '@shared/contracts/market'; +} from '@daoyou/shared/contracts/market'; import { getMarketNodeSwitchOptions, resolveMarketSwitchLayer, -} from '@shared/lib/game/marketConfig'; -import { formatCompactGameNumber } from '@shared/lib/numberFormat'; -import type { MarketAccessState, MarketListing } from '@shared/types/market'; +} from '@daoyou/shared/lib/game/marketConfig'; +import { formatCompactGameNumber } from '@daoyou/shared/lib/numberFormat'; +import type { MarketAccessState, MarketListing } from '@daoyou/shared/types/market'; import { useEffect, useRef, useState } from 'react'; import { useLocation, useSearchParams } from 'react-router'; diff --git a/src/react-app/routes/game/marrow-wash/route.tsx b/apps/web/src/routes/game/marrow-wash/route.tsx similarity index 100% rename from src/react-app/routes/game/marrow-wash/route.tsx rename to apps/web/src/routes/game/marrow-wash/route.tsx diff --git a/src/react-app/routes/game/merit-ledger/components/MeritTierCard.tsx b/apps/web/src/routes/game/merit-ledger/components/MeritTierCard.tsx similarity index 94% rename from src/react-app/routes/game/merit-ledger/components/MeritTierCard.tsx rename to apps/web/src/routes/game/merit-ledger/components/MeritTierCard.tsx index 31ca1a1a..7f1a20ba 100644 --- a/src/react-app/routes/game/merit-ledger/components/MeritTierCard.tsx +++ b/apps/web/src/routes/game/merit-ledger/components/MeritTierCard.tsx @@ -1,7 +1,7 @@ import { MeritStamp } from '@app/components/feature/merit/MeritStamp'; -import { cn } from '@shared/lib/cn'; -import type { SponsorshipTierId } from '@shared/lib/sponsorship'; -import { SPONSORSHIP_TIER_META } from '@shared/lib/sponsorship'; +import { cn } from '@daoyou/shared/lib/cn'; +import type { SponsorshipTierId } from '@daoyou/shared/lib/sponsorship'; +import { SPONSORSHIP_TIER_META } from '@daoyou/shared/lib/sponsorship'; import type { ReactNode } from 'react'; type TierPresentation = { diff --git a/src/react-app/routes/game/merit-ledger/components/MeritWall.tsx b/apps/web/src/routes/game/merit-ledger/components/MeritWall.tsx similarity index 98% rename from src/react-app/routes/game/merit-ledger/components/MeritWall.tsx rename to apps/web/src/routes/game/merit-ledger/components/MeritWall.tsx index 89ea23ff..74802ca7 100644 --- a/src/react-app/routes/game/merit-ledger/components/MeritWall.tsx +++ b/apps/web/src/routes/game/merit-ledger/components/MeritWall.tsx @@ -3,7 +3,7 @@ import { SPONSORSHIP_TIER_IDS, SPONSORSHIP_TIER_META, type SponsorshipTierId, -} from '@shared/lib/sponsorship'; +} from '@daoyou/shared/lib/sponsorship'; export type MeritPublicRow = { cultivatorId: string; diff --git a/src/react-app/routes/game/merit-ledger/route.tsx b/apps/web/src/routes/game/merit-ledger/route.tsx similarity index 99% rename from src/react-app/routes/game/merit-ledger/route.tsx rename to apps/web/src/routes/game/merit-ledger/route.tsx index 666bbc93..cc1802c0 100644 --- a/src/react-app/routes/game/merit-ledger/route.tsx +++ b/apps/web/src/routes/game/merit-ledger/route.tsx @@ -8,7 +8,7 @@ import { SPONSORSHIP_TIER_IDS, SPONSORSHIP_TIER_META, type SponsorshipTierId, -} from '@shared/lib/sponsorship'; +} from '@daoyou/shared/lib/sponsorship'; import { useCallback, useEffect, useRef, useState } from 'react'; import { MeritTierCard } from './components/MeritTierCard'; import { MeritWall, type MeritPublicRow } from './components/MeritWall'; diff --git a/src/react-app/routes/game/rankings/rankingDetailItem.ts b/apps/web/src/routes/game/rankings/rankingDetailItem.ts similarity index 72% rename from src/react-app/routes/game/rankings/rankingDetailItem.ts rename to apps/web/src/routes/game/rankings/rankingDetailItem.ts index 6124e9f3..d146db79 100644 --- a/src/react-app/routes/game/rankings/rankingDetailItem.ts +++ b/apps/web/src/routes/game/rankings/rankingDetailItem.ts @@ -1,7 +1,7 @@ import type { ItemDetailPayload } from '@app/components/feature/items'; -import { assertConsumableSpec } from '@shared/lib/consumables'; -import type { Consumable } from '@shared/types/cultivator'; -import type { ItemRankingEntry } from '@shared/types/rankings'; +import { assertConsumableSpec } from '@daoyou/shared/lib/consumables'; +import type { Consumable } from '@daoyou/shared/types/cultivator'; +import type { ItemRankingEntry } from '@daoyou/shared/types/rankings'; export function toRankingDetailItem(item: ItemRankingEntry): ItemDetailPayload { const consumable: Consumable = { diff --git a/src/react-app/routes/game/rankings/route.tsx b/apps/web/src/routes/game/rankings/route.tsx similarity index 98% rename from src/react-app/routes/game/rankings/route.tsx rename to apps/web/src/routes/game/rankings/route.tsx index 792868b8..d20bc7c7 100644 --- a/src/react-app/routes/game/rankings/route.tsx +++ b/apps/web/src/routes/game/rankings/route.tsx @@ -25,22 +25,22 @@ import { useCultivatorIdentity, usePlayerSession, } from '@app/lib/resources/player'; -import { MAX_DAILY_RANKING_CHALLENGES } from '@shared/combat-v6/ranking'; -import type { RankingChallengeRequest } from '@shared/contracts/combatV6Ranking'; -import type { CultivatorInspectionData } from '@shared/contracts/player'; -import { cn } from '@shared/lib/cn'; -import { getGameConceptInfo } from '@shared/lib/gameConceptDisplay'; +import { MAX_DAILY_RANKING_CHALLENGES } from '@daoyou/shared/combat-v6/ranking'; +import type { RankingChallengeRequest } from '@daoyou/shared/contracts/combatV6Ranking'; +import type { CultivatorInspectionData } from '@daoyou/shared/contracts/player'; +import { cn } from '@daoyou/shared/lib/cn'; +import { getGameConceptInfo } from '@daoyou/shared/lib/gameConceptDisplay'; import { RANKING_REWARDS, REALM_VALUES, type RealmType, -} from '@shared/types/constants'; +} from '@daoyou/shared/types/constants'; import type { BattleRankingItem, ItemRankingEntry, RankingsDisplayItem, WealthRankingEntry, -} from '@shared/types/rankings'; +} from '@daoyou/shared/types/rankings'; import { useEffect, useState } from 'react'; import { useNavigate, useSearchParams } from 'react-router'; import { toRankingDetailItem } from './rankingDetailItem'; diff --git a/src/react-app/routes/game/redeem/route.tsx b/apps/web/src/routes/game/redeem/route.tsx similarity index 100% rename from src/react-app/routes/game/redeem/route.tsx rename to apps/web/src/routes/game/redeem/route.tsx diff --git a/src/react-app/routes/game/reincarnate/route.tsx b/apps/web/src/routes/game/reincarnate/route.tsx similarity index 100% rename from src/react-app/routes/game/reincarnate/route.tsx rename to apps/web/src/routes/game/reincarnate/route.tsx diff --git a/src/react-app/routes/game/retreat/route.tsx b/apps/web/src/routes/game/retreat/route.tsx similarity index 100% rename from src/react-app/routes/game/retreat/route.tsx rename to apps/web/src/routes/game/retreat/route.tsx diff --git a/src/react-app/routes/game/route.tsx b/apps/web/src/routes/game/route.tsx similarity index 100% rename from src/react-app/routes/game/route.tsx rename to apps/web/src/routes/game/route.tsx diff --git a/src/react-app/routes/game/sect/abilities/redirect.tsx b/apps/web/src/routes/game/sect/abilities/redirect.tsx similarity index 100% rename from src/react-app/routes/game/sect/abilities/redirect.tsx rename to apps/web/src/routes/game/sect/abilities/redirect.tsx diff --git a/src/react-app/routes/game/sect/abilities/route.tsx b/apps/web/src/routes/game/sect/abilities/route.tsx similarity index 97% rename from src/react-app/routes/game/sect/abilities/route.tsx rename to apps/web/src/routes/game/sect/abilities/route.tsx index 1089bd76..b83e237a 100644 --- a/src/react-app/routes/game/sect/abilities/route.tsx +++ b/apps/web/src/routes/game/sect/abilities/route.tsx @@ -7,7 +7,7 @@ import { type SectNpcConversationRendererProps, } from '@app/components/feature/sect/room'; import { createSectRoomNpcHref } from '@app/components/feature/sect/sectRoomNavigation'; -import { STANDARD_SECT_PRESENTATION } from '@shared/engine/sect'; +import { STANDARD_SECT_PRESENTATION } from '@daoyou/shared/engine/sect'; import { useNavigate, useSearchParams } from 'react-router'; import { SectPermissionBoundary, SectScene } from '../components/SectScene'; diff --git a/src/react-app/routes/game/sect/affairs/components/SectAffairsRoom.tsx b/apps/web/src/routes/game/sect/affairs/components/SectAffairsRoom.tsx similarity index 99% rename from src/react-app/routes/game/sect/affairs/components/SectAffairsRoom.tsx rename to apps/web/src/routes/game/sect/affairs/components/SectAffairsRoom.tsx index 1d2dc9e4..e364e879 100644 --- a/src/react-app/routes/game/sect/affairs/components/SectAffairsRoom.tsx +++ b/apps/web/src/routes/game/sect/affairs/components/SectAffairsRoom.tsx @@ -19,7 +19,7 @@ import { decodeSectTaskOutcome, readRewardReceiptOutcome, } from '@app/components/feature/sect/sectTaskOutcomeRegistry'; -import type { SectTaskViewData } from '@shared/contracts/sect'; +import type { SectTaskViewData } from '@daoyou/shared/contracts/sect'; import { describeSectPromotionStatus, SECT_RANK_LABELS, @@ -29,7 +29,7 @@ import { type SectRoomActorDefinition, type SectTaskDialogueEmphasis, type SectTaskDialogueSegment, -} from '@shared/engine/sect'; +} from '@daoyou/shared/engine/sect'; import { useEffect, useMemo, useState } from 'react'; const TASK_KINDS: readonly SectAffairsTaskKind[] = [ diff --git a/src/react-app/routes/game/sect/affairs/route.tsx b/apps/web/src/routes/game/sect/affairs/route.tsx similarity index 100% rename from src/react-app/routes/game/sect/affairs/route.tsx rename to apps/web/src/routes/game/sect/affairs/route.tsx diff --git a/src/react-app/routes/game/sect/alchemy/route.tsx b/apps/web/src/routes/game/sect/alchemy/route.tsx similarity index 97% rename from src/react-app/routes/game/sect/alchemy/route.tsx rename to apps/web/src/routes/game/sect/alchemy/route.tsx index bdf096a9..9fffc005 100644 --- a/src/react-app/routes/game/sect/alchemy/route.tsx +++ b/apps/web/src/routes/game/sect/alchemy/route.tsx @@ -16,7 +16,7 @@ import { import { createSectRoomNpcHref } from '@app/components/feature/sect/sectRoomNavigation'; import { formatDocumentTitle } from '@app/lib/router/routeTitle'; import { getSectBenefitMetric } from '@app/lib/sect/sectPresentation'; -import { STANDARD_SECT_PRESENTATION } from '@shared/engine/sect'; +import { STANDARD_SECT_PRESENTATION } from '@daoyou/shared/engine/sect'; import { useNavigate, useSearchParams } from 'react-router'; import { SectPageLoading, diff --git a/src/react-app/routes/game/sect/archive/abilities/route.tsx b/apps/web/src/routes/game/sect/archive/abilities/route.tsx similarity index 100% rename from src/react-app/routes/game/sect/archive/abilities/route.tsx rename to apps/web/src/routes/game/sect/archive/abilities/route.tsx diff --git a/src/react-app/routes/game/sect/archive/methods/route.tsx b/apps/web/src/routes/game/sect/archive/methods/route.tsx similarity index 100% rename from src/react-app/routes/game/sect/archive/methods/route.tsx rename to apps/web/src/routes/game/sect/archive/methods/route.tsx diff --git a/src/react-app/routes/game/sect/archive/paths/route.tsx b/apps/web/src/routes/game/sect/archive/paths/route.tsx similarity index 100% rename from src/react-app/routes/game/sect/archive/paths/route.tsx rename to apps/web/src/routes/game/sect/archive/paths/route.tsx diff --git a/src/react-app/routes/game/sect/archive/route.tsx b/apps/web/src/routes/game/sect/archive/route.tsx similarity index 95% rename from src/react-app/routes/game/sect/archive/route.tsx rename to apps/web/src/routes/game/sect/archive/route.tsx index bbb1c83e..ba84e426 100644 --- a/src/react-app/routes/game/sect/archive/route.tsx +++ b/apps/web/src/routes/game/sect/archive/route.tsx @@ -5,7 +5,7 @@ import { SectRoutedRoom, type SectNpcConversationRendererProps, } from '@app/components/feature/sect/room'; -import { STANDARD_SECT_PRESENTATION } from '@shared/engine/sect'; +import { STANDARD_SECT_PRESENTATION } from '@daoyou/shared/engine/sect'; import { useState } from 'react'; import { SectPermissionBoundary, SectScene } from '../components/SectScene'; diff --git a/src/react-app/routes/game/sect/arena/route.tsx b/apps/web/src/routes/game/sect/arena/route.tsx similarity index 100% rename from src/react-app/routes/game/sect/arena/route.tsx rename to apps/web/src/routes/game/sect/arena/route.tsx diff --git a/src/react-app/routes/game/sect/cave/route.tsx b/apps/web/src/routes/game/sect/cave/route.tsx similarity index 100% rename from src/react-app/routes/game/sect/cave/route.tsx rename to apps/web/src/routes/game/sect/cave/route.tsx diff --git a/src/react-app/routes/game/sect/components/SectScene.tsx b/apps/web/src/routes/game/sect/components/SectScene.tsx similarity index 99% rename from src/react-app/routes/game/sect/components/SectScene.tsx rename to apps/web/src/routes/game/sect/components/SectScene.tsx index 3b159689..119df4cd 100644 --- a/src/react-app/routes/game/sect/components/SectScene.tsx +++ b/apps/web/src/routes/game/sect/components/SectScene.tsx @@ -18,7 +18,7 @@ import { type SectCapabilityKey, type SectDiscipleRank, type SectSceneKey, -} from '@shared/engine/sect'; +} from '@daoyou/shared/engine/sect'; import type { ReactNode } from 'react'; import { useCallback, useState } from 'react'; import { useNavigate } from 'react-router'; diff --git a/src/react-app/routes/game/sect/components/types.ts b/apps/web/src/routes/game/sect/components/types.ts similarity index 100% rename from src/react-app/routes/game/sect/components/types.ts rename to apps/web/src/routes/game/sect/components/types.ts diff --git a/src/react-app/routes/game/sect/cultivation-room/route.tsx b/apps/web/src/routes/game/sect/cultivation-room/route.tsx similarity index 99% rename from src/react-app/routes/game/sect/cultivation-room/route.tsx rename to apps/web/src/routes/game/sect/cultivation-room/route.tsx index f50ec229..3406a1dc 100644 --- a/src/react-app/routes/game/sect/cultivation-room/route.tsx +++ b/apps/web/src/routes/game/sect/cultivation-room/route.tsx @@ -21,7 +21,7 @@ import { getSectBenefitMetric } from '@app/lib/sect/sectPresentation'; import { describeSectFacilityStatus, STANDARD_SECT_PRESENTATION, -} from '@shared/engine/sect'; +} from '@daoyou/shared/engine/sect'; import { Suspense, useState } from 'react'; import { useNavigate, useSearchParams } from 'react-router'; import { diff --git a/src/react-app/routes/game/sect/enlightenment-cliff/route.tsx b/apps/web/src/routes/game/sect/enlightenment-cliff/route.tsx similarity index 95% rename from src/react-app/routes/game/sect/enlightenment-cliff/route.tsx rename to apps/web/src/routes/game/sect/enlightenment-cliff/route.tsx index 6e29902c..ddc42e11 100644 --- a/src/react-app/routes/game/sect/enlightenment-cliff/route.tsx +++ b/apps/web/src/routes/game/sect/enlightenment-cliff/route.tsx @@ -5,7 +5,7 @@ import { SectRoutedRoom, type SectNpcConversationRendererProps, } from '@app/components/feature/sect/room'; -import { STANDARD_SECT_PRESENTATION } from '@shared/engine/sect'; +import { STANDARD_SECT_PRESENTATION } from '@daoyou/shared/engine/sect'; import { useState } from 'react'; import { SectPermissionBoundary, SectScene } from '../components/SectScene'; diff --git a/src/react-app/routes/game/sect/foreign-gate/route.tsx b/apps/web/src/routes/game/sect/foreign-gate/route.tsx similarity index 98% rename from src/react-app/routes/game/sect/foreign-gate/route.tsx rename to apps/web/src/routes/game/sect/foreign-gate/route.tsx index 687ded0d..d5437108 100644 --- a/src/react-app/routes/game/sect/foreign-gate/route.tsx +++ b/apps/web/src/routes/game/sect/foreign-gate/route.tsx @@ -5,7 +5,7 @@ import { GameImmersiveLoading } from '@app/components/game-shell'; import { useSpecialSceneBackAction } from '@app/layouts/special-scene'; import { formatDocumentTitle } from '@app/lib/router/routeTitle'; import { getSectPresentation } from '@app/lib/sect/sectPresentation'; -import { productionSectRuntime } from '@shared/engine/sect/content'; +import { productionSectRuntime } from '@daoyou/shared/engine/sect/content'; import { useCallback } from 'react'; import { Navigate, useNavigate, useParams } from 'react-router'; diff --git a/src/react-app/routes/game/sect/gate/route.tsx b/apps/web/src/routes/game/sect/gate/route.tsx similarity index 98% rename from src/react-app/routes/game/sect/gate/route.tsx rename to apps/web/src/routes/game/sect/gate/route.tsx index 5368d726..72c0e897 100644 --- a/src/react-app/routes/game/sect/gate/route.tsx +++ b/apps/web/src/routes/game/sect/gate/route.tsx @@ -17,7 +17,7 @@ import { createActivityImmersiveNavigationState, requestActivityImmersiveMode, } from '@app/lib/gameActivityImmersive'; -import { STANDARD_SECT_PRESENTATION } from '@shared/engine/sect'; +import { STANDARD_SECT_PRESENTATION } from '@daoyou/shared/engine/sect'; import { useState } from 'react'; import { useNavigate } from 'react-router'; import { SectPermissionBoundary, SectScene } from '../components/SectScene'; diff --git a/src/react-app/routes/game/sect/gate/sweep/SweepPhaserRuntime.ts b/apps/web/src/routes/game/sect/gate/sweep/SweepPhaserRuntime.ts similarity index 99% rename from src/react-app/routes/game/sect/gate/sweep/SweepPhaserRuntime.ts rename to apps/web/src/routes/game/sect/gate/sweep/SweepPhaserRuntime.ts index ba582201..5f87649b 100644 --- a/src/react-app/routes/game/sect/gate/sweep/SweepPhaserRuntime.ts +++ b/apps/web/src/routes/game/sect/gate/sweep/SweepPhaserRuntime.ts @@ -10,7 +10,7 @@ import { type SweepDirection, type SweepGameProgress, type SweepGameState, -} from '@shared/engine/sect'; +} from '@daoyou/shared/engine/sect'; import * as Phaser from 'phaser'; import { attachSweepVirtualJoystick, diff --git a/src/react-app/routes/game/sect/gate/sweep/SweepVirtualJoystick.ts b/apps/web/src/routes/game/sect/gate/sweep/SweepVirtualJoystick.ts similarity index 98% rename from src/react-app/routes/game/sect/gate/sweep/SweepVirtualJoystick.ts rename to apps/web/src/routes/game/sect/gate/sweep/SweepVirtualJoystick.ts index 8cd9713f..6e2ea6bb 100644 --- a/src/react-app/routes/game/sect/gate/sweep/SweepVirtualJoystick.ts +++ b/apps/web/src/routes/game/sect/gate/sweep/SweepVirtualJoystick.ts @@ -1,5 +1,5 @@ import { GAME_VIRTUAL_JOYSTICK_ASSETS } from '@app/components/feature/game-activity/virtualJoystickAssets'; -import { SWEEP_CANVAS, type SweepDirection } from '@shared/engine/sect'; +import { SWEEP_CANVAS, type SweepDirection } from '@daoyou/shared/engine/sect'; import * as Phaser from 'phaser'; import VirtualJoystick from 'phaser4-rex-plugins/plugins/virtualjoystick.js'; diff --git a/src/react-app/routes/game/sect/gate/sweep/route.tsx b/apps/web/src/routes/game/sect/gate/sweep/route.tsx similarity index 99% rename from src/react-app/routes/game/sect/gate/sweep/route.tsx rename to apps/web/src/routes/game/sect/gate/sweep/route.tsx index 485c37ff..f0afb74c 100644 --- a/src/react-app/routes/game/sect/gate/sweep/route.tsx +++ b/apps/web/src/routes/game/sect/gate/sweep/route.tsx @@ -32,8 +32,8 @@ import type { SectTaskActionData, SectTasksData, SectTaskViewData, -} from '@shared/contracts/sect'; -import type { SweepDirection, SweepGameProgress } from '@shared/engine/sect'; +} from '@daoyou/shared/contracts/sect'; +import type { SweepDirection, SweepGameProgress } from '@daoyou/shared/engine/sect'; import { useCallback, useEffect, useRef, useState } from 'react'; import { useLocation, useNavigate } from 'react-router'; import { diff --git a/src/react-app/routes/game/sect/gate/sweep/sweepActivityState.ts b/apps/web/src/routes/game/sect/gate/sweep/sweepActivityState.ts similarity index 94% rename from src/react-app/routes/game/sect/gate/sweep/sweepActivityState.ts rename to apps/web/src/routes/game/sect/gate/sweep/sweepActivityState.ts index 2993b243..da92be26 100644 --- a/src/react-app/routes/game/sect/gate/sweep/sweepActivityState.ts +++ b/apps/web/src/routes/game/sect/gate/sweep/sweepActivityState.ts @@ -1,4 +1,4 @@ -import type { SectTasksData, SectTaskViewData } from '@shared/contracts/sect'; +import type { SectTasksData, SectTaskViewData } from '@daoyou/shared/contracts/sect'; export const SWEEP_TASK_ID = 'gate_sweep'; diff --git a/src/react-app/routes/game/sect/gate/sweep/sweepVisualLayout.ts b/apps/web/src/routes/game/sect/gate/sweep/sweepVisualLayout.ts similarity index 96% rename from src/react-app/routes/game/sect/gate/sweep/sweepVisualLayout.ts rename to apps/web/src/routes/game/sect/gate/sweep/sweepVisualLayout.ts index 1c923859..234566c6 100644 --- a/src/react-app/routes/game/sect/gate/sweep/sweepVisualLayout.ts +++ b/apps/web/src/routes/game/sect/gate/sweep/sweepVisualLayout.ts @@ -2,7 +2,7 @@ import { SWEEP_CANVAS, SWEEP_GRID_COLUMNS, type SweepCell, -} from '@shared/engine/sect'; +} from '@daoyou/shared/engine/sect'; export const SWEEP_CELL_WIDTH = 78; export const SWEEP_CELL_HEIGHT = 66; diff --git a/src/react-app/routes/game/sect/hall/route.tsx b/apps/web/src/routes/game/sect/hall/route.tsx similarity index 99% rename from src/react-app/routes/game/sect/hall/route.tsx rename to apps/web/src/routes/game/sect/hall/route.tsx index 95f5ca9a..34ae1ab6 100644 --- a/src/react-app/routes/game/sect/hall/route.tsx +++ b/apps/web/src/routes/game/sect/hall/route.tsx @@ -21,11 +21,11 @@ import { useResourceMutation } from '@app/lib/resources/mutations'; import type { SectContributionRankingData, SectMembersData, -} from '@shared/contracts/sect'; +} from '@daoyou/shared/contracts/sect'; import { SECT_RANK_LABELS, STANDARD_SECT_PRESENTATION, -} from '@shared/engine/sect'; +} from '@daoyou/shared/engine/sect'; import { useEffect, useState } from 'react'; import { postJson, diff --git a/src/react-app/routes/game/sect/herb-garden/route.tsx b/apps/web/src/routes/game/sect/herb-garden/route.tsx similarity index 97% rename from src/react-app/routes/game/sect/herb-garden/route.tsx rename to apps/web/src/routes/game/sect/herb-garden/route.tsx index abcebc76..9587e542 100644 --- a/src/react-app/routes/game/sect/herb-garden/route.tsx +++ b/apps/web/src/routes/game/sect/herb-garden/route.tsx @@ -10,7 +10,7 @@ import { type SectNpcConversationRendererProps, } from '@app/components/feature/sect/room'; import { useSectInfrastructureQuery } from '@app/components/feature/sect/sectResources'; -import { STANDARD_SECT_PRESENTATION } from '@shared/engine/sect'; +import { STANDARD_SECT_PRESENTATION } from '@daoyou/shared/engine/sect'; import { SectPermissionBoundary, SectScene } from '../components/SectScene'; const registry = new SectNpcConversationRegistry([ diff --git a/src/react-app/routes/game/sect/industries/route.tsx b/apps/web/src/routes/game/sect/industries/route.tsx similarity index 99% rename from src/react-app/routes/game/sect/industries/route.tsx rename to apps/web/src/routes/game/sect/industries/route.tsx index e11b0aac..9e9a4f60 100644 --- a/src/react-app/routes/game/sect/industries/route.tsx +++ b/apps/web/src/routes/game/sect/industries/route.tsx @@ -22,7 +22,7 @@ import { STANDARD_SECT_PRESENTATION, type SectConstructionDonationAmount, type SectFacilityState, -} from '@shared/engine/sect'; +} from '@daoyou/shared/engine/sect'; import { useMemo, useState } from 'react'; import { postJson, diff --git a/src/react-app/routes/game/sect/onboarding/route.tsx b/apps/web/src/routes/game/sect/onboarding/route.tsx similarity index 97% rename from src/react-app/routes/game/sect/onboarding/route.tsx rename to apps/web/src/routes/game/sect/onboarding/route.tsx index 5699f098..d1b8ac63 100644 --- a/src/react-app/routes/game/sect/onboarding/route.tsx +++ b/apps/web/src/routes/game/sect/onboarding/route.tsx @@ -8,13 +8,13 @@ import { usePlayerSession, } from '@app/lib/resources/player'; import { getSectPresentation } from '@app/lib/sect/sectPresentation'; -import type { SectCatalogEntry } from '@shared/contracts/sect'; +import type { SectCatalogEntry } from '@daoyou/shared/contracts/sect'; import { COMBAT_V6_SECT_DEFINITIONS, type CombatV6SectId, -} from '@shared/engine/combat-v6/content'; -import { productionSectRuntime } from '@shared/engine/sect/content'; -import { getSectLandmarkBySectId } from '@shared/lib/game/mapSystem'; +} from '@daoyou/shared/engine/combat-v6/content'; +import { productionSectRuntime } from '@daoyou/shared/engine/sect/content'; +import { getSectLandmarkBySectId } from '@daoyou/shared/lib/game/mapSystem'; import { useMemo, useRef, useState } from 'react'; import { Navigate, useNavigate, useSearchParams } from 'react-router'; import { diff --git a/src/react-app/routes/game/sect/onboarding/sectJoinAttempt.ts b/apps/web/src/routes/game/sect/onboarding/sectJoinAttempt.ts similarity index 100% rename from src/react-app/routes/game/sect/onboarding/sectJoinAttempt.ts rename to apps/web/src/routes/game/sect/onboarding/sectJoinAttempt.ts diff --git a/src/react-app/routes/game/sect/onboarding/sectOnboardingFlow.ts b/apps/web/src/routes/game/sect/onboarding/sectOnboardingFlow.ts similarity index 100% rename from src/react-app/routes/game/sect/onboarding/sectOnboardingFlow.ts rename to apps/web/src/routes/game/sect/onboarding/sectOnboardingFlow.ts diff --git a/src/react-app/routes/game/sect/refinery/route.tsx b/apps/web/src/routes/game/sect/refinery/route.tsx similarity index 96% rename from src/react-app/routes/game/sect/refinery/route.tsx rename to apps/web/src/routes/game/sect/refinery/route.tsx index d5925510..7b5636cb 100644 --- a/src/react-app/routes/game/sect/refinery/route.tsx +++ b/apps/web/src/routes/game/sect/refinery/route.tsx @@ -10,7 +10,7 @@ import { useSectInfrastructureQuery, } from '@app/components/feature/sect/sectResources'; import { formatDocumentTitle } from '@app/lib/router/routeTitle'; -import { STANDARD_SECT_PRESENTATION } from '@shared/engine/sect'; +import { STANDARD_SECT_PRESENTATION } from '@daoyou/shared/engine/sect'; import { useSearchParams } from 'react-router'; import { SectPageLoading, diff --git a/src/react-app/routes/game/sect/route.tsx b/apps/web/src/routes/game/sect/route.tsx similarity index 100% rename from src/react-app/routes/game/sect/route.tsx rename to apps/web/src/routes/game/sect/route.tsx diff --git a/src/react-app/routes/game/sect/spirit-vein/mining/MiningDropButton.tsx b/apps/web/src/routes/game/sect/spirit-vein/mining/MiningDropButton.tsx similarity index 100% rename from src/react-app/routes/game/sect/spirit-vein/mining/MiningDropButton.tsx rename to apps/web/src/routes/game/sect/spirit-vein/mining/MiningDropButton.tsx diff --git a/src/react-app/routes/game/sect/spirit-vein/mining/MiningPhaserRuntime.ts b/apps/web/src/routes/game/sect/spirit-vein/mining/MiningPhaserRuntime.ts similarity index 99% rename from src/react-app/routes/game/sect/spirit-vein/mining/MiningPhaserRuntime.ts rename to apps/web/src/routes/game/sect/spirit-vein/mining/MiningPhaserRuntime.ts index 15340cb1..9c1ab770 100644 --- a/src/react-app/routes/game/sect/spirit-vein/mining/MiningPhaserRuntime.ts +++ b/apps/web/src/routes/game/sect/spirit-vein/mining/MiningPhaserRuntime.ts @@ -15,7 +15,7 @@ import { type MiningScoreTier, type MiningTarget, type MiningTargetKind, -} from '@shared/engine/sect'; +} from '@daoyou/shared/engine/sect'; import * as Phaser from 'phaser'; const TARGET_TEXTURES: Record = { diff --git a/src/react-app/routes/game/sect/spirit-vein/mining/miningActivityState.ts b/apps/web/src/routes/game/sect/spirit-vein/mining/miningActivityState.ts similarity index 94% rename from src/react-app/routes/game/sect/spirit-vein/mining/miningActivityState.ts rename to apps/web/src/routes/game/sect/spirit-vein/mining/miningActivityState.ts index 23911740..5b5524bb 100644 --- a/src/react-app/routes/game/sect/spirit-vein/mining/miningActivityState.ts +++ b/apps/web/src/routes/game/sect/spirit-vein/mining/miningActivityState.ts @@ -1,4 +1,4 @@ -import type { SectTasksData, SectTaskViewData } from '@shared/contracts/sect'; +import type { SectTasksData, SectTaskViewData } from '@daoyou/shared/contracts/sect'; export const MINING_TASK_ID = 'spirit_mining'; diff --git a/src/react-app/routes/game/sect/spirit-vein/mining/route.tsx b/apps/web/src/routes/game/sect/spirit-vein/mining/route.tsx similarity index 99% rename from src/react-app/routes/game/sect/spirit-vein/mining/route.tsx rename to apps/web/src/routes/game/sect/spirit-vein/mining/route.tsx index 67546b78..42d8e7ff 100644 --- a/src/react-app/routes/game/sect/spirit-vein/mining/route.tsx +++ b/apps/web/src/routes/game/sect/spirit-vein/mining/route.tsx @@ -34,12 +34,12 @@ import type { SectTaskActionData, SectTasksData, SectTaskViewData, -} from '@shared/contracts/sect'; +} from '@daoyou/shared/contracts/sect'; import { MINING_DURATION_MS, MINING_MAX_SCORE, type MiningCastInput, -} from '@shared/engine/sect'; +} from '@daoyou/shared/engine/sect'; import { useCallback, useEffect, useRef, useState } from 'react'; import { useLocation, useNavigate } from 'react-router'; import { MiningDropButton } from './MiningDropButton'; diff --git a/src/react-app/routes/game/sect/spirit-vein/route.tsx b/apps/web/src/routes/game/sect/spirit-vein/route.tsx similarity index 99% rename from src/react-app/routes/game/sect/spirit-vein/route.tsx rename to apps/web/src/routes/game/sect/spirit-vein/route.tsx index e72fc6d3..35d1ab97 100644 --- a/src/react-app/routes/game/sect/spirit-vein/route.tsx +++ b/apps/web/src/routes/game/sect/spirit-vein/route.tsx @@ -24,7 +24,7 @@ import { import { describeSectFacilityStatus, STANDARD_SECT_PRESENTATION, -} from '@shared/engine/sect'; +} from '@daoyou/shared/engine/sect'; import { useState } from 'react'; import { useNavigate } from 'react-router'; import { SectPermissionBoundary, SectScene } from '../components/SectScene'; diff --git a/src/react-app/routes/game/sect/task-battle/route.tsx b/apps/web/src/routes/game/sect/task-battle/route.tsx similarity index 98% rename from src/react-app/routes/game/sect/task-battle/route.tsx rename to apps/web/src/routes/game/sect/task-battle/route.tsx index 212ce53b..a315c453 100644 --- a/src/react-app/routes/game/sect/task-battle/route.tsx +++ b/apps/web/src/routes/game/sect/task-battle/route.tsx @@ -9,7 +9,7 @@ import { } from '@app/components/feature/sect/sectTaskActivityLocations'; import { InkButton } from '@app/components/ui'; import { startSectTaskBattleOnce } from '@app/lib/sect/sectClient'; -import type { SectTaskSessionView } from '@shared/contracts/combatV6SectTask'; +import type { SectTaskSessionView } from '@daoyou/shared/contracts/combatV6SectTask'; import { useEffect, useRef, useState } from 'react'; import { useNavigate, useParams, useSearchParams } from 'react-router'; import { SectPermissionBoundary } from '../components/SectScene'; diff --git a/src/react-app/routes/game/sect/transfer/route.tsx b/apps/web/src/routes/game/sect/transfer/route.tsx similarity index 97% rename from src/react-app/routes/game/sect/transfer/route.tsx rename to apps/web/src/routes/game/sect/transfer/route.tsx index d730e5ec..7aca65df 100644 --- a/src/react-app/routes/game/sect/transfer/route.tsx +++ b/apps/web/src/routes/game/sect/transfer/route.tsx @@ -7,10 +7,10 @@ import { useInkUI } from '@app/components/providers/InkUIProvider'; import { InkBadge, InkButton, InkCard, InkNotice } from '@app/components/ui'; import { useResourceMutation } from '@app/lib/resources/mutations'; import { usePlayerSession } from '@app/lib/resources/player'; -import { CHEAT_HEAVEN_TALISMAN_NAME } from '@shared/config/sectTransferTalisman'; -import type { SectTransferPreviewData } from '@shared/contracts/sect'; -import { COMBAT_V6_SECT_DEFINITIONS } from '@shared/engine/combat-v6/content'; -import { SECT_RANK_LABELS } from '@shared/engine/sect'; +import { CHEAT_HEAVEN_TALISMAN_NAME } from '@daoyou/shared/config/sectTransferTalisman'; +import type { SectTransferPreviewData } from '@daoyou/shared/contracts/sect'; +import { COMBAT_V6_SECT_DEFINITIONS } from '@daoyou/shared/engine/combat-v6/content'; +import { SECT_RANK_LABELS } from '@daoyou/shared/engine/sect'; import { useMemo, useRef, useState } from 'react'; import { useNavigate } from 'react-router'; diff --git a/src/react-app/routes/game/sect/treasury/route.tsx b/apps/web/src/routes/game/sect/treasury/route.tsx similarity index 98% rename from src/react-app/routes/game/sect/treasury/route.tsx rename to apps/web/src/routes/game/sect/treasury/route.tsx index ee5c2485..f092d86a 100644 --- a/src/react-app/routes/game/sect/treasury/route.tsx +++ b/apps/web/src/routes/game/sect/treasury/route.tsx @@ -12,8 +12,8 @@ import { useResourceMutation } from '@app/lib/resources/mutations'; import type { SectShopBuyResponse, SectShopItemData, -} from '@shared/contracts/sectShop'; -import { STANDARD_SECT_PRESENTATION } from '@shared/engine/sect'; +} from '@daoyou/shared/contracts/sectShop'; +import { STANDARD_SECT_PRESENTATION } from '@daoyou/shared/engine/sect'; import { useRef, useState } from 'react'; import { postJson, diff --git a/src/react-app/routes/game/sect/visit/route.tsx b/apps/web/src/routes/game/sect/visit/route.tsx similarity index 96% rename from src/react-app/routes/game/sect/visit/route.tsx rename to apps/web/src/routes/game/sect/visit/route.tsx index f816d1e1..dd9e682a 100644 --- a/src/react-app/routes/game/sect/visit/route.tsx +++ b/apps/web/src/routes/game/sect/visit/route.tsx @@ -6,8 +6,8 @@ import { usePlayerSession } from '@app/lib/resources/player'; import { resolveMapReturnHref } from '@app/lib/router/mapNavigation'; import { formatDocumentTitle } from '@app/lib/router/routeTitle'; import { getSectPresentation } from '@app/lib/sect/sectPresentation'; -import { productionSectRuntime } from '@shared/engine/sect/content'; -import { getSectLandmarkBySectId } from '@shared/lib/game/mapSystem'; +import { productionSectRuntime } from '@daoyou/shared/engine/sect/content'; +import { getSectLandmarkBySectId } from '@daoyou/shared/lib/game/mapSystem'; import { useCallback } from 'react'; import { Navigate, useLocation, useNavigate, useParams } from 'react-router'; diff --git a/src/react-app/routes/game/sect/workshop/route.tsx b/apps/web/src/routes/game/sect/workshop/route.tsx similarity index 100% rename from src/react-app/routes/game/sect/workshop/route.tsx rename to apps/web/src/routes/game/sect/workshop/route.tsx diff --git a/src/react-app/routes/game/settings/components/AccountSettingsTab.tsx b/apps/web/src/routes/game/settings/components/AccountSettingsTab.tsx similarity index 99% rename from src/react-app/routes/game/settings/components/AccountSettingsTab.tsx rename to apps/web/src/routes/game/settings/components/AccountSettingsTab.tsx index ac3c41a5..c7a48b06 100644 --- a/src/react-app/routes/game/settings/components/AccountSettingsTab.tsx +++ b/apps/web/src/routes/game/settings/components/AccountSettingsTab.tsx @@ -15,8 +15,8 @@ import { import type { AuthActionError } from '@app/lib/auth/authState'; import { toAuthActionError } from '@app/lib/auth/authState'; import { authClient } from '@app/lib/auth/client'; -import type { AccountSetPasswordResponse } from '@shared/contracts/account'; -import type { ApiFailure } from '@shared/contracts/http'; +import type { AccountSetPasswordResponse } from '@daoyou/shared/contracts/account'; +import type { ApiFailure } from '@daoyou/shared/contracts/http'; import { useEffect, useMemo, useState } from 'react'; import { useNavigate } from 'react-router'; import { diff --git a/src/react-app/routes/game/settings/components/CombatAutoStrategyTab.tsx b/apps/web/src/routes/game/settings/components/CombatAutoStrategyTab.tsx similarity index 99% rename from src/react-app/routes/game/settings/components/CombatAutoStrategyTab.tsx rename to apps/web/src/routes/game/settings/components/CombatAutoStrategyTab.tsx index 4b614f4c..41b0c3b5 100644 --- a/src/react-app/routes/game/settings/components/CombatAutoStrategyTab.tsx +++ b/apps/web/src/routes/game/settings/components/CombatAutoStrategyTab.tsx @@ -1,12 +1,12 @@ import { InkButton } from '@app/components/ui/InkButton'; -import { autoStatusChoices } from '@shared/combat-v6/auto-status-options'; +import { autoStatusChoices } from '@daoyou/shared/combat-v6/auto-status-options'; import { autoComparison, MAX_AUTO_STRATEGY_RULES, SaveAutoStrategySchema, type AutoComparison, type AutoStrategy, -} from '@shared/combat-v6/auto-strategy'; +} from '@daoyou/shared/combat-v6/auto-strategy'; import { useEffect, useState } from 'react'; import { SettingsMessage } from './SettingsFields'; diff --git a/src/react-app/routes/game/settings/components/ConnectionStatusTab.tsx b/apps/web/src/routes/game/settings/components/ConnectionStatusTab.tsx similarity index 96% rename from src/react-app/routes/game/settings/components/ConnectionStatusTab.tsx rename to apps/web/src/routes/game/settings/components/ConnectionStatusTab.tsx index 78f7f463..c3051d4e 100644 --- a/src/react-app/routes/game/settings/components/ConnectionStatusTab.tsx +++ b/apps/web/src/routes/game/settings/components/ConnectionStatusTab.tsx @@ -9,8 +9,8 @@ import { type RealtimeChannelStatus, type RealtimeStatusSnapshot, } from '@app/lib/realtime/realtimeClient'; -import type { RealtimeChannel } from '@shared/contracts/realtime'; -import { cn } from '@shared/lib/cn'; +import type { RealtimeChannel } from '@daoyou/shared/contracts/realtime'; +import { cn } from '@daoyou/shared/lib/cn'; import { useEffect, useState } from 'react'; import { SettingsSection, settingsLabelClass } from './SettingsFields'; diff --git a/src/react-app/routes/game/settings/components/GameSettingsTab.tsx b/apps/web/src/routes/game/settings/components/GameSettingsTab.tsx similarity index 100% rename from src/react-app/routes/game/settings/components/GameSettingsTab.tsx rename to apps/web/src/routes/game/settings/components/GameSettingsTab.tsx diff --git a/src/react-app/routes/game/settings/components/ModelConfigTab.tsx b/apps/web/src/routes/game/settings/components/ModelConfigTab.tsx similarity index 99% rename from src/react-app/routes/game/settings/components/ModelConfigTab.tsx rename to apps/web/src/routes/game/settings/components/ModelConfigTab.tsx index fcd26156..c2d617e2 100644 --- a/src/react-app/routes/game/settings/components/ModelConfigTab.tsx +++ b/apps/web/src/routes/game/settings/components/ModelConfigTab.tsx @@ -6,7 +6,7 @@ import { LLM_PROVIDER_DEFAULT_MODELS, LlmByokConfigSchema, type LlmProviderId, -} from '@shared/config/llm'; +} from '@daoyou/shared/config/llm'; import { useState } from 'react'; import { SettingsMessage, diff --git a/src/react-app/routes/game/settings/components/QiLogsTab.tsx b/apps/web/src/routes/game/settings/components/QiLogsTab.tsx similarity index 98% rename from src/react-app/routes/game/settings/components/QiLogsTab.tsx rename to apps/web/src/routes/game/settings/components/QiLogsTab.tsx index d66169a3..8da872de 100644 --- a/src/react-app/routes/game/settings/components/QiLogsTab.tsx +++ b/apps/web/src/routes/game/settings/components/QiLogsTab.tsx @@ -1,6 +1,6 @@ import { GameLoadingState } from '@app/components/game-shell/GameLoadingState'; import { InkButton } from '@app/components/ui/InkButton'; -import type { QiLogEntry, QiLogsResponse } from '@shared/contracts/qi'; +import type { QiLogEntry, QiLogsResponse } from '@daoyou/shared/contracts/qi'; import { useEffect, useState } from 'react'; import { SettingsMessage, diff --git a/src/react-app/routes/game/settings/components/SettingsFields.tsx b/apps/web/src/routes/game/settings/components/SettingsFields.tsx similarity index 98% rename from src/react-app/routes/game/settings/components/SettingsFields.tsx rename to apps/web/src/routes/game/settings/components/SettingsFields.tsx index a86eeffb..b2f7cafb 100644 --- a/src/react-app/routes/game/settings/components/SettingsFields.tsx +++ b/apps/web/src/routes/game/settings/components/SettingsFields.tsx @@ -1,4 +1,4 @@ -import { cn } from '@shared/lib/cn'; +import { cn } from '@daoyou/shared/lib/cn'; import type { ReactNode } from 'react'; export const settingsLabelClass = diff --git a/src/react-app/routes/game/settings/components/utils.ts b/apps/web/src/routes/game/settings/components/utils.ts similarity index 100% rename from src/react-app/routes/game/settings/components/utils.ts rename to apps/web/src/routes/game/settings/components/utils.ts diff --git a/src/react-app/routes/game/settings/feedback/route.tsx b/apps/web/src/routes/game/settings/feedback/route.tsx similarity index 98% rename from src/react-app/routes/game/settings/feedback/route.tsx rename to apps/web/src/routes/game/settings/feedback/route.tsx index 28c12536..968f9a4d 100644 --- a/src/react-app/routes/game/settings/feedback/route.tsx +++ b/apps/web/src/routes/game/settings/feedback/route.tsx @@ -5,7 +5,7 @@ import { import { InkButton } from '@app/components/ui/InkButton'; import { InkChoiceButton } from '@app/components/ui/InkChoiceButton'; import { InkInput } from '@app/components/ui/InkInput'; -import type { FeedbackType } from '@shared/contracts/feedback'; +import type { FeedbackType } from '@daoyou/shared/contracts/feedback'; import { useState } from 'react'; const FEEDBACK_TYPES: { value: FeedbackType; label: string }[] = [ diff --git a/src/react-app/routes/game/settings/route.tsx b/apps/web/src/routes/game/settings/route.tsx similarity index 100% rename from src/react-app/routes/game/settings/route.tsx rename to apps/web/src/routes/game/settings/route.tsx diff --git a/src/react-app/routes/game/skills/route.tsx b/apps/web/src/routes/game/skills/route.tsx similarity index 100% rename from src/react-app/routes/game/skills/route.tsx rename to apps/web/src/routes/game/skills/route.tsx diff --git a/src/react-app/routes/game/spirit-field/route.tsx b/apps/web/src/routes/game/spirit-field/route.tsx similarity index 97% rename from src/react-app/routes/game/spirit-field/route.tsx rename to apps/web/src/routes/game/spirit-field/route.tsx index b4ec5966..625f9208 100644 --- a/src/react-app/routes/game/spirit-field/route.tsx +++ b/apps/web/src/routes/game/spirit-field/route.tsx @@ -15,13 +15,13 @@ import { InkDetailDrawer } from '@app/components/ui/InkDetailDrawer'; import { useInventoryBag } from '@app/lib/resources/bag'; import { useCraftStorage } from '@app/lib/resources/craftStorage'; import { useResourceMutation } from '@app/lib/resources/mutations'; -import type { InventoryView } from '@shared/contracts/inventory'; -import { canPlantSpiritFieldSeed } from '@shared/engine/spirit-field/rules'; -import { ConsumableFactsSchema } from '@shared/items/definitions/consumables'; -import { SeedFactsSchema } from '@shared/items/definitions/seeds'; -import { materialFactsOf } from '@shared/items/material'; -import { findItemDefinition } from '@shared/items/registry'; -import type { RealmType } from '@shared/types/constants'; +import type { InventoryView } from '@daoyou/shared/contracts/inventory'; +import { canPlantSpiritFieldSeed } from '@daoyou/shared/engine/spirit-field/rules'; +import { ConsumableFactsSchema } from '@daoyou/shared/items/definitions/consumables'; +import { SeedFactsSchema } from '@daoyou/shared/items/definitions/seeds'; +import { materialFactsOf } from '@daoyou/shared/items/material'; +import { findItemDefinition } from '@daoyou/shared/items/registry'; +import type { RealmType } from '@daoyou/shared/types/constants'; import { useCallback, useEffect, useRef, useState } from 'react'; type Resource = { diff --git a/src/react-app/routes/game/story/preview/route.tsx b/apps/web/src/routes/game/story/preview/route.tsx similarity index 96% rename from src/react-app/routes/game/story/preview/route.tsx rename to apps/web/src/routes/game/story/preview/route.tsx index e92fe5f8..3c129608 100644 --- a/src/react-app/routes/game/story/preview/route.tsx +++ b/apps/web/src/routes/game/story/preview/route.tsx @@ -2,8 +2,8 @@ import { PerformancePlayer } from '@app/components/feature/performance/Performan import { GameLoadingState } from '@app/components/game-shell/GameLoadingState'; import { InkButton, InkInput } from '@app/components/ui'; import { useCultivatorIdentity } from '@app/lib/resources/player'; -import { listPerformanceScripts, getPerformanceScript } from '@shared/performance/catalog'; -import { fillPerformanceScript, type PerformanceContext } from '@shared/performance/schema'; +import { listPerformanceScripts, getPerformanceScript } from '@daoyou/shared/performance/catalog'; +import { fillPerformanceScript, type PerformanceContext } from '@daoyou/shared/performance/schema'; import { useState } from 'react'; import { useNavigate, useParams } from 'react-router'; diff --git a/src/react-app/routes/game/story/route.tsx b/apps/web/src/routes/game/story/route.tsx similarity index 94% rename from src/react-app/routes/game/story/route.tsx rename to apps/web/src/routes/game/story/route.tsx index a671b1e1..c9d9f72a 100644 --- a/src/react-app/routes/game/story/route.tsx +++ b/apps/web/src/routes/game/story/route.tsx @@ -4,8 +4,8 @@ import { InkButton } from '@app/components/ui'; import { consumeResourceMutation } from '@app/lib/resources/mutations'; import { useCultivatorIdentity } from '@app/lib/resources/player'; import { useStory } from '@app/lib/story/useStory'; -import { fillPerformanceScript } from '@shared/performance/schema'; -import { getPerformanceScript } from '@shared/performance/catalog'; +import { fillPerformanceScript } from '@daoyou/shared/performance/schema'; +import { getPerformanceScript } from '@daoyou/shared/performance/catalog'; import { useState } from 'react'; import { Navigate, useNavigate } from 'react-router'; diff --git a/src/react-app/routes/game/tasks/challenge/route.tsx b/apps/web/src/routes/game/tasks/challenge/route.tsx similarity index 97% rename from src/react-app/routes/game/tasks/challenge/route.tsx rename to apps/web/src/routes/game/tasks/challenge/route.tsx index 4d4e64a8..4217183f 100644 --- a/src/react-app/routes/game/tasks/challenge/route.tsx +++ b/apps/web/src/routes/game/tasks/challenge/route.tsx @@ -9,7 +9,7 @@ import { } from '@app/lib/resources/definitions'; import { useResource, useSingletonResource } from '@app/lib/resources/hooks'; import { startTaskChallengeOnce } from '@app/lib/tasks/taskClient'; -import type { BreakthroughSessionView } from '@shared/contracts/combatV6Breakthrough'; +import type { BreakthroughSessionView } from '@daoyou/shared/contracts/combatV6Breakthrough'; import { useEffect } from 'react'; import { useNavigate, useParams } from 'react-router'; diff --git a/src/react-app/routes/game/tasks/components/TasksView.tsx b/apps/web/src/routes/game/tasks/components/TasksView.tsx similarity index 100% rename from src/react-app/routes/game/tasks/components/TasksView.tsx rename to apps/web/src/routes/game/tasks/components/TasksView.tsx diff --git a/src/react-app/routes/game/tasks/route.tsx b/apps/web/src/routes/game/tasks/route.tsx similarity index 100% rename from src/react-app/routes/game/tasks/route.tsx rename to apps/web/src/routes/game/tasks/route.tsx diff --git a/src/react-app/routes/game/techniques/route.tsx b/apps/web/src/routes/game/techniques/route.tsx similarity index 100% rename from src/react-app/routes/game/techniques/route.tsx rename to apps/web/src/routes/game/techniques/route.tsx diff --git a/src/react-app/routes/game/tianjiao-vault/route.tsx b/apps/web/src/routes/game/tianjiao-vault/route.tsx similarity index 97% rename from src/react-app/routes/game/tianjiao-vault/route.tsx rename to apps/web/src/routes/game/tianjiao-vault/route.tsx index 8b7a9530..86dd0e49 100644 --- a/src/react-app/routes/game/tianjiao-vault/route.tsx +++ b/apps/web/src/routes/game/tianjiao-vault/route.tsx @@ -10,8 +10,8 @@ import type { ReputationShopBuyResponse, ReputationShopItemView, ReputationShopListResponse, -} from '@shared/contracts/reputationShop'; -import { getGameConceptInfo } from '@shared/lib/gameConceptDisplay'; +} from '@daoyou/shared/contracts/reputationShop'; +import { getGameConceptInfo } from '@daoyou/shared/lib/gameConceptDisplay'; import { useCallback, useEffect, useRef, useState } from 'react'; const REPUTATION_INFO = getGameConceptInfo('reputation'); diff --git a/src/react-app/routes/game/tower/battle/route.tsx b/apps/web/src/routes/game/tower/battle/route.tsx similarity index 98% rename from src/react-app/routes/game/tower/battle/route.tsx rename to apps/web/src/routes/game/tower/battle/route.tsx index c84f5734..9f990d7d 100644 --- a/src/react-app/routes/game/tower/battle/route.tsx +++ b/apps/web/src/routes/game/tower/battle/route.tsx @@ -10,7 +10,7 @@ import { useCultivatorCurrency } from '@app/lib/resources/player'; import type { TowerSessionView, TowerView, -} from '@shared/contracts/combatV6Tower'; +} from '@daoyou/shared/contracts/combatV6Tower'; import { useNavigate } from 'react-router'; export default function TowerBattleRoute() { diff --git a/src/react-app/routes/game/tower/components/TowerLeaderboard.tsx b/apps/web/src/routes/game/tower/components/TowerLeaderboard.tsx similarity index 92% rename from src/react-app/routes/game/tower/components/TowerLeaderboard.tsx rename to apps/web/src/routes/game/tower/components/TowerLeaderboard.tsx index a539e9e6..0b64d4f1 100644 --- a/src/react-app/routes/game/tower/components/TowerLeaderboard.tsx +++ b/apps/web/src/routes/game/tower/components/TowerLeaderboard.tsx @@ -1,8 +1,8 @@ import { GameLoadingState, GameSceneTabs } from '@app/components/game-shell'; import { InkSelect } from '@app/components/ui/InkSelect'; -import { TOWER_ELIGIBLE_REALMS } from '@shared/lib/tower/helpers'; -import type { TowerLeaderboardEntry } from '@shared/lib/tower/types'; -import type { RealmType } from '@shared/types/constants'; +import { TOWER_ELIGIBLE_REALMS } from '@daoyou/shared/lib/tower/helpers'; +import type { TowerLeaderboardEntry } from '@daoyou/shared/lib/tower/types'; +import type { RealmType } from '@daoyou/shared/types/constants'; interface TowerLeaderboardProps { activeRealm: RealmType; diff --git a/src/react-app/routes/game/tower/components/TowerRewards.tsx b/apps/web/src/routes/game/tower/components/TowerRewards.tsx similarity index 97% rename from src/react-app/routes/game/tower/components/TowerRewards.tsx rename to apps/web/src/routes/game/tower/components/TowerRewards.tsx index 29b0b5ba..971020a3 100644 --- a/src/react-app/routes/game/tower/components/TowerRewards.tsx +++ b/apps/web/src/routes/game/tower/components/TowerRewards.tsx @@ -7,10 +7,10 @@ import { GameIcon } from '@app/components/ui/GameIcon'; import type { TowerRewardPreview, TowerView, -} from '@shared/contracts/combatV6Tower'; -import type { ItemGrant } from '@shared/inventory'; -import { itemDefinition } from '@shared/inventory'; -import { materialFactsOf } from '@shared/items/material'; +} from '@daoyou/shared/contracts/combatV6Tower'; +import type { ItemGrant } from '@daoyou/shared/inventory'; +import { itemDefinition } from '@daoyou/shared/inventory'; +import { materialFactsOf } from '@daoyou/shared/items/material'; import { useState } from 'react'; function rewardItem(item: ItemGrant): DisplayItem { diff --git a/src/react-app/routes/game/tower/components/TowerWeeklyEnemies.tsx b/apps/web/src/routes/game/tower/components/TowerWeeklyEnemies.tsx similarity index 97% rename from src/react-app/routes/game/tower/components/TowerWeeklyEnemies.tsx rename to apps/web/src/routes/game/tower/components/TowerWeeklyEnemies.tsx index 75f72bbf..738a1b6a 100644 --- a/src/react-app/routes/game/tower/components/TowerWeeklyEnemies.tsx +++ b/apps/web/src/routes/game/tower/components/TowerWeeklyEnemies.tsx @@ -1,7 +1,7 @@ import { GameIcon } from '@app/components/ui/GameIcon'; import { InkTag } from '@app/components/ui/InkTag'; -import type { TowerView } from '@shared/contracts/combatV6Tower'; -import type { TowerEnemyRole } from '@shared/lib/tower/formations'; +import type { TowerView } from '@daoyou/shared/contracts/combatV6Tower'; +import type { TowerEnemyRole } from '@daoyou/shared/lib/tower/formations'; import { useState } from 'react'; const ROLE_LABELS: Record = { diff --git a/src/react-app/routes/game/tower/route.tsx b/apps/web/src/routes/game/tower/route.tsx similarity index 97% rename from src/react-app/routes/game/tower/route.tsx rename to apps/web/src/routes/game/tower/route.tsx index b1aa540a..438db8b7 100644 --- a/src/react-app/routes/game/tower/route.tsx +++ b/apps/web/src/routes/game/tower/route.tsx @@ -6,13 +6,13 @@ import { GameSceneFrame } from '@app/components/game-shell'; import { GameIcon } from '@app/components/ui/GameIcon'; import { InkButton } from '@app/components/ui/InkButton'; import { InkTooltip } from '@app/components/ui/InkTooltip'; -import type { TowerView } from '@shared/contracts/combatV6Tower'; -import type { TowerBlessingId } from '@shared/lib/tower/blessings'; -import { getTowerBlessingDefinition } from '@shared/lib/tower/blessings'; -import { TOWER_MIN_REALM } from '@shared/lib/tower/helpers'; -import type { TowerLeaderboardEntry } from '@shared/lib/tower/types'; -import type { TowerEnemyPreview } from '@shared/lib/tower/weekly'; -import type { RealmType } from '@shared/types/constants'; +import type { TowerView } from '@daoyou/shared/contracts/combatV6Tower'; +import type { TowerBlessingId } from '@daoyou/shared/lib/tower/blessings'; +import { getTowerBlessingDefinition } from '@daoyou/shared/lib/tower/blessings'; +import { TOWER_MIN_REALM } from '@daoyou/shared/lib/tower/helpers'; +import type { TowerLeaderboardEntry } from '@daoyou/shared/lib/tower/types'; +import type { TowerEnemyPreview } from '@daoyou/shared/lib/tower/weekly'; +import type { RealmType } from '@daoyou/shared/types/constants'; import { useEffect, useRef, useState } from 'react'; import { Navigate } from 'react-router'; import { TowerLeaderboard } from './components/TowerLeaderboard'; diff --git a/src/react-app/routes/game/training-room/route.tsx b/apps/web/src/routes/game/training-room/route.tsx similarity index 99% rename from src/react-app/routes/game/training-room/route.tsx rename to apps/web/src/routes/game/training-room/route.tsx index 462cb503..bdeecb92 100644 --- a/src/react-app/routes/game/training-room/route.tsx +++ b/apps/web/src/routes/game/training-room/route.tsx @@ -11,7 +11,7 @@ import { useSectCombatState } from '@app/lib/resources/player'; import type { CombatV6TrainingSessionViewV1, SectCombatView, -} from '@shared/contracts/combatV6'; +} from '@daoyou/shared/contracts/combatV6'; import { useEffect, useState } from 'react'; type ContentView = { diff --git a/src/react-app/routes/game/wild/WildSeekingScene.tsx b/apps/web/src/routes/game/wild/WildSeekingScene.tsx similarity index 97% rename from src/react-app/routes/game/wild/WildSeekingScene.tsx rename to apps/web/src/routes/game/wild/WildSeekingScene.tsx index 9672ad41..0d3e6583 100644 --- a/src/react-app/routes/game/wild/WildSeekingScene.tsx +++ b/apps/web/src/routes/game/wild/WildSeekingScene.tsx @@ -4,9 +4,9 @@ import { GameLoadingState } from '@app/components/game-shell/GameLoadingState'; import { GameIcon } from '@app/components/ui/GameIcon'; import { InkButton } from '@app/components/ui/InkButton'; import { InkTag } from '@app/components/ui/InkTag'; -import type { WildRegionView } from '@shared/contracts/combatV6Wild'; -import { BEAST_SPECIES } from '@shared/engine/combat-v6/beasts/content'; -import { getMapNode } from '@shared/lib/game/mapSystem'; +import type { WildRegionView } from '@daoyou/shared/contracts/combatV6Wild'; +import { BEAST_SPECIES } from '@daoyou/shared/engine/combat-v6/beasts/content'; +import { getMapNode } from '@daoyou/shared/lib/game/mapSystem'; import './wild-seeking.css'; export function WildSeekingScene({ diff --git a/src/react-app/routes/game/wild/route.tsx b/apps/web/src/routes/game/wild/route.tsx similarity index 97% rename from src/react-app/routes/game/wild/route.tsx rename to apps/web/src/routes/game/wild/route.tsx index ef327b27..dd7254f1 100644 --- a/src/react-app/routes/game/wild/route.tsx +++ b/apps/web/src/routes/game/wild/route.tsx @@ -21,11 +21,11 @@ import type { WildEncounterView, WildRegionView, WildSessionView, -} from '@shared/contracts/combatV6Wild'; -import { combatCharacterLevel } from '@shared/engine/combat-v6/projection/character-level'; -import { itemDefinition } from '@shared/inventory'; -import { InventoryEquipmentSchema } from '@shared/inventory/equipment'; -import { REALM_ORDER } from '@shared/types/constants'; +} from '@daoyou/shared/contracts/combatV6Wild'; +import { combatCharacterLevel } from '@daoyou/shared/engine/combat-v6/projection/character-level'; +import { itemDefinition } from '@daoyou/shared/inventory'; +import { InventoryEquipmentSchema } from '@daoyou/shared/inventory/equipment'; +import { REALM_ORDER } from '@daoyou/shared/types/constants'; import { useCallback, useEffect, useRef, useState } from 'react'; import { Link, useLocation, useSearchParams } from 'react-router'; import { WildSeekingScene } from './WildSeekingScene'; diff --git a/src/react-app/routes/game/wild/wild-seeking.css b/apps/web/src/routes/game/wild/wild-seeking.css similarity index 100% rename from src/react-app/routes/game/wild/wild-seeking.css rename to apps/web/src/routes/game/wild/wild-seeking.css diff --git a/src/react-app/routes/game/world-chat/route.tsx b/apps/web/src/routes/game/world-chat/route.tsx similarity index 100% rename from src/react-app/routes/game/world-chat/route.tsx rename to apps/web/src/routes/game/world-chat/route.tsx diff --git a/src/react-app/routes/index/route.tsx b/apps/web/src/routes/index/route.tsx similarity index 100% rename from src/react-app/routes/index/route.tsx rename to apps/web/src/routes/index/route.tsx diff --git a/src/react-app/routes/login/email/route.tsx b/apps/web/src/routes/login/email/route.tsx similarity index 100% rename from src/react-app/routes/login/email/route.tsx rename to apps/web/src/routes/login/email/route.tsx diff --git a/src/react-app/routes/login/password/route.tsx b/apps/web/src/routes/login/password/route.tsx similarity index 100% rename from src/react-app/routes/login/password/route.tsx rename to apps/web/src/routes/login/password/route.tsx diff --git a/src/react-app/routes/login/route.tsx b/apps/web/src/routes/login/route.tsx similarity index 100% rename from src/react-app/routes/login/route.tsx rename to apps/web/src/routes/login/route.tsx diff --git a/src/react-app/routes/login/verify/route.tsx b/apps/web/src/routes/login/verify/route.tsx similarity index 100% rename from src/react-app/routes/login/verify/route.tsx rename to apps/web/src/routes/login/verify/route.tsx diff --git a/src/react-app/routes/not-found.tsx b/apps/web/src/routes/not-found.tsx similarity index 100% rename from src/react-app/routes/not-found.tsx rename to apps/web/src/routes/not-found.tsx diff --git a/src/react-app/routes/reset-password/route.tsx b/apps/web/src/routes/reset-password/route.tsx similarity index 100% rename from src/react-app/routes/reset-password/route.tsx rename to apps/web/src/routes/reset-password/route.tsx diff --git a/src/react-app/routes/signup/password/route.tsx b/apps/web/src/routes/signup/password/route.tsx similarity index 100% rename from src/react-app/routes/signup/password/route.tsx rename to apps/web/src/routes/signup/password/route.tsx diff --git a/src/react-app/routes/signup/route.tsx b/apps/web/src/routes/signup/route.tsx similarity index 100% rename from src/react-app/routes/signup/route.tsx rename to apps/web/src/routes/signup/route.tsx diff --git a/src/react-app/vite-env.d.ts b/apps/web/src/vite-env.d.ts similarity index 100% rename from src/react-app/vite-env.d.ts rename to apps/web/src/vite-env.d.ts diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json new file mode 100644 index 00000000..ac5740e2 --- /dev/null +++ b/apps/web/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "tsBuildInfoFile": "../../node_modules/.tmp/web.tsbuildinfo", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "useDefineForClassFields": true, + "allowImportingTsExtensions": true, + "noEmit": true, + "jsx": "react-jsx", + "types": ["vite/client", "node"], + "paths": { + "@app/*": ["./src/*"], + "@daoyou/shared/*": ["../../packages/shared/src/*"] + } + }, + "include": ["src", "../../packages/shared/src"], + "exclude": [ + "**/*.test.ts", + "**/*.test.tsx", + "**/*.spec.ts", + "../../packages/shared/src/**/*.test.ts", + "../../packages/shared/src/**/*.test.tsx", + "../../packages/shared/src/**/*.spec.ts" + ] +} diff --git a/vite.config.ts b/apps/web/vite.config.ts similarity index 86% rename from vite.config.ts rename to apps/web/vite.config.ts index 1ac1104c..50f55935 100644 --- a/vite.config.ts +++ b/apps/web/vite.config.ts @@ -5,9 +5,10 @@ import { fileURLToPath } from 'node:url'; import { defineConfig, type Plugin, type UserConfig } from 'vite'; const alias = { - '@app': fileURLToPath(new URL('./src/react-app', import.meta.url)), - '@server': fileURLToPath(new URL('./src/server', import.meta.url)), - '@shared': fileURLToPath(new URL('./src/shared', import.meta.url)), + '@app': fileURLToPath(new URL('./src', import.meta.url)), + '@daoyou/shared': fileURLToPath( + new URL('../../packages/shared/src', import.meta.url), + ), }; const devApiTarget = () => `http://127.0.0.1:${process.env.PORT ?? 3000}`; @@ -28,6 +29,7 @@ const appVersionManifestPlugin = (buildId: string): Plugin => ({ export default defineConfig((): UserConfig => { const buildId = createBuildId(); return { + envDir: false, resolve: { alias }, define: { __APP_BUILD_ID__: JSON.stringify(buildId) }, plugins: [react(), tailwindcss(), appVersionManifestPlugin(buildId)], diff --git a/bun.lock b/bun.lock deleted file mode 100644 index a6b11629..00000000 --- a/bun.lock +++ /dev/null @@ -1,1086 +0,0 @@ -{ - "lockfileVersion": 1, - "configVersion": 1, - "workspaces": { - "": { - "name": "daoyou-hono", - "dependencies": { - "@ai-sdk/alibaba": "^2.0.45", - "@ai-sdk/deepseek": "^3.0.44", - "@better-auth/drizzle-adapter": "^1.7.4", - "@better-auth/i18n": "^1.7.4", - "@microfleet/ioredis-lock": "^6.0.1", - "@tailwindcss/vite": "^4.3.3", - "ai": "^7.0.99", - "altcha": "^3.2.2", - "altcha-lib": "^2.5.0", - "better-auth": "^1.7.4", - "canvas-confetti": "^1.9.4", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "d3-format": "^3.1.2", - "drizzle-orm": "^0.45.2", - "hono": "4.13.7", - "ioredis": "^5.11.1", - "nats": "^2.29.3", - "nodemailer": "^9.1.1", - "pg": "^8.23.0", - "phaser": "^4.2.1", - "phaser4-rex-plugins": "^4.2.0", - "react": "19.3.0", - "react-dom": "19.3.0", - "react-router": "^8.3.1", - "react-zoom-pan-pinch": "^4.2.0", - "tailwind-merge": "^3.7.0", - "tailwindcss": "^4.3.3", - "zod": "^4.6.4", - }, - "devDependencies": { - "@eslint/js": "10.0.1", - "@hono/vite-build": "1.11.1", - "@types/bun": "1.4.2", - "@types/canvas-confetti": "^1.9.0", - "@types/d3-format": "^3.0.4", - "@types/node": "^26.5.1", - "@types/nodemailer": "^8.0.1", - "@types/pg": "^8.23.1", - "@types/react": "19.3.0", - "@types/react-dom": "19.3.0", - "@vitejs/plugin-react": "6.1.1", - "drizzle-kit": "^0.31.10", - "eslint": "10.10.0", - "eslint-plugin-react-hooks": "7.1.1", - "eslint-plugin-react-refresh": "0.5.6", - "globals": "17.12.0", - "prettier": "^3.9.6", - "prettier-plugin-organize-imports": "^4.3.0", - "prettier-plugin-packagejson": "^3.0.2", - "prettier-plugin-tailwindcss": "^0.8.1", - "sponsorkit": "^18.0.0", - "typescript": "^6.0.3", - "typescript-eslint": "8.70.0", - "vite": "^8.3.0", - "vitest": "^4.1.11", - }, - }, - }, - "packages": { - "@ai-sdk/alibaba": ["@ai-sdk/alibaba@2.0.45", "", { "dependencies": { "@ai-sdk/provider": "4.0.14", "@ai-sdk/provider-utils": "5.0.40" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-vMYkwnqywhh3hBMSLp+U+WcyO631inuhMSzpwrrIYBqGUaSdNVrOaHPPCGjxwvPsPWKNd4gKC0EdOfHcIfDjmA=="], - - "@ai-sdk/deepseek": ["@ai-sdk/deepseek@3.0.44", "", { "dependencies": { "@ai-sdk/provider": "4.0.14", "@ai-sdk/provider-utils": "5.0.40" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-yhHGVJFpM5tdhhr33lE6Co8ziu8dNZxk7j5r3L3Pc04NMCTDmnXZ072FY4tqRDsRtIfBEgWqm/zPXNRvdHb44w=="], - - "@ai-sdk/gateway": ["@ai-sdk/gateway@4.0.80", "", { "dependencies": { "@ai-sdk/provider": "4.0.14", "@ai-sdk/provider-utils": "5.0.40", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-6t+07o8lSthpKf64Xb1qHWR2bWvJ3Fd2oFvS9fQc45p31bi2OUqan246e/ojAmZpWCiMPjKyQ4TBr4MYytnTiQ=="], - - "@ai-sdk/provider": ["@ai-sdk/provider@4.0.14", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-yukP2tbcQQErG5gLCMBGvpvb/rM3D3KlTKG6eKKOdNHLHqtNNDEeBxdYrY/JL+O76B2ig5dXY19H/f1HFSvRiQ=="], - - "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@5.0.40", "", { "dependencies": { "@ai-sdk/provider": "4.0.14", "@standard-schema/spec": "^1.1.0", "@workflow/serde": "4.1.0", "eventsource-parser": "^3.0.8", "undici": "^7.29.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-zsXPwSAQ9mRJ2hvyITaLOYUyuGrBmzFhJXOg4mllGmla1PfNxZcm4GwqMiV2xQaDgcgBMdUGxXkp9xekZZNIkg=="], - - "@babel/code-frame": ["@babel/code-frame@7.29.0", "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.0.tgz", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], - - "@babel/compat-data": ["@babel/compat-data@7.29.0", "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.0.tgz", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="], - - "@babel/core": ["@babel/core@7.29.0", "https://registry.npmmirror.com/@babel/core/-/core-7.29.0.tgz", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="], - - "@babel/generator": ["@babel/generator@7.29.1", "https://registry.npmmirror.com/@babel/generator/-/generator-7.29.1.tgz", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], - - "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], - - "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], - - "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], - - "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="], - - "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], - - "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], - - "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], - - "@babel/helpers": ["@babel/helpers@7.29.2", "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.29.2.tgz", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="], - - "@babel/parser": ["@babel/parser@7.29.2", "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.2.tgz", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="], - - "@babel/runtime": ["@babel/runtime@7.29.7", "", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="], - - "@babel/template": ["@babel/template@7.28.6", "https://registry.npmmirror.com/@babel/template/-/template-7.28.6.tgz", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], - - "@babel/traverse": ["@babel/traverse@7.29.0", "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.29.0.tgz", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], - - "@babel/types": ["@babel/types@7.29.0", "https://registry.npmmirror.com/@babel/types/-/types-7.29.0.tgz", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], - - "@better-auth/core": ["@better-auth/core@1.7.4", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.41.1", "@standard-schema/spec": "^1.1.0", "zod": "^4.5.4" }, "peerDependencies": { "@better-auth/utils": "0.4.2", "@better-fetch/fetch": "1.3.1", "@cloudflare/workers-types": ">=4", "@opentelemetry/api": "^1.9.0", "better-call": "1.4.0", "jose": "^6.1.0", "kysely": "^0.28.5 || ^0.29.0", "nanostores": "^1.0.1" }, "optionalPeers": ["@cloudflare/workers-types", "@opentelemetry/api"] }, "sha512-g66dKOMB5PLxoujkxkZeYb6dh5wLhfAx/D+w9XtcO669Dslnyen2naBYqnD1r5ZwnxFKcr6aSrUPaH952V89Lg=="], - - "@better-auth/drizzle-adapter": ["@better-auth/drizzle-adapter@1.7.4", "", { "peerDependencies": { "@better-auth/core": "^1.7.4", "@better-auth/utils": "0.4.2", "drizzle-orm": "^0.45.2 || >=1.0.0-rc.1 <2.0.0" }, "optionalPeers": ["drizzle-orm"] }, "sha512-J9JMl0DGYNYwtDj5nq/fzycU9dzPUDEtTNn89KmToUrwPDGfByDgfmiQ9jGK+LN3EnfsG1Pt/dDmRocbnfihlA=="], - - "@better-auth/i18n": ["@better-auth/i18n@1.7.4", "", { "peerDependencies": { "@better-auth/core": "^1.7.4", "better-auth": "^1.7.4" } }, "sha512-939oFPv6hgR1Bhnr3v3KqH1VsA+KUyY9BAHg0ExsGVHj0Go+lKcrIA1Kl7swFQQCxDaPT+GNA1KicNfMZ4Z0/Q=="], - - "@better-auth/kysely-adapter": ["@better-auth/kysely-adapter@1.7.4", "", { "peerDependencies": { "@better-auth/core": "^1.7.4", "@better-auth/utils": "0.4.2", "kysely": "^0.28.17 || ^0.29.0" }, "optionalPeers": ["kysely"] }, "sha512-WHLsTs4ZwTKorifdK6Whol6Zy+0gIzOJEXzWsvzmLtlT1uIZFpd7IPBlDZTrXvIZnxPWJHPIv8/uHqS9QFPZOA=="], - - "@better-auth/memory-adapter": ["@better-auth/memory-adapter@1.7.4", "", { "peerDependencies": { "@better-auth/core": "^1.7.4", "@better-auth/utils": "0.4.2" } }, "sha512-xAihOPh4mFjvKLzmu2XNzj7RchDmytA/d/xjrGyps1oHVGCTGDGghlSt/rIT5fagtTn2R9+TBLh5QrZnTvYu9g=="], - - "@better-auth/mongo-adapter": ["@better-auth/mongo-adapter@1.7.4", "", { "peerDependencies": { "@better-auth/core": "^1.7.4", "@better-auth/utils": "0.4.2", "mongodb": "^6.0.0 || ^7.0.0" }, "optionalPeers": ["mongodb"] }, "sha512-sIdIc8vONXgQxKPfzL4bbk6gO3gdFEDzC5VG8rD18xSFB6j7blBKcuzOofbswbwwbyuspnshoViGRqSoqp2PnA=="], - - "@better-auth/prisma-adapter": ["@better-auth/prisma-adapter@1.7.4", "", { "peerDependencies": { "@better-auth/core": "^1.7.4", "@better-auth/utils": "0.4.2", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@prisma/client", "prisma"] }, "sha512-cvsMNaXLcwQiAIxQCx5Gq2VA1reW4RjkC5oLNXNJR4IhgeiqcvYF6qKF6UEC0vV2x3euKz5V/SDY/hblHWwx+w=="], - - "@better-auth/telemetry": ["@better-auth/telemetry@1.7.4", "", { "peerDependencies": { "@better-auth/core": "^1.7.4", "@better-auth/utils": "0.4.2", "@better-fetch/fetch": "1.3.1" } }, "sha512-FycnvWXP1gZ8BlSN1lW9L/YYFVqxi7+n6A9i66gJKdoJqREDdmhxBKOaVCbNhNC2UNDAo9PbBm/6J3gv6WmAEA=="], - - "@better-auth/utils": ["@better-auth/utils@0.4.2", "https://registry.npmmirror.com/@better-auth/utils/-/utils-0.4.2.tgz", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-AUxrvu+HaaODsUyzDxFgwd/8RZ1yZaYo42LXKSrU2oGgR38pS1ij8nqQKNgtTWoYGpNevNXtCfgTy6loHveW9A=="], - - "@better-fetch/fetch": ["@better-fetch/fetch@1.3.1", "https://registry.npmmirror.com/@better-fetch/fetch/-/fetch-1.3.1.tgz", {}, "sha512-ABkD1WhyfPZprKRQI3bhATjeiFuNWC9PXhfGWqL+sg/gKrM977oFrYkdb4msM3hgUGonr7KlOsOFT5TU2rht9g=="], - - "@cacheable/memory": ["@cacheable/memory@2.2.0", "", { "dependencies": { "@cacheable/utils": "^2.5.0", "@keyv/bigmap": "^1.3.1", "hookified": "^1.15.1", "keyv": "^5.6.0" } }, "sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ=="], - - "@cacheable/utils": ["@cacheable/utils@2.5.0", "", { "dependencies": { "hashery": "^1.5.1", "keyv": "^5.6.0" } }, "sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA=="], - - "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "https://registry.npmmirror.com/@drizzle-team/brocli/-/brocli-0.10.2.tgz", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], - - "@emnapi/runtime": ["@emnapi/runtime@1.11.1", "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.11.1.tgz", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="], - - "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "https://registry.npmmirror.com/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="], - - "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "https://registry.npmmirror.com/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="], - - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], - - "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], - - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], - - "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], - - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], - - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], - - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], - - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], - - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], - - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], - - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], - - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], - - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], - - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], - - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], - - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], - - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], - - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], - - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], - - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], - - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], - - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], - - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], - - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], - - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], - - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], - - "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], - - "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "https://registry.npmmirror.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], - - "@eslint/config-array": ["@eslint/config-array@0.23.5", "https://registry.npmmirror.com/@eslint/config-array/-/config-array-0.23.5.tgz", { "dependencies": { "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", "minimatch": "^10.2.4" } }, "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA=="], - - "@eslint/config-helpers": ["@eslint/config-helpers@0.7.0", "https://registry.npmmirror.com/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", { "dependencies": { "@eslint/core": "^1.2.1" } }, "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw=="], - - "@eslint/core": ["@eslint/core@1.2.1", "https://registry.npmmirror.com/@eslint/core/-/core-1.2.1.tgz", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ=="], - - "@eslint/js": ["@eslint/js@10.0.1", "https://registry.npmmirror.com/@eslint/js/-/js-10.0.1.tgz", { "peerDependencies": { "eslint": "^10.0.0" }, "optionalPeers": ["eslint"] }, "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA=="], - - "@eslint/object-schema": ["@eslint/object-schema@3.0.5", "https://registry.npmmirror.com/@eslint/object-schema/-/object-schema-3.0.5.tgz", {}, "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw=="], - - "@eslint/plugin-kit": ["@eslint/plugin-kit@0.7.3", "", { "dependencies": { "@eslint/core": "^1.2.1", "levn": "^0.4.1" } }, "sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q=="], - - "@hono/vite-build": ["@hono/vite-build@1.11.1", "https://registry.npmmirror.com/@hono/vite-build/-/vite-build-1.11.1.tgz", { "peerDependencies": { "hono": "*" } }, "sha512-EQJmFL7G+71MXZQ/mmywNl4qW6DSyjTdC30cq7mlDS451a1/jE86old0M2DCWzNGCO50sv7CzQDYkz4L5dq5VQ=="], - - "@humanfs/core": ["@humanfs/core@0.19.2", "https://registry.npmmirror.com/@humanfs/core/-/core-0.19.2.tgz", { "dependencies": { "@humanfs/types": "^0.15.0" } }, "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA=="], - - "@humanfs/node": ["@humanfs/node@0.16.8", "https://registry.npmmirror.com/@humanfs/node/-/node-0.16.8.tgz", { "dependencies": { "@humanfs/core": "^0.19.2", "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ=="], - - "@humanfs/types": ["@humanfs/types@0.15.0", "https://registry.npmmirror.com/@humanfs/types/-/types-0.15.0.tgz", {}, "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q=="], - - "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "https://registry.npmmirror.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], - - "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "https://registry.npmmirror.com/@humanwhocodes/retry/-/retry-0.4.3.tgz", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], - - "@img/colour": ["@img/colour@1.1.0", "https://registry.npmmirror.com/@img/colour/-/colour-1.1.0.tgz", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], - - "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.35.3", "https://registry.npmmirror.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.3.tgz", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.3.2" }, "os": "darwin", "cpu": "arm64" }, "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg=="], - - "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.35.3", "https://registry.npmmirror.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.3.tgz", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.3.2" }, "os": "darwin", "cpu": "x64" }, "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w=="], - - "@img/sharp-freebsd-wasm32": ["@img/sharp-freebsd-wasm32@0.35.3", "https://registry.npmmirror.com/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.3.tgz", { "dependencies": { "@img/sharp-wasm32": "0.35.3" }, "os": "freebsd" }, "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg=="], - - "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.3.2", "https://registry.npmmirror.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.2.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg=="], - - "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.3.2", "https://registry.npmmirror.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.2.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw=="], - - "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.3.2", "https://registry.npmmirror.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.2.tgz", { "os": "linux", "cpu": "arm" }, "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ=="], - - "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.3.2", "https://registry.npmmirror.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.2.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA=="], - - "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.3.2", "https://registry.npmmirror.com/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.2.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw=="], - - "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.3.2", "https://registry.npmmirror.com/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.2.tgz", { "os": "linux", "cpu": "none" }, "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w=="], - - "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.3.2", "https://registry.npmmirror.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.2.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ=="], - - "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.3.2", "https://registry.npmmirror.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.2.tgz", { "os": "linux", "cpu": "x64" }, "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w=="], - - "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.3.2", "https://registry.npmmirror.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.2.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw=="], - - "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.3.2", "https://registry.npmmirror.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.2.tgz", { "os": "linux", "cpu": "x64" }, "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ=="], - - "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.35.3", "https://registry.npmmirror.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.3.tgz", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.3.2" }, "os": "linux", "cpu": "arm" }, "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA=="], - - "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.35.3", "https://registry.npmmirror.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.3.tgz", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.3.2" }, "os": "linux", "cpu": "arm64" }, "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ=="], - - "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.35.3", "https://registry.npmmirror.com/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.3.tgz", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.3.2" }, "os": "linux", "cpu": "ppc64" }, "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA=="], - - "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.35.3", "https://registry.npmmirror.com/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.3.tgz", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.3.2" }, "os": "linux", "cpu": "none" }, "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ=="], - - "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.35.3", "https://registry.npmmirror.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.3.tgz", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.3.2" }, "os": "linux", "cpu": "s390x" }, "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw=="], - - "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.35.3", "https://registry.npmmirror.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.3.tgz", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.3.2" }, "os": "linux", "cpu": "x64" }, "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA=="], - - "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.35.3", "https://registry.npmmirror.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.3.tgz", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.3.2" }, "os": "linux", "cpu": "arm64" }, "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w=="], - - "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.35.3", "https://registry.npmmirror.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.3.tgz", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.3.2" }, "os": "linux", "cpu": "x64" }, "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg=="], - - "@img/sharp-wasm32": ["@img/sharp-wasm32@0.35.3", "https://registry.npmmirror.com/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz", { "dependencies": { "@emnapi/runtime": "^1.11.1" } }, "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w=="], - - "@img/sharp-webcontainers-wasm32": ["@img/sharp-webcontainers-wasm32@0.35.3", "https://registry.npmmirror.com/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.3.tgz", { "dependencies": { "@img/sharp-wasm32": "0.35.3" }, "cpu": "none" }, "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q=="], - - "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.35.3", "https://registry.npmmirror.com/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.3.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w=="], - - "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.35.3", "https://registry.npmmirror.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.3.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw=="], - - "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.35.3", "https://registry.npmmirror.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz", { "os": "win32", "cpu": "x64" }, "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA=="], - - "@ioredis/commands": ["@ioredis/commands@1.10.0", "https://registry.npmmirror.com/@ioredis/commands/-/commands-1.10.0.tgz", {}, "sha512-UmeW7z4LfctwoQ5wkhVzgq8tXkreED2xZGpX+Bg+zA+WJFZCT6c062AfCK/Dfk81xZnnwdhJCUMkitihRaoC2Q=="], - - "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], - - "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], - - "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], - - "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], - - "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - - "@keyv/bigmap": ["@keyv/bigmap@1.3.1", "", { "dependencies": { "hashery": "^1.4.0", "hookified": "^1.15.0" }, "peerDependencies": { "keyv": "^5.6.0" } }, "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ=="], - - "@keyv/serialize": ["@keyv/serialize@1.1.1", "", {}, "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA=="], - - "@microfleet/ioredis-lock": ["@microfleet/ioredis-lock@6.0.1", "https://registry.npmmirror.com/@microfleet/ioredis-lock/-/ioredis-lock-6.0.1.tgz", { "peerDependencies": { "ioredis": "~4.x.x || ~5.x.x" } }, "sha512-nd4pKn4GMH3N+DrCm9R/AW7nzm4oLxtmX94HvkR94ReJdpDrP54aD0bA5rjVLZarCzTpHMxGDiH8sfsvDtsjTg=="], - - "@noble/ciphers": ["@noble/ciphers@2.2.0", "https://registry.npmmirror.com/@noble/ciphers/-/ciphers-2.2.0.tgz", {}, "sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA=="], - - "@noble/hashes": ["@noble/hashes@2.2.0", "https://registry.npmmirror.com/@noble/hashes/-/hashes-2.2.0.tgz", {}, "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg=="], - - "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "https://registry.npmmirror.com/@opentelemetry/api/-/api-1.9.0.tgz", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], - - "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.43.0", "", {}, "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg=="], - - "@oxc-project/types": ["@oxc-project/types@0.149.0", "", {}, "sha512-Efcc+iF0j3Bf67YjEqIqWXbX5XddXoK/Mw4K1/JuXwRCZ8N16VR7iT23nlCc9XrveFVh/E5Rqs2StT0V8v9LdA=="], - - "@quansync/fs": ["@quansync/fs@1.0.0", "https://registry.npmmirror.com/@quansync/fs/-/fs-1.0.0.tgz", { "dependencies": { "quansync": "^1.0.0" } }, "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ=="], - - "@rolldown/binding-android-arm-eabi": ["@rolldown/binding-android-arm-eabi@1.2.8", "", { "os": "android", "cpu": "arm" }, "sha512-tN5aztYkKCte4i5SIrrz5yK/HMjEuCqCSCJa418jOV8tZ1cBY3YF2otxB1ktPxzsLA1BeTqwapK0bfjxNvHJVw=="], - - "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.8", "", { "os": "android", "cpu": "arm64" }, "sha512-dIYTWl9XprMUiQFoc55KUyk/oS8SKYH3zFl0LTR7RT0Xj4hgSVyuJcroH8JUu8RcpF8fTB6E0aOwCkZoYPcDSQ=="], - - "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-PCSDQGXD2IyTEFrcgPyBM8jJuGmrbCMuoIOXdbEGVemruKACXoLQJrb+A45Z0L5t1RQkdfJprAYPkikbh7dzdA=="], - - "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-Uk7lRsGhPFHVX/sAUC6D5H9Ol30dFHd6iquokll2th3LpdJ3F5CzQB+7DHn0Ri2mG+U7k2zXiPHDrwZenXhwSA=="], - - "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.8", "", { "os": "freebsd", "cpu": "x64" }, "sha512-DjszaTEVogPqA5bYzsEeqDCQxbcp2fexQwKcRspYji2yzR68fCf+e4fx6kBSRDwX5/brZaHw/hWS9+A/+/w9sQ=="], - - "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.8", "", { "os": "linux", "cpu": "arm" }, "sha512-zmwa7FTmdzB6aaEEuuls18H6Ap5JmJPSoPTuXixeJZV6tG40SyLkApQtz1g8ptZtiEKqj9OM0oNLPh1AgvE31Q=="], - - "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-KdYQDPHwJVnbFwdTGMgxsI9SqblBlz6STGM+w1We/d5B8OWWidYH0MwkU/uA1wM5fIpO2MkOVxXrNzzuZhw9ew=="], - - "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-jFJTifHnNPY+yzOoNZQfSIysrVyXzEQPhPnOUjmD1bcQGHH6s7c8cViKWar8YplQImE5N9JRqMCLrM2CdxOrZA=="], - - "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.8", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FhiOziBDWPBjbcmRzfLyIJnaP7AVMFXT7YCXPjXxj7wKU3vx24RjrCNN/zjvVa+N2vVoHJwCoUBvsrN/DG3zIA=="], - - "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.8", "", { "os": "linux", "cpu": "s390x" }, "sha512-WnHfADMzOV2Y55wlx1hzzQnar/wDt/VdvWSD99r18Mz9ylNieIGOkRx3UV21h7m/eJvjySYJkO26VvGNFkwsIQ=="], - - "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.8", "", { "os": "linux", "cpu": "x64" }, "sha512-H9tRr5ibfXFVLxbPOseVewewFpl28zcEdjRDt2FTUZU7odxP0gEv1ki4/kGmcGOh78oRwZuuQllGLZ9zTJp84g=="], - - "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.8", "", { "os": "linux", "cpu": "x64" }, "sha512-UefiqfM3D6IVNlZ8tSGs9+Ejjud2T+oxO0IHADU45Y+lyEjD2dVFyZHbkfX0LUb5Zugo/oIv1eCO/KVYhgYJYA=="], - - "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.8", "", { "os": "none", "cpu": "arm64" }, "sha512-637Ke4kWSy6rp9cxQ9gMOXlxPgIw/c1beASV4M//3+9I4uwBVOOl74G+e3zyU3u19U7RkRl/HuewixZ/Z6+Rjg=="], - - "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-xWBkPOF1Q9k/Gv1nQXnVdLxKu74jXppuOM4Z3mnypVUJJJwLsMl7hNJGRAUJoG8A5MgOI1ACKM+wBFxSJzKy4A=="], - - "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.8", "", { "os": "win32", "cpu": "x64" }, "sha512-uz2ZvfgXbxqNwijjjbxrnvALwpyODDcgc1T1N8N3rf/DXKQmaFwmB4LX4yyjggpwN2obdQLb2rgirX5ffCWYng=="], - - "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], - - "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.1.0.tgz", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - - "@tailwindcss/node": ["@tailwindcss/node@4.3.3", "https://registry.npmmirror.com/@tailwindcss/node/-/node-4.3.3.tgz", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.24.1", "jiti": "^2.7.0", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.3.3" } }, "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg=="], - - "@tailwindcss/oxide": ["@tailwindcss/oxide@4.3.3", "https://registry.npmmirror.com/@tailwindcss/oxide/-/oxide-4.3.3.tgz", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.3.3", "@tailwindcss/oxide-darwin-arm64": "4.3.3", "@tailwindcss/oxide-darwin-x64": "4.3.3", "@tailwindcss/oxide-freebsd-x64": "4.3.3", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", "@tailwindcss/oxide-linux-x64-musl": "4.3.3", "@tailwindcss/oxide-wasm32-wasi": "4.3.3", "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" } }, "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA=="], - - "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.3.3", "https://registry.npmmirror.com/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", { "os": "android", "cpu": "arm64" }, "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw=="], - - "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.3.3", "https://registry.npmmirror.com/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw=="], - - "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.3.3", "https://registry.npmmirror.com/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw=="], - - "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.3.3", "https://registry.npmmirror.com/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw=="], - - "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3", "https://registry.npmmirror.com/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", { "os": "linux", "cpu": "arm" }, "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ=="], - - "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.3.3", "https://registry.npmmirror.com/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w=="], - - "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.3.3", "https://registry.npmmirror.com/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA=="], - - "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.3.3", "https://registry.npmmirror.com/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", { "os": "linux", "cpu": "x64" }, "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w=="], - - "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.3.3", "https://registry.npmmirror.com/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", { "os": "linux", "cpu": "x64" }, "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img=="], - - "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.3.3", "https://registry.npmmirror.com/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", { "dependencies": { "@emnapi/core": "^1.11.1", "@emnapi/runtime": "^1.11.1", "@emnapi/wasi-threads": "^1.2.2", "@napi-rs/wasm-runtime": "^1.1.4", "@tybys/wasm-util": "^0.10.2", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ=="], - - "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.3.3", "https://registry.npmmirror.com/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ=="], - - "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.3.3", "https://registry.npmmirror.com/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", { "os": "win32", "cpu": "x64" }, "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw=="], - - "@tailwindcss/vite": ["@tailwindcss/vite@4.3.3", "https://registry.npmmirror.com/@tailwindcss/vite/-/vite-4.3.3.tgz", { "dependencies": { "@tailwindcss/node": "4.3.3", "@tailwindcss/oxide": "4.3.3", "tailwindcss": "4.3.3" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw=="], - - "@types/bun": ["@types/bun@1.4.2", "", { "dependencies": { "bun-types": "1.4.2" } }, "sha512-GimotNn7+ZV0uVArItBbriZsR1oNf0+WTzPkdcFrzShI7k2norL0uzEaJT8T33dWr7O/c9ZDuAFQrctKCi72oQ=="], - - "@types/canvas-confetti": ["@types/canvas-confetti@1.9.0", "https://registry.npmmirror.com/@types/canvas-confetti/-/canvas-confetti-1.9.0.tgz", {}, "sha512-aBGj/dULrimR1XDZLtG9JwxX1b4HPRF6CX9Yfwh3NvstZEm1ZL7RBnel4keCPSqs1ANRu1u2Aoz9R+VmtjYuTg=="], - - "@types/chai": ["@types/chai@5.2.3", "https://registry.npmmirror.com/@types/chai/-/chai-5.2.3.tgz", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], - - "@types/d3-format": ["@types/d3-format@3.0.4", "https://registry.npmmirror.com/@types/d3-format/-/d3-format-3.0.4.tgz", {}, "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g=="], - - "@types/deep-eql": ["@types/deep-eql@4.0.2", "https://registry.npmmirror.com/@types/deep-eql/-/deep-eql-4.0.2.tgz", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], - - "@types/esrecurse": ["@types/esrecurse@4.3.1", "https://registry.npmmirror.com/@types/esrecurse/-/esrecurse-4.3.1.tgz", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="], - - "@types/estree": ["@types/estree@1.0.8", "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], - - "@types/json-schema": ["@types/json-schema@7.0.15", "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], - - "@types/node": ["@types/node@26.5.1", "", { "dependencies": { "undici-types": "~8.9.0" } }, "sha512-CzNm2FezW4VR/LjG6yUdiEgLE/rAQ9Slj5gCu/C2VrdcW7I0ahNZ8DRbHT7zOZ6r3ONgd/bsQIeSaoDGrd1C6g=="], - - "@types/nodemailer": ["@types/nodemailer@8.0.1", "https://registry.npmmirror.com/@types/nodemailer/-/nodemailer-8.0.1.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-PxpaInm8V1JQDd4j0ds5HfvWQk8JupS1C0Picb96QJsrrRDjBH+DlK7L4ZdNSqNULhiZRQHc40nLVShaGxXAMw=="], - - "@types/pg": ["@types/pg@8.23.1", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-fKVHpikPdg4GKks3JuLEhvwSyvwzF23hnabPy6DD8ljVbC7+6J5dQzdv4arV6jqq57djnMgs1HKBxX4P8aBI3A=="], - - "@types/react": ["@types/react@19.3.0", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg=="], - - "@types/react-dom": ["@types/react-dom@19.3.0", "", { "peerDependencies": { "@types/react": "^19.3.0" } }, "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q=="], - - "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.70.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.70.0", "@typescript-eslint/type-utils": "8.70.0", "@typescript-eslint/utils": "8.70.0", "@typescript-eslint/visitor-keys": "8.70.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.70.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-/v8HZt6RlyIZxB3ntehELOcUcfxKPVGWXnQdJuHRmzrqgF8nQypcC/oxGW+Ot4VGKDq81XugPKxx0n5PBtf9PA=="], - - "@typescript-eslint/parser": ["@typescript-eslint/parser@8.70.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.70.0", "@typescript-eslint/types": "8.70.0", "@typescript-eslint/typescript-estree": "8.70.0", "@typescript-eslint/visitor-keys": "8.70.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-zYvrmj9Yxd63UGaXw+kdt6A0F0s0qveJyuatIM77bYC2DE4pgmg7a50u8LR7PRtXd0x+h+Tl3eXabGm06SWd3Q=="], - - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.70.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.70.0", "@typescript-eslint/types": "^8.70.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-hFHbTNqhU9G+2eKFXCBVb1tjFT/LceiJ4+HfLO4pTpDI0KHi6iajpcFFkaSQ9gXmCh7n82A0PthaayEdN6mspQ=="], - - "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.70.0", "", { "dependencies": { "@typescript-eslint/types": "8.70.0", "@typescript-eslint/visitor-keys": "8.70.0" } }, "sha512-8nP3Kwh5hlgZ4FicGvmznAmJe8UL4sdU8tLukrPaMuQmDuk4Y8xYfzu/aYZW4xT2JCgc7H/TpDI5cGlxcWJSqQ=="], - - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.70.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-adnkeeNq9Sq1sUf4+FRVc0KdgYghzsgFpZSQVZVvY0LCuUuN0FnQgyGzCJeC4fW1cdXseBAjU2EOqUIjbNcZUw=="], - - "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.70.0", "", { "dependencies": { "@typescript-eslint/types": "8.70.0", "@typescript-eslint/typescript-estree": "8.70.0", "@typescript-eslint/utils": "8.70.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-NUMKIhYVaVIVLnRL9CRt+VVcuLgSHUCpXn4/+K8wql+vdInUzvx8BjUO1oJ7cG9shjFJKtF8F8Hh2kCh3/KBVw=="], - - "@typescript-eslint/types": ["@typescript-eslint/types@8.70.0", "", {}, "sha512-asTOIYhDg4zdzOScCyaytrsV3cR6B4ecPQlXw/dJIm7J/MZTtCtfVII9JD8Geh4jTCrK/Xe6cg5UevoleMcoJQ=="], - - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.70.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.70.0", "@typescript-eslint/tsconfig-utils": "8.70.0", "@typescript-eslint/types": "8.70.0", "@typescript-eslint/visitor-keys": "8.70.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-d9NmHMPEKQ7QCLLm1jI3zmoQBwT5KwFYjXBJ9ymZfKCUU+5rmTRykKAFvH5Qn/ZCds3CEAFS9OC9M/jkl0X2bA=="], - - "@typescript-eslint/utils": ["@typescript-eslint/utils@8.70.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.70.0", "@typescript-eslint/types": "8.70.0", "@typescript-eslint/typescript-estree": "8.70.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-oZmtKJz/4fufZ2p3+Cn3ijEojcdfR+1zYDH2xKYrEly0dR/Q/1xUPRCOlKGxod78nWlU2UnDe09GZ3TaknBFGA=="], - - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.70.0", "", { "dependencies": { "@typescript-eslint/types": "8.70.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-BoC8PiO4Hkdo0TVJh9Ntxr5MxPDI7/oFsrygN5ADelFSeXG/qgNuucIGA+L5Z6JpPTE/uRfcTWtscjbUaufepQ=="], - - "@upstash/redis": ["@upstash/redis@1.38.0", "https://registry.npmmirror.com/@upstash/redis/-/redis-1.38.0.tgz", { "dependencies": { "uncrypto": "^0.1.3" } }, "sha512-wu+dZBptlLy0+MCUEoHmzrY/TnmgDey3+c7EbIGwrLqAvkP8yi5MWZHYGIFtAygmL4Bkz2TdFu+eU0vFPncIcg=="], - - "@vercel/oidc": ["@vercel/oidc@3.2.0", "https://registry.npmmirror.com/@vercel/oidc/-/oidc-3.2.0.tgz", {}, "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug=="], - - "@vitejs/plugin-react": ["@vitejs/plugin-react@6.1.1", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "oxc-transform-react": "^0.145.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler", "oxc-transform-react"] }, "sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw=="], - - "@vitest/expect": ["@vitest/expect@4.1.11", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.11", "@vitest/utils": "4.1.11", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw=="], - - "@vitest/mocker": ["@vitest/mocker@4.1.11", "", { "dependencies": { "@vitest/spy": "4.1.11", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ=="], - - "@vitest/pretty-format": ["@vitest/pretty-format@4.1.11", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw=="], - - "@vitest/runner": ["@vitest/runner@4.1.11", "", { "dependencies": { "@vitest/utils": "4.1.11", "pathe": "^2.0.3" } }, "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw=="], - - "@vitest/snapshot": ["@vitest/snapshot@4.1.11", "", { "dependencies": { "@vitest/pretty-format": "4.1.11", "@vitest/utils": "4.1.11", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog=="], - - "@vitest/spy": ["@vitest/spy@4.1.11", "", {}, "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA=="], - - "@vitest/utils": ["@vitest/utils@4.1.11", "", { "dependencies": { "@vitest/pretty-format": "4.1.11", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ=="], - - "@workflow/serde": ["@workflow/serde@4.1.0", "https://registry.npmmirror.com/@workflow/serde/-/serde-4.1.0.tgz", {}, "sha512-pav4F2BoirECWR7Nf1TKt+2eETcBj7jj4cBefQ8VXQCA6NPkaKeLfj/zMgi+3zYV5ZIBT4GuUiphsj0/b9hPQQ=="], - - "acorn": ["acorn@8.16.0", "https://registry.npmmirror.com/acorn/-/acorn-8.16.0.tgz", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], - - "acorn-jsx": ["acorn-jsx@5.3.2", "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], - - "ai": ["ai@7.0.99", "", { "dependencies": { "@ai-sdk/gateway": "4.0.80", "@ai-sdk/provider": "4.0.14", "@ai-sdk/provider-utils": "5.0.40" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Ov+3j/nSajaVH5hO8C94wN9wisG5tAJ8HWsLV9d/+nlzrRGUh3oYO3Kp1fRyUNWjAWLSpGLHLPaHCdfxb307Vg=="], - - "ajv": ["ajv@6.15.0", "https://registry.npmmirror.com/ajv/-/ajv-6.15.0.tgz", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], - - "altcha": ["altcha@3.2.2", "", { "dependencies": { "hash-wasm": "^4.12.0" } }, "sha512-yNITQaRSkRe0u/hMxdzoXFFqxqn9iDt1rJ0reREU3D3uDD3w1oFz6v5C2PMww2XnGThtYUwe8MW+PxsiBglNqw=="], - - "altcha-lib": ["altcha-lib@2.5.0", "", { "peerDependencies": { "@fastify/cookie": "^11.0.0", "@fastify/cors": "^11.0.0", "@fastify/formbody": "^8.0.0 || ^9.0.0", "@nestjs/common": "^10.0.0 || ^11.0.0 || ^12.0.0", "@nestjs/core": "^10.0.0 || ^11.0.0 || ^12.0.0", "@nestjs/platform-express": "^10.0.0 || ^11.0.0 || ^12.0.0", "@nestjs/platform-fastify": "^10.0.0 || ^11.0.0 || ^12.0.0", "@sveltejs/kit": "^2.0.0", "cookie-parser": "^1.0.0", "cors": "^2.0.0", "express": "^4.0.0 || ^5.0.0", "fastify": "^5.0.0", "h3": "^2.0.0", "hono": "^4.0.0" }, "optionalPeers": ["@fastify/cookie", "@fastify/cors", "@fastify/formbody", "@nestjs/common", "@nestjs/core", "@nestjs/platform-express", "@nestjs/platform-fastify", "@sveltejs/kit", "cookie-parser", "cors", "express", "fastify", "h3", "hono"], "bin": { "altcha-lib": "bin/cli.mjs" } }, "sha512-MiqlwnImoooVTmFzZ8odhpqtCsSl7M/6cmgaX42Tkuvd41e5IVveg6hiw/e9rXSb6dm1RvniPmeURwO64wWTuQ=="], - - "ansis": ["ansis@4.3.1", "https://registry.npmmirror.com/ansis/-/ansis-4.3.1.tgz", {}, "sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA=="], - - "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], - - "assertion-error": ["assertion-error@2.0.1", "https://registry.npmmirror.com/assertion-error/-/assertion-error-2.0.1.tgz", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], - - "balanced-match": ["balanced-match@4.0.4", "https://registry.npmmirror.com/balanced-match/-/balanced-match-4.0.4.tgz", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - - "baseline-browser-mapping": ["baseline-browser-mapping@2.10.24", "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.24.tgz", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-I2NkZOOrj2XuguvWCK6OVh9GavsNjZjK908Rq3mIBK25+GD8vPX5w2WdxVqnQ7xx3SrZJiCiZFu+/Oz50oSYSA=="], - - "better-auth": ["better-auth@1.7.4", "", { "dependencies": { "@better-auth/core": "1.7.4", "@better-auth/drizzle-adapter": "1.7.4", "@better-auth/kysely-adapter": "1.7.4", "@better-auth/memory-adapter": "1.7.4", "@better-auth/mongo-adapter": "1.7.4", "@better-auth/prisma-adapter": "1.7.4", "@better-auth/telemetry": "1.7.4", "@better-auth/utils": "0.4.2", "@better-fetch/fetch": "1.3.1", "@noble/ciphers": "^2.2.0", "@noble/hashes": "^2.2.0", "better-call": "1.4.0", "defu": "^6.1.4", "jose": "^6.2.3", "kysely": "^0.28.17 || ^0.29.0", "nanostores": "^1.3.0", "zod": "^4.5.4" }, "peerDependencies": { "@lynx-js/react": "*", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "@tanstack/solid-start": "^1.0.0", "better-sqlite3": "^12.0.0", "drizzle-kit": ">=0.31.4 || >=1.0.0-beta.1", "drizzle-orm": "^0.45.2 || >=1.0.0-rc.1 <2.0.0", "mongodb": "^6.0.0 || ^7.0.0", "mysql2": "^3.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "pg": "^8.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@prisma/client", "@sveltejs/kit", "@tanstack/react-start", "@tanstack/solid-start", "better-sqlite3", "drizzle-kit", "drizzle-orm", "mongodb", "mysql2", "next", "pg", "prisma", "react", "react-dom", "solid-js", "svelte", "vitest", "vue"] }, "sha512-rjL9g8b8UvdwK/ZSdawhqYzOm8YegurB6Fuzqtu/zFJamlFrPa+eEhP8Qj2vFWsMzHOk2mb/L4FRYh9pgaU7OA=="], - - "better-call": ["better-call@1.4.0", "", { "dependencies": { "@better-auth/utils": "^0.5.0", "@better-fetch/fetch": "^1.3.1", "rou3": "^0.9.1", "set-cookie-parser": "^3.1.2" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-bBKOT4vv1kZLDgxVePdilk/Jwkn+dtRRsmi3DzHcDP+WnswyVl6dR59l2HEeP/0cB+bDoopASAesWDPIdd/zZA=="], - - "brace-expansion": ["brace-expansion@5.0.5", "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-5.0.5.tgz", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="], - - "browserslist": ["browserslist@4.28.2", "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.2.tgz", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="], - - "buffer-from": ["buffer-from@1.1.2", "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], - - "bun-types": ["bun-types@1.4.2", "", { "dependencies": { "@types/node": "*" } }, "sha512-bxV1FgK7yBIzjRe5zBozIM4Bem11ZJcCXSrjWRG3YWLt8yFDePu4cLjpebO8OvPeIE9trbyPF4fuj3Cia4Fj3w=="], - - "cac": ["cac@7.0.0", "https://registry.npmmirror.com/cac/-/cac-7.0.0.tgz", {}, "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ=="], - - "cacheable": ["cacheable@2.5.0", "", { "dependencies": { "@cacheable/memory": "^2.2.0", "@cacheable/utils": "^2.5.0", "hookified": "^1.15.0", "keyv": "^5.6.0", "qified": "^0.10.1" } }, "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g=="], - - "caniuse-lite": ["caniuse-lite@1.0.30001791", "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", {}, "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ=="], - - "canvas-confetti": ["canvas-confetti@1.9.4", "https://registry.npmmirror.com/canvas-confetti/-/canvas-confetti-1.9.4.tgz", {}, "sha512-yxQbJkAVrFXWNbTUjPqjF7G+g6pDotOUHGbkZq2NELZUMDpiJ85rIEazVb8GTaAptNW2miJAXbs1BtioA251Pw=="], - - "chai": ["chai@6.2.2", "https://registry.npmmirror.com/chai/-/chai-6.2.2.tgz", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], - - "class-variance-authority": ["class-variance-authority@0.7.1", "https://registry.npmmirror.com/class-variance-authority/-/class-variance-authority-0.7.1.tgz", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], - - "clsx": ["clsx@2.1.1", "https://registry.npmmirror.com/clsx/-/clsx-2.1.1.tgz", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], - - "cluster-key-slot": ["cluster-key-slot@1.1.1", "https://registry.npmmirror.com/cluster-key-slot/-/cluster-key-slot-1.1.1.tgz", {}, "sha512-rwHwUfXL40Chm1r08yrhU3qpUvdVlgkKNeyeGPOxnW8/SyVDvgRaed/Uz54AqWNaTCAThlj6QAs3TZcKI0xDEw=="], - - "consola": ["consola@3.4.2", "https://registry.npmmirror.com/consola/-/consola-3.4.2.tgz", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], - - "convert-source-map": ["convert-source-map@2.0.0", "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], - - "cookie-es": ["cookie-es@3.1.1", "https://registry.npmmirror.com/cookie-es/-/cookie-es-3.1.1.tgz", {}, "sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg=="], - - "cross-fetch": ["cross-fetch@4.1.0", "", { "dependencies": { "node-fetch": "^2.7.0" } }, "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw=="], - - "cross-spawn": ["cross-spawn@7.0.6", "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - - "csstype": ["csstype@3.2.3", "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], - - "d3-format": ["d3-format@3.1.2", "https://registry.npmmirror.com/d3-format/-/d3-format-3.1.2.tgz", {}, "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg=="], - - "d3-hierarchy": ["d3-hierarchy@3.1.2", "https://registry.npmmirror.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", {}, "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA=="], - - "dagre": ["dagre@0.8.5", "", { "dependencies": { "graphlib": "^2.1.8", "lodash": "^4.17.15" } }, "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw=="], - - "debug": ["debug@4.4.3", "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - - "deep-is": ["deep-is@0.1.4", "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], - - "defu": ["defu@6.1.7", "https://registry.npmmirror.com/defu/-/defu-6.1.7.tgz", {}, "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ=="], - - "denque": ["denque@2.1.0", "https://registry.npmmirror.com/denque/-/denque-2.1.0.tgz", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="], - - "destr": ["destr@2.0.5", "https://registry.npmmirror.com/destr/-/destr-2.0.5.tgz", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="], - - "detect-indent": ["detect-indent@7.0.2", "https://registry.npmmirror.com/detect-indent/-/detect-indent-7.0.2.tgz", {}, "sha512-y+8xyqdGLL+6sh0tVeHcfP/QDd8gUgbasolJJpY7NgeQGSZ739bDtSiaiDgtoicy+mtYB81dKLxO9xRhCyIB3A=="], - - "detect-libc": ["detect-libc@2.1.2", "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - - "detect-newline": ["detect-newline@4.0.1", "https://registry.npmmirror.com/detect-newline/-/detect-newline-4.0.1.tgz", {}, "sha512-qE3Veg1YXzGHQhlA6jzebZN2qVf6NX+A7m7qlhCGG30dJixrAQhYOsJjsnBjJkCSmuOPpCk30145fr8FV0bzog=="], - - "drizzle-kit": ["drizzle-kit@0.31.10", "https://registry.npmmirror.com/drizzle-kit/-/drizzle-kit-0.31.10.tgz", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "tsx": "^4.21.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw=="], - - "drizzle-orm": ["drizzle-orm@0.45.2", "https://registry.npmmirror.com/drizzle-orm/-/drizzle-orm-0.45.2.tgz", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q=="], - - "electron-to-chromium": ["electron-to-chromium@1.5.344", "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz", {}, "sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg=="], - - "enhanced-resolve": ["enhanced-resolve@5.24.3", "https://registry.npmmirror.com/enhanced-resolve/-/enhanced-resolve-5.24.3.tgz", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-PwKooW9JUzh5chmYfHM3IQl5OkK2u2Nm011MgeZrss3JmFraUx/fqrf78kk8GUMYoibx/14MdwTl/1WKkG7TpQ=="], - - "es-module-lexer": ["es-module-lexer@2.1.0", "https://registry.npmmirror.com/es-module-lexer/-/es-module-lexer-2.1.0.tgz", {}, "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ=="], - - "esbuild": ["esbuild@0.25.12", "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.12.tgz", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], - - "escalade": ["escalade@3.2.0", "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], - - "escape-string-regexp": ["escape-string-regexp@4.0.0", "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - - "eslint": ["eslint@10.10.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.7.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.3", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "11.1.5 || >11.1.6 <12", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-NPXn6r5zl4uET1DAVPaOwzX3rut4c0wcmw3dWJAfOsTM5+TogXo0DDjz8pwm/hL8cyVNpHqeK4JpN0NjnyFFNw=="], - - "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.1.1", "https://registry.npmmirror.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" } }, "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g=="], - - "eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.5.6", "", { "peerDependencies": { "eslint": "^9 || ^10" } }, "sha512-uZnh24On2bk478AkaDAKcpRiYhS3qFSaSNvpXxV6/Ek/BBDICkWGG7MnBSfjnIlCVBei5vsLlIeQYxbF22+Udg=="], - - "eslint-scope": ["eslint-scope@9.1.2", "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-9.1.2.tgz", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="], - - "eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], - - "espree": ["espree@11.2.0", "https://registry.npmmirror.com/espree/-/espree-11.2.0.tgz", { "dependencies": { "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^5.0.1" } }, "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw=="], - - "esquery": ["esquery@1.7.0", "https://registry.npmmirror.com/esquery/-/esquery-1.7.0.tgz", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="], - - "esrecurse": ["esrecurse@4.3.0", "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], - - "estraverse": ["estraverse@5.3.0", "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], - - "estree-walker": ["estree-walker@3.0.3", "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], - - "esutils": ["esutils@2.0.3", "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], - - "eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="], - - "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], - - "eventsource-parser": ["eventsource-parser@3.0.8", "https://registry.npmmirror.com/eventsource-parser/-/eventsource-parser-3.0.8.tgz", {}, "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ=="], - - "expect-type": ["expect-type@1.3.0", "https://registry.npmmirror.com/expect-type/-/expect-type-1.3.0.tgz", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], - - "fast-deep-equal": ["fast-deep-equal@3.1.3", "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], - - "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], - - "fast-levenshtein": ["fast-levenshtein@2.0.6", "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], - - "fdir": ["fdir@6.5.0", "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], - - "file-entry-cache": ["file-entry-cache@11.1.5", "", { "dependencies": { "flat-cache": "^6.1.23" } }, "sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q=="], - - "find-up": ["find-up@5.0.0", "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], - - "flat-cache": ["flat-cache@6.1.23", "", { "dependencies": { "cacheable": "^2.5.0", "flatted": "^3.4.2", "hookified": "^1.15.0" } }, "sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA=="], - - "flatted": ["flatted@3.4.2", "https://registry.npmmirror.com/flatted/-/flatted-3.4.2.tgz", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="], - - "fsevents": ["fsevents@2.3.3", "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], - - "gensync": ["gensync@1.0.0-beta.2", "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], - - "get-tsconfig": ["get-tsconfig@4.14.0", "https://registry.npmmirror.com/get-tsconfig/-/get-tsconfig-4.14.0.tgz", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA=="], - - "git-hooks-list": ["git-hooks-list@4.2.1", "https://registry.npmmirror.com/git-hooks-list/-/git-hooks-list-4.2.1.tgz", {}, "sha512-WNvqJjOxxs/8ZP9+DWdwWJ7cDsd60NHf39XnD82pDVrKO5q7xfPqpkK6hwEAmBa/ZSEE4IOoR75EzbbIuwGlMw=="], - - "glob-parent": ["glob-parent@6.0.2", "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], - - "globals": ["globals@17.12.0", "", {}, "sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA=="], - - "graceful-fs": ["graceful-fs@4.2.11", "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], - - "graphlib": ["graphlib@2.1.8", "", { "dependencies": { "lodash": "^4.17.15" } }, "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A=="], - - "graphology": ["graphology@0.25.4", "", { "dependencies": { "events": "^3.3.0", "obliterator": "^2.0.2" }, "peerDependencies": { "graphology-types": ">=0.24.0" } }, "sha512-33g0Ol9nkWdD6ulw687viS8YJQBxqG5LWII6FI6nul0pq6iM2t5EKquOTFDbyTblRB3O9I+7KX4xI8u5ffekAQ=="], - - "graphology-types": ["graphology-types@0.24.8", "", {}, "sha512-hDRKYXa8TsoZHjgEaysSRyPdT6uB78Ci8WnjgbStlQysz7xR52PInxNsmnB7IBOM1BhikxkNyCVEFgmPKnpx3Q=="], - - "hash-wasm": ["hash-wasm@4.12.0", "https://registry.npmmirror.com/hash-wasm/-/hash-wasm-4.12.0.tgz", {}, "sha512-+/2B2rYLb48I/evdOIhP+K/DD2ca2fgBjp6O+GBEnCDk2e4rpeXIK8GvIyRPjTezgmWn9gmKwkQjjx6BtqDHVQ=="], - - "hashery": ["hashery@1.5.1", "", { "dependencies": { "hookified": "^1.15.0" } }, "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ=="], - - "hermes-estree": ["hermes-estree@0.25.1", "https://registry.npmmirror.com/hermes-estree/-/hermes-estree-0.25.1.tgz", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="], - - "hermes-parser": ["hermes-parser@0.25.1", "https://registry.npmmirror.com/hermes-parser/-/hermes-parser-0.25.1.tgz", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="], - - "hono": ["hono@4.13.7", "", {}, "sha512-c8/gF9ac8Y78/agExVocyLevgR+JlpNB444Py0FSX8pJoPdYUfUzRcXtYEYGwt6l19qIlVZPN5Mfsw9jFShmQQ=="], - - "hookified": ["hookified@1.15.1", "", {}, "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg=="], - - "i18next": ["i18next@22.5.1", "", { "dependencies": { "@babel/runtime": "^7.20.6" } }, "sha512-8TGPgM3pAD+VRsMtUMNknRz3kzqwp/gPALrWMsDnmC1mKqJwpWyooQRLMcbTwq8z8YwSmuj+ZYvc+xCuEpkssA=="], - - "i18next-http-backend": ["i18next-http-backend@3.0.6", "", { "dependencies": { "cross-fetch": "4.1.0" } }, "sha512-mBOqy8993jtqAoj6XaI1XeC/8/9v6EPS+681ziegrPvTB0DoaCY7PpTS0SpY56qLMoS4OI1TZEM2Zf59zNh05w=="], - - "ignore": ["ignore@5.3.2", "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - - "imurmurhash": ["imurmurhash@0.1.4", "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], - - "ioredis": ["ioredis@5.11.1", "https://registry.npmmirror.com/ioredis/-/ioredis-5.11.1.tgz", { "dependencies": { "@ioredis/commands": "1.10.0", "cluster-key-slot": "1.1.1", "debug": "4.4.3", "denque": "2.1.0", "redis-errors": "1.2.0", "redis-parser": "3.0.0", "standard-as-callback": "2.1.0" } }, "sha512-ehuGcf94bQXhfagULNXrJdfnWO38v070jxSx/qE87Kjzmu2fU7ro5EFAb+OPituLqgfyuQaym5DlrNydW2sJ9A=="], - - "is-extglob": ["is-extglob@2.1.1", "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], - - "is-glob": ["is-glob@4.0.3", "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], - - "is-plain-obj": ["is-plain-obj@4.1.0", "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], - - "isexe": ["isexe@2.0.0", "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - - "jiti": ["jiti@2.7.0", "https://registry.npmmirror.com/jiti/-/jiti-2.7.0.tgz", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], - - "jose": ["jose@6.2.3", "https://registry.npmmirror.com/jose/-/jose-6.2.3.tgz", {}, "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw=="], - - "js-tokens": ["js-tokens@4.0.0", "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], - - "js-yaml": ["js-yaml@4.3.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ=="], - - "jsesc": ["jsesc@3.1.0", "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], - - "json-schema": ["json-schema@0.4.0", "https://registry.npmmirror.com/json-schema/-/json-schema-0.4.0.tgz", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="], - - "json-schema-traverse": ["json-schema-traverse@0.4.1", "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], - - "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], - - "json5": ["json5@2.2.3", "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], - - "keyv": ["keyv@5.6.0", "", { "dependencies": { "@keyv/serialize": "^1.1.1" } }, "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw=="], - - "kysely": ["kysely@0.28.17", "https://registry.npmmirror.com/kysely/-/kysely-0.28.17.tgz", {}, "sha512-nbD8lB9EB3wNdMhOCdx5Li8DxnLbvKByylRLcJ1h+4SkrowVeECAyZlyiKMThF7xFdRz0jSQ2MoJr+wXux2y0Q=="], - - "levn": ["levn@0.4.1", "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], - - "lightningcss": ["lightningcss@1.33.0", "https://registry.npmmirror.com/lightningcss/-/lightningcss-1.33.0.tgz", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="], - - "lightningcss-android-arm64": ["lightningcss-android-arm64@1.33.0", "https://registry.npmmirror.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", { "os": "android", "cpu": "arm64" }, "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg=="], - - "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.33.0", "https://registry.npmmirror.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg=="], - - "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.33.0", "https://registry.npmmirror.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ=="], - - "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.33.0", "https://registry.npmmirror.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg=="], - - "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.33.0", "https://registry.npmmirror.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", { "os": "linux", "cpu": "arm" }, "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ=="], - - "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.33.0", "https://registry.npmmirror.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg=="], - - "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.33.0", "https://registry.npmmirror.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ=="], - - "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.33.0", "https://registry.npmmirror.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", { "os": "linux", "cpu": "x64" }, "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg=="], - - "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.33.0", "https://registry.npmmirror.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", { "os": "linux", "cpu": "x64" }, "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw=="], - - "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.33.0", "https://registry.npmmirror.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA=="], - - "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "https://registry.npmmirror.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="], - - "locate-path": ["locate-path@6.0.0", "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], - - "lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="], - - "lru-cache": ["lru-cache@5.1.1", "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], - - "magic-string": ["magic-string@0.30.21", "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - - "minimatch": ["minimatch@10.2.5", "https://registry.npmmirror.com/minimatch/-/minimatch-10.2.5.tgz", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], - - "ms": ["ms@2.1.3", "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="], - - "nanoid": ["nanoid@3.3.19", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug=="], - - "nanostores": ["nanostores@1.3.0", "https://registry.npmmirror.com/nanostores/-/nanostores-1.3.0.tgz", {}, "sha512-XPUa/jz+P1oJvN9VBxw4L9MtdFfaH3DAryqPssqhb2kXjmb9npz0dly6rCsgFWOPr4Yg9mTfM3MDZgZZ+7A3lA=="], - - "nats": ["nats@2.29.3", "https://registry.npmmirror.com/nats/-/nats-2.29.3.tgz", { "dependencies": { "nkeys.js": "1.1.0" } }, "sha512-tOQCRCwC74DgBTk4pWZ9V45sk4d7peoE2njVprMRCBXrhJ5q5cYM7i6W+Uvw2qUrcfOSnuisrX7bEx3b3Wx4QA=="], - - "natural-compare": ["natural-compare@1.4.0", "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], - - "nkeys.js": ["nkeys.js@1.1.0", "https://registry.npmmirror.com/nkeys.js/-/nkeys.js-1.1.0.tgz", { "dependencies": { "tweetnacl": "1.0.3" } }, "sha512-tB/a0shZL5UZWSwsoeyqfTszONTt4k2YS0tuQioMOD180+MbombYVgzDUYHlx+gejYK6rgf08n/2Df99WY0Sxg=="], - - "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], - - "node-fetch-native": ["node-fetch-native@1.6.7", "https://registry.npmmirror.com/node-fetch-native/-/node-fetch-native-1.6.7.tgz", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], - - "node-releases": ["node-releases@2.0.38", "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.38.tgz", {}, "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw=="], - - "nodemailer": ["nodemailer@9.1.1", "", {}, "sha512-izw9mVKFix6YSnC9eLgV6g1opl9DUlRio9ZNcq+Wu9Ujn2UwF+8Nl0B8nz22kEC+CTZCvinkxwJ0DeFbb6NwcQ=="], - - "normalize-url": ["normalize-url@9.0.1", "https://registry.npmmirror.com/normalize-url/-/normalize-url-9.0.1.tgz", {}, "sha512-ARftfC5HdUNu9jJeL8pHj8debUIHA2b91FizCoMzY4lG6dDX13jdvTK0TBe24IBDRf2HvJSzzwEPvmbkQWHRSg=="], - - "obliterator": ["obliterator@2.0.5", "", {}, "sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw=="], - - "obug": ["obug@2.1.1", "https://registry.npmmirror.com/obug/-/obug-2.1.1.tgz", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], - - "ofetch": ["ofetch@1.5.1", "https://registry.npmmirror.com/ofetch/-/ofetch-1.5.1.tgz", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="], - - "optionator": ["optionator@0.9.4", "https://registry.npmmirror.com/optionator/-/optionator-0.9.4.tgz", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], - - "p-limit": ["p-limit@7.3.1", "https://registry.npmmirror.com/p-limit/-/p-limit-7.3.1.tgz", { "dependencies": { "yocto-queue": "^1.2.1" } }, "sha512-0trZaiG7Y7kN/Egy9a8j47t9osC0Tch4PaIWd9yGF6bvmlk7muExRvGNYb8sXBwEKMoNKsbNN9P8EefuQekE4Q=="], - - "p-locate": ["p-locate@5.0.0", "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], - - "papaparse": ["papaparse@5.6.0", "", {}, "sha512-N2vuNQAYGK1/4vs6HJX86+VYU6OkiSTgdJz3JQfTk1y51cFCO/U8gnaeTF4iNE4r57Tt0sV47dUua1/19pxO6Q=="], - - "path-exists": ["path-exists@4.0.0", "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], - - "path-key": ["path-key@3.1.1", "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], - - "pathe": ["pathe@2.0.3", "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], - - "pg": ["pg@8.23.0", "", { "dependencies": { "pg-connection-string": "^2.14.0", "pg-pool": "^3.14.0", "pg-protocol": "^1.16.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.4.0" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-Ip2EQCngowJLGOfCwkFhPXU7/ljlhn6Rxlmy4XYfL2Y+vyRM59+8uR2xqRWKdYmbXmxCFOAmKxBuSUCdF34qLg=="], - - "pg-cloudflare": ["pg-cloudflare@1.4.0", "https://registry.npmmirror.com/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz", {}, "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A=="], - - "pg-connection-string": ["pg-connection-string@2.14.0", "https://registry.npmmirror.com/pg-connection-string/-/pg-connection-string-2.14.0.tgz", {}, "sha512-XwWDGcLRGCXAR8F/AM5bG7Q+A3Wm2s6QeEjlOKZLlH3UYcguiqCWKyWXVag5TLTIjR7oOJUY8kcADaZgWPyLeg=="], - - "pg-int8": ["pg-int8@1.0.1", "https://registry.npmmirror.com/pg-int8/-/pg-int8-1.0.1.tgz", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="], - - "pg-pool": ["pg-pool@3.14.0", "https://registry.npmmirror.com/pg-pool/-/pg-pool-3.14.0.tgz", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw=="], - - "pg-protocol": ["pg-protocol@1.15.0", "https://registry.npmmirror.com/pg-protocol/-/pg-protocol-1.15.0.tgz", {}, "sha512-cq9sECI5s0+uPUXjbz8ioyPJni6RzsRib0US67i5IoTZKw8fNeYlVE7u8F4dG7vEJJtc5wdD1K189lCCUwqWTQ=="], - - "pg-types": ["pg-types@2.2.0", "https://registry.npmmirror.com/pg-types/-/pg-types-2.2.0.tgz", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="], - - "pgpass": ["pgpass@1.0.5", "https://registry.npmmirror.com/pgpass/-/pgpass-1.0.5.tgz", { "dependencies": { "split2": "^4.1.0" } }, "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug=="], - - "phaser": ["phaser@4.2.1", "", { "dependencies": { "eventemitter3": "^5.0.4" } }, "sha512-WUNwCPJpdjvZiuT6SgCfYVW8Qw/3j0jJ4ws7P2QkhFLFu74sbGuyHJcbFueGkY/AYO4Pi47bNQXn1OCJeLX//w=="], - - "phaser4-rex-plugins": ["phaser4-rex-plugins@4.2.0", "", { "dependencies": { "dagre": "^0.8.5", "eventemitter3": "^3.1.2", "graphology": "^0.25.4", "i18next": "^22.5.1", "i18next-http-backend": "^3.0.5", "js-yaml": "^4.1.0", "mustache": "^4.2.0", "papaparse": "^5.4.1", "tinyqueue": "^3.0.0", "webfontloader": "^1.6.28" } }, "sha512-Wc5pSEbz3iv5FJpxG/TXHzDK5YM3skHjeL4CHjd92h0BvsOCvJ6dxP30M7xVWDsLLPKKLQOFE9L2b8Dez5nwAw=="], - - "picocolors": ["picocolors@1.1.1", "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - - "picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="], - - "postcss": ["postcss@8.5.28", "", { "dependencies": { "nanoid": "^3.3.18", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A=="], - - "postgres": ["postgres@3.4.9", "https://registry.npmmirror.com/postgres/-/postgres-3.4.9.tgz", {}, "sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw=="], - - "postgres-array": ["postgres-array@2.0.0", "https://registry.npmmirror.com/postgres-array/-/postgres-array-2.0.0.tgz", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="], - - "postgres-bytea": ["postgres-bytea@1.0.1", "https://registry.npmmirror.com/postgres-bytea/-/postgres-bytea-1.0.1.tgz", {}, "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="], - - "postgres-date": ["postgres-date@1.0.7", "https://registry.npmmirror.com/postgres-date/-/postgres-date-1.0.7.tgz", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="], - - "postgres-interval": ["postgres-interval@1.2.0", "https://registry.npmmirror.com/postgres-interval/-/postgres-interval-1.2.0.tgz", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="], - - "prelude-ls": ["prelude-ls@1.2.1", "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], - - "prettier": ["prettier@3.9.6", "https://registry.npmmirror.com/prettier/-/prettier-3.9.6.tgz", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g=="], - - "prettier-plugin-organize-imports": ["prettier-plugin-organize-imports@4.3.0", "https://registry.npmmirror.com/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.3.0.tgz", { "peerDependencies": { "prettier": ">=2.0", "typescript": ">=2.9", "vue-tsc": "^2.1.0 || 3" }, "optionalPeers": ["vue-tsc"] }, "sha512-FxFz0qFhyBsGdIsb697f/EkvHzi5SZOhWAjxcx2dLt+Q532bAlhswcXGYB1yzjZ69kW8UoadFBw7TyNwlq96Iw=="], - - "prettier-plugin-packagejson": ["prettier-plugin-packagejson@3.0.2", "https://registry.npmmirror.com/prettier-plugin-packagejson/-/prettier-plugin-packagejson-3.0.2.tgz", { "dependencies": { "sort-package-json": "^3.6.0" }, "peerDependencies": { "prettier": "^3" }, "optionalPeers": ["prettier"] }, "sha512-kmoj3hEynXwoHDo8ZhmWAIjRBoQWCDUVackiWfSDWdgD0rS3LGB61T9zoVbume/cotYdCoadUh4sqViAmXvpBQ=="], - - "prettier-plugin-tailwindcss": ["prettier-plugin-tailwindcss@0.8.1", "https://registry.npmmirror.com/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.8.1.tgz", { "peerDependencies": { "@ianvs/prettier-plugin-sort-imports": "*", "@prettier/plugin-hermes": "*", "@prettier/plugin-oxc": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", "@trivago/prettier-plugin-sort-imports": "*", "@zackad/prettier-plugin-twig": "*", "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", "prettier-plugin-jsdoc": "*", "prettier-plugin-marko": "*", "prettier-plugin-multiline-arrays": "*", "prettier-plugin-organize-attributes": "*", "prettier-plugin-organize-imports": "*", "prettier-plugin-sort-imports": "*", "prettier-plugin-svelte": "*" }, "optionalPeers": ["@ianvs/prettier-plugin-sort-imports", "@prettier/plugin-hermes", "@prettier/plugin-oxc", "@prettier/plugin-pug", "@shopify/prettier-plugin-liquid", "@trivago/prettier-plugin-sort-imports", "@zackad/prettier-plugin-twig", "prettier-plugin-astro", "prettier-plugin-css-order", "prettier-plugin-jsdoc", "prettier-plugin-marko", "prettier-plugin-multiline-arrays", "prettier-plugin-organize-attributes", "prettier-plugin-organize-imports", "prettier-plugin-sort-imports", "prettier-plugin-svelte"] }, "sha512-iaFMYqDsE4ffdDkn5qup0j5f2aCEBFZrdrZnvu9QKTlWx/iGPeQ4HHu7b7fCPMxeo9nwQBiOAh2nSypdFYWJkw=="], - - "punycode": ["punycode@2.3.1", "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - - "qified": ["qified@0.10.1", "", { "dependencies": { "hookified": "^2.1.1" } }, "sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA=="], - - "quansync": ["quansync@1.0.0", "https://registry.npmmirror.com/quansync/-/quansync-1.0.0.tgz", {}, "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA=="], - - "react": ["react@19.3.0", "", {}, "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog=="], - - "react-dom": ["react-dom@19.3.0", "", { "dependencies": { "scheduler": "^0.28.0" }, "peerDependencies": { "react": "^19.3.0" } }, "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q=="], - - "react-router": ["react-router@8.3.1", "", { "dependencies": { "cookie-es": "^3.1.1" }, "peerDependencies": { "react": ">=19.2.7", "react-dom": ">=19.2.7" }, "optionalPeers": ["react-dom"] }, "sha512-TEOpiO2g0TJHEOJeRVv4amUFun9v1npCKszvcquNvzETUtJ8udV86ah5eFoHT7g26bsBvT6EiIhqulR8eDF++A=="], - - "react-zoom-pan-pinch": ["react-zoom-pan-pinch@4.2.0", "", { "peerDependencies": { "react": "*", "react-dom": "*" } }, "sha512-QSw1dvr6QGv5zHCUY7SEIY7EWRDbx1y6plylrWf2Nko9KOMW1MEoPC6oJ+Y/qOSeQjhvbkMV0m5bFF7pBxCH4A=="], - - "redis-errors": ["redis-errors@1.2.0", "https://registry.npmmirror.com/redis-errors/-/redis-errors-1.2.0.tgz", {}, "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w=="], - - "redis-parser": ["redis-parser@3.0.0", "https://registry.npmmirror.com/redis-parser/-/redis-parser-3.0.0.tgz", { "dependencies": { "redis-errors": "^1.0.0" } }, "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A=="], - - "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "https://registry.npmmirror.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], - - "rolldown": ["rolldown@1.2.8", "", { "dependencies": { "@oxc-project/types": "=0.149.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm-eabi": "1.2.8", "@rolldown/binding-android-arm64": "1.2.8", "@rolldown/binding-darwin-arm64": "1.2.8", "@rolldown/binding-darwin-x64": "1.2.8", "@rolldown/binding-freebsd-x64": "1.2.8", "@rolldown/binding-linux-arm-gnueabihf": "1.2.8", "@rolldown/binding-linux-arm64-gnu": "1.2.8", "@rolldown/binding-linux-arm64-musl": "1.2.8", "@rolldown/binding-linux-ppc64-gnu": "1.2.8", "@rolldown/binding-linux-s390x-gnu": "1.2.8", "@rolldown/binding-linux-x64-gnu": "1.2.8", "@rolldown/binding-linux-x64-musl": "1.2.8", "@rolldown/binding-openharmony-arm64": "1.2.8", "@rolldown/binding-win32-arm64-msvc": "1.2.8", "@rolldown/binding-win32-x64-msvc": "1.2.8" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-Z67nTmhZe7anqnM/EjI392w5i/ANUinjip7QYsOyN37oayduxt3ksdX0hf5OOamkAd53BiIHfbfSzfUmzKFQqQ=="], - - "rou3": ["rou3@0.9.2", "", {}, "sha512-3SOzvaAg8rkHrXtRjpCvCvbyO5to9oOO27Z/XqHEYXfMRVSw/qMIVdmaOk9W2lcRLtR6dlqTjo9hDeJk70QBYQ=="], - - "scheduler": ["scheduler@0.28.0", "", {}, "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw=="], - - "semver": ["semver@6.3.1", "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "set-cookie-parser": ["set-cookie-parser@3.1.2", "", {}, "sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw=="], - - "sharp": ["sharp@0.35.3", "https://registry.npmmirror.com/sharp/-/sharp-0.35.3.tgz", { "dependencies": { "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", "semver": "^7.8.5" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.35.3", "@img/sharp-darwin-x64": "0.35.3", "@img/sharp-freebsd-wasm32": "0.35.3", "@img/sharp-libvips-darwin-arm64": "1.3.2", "@img/sharp-libvips-darwin-x64": "1.3.2", "@img/sharp-libvips-linux-arm": "1.3.2", "@img/sharp-libvips-linux-arm64": "1.3.2", "@img/sharp-libvips-linux-ppc64": "1.3.2", "@img/sharp-libvips-linux-riscv64": "1.3.2", "@img/sharp-libvips-linux-s390x": "1.3.2", "@img/sharp-libvips-linux-x64": "1.3.2", "@img/sharp-libvips-linuxmusl-arm64": "1.3.2", "@img/sharp-libvips-linuxmusl-x64": "1.3.2", "@img/sharp-linux-arm": "0.35.3", "@img/sharp-linux-arm64": "0.35.3", "@img/sharp-linux-ppc64": "0.35.3", "@img/sharp-linux-riscv64": "0.35.3", "@img/sharp-linux-s390x": "0.35.3", "@img/sharp-linux-x64": "0.35.3", "@img/sharp-linuxmusl-arm64": "0.35.3", "@img/sharp-linuxmusl-x64": "0.35.3", "@img/sharp-webcontainers-wasm32": "0.35.3", "@img/sharp-win32-arm64": "0.35.3", "@img/sharp-win32-ia32": "0.35.3", "@img/sharp-win32-x64": "0.35.3" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q=="], - - "shebang-command": ["shebang-command@2.0.0", "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], - - "shebang-regex": ["shebang-regex@3.0.0", "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], - - "siginfo": ["siginfo@2.0.0", "https://registry.npmmirror.com/siginfo/-/siginfo-2.0.0.tgz", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], - - "sort-object-keys": ["sort-object-keys@2.1.0", "https://registry.npmmirror.com/sort-object-keys/-/sort-object-keys-2.1.0.tgz", {}, "sha512-SOiEnthkJKPv2L6ec6HMwhUcN0/lppkeYuN1x63PbyPRrgSPIuBJCiYxYyvWRTtjMlOi14vQUCGUJqS6PLVm8g=="], - - "sort-package-json": ["sort-package-json@3.6.1", "https://registry.npmmirror.com/sort-package-json/-/sort-package-json-3.6.1.tgz", { "dependencies": { "detect-indent": "^7.0.2", "detect-newline": "^4.0.1", "git-hooks-list": "^4.1.1", "is-plain-obj": "^4.1.0", "semver": "^7.7.3", "sort-object-keys": "^2.0.1", "tinyglobby": "^0.2.15" }, "bin": { "sort-package-json": "cli.js" } }, "sha512-Chgejw1+10p2D0U2tB7au1lHtz6TkFnxmvZktyBCRyV0GgmF6nl1IxXxAsPtJVsUyg/fo+BfCMAVVFUVRkAHrQ=="], - - "source-map": ["source-map@0.6.1", "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], - - "source-map-js": ["source-map-js@1.2.1", "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], - - "source-map-support": ["source-map-support@0.5.21", "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], - - "split2": ["split2@4.2.0", "https://registry.npmmirror.com/split2/-/split2-4.2.0.tgz", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], - - "sponsorkit": ["sponsorkit@18.0.0", "https://registry.npmmirror.com/sponsorkit/-/sponsorkit-18.0.0.tgz", { "dependencies": { "ansis": "^4.3.1", "cac": "^7.0.0", "consola": "^3.4.2", "d3-hierarchy": "^3.1.2", "normalize-url": "^9.0.1", "ofetch": "^1.5.1", "p-limit": "^7.3.1", "sharp": "^0.35.3", "unconfig": "^7.5.0" }, "bin": { "sponsorkit": "./dist/cli.mjs" } }, "sha512-9Jr+n4SjU8Qtldq8i0UlwwqBEAVy5JFbIQNXRUIs1N+LPTCnX2/of5oyHYftCLi3KxAODVMmyEdWqkJMTagR6w=="], - - "stackback": ["stackback@0.0.2", "https://registry.npmmirror.com/stackback/-/stackback-0.0.2.tgz", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], - - "standard-as-callback": ["standard-as-callback@2.1.0", "https://registry.npmmirror.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="], - - "std-env": ["std-env@4.1.0", "https://registry.npmmirror.com/std-env/-/std-env-4.1.0.tgz", {}, "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ=="], - - "tailwind-merge": ["tailwind-merge@3.7.0", "", {}, "sha512-XPPUyAc+cvspz3lHTcR/QgPfW2A0lv/xQNIjX3HGhLR+Nq2lHaLq5MtTesHn8GUr3W3DguT2KT5x3NVgRtYwmA=="], - - "tailwindcss": ["tailwindcss@4.3.3", "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-4.3.3.tgz", {}, "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ=="], - - "tapable": ["tapable@2.3.3", "https://registry.npmmirror.com/tapable/-/tapable-2.3.3.tgz", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], - - "tinybench": ["tinybench@2.9.0", "https://registry.npmmirror.com/tinybench/-/tinybench-2.9.0.tgz", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], - - "tinyexec": ["tinyexec@1.1.2", "https://registry.npmmirror.com/tinyexec/-/tinyexec-1.1.2.tgz", {}, "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA=="], - - "tinyglobby": ["tinyglobby@0.2.17", "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], - - "tinyqueue": ["tinyqueue@3.0.0", "", {}, "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g=="], - - "tinyrainbow": ["tinyrainbow@3.1.0", "https://registry.npmmirror.com/tinyrainbow/-/tinyrainbow-3.1.0.tgz", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="], - - "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], - - "ts-api-utils": ["ts-api-utils@2.5.0", "https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-2.5.0.tgz", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], - - "tslib": ["tslib@2.8.1", "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "tsx": ["tsx@4.21.0", "https://registry.npmmirror.com/tsx/-/tsx-4.21.0.tgz", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="], - - "tweetnacl": ["tweetnacl@1.0.3", "https://registry.npmmirror.com/tweetnacl/-/tweetnacl-1.0.3.tgz", {}, "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw=="], - - "type-check": ["type-check@0.4.0", "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], - - "typescript": ["typescript@6.0.3", "https://registry.npmmirror.com/typescript/-/typescript-6.0.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], - - "typescript-eslint": ["typescript-eslint@8.70.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.70.0", "@typescript-eslint/parser": "8.70.0", "@typescript-eslint/typescript-estree": "8.70.0", "@typescript-eslint/utils": "8.70.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-P/W5cz70/cQAuKfY3xwQMWWTV7BvJ0mAQmi+9mBcsVPaBUpd6Ohpa+fECv9rBFrQcig86jAiNBFNWUqnTjr4pw=="], - - "ufo": ["ufo@1.6.4", "https://registry.npmmirror.com/ufo/-/ufo-1.6.4.tgz", {}, "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA=="], - - "unconfig": ["unconfig@7.5.0", "https://registry.npmmirror.com/unconfig/-/unconfig-7.5.0.tgz", { "dependencies": { "@quansync/fs": "^1.0.0", "defu": "^6.1.4", "jiti": "^2.6.1", "quansync": "^1.0.0", "unconfig-core": "7.5.0" } }, "sha512-oi8Qy2JV4D3UQ0PsopR28CzdQ3S/5A1zwsUwp/rosSbfhJ5z7b90bIyTwi/F7hCLD4SGcZVjDzd4XoUQcEanvA=="], - - "unconfig-core": ["unconfig-core@7.5.0", "https://registry.npmmirror.com/unconfig-core/-/unconfig-core-7.5.0.tgz", { "dependencies": { "@quansync/fs": "^1.0.0", "quansync": "^1.0.0" } }, "sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w=="], - - "uncrypto": ["uncrypto@0.1.3", "https://registry.npmmirror.com/uncrypto/-/uncrypto-0.1.3.tgz", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="], - - "undici": ["undici@7.29.0", "https://registry.npmmirror.com/undici/-/undici-7.29.0.tgz", {}, "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw=="], - - "undici-types": ["undici-types@8.9.0", "", {}, "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg=="], - - "update-browserslist-db": ["update-browserslist-db@1.2.3", "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], - - "uri-js": ["uri-js@4.4.1", "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], - - "vite": ["vite@8.3.0", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.7", "postcss": "^8.5.28", "rolldown": "~1.2.6", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.7.1", "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-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ=="], - - "vitest": ["vitest@4.1.11", "", { "dependencies": { "@vitest/expect": "4.1.11", "@vitest/mocker": "4.1.11", "@vitest/pretty-format": "4.1.11", "@vitest/runner": "4.1.11", "@vitest/snapshot": "4.1.11", "@vitest/spy": "4.1.11", "@vitest/utils": "4.1.11", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.11", "@vitest/browser-preview": "4.1.11", "@vitest/browser-webdriverio": "4.1.11", "@vitest/coverage-istanbul": "4.1.11", "@vitest/coverage-v8": "4.1.11", "@vitest/ui": "4.1.11", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "./vitest.mjs" } }, "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw=="], - - "webfontloader": ["webfontloader@1.6.28", "", {}, "sha512-Egb0oFEga6f+nSgasH3E0M405Pzn6y3/9tOVanv/DLfa1YBIgcv90L18YyWnvXkRbIM17v5Kv6IT2N6g1x5tvQ=="], - - "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], - - "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], - - "which": ["which@2.0.2", "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - - "why-is-node-running": ["why-is-node-running@2.3.0", "https://registry.npmmirror.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], - - "word-wrap": ["word-wrap@1.2.5", "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], - - "xtend": ["xtend@4.0.2", "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], - - "yallist": ["yallist@3.1.1", "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], - - "yocto-queue": ["yocto-queue@1.2.2", "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-1.2.2.tgz", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="], - - "zod": ["zod@4.6.4", "", {}, "sha512-AXSD6hvGdvRjajG/l1cC+d6IrhH+sjmPKtYeQdJIK8MFJl3LyClzS+o/YsVC+zQZPupAaeH5skwwm8YqYH7BqA=="], - - "zod-validation-error": ["zod-validation-error@4.0.2", "https://registry.npmmirror.com/zod-validation-error/-/zod-validation-error-4.0.2.tgz", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="], - - "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "https://registry.npmmirror.com/esbuild/-/esbuild-0.18.20.tgz", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], - - "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - - "@tailwindcss/node/lightningcss": ["lightningcss@1.32.0", "https://registry.npmmirror.com/lightningcss/-/lightningcss-1.32.0.tgz", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], - - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.1", "https://registry.npmmirror.com/@emnapi/core/-/core-1.11.1.tgz", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="], - - "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.1", "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.11.1.tgz", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="], - - "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "https://registry.npmmirror.com/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], - - "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "https://registry.npmmirror.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="], - - "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="], - - "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "@types/nodemailer/@types/node": ["@types/node@25.9.3", "https://registry.npmmirror.com/@types/node/-/node-25.9.3.tgz", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg=="], - - "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "https://registry.npmmirror.com/ignore/-/ignore-7.0.5.tgz", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], - - "@typescript-eslint/typescript-estree/semver": ["semver@7.8.5", "https://registry.npmmirror.com/semver/-/semver-7.8.5.tgz", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], - - "better-call/@better-auth/utils": ["@better-auth/utils@0.5.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-BL8W4EfIZFwlu0r54m3v1ztjDhu6dDe/amLTm0xybmbZaNgYUqhD3SjpAsnq0q8YD6/ki4iwIgxJNLP/N3TxiA=="], - - "eslint-plugin-react-hooks/zod": ["zod@4.3.6", "https://registry.npmmirror.com/zod/-/zod-4.3.6.tgz", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], - - "p-locate/p-limit": ["p-limit@3.1.0", "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], - - "pg/pg-protocol": ["pg-protocol@1.16.0", "", {}, "sha512-sILXutLVjCLjcDuOmvhX5e2Z4cS5qG/6Bu3VkpFwdf/633ElGLpEh9bgmuI5I4sqKqkifQiGyiCcx1HdtrK7tg=="], - - "phaser4-rex-plugins/eventemitter3": ["eventemitter3@3.1.2", "", {}, "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q=="], - - "qified/hookified": ["hookified@2.2.0", "", {}, "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA=="], - - "sharp/semver": ["semver@7.8.5", "https://registry.npmmirror.com/semver/-/semver-7.8.5.tgz", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], - - "sort-package-json/semver": ["semver@7.7.4", "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], - - "sort-package-json/tinyglobby": ["tinyglobby@0.2.16", "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.16.tgz", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], - - "tinyglobby/picomatch": ["picomatch@4.0.4", "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], - - "tsx/esbuild": ["esbuild@0.27.7", "https://registry.npmmirror.com/esbuild/-/esbuild-0.27.7.tgz", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], - - "@tailwindcss/node/lightningcss/lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "https://registry.npmmirror.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], - - "@tailwindcss/node/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "https://registry.npmmirror.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], - - "@tailwindcss/node/lightningcss/lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "https://registry.npmmirror.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], - - "@tailwindcss/node/lightningcss/lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "https://registry.npmmirror.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], - - "@tailwindcss/node/lightningcss/lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "https://registry.npmmirror.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], - - "@tailwindcss/node/lightningcss/lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "https://registry.npmmirror.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], - - "@tailwindcss/node/lightningcss/lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "https://registry.npmmirror.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], - - "@tailwindcss/node/lightningcss/lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "https://registry.npmmirror.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], - - "@tailwindcss/node/lightningcss/lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "https://registry.npmmirror.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], - - "@tailwindcss/node/lightningcss/lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "https://registry.npmmirror.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], - - "@tailwindcss/node/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "https://registry.npmmirror.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], - - "@types/nodemailer/@types/node/undici-types": ["undici-types@7.24.6", "https://registry.npmmirror.com/undici-types/-/undici-types-7.24.6.tgz", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], - - "p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - - "sort-package-json/tinyglobby/picomatch": ["picomatch@4.0.4", "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], - - "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="], - - "tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.27.7.tgz", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="], - - "tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.7", "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", { "os": "android", "cpu": "arm64" }, "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="], - - "tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.7", "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.27.7.tgz", { "os": "android", "cpu": "x64" }, "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg=="], - - "tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.7", "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw=="], - - "tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.7", "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ=="], - - "tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.7", "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w=="], - - "tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.7", "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ=="], - - "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.7", "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", { "os": "linux", "cpu": "arm" }, "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="], - - "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.7", "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="], - - "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.7", "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg=="], - - "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.7", "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", { "os": "linux", "cpu": "none" }, "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="], - - "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.7", "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", { "os": "linux", "cpu": "none" }, "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw=="], - - "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.7", "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="], - - "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.7", "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", { "os": "linux", "cpu": "none" }, "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ=="], - - "tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.7", "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="], - - "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.7", "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", { "os": "linux", "cpu": "x64" }, "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA=="], - - "tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.7", "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", { "os": "none", "cpu": "arm64" }, "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w=="], - - "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.7", "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", { "os": "none", "cpu": "x64" }, "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw=="], - - "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.7", "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", { "os": "openbsd", "cpu": "arm64" }, "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A=="], - - "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.7", "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="], - - "tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.7", "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", { "os": "none", "cpu": "arm64" }, "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="], - - "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.7", "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", { "os": "sunos", "cpu": "x64" }, "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="], - - "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.7", "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA=="], - - "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.7", "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw=="], - - "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="], - } -} diff --git a/docker/Dockerfile.app b/docker/Dockerfile.app index b912a845..65247030 100644 --- a/docker/Dockerfile.app +++ b/docker/Dockerfile.app @@ -1,21 +1,31 @@ # syntax=docker/dockerfile:1 -FROM oven/bun:1.3.13 AS builder +FROM node:24.18.0-bookworm-slim AS node-base +ENV PNPM_HOME=/pnpm +ENV PATH=$PNPM_HOME:$PATH +RUN npm install --global pnpm@10.34.6 + +FROM node-base AS builder WORKDIR /app -COPY package.json bun.lock* ./ -RUN bun install --frozen-lockfile +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ +COPY apps/api/package.json ./apps/api/package.json +COPY apps/web/package.json ./apps/web/package.json +COPY packages/shared/package.json ./packages/shared/package.json +RUN pnpm install --frozen-lockfile COPY . . -RUN bun run build:server +RUN pnpm run build:server && pnpm --filter @daoyou/api deploy --prod /out -FROM oven/bun:1.3.13 AS runtime +FROM node:24.18.0-bookworm-slim AS runtime WORKDIR /app ENV NODE_ENV=production ENV PORT=3000 -COPY --from=builder /app/dist ./dist +COPY --from=builder /out ./ +USER node EXPOSE 3000 -CMD ["bun", "run", "dist/index.js"] +STOPSIGNAL SIGTERM +CMD ["node", "dist/main.js"] diff --git a/docs/admin-inventory-rewards.md b/docs/admin-inventory-rewards.md index 5e539c2b..81d92497 100644 --- a/docs/admin-inventory-rewards.md +++ b/docs/admin-inventory-rewards.md @@ -44,7 +44,7 @@ bun --env-file=env/local.env node_modules/drizzle-kit/bin.cjs migrate - 桌面及 360px 物品预览可用,器诀展开保持浮层,Esc 关闭正常;浏览器控制台无错误。 - 验收商品已下架,兑换码已停用;实际玩法资产和领取历史保留。 -静态及共享逻辑检查:`bun run lint`、`bun run build`、`bun run test src/shared/contracts src/shared/inventory src/shared/items src/shared/lib`、`git diff --check`。 +静态及共享逻辑检查:`bun run lint`、`bun run build`、`bun run test packages/shared/src/contracts packages/shared/src/inventory packages/shared/src/items packages/shared/src/lib`、`git diff --check`。 未执行全仓战斗单测、LLM 材料/灵种批量生成的实际调用或预发布/生产部署。本轮浏览器验收覆盖上述代表性道具;全部固定物品定义的奖励校验由共享单测覆盖。 diff --git a/docs/artifact-migration.md b/docs/artifact-migration.md index 7899e9ca..7098d2fc 100644 --- a/docs/artifact-migration.md +++ b/docs/artifact-migration.md @@ -46,8 +46,8 @@ - 临时补齐背包至 40 格后,四件后续成品均进入仓库;图纸可追加原堆叠,新增堆叠在满包时进入仓库。准备阶段本地 dev 发放接口一次返回 500且未落库,随后使用既有角色命令事务及 grantInventory 准备占位物品,没有修改 dev 接口。 - 一次开发服务连接中断时,页面提示核对清单与库存;数据库确认来源保留、没有发奖,重连后成功兑换。未进行提交后断线或数据库故障注入验收。 - 验收后通过角色锁和资源事务删除本次奖励及 17 件占位道装。原有 19 条库存逐字段恢复一致;普通资源事件及递增版本保留。 -- `bun run test src/shared/artifact-migration`:6 项通过,覆盖九境界映射、异常 fallback、评分边界、六部位抽取、所有映射档位的道装生成及输入校验。`bun run lint`、`bun run build:server`、`bunx vite build` 通过。`bunx tsc -b tsconfig.app.json` 仍有两处既有 `.at()` 编译目标错误(PerformancePlayer、guide/schema),未修改这些文件。 +- `bun run test packages/shared/src/artifact-migration`:6 项通过,覆盖九境界映射、异常 fallback、评分边界、六部位抽取、所有映射档位的道装生成及输入校验。`bun run lint`、`bun run build:server`、`bunx vite build` 通过。`bunx tsc -b apps/web/tsconfig.json` 仍有两处既有 `.at()` 编译目标错误(PerformancePlayer、guide/schema),未修改这些文件。 ## 迁移结束后删除 -确认有效角色的待兑换资产及异常来源均处理完毕后,可删除 `src/server/routes/api/artifact-migration/`、`src/shared/artifact-migration/`、`src/shared/contracts/artifactMigration.ts`、玩家页面,并移除 API 注册、React 路由、gameNavigation 和 CaveQuickGrid 中对应入口。无需清理专属余额或状态表;已发放的道装、图纸和符箓均为普通既有物品,不依赖工具。旧成品表还有其他类型来源,不能随本工具整表删除。 +确认有效角色的待兑换资产及异常来源均处理完毕后,可删除 `apps/api/src/routes/api/artifact-migration/`、`packages/shared/src/artifact-migration/`、`packages/shared/src/contracts/artifactMigration.ts`、玩家页面,并移除 API 注册、React 路由、gameNavigation 和 CaveQuickGrid 中对应入口。无需清理专属余额或状态表;已发放的道装、图纸和符箓均为普通既有物品,不依赖工具。旧成品表还有其他类型来源,不能随本工具整表删除。 diff --git a/docs/auction-beast-trading-plan.md b/docs/auction-beast-trading-plan.md index e76f304b..e943f449 100644 --- a/docs/auction-beast-trading-plan.md +++ b/docs/auction-beast-trading-plan.md @@ -124,7 +124,7 @@ | 阶段 | 主要修改位置 | 完成标准 | | --- | --- | --- | -| 1. 纯协议与规则 | `src/shared/contracts/auction.ts`、`src/shared/contracts/mail.ts`、`src/shared/types/mail.ts`、附件校验所在 `src/shared/lib/itemLibrary.ts`、必要的灵兽交易纯规则模块 | 两种货单快照可区分;客户端不能提交个体事实;合法个体、数量与公共预览规则有纯函数测试 | +| 1. 纯协议与规则 | `packages/shared/src/contracts/auction.ts`、`packages/shared/src/contracts/mail.ts`、`packages/shared/src/types/mail.ts`、附件校验所在 `packages/shared/src/lib/itemLibrary.ts`、必要的灵兽交易纯规则模块 | 两种货单快照可区分;客户端不能提交个体事实;合法个体、数量与公共预览规则有纯函数测试 | | 2. 领取能力 | `MailInventory.ts` 或聚焦的 `MailBeastDelivery.ts`、`PlayerMailApplicationService.ts`、`combatV6BeastRepository.ts` | 单封、批量、满仓、占用及重复领取闭环;培养事实与原 ID 保留;领取不自动入队 | | 3. 寄售链路 | `auction.router.ts`、`AuctionApplicationService.ts`、`AuctionService.ts`、`auctionRepository.ts`、必要的共享灵兽占用检查 | 上架、购买、下架、到期、公私交易与幂等完整;普通物品链路继续可用 | | 4. 界面 | 拍卖上架/列表、共享灵兽只读详情、邮件详情/批量领取反馈、灵兽页必要刷新 | 桌面与窄屏均可选兽、验明事实、购入、领回;失败提示可理解且可重试 | @@ -178,10 +178,10 @@ ### 命令结果 -- `bun run test src/shared/contracts/beastTrade.test.ts src/shared/contracts/auction.test.ts src/shared/config/auctionConfig.test.ts src/shared/contracts/mail.test.ts`:4 文件、21 项通过。 +- `bun run test packages/shared/src/contracts/beastTrade.test.ts packages/shared/src/contracts/auction.test.ts packages/shared/src/config/auctionConfig.test.ts packages/shared/src/contracts/mail.test.ts`:4 文件、21 项通过。 - `bun run lint`、`bun run build`、`bun run build:server` 通过;构建保留既有 chunk 大小与动态/静态导入提示。 -- 最后一次重跑完整构建曾与其他任务共用 `dist/assets`,清理目录时报 `ENOTEMPTY`。随后 `bunx tsc -b tsconfig.app.json tsconfig.node.json --pretty false` 通过,客户端以 `bunx vite build --outDir /tmp/daoyou-auction-beast-client-20260919` 独立输出并构建成功,服务端 Vite 构建再次通过;最终 lint 也通过。 -- 全量 `bun run test`:179 文件通过、1 文件失败;1595 项通过、1 项失败。失败为未修改的 `src/shared/lib/bodyCultivation/pack.test.ts`,断言仍期待旧说明文案;不是灵兽交易失败。 +- 最后一次重跑完整构建曾与其他任务共用 `dist/assets`,清理目录时报 `ENOTEMPTY`。随后 `bunx tsc -b apps/web/tsconfig.json tsconfig.node.json --pretty false` 通过,客户端以 `bunx vite build --outDir /tmp/daoyou-auction-beast-client-20260919` 独立输出并构建成功,服务端 Vite 构建再次通过;最终 lint 也通过。 +- 全量 `bun run test`:179 文件通过、1 文件失败;1595 项通过、1 项失败。失败为未修改的 `packages/shared/src/lib/bodyCultivation/pack.test.ts`,断言仍期待旧说明文案;不是灵兽交易失败。 - 本轮文件定向 ESLint、服务端 TypeScript 检查及 `git diff --check` 通过。未增加服务端/数据库/React 单元测试或一次性验收脚本。 ### 验证边界 @@ -206,8 +206,8 @@ ### 本轮验证 -- `bunx tsc -b tsconfig.app.json tsconfig.node.json --pretty false`、最终 `bun run lint`、`bun run build`、`git diff --check` 通过。 -- 聚焦运行 `bun run test src/shared/contracts/beastTrade.test.ts src/shared/contracts/auction.test.ts src/shared/config/auctionConfig.test.ts src/shared/contracts/mail.test.ts`,4 文件21项通过。本轮未重跑全量共享测试;未增加 React、服务端测试或一次性验收脚本。 +- `bunx tsc -b apps/web/tsconfig.json tsconfig.node.json --pretty false`、最终 `bun run lint`、`bun run build`、`git diff --check` 通过。 +- 聚焦运行 `bun run test packages/shared/src/contracts/beastTrade.test.ts packages/shared/src/contracts/auction.test.ts packages/shared/src/config/auctionConfig.test.ts packages/shared/src/contracts/mail.test.ts`,4 文件21项通过。本轮未重跑全量共享测试;未增加 React、服务端测试或一次性验收脚本。 - Chrome local1 与独立应用内浏览器 local2 验证:上架3件材料和1只灵兽,栏目各显示正确货单数;上架后进入对应我的寄售;买方购买2件材料后库存从3变1;灵兽详情→精简确认→购入后货单移除;卖方通过同一物品预览下架余下1件。 - 检查桌面与360×800布局、物品格键盘聚焦预览、窄屏点击预览、详情与确认层、筛选切换、返回时恢复材料品级,以及携带中灵兽可查看但提交禁用、Esc关闭。修复小屏页签被操作区挤压;临时视口已恢复。最终卖方页面控制台无错误。 - 卖方两封成交款邮件(194、97灵石)和退回1件材料领取成功;买方灵兽邮件领取成功。买方2件材料的领取因 local2 旧背包存在未知物品定义返回500;该账号背包读取同样失败,邮件保持未领取。此既有数据问题未在 UI 重构中修复,不能将普通道具买方领取记为通过。 diff --git a/docs/auction-tradable-scope-audit.md b/docs/auction-tradable-scope-audit.md index 1129825e..bd453a89 100644 --- a/docs/auction-tradable-scope-audit.md +++ b/docs/auction-tradable-scope-audit.md @@ -22,7 +22,7 @@ 品质限价:玄品 100,000;真品 200,000;地品 400,000;天品 800,000;仙品 1,600,000;神品使用默认上限 9,999,999。凡品、灵品虽有价格配置,但在准入阶段就被拒绝。 -核心依据:`src/shared/contracts/auction.ts`、`src/shared/config/auctionConfig.ts`、`src/shared/items/registry.ts`、`src/server/lib/services/AuctionService.ts`。 +核心依据:`packages/shared/src/contracts/auction.ts`、`packages/shared/src/config/auctionConfig.ts`、`packages/shared/src/items/registry.ts`、`apps/api/src/lib/services/AuctionService.ts`。 ## 2. 明确不在交易范围内的资产 @@ -94,7 +94,7 @@ - 已沿注册表/纯规则 → 前端寄售与浏览 → API → 应用服务 → 交易服务/货单仓储 → 邮件附件检查当前链路。 - 已只读执行注册表与准入函数,确认定义数量和两种灵露可售;初次梳理时四种固定材料因凡品被拒绝,现已退役。 -- `bun run test src/shared/contracts/auction.test.ts src/shared/config/auctionConfig.test.ts`:2 个文件、10 项测试通过。现有无品质准入枚举测试未包含 `beast_refinement`,本次对两种灵露的结论另以实际规则调用核验。 +- `bun run test packages/shared/src/contracts/auction.test.ts packages/shared/src/config/auctionConfig.test.ts`:2 个文件、10 项测试通过。现有无品质准入枚举测试未包含 `beast_refinement`,本次对两种灵露的结论另以实际规则调用核验。 - 本次仅新增文档,检查文档差异与格式;未执行全量 lint、build、全量测试、浏览器交易、数据库查询或生产库存核对。 - 历史 Phase 10R 验收见 `docs/combat-v6-phase-10r-auction-inventory.md`。其未覆盖项仍是历史记录,本次静态梳理不将其升级为已验收;旧货单清理和生产部署状态也未重新确认。 @@ -106,9 +106,9 @@ - 幻境奖励包升级为 formatVersion 2,移除固定材料池;按挑战境界调用现有材料库产出,每件保存完整 `material.v1` 实例。四档数量、灵石、声望和每周领取规则保持不变。 - 在结算前将具体奖励保存到现有 Redis 战斗快照,重试复用相同物品;库存、货币与回放归档仍走原事务。幻境摘要显示实际材料名称。 - 不执行数据库迁移、旧库存转换、历史奖励改写或部署。存量固定材料不提供兼容读取。 -- 相关测试:`bun run test src/shared/rewards src/shared/inventory src/shared/items src/shared/forging src/shared/lib/dungeon/materialCosts.test.ts src/shared/contracts/auction.test.ts`,17 文件/100 项通过。 +- 相关测试:`bun run test packages/shared/src/rewards packages/shared/src/inventory packages/shared/src/items packages/shared/src/forging packages/shared/src/lib/dungeon/materialCosts.test.ts packages/shared/src/contracts/auction.test.ts`,17 文件/100 项通过。 - `bun run lint`、`bun run build`、`git diff --check` 通过。构建保留 chunk 大小及既有动态/静态导入提示。 -- `bun run test`:177 文件通过,1 文件失败;1586 项通过、1 项失败。失败位于本次未修改的 `src/shared/lib/bodyCultivation/pack.test.ts:20`,当前炼体说明文案与旧基线文案不同;幻境测试通过。 +- `bun run test`:177 文件通过,1 文件失败;1586 项通过、1 项失败。失败位于本次未修改的 `packages/shared/src/lib/bodyCultivation/pack.test.ts:20`,当前炼体说明文案与旧基线文案不同;幻境测试通过。 - 浏览器打开本地幻境入口,页面正常且无 error/warn 日志;只读查询确认 local1—local8 均为炼气后期,低于金丹准入门槛。未修改账号境界、库存或活动状态;未进行实际里程碑结算、断线重试、库为空或移动端验收。 ## 7. 灵兽本体接入(2026-09-19) diff --git a/docs/beast-avatar-generation.md b/docs/beast-avatar-generation.md index fc2b043a..c314b43e 100644 --- a/docs/beast-avatar-generation.md +++ b/docs/beast-avatar-generation.md @@ -10,7 +10,7 @@ ### 狰 -采用原稿 `exec-7845df59-a75b-43cd-8443-699ba3506b45.png`,有效图幅 250×256,素材 `public/assets/icons/beast-zheng.webp`,注册 `icon:beast-zheng`。 +采用原稿 `exec-7845df59-a75b-43cd-8443-699ba3506b45.png`,有效图幅 250×256,素材 `apps/web/public/assets/icons/beast-zheng.webp`,注册 `icon:beast-zheng`。 首稿仅有四条可辨尾巴,未采用;经局部修正补足五条独立尾巴后导出。 @@ -31,7 +31,7 @@ Use case: stylized-concept. 为中国修仙游戏万界道友生成一张全新 ### 蛇颈玄龟 -采用原稿 `exec-718b80ba-2725-4127-8ea4-de5527cfed6a.png`,有效图幅 256×241,素材 `public/assets/icons/beast-snake-neck-turtle.webp`,注册 `icon:beast-snake-neck-turtle`。 +采用原稿 `exec-718b80ba-2725-4127-8ea4-de5527cfed6a.png`,有效图幅 256×241,素材 `apps/web/public/assets/icons/beast-snake-neck-turtle.webp`,注册 `icon:beast-snake-neck-turtle`。 生成提示词: @@ -44,7 +44,7 @@ Use case: stylized-concept. 为万界道友生成全新「蛇颈玄龟」写意 ### 三足金蟾 -采用原稿 `exec-289fb494-6962-43ca-ab31-eea694f01819.png`,有效图幅 256×198,素材 `public/assets/icons/beast-three-legged-golden-toad.webp`,注册 `icon:beast-golden-toad`。 +采用原稿 `exec-289fb494-6962-43ca-ab31-eea694f01819.png`,有效图幅 256×198,素材 `apps/web/public/assets/icons/beast-three-legged-golden-toad.webp`,注册 `icon:beast-golden-toad`。 生成提示词: @@ -84,13 +84,13 @@ Use case: stylized-concept. 为万界道友绘制全新「三足金蟾」写意 墨迹本身构成形体,几块暖灰宽笔墨面与少量长线概括躯体,少量浓墨作结构支点,中淡墨、飞白与留白补全形体;不逐片勾鳞,不用写实体积光。盘身蓄势、低首侧顾,闭口警觉,头与前颈为主视觉;以角、狭长吻部、颈须和尾鳍表现暗河蛟类。仅在颈侧和尾鳍融入少量低饱和灰青彩墨。独立真实透明底,无背景、光效、文字、印章。人物基准只作笔墨参考,不复制人物造型。 ``` -原图 1254×1254,经统一占幅处理后导出 256×256 透明 WebP。素材为 `public/assets/icons/beast-ink-jiao.webp`,沿用 `icon:beast-ink-jiao` 注册,不修改 UI、物种或个体数据。 +原图 1254×1254,经统一占幅处理后导出 256×256 透明 WebP。素材为 `apps/web/public/assets/icons/beast-ink-jiao.webp`,沿用 `icon:beast-ink-jiao` 注册,不修改 UI、物种或个体数据。 ## 火鸦(2026-09-18) 依据当前物种设定“黑羽赤喉、翼下暗红火羽”,以头胸半身和微张的近侧翼表现机敏回望。暖灰墨面概括黑羽,喉部和翼下局部暗朱砂、赭红彩墨。使用内置 imagegen 生成,固定男修、女修墨像仅作笔墨参考,未输入旧头像。 -采用 `exec-a9304db7-ece4-4029-abd1-b6c7e640fcb9.png`,导出 256×256 透明 WebP(Lanczos、质量 90),替换 `public/assets/icons/beast-fire-crow.webp`,沿用 `icon:beast-fire-crow` 注册。 +采用 `exec-a9304db7-ece4-4029-abd1-b6c7e640fcb9.png`,导出 256×256 透明 WebP(Lanczos、质量 90),替换 `apps/web/public/assets/icons/beast-fire-crow.webp`,沿用 `icon:beast-fire-crow` 注册。 生成提示词: @@ -113,7 +113,7 @@ Use case: stylized-concept. 为万界道友绘制全新「三足金蟾」写意 依据当前物种设定重新设计:长颈轻曲、俯首、单翼舒展,暖灰淡墨与留白表现近白羽毛,翼面局部青碧与淡金彩墨提示玉质通透感。使用内置 imagegen,固定男修、女修墨像只作笔墨参考。保留活体羽毛的柔软结构,不使用玻璃折射或发光效果。 -采用 `exec-2e3ac479-008b-40aa-8f62-6451aaca25fc.png`,导出 256×256 透明 WebP(Lanczos、质量 90),替换 `public/assets/icons/beast-snow-crane.webp`。沿用 `icon:beast-snow-crane` 注册,不改物种 ID 和玩法。 +采用 `exec-2e3ac479-008b-40aa-8f62-6451aaca25fc.png`,导出 256×256 透明 WebP(Lanczos、质量 90),替换 `apps/web/public/assets/icons/beast-snow-crane.webp`。沿用 `icon:beast-snow-crane` 注册,不改物种 ID 和玩法。 生成提示词: @@ -142,7 +142,7 @@ Use case: stylized-concept. 依据当前物种设定重新设计:深灰细毛、银白腹部、蓬松长尾,以转身回首和轻探前爪表现警觉,尾端淡墨断笔融入透明留白,提示从尾端开始隐身的特点。仅使用暖灰、墨黑与留白。内置 imagegen 生成,固定男修、女修墨像只作笔墨参考,未查看或输入旧头像。 -采用 `exec-e340b0cc-0c76-40ec-81ad-d157bafee87b.png`,导出 256×256 透明 WebP(Lanczos、质量 90),替换 `public/assets/icons/beast-moon-marten.webp`,沿用 `icon:beast-moon-marten` 注册。 +采用 `exec-e340b0cc-0c76-40ec-81ad-d157bafee87b.png`,导出 256×256 透明 WebP(Lanczos、质量 90),替换 `apps/web/public/assets/icons/beast-moon-marten.webp`,沿用 `icon:beast-moon-marten` 注册。 生成提示词: @@ -167,7 +167,7 @@ Use case: stylized-concept. 依据当前设定的银灰甲壳、弯刃前肢和近透明薄翅,采用前半身近景,以三角头、细长前胸和成对捕捉足表现静伏警觉。暖灰宽笔和浓墨支点概括甲壳,薄翅融入极淡青灰;通过留白表现银灰质感。使用内置 imagegen,固定男修、女修墨像仅作笔墨参考,未输入旧头像。 -采用 `exec-203c6ca3-1849-45e3-8e58-289aa7a1f9e4.png`,导出 256×256 透明 WebP(Lanczos、质量 90),替换 `public/assets/icons/beast-silverwing-mantis.webp`,沿用 `icon:beast-silverwing-mantis` 注册。 +采用 `exec-203c6ca3-1849-45e3-8e58-289aa7a1f9e4.png`,导出 256×256 透明 WebP(Lanczos、质量 90),替换 `apps/web/public/assets/icons/beast-silverwing-mantis.webp`,沿用 `icon:beast-silverwing-mantis` 注册。 生成提示词: @@ -196,7 +196,7 @@ Use case: stylized-concept. 依据当前灰白长毛和眉侧各两枚灵目的设定,采用近正面略低首的头肩像,双主眼较大、左右眉侧各两枚小灵目,合计六目。以暖灰宽笔与留白概括长毛,眼鼻和肩背浓墨形成支点。使用内置 imagegen,固定男修、女修墨像仅作笔墨参考,未输入旧头像。 -采用 `exec-a66d9ec0-d5c1-45f4-954c-75f19a17a7ad.png`,导出 256×256 透明 WebP(Lanczos、质量 90),替换 `public/assets/icons/beast-six-eyed-ape.webp`,沿用 `icon:beast-six-eyed-ape` 注册。 +采用 `exec-a66d9ec0-d5c1-45f4-954c-75f19a17a7ad.png`,导出 256×256 透明 WebP(Lanczos、质量 90),替换 `apps/web/public/assets/icons/beast-six-eyed-ape.webp`,沿用 `icon:beast-six-eyed-ape` 注册。 生成提示词: @@ -227,7 +227,7 @@ Use case: stylized-concept. 依据当前墨蓝双翼、幽绿翅脉与缓慢游飞的设定,采用轻侧转、四瓣蝶翼错落展开的完整姿态。暖灰墨、墨黑与局部靛蓝宽笔组织翅面,少量灰玉绿彩墨翅脉提示微光,不添加背景光效。使用内置 imagegen,固定男修、女修墨像仅作笔墨参考,未输入旧头像。 -采用 `exec-f4bde284-a2d1-4cd9-816e-8f69fb909a6e.png`,导出 256×256 透明 WebP(Lanczos、质量 90),替换 `public/assets/icons/beast-ghost-lantern-butterfly.webp`,沿用 `icon:beast-lantern-butterfly` 注册。 +采用 `exec-f4bde284-a2d1-4cd9-816e-8f69fb909a6e.png`,导出 256×256 透明 WebP(Lanczos、质量 90),替换 `apps/web/public/assets/icons/beast-ghost-lantern-butterfly.webp`,沿用 `icon:beast-lantern-butterfly` 注册。 生成提示词: @@ -252,7 +252,7 @@ Use case: stylized-concept. 依据当前苍青巨翼、银色颈羽、深紫翼尖与积蓄雷息的设定,采用敛翼、侧首俯察的头胸近景。宽笔组织肩背和折翼,银羽以暖灰淡墨与留白表现,苍青与深紫局部融入灰墨,以形与笔势体现雷禽威势。使用内置 imagegen,固定男修、女修墨像仅作笔墨参考,未输入旧头像。 -采用 `exec-43de3161-38e0-4ea4-b9ec-ad61ff376664.png`,导出 256×256 透明 WebP(Lanczos、质量 90),替换 `public/assets/icons/beast-thunder-peng.webp`,沿用 `icon:beast-thunder-peng` 注册。 +采用 `exec-43de3161-38e0-4ea4-b9ec-ad61ff376664.png`,导出 256×256 透明 WebP(Lanczos、质量 90),替换 `apps/web/public/assets/icons/beast-thunder-peng.webp`,沿用 `icon:beast-thunder-peng` 注册。 生成提示词: @@ -282,7 +282,7 @@ Use case: stylized-concept. 咪咪保留低伏抬头、圆眼短足、虎斑白袜,减少写实毛发并修正尾巴从后躯绕向前方的连接。幽冥虎保留压头迈步的动作,依用户要求在背脊与尾部增加幽蓝鬼火;这是本物种局部例外,不修改通用笔墨规范。 -从1254×1254原稿以alpha≥4确定边界、保留原始半透明笔触,等比贴合256方形,WebP质量90。素材位于public/assets/icons,由统一registry注册。原稿、提示词及纸色/深底64px与256px检查图保存在工作区output/imagegen/mimi-nether-tiger;完整玩法与页面验收见[扩展记录](combat-v6-mimi-nether-tiger.md)。 +从1254×1254原稿以alpha≥4确定边界、保留原始半透明笔触,等比贴合256方形,WebP质量90。素材位于apps/web/public/assets/icons,由统一registry注册。原稿、提示词及纸色/深底64px与256px检查图保存在工作区output/imagegen/mimi-nether-tiger;完整玩法与页面验收见[扩展记录](combat-v6-mimi-nether-tiger.md)。 ### 咪咪定稿编辑提示词 diff --git a/docs/combat-v6-beast-aptitude-calibration.md b/docs/combat-v6-beast-aptitude-calibration.md index 9a1cacc0..09d6dc10 100644 --- a/docs/combat-v6-beast-aptitude-calibration.md +++ b/docs/combat-v6-beast-aptitude-calibration.md @@ -83,7 +83,7 @@ ## 6. 验证 -更新三物种新生/捕获/投影的 128 种子基线,显式检查五资质与成长落在采纳区间;保留旧范围个体在新配置下合法的测试。使用 `bun run test src/shared/engine/combat-v6/beasts`,再检查完整共享测试、lint 与构建。基线只随本次已授权的出生范围变化更新。 +更新三物种新生/捕获/投影的 128 种子基线,显式检查五资质与成长落在采纳区间;保留旧范围个体在新配置下合法的测试。使用 `bun run test packages/shared/src/engine/combat-v6/beasts`,再检查完整共享测试、lint 与构建。基线只随本次已授权的出生范围变化更新。 [boar]: https://news.4399.com/gonglue/mhxyou/chongwu/55/m/476195.html [tiger]: https://news.4399.com/gonglue/mhxyou/chongwu/55/m/476203.html diff --git a/docs/combat-v6-beast-birth-skills.md b/docs/combat-v6-beast-birth-skills.md index 2c1895b5..7bcab414 100644 --- a/docs/combat-v6-beast-birth-skills.md +++ b/docs/combat-v6-beast-birth-skills.md @@ -62,7 +62,7 @@ - 先添加失败测试复现:18种均无法满天生技能、没有无必带物种,共19项失败;改动后全部通过。 - 灵兽、野外、背包相关311项全部通过(包含在最终全量运行中)。覆盖0格、完整天生集合、初领/捕捉/洗炼共用结果、传承不扩格、全部物种概率与候选等权、非法池/必带数/满技能不可达拒绝。 -- `bun run test`:1542通过、1失败。唯一失败为未改动的 `src/shared/lib/bodyCultivation/pack.test.ts` 描述文案旧基线,与上轮一致。 +- `bun run test`:1542通过、1失败。唯一失败为未改动的 `packages/shared/src/lib/bodyCultivation/pack.test.ts` 描述文案旧基线,与上轮一致。 - `bun run lint`、`bun run build`通过;构建仍有既有的动态/静态混合导入及较大chunk提示。 - 18种各1000个固定种子,与本轮改动前数据比较:18,000组资质及成长完全一致;除出生技能配置外,18种所有字段保持不变。旧个体解析测试保留,生成摘要因本次有意变更技能而更新为修订9。 - 18种等权理论平均格数从2.786降为1.421;只是配置期望,不是按地图或玩家行为加权的实测。 @@ -76,7 +76,7 @@ - 18种物种只调整数量概率,技能池、必带、资质、成长、携带要求均保持修订9配置;内容修订升至10。 - 平均格数(物种等权)从1.421小幅上升至1.471,每种物种仍至少75%个体不超过两格。 -- `bun run test src/shared/engine/combat-v6/beasts src/shared/engine/combat-v6/wild src/shared/inventory`:22文件、311项通过。新增逐物种满技能概率3%—5%边界断言,既有1万种子抽样覆盖新概率;生成摘要同步为修订10。 +- `bun run test packages/shared/src/engine/combat-v6/beasts packages/shared/src/engine/combat-v6/wild packages/shared/src/inventory`:22文件、311项通过。新增逐物种满技能概率3%—5%边界断言,既有1万种子抽样覆盖新概率;生成摘要同步为修订10。 - `bun run lint`、`git diff --check`通过。 - 仅概率配置及测试/文档调整,本轮未重复全量测试、构建或浏览器验收。 @@ -92,7 +92,7 @@ 验收结果: -- `bun run test src/shared/inventory src/shared/engine/combat-v6/beasts src/shared/engine/combat-v6/wild`:22文件、312项通过。新增测试先复现旧拦截,随后验证0→1、第二次仅覆盖、旧对象不变、重复技能/越界格位/等级限制。 +- `bun run test packages/shared/src/inventory packages/shared/src/engine/combat-v6/beasts packages/shared/src/engine/combat-v6/wild`:22文件、312项通过。新增测试先复现旧拦截,随后验证0→1、第二次仅覆盖、旧对象不变、重复技能/越界格位/等级限制。 - `bun run lint`、`bun run build`、skill-creator校验、`git diff --check`通过。本轮未重复全量测试。 - 本地浏览器真实流程:临时样本经两次正常洗炼成为0技能;首次消耗1枚连击灵印,显示“开启第一个技能格”并获得连击;第二次消耗1枚噬血灵印,显示“随机覆盖一个已有技能”,结果为连击→噬血。 - 只读数据库确认样本保存为1格、仅噬血、修订4(两次洗炼+两次传承)。两种灵印各从测试补充后的6枚回到5枚。页面控制台无错误/警告。 diff --git a/docs/combat-v6-beast-classic-skills.md b/docs/combat-v6-beast-classic-skills.md index e64e926b..dcb3036d 100644 --- a/docs/combat-v6-beast-classic-skills.md +++ b/docs/combat-v6-beast-classic-skills.md @@ -307,7 +307,7 @@ ## 第二批验证记录 -- 定向:`bun run test src/shared/engine/combat-v6/beasts src/shared/rewards src/shared/combat-v6/skill-details.test.ts`,115 项通过。 +- 定向:`bun run test packages/shared/src/engine/combat-v6/beasts packages/shared/src/rewards packages/shared/src/combat-v6/skill-details.test.ts`,115 项通过。 - 全量:`bun run test`,152 个文件、1073 项通过。 - `bun run build`:客户端、服务端类型检查与构建通过;保留现有大体积 chunk 提示。 - `bun run lint`、`git diff --check`:通过。 diff --git a/docs/combat-v6-beast-inheritance.md b/docs/combat-v6-beast-inheritance.md index d6d16770..1c636286 100644 --- a/docs/combat-v6-beast-inheritance.md +++ b/docs/combat-v6-beast-inheritance.md @@ -11,7 +11,7 @@ ## 验证 -`bun run build`、`bun run lint`、相关23个测试文件270项通过;新增同名不同传承/品级堆叠回归后,`bun run test src/shared/inventory/inventory.test.ts`的7项通过(含新增1项)。`src`及`.agents`中旧兽诀/兽决名称检索无匹配,历史文档保留追溯记录。 +`bun run build`、`bun run lint`、相关23个测试文件270项通过;新增同名不同传承/品级堆叠回归后,`bun run test packages/shared/src/inventory/inventory.test.ts`的7项通过(含新增1项)。`src`及`.agents`中旧兽诀/兽决名称检索无匹配,历史文档保留追溯记录。 本地道友1实测已有灵火灵印:列表只显示传承灵印,预览显示所载传承灵火;领悟入口、选中提示、桌面与390×844确认框均明确技能与消耗。取消确认,未消耗物品、未替换技能。上品名称及不同传承的堆叠由共享测试验证,当前背包无上品样本,未宣称上品页面实测。未运行全量测试或发布部署。 diff --git a/docs/combat-v6-beast-mutation.md b/docs/combat-v6-beast-mutation.md index eaf93c2b..231acddb 100644 --- a/docs/combat-v6-beast-mutation.md +++ b/docs/combat-v6-beast-mutation.md @@ -29,8 +29,8 @@ - 新增纯共享领域测试覆盖全部 18 物种的数值提升、技能独立、普通兼容、面板只强化一次、重复洗炼、传承保留身份、概率边界、幼崽/成年变异、预览边界、身份一致性、战局恢复及回放归档。 - 普通个体的既有生成摘要测试保持原值。 -- `bun run lint`、`bun run build` 通过。全量 `bun run test`:1,584 通过,1 项既有失败为 `src/shared/lib/bodyCultivation/pack.test.ts` 的 `shortDesc` 文案基准,与本机制无关。 -- 最终聚焦测试 `bun run test src/shared/engine/combat-v6/beasts src/shared/engine/combat-v6/wild src/shared/combat-v6`:28 个文件、408 项通过。 +- `bun run lint`、`bun run build` 通过。全量 `bun run test`:1,584 通过,1 项既有失败为 `packages/shared/src/lib/bodyCultivation/pack.test.ts` 的 `shortDesc` 文案基准,与本机制无关。 +- 最终聚焦测试 `bun run test packages/shared/src/engine/combat-v6/beasts packages/shared/src/engine/combat-v6/wild packages/shared/src/combat-v6`:28 个文件、408 项通过。 ### 首版的本地浏览器验收(0.1%) @@ -50,4 +50,4 @@ 按用户要求,出现率从 0.1% 提高到 0.8%(每只独立,内容修订 8);移除所有战斗展示组件及其无障碍名称中的“变异”标识,保留异色。原始外观事实继续随战局和回放保存,数值、技能及洗炼规则不变。野外发现、灵兽袋列表和详情仍保留标签。 -本次调整验收:`bun run test src/shared/engine/combat-v6/wild src/shared/combat-v6` 的 12 个文件、92 项通过;`bun run lint`、`bun run build` 和 `git diff --check` 通过。代码检查确认三个战斗展示组件仅将变异身份传给头像,没有变异标签或变异无障碍名称。当前浏览器处于登录页,本次未重新开战手测,也未重跑全量测试;未操作用户保留的两只灵兽。 +本次调整验收:`bun run test packages/shared/src/engine/combat-v6/wild packages/shared/src/combat-v6` 的 12 个文件、92 项通过;`bun run lint`、`bun run build` 和 `git diff --check` 通过。代码检查确认三个战斗展示组件仅将变异身份传给头像,没有变异标签或变异无障碍名称。当前浏览器处于登录页,本次未重新开战手测,也未重跑全量测试;未操作用户保留的两只灵兽。 diff --git a/docs/combat-v6-beast-refinement.md b/docs/combat-v6-beast-refinement.md index eb609dfa..25c208d2 100644 --- a/docs/combat-v6-beast-refinement.md +++ b/docs/combat-v6-beast-refinement.md @@ -4,7 +4,7 @@ ## 规则与配置 -策划配置:`src/shared/engine/combat-v6/beasts/data/refinement.json`,配套JSON Schema和`refinement-config.ts`校验。配置维护名称、图标、颜色、描述、适用境界、单次消耗、叠加上限、归零等级修为和寿命恢复开关。 +策划配置:`packages/shared/src/engine/combat-v6/beasts/data/refinement.json`,配套JSON Schema和`refinement-config.ts`校验。配置维护名称、图标、颜色、描述、适用境界、单次消耗、叠加上限、归零等级修为和寿命恢复开关。 两档灵露共用 `origin-dew` 瓶形图标,通过 `color` 区分:归元灵露为 `jade`(青碧),上品归元灵露为 `gold`(金色)。储物袋格子与物品详情的图标、名称同步使用该配色,颜色不影响洗炼概率。 diff --git a/docs/combat-v6-beast-skill-copy.md b/docs/combat-v6-beast-skill-copy.md index cead6747..5585c9e4 100644 --- a/docs/combat-v6-beast-skill-copy.md +++ b/docs/combat-v6-beast-skill-copy.md @@ -1,6 +1,6 @@ # 灵兽技能名称与说明 -现行名称和首句说明以 `src/shared/engine/combat-v6/beasts/data/skills.json` 为准。下表记录本次旧名与现名,效果参数和稳定技能 ID 未改。玩家打开技能详情时,先看到简明效果,再看到由参数生成的概率、数值、代价和限制。 +现行名称和首句说明以 `packages/shared/src/engine/combat-v6/beasts/data/skills.json` 为准。下表记录本次旧名与现名,效果参数和稳定技能 ID 未改。玩家打开技能详情时,先看到简明效果,再看到由参数生成的概率、数值、代价和限制。 四种群体灵法由上品传承灵印承载:九霄神雷、山崩地裂、翻江倒海、红莲业火。它们仍是单档技能,不新增高级技能版本。普通和高级灵魂体暂时效果相同,保留两个独立技能与灵印,供后续扩展。 @@ -74,4 +74,4 @@ | `beast.denial` | 闭灵 | 绝灵 | 免疫常规异常且无法接受增益,但受到灵魂体的伤害增加。 | | `beast.advanced-denial` | 高级闭灵 | 高级绝灵 | 免疫常规异常且无法接受增益,所受法术伤害降低,但更易受到灵魂体的伤害。 | -当前数值与限制由 `src/shared/combat-v6/skill-details.ts` 读取技能参数生成;调整文案不能凭名称添加元素克制、持续伤害、物种条件或高级灵魂体的额外收益。 +当前数值与限制由 `packages/shared/src/combat-v6/skill-details.ts` 读取技能参数生成;调整文案不能凭名称添加元素克制、持续伤害、物种条件或高级灵魂体的额外收益。 diff --git a/docs/combat-v6-beast-species-generation.md b/docs/combat-v6-beast-species-generation.md index 3406aac4..c7a96d24 100644 --- a/docs/combat-v6-beast-species-generation.md +++ b/docs/combat-v6-beast-species-generation.md @@ -8,7 +8,7 @@ ## 数据权威与版本 -- `src/shared/engine/combat-v6/beasts/data/species.json` 是物种权威:格式2、内容修订5。 +- `packages/shared/src/engine/combat-v6/beasts/data/species.json` 是物种权威:格式2、内容修订5。 - `species.schema.json` 由 `BeastSpeciesPackShape` 生成,提供编辑提示;`loadBeastPacks` 负责结构与跨技能引用校验。 - 图标、名称、描述、开放境界、携带等级、初始伙伴资格、五项资质、成长及出生技能概率都在JSON中。没有固定定位或物种加点偏好。 - 讨论中的“结丹”对应游戏已有“金丹”键;炼气/筑基/金丹/元婴/化神初期分别为5/25/45/65/85级。 @@ -103,8 +103,8 @@ - `species.json` 内容修订7与设计基准逐项核对:十六种名称、五项资质、成长、核心、候选及概率一致,保留全部十五个旧ID,仅新增三足金蟾ID。生成参数、技能效果与投影公式未改。 - `wild.json` 内容修订5仍使用十个节点,狰沿用长风石海,三足金蟾加入黑水潭;地图描述同步爪痕、尾迹和地火泉眼。均使用现有等权遭遇与成年65—75级配置。 -- `bun run test src/shared/engine/combat-v6/beasts src/shared/engine/combat-v6/wild`:16个文件、258项通过。包括全物种确定性抽取、技能数量与概率、野外覆盖,以及旧犀牛在新物种目录下保留名称/资质/成长/技能/加点的解析和投影。初始物种生成摘要已按确认的名称、内容修订及出生规则更新。 +- `bun run test packages/shared/src/engine/combat-v6/beasts packages/shared/src/engine/combat-v6/wild`:16个文件、258项通过。包括全物种确定性抽取、技能数量与概率、野外覆盖,以及旧犀牛在新物种目录下保留名称/资质/成长/技能/加点的解析和投影。初始物种生成摘要已按确认的名称、内容修订及出生规则更新。 - `bun run lint`、`bun run build`(前后端类型检查与构建)、`git diff --check`通过。构建仍提示现有模块动态/静态导入重叠和大包体积。 -- 全量`bun run test`:1511项通过、1项失败;失败为`src/shared/lib/bodyCultivation/pack.test.ts`的炼体文案与`before-g5.json`旧基线不一致。相关配置、基线、加载代码及测试均与HEAD相同,未修改该无关问题。 +- 全量`bun run test`:1511项通过、1项失败;失败为`packages/shared/src/lib/bodyCultivation/pack.test.ts`的炼体文案与`before-g5.json`旧基线不一致。相关配置、基线、加载代码及测试均与HEAD相同,未修改该无关问题。 - 本地`127.0.0.1:5174`以已登录的本地道友1进行只读页面检查:旧个体“疾风狼2”显示物种“精灵狼”;旧“岩角犀”显示物种“狰”,保留成长1.208、速度资质644和原蛮力/避锋/耐法;黑水潭地图列出三足金蟾65—75级,进入野外后显示元婴门槛并禁用寻觅。控制台无error/warn。 - 未操作实际领取、捕捉、洗炼或数据库迁移;新个体生成与捕捉池由共享领域测试验证,不将只读页面检查记为端到端捕获验收。已有个体名作为可改名字段保留,物种名称由配置读取;专用头像未制作,狰、三足金蟾暂用豹、蛙emoji。 diff --git a/docs/combat-v6-build-content-governance-plan.md b/docs/combat-v6-build-content-governance-plan.md index 286ca8ac..1b32bd4c 100644 --- a/docs/combat-v6-build-content-governance-plan.md +++ b/docs/combat-v6-build-content-governance-plan.md @@ -21,17 +21,17 @@ | 编号 | 领域 | 当前主要入口 | 需要治理的内容 | | --- | --- | --- | --- | -| G1 | 道装基础与阵纹 | `src/shared/engine/combat-v6/equipment/content.ts`、`generator.ts` | 六部位模板、基础属性系数、偏好属性、九种阵纹、基础附加属性生成参数 | -| G2 | 器蕴、器诀与打造 | 同目录 `special-content.ts`、`generator.ts`、`forging.ts`;`src/shared/forging/rules.ts` | 特殊被动、器诀技能、战意消耗、数量概率、抽取池、打造材料增益和成本 | -| G3 | 召唤兽与兽诀 | `src/shared/engine/combat-v6/beasts/index.ts`、`progression.ts`;`src/shared/items/definitions/beast-books.ts` | 物种、资质成长、初始技能、兽诀效果及普通/高级关系、培养参数;拆开 Schema、生成和投影职责 | -| G4 | 宗门构筑 | `src/shared/engine/combat-v6/content/`、`sect-progression/` | 心法、技能、状态、流派、资源、经脉节点、技能修改项、特殊反应和养成费用 | -| G5 | 炼体与修炼 | `src/shared/lib/bodyCultivation/config.ts`;`src/shared/engine/combat-v6/projection/body-cultivation-v6.ts` | 五轨成长、肉身位阶要求、上限和战斗收益参数,统一成长与投影的参数来源 | -| G6 | 局内祝福与遭遇 | `src/shared/lib/tower/`;`src/shared/engine/combat-v6/tower/host.ts`、`wild/content.ts`、`encounter/content.ts`;`src/shared/rewards/` | 临时 build 的祝福效果,以及配套敌人、编组和奖励表;内容从 Host 中分离 | -| A1 | 构筑装配收敛 | `src/shared/engine/combat-v6/projection/`、`content/index.ts`、`version.ts` | 当前人物入口、历史阶段入口、宗门注册表和版本职责;独立于配置搬迁 | +| G1 | 道装基础与阵纹 | `packages/shared/src/engine/combat-v6/equipment/content.ts`、`generator.ts` | 六部位模板、基础属性系数、偏好属性、九种阵纹、基础附加属性生成参数 | +| G2 | 器蕴、器诀与打造 | 同目录 `special-content.ts`、`generator.ts`、`forging.ts`;`packages/shared/src/forging/rules.ts` | 特殊被动、器诀技能、战意消耗、数量概率、抽取池、打造材料增益和成本 | +| G3 | 召唤兽与兽诀 | `packages/shared/src/engine/combat-v6/beasts/index.ts`、`progression.ts`;`packages/shared/src/items/definitions/beast-books.ts` | 物种、资质成长、初始技能、兽诀效果及普通/高级关系、培养参数;拆开 Schema、生成和投影职责 | +| G4 | 宗门构筑 | `packages/shared/src/engine/combat-v6/content/`、`sect-progression/` | 心法、技能、状态、流派、资源、经脉节点、技能修改项、特殊反应和养成费用 | +| G5 | 炼体与修炼 | `packages/shared/src/lib/bodyCultivation/config.ts`;`packages/shared/src/engine/combat-v6/projection/body-cultivation-v6.ts` | 五轨成长、肉身位阶要求、上限和战斗收益参数,统一成长与投影的参数来源 | +| G6 | 局内祝福与遭遇 | `packages/shared/src/lib/tower/`;`packages/shared/src/engine/combat-v6/tower/host.ts`、`wild/content.ts`、`encounter/content.ts`;`packages/shared/src/rewards/` | 临时 build 的祝福效果,以及配套敌人、编组和奖励表;内容从 Host 中分离 | +| A1 | 构筑装配收敛 | `packages/shared/src/engine/combat-v6/projection/`、`content/index.ts`、`version.ts` | 当前人物入口、历史阶段入口、宗门注册表和版本职责;独立于配置搬迁 | 宗门五个定义文件当前合计约 2,077 行,已有类型与编译器,但技能、状态、节点、生成辅助函数和专属校验交织。不是全部重写,也不能简单把函数展开成巨型 JSON 就视为完成。 -`src/shared/engine/sect` 仍承担宗门组织与外围玩法,不视为整套废弃实现。治理宗门时,需要核定其与 combat-v6 对名称、ID、心法与解锁信息的权威归属。 +`packages/shared/src/engine/sect` 仍承担宗门组织与外围玩法,不视为整套废弃实现。治理宗门时,需要核定其与 combat-v6 对名称、ID、心法与解锁信息的权威归属。 ## 3. 推荐推进顺序 @@ -152,7 +152,7 @@ pack.ts # 加载与严格校验 ## 7. G1 实施与验证记录 -配置与策划说明:[道装基础数据包](../src/shared/engine/combat-v6/equipment/data/README.md)。 +配置与策划说明:[道装基础数据包](../packages/shared/src/engine/combat-v6/equipment/data/README.md)。 - 六模板、九阵纹与基础生成参数移至 `equipment/data/equipment-base.json`;`pack.ts` 严格校验结构、唯一性、部位身份、数值范围和概率总和。错误不会静默回退。 - `content.ts` 保留稳定导出和查询入口;`generator.ts`、`forging.ts`、`compiler.ts` 共享附灵数值范围,器胚继续共享模板定义。未改数值、ID、随机调用顺序、版本或数据库。 @@ -172,7 +172,7 @@ pack.ts # 加载与严格校验 ## 8. G2 实施与验证记录 -用户确认按 G2-A → G2-B 推进,并保留打造命名库;两批代码已完成。[策划编辑说明](../src/shared/engine/combat-v6/equipment/data/README-special-forging.md)。 +用户确认按 G2-A → G2-B 推进,并保留打造命名库;两批代码已完成。[策划编辑说明](../packages/shared/src/engine/combat-v6/equipment/data/README-special-forging.md)。 ### G2-A 特殊内容 @@ -189,7 +189,7 @@ pack.ts # 加载与严格校验 ### 验证结果(2026-09-11) -- 聚焦 `bun run test src/shared/engine/combat-v6/equipment src/shared/forging/forging.test.ts`:7 文件、99 项通过。 +- 聚焦 `bun run test packages/shared/src/engine/combat-v6/equipment packages/shared/src/forging/forging.test.ts`:7 文件、99 项通过。 - G1 18 组固定摘要全部保持一致,继续覆盖六部位、三器阶、各 256 种子及 V1/V2/三组 V3 生成流。 - 两份 Schema 与结构定义一致;非法 ID/机制/部位/引用/概率/成本及溢出输入拒绝测试通过;修改配置后生成池、成本、技能消耗和效果参数采用新值。 - 最终 `bun run test`:120 文件、849 项通过;`bun run lint`、`bun run build`(含前后端类型检查)通过,构建仍有前端大包提示。 @@ -203,7 +203,7 @@ pack.ts # 加载与严格校验 ## 9. G3 实施与验证记录 -用户确认按 G3-A → G3-B → G3-C 推进,并包含培养和面板参数。[策划编辑说明](../src/shared/engine/combat-v6/beasts/data/README.md)。 +用户确认按 G3-A → G3-B → G3-C 推进,并包含培养和面板参数。[策划编辑说明](../packages/shared/src/engine/combat-v6/beasts/data/README.md)。 - G3-A:三种物种、携带要求、初始技能、偏好资质/加点、资质与成长生成范围、出生等级/寿命及捕捉附带技能概率迁至 `beasts/data/species.json`。通过 ID 校验野外身份引用,保留野外敌人的独立数值。 - G3-B:五种技能的效果、耗蓝、普通/高级关系与兽诀注册标记迁至 `skills.json`。`skill-compiler.ts` 只实现现有四种机制;兽诀物品和连击说明识别从配置派生,删除另一份手工 ID 清单。 @@ -216,7 +216,7 @@ pack.ts # 加载与严格校验 - 迁移前采集三物种各 128 种子基线,对照免费出生、0/10/90/180 级捕捉、面板、升级、休养费用和阵容投影;迁移后全部一致。 - 五技能定义与旧 fixture 完全相同;各 32 种子真实内核状态与事件一致,并验证耗蓝、护盾、伤害及连击最多追加一次。普通/高级互斥沿用已有测试。 - 三份 JSON Schema 与结构定义一致;非法字段、引用、配对循环/交叉、概率、区间、经验和面板溢出测试通过。修改配置后的个体生成、点数、经验、面板、寿命、门槛与兽诀注册联动测试通过。 -- `bun run test src/shared/engine/combat-v6/beasts`:4 文件、39 项通过。 +- `bun run test packages/shared/src/engine/combat-v6/beasts`:4 文件、39 项通过。 - `bun run test`:123 文件、883 项通过;`bun run lint` 通过。 - `bun run build` 初次发现目标库不支持 `Array.at`,已替换为普通索引;重跑前后端类型检查及构建通过,并重跑灵兽聚焦测试与该文件 ESLint。保留原前端大包提示。 - `git diff --check` 通过;数值检索中留下的 1000 是成长/资质的单位换算,100000 是实例合法上限,均非另一份产出参数。 @@ -246,7 +246,7 @@ pack.ts # 加载与严格校验 - 加载时遍历全部可修炼等级与经脉层数,拒绝非整数、溢出与过大费用,防止结构合法的参数在高等级产生无效资源数。 - 迁移前记录全部 180 级心法与七层经脉费用摘要,迁移后逐表摘要一致。A1 五阶段装配摘要仍通过;新增配置变更实际影响心法面板与养成费用、非法配置和 Schema 同步检查。 - `bun run test`:126 文件、896 项通过;`bun run lint`、`bun run build`、`git diff --check` 通过。构建保留原前端大包提示。未新增本轮浏览器操作,因此修炼/经脉实际扣费与显示闭环待验。 -- 后续仍需迁移技能归属与解锁关系,核定 `src/shared/engine/sect/content/*/definition.ts` 中外围心法列表的真实消费者和权威归属,再继续技能/状态样板及流派节点迁移。第一批与 G4 整体均未标记完成。 +- 后续仍需迁移技能归属与解锁关系,核定 `packages/shared/src/engine/sect/content/*/definition.ts` 中外围心法列表的真实消费者和权威归属,再继续技能/状态样板及流派节点迁移。第一批与 G4 整体均未标记完成。 ### 技能学习关系迁移 diff --git a/docs/combat-v6-equipment-arts-refinement.md b/docs/combat-v6-equipment-arts-refinement.md index 4651aaf7..88214a2f 100644 --- a/docs/combat-v6-equipment-arts-refinement.md +++ b/docs/combat-v6-equipment-arts-refinement.md @@ -140,7 +140,7 @@ ## 首批30种实施验收(2026-09-13,历史记录) -- 30种器诀、8%产出率、全部六部位、单件一条已接入;8种待补证内容未加入定义及产出池。策划入口为 `src/shared/engine/combat-v6/equipment/data/equipment-special.json` 与 `equipment-forging.json`。 +- 30种器诀、8%产出率、全部六部位、单件一条已接入;8种待补证内容未加入定义及产出池。策划入口为 `packages/shared/src/engine/combat-v6/equipment/data/equipment-special.json` 与 `equipment-forging.json`。 - 机制测试覆盖30种基础施展与消耗,以及百分比治疗、群体逐目标基数、减疗、伤势上限、禁复活、在场灵兽复活、封法/封物理/禁行动、强弱覆盖、有限与整场期限、结果增减伤、固定伤害排除、多段比例与倒地停止、产出池隔离。既有器蕴用例继续通过。 - `bun run lint`、`bun run build`、`bun run test`通过:165文件、1320测试。`git diff --check`通过。构建仅有既有chunk大小提示。 - 本地浏览器:已登录的本地道友8,准备三叠灵音(现名三叠仙音)武器样本,核对60战意/归元后48、30%/50%/100%说明。桌面及360×800视口截图检查通过,说明与卸下按钮完整可见。实际穿戴与卸下成功,样本经库存计划、角色锁、事务及资源事件清理,储物袋回到0/40;临时视口已恢复。 @@ -193,7 +193,7 @@ ### 命名调整验收 -- `bun run test src/shared/engine/combat-v6/equipment src/shared/forging/forging.test.ts`:11文件、216测试通过,覆盖配置加载、编译、器诀机制及生成基线。 +- `bun run test packages/shared/src/engine/combat-v6/equipment packages/shared/src/forging/forging.test.ts`:11文件、216测试通过,覆盖配置加载、编译、器诀机制及生成基线。 - `git diff --check`:通过。逐项对比确认除20个名称、1处描述名称引用及内容修订号外,配置事实全部保持不变;38个器诀名称无重复。 - 本轮仅数据文案与文档调整,未运行全量测试、lint、build或浏览器验收;未部署。 diff --git a/docs/combat-v6-equipment-system-design.md b/docs/combat-v6-equipment-system-design.md index 2bac509f..76334aad 100644 --- a/docs/combat-v6-equipment-system-design.md +++ b/docs/combat-v6-equipment-system-design.md @@ -111,7 +111,7 @@ interface DaoEquipmentInstanceV1 { 2026-09-23 按确认方案硬切双孔:所有开放境界固定两孔,炼气/筑基/金丹/元婴/化神对应每孔上限 3/5/7/9/11。两孔可以同种、异种或空孔,固定属性相加,不乘武器器形或材料倍率。 -现行九种阵纹、保留 ID、部位与每级数值以 [双孔阵纹规则](../src/shared/engine/combat-v6/equipment/data/README.md#双孔阵纹) 为准。沧海删除、流云改名定神,洞明与定神分别改为封印命中和抵抗,新增回春治疗。旧单孔结构不保留运行时兼容;从未烙印但缺少双孔字段的已生成装备,通过业务迁移 `0054_equipment_formation_slots` 一次性补齐双空孔,避免人物资源投影失败。 +现行九种阵纹、保留 ID、部位与每级数值以 [双孔阵纹规则](../packages/shared/src/engine/combat-v6/equipment/data/README.md#双孔阵纹) 为准。沧海删除、流云改名定神,洞明与定神分别改为封印命中和抵抗,新增回春治疗。旧单孔结构不保留运行时兼容;从未烙印但缺少双孔字段的已生成装备,通过业务迁移 `0054_equipment_formation_slots` 一次性补齐双空孔,避免人物资源投影失败。 已实现注册表、双孔校验、属性贡献、宗门联动和装备预览;获取、合成、烙印、替换操作与经济消耗尚未接入。 diff --git a/docs/combat-v6-history-ui.md b/docs/combat-v6-history-ui.md index 39075b5d..fcfacb78 100644 --- a/docs/combat-v6-history-ui.md +++ b/docs/combat-v6-history-ui.md @@ -30,7 +30,7 @@ ## 验证(2026-09-15) - `bun run lint`、`bun run build` 通过;构建保留已有大包提示。 -- `bun run test src/shared/contracts/combatV6.test.ts src/shared/combat-v6/replay.test.ts`:23 项通过,未运行全量引擎测试。 +- `bun run test packages/shared/src/contracts/combatV6.test.ts packages/shared/src/combat-v6/replay.test.ts`:23 项通过,未运行全量引擎测试。 - 生成并检查 `0048` SQL、快照和 journal,确认唯一结构变化为 `replay` 允许 NULL;迁移已应用本地数据库,未触及其他环境。 - 本地已有角色页面:列表只显示天骄榜/擂台,天骄榜回放可访问,原野外回放 URL 返回不存在。 - 练功房完成一次逃离终局,当场播放和结束操作正常,不出现回放入口;只读数据库确认没有新增训练归档。 diff --git a/docs/combat-v6-jiujie-tiangong-redesign.md b/docs/combat-v6-jiujie-tiangong-redesign.md index 5a7b19f7..9e40d182 100644 --- a/docs/combat-v6-jiujie-tiangong-redesign.md +++ b/docs/combat-v6-jiujie-tiangong-redesign.md @@ -240,4 +240,4 @@ - 本轮不改变页面结构,未重复浏览器界面验收;不将机制回归测试视为完整PvP平衡验证。 - `bun run lint`、`bun run build`、`git diff --check`:通过。 - `bun run test`:209个文件中208个通过,2016项中2015项通过;唯一失败为原有幽都无宠试炼塔多种子用例超出默认5000ms(实际6384ms),没有机制断言失败。 -- 对九劫、通用伤害原语、自动战斗及试炼塔执行 `bun run test src/shared/combat-v6/auto.test.ts src/shared/engine/combat-v6/core/phase-3-primitives.test.ts src/shared/engine/combat-v6/content/jiujie src/shared/engine/combat-v6/tower/phase-two.test.ts --testTimeout=20000`:6个文件94项全部通过,包含追加的自动战斗准备阶段不变性测试。未修改项目超时设置或试炼塔断言。 +- 对九劫、通用伤害原语、自动战斗及试炼塔执行 `bun run test packages/shared/src/combat-v6/auto.test.ts packages/shared/src/engine/combat-v6/core/phase-3-primitives.test.ts packages/shared/src/engine/combat-v6/content/jiujie packages/shared/src/engine/combat-v6/tower/phase-two.test.ts --testTimeout=20000`:6个文件94项全部通过,包含追加的自动战斗准备阶段不变性测试。未修改项目超时设置或试炼塔断言。 diff --git a/docs/combat-v6-legacy-table-retirement.md b/docs/combat-v6-legacy-table-retirement.md index 2bed537c..acb0cb2c 100644 --- a/docs/combat-v6-legacy-table-retirement.md +++ b/docs/combat-v6-legacy-table-retirement.md @@ -45,7 +45,7 @@ ### 本次验证 - `bun run lint`、`bun run build` 通过;构建保留已有的大 chunk/混合静动态导入提示。 -- `bun run test src/shared/engine/combat-v6/tower src/shared/lib/tower src/shared/contracts/backgroundCommands.test.ts`:155 项通过,1 项战斗矩阵超出默认 5 秒限制;随后 `bun run test src/shared/engine/combat-v6/tower/phase-two.test.ts --testTimeout=30000`,该文件 10 项全部通过。未运行全仓测试。 +- `bun run test packages/shared/src/engine/combat-v6/tower packages/shared/src/lib/tower packages/shared/src/contracts/backgroundCommands.test.ts`:155 项通过,1 项战斗矩阵超出默认 5 秒限制;随后 `bun run test packages/shared/src/engine/combat-v6/tower/phase-two.test.ts --testTimeout=30000`,该文件 10 项全部通过。未运行全仓测试。 - 纯本地服务与现有管理员账号验证:本周配置重生成并发布成功,切换境界与首领层正常,历史周没有发布按钮,下周未发布空态正确;1280px/360px 页面无横向溢出,浏览器无 error/warn 日志。 - 游戏邮件仅执行人数预览(9 名活跃角色),未发送邮件;菜单及表单已移除邮箱群发和模板入口。匿名读取/重生成接口返回 401,已删除模板与邮箱群发 API 返回 404。 - 验收后恢复原本地登录账号与浏览器尺寸。未执行数据库迁移、生产操作或外部调度变更;多管理员并发冲突分支经代码检查,未进行多人运行验收。 diff --git a/docs/combat-v6-lingxiao-balance-review-2026-09-21.md b/docs/combat-v6-lingxiao-balance-review-2026-09-21.md index 57876d23..ea4050a1 100644 --- a/docs/combat-v6-lingxiao-balance-review-2026-09-21.md +++ b/docs/combat-v6-lingxiao-balance-review-2026-09-21.md @@ -19,12 +19,12 @@ 当前事实入口: -- [技能与状态配置](../src/shared/engine/combat-v6/content/data/lingxiao-combat.json) -- [双流派配置](../src/shared/engine/combat-v6/content/data/lingxiao-paths.json) -- [宗门编译器](../src/shared/engine/combat-v6/content/compiler.ts) -- [物理与命中公式](../src/shared/engine/combat-v6/rules-daoyou/formulas.ts) +- [技能与状态配置](../packages/shared/src/engine/combat-v6/content/data/lingxiao-combat.json) +- [双流派配置](../packages/shared/src/engine/combat-v6/content/data/lingxiao-paths.json) +- [宗门编译器](../packages/shared/src/engine/combat-v6/content/compiler.ts) +- [物理与命中公式](../packages/shared/src/engine/combat-v6/rules-daoyou/formulas.ts) -旧社会内容目录中的《此剑平生》、照影游尘、守拙藏锋不是本轮 V6 的技能和流派事实。该目录的 [DESIGN.md](../src/shared/engine/sect/content/lingxiao/DESIGN.md) 仅用于参考宗门主题,不能据此认定 V6 已有剑痕、承势等机制。 +旧社会内容目录中的《此剑平生》、照影游尘、守拙藏锋不是本轮 V6 的技能和流派事实。该目录的 [DESIGN.md](../packages/shared/src/engine/sect/content/lingxiao/DESIGN.md) 仅用于参考宗门主题,不能据此认定 V6 已有剑痕、承势等机制。 ## 2. 优先修复的机制与配置问题 @@ -236,7 +236,7 @@ 执行: ```bash -bun run test src/shared/engine/combat-v6/content/content.test.ts src/shared/engine/combat-v6/content/lingxiao-pack.test.ts src/shared/engine/combat-v6/content/lingxiao-path-pack.test.ts src/shared/engine/combat-v6/core/phase-3-primitives.test.ts src/shared/engine/combat-v6/rules-daoyou/rules-daoyou.test.ts +bun run test packages/shared/src/engine/combat-v6/content/content.test.ts packages/shared/src/engine/combat-v6/content/lingxiao-pack.test.ts packages/shared/src/engine/combat-v6/content/lingxiao-path-pack.test.ts packages/shared/src/engine/combat-v6/core/phase-3-primitives.test.ts packages/shared/src/engine/combat-v6/rules-daoyou/rules-daoyou.test.ts ``` 结果:5 个文件、35 项测试全部通过。其中包含双流派各 `3^7` 种组合的编译检查,但这不验证组合在战斗中的收益是否兑现。 diff --git a/docs/combat-v6-manual-enlightenment-design.md b/docs/combat-v6-manual-enlightenment-design.md index f623bb61..5c76c2d5 100644 --- a/docs/combat-v6-manual-enlightenment-design.md +++ b/docs/combat-v6-manual-enlightenment-design.md @@ -40,7 +40,7 @@ ### 锚定价值与候选境界 -品质基础价格与功法典籍类型倍率来自 `src/shared/engine/material/creation/config.ts`:品质基础价 × 3。合成不使用购买折扣、随机估价、回收价或拍卖成交价。 +品质基础价格与功法典籍类型倍率来自 `packages/shared/src/engine/material/creation/config.ts`:品质基础价 × 3。合成不使用购买折扣、随机估价、回收价或拍卖成交价。 | 品质 | 单本锚定价值 | 完整候选境界 | | --- | ---: | --- | @@ -130,7 +130,7 @@ ## 实现边界 -- 纯规则放入 `src/shared/manuals`:资格、单格分布、组合概率、费用与可注入随机源的结果选择。 +- 纯规则放入 `packages/shared/src/manuals`:资格、单格分布、组合概率、费用与可注入随机源的结果选择。 - 沿用既有功法内容和玉简定义,不给功法重新引入品质、稀有度或额外能力字段;产出概率属于悟道室供应规则。 - 服务端使用现有身份、中间件、库存、角色资源与天地灵气服务,校验归属、修订号、数量和既有活动占用规则。 - 用同一事务和幂等请求记录完成扣材、扣两项资源、结果入包及资源事件;失败结果也保存,重复请求不能重新抽取或重复收费。 @@ -160,10 +160,10 @@ - 本地 `127.0.0.1:5174`/`3001`,本地道友1(筑基中期):四凡必成得到庚金诀,扣 4 灵气+4 感悟;单凡随机失败,扣 1+1;两灵两玄显示 72.38%/24.13%/3.49%,临时天品参悟命格倍率 0.82 时实扣 10 灵气+9 感悟,得到清泉诀。三次结果均已保存幂等请求记录。 - 验证同堆叠连续投入、地/天品在筑基人物下禁用、高阶玉简概率保留、感悟为 0 时禁用提交、洞府入口、360px 抽屉与概率展示、返回桌面。360px 下文档 scrollWidth 为 345,不超出视口;页面未记录 error/warn。 - 临时典籍和两枚产出玉简通过库存计划精确清理,未清空背包。只读复核原 32 格物品 ID、定义、数量、实例事实全部一致,原命格名称/品质/描述/效果全部恢复,天地灵气恢复 290、感悟恢复 8;未改变已学功法。 -- `bun run test src/shared/manuals src/shared/lib/fates.test.ts src/shared/lib/qi.test.ts`:4 文件、38 项通过,含本次新增 8 项测试与全部 209 种材料组合。 +- `bun run test packages/shared/src/manuals packages/shared/src/lib/fates.test.ts packages/shared/src/lib/qi.test.ts`:4 文件、38 项通过,含本次新增 8 项测试与全部 209 种材料组合。 - `bun run lint`、`bun run build`、修改文件 Prettier 和 `git diff --check` 通过;构建仍提示既有 chunk 大小提醒。 -- `bun run test`:1760 项通过、2 项失败。未修改的 `src/shared/lib/bodyCultivation/pack.test.ts` 文案与旧 fixture 不同;未修改的 `src/shared/engine/combat-v6/tower/tower.test.ts` 仍断言第 1 层不发奖励,而当前逻辑发灵石。两者不在本次功能改动范围。 -- 尝试 `bun --env-file=env/local.env x hono request src/server/app.ts -P /api/combat-v6/enlightenment`,当前 hono 包无可执行 CLI;改用真实本地 HTTP 核对未登录 GET/POST 均返回 401。正常写入通过上述真实浏览器流程验收。 +- `bun run test`:1760 项通过、2 项失败。未修改的 `packages/shared/src/lib/bodyCultivation/pack.test.ts` 文案与旧 fixture 不同;未修改的 `packages/shared/src/engine/combat-v6/tower/tower.test.ts` 仍断言第 1 层不发奖励,而当前逻辑发灵石。两者不在本次功能改动范围。 +- 尝试 `bun --env-file=env/local.env x hono request apps/api/src/app.ts -P /api/combat-v6/enlightenment`,当前 hono 包无可执行 CLI;改用真实本地 HTTP 核对未登录 GET/POST 均返回 401。正常写入通过上述真实浏览器流程验收。 - 未做手机真机触摸、多标签并发和断网后重试实测;幂等与恢复路径完成代码检查,并核对成功/失败持久记录。请求遇锁冲突或超时保留原标识;仅确定未提交的参数/领域错误释放待核对状态。 ### 交互打磨(2026-09-21) diff --git a/docs/combat-v6-manual-first-release-plan.md b/docs/combat-v6-manual-first-release-plan.md index 9ff97ecd..e077c0a4 100644 --- a/docs/combat-v6-manual-first-release-plan.md +++ b/docs/combat-v6-manual-first-release-plan.md @@ -143,7 +143,7 @@ | `CombatV6ManualService.ts`、`characterLoadoutRepository.ts` | learned JSONB、槽位表、版本校验与事务 | 默认不改持久结构;保留角色锁、归属、revision、占用和提交后事件 | | 功法页面与物品 preview | 效果偏六维,页面仍显示常见/珍稀 | 共用效果描述器,展示本层/下层六维与机制;去掉功法稀有度文案 | -完整模块路径以 `src/shared/engine/combat-v6` 为基准;服务/仓储位于 `src/server/lib/services/combat-v6`、`src/server/lib/repositories`。 +完整模块路径以 `packages/shared/src/engine/combat-v6` 为基准;服务/仓储位于 `apps/api/src/lib/services/combat-v6`、`apps/api/src/lib/repositories`。 ### 4.1 功法内容格式 @@ -248,13 +248,13 @@ 按 [测试规范](testing.md) 复用本地测试账号,完成真实学习、升层、瓶颈、切换、刷新、查探与一场战斗。仅在 local 环境准备测试资源。核对当前资源不因面板变化免费补满,失败请求不扣资源,浏览器多页使用旧版本提交时正确刷新。 -不在 `src/react-app`、`src/server` 新增 test/spec;不 mock 数据库、API、Redis、SMTP 或其他第三方服务。 +不在 `apps/web/src`、`apps/api/src` 新增 test/spec;不 mock 数据库、API、Redis、SMTP 或其他第三方服务。 建议实施检查命令: ```bash -bun run test src/shared/engine/combat-v6/manuals src/shared/manuals -bun run test src/shared/engine/combat-v6/core src/shared/engine/combat-v6/projection src/shared/engine/combat-v6/rules-daoyou +bun run test packages/shared/src/engine/combat-v6/manuals packages/shared/src/manuals +bun run test packages/shared/src/engine/combat-v6/core packages/shared/src/engine/combat-v6/projection packages/shared/src/engine/combat-v6/rules-daoyou bun run lint bun run test bun run build @@ -286,7 +286,7 @@ git diff --check - 运行配置为 manual pack v3,野外 contentRevision/poolVersion 同步递增。没有改动原始 Excel。 - 本地迁移后、发放新验收玉简前:槽位 0;两个状态 learned 均为空;库存、拍卖、邮件、兑换码中的旧玉简引用均为 0。 -- 已执行聚焦功法/奖励/公开构筑测试、全量 shared 测试、lint、TypeScript 及客户端/服务端构建。全量结果:168 个文件通过,1390 项通过;仅原有炼体标签快照失败(src/shared/lib/bodyCultivation/pack.test.ts:20),该目录本次无修改。聚焦机制测试 20 项通过。lint、build、tsc 与 git diff --check 均通过。 +- 已执行聚焦功法/奖励/公开构筑测试、全量 shared 测试、lint、TypeScript 及客户端/服务端构建。全量结果:168 个文件通过,1390 项通过;仅原有炼体标签快照失败(packages/shared/src/lib/bodyCultivation/pack.test.ts:20),该目录本次无修改。聚焦机制测试 20 项通过。lint、build、tsc 与 git diff --check 均通过。 - 本地已有账号“本地道友1”真实页面验收:新玉简学习、首本自动激活、后续收藏不覆盖、六本上限隐藏学习入口、免费切换、刷新保留、长春一升二/二升三、三层玉简解锁、感悟不足禁用修炼。当前气血未因属性增长补满。 - 学习详情与升级确认同时展示六维、当前/下一层/圆满机制。首屏保持简洁,完整机制放在参悟与物品预览层。 - 九层成长、六层瓶颈、条件边界、来源/主目标过滤、比例固伤排除、状态分类、回合恢复和封印小数精度由纯共享测试覆盖。 diff --git a/docs/combat-v6-manual-portrait-assets.md b/docs/combat-v6-manual-portrait-assets.md index 790f2ea8..4c429922 100644 --- a/docs/combat-v6-manual-portrait-assets.md +++ b/docs/combat-v6-manual-portrait-assets.md @@ -4,8 +4,8 @@ ## 生产素材 -- `public/assets/manuals/cultivator-male-meditation.webp` -- `public/assets/manuals/cultivator-female-meditation.webp` +- `apps/web/public/assets/manuals/cultivator-male-meditation.webp` +- `apps/web/public/assets/manuals/cultivator-female-meditation.webp` 两张均为 960×960 WebP,保留透明度,使用质量 90、alphaQuality 100;轮廓框内留边。像素 alpha 覆盖 0–255。按白底反合成得到墨迹透明度,清除接近白色的背景微纹,保留衣内留白与飞白。页面正常 opacity 显示,不使用 CSS 混合模式。纸色并排检查:盘坐轮廓完整,暖灰墨色一致,发髻与衣侧有浓墨支点,边缘无棋盘或白色矩形;脸部仍有简化眉眼,非纯减笔肖像。 diff --git a/docs/combat-v6-manual-system-design.md b/docs/combat-v6-manual-system-design.md index 1db0db0f..b1365bd3 100644 --- a/docs/combat-v6-manual-system-design.md +++ b/docs/combat-v6-manual-system-design.md @@ -19,7 +19,7 @@ ## 策划数据包 -入口:`src/shared/engine/combat-v6/manuals/data/manual-pack.json`。 +入口:`packages/shared/src/engine/combat-v6/manuals/data/manual-pack.json`。 同目录 JSON Schema 提供编辑器提示;`pack.ts` 使用 Zod 严格校验结构和跨字段规则。构建加载即校验,不做在线热更新、不支持任意公式或脚本。 @@ -75,7 +75,7 @@ - 只读数据库确认永久体魄仍为 50;长春功九层激活时页面气血上限由 1552 增至 1732,切换松鹤后为 1572,法力上限 900 增至 910,符合派生公式。 - 桌面及 360×800 页面和详情抽屉已检查,无横向溢出;当前新测试页面控制台无 error。未开展本轮真实 PvP、概率掉落采样、拍卖成交及并发故障注入;对应确定性边界由共享测试与事务检查覆盖。 - 准备性修为/感悟恢复原值 235/0;保留真实学成的两本功法记录,已发四本玉简全部正常消费,没有删除既有角色或旧测试数据。迁移仅应用本地,不涉及预发布、生产或部署。 -- 检查命令:聚焦 Vitest、全量 `bun run test`、`bun run lint`、`bunx tsc -b tsconfig.app.json tsconfig.node.json`、`bun run build`、`git diff --check`。数据迁移由 Drizzle Kit generate 生成并检查,使用显式 local.env 执行 migrate。 +- 检查命令:聚焦 Vitest、全量 `bun run test`、`bun run lint`、`bunx tsc -b apps/web/tsconfig.json tsconfig.node.json`、`bun run build`、`git diff --check`。数据迁移由 Drizzle Kit generate 生成并检查,使用显式 local.env 执行 migrate。 ### 页面重构验收(2026-09-11) @@ -84,7 +84,7 @@ - local2 只读检查女修立绘与无可学习玉简提示,未修改其功法或资源;完成后恢复浏览器原 local5 会话。 - 两个页面先后操作真实激活接口,过期 expectedRevision 被拒绝,页面显示版本变化提示并读取服务端当前激活项。正常切换请求与状态读取均为 200,无额外重复提交。 - 桌面 1280×800、窄屏 360×800 及物品栏/确认抽屉完成检查,立绘保持完整轮廓与真实透明底。正常功法流程未见控制台 error。内置浏览器不支持原生触摸事件注入,横向拖动通过真实指针事件验证;未做手机真机验收、战斗占用流程或本轮高境界实号学习,相关边界由共享规则测试与现有服务端守卫检查覆盖。 -- 新增学习上限测试先复现旧规则允许第四种,再验证修复。`bun run test src/shared/engine/combat-v6/manuals src/shared/manuals`:21 项通过;全量 `bun run test`:148 文件/1017 项通过;`bun run lint`、`bunx tsc -b tsconfig.app.json tsconfig.node.json`、最终 `bun run build`、`git diff --check` 通过。构建仍有既有包体大小提醒。本轮不涉及迁移或部署。 +- 新增学习上限测试先复现旧规则允许第四种,再验证修复。`bun run test packages/shared/src/engine/combat-v6/manuals packages/shared/src/manuals`:21 项通过;全量 `bun run test`:148 文件/1017 项通过;`bun run lint`、`bunx tsc -b apps/web/tsconfig.json tsconfig.node.json`、最终 `bun run build`、`git diff --check` 通过。构建仍有既有包体大小提醒。本轮不涉及迁移或部署。 ### 功法交互重设计验收(2026-09-21) diff --git a/docs/combat-v6-mhxy-redesign-roadmap.md b/docs/combat-v6-mhxy-redesign-roadmap.md index 5d97e7fb..6e78accb 100644 --- a/docs/combat-v6-mhxy-redesign-roadmap.md +++ b/docs/combat-v6-mhxy-redesign-roadmap.md @@ -16,8 +16,8 @@ > 建立日期:2026-09-01 > 适用分支:`codex/combat-v6` > 建立时基线提交:`f9510ab5` -> 核心目录:`src/shared/engine/combat-v6` -> 角色面板:[`combat-v6 角色六维与战斗面板设计`](./combat-v6-character-panel-design.md) 修炼系统:[`combat-v6 新版修炼系统设计`](./combat-v6-training-system-design.md) 宗门系统:[`combat-v6 宗门心法、技能与经脉系统设计`](./combat-v6-sect-skill-meridian-system-design.md) 红尘剑宗纵切:[`combat-v6 红尘剑宗纵切设计`](./combat-v6-lingxiao-datang-sect-design.md) 九劫天宫纵切:[`combat-v6 九劫天宫经典双流派纵切`](./combat-v6-jiujie-sect-design.md) 召唤兽系统:[`combat-v6 召唤兽系统设计`](./combat-v6-summoned-beast-system-design.md) 兼容性明细:`src/shared/engine/combat-v6/CHARACTER_COMPATIBILITY.md` +> 核心目录:`packages/shared/src/engine/combat-v6` +> 角色面板:[`combat-v6 角色六维与战斗面板设计`](./combat-v6-character-panel-design.md) 修炼系统:[`combat-v6 新版修炼系统设计`](./combat-v6-training-system-design.md) 宗门系统:[`combat-v6 宗门心法、技能与经脉系统设计`](./combat-v6-sect-skill-meridian-system-design.md) 红尘剑宗纵切:[`combat-v6 红尘剑宗纵切设计`](./combat-v6-lingxiao-datang-sect-design.md) 九劫天宫纵切:[`combat-v6 九劫天宫经典双流派纵切`](./combat-v6-jiujie-sect-design.md) 召唤兽系统:[`combat-v6 召唤兽系统设计`](./combat-v6-summoned-beast-system-design.md) 兼容性明细:`packages/shared/src/engine/combat-v6/CHARACTER_COMPATIBILITY.md` 本文档是 combat-v6 后续设计和实现的方向基线。代码实现、数据模型、内容配置和迁移策略若与本文冲突,应先修改本文并记录决策,再修改代码,避免实现过程中重新滑回 battle-v5 / SPA + GAS 的设计方式。 @@ -106,10 +106,10 @@ Host / PVE / Online Battle ### 3.2 禁止依赖 -`src/shared/engine/combat-v6/**` 的生产代码不得导入: +`packages/shared/src/engine/combat-v6/**` 的生产代码不得导入: -- `src/shared/engine/battle-v5/**` -- `src/shared/engine/creation-v2/**` +- `packages/shared/src/engine/battle-v5/**` +- `packages/shared/src/engine/creation-v2/**` - 旧宗门的 v5 战斗投影和编译产物 - v5 `AbilityConfig`、`AttributeModifierConfig`、Buff、Listener、GameplayTags @@ -1143,7 +1143,7 @@ V6 擂台仍使用的 ArenaRoomService、ArenaBattleStartOrchestrator、NATS 共 ### 14.1 共享引擎测试 -测试只放在 `src/shared`。至少覆盖: +测试只放在 `packages/shared/src`。至少覆盖: - core 指令锁定、速度顺序、状态、死亡/倒地和 RNG。 - rules-daoyou 物理、法术、固伤、治疗、封印和修炼差公式。 diff --git a/docs/combat-v6-mimi-nether-tiger.md b/docs/combat-v6-mimi-nether-tiger.md index 48928302..e0599ae1 100644 --- a/docs/combat-v6-mimi-nether-tiger.md +++ b/docs/combat-v6-mimi-nether-tiger.md @@ -49,7 +49,7 @@ - `species.json`:完整物种定义、出生规则与咪咪starter资格;两者独立ID。 - `wild.json`:青溪坡/不见天物种池、成年等级、描述与寻觅文案;`map.json`同步地图说明,保留青溪坡副本能力。 -- `public/assets/icons/beast-mimi.webp`、`beast-nether-tiger.webp`:第二版256×256透明头像;统一图标registry注册。详情、名册、野外、战斗及回放的物种图标复用既有GameIcon链路。 +- `apps/web/public/assets/icons/beast-mimi.webp`、`beast-nether-tiger.webp`:第二版256×256透明头像;统一图标registry注册。详情、名册、野外、战斗及回放的物种图标复用既有GameIcon链路。 - 初始领取、野外生成、捕获结算、洗炼、加点、喂养、传承与战斗投影沿用共享物种配置;无需增加服务端物种分支或数据库迁移。 - 设计基准记录新增行;通用SKILL正文不加入具体物种案例。 @@ -75,10 +75,10 @@ 命令结果: -- `bun run test src/shared/engine/combat-v6/beasts src/shared/engine/combat-v6/wild`:16文件、266项通过。 +- `bun run test packages/shared/src/engine/combat-v6/beasts packages/shared/src/engine/combat-v6/wild`:16文件、266项通过。 - `bun run lint`:通过。 - `bun run build`:客户端/服务端类型检查和构建通过;仍有既有BodyCultivationPanels动态/静态导入重叠和大包警告。 -- `bun run test`:1519项通过、1项失败。唯一失败为既有 `src/shared/lib/bodyCultivation/pack.test.ts` 的「标签、位阶、进度和投影保持迁移前基线」,当前shortDesc与旧fixture不同;本轮未修改炼体领域。 +- `bun run test`:1519项通过、1项失败。唯一失败为既有 `packages/shared/src/lib/bodyCultivation/pack.test.ts` 的「标签、位阶、进度和投影保持迁移前基线」,当前shortDesc与旧fixture不同;本轮未修改炼体领域。 - `git diff --check`:通过。 验收边界:已有本地测试角色均领取过初始伙伴,未重置领取资格或创建新角色,因此咪咪首次免费领取由共享名单/生成测试与前后端调用审查覆盖,未完成首次领取的浏览器提交。幽冥虎完整捕获未重复实测;洗炼、喂养、传承的完整付费页面操作未逐物种重复执行,沿用现有公共链路及领域测试。未做生产部署、长周期实战平衡或并发故障注入。 diff --git a/docs/combat-v6-mobile-numerical-governance-plan.md b/docs/combat-v6-mobile-numerical-governance-plan.md index cb345169..ac94fc49 100644 --- a/docs/combat-v6-mobile-numerical-governance-plan.md +++ b/docs/combat-v6-mobile-numerical-governance-plan.md @@ -35,7 +35,7 @@ G2 与 G3 同属属性层,均须先于技能平衡完成。各阶段可独立 ### 2.1 伤害结算 -实际规则为 `daoyouRulesetV6 → V5 → V4 → V3`,法术使用包含 `coeff` 的 `magicStrikeV3`。不能把文件中保留的旧 `magicStrike` 或历史导出 `baseDamage` 当作当前全部规则。见 [规则入口](../src/shared/engine/combat-v6/rules-daoyou/index.ts)、[公式](../src/shared/engine/combat-v6/rules-daoyou/formulas.ts)。 +实际规则为 `daoyouRulesetV6 → V5 → V4 → V3`,法术使用包含 `coeff` 的 `magicStrikeV3`。不能把文件中保留的旧 `magicStrike` 或历史导出 `baseDamage` 当作当前全部规则。见 [规则入口](../packages/shared/src/engine/combat-v6/rules-daoyou/index.ts)、[公式](../packages/shared/src/engine/combat-v6/rules-daoyou/formulas.ts)。 | 领域 | 当前实际行为 | 治理要求 | | --- | --- | --- | @@ -54,7 +54,7 @@ G2 与 G3 同属属性层,均须先于技能平衡完成。各阶段可独立 ### 2.2 人物:六维与点数预算都不是手游基线 -当前 [人物裸身面板](../src/shared/engine/combat-v6/projection/character-panel-v1.ts): +当前 [人物裸身面板](../packages/shared/src/engine/combat-v6/projection/character-panel-v1.ts): ```text 物攻 = floor(40 + 力道×3.5) @@ -72,33 +72,33 @@ G2 与 G3 同属属性层,均须先于技能平衡完成。各阶段可独立 存储键为 `vitality/strength/spirit/endurance/speed/willpower`,人物仍有六维;召唤灵已经是五维,但使用另一套公式。当前增加 10 点力量产生 35 物攻,增加 10 点气血属性产生 200 最大气血。这与已找到的手游历史资料所述 10 物攻、80 气血明显不同,但资料尚不能代替目标版本的正式验收。[^1][^4] -更根本的是**每级可用点数也不同**:[境界成长](../src/shared/config/realmProgression.ts) 每维初始 10,小阶段每维自然 +2、自由点 +10,跨大境界每维自然 +4、自由点 +20;[人物战斗等级](../src/shared/engine/combat-v6/projection/character-level.ts) 却按 `(境界阶段序号+1)×5` 映射。仅把 3.5 改成 1 会降低面板,却没有复刻手游同等级的属性预算。 +更根本的是**每级可用点数也不同**:[境界成长](../packages/shared/src/config/realmProgression.ts) 每维初始 10,小阶段每维自然 +2、自由点 +10,跨大境界每维自然 +4、自由点 +20;[人物战斗等级](../packages/shared/src/engine/combat-v6/projection/character-level.ts) 却按 `(境界阶段序号+1)×5` 映射。仅把 3.5 改成 1 会降低面板,却没有复刻手游同等级的属性预算。 必须一起核对:出生属性、自然成长、自由点、额外永久点、重置规则、境界奖励、等级映射。禁止用装备或技能去补偿点数预算的错误。 ### 2.3 召唤灵:现有资质和成长的作用方式需重建 -[宠物投影](../src/shared/engine/combat-v6/beasts/projection.ts) 对五维先生成 `10 + 等级 + 已分配点`,再使用统一函数: +[宠物投影](../packages/shared/src/engine/combat-v6/beasts/projection.ts) 对五维先生成 `10 + 等级 + 已分配点`,再使用统一函数: ```text 贡献 = floor(属性值 × 成长 × 对应资质 / 1000 × 系数) ``` -当前气血系数 8、法力 3、物攻/法攻 2、物防 1.5、速度 1.5;法防把耐力与半数魔力相加后乘防御资质和成长。见 [成长配置](../src/shared/engine/combat-v6/beasts/data/progression.json)。 +当前气血系数 8、法力 3、物攻/法攻 2、物防 1.5、速度 1.5;法防把耐力与半数魔力相加后乘防御资质和成长。见 [成长配置](../packages/shared/src/engine/combat-v6/beasts/data/progression.json)。 这意味着现有系统中每增加一点力量的物攻收益同时依赖攻资和成长。手游历史资料候选式则把“等级×资质项”和“加点×成长项”分开,结构上就不同。[^2] 不能只替换系数 2。 其他影响项: - 自由点总数校验为 `level×5`,五维自然底数均为 10;没有独立出生五维分布事实。 -- 五种资质当前共用 900~1100 的生成范围,偏好资质另加 100;成长 0.95~1.05。物种只选择偏好和职业,并非每物种、每资质独立范围。[物种生成配置](../src/shared/engine/combat-v6/beasts/data/species.json) -- `BeastSchema` 对成长、资质的通用数值边界较宽;需补充与物种、来源、目标版本一致的有效范围。[个体模型](../src/shared/engine/combat-v6/beasts/schema.ts) +- 五种资质当前共用 900~1100 的生成范围,偏好资质另加 100;成长 0.95~1.05。物种只选择偏好和职业,并非每物种、每资质独立范围。[物种生成配置](../packages/shared/src/engine/combat-v6/beasts/data/species.json) +- `BeastSchema` 对成长、资质的通用数值边界较宽;需补充与物种、来源、目标版本一致的有效范围。[个体模型](../packages/shared/src/engine/combat-v6/beasts/schema.ts) - 投影从 `DEFAULT_ATTRS` 开始,没有从主人修炼映射宠物修炼;手游宠修/人物修炼关系须独立核验,不能静默套用主人修炼。 - 可携带、可出战等级受角色等级约束。新的等级坐标必须同步检查捕获、升级、首发、替换和野外投影。 ### 2.4 装备:部位语义、区间和生成分布均需对齐 -当前 [器胚配置](../src/shared/engine/combat-v6/equipment/data/equipment-base.json) 为“等级×系数”并均匀抽整数;六部位都可抽 0~2 条六维附加值,概率 50%/40%/10%,单条范围为等级×0.08~0.14,偏好属性权重 2。它们均是现有自定义规则,不是已验证的手游概率。 +当前 [器胚配置](../packages/shared/src/engine/combat-v6/equipment/data/equipment-base.json) 为“等级×系数”并均匀抽整数;六部位都可抽 0~2 条六维附加值,概率 50%/40%/10%,单条范围为等级×0.08~0.14,偏好属性权重 2。它们均是现有自定义规则,不是已验证的手游概率。 | 60 级部位 | 当前器胚范围 | 可访问的手游历史表例(普通打造) | 状态 | | --- | --- | --- | --- | @@ -112,13 +112,13 @@ G2 与 G3 同属属性层,均须先于技能平衡完成。各阶段可独立 还有三个联动点: -1. [现有强化铸造](../src/shared/engine/combat-v6/equipment/forging.ts) 是概率二次抽样取较高值,仍在原区间内;这不等于手游“普通打造/强化打造不同区间”。不能只改名称。 -2. 阵纹已于 2026-09-23 按确认方案硬切双孔 `formationInscriptions`,炼气至化神每孔上限为 3/5/7/9/11。现行类型、数值与部位见 [双孔阵纹规则](../src/shared/engine/combat-v6/equipment/data/README.md#双孔阵纹);产出和操作经济尚未接入。 -3. [装备校验](../src/shared/engine/combat-v6/equipment/compiler.ts) 使用当前模板计算合法范围;直接改模板会使已存旧装备在读取时变成非法。生成器版本本身不等于完整的旧区间解释能力。 +1. [现有强化铸造](../packages/shared/src/engine/combat-v6/equipment/forging.ts) 是概率二次抽样取较高值,仍在原区间内;这不等于手游“普通打造/强化打造不同区间”。不能只改名称。 +2. 阵纹已于 2026-09-23 按确认方案硬切双孔 `formationInscriptions`,炼气至化神每孔上限为 3/5/7/9/11。现行类型、数值与部位见 [双孔阵纹规则](../packages/shared/src/engine/combat-v6/equipment/data/README.md#双孔阵纹);产出和操作经济尚未接入。 +3. [装备校验](../packages/shared/src/engine/combat-v6/equipment/compiler.ts) 使用当前模板计算合法范围;直接改模板会使已存旧装备在读取时变成非法。生成器版本本身不等于完整的旧区间解释能力。 ### 2.5 隐藏在其他层的数值来源 -当前完整入口 [projectCharacterToCombatV6](../src/shared/engine/combat-v6/projection/project-character.ts) 经个人功法装配、道装、炼体、可选宗门共同形成面板。个人功法既可加基础属性,也可追加面板;门派心法和经脉也有面板与事件钩子。只核对裸身与装备会漏算它们。 +当前完整入口 [projectCharacterToCombatV6](../packages/shared/src/engine/combat-v6/projection/project-character.ts) 经个人功法装配、道装、炼体、可选宗门共同形成面板。个人功法既可加基础属性,也可追加面板;门派心法和经脉也有面板与事件钩子。只核对裸身与装备会漏算它们。 | 必审来源 | 当前入口 | 必须交付的核对结果 | | --- | --- | --- | @@ -338,7 +338,7 @@ G0/G2 必须输出“道友境界阶段 → 基线战斗等级 → 人物点数 2. **外部参考样本:** 官方明确数值或可复核的游戏实测;包含完整输入、输出、版本和来源,用于决定期望值。 3. **道友特色样本:** 基线加一个可识别的特色因素,记录增量与代价,用于平衡与防止漂移。 -所有纯确定性样本与测试留在 `src/shared`,沿用 Vitest;不新增独立 smoke/e2e/seed/benchmark 脚本,不把数据库或第三方服务写进单测。新测试路径只按已实现域建立,不预先搭建空框架。 +所有纯确定性样本与测试留在 `packages/shared/src`,沿用 Vitest;不新增独立 smoke/e2e/seed/benchmark 脚本,不把数据库或第三方服务写进单测。新测试路径只按已实现域建立,不预先搭建空框架。 ### 6.2 最小验证矩阵 @@ -445,6 +445,6 @@ CI 后续增加与本治理直接相关的 lint、构建和共享测试质量门 [^6]: [“梦幻西游手游 物理伤害 法术伤害 公式 修炼 实测”检索结果](https://www.baidu.com/s?wd=梦幻西游手游%20物理伤害%20法术伤害%20公式%20修炼%20实测)。检索发现网易新闻《梦幻西游手游物理伤害与法术伤害的计算公示》及官网《手游弱点击破大改,重“伤”破敌大技》(搜索标注 2017-04-19)存在不同普攻系数线索。前者原文 URL 为 https://www.163.com/game/article/B444PTC800314U1Q_mobile.html,访问被阻止;后者未读取原文。只登记冲突,不引用搜索 AI 汇总作为证据。 -- **[C1]** 当前工作区 [combat-v6 规则](../src/shared/engine/combat-v6/rules-daoyou/)、[人物投影](../src/shared/engine/combat-v6/projection/)、[宠物](../src/shared/engine/combat-v6/beasts/)、[装备](../src/shared/engine/combat-v6/equipment/)。D 级:项目现状的代码依据。 +- **[C1]** 当前工作区 [combat-v6 规则](../packages/shared/src/engine/combat-v6/rules-daoyou/)、[人物投影](../packages/shared/src/engine/combat-v6/projection/)、[宠物](../packages/shared/src/engine/combat-v6/beasts/)、[装备](../packages/shared/src/engine/combat-v6/equipment/)。D 级:项目现状的代码依据。 - **[C2]** [现有战斗路线](combat-v6-mhxy-redesign-roadmap.md)、[人物面板设计](combat-v6-character-panel-design.md)、[装备设计](combat-v6-equipment-system-design.md)、[召唤兽设计](combat-v6-summoned-beast-system-design.md)。D 级历史设计,冲突时以当前实现和本轮目标说明区分,不当作外部真值。 - **[C3]** [领域归属](combat-domain-ownership.md)、[测试规范](testing.md)、[AGENTS](../AGENTS.md)。用于实施边界和验证方式;五维迁移实施时同步更新其相关约束。 diff --git a/docs/combat-v6-phase-10a-dungeon-plan.md b/docs/combat-v6-phase-10a-dungeon-plan.md index 9595e256..4ab842e8 100644 --- a/docs/combat-v6-phase-10a-dungeon-plan.md +++ b/docs/combat-v6-phase-10a-dungeon-plan.md @@ -69,7 +69,7 @@ - 本地真实认证跑通代表秘境“探索收益 → 战斗 → 继续探索 → 第二场战斗 → 通关 → 发放 → 战绩回放”。 - 检查失败、逃跑、胜利后离开、播放中刷新、重复点击、恢复入口、满包、结算中断后的恢复及构筑修改拒绝。 - 检查其他秘境入口配置与硬切结果,桌面及 360px 窄屏展示;保持 HP/MP 对齐及逐行动同步。 -- 仅在 `src/shared` 为可复用确定性逻辑添加测试,覆盖配置校验、奖励累计规则、灵兽经验归属和重复结果处理等实际新增规则。不新增前后端/数据库单元测试或一次性测试脚本。 +- 仅在 `packages/shared/src` 为可复用确定性逻辑添加测试,覆盖配置校验、奖励累计规则、灵兽经验归属和重复结果处理等实际新增规则。不新增前后端/数据库单元测试或一次性测试脚本。 - 执行相关共享测试、`bun run lint`、`bun run build`、触及文件 Prettier 与 `git diff --check`;跨域变更完成后执行完整共享测试。 - 自动化仅使用纯本地环境和 `docs/testing.md` 测试账号,不写预发布或生产。 - 实施记录必须分别列出已实现、已验证及未验证项,不将待验收条目视为通过。 diff --git a/docs/combat-v6-phase-10ab-auto-utility.md b/docs/combat-v6-phase-10ab-auto-utility.md index 20e01e5d..6ff531b0 100644 --- a/docs/combat-v6-phase-10ab-auto-utility.md +++ b/docs/combat-v6-phase-10ab-auto-utility.md @@ -12,7 +12,7 @@ ## 2. 实现职责 -共享应用层 `src/shared/combat-v6`: +共享应用层 `packages/shared/src/combat-v6`: | 模块 | 职责 | | --- | --- | @@ -52,9 +52,9 @@ 检查命令: -- `bun run test src/shared/combat-v6 src/shared/engine/combat-v6`:中途375项通过;追加资源蓄积边界后由全量测试覆盖。 +- `bun run test packages/shared/src/combat-v6 packages/shared/src/engine/combat-v6`:中途375项通过;追加资源蓄积边界后由全量测试覆盖。 - `bun run test`:最终147个文件、997项通过。 -- `bunx tsc -b tsconfig.app.json tsconfig.node.json --pretty false`:通过;最终由build再次覆盖两端类型检查。 +- `bunx tsc -b apps/web/tsconfig.json tsconfig.node.json --pretty false`:通过;最终由build再次覆盖两端类型检查。 - `bun run lint`、`bun run build`、`git diff --check`:最终均通过。构建既有大chunk提示不在本阶段调整范围。 - 对新增模块及实际改动块检查格式;代码全部通过内置apply_patch修改,未使用Python改写或格式工具写回。 diff --git a/docs/combat-v6-phase-10b-tower-plan.md b/docs/combat-v6-phase-10b-tower-plan.md index 5284760c..65f66964 100644 --- a/docs/combat-v6-phase-10b-tower-plan.md +++ b/docs/combat-v6-phase-10b-tower-plan.md @@ -10,10 +10,10 @@ ## 2. 改造前代码事实 -- `src/server/lib/tower/service.ts` 使用 `simulateBattleV5`、旧人物投影、旧秘境 `RewardFactory` 和邮件附件奖励;挑战状态及领取标记目前存于 Redis。 -- `src/server/lib/tower/battleInit.ts` 负责旧属性祝福、恢复和伤势处理;本阶段替换其幻境职责,不将旧伤势引入 v6。 -- `src/react-app/routes/game/tower/route.tsx` 直接使用 v5 入场投影;改为读取服务端 v6 展示契约。 -- `src/shared/lib/tower` 已有周期、楼层、榜单与 11 种祝福定义,保留适用的纯逻辑。 +- `apps/api/src/lib/tower/service.ts` 使用 `simulateBattleV5`、旧人物投影、旧秘境 `RewardFactory` 和邮件附件奖励;挑战状态及领取标记目前存于 Redis。 +- `apps/api/src/lib/tower/battleInit.ts` 负责旧属性祝福、恢复和伤势处理;本阶段替换其幻境职责,不将旧伤势引入 v6。 +- `apps/web/src/routes/game/tower/route.tsx` 直接使用 v5 入场投影;改为读取服务端 v6 展示契约。 +- `packages/shared/src/lib/tower` 已有周期、楼层、榜单与 11 种祝福定义,保留适用的纯逻辑。 - 现有 `tower_enemy_floors` 保存旧敌人结构,并有后台和生成任务调用链;硬切时需要同步处理,不能只替换玩家页面。 ## 3. 玩法约定 @@ -103,7 +103,7 @@ React 遵循指定最佳实践及仓库格式,避免 Effect 同步派生状态 - 周周期:按项目允许的本地测试方式核对边界;如缺少真实跨周或故障条件,明确记录未验收,不伪造权威状态冒充通过。 - 页面:桌面和 360px、血条对齐、逐行动同步、祝福详情、奖励去向及战绩列表不解包。 - 静态检查:触及幻境的服务端、共享展示契约和页面不依赖 battle-v5;后台/任务无遗留旧敌人写入调用。全仓仍有旧依赖不等于本阶段失败,但必须列出后续范围。 -- 按仓库规则运行 `bun run lint`、`bun run build`、相关 `src/shared` 测试、触及文件 Prettier 和 `git diff --check`;共享影响扩大时运行完整共享测试。 +- 按仓库规则运行 `bun run lint`、`bun run build`、相关 `packages/shared/src` 测试、触及文件 Prettier 和 `git diff --check`;共享影响扩大时运行完整共享测试。 - 测试只用纯本地环境与既有账号,遵循 `docs/testing.md`;不新增前端/服务端/数据库单元测试或一次性故障脚本。 Phase 10A 尚未通过的服务进程中断、事务失败恢复等,在复用相关机制时补验并分别记录;其他既存未验证项保留原状态。本阶段完成不代表可删除整个 v5,也不代表完成生产维护验收。 @@ -112,8 +112,8 @@ Phase 10A 尚未通过的服务进程中断、事务失败恢复等,在复用 ### 已实现 -- `src/shared/engine/combat-v6/tower/host.ts` 提供独立 Host、可恢复快照、冻结构筑祝福投影、局内资源、死亡灵兽排除及集中敌人配置。普通层单敌、精英双敌、首领单敌双气血,NPC 首轮普攻;尚未做真实高等级难度调优。 -- `src/server/lib/tower/combatV6.ts` 提供挑战、祝福、结束、指令、回合结算和恢复。先保存终局快照,再事务写入奖励与统一回放,最后更新 Redis 领取标记及进度。事务失败后可从终局完成入口重试;跨存储故障窗口接受重复发奖。 +- `packages/shared/src/engine/combat-v6/tower/host.ts` 提供独立 Host、可恢复快照、冻结构筑祝福投影、局内资源、死亡灵兽排除及集中敌人配置。普通层单敌、精英双敌、首领单敌双气血,NPC 首轮普攻;尚未做真实高等级难度调优。 +- `apps/api/src/lib/tower/combatV6.ts` 提供挑战、祝福、结束、指令、回合结算和恢复。先保存终局快照,再事务写入奖励与统一回放,最后更新 Redis 领取标记及进度。事务失败后可从终局完成入口重试;跨存储故障窗口接受重复发奖。 - Redis 使用 `tower:v6:run:<角色>`、`tower:v6:week:<角色>:<周期>`、`tower:v6:leaderboard:<周期>:<境界>`,与旧挑战和成绩隔离;过期时间为周期结束后 24 小时。领取标记在正常重开时保留,不新增任何数据表或迁移。 - 构筑、库存、灵兽、练功房、野外、擂台及命令执行器接入幻境占用;现实自然恢复不暂停,也不在离场时回写旧资源。 - `/game/tower` 展示挑战、祝福和周榜;`/game/tower/battle` 使用已有沉浸壳与 v6 公共战斗组件。终局播放结束后返回幻境,刷新从 HTTP 会话恢复。 @@ -132,7 +132,7 @@ Phase 10A 尚未通过的服务进程中断、事务失败恢复等,在复用 | 人物修为、灵兽经验 | 首轮不发放 | | 发放位置 | 物品入统一储物袋,满包入储藏室;灵石与声望直接更新角色资源 | -掉落池在 `src/shared/rewards/tower.ts`,敌人参数在 `TOWER_ENEMY_CONFIG`。目前复用既有凡品材料验证统一物品链路,没有新增高境界专属材料或扩张经济内容;这些首轮数值尚未经过真实金丹以上角色的收益与难度验收。 +掉落池在 `packages/shared/src/rewards/tower.ts`,敌人参数在 `TOWER_ENEMY_CONFIG`。目前复用既有凡品材料验证统一物品链路,没有新增高境界专属材料或扩张经济内容;这些首轮数值尚未经过真实金丹以上角色的收益与难度验收。 ### 首轮已验证(补验前) @@ -156,7 +156,7 @@ Phase 10A 尚未通过的服务进程中断、事务失败恢复等,在复用 - 此故障发现原错误响应泄露 SQL 和回放数据,已改为仅已知业务错误返回具体信息,意外错误返回安全的 500 提示;再次制造锁超时复验通过。 - dev 接口非法境界、未知字段返回 400,活动战斗及不存在角色返回 409。修复已结束挑战保留时境界变化未重新判断进入资格的问题。 - dev 资源调整实测发现底层 Condition 写入方法不主动裁剪,已在 dev 服务按新构筑上限显式裁剪;输入 HP/MP 各 10000000 后数据库核对为 1552/900,恢复到原资源值(资源更新时间随调整刷新)。故障恢复战绩从列表进入详情并完整播放至胜利通过;列表响应只含元数据。 -- 补验修改后重新执行 `bun run lint`、`bun run build`、`bun run test src/shared/engine/combat-v6/tower src/shared/lib/tower`(22 项)、触及文件 Prettier 及 `git diff --check`,全部通过。全量 2020 项共享测试结果来自首轮,本次未重复全量运行。 +- 补验修改后重新执行 `bun run lint`、`bun run build`、`bun run test packages/shared/src/engine/combat-v6/tower packages/shared/src/lib/tower`(22 项)、触及文件 Prettier 及 `git diff --check`,全部通过。全量 2020 项共享测试结果来自首轮,本次未重复全量运行。 - 测试后恢复炼气后期、六项属性 50、原首发阵容,放生临时备用灵兽,精确移除 40 件容量填充物。保留真实奖励(最终 3073 灵石、55 声望)与战绩;原灵兽经验 50、寿命 950 不变。数据库测试锁均释放,API 保持运行。 ### 待验收与发布边界 diff --git a/docs/combat-v6-phase-10e-auto-sect-tasks-plan.md b/docs/combat-v6-phase-10e-auto-sect-tasks-plan.md index 8c5809fb..6249652b 100644 --- a/docs/combat-v6-phase-10e-auto-sect-tasks-plan.md +++ b/docs/combat-v6-phase-10e-auto-sect-tasks-plan.md @@ -102,7 +102,7 @@ Redis 保存会话及终局事实。使用稳定 battleId 幂等,在同一 Pos ### 9.2 简化改造验证 -- 本轮 `bun run test`:224 个文件、2048 项通过;`bun run lint`、`bunx tsc -b tsconfig.app.json tsconfig.node.json --pretty false`、`bun run build` 和 `git diff --check` 通过。 +- 本轮 `bun run test`:224 个文件、2048 项通过;`bun run lint`、`bunx tsc -b apps/web/tsconfig.json tsconfig.node.json --pretty false`、`bun run build` 和 `git diff --check` 通过。 - 本地练功房实际网络记录确认按回合提交 POST AUTO(type、round、expectedRevision),每次只推进一回合;人物与在场灵兽正常出招,没有开关写入和自动状态轮询。 - 页面实测:连续自动、动画期间取消后不再提交、开启后刷新恢复手动、开启后离页停止并在返回时保持原回合;取消前已发请求允许完成。 - 暂停本地 API 后,AUTO 失败使开关关闭;恢复服务并重新载入仍保持手动。 @@ -177,7 +177,7 @@ Redis 保存会话及终局事实。使用稳定 battleId 幂等,在同一 Pos 测试准备仅将本地道友1的力量、速度50临时改为1000,两项已恢复50;没有补满HP/MP或修改弟子职阶。保留实际任务奖励、战败消耗和回放。没有遗留本轮活动战斗。 -维护工具 `scripts/migrate-sect-task-battles-v6.ts` 默认只读;本地 dry-run 为0条。`bun --env-file=env/local.env scripts/migrate-sect-task-battles-v6.ts --dry-run` 可核对数量及逐条记录;正式维护需在对应环境停机、核对后以 `--apply ...` 指定记录。只撤销 active 且携带旧 combatant 的战斗任务,保留 completed 待领奖、非战斗和新版活动任务。领取名额由非 abandoned 记录占用,没有独立额度计数或领取货币扣款,因此撤销即释放实际占用;重复执行不额外发资源。本轮未执行 apply,不能标记迁移幂等已运行验收。 +维护工具 `apps/api/scripts/migrate-sect-task-battles-v6.ts` 默认只读;本地 dry-run 为0条。`node --env-file=env/local.env --import tsx apps/api/scripts/migrate-sect-task-battles-v6.ts --dry-run` 可核对数量及逐条记录;正式维护需在对应环境停机、核对后以 `--apply ...` 指定记录。只撤销 active 且携带旧 combatant 的战斗任务,保留 completed 待领奖、非战斗和新版活动任务。领取名额由非 abandoned 记录占用,没有独立额度计数或领取货币扣款,因此撤销即释放实际占用;重复执行不额外发资源。本轮未执行 apply,不能标记迁移幂等已运行验收。 验证:`bun run test` 224文件/2051测试通过;相关宗门 Host 测试覆盖资源隔离、目标与首发宠冻结、恢复确定性、NPC缺法力回退、版本拒绝、原生快照和JSON字段顺序。`bun run lint`、`bun run build`、`git diff --check` 通过;构建仍有既有大chunk提示。没有添加数据库或网络 mock 测试。 diff --git a/docs/combat-v6-phase-10l-alchemy-inventory-plan.md b/docs/combat-v6-phase-10l-alchemy-inventory-plan.md index 251abe60..d38d0e8c 100644 --- a/docs/combat-v6-phase-10l-alchemy-inventory-plan.md +++ b/docs/combat-v6-phase-10l-alchemy-inventory-plan.md @@ -52,7 +52,7 @@ - 复用新版炼器的设施入口、桌面炉阵与物品栏双栏布局、移动端材料抽屉、开炉确认及结果交互。 - 保留即兴炼丹、丹方炼丹、剂量调整、推演及结果信息,材料格数量遵循原炼丹上限。 -- 新炼丹炉采用独立透明背景素材。2026-09-20与炼器炉统一重制为写意国画水墨,现用 `public/assets/icons/xuanfire-furnace-ink.png`,通过 `GameIcon` 接入;[素材与提示词](art/craft-furnaces.md)。 +- 新炼丹炉采用独立透明背景素材。2026-09-20与炼器炉统一重制为写意国画水墨,现用 `apps/web/public/assets/icons/xuanfire-furnace-ink.png`,通过 `GameIcon` 接入;[素材与提示词](art/craft-furnaces.md)。 - 数字沿用 Tailwind `font-mono`,不添加全局数字字体变量。 ### 旧生产清理 @@ -104,7 +104,7 @@ ## 静态验证 -已执行并通过:`bun run lint`、`bunx tsc -p tsconfig.app.json --noEmit --pretty false`、`bunx tsc -p tsconfig.node.json --noEmit --pretty false`、`bun run build`、`git diff --check`。共享全量测试 178 个文件 / 1289 项通过(含完整材料事实、拆堆、草药与炼器边界、历史 rehydrate);后续仅缓存通知、文案及格式调整,未重复全量测试。构建仍提示既有 Phaser 大分块警告,没有数据库迁移。 +已执行并通过:`bun run lint`、`bunx tsc -p apps/web/tsconfig.json --noEmit --pretty false`、`bunx tsc -p tsconfig.node.json --noEmit --pretty false`、`bun run build`、`git diff --check`。共享全量测试 178 个文件 / 1289 项通过(含完整材料事实、拆堆、草药与炼器边界、历史 rehydrate);后续仅缓存通知、文案及格式调整,未重复全量测试。构建仍提示既有 Phaser 大分块警告,没有数据库迁移。 ## 2026-09-09 炼丹界面精修 @@ -116,7 +116,7 @@ - 预览保留炉阵和简短药效/成丹范围摘要;逐味判断可在材料格详情查看,详细信息折叠。修改材料、份量或模式会使旧预览失效。 - 炼制中仅保留炉图反馈与状态行;成丹使用公共 ItemSlot 及药效详情,保留丹方发现保存、熟练度和继续炼制操作。 - 运行验收:桌面 1280×900、移动端 360×800;完整物品栏、逐份追加、批量份量输入、模式切换保留材料、丹方选择与预览、改份量使预览失效、确认炼制、成丹详情入口、再炼一炉和返回。一次真实丹方炼制投入两味各两份,生成一枚丹药、熟练度 +2。 -- 验证通过:bun run lint、bunx tsc -p tsconfig.app.json --noEmit --pretty false、bun run build、git diff --check。此次仅前端交互与视图调整,没有新增单元测试或重复运行共享引擎测试。构建仍有既有大分块提示。 +- 验证通过:bun run lint、bunx tsc -p apps/web/tsconfig.json --noEmit --pretty false、bun run build、git diff --check。此次仅前端交互与视图调整,没有新增单元测试或重复运行共享引擎测试。构建仍有既有大分块提示。 ## 2026-09-09 第二轮视觉修正 diff --git a/docs/combat-v6-phase-10m-legacy-engine-retirement.md b/docs/combat-v6-phase-10m-legacy-engine-retirement.md index 4098db17..28109d6f 100644 --- a/docs/combat-v6-phase-10m-legacy-engine-retirement.md +++ b/docs/combat-v6-phase-10m-legacy-engine-retirement.md @@ -13,7 +13,7 @@ ## 实施结果 - 删除 battle-v5、creation-v2 全目录及其专属测试、旧模拟调用、战后消费者、condition 战斗桥接和旧法宝境界缩放。 -- 历史读取改用 src/shared/legacy/products.ts 的存档工具。未知词缀和额外存档字段原样保留,附件入库移除 battleProjection,不再 rehydrate、编译能力或重新评分。 +- 历史读取改用 packages/shared/src/legacy/products.ts 的存档工具。未知词缀和额外存档字段原样保留,附件入库移除 battleProjection,不再 rehydrate、编译能力或重新评分。 - 旧装配读取保留既有记录用于历史展示,移除运行投影。旧表 JSON 列使用不透明归档类型;schema 与迁移目录没有修改。 - 神通、功法页面共用历史列表,保留分页和详情,移除启用、遗忘操作;宝库法宝列表仅保留详情。 - 移除旧战斗投影展示组件与消费者,历史详情直接展示已有属性和词缀说明。 diff --git a/docs/combat-v6-phase-10n-npc-recycle.md b/docs/combat-v6-phase-10n-npc-recycle.md index 1450356c..9b34db12 100644 --- a/docs/combat-v6-phase-10n-npc-recycle.md +++ b/docs/combat-v6-phase-10n-npc-recycle.md @@ -25,7 +25,7 @@ ## 静态验证 - bun run lint:通过。 -- bunx tsc -p tsconfig.app.json --noEmit、bunx tsc -p tsconfig.node.json --noEmit:通过;最终 build 再次覆盖两端类型检查。 +- bunx tsc -p apps/web/tsconfig.json --noEmit、bunx tsc -p tsconfig.node.json --noEmit:通过;最终 build 再次覆盖两端类型检查。 - bun run test:126 文件 / 848 项通过,含四项回收输入与可售范围纯规则测试,以及既有丹药价格与经济约束测试。 - bun run build:客户端、服务端通过,保留既有大分块提示。 - git diff --check:通过。 diff --git a/docs/combat-v6-phase-10o-market-inventory.md b/docs/combat-v6-phase-10o-market-inventory.md index 262101e4..ae60479e 100644 --- a/docs/combat-v6-phase-10o-market-inventory.md +++ b/docs/combat-v6-phase-10o-market-inventory.md @@ -15,7 +15,7 @@ ## 实施与事务边界 -- 新版购买契约位于 `src/shared/contracts/market.ts`:单件与批量统一为1–40个唯一 listingId,每项数量固定为1;提交 requestId 和确认金额,拒绝未知字段、客户端价格/商品事实。服务端重新计算价格,确认金额仅用于防止陈旧价格成交。 +- 新版购买契约位于 `packages/shared/src/contracts/market.ts`:单件与批量统一为1–40个唯一 listingId,每项数量固定为1;提交 requestId 和确认金额,拒绝未知字段、客户端价格/商品事实。服务端重新计算价格,确认金额仅用于防止陈旧价格成交。 - `MarketInventoryDelivery` 复用 `grantInventory` 和既有 `material.v1` 事实,普通坊市与黑市共用;不会先写旧表再转换。 - 普通坊市扣款、整批入库和逐商品成交凭据同一 SQL 事务完成。复用已有玩家命令凭据表,不新增表、迁移、调度器或交易框架。 - 请求凭据处理响应丢失/重复提交;逐商品凭据阻止更换请求编号或改变批次组合再次购买。角色锁加账号购买锁保持跨角色的个人额度语义;查询按账号读取凭据。 diff --git a/docs/combat-v6-phase-10p-spirit-field-inventory.md b/docs/combat-v6-phase-10p-spirit-field-inventory.md index 12671432..5c444207 100644 --- a/docs/combat-v6-phase-10p-spirit-field-inventory.md +++ b/docs/combat-v6-phase-10p-spirit-field-inventory.md @@ -62,7 +62,7 @@ | 幂等与并发 | 重复及并发播种/培育/收获不重复扣除或发放,不出现已扣物未推进或重置后未发货 | | UI | PC及窄屏公共物品栏保持完整视图;切换田块不会沿用错误投入;刷新恢复正确状态 | -纯规则测试仅放 src/shared;服务、数据库与页面采用代码审查和本地浏览器运行验收,不新增一次性验收脚本。实施后运行 lint、build 与受影响共享测试,按影响范围决定是否全量测试。运行准备复用本地测试账号及现有 dev 接口,若无法覆盖成熟作物等场景,再补最小必要的本地验收能力;不改变正式成长时长。 +纯规则测试仅放 packages/shared/src;服务、数据库与页面采用代码审查和本地浏览器运行验收,不新增一次性验收脚本。实施后运行 lint、build 与受影响共享测试,按影响范围决定是否全量测试。运行准备复用本地测试账号及现有 dev 接口,若无法覆盖成熟作物等场景,再补最小必要的本地验收能力;不改变正式成长时长。 ## 实施结果 diff --git a/docs/combat-v6-phase-10s-sect-inventory.md b/docs/combat-v6-phase-10s-sect-inventory.md index 27559208..572c1a47 100644 --- a/docs/combat-v6-phase-10s-sect-inventory.md +++ b/docs/combat-v6-phase-10s-sect-inventory.md @@ -41,8 +41,8 @@ 在选定环境、停机并备份后执行。环境文件需显式指定,以下仅以本地为例: ```bash -bun --env-file=env/local.env scripts/retire-sect-artifact-deliveries.ts --dry-run -bun --env-file=env/local.env scripts/retire-sect-artifact-deliveries.ts --apply <已核对的记录UUID> ... +node --env-file=env/local.env --import tsx apps/api/scripts/retire-sect-artifact-deliveries.ts --dry-run +node --env-file=env/local.env --import tsx apps/api/scripts/retire-sect-artifact-deliveries.ts --apply <已核对的记录UUID> ... ``` 应用前保留盘点输出;工具只处理指定 ID 且仍带旧 artifact 要求的记录。active 改为 abandoned,非 abandoned 记录计数因而释放;其他状态不变。移除全部旧法宝要求并更新执行器名,奖励快照和交付历史不变。重复执行不会再命中已处理记录。全部处理后再次无参数盘点,应为0,再启动新版。旧拍卖货单仍由用户用旧版本下架返还,不在此工具内处理。 diff --git a/docs/combat-v6-phase-10t-chat-inventory.md b/docs/combat-v6-phase-10t-chat-inventory.md index 29d9b7f3..da6ad246 100644 --- a/docs/combat-v6-phase-10t-chat-inventory.md +++ b/docs/combat-v6-phase-10t-chat-inventory.md @@ -37,7 +37,7 @@ | 异主物品 | 使用只读查询得到的其他角色真实物品ID,宗门接口404 | | 纯共享测试 | 旧协议/伪造快照拒绝、revision契约、快照隔离及版本识别通过 | -静态验证:bun run lint、bunx tsc -b tsconfig.app.json tsconfig.node.json、bun run build(两端)、bun run test(130文件/874项)通过;最终契约收简后再次通过lint、类型检查和5项相关测试。构建有既有大chunk提示。git diff --check通过。 +静态验证:bun run lint、bunx tsc -b apps/web/tsconfig.json tsconfig.node.json、bun run build(两端)、bun run test(130文件/874项)通过;最终契约收简后再次通过lint、类型检查和5项相关测试。构建有既有大chunk提示。git diff --check通过。 界面通过真实本地浏览器检查,交互包含DOM点击和360px视口模拟,不等同于真机触控验收。道装发送期间捕获的页面错误/未处理拒绝列表为空,发送请求一次200;未持续采集整个验收过程的完整控制台日志。 diff --git a/docs/combat-v6-phase-10u-character-build.md b/docs/combat-v6-phase-10u-character-build.md index 3fc9825b..64f3a825 100644 --- a/docs/combat-v6-phase-10u-character-build.md +++ b/docs/combat-v6-phase-10u-character-build.md @@ -36,7 +36,7 @@ | 视口 | 1280px桌面与360px页面检查;窄屏文档宽度360,无横向溢出 | | 共享逻辑 | 公开构筑面板与现有角色投影一致、无私有构筑字段、装备/功法深复制;旧功法品质不再影响闭关收益 | -通过:bun run lint、bunx tsc -b tsconfig.app.json tsconfig.node.json、bun run test(131文件/877项)、bun run build(两端)、git diff --check。构建仅既有大chunk提示。 +通过:bun run lint、bunx tsc -b apps/web/tsconfig.json tsconfig.node.json、bun run test(131文件/877项)、bun run build(两端)、git diff --check。构建仅既有大chunk提示。 检索确认 src 中不再存在 player.loadout、usePlayerLoadout、CultivatorLoadoutReader、loadCultivatorDungeonPromptFacts、getCultivationTechniqueMultiplier 或旧功法倍率表。 diff --git a/docs/combat-v6-phase-10w-maintenance-runbook.md b/docs/combat-v6-phase-10w-maintenance-runbook.md index 7ec75346..193b2a2d 100644 --- a/docs/combat-v6-phase-10w-maintenance-runbook.md +++ b/docs/combat-v6-phase-10w-maintenance-runbook.md @@ -24,15 +24,15 @@ 以下命令是模板。先在受控维护终端设置 `RELEASE_ENV_FILE` 为已核对目标的绝对路径;本轮只运行过明确的 `env/local.env --dry-run`。 ```bash -bun --env-file="$RELEASE_ENV_FILE" scripts/migrate-sect-task-battles-v6.ts --dry-run -bun --env-file="$RELEASE_ENV_FILE" scripts/retire-sect-artifact-deliveries.ts --dry-run +node --env-file="$RELEASE_ENV_FILE" --import tsx apps/api/scripts/migrate-sect-task-battles-v6.ts --dry-run +node --env-file="$RELEASE_ENV_FILE" --import tsx apps/api/scripts/retire-sect-artifact-deliveries.ts --dry-run ``` 保存盘点输出及备份,逐条核对UUID后才执行。下例 `RECORD_ID_1` 必须是对应脚本盘点出的任务记录UUID,可追加明确核对的UUID;空列表不会允许apply。 ```bash -bun --env-file="$RELEASE_ENV_FILE" scripts/migrate-sect-task-battles-v6.ts --apply "$RECORD_ID_1" -bun --env-file="$RELEASE_ENV_FILE" scripts/retire-sect-artifact-deliveries.ts --apply "$RECORD_ID_1" +node --env-file="$RELEASE_ENV_FILE" --import tsx apps/api/scripts/migrate-sect-task-battles-v6.ts --apply "$RECORD_ID_1" +node --env-file="$RELEASE_ENV_FILE" --import tsx apps/api/scripts/retire-sect-artifact-deliveries.ts --apply "$RECORD_ID_1" ``` 两个命令的UUID列表必须分别准备,不能直接复用另一脚本的清单。 diff --git a/docs/combat-v6-phase-10w-release-acceptance.md b/docs/combat-v6-phase-10w-release-acceptance.md index 0b1686d9..6c5fb5f3 100644 --- a/docs/combat-v6-phase-10w-release-acceptance.md +++ b/docs/combat-v6-phase-10w-release-acceptance.md @@ -55,8 +55,8 @@ - `bun run lint`:通过。 - `bun run test`:114个文件、759项通过。此为当前代码基线;10V历史131/876对应外部删除17份测试之前,不沿用旧数量。 - `bun run build`:类型检查与两端构建通过;保留既有较大chunk提示。 -- `bun --env-file=env/local.env scripts/migrate-sect-task-battles-v6.ts --dry-run`:通过,0条。 -- `bun --env-file=env/local.env scripts/retire-sect-artifact-deliveries.ts --dry-run`:通过,0条。 +- `bun --env-file=env/local.env apps/api/scripts/migrate-sect-task-battles-v6.ts --dry-run`:通过,0条。 +- `bun --env-file=env/local.env apps/api/scripts/retire-sect-artifact-deliveries.ts --dry-run`:通过,0条。 - 上述应用检查后没有修改业务代码,不为后续纯文档编辑重复全量测试。没有新增服务端、前端、数据库测试或一次性验收脚本。 - local1保留真实学习进度、已消费丹药效果、野外掉落与宗门奖励;原流派和青石袍装备状态已恢复。历史邮件样本已领且药品已消耗,周勤务奖励保持未领。 - 未提交Git、执行迁移、删表、停机维护或部署;既有 `eslint.config.js` 修改保持原样。 diff --git a/docs/combat-v6-phase-10x-dungeon-polish.md b/docs/combat-v6-phase-10x-dungeon-polish.md index eba59fc8..17c830ff 100644 --- a/docs/combat-v6-phase-10x-dungeon-polish.md +++ b/docs/combat-v6-phase-10x-dungeon-polish.md @@ -20,7 +20,7 @@ ### 已实现 -- `src/shared/lib/dungeon/materialCosts.ts` 负责纯材料匹配与扣量规划;固定材料与历史取出的转换材料共用事实读取。服务端 `costs.ts` 只查询bag,通过既有库存revision校验保存,与资源、状态和费用账本一起提交。 +- `packages/shared/src/lib/dungeon/materialCosts.ts` 负责纯材料匹配与扣量规划;固定材料与历史取出的转换材料共用事实读取。服务端 `costs.ts` 只查询bag,通过既有库存revision校验保存,与资源、状态和费用账本一起提交。 - `prepareDungeonEncounter` 固定人物、携带灵兽与敌方输入,保存在既有run的私有battlePayload中。公开state只包含确认所需摘要;`POST /api/dungeon/battle/begin` 才创建战局,同一遭遇重复迎战返回同一battleId。 - 探索请求携带runId、round和稳定actionId;已提交行动重放返回当前状态,陈旧轮次的新操作及不允许行动的状态拒绝执行。 - 新增战前正文并复用秘境壳层。实测修复了退出结算后旧WAITING_BATTLE分支遮挡结算页的问题。 @@ -37,7 +37,7 @@ ### 检查与剩余范围 -- `bun run test src/shared/lib/dungeon src/shared/engine/combat-v6/dungeon src/shared/rewards/dungeon.test.ts`:4文件24项通过。其中5项新增纯逻辑用例覆盖跨堆叠/品质顺序、指定名称保留、重叠不足不改输入、storage排除、固定与转换材料。 -- `bunx tsc -b tsconfig.app.json tsconfig.node.json --pretty false`、`bun run lint`、`bun run build`、`git diff --check`通过;退出页面修复后重新运行lint/build。未重复全仓共享测试,未新增前后端或数据库单测、一次性验收脚本;未执行迁移或发布。 +- `bun run test packages/shared/src/lib/dungeon packages/shared/src/engine/combat-v6/dungeon packages/shared/src/rewards/dungeon.test.ts`:4文件24项通过。其中5项新增纯逻辑用例覆盖跨堆叠/品质顺序、指定名称保留、重叠不足不改输入、storage排除、固定与转换材料。 +- `bunx tsc -b apps/web/tsconfig.json tsconfig.node.json --pretty false`、`bun run lint`、`bun run build`、`git diff --check`通过;退出页面修复后重新运行lint/build。未重复全仓共享测试,未新增前后端或数据库单测、一次性验收脚本;未执行迁移或发布。 - 真实浏览器未覆盖仅旧宝库有物/storage有物、跨堆叠混合品质、材料不足重选、支付事务故障/并发竞争,以及战前全部构筑入口互斥。匹配规则已有共享测试与事务代码检查,但不能替代这些运行验收;G01相应子项继续待验,G11其余秘境矩阵保持原状态。 - 本地首次场景生成被既有LLM结构校验拒绝,重试正常成功,未放宽校验;继续探索曾出现一次空响应JSON解析提示,服务端完成HTTP 200后刷新恢复到下一轮,原因未定位。该恢复体验留作后续排查,不声称本次修复。 diff --git a/docs/combat-v6-phase-10y-dungeon-recovery.md b/docs/combat-v6-phase-10y-dungeon-recovery.md index 8c120407..03f0cfa1 100644 --- a/docs/combat-v6-phase-10y-dungeon-recovery.md +++ b/docs/combat-v6-phase-10y-dungeon-recovery.md @@ -42,8 +42,8 @@ run:`f1d6b5f5-60f4-45cc-a406-cbcfce4b981a`。 ### 静态检查与未覆盖项 -- `bun run test src/shared/lib/dungeon src/shared/engine/combat-v6/dungeon src/shared/rewards/dungeon.test.ts`:4文件27项通过;8项选物用例覆盖高品质自主选择、跨堆叠、漏选/多交/少交、revision、位置/类型/品质/名称、跨要求共享数量、重复引用和固定/转换物品。 -- `bunx tsc -b tsconfig.app.json tsconfig.node.json --pretty false`、`bun run lint`、`bun run build`通过;最终入口超时调整后重新lint/build,重启API后健康检查200、已完成秘境重复退出409。保留既有构建大chunk提示。 +- `bun run test packages/shared/src/lib/dungeon packages/shared/src/engine/combat-v6/dungeon packages/shared/src/rewards/dungeon.test.ts`:4文件27项通过;8项选物用例覆盖高品质自主选择、跨堆叠、漏选/多交/少交、revision、位置/类型/品质/名称、跨要求共享数量、重复引用和固定/转换物品。 +- `bunx tsc -b apps/web/tsconfig.json tsconfig.node.json --pretty false`、`bun run lint`、`bun run build`通过;最终入口超时调整后重新lint/build,重启API后健康检查200、已完成秘境重复退出409。保留既有构建大chunk提示。 - 检查 `git diff --check` 与本阶段Markdown链接。未新增前端/服务端/数据库单测、一次性验收脚本;未执行迁移或部署,未重复全仓共享测试。 - 尚未真实覆盖:仅旧宝库/storage有物、混合品质跨堆叠提交、多个材料要求、全部构筑入口占用、数据库提交故障、Redis确认失败与多实例竞争。共享测试和代码核对不冒充这些运行证据,相关G01/G11/R02/R06子项继续待验。 - 10X空响应的原始网络原因未原样复现;本轮验证了真实服务中断及结算请求发出后刷新恢复,未声称覆盖所有代理/网络故障。 diff --git a/docs/combat-v6-phase-10z-dungeon-continuity.md b/docs/combat-v6-phase-10z-dungeon-continuity.md index f819d2f8..a8f5da60 100644 --- a/docs/combat-v6-phase-10z-dungeon-continuity.md +++ b/docs/combat-v6-phase-10z-dungeon-continuity.md @@ -52,8 +52,8 @@ run `6fd2c4a2-cab8-442f-86b9-d82f2992c0bf`,battle `125bf89f-3cba-4ab3-a54a-be7 ## 检查与剩余边界 -- 聚焦共享测试:`bun run test src/shared/lib/dungeon src/shared/engine/combat-v6/dungeon src/shared/rewards/dungeon.test.ts`,5文件29项通过。 -- `bunx tsc -b tsconfig.app.json tsconfig.node.json --pretty false`、`bun run lint`通过;`bun run build`通过,沿用已有大chunk提示。末次界面读取错误禁用调整后复跑静态检查。 +- 聚焦共享测试:`bun run test packages/shared/src/lib/dungeon packages/shared/src/engine/combat-v6/dungeon packages/shared/src/rewards/dungeon.test.ts`,5文件29项通过。 +- `bunx tsc -b apps/web/tsconfig.json tsconfig.node.json --pretty false`、`bun run lint`通过;`bun run build`通过,沿用已有大chunk提示。末次界面读取错误禁用调整后复跑静态检查。 - 本阶段diff检查通过。没有新增服务端/前端单测、数据库测试或一次性验收脚本,没有迁移、发布或提交代码。保留并行装备治理与既有eslint配置变更。 - 最终浏览器控制台错误/警告读取为空;本地API保持运行,测试创建的两个秘境均已结束。 - 未覆盖:恢复药读取/提交时真实服务中断、仅storage/旧宝库有材料、混合品质跨堆叠与多要求的真实提交;这些仍有共享规则证据,但没有本轮正式页面正向样本。 diff --git a/docs/combat-v6-phase-8a-arena-online-design.md b/docs/combat-v6-phase-8a-arena-online-design.md index 3d87ec72..8747cd7a 100644 --- a/docs/combat-v6-phase-8a-arena-online-design.md +++ b/docs/combat-v6-phase-8a-arena-online-design.md @@ -22,7 +22,7 @@ | 位置 | 已有事实 | 本阶段处理 | | --- | --- | --- | -| `src/shared/contracts/arena.ts` | 每方四席、房间规则、冻结阵容 | 保留房间领域,不另起模型 | +| `packages/shared/src/contracts/arena.ts` | 每方四席、房间规则、冻结阵容 | 保留房间领域,不另起模型 | | `ArenaBattleStartOrchestrator.ts` | 从冻结阵容调用旧 MatchFactory/Matchmaker | 替换为 v6 开战编排与幂等关联 | | `routes/game/arena/route.tsx` | 开战后跳转 `/game/battle/live/:id` | 跳转独立 v6 擂台战斗路由 | | `BattleArenaRoomFinalizer.ts` | 依赖旧终局 manifest 清理房间 | 新增 v6 终局适配,复用房间操作,不导入旧战斗契约 | @@ -84,7 +84,7 @@ | 首次进入、重连、缺口 | HTTP 读取完整基准或匹配游标后的增量 | | 逐行动文字与血条 | React/播放控制器,不按画面帧率发送网络消息 | -新增擂台 v6 HTTP 契约放在 `src/shared/contracts`,接入现有认证中间件。建议独立战斗路由 `/game/combat-v6/arena/:battleId` 与 `/api/combat-v6/arena/...`,最终名称在实现时与路由注册统一;不复用 v5 live 路由组件。玩家没有直接触发权威 resolve 的在线接口。 +新增擂台 v6 HTTP 契约放在 `packages/shared/src/contracts`,接入现有认证中间件。建议独立战斗路由 `/game/combat-v6/arena/:battleId` 与 `/api/combat-v6/arena/...`,最终名称在实现时与路由注册统一;不复用 v5 live 路由组件。玩家没有直接触发权威 resolve 的在线接口。 推送信封至少包含协议版本、battle ID、round ID、revision、可见事件游标及消息种类。业务事件区分指令已接受/提交状态、阶段变化、回合结果与终局;消息中携带服务端时间。NATS Core 复用跨实例分发,WebSocket 订阅必须经认证和参与者归属校验。Redis 为权威,消息丢失不等于战斗丢失;不新增外部状态库或消息中间件。 @@ -128,7 +128,7 @@ HTTP 补增量必须校验基准和可见游标;服务端不再保留所需区 ## 9. 验收矩阵 -纯单元测试仅位于 `src/shared`,不新增前端、服务端或基础设施 mock 测试。 +纯单元测试仅位于 `packages/shared/src`,不新增前端、服务端或基础设施 mock 测试。 - 倒地人物成功提交;快治疗拉起慢人物后本轮执行;慢治疗拉起已错过顺序的人物不补行动;二次倒地、保护、状态解除和资源恢复后的执行裁定。 - 1v1、现有规则允许的不等人数及 4v4;全部参与者的指令归属、同速顺序、超时与离线普攻、逃跑后剩余队友继续战斗。 diff --git a/docs/combat-v6-phase-9a-summoned-beast-plan.md b/docs/combat-v6-phase-9a-summoned-beast-plan.md index 5c49551f..fab4f5f5 100644 --- a/docs/combat-v6-phase-9a-summoned-beast-plan.md +++ b/docs/combat-v6-phase-9a-summoned-beast-plan.md @@ -76,7 +76,7 @@ ## 验证与发布边界 -- 仅在 `src/shared` 添加纯确定性测试,不新增前端、服务端、数据库或第三方模拟单元测试,不新增一次性 seed/smoke/E2E 脚本。 +- 仅在 `packages/shared/src` 添加纯确定性测试,不新增前端、服务端、数据库或第三方模拟单元测试,不新增一次性 seed/smoke/E2E 脚本。 - 浏览器验证遵循 `docs/testing.md`,使用纯本地环境和项目测试账号规范。 - 代码修改使用内置编辑工具;React 遵循指定最佳实践,格式使用仓库配置;不引入全局状态库或全站整改。 - 执行适用的共享测试、`bun run lint`、`bun run build`、触及文件 Prettier 检查与 `git diff --check`,记录真实结果和未验证项。 @@ -85,7 +85,7 @@ ## 实施记录(2026-09-06) -- `src/shared/engine/combat-v6/beasts` 保存正式个体模型、确定性生成、独立面板与技能投影;同系普通/高级连击只启用高级效果。 +- `packages/shared/src/engine/combat-v6/beasts` 保存正式个体模型、确定性生成、独立面板与技能投影;同系普通/高级连击只启用高级效果。 - 迁移 `0038_v6_summoned_beasts.sql` 新增个体表及编组表;领取、编组、休养通过人物锁和事务保护。战斗开始冻结携带个体,战中禁止修改。 - `/game/beasts` 提供一次性领取、详情、携带、首发和休养;复用场景壳、抽屉和 tooltip。 - 野外、练功房、擂台统一人物/宠物整组指令;召回和换宠按权威行动顺序更新差量帧。己方宠物可查看具体属性,其他玩家及观众只获得公开比例,不公开未出场备用宠物。 diff --git a/docs/combat-v6-phase-9b-capture-progression-plan.md b/docs/combat-v6-phase-9b-capture-progression-plan.md index 263bdcdf..52ca6106 100644 --- a/docs/combat-v6-phase-9b-capture-progression-plan.md +++ b/docs/combat-v6-phase-9b-capture-progression-plan.md @@ -122,7 +122,7 @@ - 完整循环:捕捉 → 结算入库 → 编组 → 胜利经验 → 自动升级 → 加点 → 下一场投影 → 死亡寿命 → 灵石休养。 - 验证最后在场且存活、末回合换宠、死亡、召回、上限丢弃经验和超等级捕获后的出战限制。 - 接续 9A 未验收的真实 4v4、带宠观战、PvE 死亡与结算重试、多备用宠换宠;多实例、慢连接等故障矩阵单独记录,不默认通过。 -- 仅在 `src/shared` 增加纯确定性测试;浏览器按 `docs/testing.md` 使用本地账号,不添加一次性 seed/smoke/E2E 脚本,不向远程环境写测试数据。 +- 仅在 `packages/shared/src` 增加纯确定性测试;浏览器按 `docs/testing.md` 使用本地账号,不添加一次性 seed/smoke/E2E 脚本,不向远程环境写测试数据。 - 执行 focused tests、适用的全量共享测试、`bun run lint`、`bun run build`、触及文件 Prettier 与 `git diff --check`。 - 维护期硬切协议与内容版本;是否需要数据库迁移由最终持久字段差异决定,不预先新增表。永久个体事实保留,归档继续冻结展示资料,列表不解包回放。无 v5 回退和功能开关。 diff --git a/docs/combat-v6-phase-9c-inventory-plan.md b/docs/combat-v6-phase-9c-inventory-plan.md index bf51b64b..9ad9cba9 100644 --- a/docs/combat-v6-phase-9c-inventory-plan.md +++ b/docs/combat-v6-phase-9c-inventory-plan.md @@ -77,14 +77,14 @@ v6 原有 `combatV6EquipmentInstances` 与 `combatV6EquipmentLoadouts`;本轮 - 本地实际验证掉落入包、满包溢出储藏室、取出、学习、下一场生效、旧回放不变;以及越权、过期 revision、连续点击、响应不明后的刷新和战中管理限制。 - 检查桌面及 360px 格子布局、详情和二级选择,避免弹窗层叠;新背包不读取旧物品作为可消费副本。 - 完成 v6 装备引用、持有归属与迁移核对;不留下平行所有权或失效引用。 -- 按项目规范只在 `src/shared` 新增纯测试,不新增前端/服务端/数据库测试或一次性脚本。业务实现后执行 lint、build、适用共享测试、触及文件 Prettier 及差异检查。 +- 按项目规范只在 `packages/shared/src` 新增纯测试,不新增前端/服务端/数据库测试或一次性脚本。业务实现后执行 lint、build、适用共享测试、触及文件 Prettier 及差异检查。 - 9B 付费休养、结算故障重试和多人矩阵的未验收项继续单独记录,不能因 9C 完成自动标记通过。 ## 8. 实施、发布与验证记录 ### 代码落点 -- `src/shared/inventory`:固定定义、实例 schema、获得/堆叠/溢出/整理、学习和掉落纯规则;具体效果仍由 v6 领域负责。 +- `packages/shared/src/inventory`:固定定义、实例 schema、获得/堆叠/溢出/整理、学习和掉落纯规则;具体效果仍由 v6 领域负责。 - `InventoryService.ts`:统一查询、转移、移动/交换/合并、拆分、整理、学习与装卸。角色锁与 SQL 行锁保护事务,数量和 revision 校验防止旧请求重复消费。没有新增消费凭据表。 - `/api/combat-v6/inventory`:GET 分页查询与 POST 操作,复用活跃角色认证;储藏室每页 40 条,不一次下载无限库存。 - `/game/inventory`:新版格子背包;`/game/cave/storage/new?location=storage`:新储藏室;`/game/cave/storage`:原物品页面保留为洞府旧藏。旧消费玩法返回入口指向旧藏,不会把旧库存当成新背包可消费记录。 diff --git a/docs/combat-v6-phase-9d-forging-plan.md b/docs/combat-v6-phase-9d-forging-plan.md index 8b365e41..0a214ba9 100644 --- a/docs/combat-v6-phase-9d-forging-plan.md +++ b/docs/combat-v6-phase-9d-forging-plan.md @@ -35,7 +35,7 @@ ## 3. 已确认实现边界 -- 物品定义推荐放在 `src/shared/items/definitions`,按类别组织并统一注册;铸造规则与装备效果仍由独立共享领域负责,不放入库存操作服务。 +- 物品定义推荐放在 `packages/shared/src/items/definitions`,按类别组织并统一注册;铸造规则与装备效果仍由独立共享领域负责,不放入库存操作服务。 - 图纸同槽位、同等级可堆叠,上限 99;可堆叠材料每格上限 99。1~5 件材料按实际消耗件数计算,允许同种材料多件及混合类型,必须恰好满足配方数量,不额外加料。 - 宝库取出在同一事务中扣减旧材料并写入新库存,失败整体回滚。保留材料有效事实,按结构校验转换,不重新随机,不将旧自由 details 作为规则执行。 - 铸造在同一事务中完成资源、图纸、材料扣除及成品入包;校验消耗完成后的背包容量。服务端检查归属、数量及版本,复用角色锁,不新增通用任意数据写入接口。 @@ -77,8 +77,8 @@ ## 6. 实现与发布 -- `src/shared/items/definitions` 按兽诀、道装、图纸、材料分类;108 种图纸覆盖六个槽位与 10~180 级。材料使用校验后的名称、类型、品质、元素、描述作为堆叠事实。 -- `src/shared/forging` 管理成本与名称池;装备生成器 v3 独立材料增益随机流,基础 v2 随机流与器诀分布不变。v3 按对应槽位和等级的名称池校验,旧版本仍按旧模板名校验。历史名称池需保留,调整已有名称规则时发布新生成版本。 +- `packages/shared/src/items/definitions` 按兽诀、道装、图纸、材料分类;108 种图纸覆盖六个槽位与 10~180 级。材料使用校验后的名称、类型、品质、元素、描述作为堆叠事实。 +- `packages/shared/src/forging` 管理成本与名称池;装备生成器 v3 独立材料增益随机流,基础 v2 随机流与器诀分布不变。v3 按对应槽位和等级的名称池校验,旧版本仍按旧模板名校验。历史名称池需保留,调整已有名称规则时发布新生成版本。 - 复用现有库存表及装备装配表,无数据库迁移、无新增消费凭据表。角色 Redis 锁、角色行锁、同一事务串联校验、资源消费与成品入包;容量按消费后的格位判断。 - `/game/craft/refine` 硬切新炼器室;旧 `refine` 估价、生成与确认入口返回 410,放弃旧任务入口保留用于释放既有占用。 - `GET /api/combat-v6/forging` 读取背包及资源;`POST` 提交图纸和材料的 ID、revision、消耗数量。`GET /vault` 分页搜索旧宝库;`POST /vault/withdraw` 提交 ID、数量及 expectedQuantity。所有正式入口校验登录和角色归属。 @@ -106,7 +106,7 @@ ## 8. 炼器室视觉与交互重构(2026-09-08) - 一级页面复用 `RoomView`,提供地火器炉、道装图录、铸器碑。设施间切换保留当前备料,不增加新仓库或旧炼器入口。 -- 炉体采用独立透明美术,2026-09-20重制为焦墨与朱砂的写意国画,现用 `public/assets/icons/earthfire-furnace-ink.png`,通过 `GameIcon` 接入;[素材与提示词](art/craft-furnaces.md)。固定一个图纸位、五个材料位,按实际需求开放;无需位置弱化。背景美术不包含文字与操作控件。 +- 炉体采用独立透明美术,2026-09-20重制为焦墨与朱砂的写意国画,现用 `apps/web/public/assets/icons/earthfire-furnace-ink.png`,通过 `GameIcon` 接入;[素材与提示词](art/craft-furnaces.md)。固定一个图纸位、五个材料位,按实际需求开放;无需位置弱化。背景美术不包含文字与操作控件。 - 桌面右侧五列八行储物袋沿用真实 40 格库存位置,按类别弱化其他格;点击材料逐份投入,炉中点击移出。更换图纸保留合格且数量允许的材料,并提示移出的材料。移动端只显示炉阵,通过 `InkDetailDrawer` 连续选料。 - 同一库存格的多份材料在提交前合并数量,沿用 ID、revision 与既有铸造接口;本轮没有修改服务端、生成算法、费用、掉落或持久化结构。主操作统一 `InkButton`,样式使用 Tailwind v4。 - 请求期间禁止重复提交与离开;短暂炉火反馈与请求并行,减少动态效果偏好下跳过等待。成功后才揭晓真实成品,详情复用道装比较组件。失败清空备料并重新核对,不自动重试消费请求。 @@ -119,5 +119,5 @@ - 桌面与 360px 浏览器复查通过;炉体正常加载、六格固定、物品栏40格、移动抽屉只有一份库存视图,无横向溢出。修正手机格子文字拥挤,页面共享 HUD 与底部导航保持。 - 编辑与构建期间曾遇到页面重载及热更新异常,稳定代码后完整刷新重验成功,未将热更新期异常归因于铸造接口或修改全局资源壳。 - 临时图纸、材料、三件成品已按具体 ID 清理,灵石与灵气恢复基准0/200,保留资源审计记录及角色既有进度。 -- 8 项 `src/shared/forging` 测试、lint、四个触及源码文件 Prettier 与 diff 检查通过。炼器代码完成后的客户端/服务端/resolver Worker 构建曾完整通过;收尾重跑时被并行新增的 `src/shared/engine/combat-v6/dungeon/host.ts` 类型错误阻断(`snapshot` 返回类型及 `contentVersion`)。未改动该副本工作,不能将最终工作区构建记为通过。未新增前端或服务端测试。 +- 8 项 `packages/shared/src/forging` 测试、lint、四个触及源码文件 Prettier 与 diff 检查通过。炼器代码完成后的客户端/服务端/resolver Worker 构建曾完整通过;收尾重跑时被并行新增的 `packages/shared/src/engine/combat-v6/dungeon/host.ts` 类型错误阻断(`snapshot` 返回类型及 `contentVersion`)。未改动该副本工作,不能将最终工作区构建记为通过。未新增前端或服务端测试。 - 未重复满包、五份高等级材料、跨角色权限、战斗占用及装备实战回归;360px 为浏览器视口验收,非实体手机。 diff --git a/docs/combat-v6-phase-9e-drops-plan.md b/docs/combat-v6-phase-9e-drops-plan.md index 9a0ec77a..0e7a6831 100644 --- a/docs/combat-v6-phase-9e-drops-plan.md +++ b/docs/combat-v6-phase-9e-drops-plan.md @@ -16,8 +16,8 @@ | 模块 | 职责 | | --- | --- | -| `src/shared/drops` | 独立配置校验、概率/权重/数量抽取,输出不透明奖励 ID | -| `src/shared/rewards/wild.ts` | 野外池与物品奖励解析,固定物品引用及随机装备生成适配 | +| `packages/shared/src/drops` | 独立配置校验、概率/权重/数量抽取,输出不透明奖励 ID | +| `packages/shared/src/rewards/wild.ts` | 野外池与物品奖励解析,固定物品引用及随机装备生成适配 | | `CombatV6WildSessionService` | 每场胜利触发、开战冻结池、原生哈希创建独立随机流、冻结终局物品事实 | | `CombatV6ConditionProjector` / `InventoryService` | 沿用既有结算幂等、事务和容量规则发放 | diff --git a/docs/combat-v6-phase-9g-sect-plan.md b/docs/combat-v6-phase-9g-sect-plan.md index 34af0c9f..92975560 100644 --- a/docs/combat-v6-phase-9g-sect-plan.md +++ b/docs/combat-v6-phase-9g-sect-plan.md @@ -61,7 +61,7 @@ - 本地认证浏览器验收:主心法连续 1→2→3 级,每次扣 60 修为与 200 灵石,所选被动神通保持,贡献从 +2→+4→+6;三层节点保存后保留焦点并正确回显,取消节点后切换流派触发草稿保护,确认放弃后可切换。 - 桌面与 360px 窄屏检查通过;手机节点详情以独立抽屉展示,选择后收起,无重复标题。修复吸底操作栏遮挡神通说明的问题,操作栏随正文排列;心法页面横向宽度与视口均为 360px。 - 本地道友2临时资源、心法等级、经脉深度及节点已恢复原值,revision 保持单调,资源审计记录保留。 -- `bun run lint`、`bun run build`、`bun run test src/shared/engine/combat-v6/sect-progression`(6项)、四个触及源码文件 Prettier 检查通过。未重复全量共享测试、跨页离开/冲突恢复和完整战斗/转宗链路;这些链路此前的验收记录及未完成项不因本次 UI 验收而改变。 +- `bun run lint`、`bun run build`、`bun run test packages/shared/src/engine/combat-v6/sect-progression`(6项)、四个触及源码文件 Prettier 检查通过。未重复全量共享测试、跨页离开/冲突恢复和完整战斗/转宗链路;这些链路此前的验收记录及未完成项不因本次 UI 验收而改变。 ### 页面细节修订(2026-09-08) diff --git a/docs/combat-v6-polish-and-closure-plan.md b/docs/combat-v6-polish-and-closure-plan.md index 591dc4d0..c2728828 100644 --- a/docs/combat-v6-polish-and-closure-plan.md +++ b/docs/combat-v6-polish-and-closure-plan.md @@ -39,7 +39,7 @@ | --- | --- | --- | --- | | R01 P0 待完善 | 汇总文档落后于代码:10W旧待验已部分补齐,维护单止于较早迁移;旧表计划仍引用旧V6表名;技能仍有已删除引擎说明 | 更新当前状态和维护引用,审查0043~0045及实际目标迁移账本;保留历史记录但明确替代关系,不照抄旧profile初始化/回放兼容要求 | [10W](combat-v6-phase-10w-release-acceptance.md)、[维护单](combat-v6-phase-10w-maintenance-runbook.md)、[领域归属](combat-domain-ownership.md) | | R02 P0 待验 | 战斗恢复:正常重启、部分事务失败已验;PG/Redis/NATS中断、多实例竞争、截止边缘及终局消费故障未完整覆盖 | 按实际部署拓扑验证收令、锁过期、重连、终局重试和占用释放;核对资源/奖励/回放不重复,无永久卡战;两小时战局与终局载荷TTL到期的处置有证据 | [8A](combat-v6-phase-8a-arena-online-design.md)、[7C](combat-v6-phase-7c-redis-runtime-replay-design.md)、[10W](combat-v6-phase-10w-release-acceptance.md) | -| R03 P0 待验/待决策 | 蜃楼防重边界:旧10B允许跨存储窗口重复;当前代码已按battleId在奖励事务内检查归档。同场重试保护与周领取记录全丢失后重开不是同一问题 | 实测PG提交后Redis确认失败;分开核实同场重试、周记录丢失新开挑战、回放清理对防重的影响。明确“允许进度重置”是否也允许再次领周奖励,与10W重复发奖阻断标准统一;确需修复时只补最小持久事实 | [10B](combat-v6-phase-10b-tower-plan.md)、[当前结算](../src/server/lib/tower/combatV6.ts) | +| R03 P0 待验/待决策 | 蜃楼防重边界:旧10B允许跨存储窗口重复;当前代码已按battleId在奖励事务内检查归档。同场重试保护与周领取记录全丢失后重开不是同一问题 | 实测PG提交后Redis确认失败;分开核实同场重试、周记录丢失新开挑战、回放清理对防重的影响。明确“允许进度重置”是否也允许再次领周奖励,与10W重复发奖阻断标准统一;确需修复时只补最小持久事实 | [10B](combat-v6-phase-10b-tower-plan.md)、[当前结算](../apps/api/src/lib/tower/combatV6.ts) | | R04 P0 待验 | 经济写入:已有顺序交易及普通重放证据,购买/下架/自然到期三方竞争、跨进程锁失效、响应丢失与消息故障不足 | 两独立买家及卖家竞争同一货单;核对物品、扣费、税款、邮件与剩余量;领取、赠送、回收、兑换发生故障后重试不得损物或多发 | [10N](combat-v6-phase-10n-npc-recycle.md)、[10Q](combat-v6-phase-10q-mail-inventory.md)、[10R](combat-v6-phase-10r-auction-inventory.md) | | R05 P0 待验 | 真实周期:午夜次数、宗门任务跨日/跨周、幻境周结、兑换周限、48小时拍卖到期、10分钟回收报价自然到期 | 实际边界前后验证归属周期、额度和奖励;旧周期战斗完成后不能进入下一旧周期场次;过期与并发购买不重发。按测试规范使用本地环境,不新增一次性脚本 | [10B](combat-v6-phase-10b-tower-plan.md)、[10C](combat-v6-phase-10c-ranking-plan.md)、[10E](combat-v6-phase-10e-auto-sect-tasks-plan.md)、10N/10R/10S/10V | | R06 P0 待验 | 权限与互斥:部分401/404/409已验,不能外推全部入口 | 第三账号专属货单不可见/不可买;回放非参与者拒绝;观众不能下令;活动及待结算期间逐项拒绝换装、功法、宗门、灵兽、资源消费与互斥开战;失败后可恢复 | [8C](combat-v6-phase-8c-spectator-design.md)、[10F](combat-v6-phase-10f-breakthrough-plan.md)、[10R](combat-v6-phase-10r-auction-inventory.md) | @@ -53,14 +53,14 @@ R08依赖R01~R07及首发范围内的核心场景通过;不能仅靠健康 | ID/优先级/状态 | 当前简化或缺口 | 建议范围与完成标准 | 来源/代码 | | --- | --- | --- | --- | | G01 P1 已实现/部分待验 | 10X恢复冻结遭遇的战前确认;10Y依用户补充改为公共物品栏自主提交,实际选物、取消、精确扣除、行动重放及服务中断/结算刷新恢复通过 | 继续补仅旧宝库/storage有物、跨堆叠混合品质、多要求与支付故障/并发运行证据;纯选物测试已覆盖,不据此关闭全部运行矩阵 | [10Y实施与验收](combat-v6-phase-10y-dungeon-recovery.md)、[10X](combat-v6-phase-10x-dungeon-polish.md) | -| G02 P1 待完善 | 青溪仅5~15级三物种,正式图纸/成品掉落仅10级;领域定义10~180级不等于玩家可获得,宗门交付因此封顶10级 | 先规划一个相邻等级带的区域、图纸、材料、灵兽、成本和任务要求闭环,再递进开放;每一开放档位能靠正式玩法获取并消耗,不用dev发物证明供给 | [9B](combat-v6-phase-9b-capture-progression-plan.md)、[9E](combat-v6-phase-9e-drops-plan.md)、[10S](combat-v6-phase-10s-sect-inventory.md)、[wild.ts](../src/shared/rewards/wild.ts) | +| G02 P1 待完善 | 青溪仅5~15级三物种,正式图纸/成品掉落仅10级;领域定义10~180级不等于玩家可获得,宗门交付因此封顶10级 | 先规划一个相邻等级带的区域、图纸、材料、灵兽、成本和任务要求闭环,再递进开放;每一开放档位能靠正式玩法获取并消耗,不用dev发物证明供给 | [9B](combat-v6-phase-9b-capture-progression-plan.md)、[9E](combat-v6-phase-9e-drops-plan.md)、[10S](combat-v6-phase-10s-sect-inventory.md)、[wild.ts](../packages/shared/src/rewards/wild.ts) | | G03 P1 待完善 | 秘境普通/精英/首领共用少量等级模板;蜃楼首轮NPC普攻、首领主要加气血;宗门矿兽/长老复用既有技能 | 先验普通构筑难度,再给关键敌人增加能用现有原语表达的技能与行动差异;首发地图和楼层有可解释的难度,不靠重复加血撑时长。地图独有Boss可后续立项 | [10A](combat-v6-phase-10a-dungeon-plan.md)、[10B](combat-v6-phase-10b-tower-plan.md)、[10E](combat-v6-phase-10e-auto-sect-tasks-plan.md) | -| G04 P1 首版已实现/部分待验 | 10AB统一效用评分+均衡/进攻/保守权重,主动AUTO、非实时对战与所有NPC接入;仅可观察信息评分,手动及超时补令保持现状。训练和榜单重点本地验收通过 | 继续补全场景运行矩阵和复杂效果估值;全宗门平衡仍单列,不增加自动换宠或玩家策略编辑器。997项共享测试不代表全部真实玩法已验 | [10AB](combat-v6-phase-10ab-auto-utility.md)、[ranking/battle](../src/shared/engine/combat-v6/ranking/battle.ts)、[公共AUTO](../src/shared/combat-v6/auto.ts) | +| G04 P1 首版已实现/部分待验 | 10AB统一效用评分+均衡/进攻/保守权重,主动AUTO、非实时对战与所有NPC接入;仅可观察信息评分,手动及超时补令保持现状。训练和榜单重点本地验收通过 | 继续补全场景运行矩阵和复杂效果估值;全宗门平衡仍单列,不增加自动换宠或玩家策略编辑器。997项共享测试不代表全部真实玩法已验 | [10AB](combat-v6-phase-10ab-auto-utility.md)、[ranking/battle](../packages/shared/src/engine/combat-v6/ranking/battle.ts)、[公共AUTO](../packages/shared/src/combat-v6/auto.ts) | | G05 P1 待验/待调优 | 六种突破、金丹以上幻境、全境界排名主要有链路证据,强化测试属性不能证明正常难度 | 先确定正式可得装备/功法/修炼/心法样本,再制定目标回合、失败率、恢复消耗与收益范围;比较清心前后心魔、同级和跨档挑战,记录实际结果再调系数 | [10F](combat-v6-phase-10f-breakthrough-plan.md)、[10B](combat-v6-phase-10b-tower-plan.md)、[10C](combat-v6-phase-10c-ranking-plan.md) | | G06 P1 待验/待调优 | 五宗门双流派已实现,器蕴/器诀逐项实战、同队手动AUTO、特殊状态组合未全验 | 覆盖五宗十流派、关键经脉、复活/伤势/护盾/五行/形态和器诀,优先容易叠加或重复计效的组合;纯确定性边界使用shared测试,真实页面验证可选、可用及说明一致 | [宗门设计](combat-v6-sect-skill-meridian-system-design.md)、[9D](combat-v6-phase-9d-forging-plan.md)、[10E](combat-v6-phase-10e-auto-sect-tasks-plan.md) | | G07 P1 待调优 | 炼体0~60级节奏、丹毒、配方/灵果供给没有整体经济验证;心法经脉成本已配置但未整体平衡 | 对照正式来源核算升级所需时间、药力、灵石、修为和感悟;发现不可达或明显失衡再改配置。保留消耗品提升五轨、确定性位阶晋升,不新增直接点修 | [修炼设计§13](combat-v6-training-system-design.md#13-后续平衡项)、[宗门设计§13](combat-v6-sect-skill-meridian-system-design.md#13-后续专项平衡项) | -| G08 P1 待调优 | 新功法四境界位、24本九层、每位收藏六本已实现;六维与单条件机制同步成长;全部玉简暂共用青溪3%组、各权重100,没有稀有度或新渠道 | 评估三本同名玉简与八次修炼的实际养成时间、境界供给和交易价格;先决定分层掉落或兑换是否必要,再扩渠道。按[首版验证记录](combat-v6-manual-first-release-plan.md)继续验证机制收益及其低于装备/灵兽的整体预算 | [功法设计](combat-v6-manual-system-design.md)、[wild.ts](../src/shared/rewards/wild.ts) | -| G09 P1 待调优 | 蜃楼物资仍复用凡品固定材料,高境界收益未调;掉落长期经济样本不足 | 联合G02/G05建立野外、秘境、蜃楼、宗门、炼器与NPC回收的产销对照,关注资源通胀与无用途产物;每个开放奖励有实际用途,记录版本与既有快照边界 | [10B](combat-v6-phase-10b-tower-plan.md)、[tower奖励](../src/shared/rewards/tower.ts)、[9E](combat-v6-phase-9e-drops-plan.md) | +| G08 P1 待调优 | 新功法四境界位、24本九层、每位收藏六本已实现;六维与单条件机制同步成长;全部玉简暂共用青溪3%组、各权重100,没有稀有度或新渠道 | 评估三本同名玉简与八次修炼的实际养成时间、境界供给和交易价格;先决定分层掉落或兑换是否必要,再扩渠道。按[首版验证记录](combat-v6-manual-first-release-plan.md)继续验证机制收益及其低于装备/灵兽的整体预算 | [功法设计](combat-v6-manual-system-design.md)、[wild.ts](../packages/shared/src/rewards/wild.ts) | +| G09 P1 待调优 | 蜃楼物资仍复用凡品固定材料,高境界收益未调;掉落长期经济样本不足 | 联合G02/G05建立野外、秘境、蜃楼、宗门、炼器与NPC回收的产销对照,关注资源通胀与无用途产物;每个开放奖励有实际用途,记录版本与既有快照边界 | [10B](combat-v6-phase-10b-tower-plan.md)、[tower奖励](../packages/shared/src/rewards/tower.ts)、[9E](combat-v6-phase-9e-drops-plan.md) | | G10 P1 待验 | 多人/展示:旧8A曾有真实4v4,后续带宠、观众、慢连接与新协议未完成全矩阵 | 8个独立角色每人1宠、另设观众;混合手动/AUTO、倒地、复活、多备用宠换宠、逃跑、重连与终局;确认公开视图不泄露精确属性,按首发容量测请求与播放积压 | [9A](combat-v6-phase-9a-summoned-beast-plan.md)、[8C](combat-v6-phase-8c-spectator-design.md)、[战斗UI](combat-v6-battle-ui.md) | | G11 P1 部分已验 | 10Z已验证恢复丹具体堆叠消耗、人物MP跨场继承、换宠后两只灵兽实际经验到账、真实战败刷新结算;修正休整旧库存来源和扣药占用冲突 | 继续补灵兽MP实际消耗与死亡本轮回归、全地图高等级、任务消费者并发去重及完整状态/构筑入口互斥矩阵;不把部分入口通过外推全部 | [10Z验收](combat-v6-phase-10z-dungeon-continuity.md)、[10A](combat-v6-phase-10a-dungeon-plan.md#待专项验收) | | G12 P1 待验 | 手机与性能:360px响应式不等于真机触控,多数阶段Profiler空缺;回放长时间线、列表分页和大背包仍缺统一巡检 | 桌面/360px/真实触屏完成核心下令、抽屉、数量、高额确认、已改未保存离开、错误重试;检查遮挡、控制台、请求次数及长战报/回放性能,只修测得瓶颈 | [8B](combat-v6-phase-8b-replay-design.md)、[9G](combat-v6-phase-9g-sect-plan.md)、10N~10V各未覆盖项 | @@ -83,7 +83,7 @@ G10~G13中涉及核心入口不可用、越权、损物或无法结算的失 | E07 P2 延期 | 灵兽洗点、更多物种/技能、魂生等特殊兽诀(后续已接入,见技能迁移记录)、经验等级衰减复核 | 洗点只影响自由分配属性还是其他事实;技能仍遵守固定出生格。高等级野外开放前重评当前无经验衰减规则 | [9B](combat-v6-phase-9b-capture-progression-plan.md)、[灵兽设计](combat-v6-summoned-beast-system-design.md) | | E08 P3 延期 | 战斗药品与物品指令 | 药品类型、人物/灵兽使用权、下令与执行时扣物边界、各Host是否真实消耗、失败/重试/离场结算;需重新立项,现有战外服药不代表已完成 | [路线图明确延期](combat-v6-mhxy-redesign-roadmap.md#战斗药品与物品指令延后开发) | | E09 P3 延期 | 队伍阵法、宗门怒气技扩展、法宝 | 阵位与作用范围、独立资源/装配事实、投影与版本;4B战意和器诀已交付,不重复造同义系统 | [路线图](combat-v6-mhxy-redesign-roadmap.md)、[法宝方向](combat-v6-summoned-beast-system-design.md#12-法宝的未来方向) | -| E10 P3 候选 | 种族、灵根、命格进入战斗;境界压制、穿透、个体暴击抵抗/暴伤减免 | 当前均不能从旧V5偷偷继承。先确认设计收益和公式族,再以新规则/投影版本实施;旧命格战外作用不受此项否定 | [兼容边界](../src/shared/engine/combat-v6/CHARACTER_COMPATIBILITY.md)、[角色面板](combat-v6-character-panel-design.md) | +| E10 P3 候选 | 种族、灵根、命格进入战斗;境界压制、穿透、个体暴击抵抗/暴伤减免 | 当前均不能从旧V5偷偷继承。先确认设计收益和公式族,再以新规则/投影版本实施;旧命格战外作用不受此项否定 | [兼容边界](../packages/shared/src/engine/combat-v6/CHARACTER_COMPATIBILITY.md)、[角色面板](combat-v6-character-panel-design.md) | | E11 P3 候选 | 灵兽修炼、合宠/繁殖、还童/洗资质成长、内丹、装备、忠诚、进阶造型、交易 | 每项分别立项;现有0修炼、24持有/6携带/单宠在场为基线。不能将“不包含清单”视为全部承诺 | [灵兽首版范围](combat-v6-summoned-beast-system-design.md#11-首版范围) | | E12 P3 候选 | 新赌战;匹配/排位/跨服/组队多人秘境;PvE观战、公开大厅、战绩分享、视角切换、视频导出、统计分析 | 分别确定玩法目标、权限/隐私、资产与容量边界;现有邀请码擂台和参与者回放已是有效产品,不恢复旧分享接口 | [10D](combat-v6-phase-10d-bet-retirement.md)、[8A](combat-v6-phase-8a-arena-online-design.md)、[8B](combat-v6-phase-8b-replay-design.md)、[8C](combat-v6-phase-8c-spectator-design.md) | | E13 P3 候选 | 背包扩容、竞价/求购/批量上架、绑定体系;蜃楼商店/事件/局外天赋 | 保持40格、无限storage、一口价寄售及现有幻境流程,直到独立立项。先证明现有痛点,不预埋容器/绑定/持久周进度体系 | [9C](combat-v6-phase-9c-inventory-plan.md)、[10R](combat-v6-phase-10r-auction-inventory.md)、[10B](combat-v6-phase-10b-tower-plan.md) | diff --git a/docs/combat-v6-realm-attribute-budget.md b/docs/combat-v6-realm-attribute-budget.md index 9483b28b..522903ad 100644 --- a/docs/combat-v6-realm-attribute-budget.md +++ b/docs/combat-v6-realm-attribute-budget.md @@ -4,7 +4,7 @@ ## 规则与权威 -`src/shared/config/realmProgression.ts` 同时提供境界阶段等级、自然六维预算、自由点预算和突破前后预算差。战斗等级入口转出同一函数,加点和洗点继续使用同一自然值。0 级是六维各 10 的计算基底,不是正式境界。 +`packages/shared/src/config/realmProgression.ts` 同时提供境界阶段等级、自然六维预算、自由点预算和突破前后预算差。战斗等级入口转出同一函数,加点和洗点继续使用同一自然值。0 级是六维各 10 的计算基底,不是正式境界。 | 境界 | 初期 | 中期 | 后期 | 圆满 | | --- | ---: | ---: | ---: | ---: | diff --git a/docs/combat-v6-summoned-beast-system-design.md b/docs/combat-v6-summoned-beast-system-design.md index 1efb2b29..31bef52a 100644 --- a/docs/combat-v6-summoned-beast-system-design.md +++ b/docs/combat-v6-summoned-beast-system-design.md @@ -151,7 +151,7 @@ skillSlotCapacity = capturedSkills.length 每个物种独立配置五项资质闭区间与成长千分闭区间。2026-09-11 物种修订 3 已按历史手游图鉴校准:青灵狐参照花妖,岩甲猪参照龟丞相,疾风狼参照老虎,完整采用各自资质和成长范围。完整范围与来源见 [资质校准记录](combat-v6-beast-aptitude-calibration.md)。这些是已核验的第三方历史手游数据,不宣称当前官方版本已完全对齐;道友仍保留现有携带门槛。 -已有个体继续使用保存的资质、成长和加点,不重掷、不用当前生成范围判废旧个体;本次只改变后续派生面板。出生与捕捉随机顺序保持一致。战斗投影与加点预览共用 `beastPanel`,不接受前端面板作为权威输入。配置与校验见 [数据包说明](../src/shared/engine/combat-v6/beasts/data/README.md)。 +已有个体继续使用保存的资质、成长和加点,不重掷、不用当前生成范围判废旧个体;本次只改变后续派生面板。出生与捕捉随机顺序保持一致。战斗投影与加点预览共用 `beastPanel`,不接受前端面板作为权威输入。配置与校验见 [数据包说明](../packages/shared/src/engine/combat-v6/beasts/data/README.md)。 --- diff --git a/docs/combat-v6-ui-polish-plan.md b/docs/combat-v6-ui-polish-plan.md index 8e7115aa..4b73d807 100644 --- a/docs/combat-v6-ui-polish-plan.md +++ b/docs/combat-v6-ui-polish-plan.md @@ -162,7 +162,7 @@ - 日志转换迁至共享纯展示模块;补齐暴击/狂暴/伤害类型、护盾/解除原因。同目标连续伤害仅在相邻且无保护、转火、倒地分界时合并,细节可还原。新增确定性测试覆盖分界和增量不可变性。 - local2 真实训练完成:防御草稿→返回改攻击→灵兽防御;黄泉蚀生选目标→离线提交失败→草稿保留→恢复重试成功;保护合法目标选择;召回后重新召唤;统一详情和空器诀分类。360×640 下操作区仍为 168px,无内容溢出。 - 捕捉条件入口与不可用原因完成代码核查,实际捕捉场景留作最终矩阵补充;稀有暴击/保护/控制组合由共享事件测试与真实 18 回合擂台战报共同覆盖。真机动效不作已验证声明。 -- `bun run lint`、`bun run build` 通过;`bun run test src/shared/combat-v6` 共 9 文件/76 项通过;`git diff --check` 通过。 +- `bun run lint`、`bun run build` 通过;`bun run test packages/shared/src/combat-v6` 共 9 文件/76 项通过;`git diff --check` 通过。 - 阶段三开始:历史回合折叠、长局、回放/观战、满编阵容及最终场景复验。 ### 2026-09-16:满编开战阻塞与授权修复 @@ -175,8 +175,8 @@ - local1 与 local7 两个浏览器身份进入双方视角;第三个本地既有账号林归尘通过邀请码加入观战。观战青/赤方、同名灵兽标签、公开比例详情及无下令入口通过实测。 - 360×640 下全部 10 单位可见,操作区高 168px、顶部 461.60px,页面宽度 360px 无横向溢出;桌面及 1280×720 布局完成截图检查。实战已观察到自动/超时下令、逐行动播放、灵兽死亡、人物倒地、转火、暴击及负面状态。 - 连击日志补充来源边界;不同来源不合并,来源不同于当前行动者时注明实际来源,避免持续伤害被误归因。不推测事件未提供的机制原因。 -- 战斗相关测试 `bun run test src/shared/combat-v6 src/shared/engine/combat-v6`:64 文件、737 项通过;日志修正后其 3 项测试另行通过。最终 lint 与双端 build 通过。 -- 全量 `bun run test`:171 文件通过、1 文件失败,1435 项通过、1 项失败(当次新增日志与详情测试前的数量)。唯一失败在未修改的 `src/shared/lib/bodyCultivation/pack.test.ts`:既有用户向文案与旧迁移基线预期不一致;未扩大本轮范围修改该测试。构建现有大包与混合静态/动态导入警告保留。 +- 战斗相关测试 `bun run test packages/shared/src/combat-v6 packages/shared/src/engine/combat-v6`:64 文件、737 项通过;日志修正后其 3 项测试另行通过。最终 lint 与双端 build 通过。 +- 全量 `bun run test`:171 文件通过、1 文件失败,1435 项通过、1 项失败(当次新增日志与详情测试前的数量)。唯一失败在未修改的 `packages/shared/src/lib/bodyCultivation/pack.test.ts`:既有用户向文案与旧迁移基线预期不一致;未扩大本轮范围修改该测试。构建现有大包与混合静态/动态导入警告保留。 ### 2026-09-16:阶段三满编复验 diff --git a/docs/combat-v6-wild-seeking.md b/docs/combat-v6-wild-seeking.md index 75f573af..9c0ddcd8 100644 --- a/docs/combat-v6-wild-seeking.md +++ b/docs/combat-v6-wild-seeking.md @@ -8,7 +8,7 @@ - 当前开放 10 个栖息地,覆盖已有 18 个物种。每次随机组成 1–3 只编组,物种可重复。分布优先遵循生活习性,再兼顾携带境界。 - 每只灵兽暂定 5% 概率为 0 级幼崽;移除旧每日 20 次上限,以灵气消耗约束寻觅。两项属于本次实施默认值,可继续调优。 - 成年五维额外点数先均衡分配,再在均值 ±30% 的整数边界内转移,总量不变。幼崽无额外加点。资质、成长、出生技能沿用灵兽内容包。 -- 区域配置:`src/shared/engine/combat-v6/wild/data/wild.json`;统一费用:`src/shared/config/qiSystem.ts`;掉落:`src/shared/rewards/data/wild.json`。 +- 区域配置:`packages/shared/src/engine/combat-v6/wild/data/wild.json`;统一费用:`packages/shared/src/config/qiSystem.ts`;掉落:`packages/shared/src/rewards/data/wild.json`。 ## 栖息地分布 @@ -60,7 +60,7 @@ - 本地页面完成连续两次寻觅:天地灵气 200 → 198 → 196,编组由三只变为一只;刷新保留原结果且不再扣费。 - 开战沿用 7 级岩甲猪,在战斗中捕获成功并结算;只读比对入库个体和寻觅记录,全部字段一致。结束战斗后回到可再次寻觅状态。 - 已检查桌面和 360px 布局、常用地图入口。灵气不足与并发事务保护经代码审查;未做故障注入或并发压力验证。 -- `bun run lint`、`bun run build`、野外及野外掉落的聚焦测试通过。全量测试 1504 项通过、1 项失败:既有 `src/shared/lib/bodyCultivation/pack.test.ts` 的描述文案基线与当前配置不一致,本次未修改该领域。 +- `bun run lint`、`bun run build`、野外及野外掉落的聚焦测试通过。全量测试 1504 项通过、1 项失败:既有 `packages/shared/src/lib/bodyCultivation/pack.test.ts` 的描述文案基线与当前配置不一致,本次未修改该领域。 ## 栖息地扩展验证(2026-09-17) diff --git a/docs/combat-v6-wuxiang-tianji-redesign.md b/docs/combat-v6-wuxiang-tianji-redesign.md index 3b40984c..993b3366 100644 --- a/docs/combat-v6-wuxiang-tianji-redesign.md +++ b/docs/combat-v6-wuxiang-tianji-redesign.md @@ -275,7 +275,7 @@ C所载「琢磨」基础治疗+12%尚不能确认当前是否仍在。和旧厉 ### 本轮验证结果 - `bun run test --testTimeout=30000`:205个文件、1,930项通过。 -- 最终补充封抗进入实际公式的修正后,运行 `bun run test src/shared/engine/combat-v6/core src/shared/engine/combat-v6/content src/shared/engine/combat-v6/rules-daoyou src/shared/engine/combat-v6/projection`:27个文件、253项通过。 +- 最终补充封抗进入实际公式的修正后,运行 `bun run test packages/shared/src/engine/combat-v6/core packages/shared/src/engine/combat-v6/content packages/shared/src/engine/combat-v6/rules-daoyou packages/shared/src/engine/combat-v6/projection`:27个文件、253项通过。 - `bun run lint`、`bun run build`、`git diff --check`通过。构建保留大体积chunk提示,无构建错误。 - 本轮没有修改页面结构,未进行浏览器实测。技能、资源、流派名称由现有内容驱动展示。 diff --git a/docs/combat-v6-youdu-meridian-alignment.md b/docs/combat-v6-youdu-meridian-alignment.md index 5e8c99a8..609592fa 100644 --- a/docs/combat-v6-youdu-meridian-alignment.md +++ b/docs/combat-v6-youdu-meridian-alignment.md @@ -139,7 +139,7 @@ ## 验证记录 - `bun run test --testTimeout=30000`:204个文件、1893项通过。初次默认5秒时限有三项试炼塔矩阵超时,未更改测试断言或项目超时配置。 -- 最后补充生杀保护分摊及倒地冷却边界后,`bun run test src/shared/engine/combat-v6/content src/shared/engine/combat-v6/projection/assembly-baseline.test.ts`:20个文件、160项通过,其中幽都经脉行为测试27项。 +- 最后补充生杀保护分摊及倒地冷却边界后,`bun run test packages/shared/src/engine/combat-v6/content packages/shared/src/engine/combat-v6/projection/assembly-baseline.test.ts`:20个文件、160项通过,其中幽都经脉行为测试27项。 - `bun run lint`、`bun run build`(客户端及服务端):通过。 - `git diff --check`:通过。 - 页面未修改组件,已检查 `MeridianEditor` 使用通用连线与自动奖励字段;本次未做浏览器游戏页面手动验收,也未写入角色数据。 diff --git a/docs/combat-v6-youdu-restoration.md b/docs/combat-v6-youdu-restoration.md index 08fd1b85..99e4ad00 100644 --- a/docs/combat-v6-youdu-restoration.md +++ b/docs/combat-v6-youdu-restoration.md @@ -54,7 +54,7 @@ ## 实现及验证 -内容入口:`src/shared/engine/combat-v6/content/data/youdu-combat.json`、`youdu-paths.json`、`skill-learning.json`。引擎增加通用的受封乘法因子、宗门状态来源过滤、状态维持费和持续伤害上限,不在 core 内写幽都 ID 分支。 +内容入口:`packages/shared/src/engine/combat-v6/content/data/youdu-combat.json`、`youdu-paths.json`、`skill-learning.json`。引擎增加通用的受封乘法因子、宗门状态来源过滤、状态维持费和持续伤害上限,不在 core 内写幽都 ID 分支。 已验证抗封概率乘法、5/6 回合锢魂、隐身/看破与禁施法、维持费、复活及增攻降防、宗门增益驱散、毒上限与战局快照恢复。投影输入夹具不变,更新内容改动造成的哈希期望。 diff --git a/docs/cooperative-hunts.md b/docs/cooperative-hunts.md index 5c3b4258..95124d73 100644 --- a/docs/cooperative-hunts.md +++ b/docs/cooperative-hunts.md @@ -24,7 +24,7 @@ - 每六小时一轮,北京时间每日 0、6、12、18 点刷新,到下一轮统一消失。各境界同时出现一个目标,共七个;八个 BOSS 类型随轮次轮换,每个境界八轮内遍历全部类型,地点目前固定。 - 每个目标采用对应境界中期等级。首次胜利的修为、灵石、感悟复用 BOSS 境界中期挂机历练 6 小时的计算器,保留随机浮动,每位角色独立抽取;不消费实际挂机时长。奖励档位不读取领取者境界,低境界角色挑战高境界目标不会降档,高境界角色挑战低境界目标不会升档。感悟沿用挂机的固定范围,灵印、灵露沿用通用概率池。 -- 通用掉落配置在 `src/shared/rewards/data/hunt.json`:保底 2 件 `material.v1`,品质按 BOSS 境界使用 `hunt-material-quality.ts` 的讨伐分布,范围与副本一致;独立 20% 概率掉落 1 枚传承灵印、20% 概率掉落 1 瓶归元灵露。灵印池 version 2 显式配置当前开放的 67 种技能(普通 34 种、上品 33 种),每种普通权重 4、上品权重 1;有奖励资格的单次胜利掉落上品灵印的概率约 3.91%。灵露池普通/上品权重为 3:1。这些数量和概率为本次初始默认值。 +- 通用掉落配置在 `packages/shared/src/rewards/data/hunt.json`:保底 2 件 `material.v1`,品质按 BOSS 境界使用 `hunt-material-quality.ts` 的讨伐分布,范围与副本一致;独立 20% 概率掉落 1 枚传承灵印、20% 概率掉落 1 瓶归元灵露。灵印池 version 2 显式配置当前开放的 67 种技能(普通 34 种、上品 33 种),每种普通权重 4、上品权重 1;有奖励资格的单次胜利掉落上品灵印的概率约 3.91%。灵露池普通/上品权重为 3:1。这些数量和概率为本次初始默认值。 - 基础资源直接入账,遵循现有上限,结算页显示实际增量;道具通过奖励邮件送达,领取进入统一库存,背包满不阻塞战斗结算。 - 角色以当前持久气血、法力参战,入场前补算正常的自然恢复;战斗期间暂停场外恢复,战后保存剩余气血、法力。倒地沿用其他持久战斗的战后 1 气血规则。灵兽寿元仍不受影响。 - 队伍胜利时,只有终局仍存活且未离场的角色算个人挑战成功并领取奖励;终局倒地者不领取资源或道具,也不占用本次出现的领奖机会,养伤后可再战。战中倒地但终局前被救起者可正常获奖。 @@ -49,7 +49,7 @@ 讨伐服务和材料预览共用 `hunt-material-quality.ts`。只有讨伐启用严格品质选材;云游和挂机原来的选材逻辑不变。旧战局沿用已生成的奖励事实。 -验证:`bun run test src/shared/rewards/hunt-material-quality.test.ts src/shared/rewards/hunt.test.ts src/shared/rewards/dungeon-material-quality.test.ts` 共 22 项通过,覆盖归一化、品质范围、七个境界的累计概率、两件材料前提和高品质不倒挂。`bun run lint`、`bun run build`、修改代码的 Prettier 检查和 `git diff --check` 通过。本地材料库只读查询确认所有品质均有已发布材料;未新增服务/数据库单测,未执行本轮浏览器胜利发奖、生产材料库检查或全量单测,尚未部署。 +验证:`bun run test packages/shared/src/rewards/hunt-material-quality.test.ts packages/shared/src/rewards/hunt.test.ts packages/shared/src/rewards/dungeon-material-quality.test.ts` 共 22 项通过,覆盖归一化、品质范围、七个境界的累计概率、两件材料前提和高品质不倒挂。`bun run lint`、`bun run build`、修改代码的 Prettier 检查和 `git diff --check` 通过。本地材料库只读查询确认所有品质均有已发布材料;未新增服务/数据库单测,未执行本轮浏览器胜利发奖、生产材料库检查或全量单测,尚未部署。 ## 八类目标 @@ -87,19 +87,19 @@ - 高防不等于免伤。按合法同境界输出构筑核对,高物抗或高法抗承受的对应基础伤害不足普通首领的四分之一,另一伤害类型则更有效;固定伤害保留优势。妖貂可将参考物理构筑命中压到现有公式下限 45%,法术不受该闪避影响。 - 已开始的战斗沿用冻结的属性和技能;新增的凶袭、妖火只用于新战局,旧战局仍能使用原有普攻和妖焰。没有改动其他玩法的预设怪物或通用伤害公式。 -内容位于 `src/shared/engine/combat-v6/hunts/content.ts`,不修改 V6 核心。出现与招募规则位于 `src/shared/hunts`。 +内容位于 `packages/shared/src/engine/combat-v6/hunts/content.ts`,不修改 V6 核心。出现与招募规则位于 `packages/shared/src/hunts`。 ### 本轮平衡验证(2026-09-30) - 参考角色复用合法的六维加点、当前可获得的普通道装、同境界宗门修行与炼体、普通灵兽;无额外幻境祝福、稀有装备或经脉加成。运行时怪物不会读取这些参考构筑。 - 覆盖七个境界、八类首领、三种人数与三个随机种子的 504 场完整战斗。队伍包含治疗,物抗怪与闪避怪选用法术主攻;采用先清随从、及时疗伤、古魔标记时防御的策略。503 场胜利;金丹双人队挑战血河妖蟒的一个种子在第 45 回合败北。三、四人样本全部胜利,双人每种目标至少 2/3 样本胜利;测试限制双人战斗不超过 50 回合、三/四人不超过 30 回合。此结果不是生产玩家胜率,也不保证任意阵容挂机通过。 - 672 组首轮爆发检查覆盖各首领、境界、人数、首领/精英目标和物理/法术输出。参考角色设为全暴击、物理全命中、1.1 倍伤害浮动;目标不防御,首领与精英均能存活。普通小怪允许被集中输出击杀。 -- `bun run test src/shared/engine/combat-v6/hunts src/shared/engine/combat-v6/rules-daoyou src/shared/engine/combat-v6/projection src/shared/combat-v6 src/shared/hunts`:18 个文件、237 项测试通过,包括真实角色属性下的特色强弱项、伤害下限、168 种八人敌阵、旧战局指令兼容和确定性恢复。 +- `bun run test packages/shared/src/engine/combat-v6/hunts packages/shared/src/engine/combat-v6/rules-daoyou packages/shared/src/engine/combat-v6/projection packages/shared/src/combat-v6 packages/shared/src/hunts`:18 个文件、237 项测试通过,包括真实角色属性下的特色强弱项、伤害下限、168 种八人敌阵、旧战局指令兼容和确定性恢复。 - `bun run lint`、`bun run build`(前后端类型检查及构建)、修改代码的 Prettier 检查与 `git diff --check` 通过。未运行仓库全量测试,未改通用战斗公式;本轮未做真人联机、浏览器战斗或生产验收,未部署。 ### 八类目标与八人敌阵验证(2026-09-30) -- 聚焦命令 `bun run test src/shared/hunts src/shared/engine/combat-v6/hunts src/shared/rewards/hunt.test.ts src/shared/combat-v6/arena.test.ts src/shared/engine/combat-v6/rules-daoyou`:70 项通过。覆盖八类目标 × 七个境界 × 三种人数的 168 种阵容、V6 指令提交和首轮结算、五类属性弱点、全部类型的快照恢复、双虎同伴阵亡后不护小怪、新旧出现 ID 及全境界轮换。 +- 聚焦命令 `bun run test packages/shared/src/hunts packages/shared/src/engine/combat-v6/hunts packages/shared/src/rewards/hunt.test.ts packages/shared/src/combat-v6/arena.test.ts packages/shared/src/engine/combat-v6/rules-daoyou`:70 项通过。覆盖八类目标 × 七个境界 × 三种人数的 168 种阵容、V6 指令提交和首轮结算、五类属性弱点、全部类型的快照恢复、双虎同伴阵亡后不护小怪、新旧出现 ID 及全境界轮换。 - `bun run lint`、`bun run build`(含前后端 TypeScript 检查)、`git diff --check` 通过。构建仍提示分块大小及静态/动态导入混用,未扩展处理无关打包配置。 - 本地 local1、local2 通过页面创建队伍、快速匹配、准备并开战,战局 `3b2f3af4-d839-4d14-b2f0-41a0a11bc632`,金丹期吞霞灵蟾。桌面和 360px 均完整显示八个敌人,首轮战报确认 BOSS、精英、四个近战小怪和两个术士均出招,目标分散到两位玩家。 - 使用原有炼气/筑基测试角色,未临时提高属性;第二回合落败并正常结算,双方随后退出队伍。无遗留测试战斗或队伍,检查的浏览器错误日志为空。内置浏览器一度点击无响应,换新标签后完成验证。 @@ -107,7 +107,7 @@ ## 状态与结算边界 -六小时刷新验证(2026-09-30):`bun run test src/shared/hunts src/shared/rewards/hunt.test.ts src/shared/engine/combat-v6/hunts src/shared/combat-v6/arena.test.ts` 共 59 项通过,覆盖北京时间四个刷新点、窗口末毫秒、事件身份换轮、v1/v2 原时间和目标,以及七个 BOSS 境界的六小时资源收益。奖励生成、材料选取、预览和结算调用链经检查均按目标境界确定奖励;领取者境界只用于自身修行进度初始化,不用于重算奖励。`bun run lint`、`bun run build`、修改代码的 Prettier 检查和 `git diff --check` 通过。浏览器验证 `hunt-v3-82907-0` 的 00:00 到期提示、创建与退出队伍,控制台无错误;未重跑胜利发奖、全量单测或生产验证。测试队伍已退出,截图为 `/tmp/daoyou-hunt-connection/six-hour-cycle.png`。 +六小时刷新验证(2026-09-30):`bun run test packages/shared/src/hunts packages/shared/src/rewards/hunt.test.ts packages/shared/src/engine/combat-v6/hunts packages/shared/src/combat-v6/arena.test.ts` 共 59 项通过,覆盖北京时间四个刷新点、窗口末毫秒、事件身份换轮、v1/v2 原时间和目标,以及七个 BOSS 境界的六小时资源收益。奖励生成、材料选取、预览和结算调用链经检查均按目标境界确定奖励;领取者境界只用于自身修行进度初始化,不用于重算奖励。`bun run lint`、`bun run build`、修改代码的 Prettier 检查和 `git diff --check` 通过。浏览器验证 `hunt-v3-82907-0` 的 00:00 到期提示、创建与退出队伍,控制台无错误;未重跑胜利发奖、全量单测或生产验证。测试队伍已退出,截图为 `/tmp/daoyou-hunt-connection/six-hour-cycle.png`。 - 出现 ID 由版本、轮次与境界确定,所有实例生成相同 ID。六小时刷新使用 `hunt-v3`;`hunt-v1` 保留原三类映射,`hunt-v2` 保留八类映射,两者都按原两小时周期解析,避免旧传闻和已建队伍的目标、到期时间被重新解释。切换版本的当轮,新旧出现各自保持独立领奖资格;已经创建的战局沿用冻结阵容,新开战使用八人敌阵。传闻使用确定性消息 ID 与 Redis 去重标记。 - Redis 保存队伍、账号归属、事件索引,短期互斥锁配合 Lua revision 检查原子更新。当前实现的队伍互斥锁是全局锁,尚未做高并发容量验收。 @@ -144,9 +144,9 @@ bun --env-file=env/local.env node_modules/drizzle-kit/bin.cjs migrate - 重复助战战局:`6859680a-1366-4303-a807-6d7334f59f27`。双方均显示「助战完成,本次不重复获得奖励」,余额及两条领奖记录数量不变。 - 已检查桌面、360px 组队布局及过期事件链接;检查的浏览器控制台没有错误。内置浏览器旧标签曾出现点击无响应,使用新标签后完成流程;Chrome 流程正常。 - 为快速验证胜利结算,曾通过正式本地 dev 接口临时提高 local1 三项属性;结束后均恢复为 61。保留实际玩法奖励,测试队伍已退出,没有遗留测试战斗。 -- 聚焦测试命令 `bun run test src/shared/hunts src/shared/engine/combat-v6/hunts src/shared/combat-v6/arena.test.ts`:27 项通过,覆盖准入、到期边界、匹配、三类机制和快照恢复。 +- 聚焦测试命令 `bun run test packages/shared/src/hunts packages/shared/src/engine/combat-v6/hunts packages/shared/src/combat-v6/arena.test.ts`:27 项通过,覆盖准入、到期边界、匹配、三类机制和快照恢复。 - `bun run lint`、`bun run build`(含前后端 TypeScript 检查)、`git diff --check` 通过。 -- 完整 `bun run test`:2288 项通过,2 项失败,分别为未修改文件 `src/shared/forging/forging.test.ts` 的材料堆叠预期、`src/shared/engine/combat-v6/content/skill-learning.test.ts` 的内容哈希预期。未在独立基线检出中复现,因此不把它们标为已确认的基线失败。 +- 完整 `bun run test`:2288 项通过,2 项失败,分别为未修改文件 `packages/shared/src/forging/forging.test.ts` 的材料堆叠预期、`packages/shared/src/engine/combat-v6/content/skill-learning.test.ts` 的内容哈希预期。未在独立基线检出中复现,因此不把它们标为已确认的基线失败。 - 未做真人三/四人、已领奖与未领奖者混合队、跨到期仍在战斗、服务中断恢复或高并发的浏览器验收;相关分支已做代码检查,人数和到期纯规则由单测覆盖。未验证全境界数值平衡。 浏览器证据保存在本次任务产物 `/tmp/daoyou-hunt-proof/`,不作为仓库测试脚本或长期资源提交。 @@ -159,7 +159,7 @@ bun --env-file=env/local.env node_modules/drizzle-kit/bin.cjs migrate - 重复助战战局 `d0b32d78-6b6b-459f-b918-3c803f00c06c` 胜利后显示不重复获得奖励;两位角色的资源、奖励快照、邮件 ID 均与首次胜利后相同,仍为两条领奖记录、两封奖励邮件。 - 结算详情使用弹窗,避免战斗指令栏溢出;桌面与 360px 手机视口均已检查,浏览器错误日志为空。证据为 `rewards-desktop.png`、`rewards-mobile.png`。 - 测试队伍已全部退出,战斗已结束。local1 临时提高的气血、力量、速度属性均恢复为 61,保留正常玩法所得资源与道具。 -- 聚焦命令 `bun run test src/shared/rewards/hunt.test.ts src/shared/hunts src/shared/engine/combat-v6/hunts src/shared/rewards/yield.test.ts src/shared/rewards/dungeon-material-quality.test.ts`:35 项通过,覆盖七个境界的六小时收益、材料品质范围、保底与概率组独立性、非法配置以及讨伐机制。 +- 聚焦命令 `bun run test packages/shared/src/rewards/hunt.test.ts packages/shared/src/hunts packages/shared/src/engine/combat-v6/hunts packages/shared/src/rewards/yield.test.ts packages/shared/src/rewards/dungeon-material-quality.test.ts`:35 项通过,覆盖七个境界的六小时收益、材料品质范围、保底与概率组独立性、非法配置以及讨伐机制。 - `bun run lint`、`bun run build` 和 `git diff --check` 通过。此次未重跑完整测试集,上一轮完整测试结果见上节。 - 未进行混合领奖资格队伍、资源上限、满背包、消息故障重试的运行验收;对应事务、去重、邮件发放和上限路径已做代码检查。概率和数量仍为待平衡的初始配置。 @@ -177,7 +177,7 @@ bun --env-file=env/local.env node_modules/drizzle-kit/bin.cjs migrate - 事件 `hunt-v1-248717-0` 首胜战局 `c003a0ec-930f-4a37-b318-958dbcfa6727`:两位角色各生成一条日志凭据和一封邮件。local1 实际灵石 +3364、修为 +159、感悟 +12;local2 实际灵石 +3839、修为 +182、感悟 +12。战果接口从日志返回完整奖励及邮件 ID,公开日志不返回私有 result。 - 同事件助战战局 `98fa29ca-69e3-4d47-9377-9cd741208e7e`:双方均返回 assisting,首胜日志和私有奖励快照完全未变,仍只有两封奖励邮件,余额与首胜实际增量一致。队伍列表也显示已获酬/助战资格。 - local1 领取讨伐邮件后另有一条道具日志(碧鳞蛇蜕 +1、紫晶矿 +1),胜利结算不提前记录邮件道具。双方已退出测试队伍,临时境界与属性通过本地 dev 接口恢复,保留正常奖励。 -- `bun run lint`、`bun run build`、`git diff --check` 通过;`bun run test src/shared/playerJournal.test.ts src/shared/rewards/hunt.test.ts src/shared/hunts src/shared/engine/combat-v6/hunts` 共 28 项通过。未重复跑全量测试,未实测消息故障重投、资源全部封顶及混合领奖资格队伍;这些分支完成事务和唯一键检查。 +- `bun run lint`、`bun run build`、`git diff --check` 通过;`bun run test packages/shared/src/playerJournal.test.ts packages/shared/src/rewards/hunt.test.ts packages/shared/src/hunts packages/shared/src/engine/combat-v6/hunts` 共 28 项通过。未重复跑全量测试,未实测消息故障重投、资源全部封顶及混合领奖资格队伍;这些分支完成事务和唯一键检查。 ## 讨伐持久状态与个人奖励(2026-09-30) @@ -188,7 +188,7 @@ bun --env-file=env/local.env node_modules/drizzle-kit/bin.cjs migrate 本轮验证: -- `bun run test src/shared/hunts src/shared/combat-v6/arena.test.ts src/shared/engine/combat-v6/projection src/shared/engine/combat-v6/wild`:11 个文件、93 项测试通过。`bun run lint`、`bun run build`(含 TypeScript)、`git diff --check` 通过。 +- `bun run test packages/shared/src/hunts packages/shared/src/combat-v6/arena.test.ts packages/shared/src/engine/combat-v6/projection packages/shared/src/engine/combat-v6/wild`:11 个文件、93 项测试通过。`bun run lint`、`bun run build`(含 TypeScript)、`git diff --check` 通过。 - 本地 API `127.0.0.1:3001`、Vite `127.0.0.1:5174`,local1/local2 使用独立浏览器会话。通过正式本地准备接口临时调整属性与资源,仅为验收结算,不用此战绩评估平衡。 - 第一场 `81c4e72c-3294-408d-a529-eeb2ca8b96ac`:入场分别为气血 10049/24753、6/955,法力 638/50525、8/850。local2 首回合倒地,队伍第 10 回合获胜。local1 正常领奖,local2 显示落败与未领取报酬,数据库仅有 local1 的领奖凭据。最终持久资源分别为 8498/638、1/8,与战内终局及战后濒死规则一致。验收期间本地开发服务中断,恢复服务并刷新后继续完成战斗。 - 第二场 `d62b8524-66cc-460f-bfdc-5f76acf07143`:同一次出现,local2 经本地准备接口恢复资源后再次参战,两人存活获胜。local2 首次领奖成功;local1 显示助战,不新增领奖凭据。数据库两条凭据分别指向各自首次成功的战斗。终局资源 8805/722、14692/353 均正确保存,覆盖助战和施法消耗。 diff --git a/docs/daily-divination.md b/docs/daily-divination.md index f0545b16..2457b07e 100644 --- a/docs/daily-divination.md +++ b/docs/daily-divination.md @@ -5,7 +5,7 @@ - 独立页面 `/game/divination`,洞府「每日占卜」与扩展导航进入。 - 每个活跃角色北京时间每自然日一次,不消耗天地灵气。服务器确定日期与三枚独立、公平的六面骰,客户端只播放演出。 - 八个所问方向:炼器、炼丹、灵兽培养、闭关、突破、秘境探索、野外寻觅、灵田照料。 -- 26 个固定卦象定义在 `src/shared/lib/divination.ts`。忽略骰子顺序,优先匹配六种三同号、四种顺子、六种对子,其余16种散点组合映射到十种卦象;概率并非均等。 +- 26 个固定卦象定义在 `packages/shared/src/lib/divination.ts`。忽略骰子顺序,优先匹配六种三同号、四种顺子、六种对子,其余16种散点组合映射到十种卦象;概率并非均等。 - 通常赠小聚灵符一张(使用恢复50点灵气);只有6、6、6赠大聚灵符一张(恢复200点),替代小符,概率1/216。 - 签意仅供趣味解读,不参与真实玩法数值计算。获得符箓不消耗使用次数;实际使用沿用每日3次和2400点溢出上限。 @@ -32,7 +32,7 @@ AI使用 `streamAiText`、`daily-divination` 提示词与现有BYOK校验。30 React负责案台内的方向选择与可访问点击层、结果文字和SSE状态;Phaser按需加载,负责案台背景与三骰演出。动画结束展示服务端点数。离开页面销毁Phaser并取消请求;减少动态效果偏好下直接显示结果。手机未占卜时使用 2:3 竖版案台与更大的骰子,解签后收为 3:2 横版场景并在下方阅读签文;宽屏沿用横版并列签文。初始在案台左上角用下拉选择所问方向,案台内提示「轻触案台 · 掷骰占卜」,不再在游戏下方放开始按钮;掷骰后收起方向选择,展开卦象与签文,正常完成自动发奖,只展示领取结果。中断后在签文下方继续解签或领取,领取不依赖 Phaser 加载成功。案台按视口高度限制首屏占用,保持整幅画面等比显示。ResizeObserver 先调用 Phaser getParentBounds 更新父容器尺寸,再 refresh,保证进入签文双栏时画布、背景与骰子同步缩放。 -生产素材:`public/assets/divination/qiantai-v1.jpg`,1536×1024,约581KiB,作为Phaser纹理。由内置imagegen生成PNG后编码为JPEG;未使用CLI/API生成。骰子面由代码绘制以保证点数准确。 +生产素材:`apps/web/public/assets/divination/qiantai-v1.jpg`,1536×1024,约581KiB,作为Phaser纹理。由内置imagegen生成PNG后编码为JPEG;未使用CLI/API生成。骰子面由代码绘制以保证点数准确。 生成提示词: @@ -40,7 +40,7 @@ React负责案台内的方向选择与可访问点击层、结果文字和SSE状 ## 本轮验证(2026-09-19,本地) -- `bun run test src/shared/lib/divination.test.ts src/shared/lib/qi.test.ts`:21项通过;遍历216种骰子结果,验证26卦全部可达、排列不改变结果、大奖唯一、非法点数拒绝、北京时间换日及符箓协议。 +- `bun run test packages/shared/src/lib/divination.test.ts packages/shared/src/lib/qi.test.ts`:21项通过;遍历216种骰子结果,验证26卦全部可达、排列不改变结果、大奖唯一、非法点数拒绝、北京时间换日及符箓协议。 - `bun run lint`、`bun run build`通过;客户端构建仍有现有分包体积与动态/静态混用导入警告。 - 用本地配置执行Drizzle业务迁移;未迁移预发布或生产。 - 浏览器在本地道友1选择灵兽培养,得到2、2、4与「双镜照心」;真实AI输出222字,`fallback=false`,小聚灵符由0变1。 @@ -53,7 +53,7 @@ React负责案台内的方向选择与可访问点击层、结果文字和SSE状 - 名称统一为「每日占卜」,洞府入口用 GameIcon 展示骰子图标。删除页面规则、免责声明和氛围填充文案,保留状态反馈和必要的恢复操作。 - 领取失败根因为该角色背包中有四种已不在注册表中的旧材料;原 grantInventory 会解析全部背包物品,导致无关旧物品触发「未知物品定义」。现仅解析可参与本次奖励堆叠的物品,所有原有背包格位仍参与占位,旧物品不被修改或删除。新增奖励和相关堆叠仍严格校验。 -- 新增纯库存规划回归用例,覆盖保留格位、满包转储藏室、禁止溢出时报错和已有奖励堆叠。`bun run test src/shared/inventory src/shared/lib/divination.test.ts`:32 项通过。 +- 新增纯库存规划回归用例,覆盖保留格位、满包转储藏室、禁止溢出时报错和已有奖励堆叠。`bun run test packages/shared/src/inventory packages/shared/src/lib/divination.test.ts`:32 项通过。 - lint 和前后端构建通过;仍有既有分包警告。 - 已只读保存本地道友2失败记录与背包基准;浏览器领取验收被 Chrome 的扩展界面阻挡,本轮尚未确认实际领取结果和新版页面的视觉表现。没有通过数据库改写发奖状态或补发物品。 @@ -75,7 +75,7 @@ React负责案台内的方向选择与可访问点击层、结果文字和SSE状 手机初始画面采用独立竖版背景,保留方向选择与点击层。React 根据阶段切换容器比例,Phaser 根据实际容器横竖比例切换逻辑画布(960×1440 / 960×640)、背景纹理与骰子容器位置/缩放。骰子动画在容器局部坐标内运行,尺寸变化时无需重置点数或重建玩法。横版仍保留桌面布局。 -新增生产素材 `public/assets/divination/qiantai-mobile-v1.jpg`,1024×1536、约582KiB。使用内置 image_gen,以原横版背景为参考重新构图,随后用 sips 编码 JPEG;未使用 CLI/API 生成。原横版素材保留。 +新增生产素材 `apps/web/public/assets/divination/qiantai-mobile-v1.jpg`,1024×1536、约582KiB。使用内置 image_gen,以原横版背景为参考重新构图,随后用 sips 编码 JPEG;未使用 CLI/API 生成。原横版素材保留。 生成提示词: @@ -87,7 +87,7 @@ React负责案台内的方向选择与可访问点击层、结果文字和SSE状 首页 HomeAside 的「今日天机」区域替换为 DailyDivinationEntry,读取当前活跃角色真实占卜状态。未占卜时提示「今日尚未占卜」并提供「去占卜」;已占卜时回显预设卦名与卦辞,提供「查看解签」。未完成解读或发奖时分别提供「继续解签」「领取签礼」,跨日未结算记录标为「上次占卜待完成」。读取失败时保留进入占卜的入口,不展示随机卦象或错误地标为未占卜。 -组件随角色 ID 重新挂载;页面返回、窗口聚焦、标签页重新可见、北京时间换日时重新读取,不缓存每日状态。占卜 HTTP/SSE 客户端移至 `src/react-app/lib/divinationApi.ts` 供首页与占卜页复用。删除首页不再使用的 DivineFortune 组件与 useDivineFortune hook。 +组件随角色 ID 重新挂载;页面返回、窗口聚焦、标签页重新可见、北京时间换日时重新读取,不缓存每日状态。占卜 HTTP/SSE 客户端移至 `apps/web/src/lib/divinationApi.ts` 供首页与占卜页复用。删除首页不再使用的 DivineFortune 组件与 useDivineFortune hook。 浏览器实测已有「双鹭临汀」回显与查看解签跳转;用本地 dev 接口重置后验证「今日尚未占卜」及去占卜跳转,保留已领取符箓。390×844 手机布局可读,无横向溢出。`bun run lint`、`bun run build`、`git diff --check`通过;未新增前后端单元测试,真实跨午夜与角色切换只完成代码审查。 diff --git a/docs/design-qi-system.md b/docs/design-qi-system.md index 21d20833..b35de471 100644 --- a/docs/design-qi-system.md +++ b/docs/design-qi-system.md @@ -239,7 +239,7 @@ CREATE INDEX idx_qi_logs_status_created_at ### 5.3 `QiService` 职责 -新增 `src/server/lib/services/QiService.ts`。 +新增 `apps/api/src/lib/services/QiService.ts`。 核心方法: @@ -433,7 +433,7 @@ class QiService { ## 九、配置设计 -新增 `src/shared/config/qiSystem.ts`,集中维护首版数值: +新增 `packages/shared/src/config/qiSystem.ts`,集中维护首版数值: ```typescript export const QI_MAX = 200; @@ -593,17 +593,17 @@ export interface QiRestoreResult { | 文件 | 操作 | 说明 | |------|------|------| | `drizzle/00xx_qi_system.sql` | 新增 | 灵气字段与日志表 | -| `src/server/lib/drizzle/schema.ts` | 修改 | 增加 Drizzle schema | -| `src/shared/config/qiSystem.ts` | 新增 | 灵气数值配置 | -| `src/shared/contracts/qi.ts` | 新增 | API contract | -| `src/shared/types/qi.ts` | 新增 | 服务与日志类型 | -| `src/server/lib/services/QiService.ts` | 新增 | 灵气核心服务 | -| `src/server/routes/api/cultivator.router.ts` | 修改 | 灵气查询、日志、恢复接口;闭关接入 | -| `src/server/lib/dungeon/service_v2.ts` | 修改 | 副本开始接入灵气 | -| `src/server/lib/dungeon/dungeonLimiter.ts` | 废弃 | 首版不再使用 | -| `src/server/lib/redis/retreatLock.ts` | 保留 | 仅用于闭关/突破并发锁,不再承载每日寿元限制 | +| `apps/api/src/lib/drizzle/schema.ts` | 修改 | 增加 Drizzle schema | +| `packages/shared/src/config/qiSystem.ts` | 新增 | 灵气数值配置 | +| `packages/shared/src/contracts/qi.ts` | 新增 | API contract | +| `packages/shared/src/types/qi.ts` | 新增 | 服务与日志类型 | +| `apps/api/src/lib/services/QiService.ts` | 新增 | 灵气核心服务 | +| `apps/api/src/routes/api/cultivator.router.ts` | 修改 | 灵气查询、日志、恢复接口;闭关接入 | +| `apps/api/src/lib/dungeon/service_v2.ts` | 修改 | 副本开始接入灵气 | +| `apps/api/src/lib/dungeon/dungeonLimiter.ts` | 废弃 | 首版不再使用 | +| `apps/api/src/lib/redis/retreatLock.ts` | 保留 | 仅用于闭关/突破并发锁,不再承载每日寿元限制 | | 造物/炼丹相关服务 | 修改 | 接入预扣、提交、退款 | -| `src/react-app` 相关游戏 UI | 修改 | 灵气展示、消耗预览、恢复入口 | +| `apps/web/src` 相关游戏 UI | 修改 | 灵气展示、消耗预览、恢复入口 | --- diff --git a/docs/development.md b/docs/development.md index 6781cf81..10b86f04 100644 --- a/docs/development.md +++ b/docs/development.md @@ -6,15 +6,15 @@ ```text . -├── src/index.ts # Bun 后端入口,导出 Hono API 与 WebSocket 配置 -├── src/server/ # Hono API、认证、服务层、数据库访问 -├── src/react-app/ # React SPA -├── src/shared/ # 共享引擎、配置、类型、契约 +├── apps/api/src/main.ts # Node/Nest 后端入口与原生 WebSocket 配置 +├── apps/api/src/ # Nest 模块、认证、服务层、数据库访问 +├── apps/web/src/ # React SPA +├── packages/shared/src/ # 共享引擎、配置、类型、契约 ├── drizzle/ # 业务表 Drizzle migrations ├── drizzle-auth/ # Better Auth Drizzle migrations ├── drizzle.auth.config.ts # Better Auth 独立迁移配置 ├── scripts/ # 部署脚本与生产/NATS Compose -├── docker/Dockerfile.app # Bun 主服务镜像 +├── docker/Dockerfile.app # Node/Nest 主服务镜像 └── vite.config.ts ``` @@ -22,9 +22,9 @@ 这个仓库不是 SSR 应用。 -- `src/react-app` 使用 `BrowserRouter` 管理前端路由 -- `src/server/app.ts` 提供 `/api/*` 和 `/internal/*` 接口 -- `src/index.ts` 在生产环境注册 Bun 内置 cron;Cron 只向 NATS WorkQueue 发布后台 command +- `apps/web/src` 使用 `BrowserRouter` 管理前端路由 +- `apps/api/src/app.module.ts`组织提供`/api/*`和`/internal/*`接口的功能模块 +- Nest运行时模块在生产环境注册UTC定时任务;Cron只向NATS WorkQueue发布后台command - 前端 SPA 独立部署到 Cloudflare Pages;后端 Docker 不再服务 `index.html` 或静态资源 当前路由约定: @@ -37,14 +37,15 @@ ## 环境要求 -- `Bun 1.3+` +- `pnpm 10.34.6`(workspace依赖管理;Turborepo编排任务) +- `Node.js 24.18+`(Nest运行时) - `PostgreSQL` - `Redis`:在线对局、邀请、截止时间、恢复索引和 API 部分能力的权威存储 - `NATS`:进程启动硬依赖;JetStream 承载领域事件、异步投影、后台 command、战斗演算指针、终态清理和回放归档,Core 只承载可丢失的跨实例实时提示 说明: -- 仓库脚本默认围绕 `bun` / `bunx` 编写,不建议继续沿用旧的 `npm + Next.js` 使用方式 +- 仓库使用 pnpm workspace 与单一 pnpm-lock.yaml;框架为 NestJS + React SPA - 专用本地开发前端端口是 `5174`,预发布调试默认 `5173` - 构建后服务默认端口是 `3000` @@ -53,10 +54,10 @@ 推荐使用[纯本地开发与预发布隔离](./local-development.md):启停服务、迁移数据库和启动应用分别执行。`dev` 组读取 `env/local.env`,`prd` 组读取 `env/staging.env`;浏览器自动化遵循[测试规范](./testing.md)。 ```bash -bun install -bun run services up -d --wait -bun run db:migrate -bun run dev +pnpm install +pnpm run services up -d --wait +pnpm run db:migrate +pnpm run dev ``` ## 环境变量 @@ -73,14 +74,14 @@ bun run dev | `NATS_SERVERS` | NATS 服务地址,多个地址使用逗号分隔 | | `NATS_USER` / `NATS_PASSWORD` | NATS 应用用户凭据 | -实时战斗由 Bun/Hono 主服务直接承载;生产环境还必须配置: +实时战斗由 Node/Nest 主服务直接承载;生产环境还必须配置: | 变量 | 说明 | | --- | --- | | `REDIS_URL` | 在线对局唯一权威状态、邀请、凭据、截止时间与恢复索引 | | `NATS_SERVERS` / `NATS_USER` / `NATS_PASSWORD` | 战斗演算、终态清理和回放归档使用的 JetStream,以及跨实例状态提示使用的 NATS Core | -客户端通过已认证的 session API 获取 60 秒有效的一次性 WebSocket ticket,不能自行声明玩家身份。实时战斗的数据边界固定为:`battle-v5` 只做确定性规则解析;Bun 主服务负责协议、调度和广播;进行中的权威状态、选招、锁定、邀请、演出战报和回放素材只在 Redis。主服务通过 JetStream 小型指针任务分配统一演算和终态清理;回放归档 consumer 再从 Redis 组装 `BattleReplayV1`,异步、幂等写入 `wanjiedaoyou_battle_replay_archives`。NATS 消息不承载完整 battle save 或战报;玩家 command 链路不查询或写入 PostgreSQL,也不使用 Redis Stream。 +原生WebSocket升级验证Better Auth会话、Origin和参与/观战权限,客户端不能自行声明玩家身份。`combat-v6`共享引擎保持确定性,Nest负责传输与生命周期,现有应用服务保留战斗编排。进行中的V6战斗以Redis为权威,保留CAS与占用锁;终态投影与回放归档继续通过NATS和现有幂等处理写入`combat_replay_archives`/`combat_replay_participants`。框架迁移不更换战斗协议、奖励事务或持久化模型。 ### 建议同时配置 @@ -168,18 +169,18 @@ AI 相关功能支持 DeepSeek 与阿里云百炼(Qwen),统一通过 `aiCl 2. 应用业务表迁移 ```bash -bun run db:migrate +pnpm run db:migrate ``` 说明: -- 本地命令从 `env/local.env` 加载配置;独立的 Drizzle/认证工具需由进程环境或 `bun --env-file=...` 显式提供配置 +- 本地命令从 `env/local.env` 加载配置;独立的 Drizzle/认证工具需由进程环境或 `node --env-file=...` 显式提供配置 - `drizzle/` 目录下已经存在业务表迁移文件 - `drizzle/` 只管理 `wanjiedaoyou_*` 业务表 - `drizzle-auth/` 只管理固定 `better_auth` schema,并使用独立迁移历史表 -- `bun run auth:migrate` 使用 `drizzle.auth.config.ts` 执行认证迁移 -- `bun run auth:generate` 用于认证 Drizzle schema 变更后生成迁移,不是每次启动都要执行 -- 升级部署时先执行 `bun run auth:migrate` 建立认证基线,再部署使用共享 Bun SQL 连接池的新版本 +- `pnpm run auth:migrate` 使用 `drizzle.auth.config.ts` 执行认证迁移 +- `pnpm run auth:generate` 用于认证 Drizzle schema 变更后生成迁移,不是每次启动都要执行 +- 升级部署时先执行 `pnpm run auth:migrate` 建立认证基线,再部署使用共享node-postgres连接池的新版本 ## 本地开发 @@ -189,8 +190,8 @@ bun run db:migrate 4. 启动开发服务器 ```bash -bun run services up -d --wait -bun run dev +pnpm run services up -d --wait +pnpm run dev ``` 专用本地 NATS 监听 `14222`,监控端口为 `18222`;开发凭据与 `env/local.env` 一致。持久数据保存在 `daoyou-local` 专用卷中。 @@ -202,7 +203,7 @@ bun run dev - 前端页面:`http://127.0.0.1:5174` - 健康检查:`http://127.0.0.1:5174/api/health-check` -`bun run dev` 会同时启动 Vite 前端和 Bun/Hono 主服务;Vite 将 `/api`、`/internal` 与 WebSocket 升级代理到 Bun 服务。 +`pnpm run dev` 会同时启动 Vite 前端和 Node/Nest 主服务;Vite 将 `/api`、`/internal` 与 WebSocket 升级代理到Nest服务。 本地 NATS 使用 JetStream 文件卷保存消息和 durable consumer 的投递位点。启动时发现历史消息是预期行为;回放归档和事务消息会在 PostgreSQL 可用后继续消费。若 PostgreSQL 暂时不可用,事务消息恢复器会以 5 秒至 60 秒退避重试,避免连接超时期间持续打满连接池;不应通过删除 NATS 数据卷来规避数据库故障。 @@ -210,27 +211,28 @@ bun run dev | 命令 | 作用 | | --- | --- | -| `bun run dev` | 启动 Vite 与 Bun/Hono 主服务 | -| `bun run dev:api` / `dev:web` | 独立启动本地 API/Web | -| `bun run prd` / `prd:api` / `prd:web` | 使用预发布配置启动两者/仅 API/仅 Web | -| `bun run build` | 依次构建前端与服务端 | -| `bun run build:client` | 构建 Cloudflare Pages 使用的前端 SPA | -| `bun run build:server` | 构建 Docker 使用的 Bun/Hono 后端 | -| `bun run lint` | ESLint 检查 | -| `bun run test` | Vitest | -| `bun run services up -d --wait` / `down` | 启停本地依赖服务 | -| `bun run db:migrate` | 使用选定环境依次迁移认证与业务表 | -| `bun run auth:generate` | 生成 `better_auth` Drizzle 迁移 | -| `bun run auth:migrate` | 执行 `better_auth` 独立迁移流 | +| `pnpm run dev` | 启动 Vite 与 Node/Nest 主服务 | +| `pnpm run dev:api` / `dev:web` | 独立启动本地 API/Web | +| `pnpm run prd` / `prd:api` / `prd:web` | 使用预发布配置启动两者/仅 API/仅 Web | +| `pnpm run build` | 通过Turbo构建前端与服务端 | +| `pnpm run build:client` | 构建 Cloudflare Pages 使用的前端 SPA | +| `pnpm run build:server` | 构建 Docker 使用的 Node/Nest 后端 | +| `pnpm run lint` | ESLint 检查 | +| `pnpm run typecheck` | API、Web、共享源码与维护工具类型检查 | +| `pnpm run test` | Vitest | +| `pnpm run services up -d --wait` / `down` | 启停本地依赖服务 | +| `pnpm run db:migrate` | 使用选定环境依次迁移认证与业务表 | +| `pnpm run auth:generate` | 生成 `better_auth` Drizzle 迁移 | +| `pnpm run auth:migrate` | 执行 `better_auth` 独立迁移流 | 构建产物: -- `build:client` 产出前端 SPA -- `build:server` 产出 Bun 运行的 Hono 服务入口 `dist/index.js` +- `build:client`在 `apps/web` 使用Vite,产出SPA至 `apps/web/dist` +- `build:server`在 `apps/api` 执行 `nest build`(CLI 12 ESM Rspack),产出Node运行的Nest服务入口`apps/api/dist/main.js`,需配套生产node_modules ## Docker -React SPA 继续独立部署到 Cloudflare Pages,不进入后端镜像。`app`(`3000`)使用 Bun,同时承载 Hono API 与实时战斗 WebSocket;`battle-v5` 仍是无框架依赖的纯战斗引擎。PostgreSQL 回放归档由应用侧 NATS consumer 完成。 +React SPA继续独立部署到Cloudflare Pages,不进入后端镜像。`app`(`3000`)使用Node 24运行Nest API与原生WebSocket;`combat-v6`保持独立于框架。镜像以非root用户运行,仅包含Node、生产依赖与编译产物。PostgreSQL回放归档继续由应用侧NATS consumer完成。 本地构建镜像: @@ -241,7 +243,7 @@ docker build -t daoyou-app:local -f docker/Dockerfile.app . 运行镜像: ```bash -docker run --rm -p 3000:3000 \ +docker run --rm --stop-timeout 75 -p 3000:3000 \ --env-file /path/to/.env.production \ daoyou-app:local ``` @@ -253,7 +255,9 @@ docker run --rm -p 3000:3000 \ ## 仓库内现成部署脚本 -### Hono API 蓝绿发布 +### Nest API 蓝绿发布 + +标签推送构建镜像时同时写入版本标签、`sha-<完整提交SHA>`和`latest`。部署时指定版本/SHA或镜像digest,并记录上一版本;回滚使用同一脚本传入上一镜像。Nest镜像使用Node健康检查和75秒停机宽限期。若回滚到迁移前的Bun镜像,须同时恢复该版本的Compose健康检查(旧镜像不保证包含Node)。仓库命令切换不代表已经发布生产,当前验收状态见[迁移记录](nestjs-migration.md)。 ```bash APP_IMAGE=swkzymlyy/daoyou-app: \ @@ -265,62 +269,60 @@ ENV_FILE=/root/daoyou/.env.production \ - 在 `daoyou-app-blue` / `daoyou-app-green` 间部署闲置颜色 - 同时验证 Docker health 与宿主机 `/api/health-check` -- 通过 `nginx -t` 后原子切换 OpenResty upstream +- 写入目标 upstream 配置,经 `nginx -t` 校验后 reload OpenResty;校验或 reload 失败时恢复备份 - 短暂 drain 后停止旧颜色容器 -生产 Compose 只定义 `app-blue` 和 `app-green`;实时战斗与 API 随同一 Bun 主服务蓝绿发布; `blue-green-app.sh` 通过 Compose 启动闲置 app profile 并切换 OpenResty。React SPA 仍由 Cloudflare Pages 独立部署。 +生产 Compose 只定义 `app-blue` 和 `app-green`;实时战斗与 API 随同一Node/Nest主服务蓝绿发布; `blue-green-app.sh` 通过 Compose 启动闲置 app profile 并切换 OpenResty。React SPA 仍由 Cloudflare Pages 独立部署。 ## 生产 cron 配置方式 当前仓库默认采用两层设计: -- 生产环境中 `src/index.ts` 注册 Bun 内置 cron,Cron 只发布 JetStream command,durable Worker consumer 执行 job runner +- 生产环境中Nest `CronService`按`apps/api/src/lib/jobs/schedules.ts`注册UTC调度,Cron只发布JetStream command,durable Worker consumer执行job runner - `/internal/cron/*` 仍然保留,便于手动触发、联调,或后续切回外部调度器 -- `GET /internal/cron/auction-expire` -- `GET /internal/cron/rank-rewards` -- `GET /internal/cron/market-refresh` -- `GET /internal/cron/tower-enemy-sets` -- `GET /internal/cron/player-state-events-cleanup` -- `GET /internal/cron/expired-data-cleanup` +当前十项调度与手动入口如下。HTTP入口均为`GET /internal/cron/<路径>`;频率以`apps/api/src/lib/jobs/schedules.ts`为准,入口以`apps/api/src/runtime/internal-cron.controller.ts`为准。 -当前内置调度频率: - -- `auction-expire`:每 2 分钟 -- `rank-rewards`:每天 `00:00 Asia/Shanghai` -- `market-refresh`:每 5 分钟 -- `tower-enemy-sets`:每小时 -- `player-state-events-cleanup`:每天 `02:30 Asia/Shanghai` -- `expired-data-cleanup`:每天 `02:45 Asia/Shanghai` +| HTTP路径 | 后台command | 频率(Asia/Shanghai) | +| --- | --- | --- | +| `auction-expire` | `auction.expire` | 每2分钟 | +| `rank-rewards` | `ranking.rewards.distribute` | 每天00:00 | +| `market-refresh` | `market.refresh` | 每5分钟 | +| `resource-replay-cleanup` | `resource-replay.cleanup` | 每天02:30 | +| `expired-data-cleanup` | `expired-data.cleanup` | 每天02:45 | +| `material-library-daily-generation` | `material-library.generate` | 每天01:00 | +| `sponsorship-reconcile` | `sponsorship.reconcile` | 每10分钟 | +| `sponsorship-deep-reconcile` | `sponsorship.deep-reconcile` | 每天03:15 | +| `sponsorship-cleanup` | `sponsorship.cleanup` | 每天03:30 | +| `sponsorship-admin-digest` | `sponsorship.admin-digest` | 每天09:00 | 说明: -- Bun 内置 cron 仍运行在 Web 进程内,但实际任务已与调度回调解耦;发布成功的 command 由 JetStream 持久化并可跨应用重启继续执行 -- Bun 的 cron 表达式按 `UTC` 解释,所以 `rank-rewards` 在代码里配置为 `0 16 * * *`,对应北京时间次日 `00:00` +- Nest Schedule仍运行在Web进程内,但实际任务已与调度回调解耦;发布成功的 command 由 JetStream 持久化并可跨应用重启继续执行 +- 调度显式使用`UTC`时区,所以 `rank-rewards` 在代码里配置为 `0 16 * * *`,对应北京时间次日 `00:00` - 内置调度不直接调用 job runner,也不走 HTTP;它发布 `daoyou.command.cron.>` command - `/internal/cron/*` 接口继续要求 `Authorization: Bearer ${CRON_SECRET}`,适合人工补跑或外部调度 - 这些任务内部带 Redis 分布式锁与幂等保护,重复触发会返回 `skipped` -如果你想改回外部 HTTP 调度,可使用: +需要外部HTTP调度时,先明确停用内置调度的方案;当前没有独立开关,不能仅添加以下配置就视为已切换。下面仅列前三项等价请求,完整十项需按上表配置;crontab时区为Asia/Shanghai: ```cron */2 * * * * curl -fsS -H "Authorization: Bearer ${CRON_SECRET}" https://your-domain/internal/cron/auction-expire 0 0 * * * curl -fsS -H "Authorization: Bearer ${CRON_SECRET}" https://your-domain/internal/cron/rank-rewards */5 * * * * curl -fsS -H "Authorization: Bearer ${CRON_SECRET}" https://your-domain/internal/cron/market-refresh -0 * * * * curl -fsS -H "Authorization: Bearer ${CRON_SECRET}" https://your-domain/internal/cron/tower-enemy-sets -30 2 * * * curl -fsS -H "Authorization: Bearer ${CRON_SECRET}" https://your-domain/internal/cron/player-state-events-cleanup -45 2 * * * curl -fsS -H "Authorization: Bearer ${CRON_SECRET}" https://your-domain/internal/cron/expired-data-cleanup ``` ## CI / 镜像发布 -当前仓库的 [`.github/workflows/deploy.yml`](../.github/workflows/deploy.yml) 会在 `master` 分支推送时: +当前仓库的 [`.github/workflows/deploy.yml`](../.github/workflows/deploy.yml) 会在推送任意Git标签时: - 构建 Docker 镜像 - 推送到 Docker Hub +工作流为镜像写入Git标签、完整提交SHA标签及`latest`,不会自动运行服务器蓝绿脚本,也没有独立的lint/共享测试质量门禁。Docker构建包含服务端类型检查与构建;完整前后端构建、lint和共享测试仍需在发布前执行。 + ## 架构原则 -- 引擎层(`src/shared/engine`)完全独立于 UI 和框架 +- 引擎层(`packages/shared/src/engine`)完全独立于 UI 和框架 - 业务逻辑放在 Service 层 - 数据访问使用 Repository 模式 diff --git a/docs/game-copy-guidelines.md b/docs/game-copy-guidelines.md index 0428c63e..42e1ad88 100644 --- a/docs/game-copy-guidelines.md +++ b/docs/game-copy-guidelines.md @@ -55,6 +55,6 @@ 按“位置、触发状态、原文、事实来源、建议文案、修改理由”记录。先核对规则与实际动作,再改措辞;同一文案如果由前端、服务端或生成模板共同决定,要查到最终玩家看到的文本。 -主流程场景的顶部简介以 `src/react-app/router.tsx` 的 `scene().summary` 为准;页面传入的 `GameSceneFrame.description` 可能被覆盖。修改后要在实际页面核对显示结果。 +主流程场景的顶部简介以 `apps/web/src/router.tsx` 的 `scene().summary` 为准;页面传入的 `GameSceneFrame.description` 可能被覆盖。修改后要在实际页面核对显示结果。 每条修改最后检查:玩家能否看懂下一步;是否增加或丢失事实;是否重复相邻文字;成功、失败、空状态是否各说各的事;术语是否与其他入口一致;剧情味道是否妨碍理解。生成文本还要检查提示词与展示层的约束,不能只改一份示例输出。 diff --git a/docs/game-icons.md b/docs/game-icons.md index bfcc6ded..a297438e 100644 --- a/docs/game-icons.md +++ b/docs/game-icons.md @@ -2,7 +2,7 @@ 灵兽头像的美术方向、固定参考、生成与视觉验收见 [写意墨像技能](../.agents/skills/daoyou-ink-portraits/SKILL.md)。本文负责渲染和资源管理约定。 -React 图标入口为 `src/react-app/components/ui/GameIcon.tsx`。普通字符串直接显示(主要用于 emoji);`icon:名称` 只查询集中注册的 SVG/WebP/PNG 图片,不拼接路径、不加载远程地址、不注入 SVG 字符串。未知名称和空值显示 `❔`。 +React 图标入口为 `apps/web/src/components/ui/GameIcon.tsx`。普通字符串直接显示(主要用于 emoji);`icon:名称` 只查询集中注册的 SVG/WebP/PNG 图片,不拼接路径、不加载远程地址、不注入 SVG 字符串。未知名称和空值显示 `❔`。 ```tsx @@ -14,8 +14,8 @@ React 图标入口为 `src/react-app/components/ui/GameIcon.tsx`。普通字符 ## 新增与使用 -1. 图标文件统一放进 `public/assets/icons/`。头像优先使用 256×256 透明 WebP;灵兽头像裁掉透明外边距后等比贴合,宽或高一边尽量占满画布,另一边居中,保留完整轮廓和内部留白。简洁图形可用 SVG,也支持 PNG。检查小尺寸轮廓与透明边缘;SVG 采用正方形 viewBox,禁用脚本、外链和 foreignObject。 -2. 在 `src/react-app/components/ui/icons/registry.ts` 显式注册稳定名称及 `/assets/icons/文件名` 静态路径,业务配置填写 `icon:名称`。名称按类别加前缀,例如 `beast-`。 +1. 图标文件统一放进 `apps/web/public/assets/icons/`。头像优先使用 256×256 透明 WebP;灵兽头像裁掉透明外边距后等比贴合,宽或高一边尽量占满画布,另一边居中,保留完整轮廓和内部留白。简洁图形可用 SVG,也支持 PNG。检查小尺寸轮廓与透明边缘;SVG 采用正方形 viewBox,禁用脚本、外链和 foreignObject。 +2. 在 `apps/web/src/components/ui/icons/registry.ts` 显式注册稳定名称及 `/assets/icons/文件名` 静态路径,业务配置填写 `icon:名称`。名称按类别加前缀,例如 `beast-`。 3. 调用方只使用 `GameIcon`;不自行解析协议、不直接引用资源、不创建第二份注册表。删除或更名时同时检索配置引用。 4. 业务适配组件只负责从领域 ID 取图标值,例如共享的 `feature/beasts/BeastIcon.tsx`,渲染始终交给 `GameIcon`。 @@ -30,11 +30,11 @@ Phaser 等 Canvas 渲染器通过 `GameIcon.resolveSource(iconValue)` 获取同 | 素材 | 用途 | 规格 | 接入 | | --- | --- | --- | --- | -| `public/assets/icons/map-wild.webp` | 灵兽出没地 | 160×160,RGBA | 地图、筛选、查找、详情 | -| `public/assets/icons/map-dungeon.webp` | 秘境 | 160×160,RGBA | 地图、筛选、查找、详情 | -| `public/assets/icons/map-market.webp` | 坊市 | 160×160,RGBA | 地图、筛选、查找、详情 | -| `public/assets/icons/map-sect.webp` | 宗门 | 160×160,RGBA | 地图、筛选、查找、详情 | -| `public/assets/icons/map-landmark.webp` | 山川地标 | 160×160,RGBA | 地图、筛选、查找、详情 | +| `apps/web/public/assets/icons/map-wild.webp` | 灵兽出没地 | 160×160,RGBA | 地图、筛选、查找、详情 | +| `apps/web/public/assets/icons/map-dungeon.webp` | 秘境 | 160×160,RGBA | 地图、筛选、查找、详情 | +| `apps/web/public/assets/icons/map-market.webp` | 坊市 | 160×160,RGBA | 地图、筛选、查找、详情 | +| `apps/web/public/assets/icons/map-sect.webp` | 宗门 | 160×160,RGBA | 地图、筛选、查找、详情 | +| `apps/web/public/assets/icons/map-landmark.webp` | 山川地标 | 160×160,RGBA | 地图、筛选、查找、详情 | ## 网页图片显示强度 diff --git a/docs/game-layout-ownership.md b/docs/game-layout-ownership.md index 83251448..479709bd 100644 --- a/docs/game-layout-ownership.md +++ b/docs/game-layout-ownership.md @@ -26,12 +26,12 @@ - `/game/tower` 使用主流程壳展示挑战、祝福和周榜;活动战斗跳转 `/game/tower/battle` 的既有沉浸壳,复用 v6 公共战斗组件。结算播放结束后返回幻境,不在入口正文嵌入旧战斗播放器。 - `/game/rankings` 保留榜单主流程壳;`/game/battle/challenge` 使用 v6 公共回放播放器自动逐行动播放服务端已结算的挑战,播放完成后展示名次摘要。挑战请求 UUID 保留在 URL,刷新及失败后恢复同一结果,观看不占用角色。 -- v6 战斗页面、阵容、指令和逐行动播报放在 `src/react-app/components/feature/combat-v6/`;仅复用通用 UI 和全局配色,不依赖旧 `feature/battle` 组件。协议与恢复规则见 [v6 战斗 UI](combat-v6-battle-ui.md) -- 造化/参悟共享材料选择器放在 `src/react-app/components/feature/creation/MaterialSelector.tsx` +- v6 战斗页面、阵容、指令和逐行动播报放在 `apps/web/src/components/feature/combat-v6/`;仅复用通用 UI 和全局配色,不依赖旧 `feature/battle` 组件。协议与恢复规则见 [v6 战斗 UI](combat-v6-battle-ui.md) +- 造化/参悟共享材料选择器放在 `apps/web/src/components/feature/creation/MaterialSelector.tsx` - `/game/cultivator` 使用「人物属性 / 先天设定 / 所修功法 / 肉身修炼」四页签,桌面左侧纵向排列,移动端顶部横向排列,URL 的 `tab=innate|manuals|body` 支持直达。人物属性独占左侧墨像与右侧姓名、名号、境界、宗门和寿元,随后显示气血法力、状态、战斗属性与六维加点、修为。先天设定合并先天灵根(仅原始强度)、命格与人物志,转世重修置于该页末尾;后天灵根增益仅在肉身修炼的洗髓区域展示。所修功法复用 `ManualRoom`;肉身修炼按肉身阶位、五轨修炼、洗髓、灵根后天增益排序,以浅底色分组突出阶位与等级,五轨直接展示实际战斗收益的简短说明与进度,不设展开详情,升阶条件按需展开;洗髓保留破限操作,后天灵根突出增益并辅以先天与当前强度。各页签不重复身份资料或场景壳,以留白分组。旧功法、炼体、洗髓地址继续重定向并透传参数,旧加点地址进入人物属性加点状态。加点预览仍走只读 V6 投影接口。 -- 道身长期状态与称号编辑放在 `src/react-app/components/feature/cultivator/` -- 剧情演出播放器在 `src/react-app/components/feature/performance/PerformancePlayer.tsx`。配图铺满自己的那一块,不加边框和边距;手机上它只是一截辅助,字占主要位置。没有配图时不留画框,场景说明改写在简上。眼前、旁白和人物说的话三种样子分开。点简文继续,第一下只把当前句看完;选项写在简上,看完才离开这一幕。回看翻开前面的字,离开不推进剧情。本地开发的 `/game/story/preview` 用同一播放器按演出编号观看,不写进度。页面教学播放器在 `src/react-app/components/feature/guide/GuideOverlay.tsx`,挂在主流程壳和山河舆图壳上。只有当前这一幕的配置要求这场教学时才挖孔;看完才记下,先不看不记。没有教学的幕不会因为地址上带着 `guide` 而罩住页面。丹房第一课是 `guide=alchemy-first-furnace`。青溪一课是 `guide=map-qingxi`,只在文字舆图上指出天南和青溪坡。灵兽袋一课是 `guide=beast-pouch`,指出袋子、名册和详情。洞府一课是 `guide=cave-layout`,指出洞府内、灵眼之泉和出洞府。炼器一课是 `guide=forge-first-weapon`,指出器炉、图录和开炉;看完还要真的铸成法兵。山门一课是 `guide=sect-door`,只给散修指出身份和自愿的门;看完不结束这一幕,拜入任一宗才过。宗门入门仍用 `src/react-app/components/feature/narrative/` 的旧舞台,两者不共用画面和操作。 -- 清扫与采掘共用的横屏、全屏进入和释放逻辑放在 `src/react-app/lib/gameActivityImmersive.ts`;共享启动层和沉浸状态监听放在 `src/react-app/components/feature/game-activity/` +- 道身长期状态与称号编辑放在 `apps/web/src/components/feature/cultivator/` +- 剧情演出播放器在 `apps/web/src/components/feature/performance/PerformancePlayer.tsx`。配图铺满自己的那一块,不加边框和边距;手机上它只是一截辅助,字占主要位置。没有配图时不留画框,场景说明改写在简上。眼前、旁白和人物说的话三种样子分开。点简文继续,第一下只把当前句看完;选项写在简上,看完才离开这一幕。回看翻开前面的字,离开不推进剧情。本地开发的 `/game/story/preview` 用同一播放器按演出编号观看,不写进度。页面教学播放器在 `apps/web/src/components/feature/guide/GuideOverlay.tsx`,挂在主流程壳和山河舆图壳上。只有当前这一幕的配置要求这场教学时才挖孔;看完才记下,先不看不记。没有教学的幕不会因为地址上带着 `guide` 而罩住页面。丹房第一课是 `guide=alchemy-first-furnace`。青溪一课是 `guide=map-qingxi`,只在文字舆图上指出天南和青溪坡。灵兽袋一课是 `guide=beast-pouch`,指出袋子、名册和详情。洞府一课是 `guide=cave-layout`,指出洞府内、灵眼之泉和出洞府。炼器一课是 `guide=forge-first-weapon`,指出器炉、图录和开炉;看完还要真的铸成法兵。山门一课是 `guide=sect-door`,只给散修指出身份和自愿的门;看完不结束这一幕,拜入任一宗才过。宗门入门仍用 `apps/web/src/components/feature/narrative/` 的旧舞台,两者不共用画面和操作。 +- 清扫与采掘共用的横屏、全屏进入和释放逻辑放在 `apps/web/src/lib/gameActivityImmersive.ts`;共享启动层和沉浸状态监听放在 `apps/web/src/components/feature/game-activity/` - 清扫摇杆使用 `phaser4-rex-plugins` 的 Virtual Joystick,并由清扫 Phaser runtime 持有、渲染和销毁;采掘放索按钮仍是玩法私有 DOM 控件。各玩法 runtime 与服务端重放规则保持独立 - PWA 安装状态由应用根 Provider 统一持有;小游戏只在全屏失败时给出场景化安装提示,系统设置保留固定安装入口 - PWA 安全区由顶层布局和共享固定层分别负责:背景与画布可以铺满系统区域,HUD、导航、正文和模态交互必须避让 `safe-area-inset-*`;不得给 `body` 统一增加 padding diff --git a/docs/game-map-art.md b/docs/game-map-art.md index c802b755..eb97fd42 100644 --- a/docs/game-map-art.md +++ b/docs/game-map-art.md @@ -4,12 +4,12 @@ | 用途 | 文件 | 静态 URL | 尺寸 | 大小 | 接入状态 | | --- | --- | --- | --- | ---: | --- | -| 世界总览底画 | [world-overview-v1.webp](../public/assets/maps/world-overview-v1.webp) | `/assets/maps/world-overview-v1.webp` | 1536×1024 | 566,572 bytes | 已接入 | -| 天南区域底画 | [tiannan-region-v1.webp](../public/assets/maps/tiannan-region-v1.webp) | `/assets/maps/tiannan-region-v1.webp` | 1536×1024 | 809,114 bytes | 已接入 | -| 乱星海区域底画 | [luanxinghai-region-v1.webp](../public/assets/maps/luanxinghai-region-v1.webp) | `/assets/maps/luanxinghai-region-v1.webp` | 1536×1024 | 557,450 bytes | 已接入 | -| 慕兰区域底画 | [mulan-region-v1.webp](../public/assets/maps/mulan-region-v1.webp) | `/assets/maps/mulan-region-v1.webp` | 1536×1024 | 720,578 bytes | 已接入 | -| 大晋皇朝区域底画(不含南疆、北境冰原) | [dajin-region-v1.webp](../public/assets/maps/dajin-region-v1.webp) | `/assets/maps/dajin-region-v1.webp` | 1536×1024 | 782,562 bytes | 已接入 | -| 南疆区域底画 | [nanjiang-region-v1.webp](../public/assets/maps/nanjiang-region-v1.webp) | `/assets/maps/nanjiang-region-v1.webp` | 1536×1024 | 760,402 bytes | 已接入 | -| 北境冰原区域底画 | [northland-region-v1.webp](../public/assets/maps/northland-region-v1.webp) | `/assets/maps/northland-region-v1.webp` | 1536×1024 | 617,564 bytes | 已接入 | +| 世界总览底画 | [world-overview-v1.webp](../apps/web/public/assets/maps/world-overview-v1.webp) | `/assets/maps/world-overview-v1.webp` | 1536×1024 | 566,572 bytes | 已接入 | +| 天南区域底画 | [tiannan-region-v1.webp](../apps/web/public/assets/maps/tiannan-region-v1.webp) | `/assets/maps/tiannan-region-v1.webp` | 1536×1024 | 809,114 bytes | 已接入 | +| 乱星海区域底画 | [luanxinghai-region-v1.webp](../apps/web/public/assets/maps/luanxinghai-region-v1.webp) | `/assets/maps/luanxinghai-region-v1.webp` | 1536×1024 | 557,450 bytes | 已接入 | +| 慕兰区域底画 | [mulan-region-v1.webp](../apps/web/public/assets/maps/mulan-region-v1.webp) | `/assets/maps/mulan-region-v1.webp` | 1536×1024 | 720,578 bytes | 已接入 | +| 大晋皇朝区域底画(不含南疆、北境冰原) | [dajin-region-v1.webp](../apps/web/public/assets/maps/dajin-region-v1.webp) | `/assets/maps/dajin-region-v1.webp` | 1536×1024 | 782,562 bytes | 已接入 | +| 南疆区域底画 | [nanjiang-region-v1.webp](../apps/web/public/assets/maps/nanjiang-region-v1.webp) | `/assets/maps/nanjiang-region-v1.webp` | 1536×1024 | 760,402 bytes | 已接入 | +| 北境冰原区域底画 | [northland-region-v1.webp](../apps/web/public/assets/maps/northland-region-v1.webp) | `/assets/maps/northland-region-v1.webp` | 1536×1024 | 617,564 bytes | 已接入 | 七张均为不透明背景图,不是节点图集,已接入 `/game/map-v2`。天南、乱星海、慕兰草原、大晋皇朝、南疆、北境冰原分别锚定现有 23 个、11 个、4 个、20 个、5 个、3 个地点。入口、地点、文字与命中区由交互层独立提供,锚点按各自构图确定,新版不绘制节点连线。一级地图另标注天澜草原、天沙大陆、五龙海、无边海、极西之地、飓风沙漠,均显示“未开放”并禁用进入;这六区底画尚未制作,仅标注地理名称。现有规格不约束后续素材。 diff --git a/docs/inscription-room.md b/docs/inscription-room.md index 0b58c23b..3b8c5d8e 100644 --- a/docs/inscription-room.md +++ b/docs/inscription-room.md @@ -42,7 +42,7 @@ ## 写入与界面 -规则位于 `src/shared/inscriptions/rules.ts`,协议位于 `src/shared/contracts/inscriptions.ts`。服务端经活动角色认证、角色锁与 `playerCommandExecutor` 事务;材料/阵纹/装备均按所属角色、位置、数量和版本校验,费用重新计算。随机结果、库存、资源扣减及幂等结果在同一事务中提交。客户端持久化未确认请求,刷新或网络失败复用原请求ID核对结果。 +规则位于 `packages/shared/src/inscriptions/rules.ts`,协议位于 `packages/shared/src/contracts/inscriptions.ts`。服务端经活动角色认证、角色锁与 `playerCommandExecutor` 事务;材料/阵纹/装备均按所属角色、位置、数量和版本校验,费用重新计算。随机结果、库存、资源扣减及幂等结果在同一事务中提交。客户端持久化未确认请求,刷新或网络失败复用原请求ID核对结果。 已穿戴装备变更发布 `player.profile`,由已有资源提交器刷新投影、气血法力上限与关联缓存,不自动补满气血。 @@ -50,7 +50,7 @@ ## 美术素材 -生产文件:`public/assets/inscriptions/drawing-board.webp`,960×960,保留真实alpha。生成依据为 `.agents/skills/daoyou-ink-portraits` 的男女冻结参考,只沿用暖灰宽笔、飞白和留白语言。 +生产文件:`apps/web/public/assets/inscriptions/drawing-board.webp`,960×960,保留真实alpha。生成依据为 `.agents/skills/daoyou-ink-portraits` 的男女冻结参考,只沿用暖灰宽笔、飞白和留白语言。 采用提示词:略俯视的古朴圆形绘阵盘,疏朗断续的同心墨线与少量抽象阵纹,中间留白,斜置符笔与小砚收在右下;四角留给材料格。宽笔暖灰墨面,少量浓墨强调笔砚,中淡墨、飞白与透明留白构形。独立透明底,不画文字、印章、室内建筑、人物、光环、发光符文、粒子或写实材质。 @@ -60,10 +60,10 @@ 2026-09-23,使用 `local4` 完成桌面及360px移动端验收:绘制及尾数确认、背包合成、空孔烙印、覆盖不返还、孔内合成、装备等级限制、刷新持久化与已穿戴装备属性同步。增加气血上限不会即时补满气血。四种材料合计130份的预览为6级一枚、尾数2份、消耗2点灵气;该组合未提交。 -- `bun run test src/shared/inscriptions src/shared/inventory src/shared/contracts/auction.test.ts`:8文件、45项通过。 +- `bun run test packages/shared/src/inscriptions packages/shared/src/inventory packages/shared/src/contracts/auction.test.ts`:8文件、45项通过。 - `bun run test --testTimeout 30000`:208文件、1971项通过。 -- `bunx tsc -b tsconfig.app.json tsconfig.node.json`、首轮 `bun run build` 通过;最终 `bun run lint` 与 `git diff --check` 通过。 -- 最终构建被并行战斗改动阻断:`src/shared/combat-v6/skill-details.ts:85` 的效果名称映射缺少新增的 `repeat`。同时九劫天宫内容包与新解析结构暂不匹配,导致页面加载失败;未修改这些并行文件。 +- `bunx tsc -b apps/web/tsconfig.json tsconfig.node.json`、首轮 `bun run build` 通过;最终 `bun run lint` 与 `git diff --check` 通过。 +- 最终构建被并行战斗改动阻断:`packages/shared/src/combat-v6/skill-details.ts:85` 的效果名称映射缺少新增的 `repeat`。同时九劫天宫内容包与新解析结构暂不匹配,导致页面加载失败;未修改这些并行文件。 验收收尾时本地3001 API已不可连接,测试样本尚未清理。角色 `a7b1252b-63ed-4c4e-a8c1-d0bc06772ba7` 验收前统一库存为空;现有样本为青石剑、已穿戴青石袍、玄锋4级/玄锋2级/长生3级各一枚及四种验收材料。服务恢复后应先通过正式操作卸下青石袍,再核对库存仅含本次样本后使用本地清包接口;保留真实消耗与历史记录,不直接改库。未执行断网恢复模拟。 diff --git a/docs/inventory-bag-refresh-audit.md b/docs/inventory-bag-refresh-audit.md index 54e67a6f..6dd0ed0b 100644 --- a/docs/inventory-bag-refresh-audit.md +++ b/docs/inventory-bag-refresh-audit.md @@ -38,7 +38,7 @@ - 首次成交的网络记录显示成交 POST 200,随后一次 `/api/combat-v6/inventory?location=bag` GET 200;浏览器控制台无 error。 - 同一页面通过导航进入储物袋后立即显示剩余两份。另开储物袋标签页,再在鉴宝页面出售,另一个标签页自动从两份更新为一份、最后移除,验证实时广播。 - 三份准备物品已全部通过正式回收流程消耗;保留实际成交所得 39 灵石。 -- `bun run lint`、`bun run build`(包含前后端 TypeScript)通过;`bunx vitest run src/shared/lib/bagResourceChanges.test.ts` 的两项已有纯函数测试通过;`git diff --check` 通过。 +- `bun run lint`、`bun run build`(包含前后端 TypeScript)通过;`bunx vitest run packages/shared/src/lib/bagResourceChanges.test.ts` 的两项已有纯函数测试通过;`git diff --check` 通过。 - `bun run db:migrate` 的认证迁移成功,但业务段的 `bun ... x` 未传入 DATABASE_URL;改用 `bun --env-file=env/local.env run bunx drizzle-kit migrate` 后业务迁移成功。本次没有修改迁移、环境配置或启动脚本。 - 本地 API 已有 watch 进程占用 3001,本次组合启动的重复 API 实例未启动,复用了已有本地 API。 diff --git a/docs/inventory-equipment-ui.md b/docs/inventory-equipment-ui.md index 427c16f4..c48b5458 100644 --- a/docs/inventory-equipment-ui.md +++ b/docs/inventory-equipment-ui.md @@ -30,7 +30,7 @@ 跨标签页存取复测:保持邮件附件选择打开,将 `10Q新版草药` 两份存入储藏室,主背包立即从 12/40 变为 11/40,邮件旧附件同时失效并禁用发送;取回后恢复原格位、数量和储藏室空置状态。未发送邮件或聊天、未上架商品,保留正常增加的物品修订号与资源事件。 -执行 `bun run lint`、`bun run build`(包含前后端类型检查)、`bun run test src/shared/contracts/resources/bag.test.ts src/shared/lib/bagResourceChanges.test.ts src/shared/inventory/inventory.test.ts`(10 项通过)以及 `git diff --check`。未新增前后端单元测试,未跑全量共享引擎测试;本轮不改引擎规则。当前角色没有宗门交付任务或进行中的秘境,三处对应业务流程未实测;兽诀/炼丹/铸造实际消耗、断网恢复和角色切换也未计为已验收。 +执行 `bun run lint`、`bun run build`(包含前后端类型检查)、`bun run test packages/shared/src/contracts/resources/bag.test.ts packages/shared/src/lib/bagResourceChanges.test.ts packages/shared/src/inventory/inventory.test.ts`(10 项通过)以及 `git diff --check`。未新增前后端单元测试,未跑全量共享引擎测试;本轮不改引擎规则。当前角色没有宗门交付任务或进行中的秘境,三处对应业务流程未实测;兽诀/炼丹/铸造实际消耗、断网恢复和角色切换也未计为已验收。 ## 穿戴道装独立存放(2026-09-15) diff --git a/docs/inventory-ink-portraits.md b/docs/inventory-ink-portraits.md index 92c44105..fb7873a7 100644 --- a/docs/inventory-ink-portraits.md +++ b/docs/inventory-ink-portraits.md @@ -1,6 +1,6 @@ # 储物袋写意墨像 -2026-09-10 使用内置 image_gen 生成男女新稿,替换 public/assets/inventory 下同名 WebP,保留真实透明通道。中间生成的棋盘底稿已排除,不进入项目。生产图高 960px。 +2026-09-10 使用内置 image_gen 生成男女新稿,替换 apps/web/public/assets/inventory 下同名 WebP,保留真实透明通道。中间生成的棋盘底稿已排除,不进入项目。生产图高 960px。 页面按角色性别加载一张,保持素材原始透明度;限制在装备栏中央范围,袖摆不延伸至槽位背后。只承担静态装饰,无操作、评分或动效。 @@ -16,7 +16,7 @@ ## 人物属性头像(2026-09-15) -人物属性页改用独立生成的头肩头像,装备页继续使用全身墨像。新素材为 `public/assets/icons/cultivator-{male,female}-avatar.png`,通过 GameIcon 注册表接入,384px,真实 alpha,不使用混合模式。男女分别参考技能中的 `male-baseline.webp`、`female-baseline.webp`。男版首稿含棋盘格,已排除。 +人物属性页改用独立生成的头肩头像,装备页继续使用全身墨像。新素材为 `apps/web/public/assets/icons/cultivator-{male,female}-avatar.png`,通过 GameIcon 注册表接入,384px,真实 alpha,不使用混合模式。男女分别参考技能中的 `male-baseline.webp`、`female-baseline.webp`。男版首稿含棋盘格,已排除。 生成提示:以参考墨像重新创作方形头肩头像,发髻与肩部完整,衣领下以干笔收尾;暖灰宽笔墨面、少量深墨、飞白和断续边缘,沉静的简笔眉眼,朴素束发;真实透明背景,不画棋盘格、纸张、背景、印章或文字。男版与女版分别生成,保持相同笔墨语言。成稿保留较清楚的五官,与原有可用墨像一致;经页面浅纸底缩小检查。 diff --git a/docs/item-presentation-ui.md b/docs/item-presentation-ui.md index ca39a027..8c1fd8da 100644 --- a/docs/item-presentation-ui.md +++ b/docs/item-presentation-ui.md @@ -58,7 +58,7 @@ PC 悬停延迟 200ms 展示,允许移入预览操作;触屏点击展示同 - 修复窄屏炉中格子由固定内容高度撑成约 58×82px 的问题,复测六格均约 57.76×57.76px;名称仍最多两行,最小字号 10px。 - 三次真实铸造成功:两次普通动态效果,一次减少动态效果;共消耗 3 张图纸、3 份材料、300 灵石、21 灵气,生成 3 件成品。普通模式捕获两个已投入位置的 `forge-gather`、`forge-fire`、`forge-reveal`,聚光约 800ms、成器约 1100ms。成器结束自动打开详情,关闭后保留炉心成品,继续铸造清除成品。 - 单次成功开炉网络记录为一次 POST、一次后续 GET。离线提交没有生成成品或扣费,恢复网络后「重新核对」可恢复备料,随后成功铸造;修复后续库存读取错误覆盖开炉恢复说明的问题。 -- `bun run lint`、`bun run build`、触及文件 Prettier、`git diff --check` 通过;`bun run test src/shared/inventory src/shared/forging` 共 15 项通过。没有新增前端/服务端测试文件,未运行全仓共享测试。 +- `bun run lint`、`bun run build`、触及文件 Prettier、`git diff --check` 通过;`bun run test packages/shared/src/inventory packages/shared/src/forging` 共 15 项通过。没有新增前端/服务端测试文件,未运行全仓共享测试。 测试剩余的七条临时库存已精确删除;拆分临时条目已合并、图纸已消耗完。灵石、灵气及灵气刷新时间恢复至测试前基准,保留四条原有储藏室材料和其他角色进度。网络、动态效果、触屏与视口模拟已恢复。 diff --git a/docs/local-development.md b/docs/local-development.md index 37478623..5af6b928 100644 --- a/docs/local-development.md +++ b/docs/local-development.md @@ -1,16 +1,16 @@ # 本地开发 -需要 Bun 1.3.14+、Node.js 和 Docker。`env/local.env` 随仓库提供,包含专用本地容器配置,克隆后无需复制模板。 +需要 pnpm 10.34.6、Node.js 24.18+ 和 Docker。pnpm管理workspace依赖,Turborepo编排任务,Node运行Nest API与Vite。先执行 `pnpm install --frozen-lockfile`。首次将 `env/local.example.env` 复制为 `env/local.env`;已有本地配置保留。模板只含专用本地容器凭据,实际文件被Git忽略。 启动本地服务: ```bash -bun run services up -d --wait # 启动 PostgreSQL、Redis、NATS、Mailpit -bun run db:migrate # 执行认证和业务迁移 -bun run dev # 启动 API 与 Vite +pnpm run services up -d --wait # 启动 PostgreSQL、Redis、NATS、Mailpit +pnpm run db:migrate # 执行认证和业务迁移 +pnpm run dev # 启动 API 与 Vite ``` -环境文件不再由脚本自动生成。启动和迁移命令重复执行不会清空数据;停止容器使用 `bun run services down`,数据保留在专用卷中。 +环境文件不再由脚本自动生成。启动和迁移命令重复执行不会清空数据;停止容器使用 `pnpm run services down`,数据保留在专用卷中。 | 服务 | 地址 | | --- | --- | @@ -21,35 +21,66 @@ bun run dev # 启动 API 与 Vite | NATS / 监控 | `127.0.0.1:14222` / http://127.0.0.1:18222 | | SMTP / Mailpit | `127.0.0.1:11025` / http://127.0.0.1:18025 | +## Workspace 与配置边界 + +- `apps/api`:Nest 功能模块、服务端基础设施与维护命令;运行依赖、Nest CLI 和 Rspack 构建依赖在本包声明。 +- `apps/web`:React SPA、静态资源与 Vite 配置;React、Vite 和 Tailwind 依赖在本包声明。 +- `packages/shared`:源码形式的契约、类型和纯计算。两端通过 `@daoyou/shared/*` 使用,主程序构建时打包;不独立部署。新增带 `index.ts` 的目录入口需要在本包 `exports` 注册。 +- 根目录保留 `pnpm-workspace.yaml`、单一 `pnpm-lock.yaml`、`turbo.json`、lint/test/typecheck、迁移配置和部署入口。内部依赖使用 `workspace:*`,各包自行声明依赖。 + +ESLint限制共享包导入API/Web,以及两端互相导入。LLM生成和提示词位于API的 `lib/generation`;共同的修为与突破计算位于共享包。 + +Nest `ConfigurationModule` 使用 `@nestjs/config`、关闭dotenv自动发现,并提供一次Zod校验后的不可变环境快照。Nest服务可注入 `runtimeConfig`,独立仓储/基础库使用 `getRuntimeEnvironment()`。端口、数据库URL、连接池上限、认证配置和消息配置在启动时校验;production还要求cron密钥且禁止 `APP_ENV=local`。错误只报告字段与约束,不输出凭据。 + +`DatabaseModule`导出既有Drizzle客户端和 `DatabaseService`,与独立仓储共用一个pg.Pool,事务继续使用 `DbExecutor`/`DbTransaction`。Runtime在请求及消息排空后关闭连接;健康检查同时探测PostgreSQL、Redis、NATS及消息设施。 + +## NestJS API + +`dev:api`、`prd:api`、`build:server`和Docker均使用Nest;旧Hono入口、路由和依赖已移除。框架代码与工具链迁移完成;特殊数据/发布验收见迁移记录。 + +```bash +pnpm run dev:api # 首次构建 Nest,随后监听源码并重建、重启 Node.js +pnpm run dev:web # 另一个终端启动前端 +pnpm run build:server # 类型检查并生成 apps/api/dist/main.js +``` + +Nest使用 `nest start --watch --no-shell --env-file ...`(CLI 12 的 ESM Rspack 构建器)增量构建并通过SIGTERM重启Node进程,组合开发任务由Turbo管理。类型检查随 Nest 构建执行。API只清理 `apps/api/dist`,Vite只清理 `apps/web/dist`,两者可独立构建。Docker使用同一pnpm锁文件构建,再通过 `pnpm --filter @daoyou/api deploy --prod /out` 生成独立运行目录;最终镜像仅复制该目录,入口为 `dist/main.js`。Web及根开发工具不会进入运行镜像。workspace禁用自动安装peer依赖,必须显式声明所需peer。 + +当前HTTP入口已迁入Nest,特殊数据与生产验收仍有未完成项。长流程验证需要关闭watch时,先执行`pnpm run build:server`,再执行`APP_ENV=local NODE_ENV=development node --env-file=env/local.env apps/api/dist/main.js`。详情见 [迁移进度](nestjs-migration.md)。 + +Nest收到SIGTERM/SIGINT后拒绝新请求,停止定时发布并排空HTTP;30秒后关闭剩余HTTP连接,触发流式请求取消,继续等待处理器结束。随后停止消息消费者/发布任务、关闭数据库与Redis。启动期间收到信号会先等待初始化结束,再统一清理。整个停机过程超过60秒则以失败状态强制退出;生产Compose设置75秒停机宽限期。请求取消不能撤销已经提交的事务,恢复仍依赖现有幂等与outbox机制。 + ## 选择环境 两套开发命令分别显式加载一个文件: | 用途 | 本地 `env/local.env` | 预发布 `env/staging.env` | | --- | --- | --- | -| 同时启动 | `bun run dev` | `bun run prd` | -| 仅 API | `bun run dev:api` | `bun run prd:api` | -| 仅 Web | `bun run dev:web` | `bun run prd:web` | +| 同时启动 | `pnpm run dev` | `pnpm run prd` | +| 仅 API | `pnpm run dev:api` | `pnpm run prd:api` | +| 仅 Web | `pnpm run dev:web` | `pnpm run prd:web` | `prd` 指预发布调试,两组都使用 `NODE_ENV=development`,API 启用 watch,Web 使用 Node.js 运行 Vite。API 和 Web 分别读取同一份环境文件;Vite 根据 `HOST`、`WEB_PORT` 监听,并把 API/WebSocket 代理到 `PORT`。本地端口为 3001/5174;预发布调试默认 3000/5173。 -`env/staging.env` 从 `env/example.env` 准备,已有文件继续使用;它连接远程服务,不能用于自动化模拟测试写入。`env/local.env` 和 `env/example.env` 纳入 Git,其他 `env/` 配置默认忽略。原根目录 `.env` 原样移至 `env/legacy.env` 保留,不被任何命令加载;不将真实凭据放入本地配置或 `VITE_*`。整个 `env/` 排除在 Docker 构建上下文之外。 +`env/staging.env` 从 `env/example.env` 准备,已有文件继续使用;它连接远程服务,不能用于自动化模拟测试写入。仅 `env/local.example.env` 和 `env/example.env` 纳入Git,实际 `env/` 配置默认忽略。原根目录 `.env` 原样移至 `env/legacy.env` 保留,不被任何命令加载;不将真实凭据放入本地配置或 `VITE_*`。整个 `env/` 排除在 Docker 构建上下文之外。 -`bunfig.toml` 的 `env = false` 关闭 Bun 自动加载,Bun `--env-file` 显式选择文件。Vite 保持默认环境文件加载行为;项目根目录不放 `.env` 或其他 `.env.*` 文件,环境配置集中在 `env/`,由启动命令注入。保留系统/CI 注入变量,不再手写解析、清理环境变量、固定端口校验或子进程管理;不要在 Shell 中预先导出另一环境的业务凭据。 +Node的 `--env-file` 显式选择文件;Nest ConfigModule关闭dotenv自动发现。Vite使用 `envDir: false`,避免叠加另一份配置;公开的 `VITE_*` 由进程环境注入。不要在Shell中预先导出另一环境的业务凭据。 -组合启动由 Bun `run --parallel` 管理。浏览器长流程测试时不编辑 API 文件;如需关闭 watch,在两个终端执行 `bun --env-file=env/local.env src/index.ts` 和 `bun run dev:web`,不增加专用脚本。 +Turbo开发任务不缓存,使用loose环境模式向子进程传递开发配置。构建使用默认strict模式,只传入声明的 `NODE_ENV`、`VITE_*`、`CF_PAGES_COMMIT_SHA`、`GITHUB_SHA`;这些值参与缓存键。共享包源码和根tsconfig变化会使依赖任务失效。`.turbo/` 不入Git或Docker上下文。 + +终端Ctrl-C终止开发任务及子进程。浏览器长流程测试使用上述构建后的无watch命令和 `pnpm run dev:web`,不增加专用脚本。 ## 构建与迁移 `build:client` 和 `build:server` 保留为 CI/CD 稳定入口,不要求本地环境文件或本地数据库启动: -- `build:client`:`vite.config.ts` 构建 SPA,保留 `version.json` 和构建 ID。 -- `build:server`:`vite.server.config.ts` 构建 Hono 服务,再由 `vite.battle-worker.config.ts` 构建 resolver Worker。Docker 继续调用此命令,产物路径不变。 -- `build`:Bun `run --sequential` 依次调用两个构建入口;各目标会清理自己的输出,生产仍按原有独立构建方式发布。 +- `build:client`:`apps/web/vite.config.ts` 构建 SPA 至 `apps/web/dist`,保留 `version.json` 和构建 ID。 +- `build:server`:`nest build` 根据 `apps/api/nest-cli.json` 构建Nest服务`apps/api/dist/main.js`,由Node运行并使用外部生产依赖。Docker调用此命令;旧resolver Worker已退役,没有额外Worker构建。 +- `build`:Turbo按依赖图构建两个独立包;各目标会清理自己的输出,生产仍按原有独立构建方式发布。 -前端公开配置由 CI 环境变量注入;需要文件时显式使用 `bun --env-file=env/staging.env run build:client`。服务端密钥和连接串在部署启动时注入,不在构建时绑定环境。构建目标使用 `--config` 区分,Vite mode 不再用于 client/server 分流。 +前端公开配置由 CI 环境变量注入;需要文件时显式使用 `node --env-file=env/staging.env node_modules/turbo/bin/turbo run build --filter=@daoyou/web`。服务端密钥和连接串在部署启动时注入,不在构建时绑定环境。根命令分别进入对应 workspace;Vite只负责Web,Nest CLI只负责API。 -`bun run db:migrate` 仅加载 `env/local.env`,依次执行认证和业务迁移。预发布/生产迁移沿用原子工具并显式注入环境,例如 `bun --env-file=env/staging.env run auth:migrate` 和 `bun --env-file=env/staging.env x drizzle-kit migrate`;不会随 `prd` 启动自动迁移。 +`pnpm run db:migrate` 仅加载 `env/local.env`,依次执行认证和业务迁移。预发布/生产迁移沿用原子工具并显式注入环境,例如 `node --env-file=env/staging.env node_modules/drizzle-kit/bin.cjs migrate --config drizzle.auth.config.ts` 和 `node --env-file=env/staging.env node_modules/drizzle-kit/bin.cjs migrate`;不会随 `prd` 启动自动迁移。 ## 注册与测试 @@ -57,7 +88,7 @@ bun run dev # 启动 API 与 Vite 邮箱激活、密码重置和邮箱 OTP 使用所有环境共用的正常流程;本地邮件送到 Mailpit。账号通过真实页面注册,验证码和链接从 Mailpit 获取,不再用脚本绕过账号/角色创建。已有本地测试账号可复用;若未验证邮箱,按登录页提示完成验证。多人操作应使用独立浏览器配置文件/上下文,避免共享 Cookie。 -项目只有两层测试:`src/shared` 纯单元测试,以及 Codex 使用浏览器/Playwright 的真实用户流程模拟。操作规范见 [测试规范](./testing.md)。不保留一次性冒烟、故障注入、种子或性能采样脚本;需要的数据与步骤由当次任务按实际页面准备,结果记录在任务或相关设计文档中。 +项目只有两层测试:`packages/shared/src` 纯单元测试,以及 Codex 使用浏览器/Playwright 的真实用户流程模拟。操作规范见 [测试规范](./testing.md)。不保留一次性冒烟、故障注入、种子或性能采样脚本;需要的数据与步骤由当次任务按实际页面准备,结果记录在任务或相关设计文档中。 已有测试账号的查询方式、统一密码和登录要求统一维护在[本地测试账号与密码](./testing.md#3-本地测试账号与密码),测试前按该规范复用账号。 @@ -66,9 +97,10 @@ bun run dev # 启动 API 与 Vite ## 检查与迁移 ```bash -bun run lint -bun run test -bun run build +pnpm run lint +pnpm run typecheck +pnpm run test +pnpm run build ``` 新库安装已修正 `0000` 中唯一索引与外键的创建顺序。`0036_unified_v6_replay` 针对尚未上线的 v6 清空旧开发回放并建立统一角色关联表。迁移与清理仅在本地验证,未向预发布执行。 diff --git a/docs/manual-migration.md b/docs/manual-migration.md index c21c45d7..d8331fdb 100644 --- a/docs/manual-migration.md +++ b/docs/manual-migration.md @@ -25,7 +25,7 @@ 2. 上线后,玩家从洞府进入传承页。待兑换列表直接读取当前角色在原表持有的 `gongfa`;品质、评分及归属以兑换事务锁定的原记录为准。兑换成功才删除原记录,失败保留。旧玩法已停用,不再产生新的旧功法成品。 3. 玩家先选好该本旧功法的全部额外玉简,再确认兑换。随机抽取、自选及神品感悟果发放和原记录删除一次完成。后台仅统计原表中尚未兑换的功法及异常来源,不保留已兑换数量或历史凭证。 -补偿配置集中在 `src/shared/manual-migration/rules.ts`,迁移期间保持不变。不使用 `app_settings`,不新增迁移表、余额、凭证或请求结果存储,也不需要提前为玩家创建记录。 +补偿配置集中在 `packages/shared/src/manual-migration/rules.ts`,迁移期间保持不变。不使用 `app_settings`,不新增迁移表、余额、凭证或请求结果存储,也不需要提前为玩家创建记录。 抽取在服务端进行,完整事务提交后才向玩家返回本次随机和自选结果。若请求中断,刷新原功法列表并检查背包/仓库:原记录仍在时可重试;已经成功兑换则原记录已删除,重复请求无法再发奖。工具不保存或重放上次抽取结果。 @@ -35,9 +35,9 @@ 原表中有效角色的旧功法兑换完成且异常处理完毕后,可删除: -- `src/server/routes/api/manual-migration/` -- `src/react-app/routes/game/manual-migration/`、`src/react-app/routes/admin/manual-migration/` -- `src/shared/manual-migration/`、`src/shared/contracts/manualMigration.ts`、本文档 +- `apps/api/src/routes/api/manual-migration/` +- `apps/web/src/routes/game/manual-migration/`、`apps/web/src/routes/admin/manual-migration/` +- `packages/shared/src/manual-migration/`、`packages/shared/src/contracts/manualMigration.ts`、本文档 - API、admin、React router、后台 nav、gameNavigation 的入口,以及 CaveQuickGrid 的快捷入口配置 未兑换完不得直接删除工具,需要另行确定收尾方案。补偿玉简为普通既有物品,感悟果的完整效果存于通用消耗品实例;领取后均不依赖本工具,没有新增永久道具定义、货币或 SQL 表,也没有专属状态需要清理。 diff --git a/docs/nestjs-migration.md b/docs/nestjs-migration.md new file mode 100644 index 00000000..adaea3f2 --- /dev/null +++ b/docs/nestjs-migration.md @@ -0,0 +1,932 @@ +# NestJS 后端迁移 + +## 目标与边界 + +最终由 NestJS 承载全部 HTTP、SSE、原生 WebSocket、调度与消息生命周期,移除 Hono 运行依赖和构建插件。保留 React、共享战斗规则、Zod 契约、Better Auth 会话、Drizzle、Redis CAS、NATS JetStream 及资源/奖励提交语义。 + +采用按功能划分的模块、构造器注入、单例无请求状态的服务、显式导出与导入。共享引擎与仓储不依赖 Nest。遵循项目测试规范,不引入服务器、数据库或第三方服务 mock 单元测试。 + +## 可行性与方案取舍 + +可行。主要迁移对象是传输层和进程生命周期:原有应用服务、共享战斗引擎、Drizzle仓储与资源提交机制已有独立边界,可以继续复用。Nest的收益在功能模块、依赖注入、统一鉴权与生命周期管理;没有基准数据支持吞吐或延迟会因此改善,也不需要同时拆微服务、替换ORM或重做数据库模型。 + +- HTTP采用Nest+Express;Controller负责契约、身份和响应,功能Service编排已有应用服务。保留Zod,避免同时维护class-validator DTO及前后端共享Schema。 +- Better Auth继续拥有认证原始请求;业务Guard、Pipe、Filter分别处理授权、输入和异常。API方法、路径、JSON结构、Cookie及错误顺序都属于兼容范围,不能只按Controller数量验收。 +- POST SSE保留现有事件格式、AbortSignal和业务幂等;原生WebSocket保留既有协议,不改Socket.IO。重点验证掉线、重连、权限撤销、终局及结算,不重写战斗核心。 +- Nest生命周期统一拥有调度和消息设施。迁移双入口并存阶段要避免重复启动Cron或消费者;上线仍按已有锁、消息去重、outbox与事务规则运行,不另加一条奖励发放路径。 +- 工具链使用pnpm workspace与Turborepo,运行Nest编译产物采用Node 24。开发watch、镜像生产依赖、健康检查和停机信号一起验证。 +- 实施顺序为基础设施适配→按功能迁入HTTP→SSE/WS/调度→协议及真实玩法验收→默认入口与构建切换→生产灰度和回滚演练。当前代码已完成入口切换,生产发布仍应以剩余验收收口为前置条件。数据库结构未随框架迁移改变,回滚以固定旧镜像及其匹配的Compose健康检查配置为基础。 + +## 当前阶段(2026-10-02) + +框架代码迁移与monorepo工具链切换已完成:`apps/api`(Nest)、`apps/web`(React/Vite)、`packages/shared`由pnpm workspace管理,Turborepo编排构建、类型检查与开发任务。API、Docker和watch均运行Nest,旧Hono入口、路由、构建插件和运行依赖已移除,无Hono兼容转发层。44个Module复核无循环模块导入或重复类provider;配置校验、单一Drizzle连接池和退出排空沿用已验收实现。 + +第五十八批在最终pnpm依赖树下通过lint、类型检查、Nest/Vite构建、冻结安装、Docker构建与production模式本地健康检查,浏览器验证已有登录会话和背包读取。完整共享测试为2401通过/2项迁移前已存在的失败。本次完成的是代码及本地交付,未发布真实生产;下表的特殊数据、自然续期和生产验收仍未完成,不将其记为通过。以下分批记录保留当时命令和状态,现行命令见[本地开发](local-development.md)。 + +第五十三批交付审计:当时42个Module、77个Controller共362条展开后的HTTP路由声明,无重复method/path;旧348条路由清单中仅Better Auth通配入口由Nest外层原始请求处理器承载,其余已覆盖。当前lint与完整构建通过,服务端产物与已验收amd64镜像的文件SHA-256一致;两项共享测试失败已在迁移前原提交/原锁文件独立复现。路由覆盖和构建通过不等于全部业务分支或生产发布验收通过。 + +以下为第五十六批起保留的发布/业务分支验收条件。代码与工具链交付完成不表示这些外部条件已满足。 + +| 剩余验收 | 当前限制及证据 | 继续所需条件 | +| --- | --- | --- | +| 自然会话续期与响应Cookie | 现有可操作浏览器会话尚未达到updateAge;第五十四批正常访问没有续期。数据库有较老会话,但未读取其令牌来构造登录 | 既有浏览器会话自然达到一天阈值;已知local2最早为2026-10-02 09:41:47 UTC,以实际updatedAt为准 | +| 私有寄售成功与可见性 | local1—local8活跃角色的统一库存均无贵宾符;物品库唯一对应条目已归档,声望/宗门货架均无对应来源 | 已合法持有贵宾符的本地测试角色或正常可用来源 | +| 旧消耗品/灵种取出及旧功法/法宝兑换 | 八个测试账号的活跃角色均无数量大于0的旧消耗品或旧灵种,也无creation_products记录 | 含相应历史存量的本地测试数据 | +| 多人讨伐胜利奖励、蜃楼等境界受限玩法 | 八个现有角色最高筑基中期;当前最低讨伐目标金丹50级,第四十六批实战落败。境界差距不证明所有策略必败,但现有证据不足以验证胜利发奖 | 具备正常完成相应玩法条件的测试角色/队伍;不为覆盖而提高属性或伪造胜利 | +| 目标生产配置、灰度及回滚 | 本地amd64 production模式与双实例检查不等于真实目标环境;第五十三批已询问本轮是否包括生产交付,尚无答复或目标环境信息 | 明确生产交付范围及目标环境;此前不擅自部署 | + +其余历史批次未穷举的业务分支仍按各自记录保留,不能因框架迁移而声称整个游戏全部业务测试通过。两项共享测试原有失败也继续单独列明,不修改断言制造全绿。 + +已实现: + +- Express adapter、原生 WsAdapter 与 ESM 服务端构建,显式 `@Inject` 不依赖编译器隐式生成设计类型元数据。 +- Better Auth 原始请求处理器独立于框架,保留 ALTCHA、OTP 首次注册昵称检查和原认证管理路径屏蔽。 +- 默认要求登录的 AccessGuard,可声明 public、active、admin、account-admin;透传会话续期 Cookie。 +- Zod Pipe、异常 Filter、请求日志、CORS、Origin 校验、BYOK 校验及现有 Redis IP 限流。 +- AsyncLocalStorage保存框架无关的身份和LLM配置,由Nest请求层填充;后台任务无请求上下文时维持服务端路由选择。 +- 角色引用与缓存失效从 Hono middleware 抽离;服务层不再反向依赖该 middleware。 +- 认证、验证码、账号密码、健康检查、玩家资源快照/事件窗口、每日占卜 Controller。 +- POST SSE 传输及取消信号适配;占卜服务继续复用原有发奖和事务逻辑。 +- `/api/realtime` Gateway,保留频道、身份作用域、心跳、连接配额、Origin、Cookie 和背压处理。 +- 现有消息基础设施由Nest生命周期管理;Nest Schedule使用迁出的十项UTC调度定义,保留发布去重键。 +- 社交、剧情、任务、邮件、角色名号/属性/状态、修仙日志、反馈,以及游戏壳的战斗活动和宗门身份查询已接入 `AppModule`。 +- 背包、灵兽、功法、宗门战斗养成、训练、野外战斗、突破试炼、宗门任务战斗、自动策略、回放、狩猎队伍、秘境、蜃楼幻境、竞技场、炼器、铭文、悟道、炼丹、丹方、市场、拍卖、灵田、声望商店、黑市、排行榜、社区配置、敌人查询、宗门组织/社交、角色生成/保存/删除、命格生成/重塑、身份重塑、公开天机、赞助、internal cron、闭关/历练、旧产物与功法/法宝迁移已接入。全量静态清单核对及后续socket普通HTTP补齐见第十七、十九批;Better Auth通配入口与原生WebSocket单独处理。代码接入不表示全部方法和异常路径均已运行验收。 +- 竞技场房间服务和开战编排通过 Nest factory provider 注入;竞技场 Gateway 接入参战者及观战者两类动态 URL。升级请求由同一个 WsAdapter listener 分派,保留通用实时频道的固定路径,两类 Gateway 共用 BYOK 和 IP 限流检查。竞技场连接在销毁阶段先关闭订阅、等待异步连接与 presence 清理,再进入消息设施/数据库/Redis 的退出流程。 +- 业务请求体由`JsonBody`的首个Pipe在Guard之后按原始字节读取、解析,之后才执行Zod等校验Pipe。参数工厂保持同步,避免Nest 12将未解析的Promise直接交给校验器(修复见第二十四批)。不按Content-Encoding自动解压,保留原Bun的128MiB读取上限;Webhook独立限制256KiB。每个Controller显式保留原JSON解析失败回退(undefined、null或空对象);Zod仍是输入契约,不引入第二套DTO校验。方法级Filter保留各接口的领域错误、锁冲突及状态码。 + +第二批接入范围(38 个 HTTP 方法): + +| 模块 | 对应入口 | 方法数 | +| --- | --- | --- | +| Social | world-chat/messages、friends 列表/查询/邀请/添加/移除 | 7 | +| Story | 剧情读取、演出完成、教学完成 | 3 | +| Tasks | 列表、详情、挑战、领奖 | 4 | +| Mail | 列表、未读数、发送、单封/批量领取、单封/批量已读 | 7 | +| Cultivator Profile | 转世、旧装配 410、灵气日志、名号、属性预览/分配/重置、兑换码 | 8 | +| Cultivator Condition | 使用消耗品、灵泉恢复、肉身突破预览/执行、洗髓破限 | 5 | +| Player Journal / Feedback | 修仙日志、反馈提交 | 2 | +| Combat / Sects | combat-v6/activity、sects/current/context | 2 | + +第三批接入范围(52 个 HTTP 方法): + +| 模块 | 对应入口 | 方法数 | +| --- | --- | --- | +| Inventory / Beasts | 背包读取与操作、灵兽列表/融合/领取/阵容/休息/加点/改名/放生 | 11 | +| Training / Wild | 训练、野外探索和战斗会话、指令、结算、自动与放弃 | 18 | +| Replays / Auto Strategy | 历史、私有回放、分享创建与公共回放、自动策略 | 7 | +| Breakthrough / Sect Tasks | 突破试炼和宗门任务战斗 | 10 | +| Manuals / Sect Combat | 个人功法、宗门构筑/流派/养成 | 6 | + +第四批接入范围(37 个 HTTP 方法): + +| 模块 | 对应入口 | 方法数 | +| --- | --- | --- | +| Hunts | 活动列表、大厅、奖励读取、建队、入队、队伍指令 | 6 | +| Dungeon | 开始/状态/推进/恢复/退出、历史/次数、搜刮、战斗会话及旧接口 410 | 19 | +| Tower | 状态/开始/推进/排行榜、战斗会话及旧接口 410 | 12 | + +第四批继续调用既有领域服务,保留秘境资源事务、狩猎队伍 Redis CAS 和蜃楼结算路径。各入口分别保留原有错误响应,不统一改写秘境恢复、战斗指令与旧接口状态码。狩猎保留 `private, no-store`;蜃楼旧战斗通配入口排在现有会话入口之后。 + +第五批接入范围(13 个 HTTP 方法及 2 类 WebSocket 路径): + +| 模块 | 对应入口 | 方法数 | +| --- | --- | --- | +| Arena Rooms | 当前房间、房间详情、建房、入房、准备、保活、换队、开战、离房 | 9 | +| Arena Battles | 参战视图、观战视图、指令提交、战局回放 | 4 | +| Arena Gateway | `/api/combat-v6/arena/:battleId/socket`、`/:battleId/watch/socket` | 单独统计 | + +第六批接入范围(18 个 HTTP 方法): + +| 模块 | 对应入口 | 方法数 | +| --- | --- | --- | +| Forging | 炼器读取/铸造、宝库分页/单项取出/本页取出 | 5 | +| Enlightenment / Inscriptions | 悟道与阵纹读取/提交 | 4 | +| Alchemy Formulas | 材料、丹方分页、删除、推演、发现确认 | 5 | +| Craft | 炼丹预览/执行,以及 pending/confirm 旧接口 410 | 4 | + +本批仍复用既有领域服务和应用命令,不改炼制规则或数据库模型。Controller 保留各自错误结构;炼器、悟道、铭文保留 no-store。炼丹旧制作类型在参数校验前返回 410,丹方推演在领域分析前核对材料版本,正式制作继续走角色锁、灵气预留、事务与资源提交。 + +第七批接入范围(20 个 HTTP 方法): + +| 模块 | 对应入口 | 方法数 | +| --- | --- | --- | +| Market | 货架、购买、回收 | 3 | +| Auction | 货单、物品/灵兽上架、购买、下架 | 5 | +| Reputation Shop | 货架、兑换 | 2 | +| Spirit Field | 读取、初始种子、播种、培育、收获 | 5 | +| Black Market | 读取、入场、流式交互、成交、离开 | 5 | + +本批保留原有资源事务、灵气预留、拍卖锁及黑市入场/成交幂等路径。流式交互在准备完成后发送 SSE 头,准备阶段和流式阶段均监听连接关闭。灵田培育/收获继续向原应用服务传递取消信号。 + +第八批接入范围(11 个 Controller 方法,对应 13 个 HTTP 方法/路径): + +| 模块 | 对应入口 | HTTP 路径数 | +| --- | --- | --- | +| Rankings | 三类公开榜单、个人排名、查探、待恢复挑战、V6挑战、三个旧挑战410入口 | 10 | +| Community | QQ群配置、登录页公告 | 2 | +| Enemies | 登录用户所属活跃角色查询 | 1 | + +公开榜单和社区配置不要求登录;个人排名、查探和挑战要求活跃角色,敌人查询保留账号及活跃状态谓词。排行榜继续使用既有 Redis 排名和 V6 幂等挑战服务;丹药、财富和敌人查询的 SQL 与展示映射从旧路由原样迁入功能服务。具名查询参数保留重复参数首值语义;不更换 Zod 契约、共享战斗规则或结算路径。 + +第九批接入范围(新增21个 Controller 方法,对应33组 HTTP 方法/路径): + +| 模块 | 对应入口 | 新增方法/路径数 | +| --- | --- | --- | +| Sect Organization | 设施、俸禄、晋升、任务、候选提交物、商店、建设、成员、转宗与入宗 | 16 | +| Sect Social | 聊天读取/发送、累积贡献榜 | 3 | +| Retired Sect Progression | 七条旧养成路径,各自保留POST和PUT的410响应 | 14 | + +包含已有身份查询在内,两个旧宗门router的34组方法/路径全部接入。组织命令保留 `Idempotency-Key` 与 `source:method:path:canonicalPayload` 的SHA-256指纹、角色锁、事务和资源提交。查询继续按角色或宗门读取资源作用域。宗门聊天保留冷却、成员查询和独立错误契约;旧战报分享拦截Pipe抽至HTTP公共目录供世界聊天与宗门聊天复用。新增 `FirstQuery` 参数装饰器在本批查询对象校验前保留重复参数首值;其他已迁入口的查询兼容仍需收口。 + +第十批接入范围(18 个 HTTP 方法): + +| 模块 | 对应入口 | 方法数 | +| --- | --- | --- | +| Genesis | 角色生成配额/生成、命格生成、角色保存 | 4 | +| Cultivator Lifecycle | 删除、转世上下文 | 2 | +| Fate Reshape | 会话读取/开启、重抽、确认、放弃 | 5 | +| Identity Reshape | 会话读取/开启/草稿、生成、确认、放弃 | 6 | +| Divine Fortune | 公开天机读取/生成缓存 | 1 | + +保持原有JSON接口;这些角色生成和重塑服务原本未接入请求取消,本批不引入新的SSE或取消语义。角色生成保留规范化字符计数、邮箱/IP额度和Redis临时数据;保存/删除保留已有角色应用命令和缓存失效。重塑保留符箓消耗、会话恢复及资源提交。命格确认在路由局部处理JSON解码错误,身份草稿/生成则保留原有全局错误处理边界。公开天机保留24小时缓存和降级内容。 + +第十一批接入范围(18 个 HTTP 方法): + +| 模块 | 对应入口 | 方法数 | +| --- | --- | --- | +| Sponsorship | Webhook、公开配置/名录、个人功德/公开设置、下单意图创建/查询、认领 | 8 | +| Internal Cron | 过期拍卖、排名奖励、市场刷新、两类清理、材料生成、赞助普通/深度对账、赞助清理/摘要 | 10 | + +赞助复用原有签名验证、订单事务、资源履约和脱敏读模型。Webhook在读取请求体阶段限制256 KiB;实际旧应用将超限异常映射成500,本批保留该响应。赞助的中间件输入校验也按旧应用实测的500处理,通过ZodPipe的显式`legacy-unhandled`契约与领域错误区分。Cron使用独立Guard,保留生产缺少密钥500、配置密钥时不匹配401、非生产缺省放行;入口仍调用原有带锁作业,不新增调度或重复消费者。 + +第十二批接入范围(2 个 HTTP 方法): + +| 模块 | 对应入口 | 方法数 | +| --- | --- | --- | +| Cultivator | POST retreat、yield | 2 | + +闭关与历练继续调用原应用命令,保留锁、事务、请求编号幂等及资源事件。命令结算后才发送SSE结果、状态与叙事;连接在准备阶段断开不会取消已经执行的结算,之后不再开始叙事。流式阶段将断开信号传给LLM,闭关叙事仍在finally中执行原历史持久化回调。闭关保留独立路由错误处理,历练保留旧全局错误契约;非法JSON均按旧validateJson回退为undefined后校验。 + +第十三批接入范围(11 个 HTTP 方法): + +| 模块 | 对应入口 | 方法数 | +| --- | --- | --- | +| Legacy Products | 类型分页、单件读取、旧装配/删除退役入口 | 4 | +| Artifact Migration | 可用性、列表、兑换 | 3 | +| Manual Migration | 可用性、列表、兑换、管理员统计 | 4 | + +旧路由目录内的两份兑换服务迁至`apps/api/src/lib/services/legacy`,仅替换身份类型import,两套入口复用同一业务实现。保留来源归属、只读事务隔离级别、兑换行锁与资源提交;旧装配/删除继续410。LegacyItemsModule的迁移路径middleware在Guard之前设置no-store,参数解析继续复用共享Zod契约及undefined回退;迁移路由保留独立错误处理。 + +第十四批接入范围(14 个 HTTP 方法): + +| 模块 | 对应入口 | 方法数 | +| --- | --- | --- | +| Account Administration | 列表、改邮箱、封禁、解封、撤销会话 | 5 | +| Admin Settings / Monitoring | 管理员会话、公告与QQ群读写、LLM指标、在线人数 | 7 | +| Admin Feedback | 反馈列表与状态更新 | 2 | + +账号管理继续要求`ADMIN_USER_IDS`,普通管理入口允许原有ID/邮箱管理员。Better Auth响应分别追加每条Set-Cookie,保留邮箱前置条件、审计、撤销会话及部分成功错误。反馈仍按原有状态更新、奖励与邮件顺序执行;本批不改变该业务流程。设置继续使用safeParse及flatten错误,账号输入校验保留旧全局500契约。 + +第十五批接入范围(11 个 HTTP 方法): + +| 模块 | 对应入口 | 方法数 | +| --- | --- | --- | +| Admin Reputation / Sect Shop | 两类商店列表、创建、更新、归档 | 8 | +| Admin Reward Items | 奖励物品生成 | 1 | +| Admin Tower | 周配置查询、带指纹重生成 | 2 | + +四类Controller与服务注册到AdminModule,继续使用admin Guard。商店调用原有服务并保留局部400、领域状态码及缺失商品404,查询保留重复参数首值。奖励生成复用原有共享生成器与RewardItemSchema,保持随机种子/UUID生成方式;返回草稿不发放物品。蜃楼查询仍从同一快照生成指纹和展示,只有成功读取设置no-store;重生成继续走原仓储的事务行锁与expectedFingerprint比较,不改战斗配置生成规则。 + +第十六批接入范围(29 个 HTTP 方法): + +| 模块 | 对应入口 | 方法数 | +| --- | --- | --- | +| Admin System Mails | 列表、详情、草稿创建/更新、发布、停止 | 6 | +| Admin Item Library | 列表/详情、材料与灵种生成、每日设置、创建/更新/归档、旧预览退役 | 10 | +| Admin Redeem Codes | 列表、创建、启停 | 3 | +| Admin Sponsorship | 配置读写、连接检查、订单读写、快照查看、手动发放 | 10 | + +继续使用admin Guard,JSON局部null回退与safeParse错误保持原样。系统邮件复用共享契约并保留创建指纹、草稿revision条件、发布事务行锁和停止投递锁语义;不额外引入邮件发送路径。材料库仅开放material,旧产物创建/预览保持410。兑换码保留奖励快照、自动码重试和手动码格式检查;赞助继续委托原应用服务并保留领域error/code/status,手动发放成功仍201。路由里的局部catch迁成HttpException时,保留原直接返回的404/400边界。 + +第十七批接入范围(8 个 Controller HTTP 方法): + +| 模块 | 对应入口 | 方法数 | +| --- | --- | --- | +| Dev Tools | 物资发放、角色调整、背包清空、占卜重置 | 4 | +| Legacy Inventory | 旧背包分页、丢弃、鉴定退役 | 3 | +| Legacy Battle Records | 旧战绩根路径和子路径统一410 | 1 | + +DevToolsModule仅在`allowsLocalDevTools(APP_ENV, NODE_ENV)`为true时导入,并保留请求期环境检查与no-store。角色调整先校验ID再解码JSON,避免非法ID加畸形JSON从原400变成500。旧背包保持原仓储/丢弃应用服务及资源元数据;不把仍有运行逻辑的入口误改成退役。旧战绩入口公开返回原410及LEGACY_BATTLE_HISTORY_RETIRED,不恢复历史战斗实现。 + +开发启动:`bun run dev:api`(或组合命令`bun run dev`)。该命令先类型检查并构建,再由Vite监听源码重建、Node.js监听产物并用SIGTERM停止旧进程后重启,加载本地环境。编译产物`apps/api/dist/main.js`需要node_modules;原临时`build:nest`/`dev:api:nest`命令已合并到默认入口。旧框架对照记录保留在下文,源码历史可从Git读取。 + +## 验收清单 + +- [x] 迁入后台管理、开发工具及全量清单发现的旧背包/战绩遗漏入口;仅表示代码接入,成功写入和异常/并发路径仍按下述记录逐项验收。 +- [x] 修正已复现的请求体大小/自动解压/读取顺序、查询1,000项截断以及WS失败握手JSON/响应头偏差,见第二十二批。 +- [x] 对齐异常查询编码、具名参数的编码别名优先级和查询对象首值语义,见第二十七批。 +- [x] 恢复旧整段路由鉴权覆盖的未知子路径,并核对兜底优先级和相关缓存头,见第二十八批。 +- [ ] 完成会话续期Cookie及其余响应头核对;不把已测样本等同于所有HTTP输入完全兼容。 +- [x] 对齐已识别的旧Hono全局异常行为:未被局部处理器捕获的Zod/JSON错误保留500,局部400/409继续保留,真实协议对照见第十八批。 +- [x] 四类SSE入口均完成代表性断开/恢复及重试检查:占卜、历练、闭关(普通修炼与成功突破)、黑市,见第二十四、二十六、二十八、三十四、三十五批;寿元耗尽等特殊业务分支未运行,不代表穷举所有时序。 +- [x] 核对原41个模块、构造器注入、Provider归属及四项模块导出;第二十八批增加第42个末位兜底模块,仅含两个Controller,没有新增Provider或导出。共享纯函数与应用服务不导入Nest。 +- [x] 验证密码认证POST、ALTCHA开关/校验/并发防重放、账号权限隔离与撤销会话Cookie透传,见第二十四、二十五批。 +- [x] 当前55个后台Controller方法完成普通已登录非管理员403检查,包括旧功法迁移管理入口和account-admin,见第四十九批;不替代管理员成功写入或任意未知路径覆盖。 +- [x] 系统邮件后台草稿完成管理员创建/更新/读取、创建指纹冲突、旧版本及并发更新冲突验证,见第五十一批;未发布或投递邮件,其他后台写入仍未全部验收。 +- [x] 宗门清扫任务完成页面承接、开局、正常清扫、领奖及刷新恢复;四阶段请求重放、缺少幂等键与指纹冲突未重复发奖,见第五十批。不替代其他任务及并发/过期分支。 +- [x] 自动战斗策略完成合法保存/读回/重置,错误流派、技能、资源、状态及客户端角色ID拒绝,见第五十四批;不替代实际战斗中策略执行验证。 +- [x] 经本地Mailpit完成OTP邮件、错误验证码拒绝、首次注册登录及重放拒绝,见第二十九批。 +- [x] 两账号并发调用实际LLM,确认BYOK模型/凭据选择及成功/兜底结果分别归属原请求,见第二十九批。 +- [x] 完成密码注册、未验证邮箱登录拒绝、Mailpit验证链接与验证后的密码登录,见第三十三批。 +- [ ] 补齐会话到期续期;不把OTP注册或新会话登录当作续期分支通过。 +- [x] 通用WebSocket完成拒绝连接、同账号配额释放/重连及断线期间资源事件补读,见第十九、二十二、二十五批。 +- [x] 两个本地Nest实例并存时,资源事件跨进程送达且按账号隔离;旧实例退出后,新实例继续处理读写并接受重连,见第五十二批。不替代实际OpenResty切流、活跃战斗或生产灰度验证。 +- [x] 两个本地Nest实例并发移动同一版本物品时,仅一次提交成功;锁竞争429保留Retry-After,旧版本重试409且无额外变更,见第五十五批。 +- [x] 完成实际擂台参战/观战连接、观战重连、权限撤销、终局资源与回放权限检查,见第二十六批。 +- [x] 完成待完成握手停机验证及通用实时频道慢消费者背压运行验收,见第三十批;不推广为所有频道和负载组合均已覆盖。 +- [x] 完成活跃HTTP排空、30秒连接关闭、启动信号串行清理及开发watch验收,见第二十批。 +- [x] 完成占卜实际生成中SSE的停机排空与重启后奖励幂等,见第二十六批。 +- [x] 完成占卜客户端先断开再停机的结算、重启读取及重试幂等,见第二十八批。 +- [x] 完成本地NATS重启后持久消费者处理已结算事件及原有WebSocket实时订阅恢复,见第三十二批;仅证明所验消费者/频道与同一Nest进程的恢复。 +- [x] 完成历练生成中SSE断开、事务relay发布中停机、重启后新奖励消息结算及同编号重试,见第三十四批。 +- [x] 完成正常成功突破生成中SSE断开/停机、部分叙事持久化及重试幂等,见第三十四批。 +- [x] 完成黑市流式回复与准备阶段断开/停机恢复,见第三十五批;事务relay活跃发布排空及新历练奖励恢复见第三十四批。 +- [x] 新历练奖励消息在消费者数据库事务中断后完成重投、结算与同请求重试核对,见第三十七批;不推广为所有消费者及中断时序均已覆盖。 +- [x] 完成默认入口切换、Docker生产依赖、本地容器运行与停机验证、版本/提交镜像标签和回滚操作文档;未部署生产或运行CI推送,见第二十一批。 +- [x] 重建包含第二十七、二十八批兼容性修复的本地arm64镜像,并在该镜像执行协议及停机检查,见第三十一批;生产模式、amd64与实际灰度仍不在此次运行范围。 +- [x] 移除旧入口、路由、中间件、Hono依赖和构建插件,更新当前项目技能与启动文档;历史验证记录保留原始阶段描述。 +- [x] 炼器完成真实页面成功提交、已提交响应丢失后的页面重试、刷新持久化、版本冲突及幂等指纹冲突,见第三十八批。 +- [x] 随心炼丹与丹方炼丹完成真实页面成功提交、资源/材料/产物持久化及同请求重试;随心炼丹另验证旧材料版本与幂等指纹冲突,见第三十九批。丹方发现确认尚未覆盖。 +- [x] 随心炼丹自然发现新丹方后,完成本人页面保存、刷新持久化、跨账号拒绝及重复确认不新增,见第四十八批;不推广为并发保存或过期时序均已覆盖。 +- [x] 公开拍卖完成页面上架、跨账号API部分/全部购买、购买与买方邮件领取重试、越权领取拒绝、卖方页面领取成交款,见第四十批;私有货单与灵兽交易未覆盖,也不记为双账号浏览器全流程通过。 +- [x] 灵兽拍卖完成双账号独立浏览器上架/购买/邮件领取、个体归属转移和重试验证;私有寄售缺符返回400且持久状态不变,见第四十三批。私有货单成功上架及可见性仍未覆盖。 +- [x] 旧宝库材料完成单项/批量取出、背包满后的储藏室溢出、数量冲突/重复项的整批回滚及越权拒绝,见第四十四批;旧消耗品/灵种成功取出尚无本批运行证据。 +- [x] 黑市完成页面按价成交、库存交付、持久journal防重复扣款/发货及刷新恢复;重试仍递增Redis会话版本并追加提示的既有行为单独记录,见第四十五批。 +- [x] 灵田完成真实页面播种、三阶段自然生长/培育、跨进程恢复及收获入包;五个原请求重试和版本/指纹/阶段冲突未产生重复变更,见第四十一批。 +- [x] 当前源码构建linux/amd64镜像,并完成本地production模式鉴权、开发接口关闭、Cron密钥分支与正常停机检查,见第四十二批;不替代目标服务器及真实生产配置验收。 +- [x] 双人讨伐完成页面组队/准备/开战、落败终局、持久气血法力结算、无奖励及回放权限核对,见第四十六批;多人胜利奖励发放仍未覆盖。 +- [ ] 全量浏览器玩法回归,包括多人战终局、奖励幂等和回放权限。 + +## 第一阶段验证记录 + +- `bun run lint`:通过。 +- `bun run build`:旧前后端入口构建通过,保留现有前端分包警告。 +- `bun run build:nest`:类型检查与 ESM 构建通过;prompt 的 `?raw` 导入可正常构建。 +- 本地 Nest 启动:NATS topology、消费者与 Redis 正常;`/api/health-check` 返回 200 和 up 状态。 +- HTTP 检查:未登录占卜返回 401,非法验证码场景返回 400,认证管理路径返回 404,非法写入 Origin 返回 403,不完整 BYOK 返回 400。 +- 本地浏览器现有 local2 会话:会话读取、玩家资源快照和占卜记录读取正常;捕获到游戏 WebSocket 的 ready 与 ping 事件。 +- Node.js 24.18.0 编译产物 SIGTERM:消息基础设施、数据库、Redis 完成清理,进程退出。Bun 1.3.14 直接运行 Nest 源码时 SIGTERM 后进程未退出,尚未定位根因,因此 Nest 启动命令采用 Node。 +- `bun run test`:237 个文件中 235 通过;2403 项中 2401 通过。失败为材料堆叠测试预期 `[99, 2]`、实际 `[101]`,以及宗门技能内容哈希基线不匹配。两个文件定向复跑同样失败。`packages/shared/src` 与 Vitest 配置未改,lockfile 中原有依赖版本未变;未为框架迁移修改领域断言。 + +占卜页面已显示记录,但本轮页面点击未产生可验证的 POST SSE 请求,不能将流式发奖记作通过。生产环境、外部 LLM/OAuth/赞助和完整游戏流程尚未验证。没有执行数据迁移或部署。 + +## 第二批验证记录(2026-10-01) + +- `bun run lint`、`bun run build`、`bun run build:nest`、`bunx prettier --check apps/api/src`、`git diff --check` 均通过。前端构建保留既有分包警告。 +- 本地运行 Node Nest 编译产物,原 Hono API 未同时启动;使用现有 local2 浏览器会话,数据仅来自本地环境。 +- 邮件页载入 20 封邮件、11 封未读;好友名录显示已有的本地道友1。观察到邮件分页、好友列表、剧情、宗门身份、战斗活动接口均返回 200。 +- 角色名号从空值改为「迁移验收」,POST `/api/cultivator/title` 返回 200,响应包含 `success/data/state`,HUD 与角色页更新。随后通过相同页面恢复为空名号,返回 200,页面重新显示「暂无名号」。 +- 属性分配界面暂选体魄 +1,POST `/api/cultivator/attributes/preview` 返回 200;随后取消,保留原有 75 点待分配属性与六维各 65。未执行实际加点、重置、转世、消耗或发奖。 +- 任务页与修仙日志页显示已有本地记录;肉身修炼页显示铁骨与五轨数据。部分页面复用资源快照,因此不能据此将所有独立 Controller 方法都记为运行验证通过。 +- 未登录邮件 GET 返回 401;未登录名号 POST 即使携带畸形 JSON 也先返回 401,确认鉴权先于参数解析。 +- 浏览器与 API 日志仍观察到未迁移的赞助、背包、宗门聊天和战绩入口 404。这些属于剩余迁移范围,不代表游戏已可完整运行。 +- 邮件发送/领奖、任务挑战/领奖、剧情完成、消耗品/突破、兑换码、反馈写入,以及已登录状态下各种 JSON 回退尚未逐项运行验收。 +- 未新增前端/后端单元测试;共享领域逻辑没有改动,本批不重复运行全量共享测试,第一阶段的两个已知失败仍待独立处理。 +- 本轮启动的 Nest 与 Vite 进程已停止,临时浏览器页已关闭。Nest 收到 SIGTERM 后记录消息设施、数据库和 Redis 清理完成并退出;未将这次空闲退出当作活跃 HTTP/SSE 排空验收。 + +## 第三批验证记录(2026-10-01) + +- `bun run lint`、`bun run build`、`bun run build:nest`、`bunx prettier --check apps/api/src`、`git diff --check` 通过。静态 AST 对照七个原路由文件的 53 个入口(含已有 activity),未发现缺失。 +- 本地 local2 背包显示 21/40 格;主动刷新背包接口返回 200。灵兽页读取返回 200,展示已有三只灵兽,未执行灵兽写入。 +- 实际创建训练会话,为人物和灵兽提交防御指令,结算后进入第 2 回合;创建、提交与结算均返回 200。刷新恢复相同回合和战报,随后通过页面确认放弃,DELETE 返回 200,页面恢复为「开始训练」。该临时训练已经结束。 +- 返回游戏后气血为 955/955、法力 850/850,训练中的 1 点伤害没有持久化;灵石 35977、天地灵气 240/240。 +- 未登录训练查询返回 401;非法公开分享链接返回 404 和「分享链接无效」。尚未逐项验证回放归属与分享、融合/放生、野外结算、突破及宗门任务战斗。 + +## 第四批验证记录(2026-10-01) + +- `bun run lint`、`bun run build`、`bun run build:nest`、`bunx prettier --check apps/api/src`、`git diff --check` 通过;旧前端构建仍有既有分包警告。静态 AST 比较狩猎、秘境、蜃楼三个原 router 的 37 个方法/路径,全部在 Nest 中有对应入口。通配符按两个框架的语法归一后比较。 +- 新 Nest 产物成功启动,NATS 消费者、Redis 正常;未并行启动 Hono API。 +- 本地 local2 页面:秘境状态返回 200;历史分页返回 200,展示 8 条历史,能打开黄枫谷藏经阁地窖的已有详情。此操作未新建秘境、消耗资源或结算奖励。 +- 蜃楼页面正确显示金丹境界门槛,金丹排行榜查询返回 200。只读查询确认现有 local1—local8 角色均低于金丹;本批未修改角色境界,未将排行榜读取记作蜃楼战斗通过。 +- 地图狩猎活动列表与金身尸王大厅返回 200。通过页面创建临时单人队伍,POST `/api/hunts/teams` 返回 200;点击准备后显示「已准备」,刷新仍恢复该队伍和准备状态。 +- 随后通过「离开队伍」发送队伍指令,POST 返回 200;大厅恢复「还没有队伍」,临时队伍已退出。未开始讨伐、未执行奖励领取。该队伍 ID 为 `43109ee1-5f9e-47be-8257-a1a4b62e1212`。 +- 未登录请求 `/api/hunts`、`/api/dungeon/state`、`/api/tower/battle/legacy` 均返回 401,旧接口退役入口也保留鉴权。 +- 秘境完整推进/恢复/战斗结算、蜃楼挑战、多人狩猎与奖励幂等、所有错误分支及已登录旧接口 410 尚待后续验收;竞技场 HTTP/动态 WebSocket 仍未迁入。 +- 本批未修改共享领域逻辑,不重复运行全量共享测试,保留第一阶段两个已知失败记录。未新增服务器/前端单元测试或一次性验收脚本,未执行数据库迁移或部署。 +- 临时浏览器页、Nest 和 Vite 已关闭。Nest 在 SIGTERM 后记录消息设施、数据库和 Redis 清理完成并退出;本次同样没有验证活跃 HTTP/SSE 请求排空。本地 Docker 基础设施保持运行。 + +## 第五批验证记录(2026-10-01) + +- 修正新增参数的 lint 未使用提示和握手 IP 返回类型后,`bun run lint`、`bun run build`、`bun run build:nest`、`bunx prettier --check apps/api/src`、`git diff --check` 通过。完整前端构建保留既有分包警告。 +- 静态 AST 对照两个旧竞技场 router 的 13 个普通 HTTP 方法/路径,全部找到 Nest 对应入口。动态 WebSocket 另外运行验收。 +- 使用 `127.0.0.1:5174` 的既有 local2 会话和 `localhost:5174` 的既有 local1 会话,两者均为已配置的本地 Origin;未重新注册、修改认证配置或角色数据。 +- local2 建房返回 201,local1 凭邀请码加入;双方准备后,页面实时显示两人已准备。开战返回 200,双方进入同一战斗 `40a66919-396e-4616-8724-a120e6975641`;房间为 `arena-ebde7315-eeaf-4591-91d7-e5e0412aff20`。 +- 参战者动态 socket 握手返回 101,并收到 `ready`、`state`、`ping`、`resync`;同时通用 `/api/realtime?channels=player-state` 握手为 101,两个 Gateway 可共用同一 HTTP 服务。 +- 第一回合 local2 人物逃跑、灵兽防御,local1 防御;指令提交返回 200,逃跑失败,双方进入第 2 回合。第二回合 local1 逃跑成功,战斗正常结束,local2 显示胜利。 +- 刷新 local2 战斗页恢复相同第 2 回合终局与战报;归档接口 `/api/combat-v6/replays/:id` 返回 200,页面可跳转第二回合并推进到 6/6 行动及最终胜利。真实本地战绩保留,未删除归档。 +- 双方返回擂台后均显示建房/入房入口,没有遗留候场房间。local2 气血 955/955、法力 850/850、灵石 35977、灵气 240/240;local1 气血 1241/1241、法力 830/830、修为 620/2500、感悟 49/200,页面显示与战前一致。 +- local2 未加入观战席时,观战 HTTP 返回 403,页面显示「请先凭邀请码进入房间观战席」。无 Cookie 的参战者和观战者 WebSocket 升级请求均返回 401。 +- 停机前重新确认已结束战局的 socket 仍握手 101 并收取心跳;SIGTERM 后观察到战斗 socket 与通用 realtime socket 关闭,消息设施、数据库和 Redis 清理完成,进程退出。该项只覆盖已连接且战局已终结的场景,不代表活跃结算、HTTP/SSE 请求排空已完成验收。 +- 尚待验证:有权限观战者的广播/权限撤销、四连接配额及释放、慢消费者、握手中断、活跃回合断线恢复、重复指令、待开战 202 和失败重试。此次没有修改共享引擎,不重跑全量共享单元测试,第一阶段的两个已知失败继续单独记录。 +- 临时浏览器页及 Nest/Vite 测试进程已关闭,Docker 基础设施保持运行。默认 Hono 入口与生产镜像未切换,未部署。 + +## 第六批验证记录(2026-10-01) + +- `bun run lint`、`bun run build`、`bun run build:nest`、本批文件的 `bunx prettier --check`、`git diff --check` 通过。全量构建后再次构建 Nest,恢复被默认构建清理的 Nest 产物;保留既有前端分包警告。 +- 通过 TypeScript AST 对照五个旧 router 的注册方法与新 Controller:5 + 2 + 2 + 5 + 4 = 18,方法/路径没有遗漏或新增。该核对不等于所有请求分支完成验收。 +- 本地 Node Nest 与 Vite 使用现有 local2 会话。丹方列表 GET 200,账号暂无丹方;炼器读取 GET 200,现有背包没有图纸;悟道页面读取并选择聚灵书,显示 25% 成功率、1 灵气及1感悟的前端预览,未执行参悟。 +- 炼丹预览 GET 200。先投入青纹藤叶1份,再以青纹藤叶和紫蕴灵参各1份尝试,两次 POST 均调用了现有 LLM 和应用命令,最终返回领域错误400「本炉药蕴不足,无法凝成丹药。」。代码定位到 `alchemyServiceV2.ts` 原有产量检查,在材料和灵石扣除前抛错,事务中灵气预留一并回滚;没有修改该规则以获得成功验收。刷新后灵石35977、灵气240/240、青纹藤叶3份、紫蕴灵参1份均保持原值。成功炼丹和丹方发现仍未验证。 +- 铭文实际绘制成功:青溪磨石、青溪铁砂、青溪兽骨、青溪灵玉各投入1份,产出「定神阵纹」1件;灵石35977→35970,灵气240→239,四种材料数量分别3→2、2→1、4→3、5→4。刷新后资源保持一致,合成物品栏可见新阵纹,读取接口200。保留本次真实本地玩法产物和消耗;没有通过数据库修改恢复它们。截图:`/tmp/daoyou-nest-verification/inscriptions-created.png`。 +- 未登录炼器GET返回401;未登录炼丹POST携带畸形JSON也返回401,确认这些请求在JSON解码前经过鉴权。 +- 尚待验证:炼器成功/版本冲突、宝库取出、悟道提交、铭文合成与烙印、炼丹成功/重复提交/材料版本冲突、丹方推演与发现确认。没有为验证删除已有丹方,也未准备或修改测试账号数据。 +- 本批未修改共享引擎,不重跑全量共享单元测试,第一阶段的两项失败仍作为已知基线记录。默认入口和生产镜像未切换,未部署。 +- 本轮临时浏览器页、Nest 和 Vite 进程已关闭。Nest 收到 SIGTERM 后完成消息设施、数据库和 Redis 清理并退出;Docker 基础设施保持运行。 + +## 第七批验证记录(2026-10-01) + +- `bun run lint`、`bun run build`、`bun run build:nest`、本批文件 `bunx prettier --check`、`git diff --check` 通过。静态 AST 核对五个 router 的 20 个方法/路径,无遗漏或新增。 +- 本地 local2 在市场购买露凝草1份,POST 与刷新 GET 均200;灵石35970→35916,背包增加该药材。截图:`/tmp/daoyou-nest-verification/market-purchased.png`。 +- 灵田初始种子领取 POST200,刷新可见青木藤籽3份、赤炎火核2份;初始领取按钮消失,六块田仍为空。没有播种、培育或收获。 +- 将上批产出的定神阵纹1件以100灵石公开上架,POST200;随后下架 DELETE200,货单消失。仅领取对应「拍卖行物品返还」邮件,刷新背包确认阵纹返还,背包25/40,灵石35916。货单 ID 为 `1bf77a38-aa7a-44da-ba36-40a7572725fc`,没有遗留活跃测试货单。截图:`/tmp/daoyou-nest-verification/auction-returned.png`。 +- local2 黑市读取按境界门槛403。local1筑基角色首次免费入场,两次尝试均遇到原有 black-market-observations LLM 输出 Zod 校验失败;接口200返回 retryable,第二次 `entry.replayed` 和 `state.replayed` 为true、费用0,验证凭证重放不重复扣费。未生成可交互会话,未验证 SSE 成功、成交及离开。保留凭证,不改提示词或领域校验来获得通过结果。 +- local1声望商店页面读取正常,声望65、商品0件;没有兑换。未登录拍卖读取401;未登录黑市入场携带畸形JSON仍401。 +- 尚待验证:市场回收/库存冲突、拍卖购买/私有可见性/部分成交/灵兽交易、声望兑换、灵田播种/培育/收获、黑市成功交互/断开取消/成交幂等。真实本地购买、种子、阵纹与费用均保留,没有数据库回写恢复。 +- 未修改共享引擎,不重复运行全量共享测试,第一阶段两项已知失败保留。没有执行数据迁移或部署。临时浏览器页及 Nest/Vite 进程已关闭;Nest SIGTERM 完成消息设施、数据库和Redis清理并退出。Docker基础设施保持运行;本次不代表活跃请求排空验收。 + +## 第八批验证记录(2026-10-01) + +- `bun run lint`、`bun run build`、`bun run build:nest`、本批文件 `bunx prettier --check`、`git diff --check` 通过。AST 对照三个旧 router 与新 Controller,排行榜10、社区2、敌人1个方法/路径均匹配。当前全部 Controller 共198个方法;数组路径展开后201条注册,不含 Better Auth middleware 与原生 WebSocket。 +- 本地匿名 HTTP:健康检查200;公告和QQ群配置200;炼气榜200;财富榜200共10人;丹药榜200共1件。重复 `realm` 及 `type` 参数按首值处理;旧装备榜410,非法类型400。未登录个人排名和敌人读取401;未登录查探、V6挑战、旧挑战入口即使携带畸形JSON仍401。 +- 现有 local2 浏览器会话:炼气榜初始3人,个人未留名、次数30/30;查探本地道友5返回200并显示V6面板;财富榜和丹药榜切换正常,各自请求200。待恢复挑战与个人排名读取200。 +- 实际挑战林归尘成功,POST `/api/rankings/challenge-battle/v6`200,回放读取200。请求与战局 ID 均为 `c09a02c9-2482-4cfb-806f-d49ccbef435d`,结果 victory,local2排名3、对手排名4,剩余挑战29次。 +- 刷新相同挑战页面后恢复回放;返回榜单排名3、次数29/30。再次通过浏览器返回相同挑战链接,捕获POST200及响应体:同一战局、同一结果和29次,未重复扣次数。保留真实本地排名和战绩,不回写数据库撤销。返回榜单气血955/955、法力850/850、灵石35916、灵气240/240、声望65。截图:`/tmp/daoyou-nest-verification/rankings-challenge.png`。 +- 敌人查询已静态核对 `id + userId + active` 查询条件和原有灵根映射,并验证匿名401。浏览器直接导航本地API地址被客户端阻止,未完成已登录成功/跨账号404运行验证;未尝试绕过该限制。 +- 尚待验证:排行榜已登录非法JSON/参数、查探不存在角色、旧挑战410、越境切磋、空榜直接入榜、并发挑战/失败恢复/次数耗尽、社区配置读取失败503和敌人归属边界。已验证的单请求重放不代表所有并发故障路径均通过。 +- 本批没有修改共享引擎,不重复全量共享测试;第一阶段两项已知失败保留。没有新增服务端/前端测试或验收脚本,没有数据库迁移或部署;默认Hono入口和生产镜像尚未切换。 +- 临时浏览器页及本轮 Nest/Vite 进程已关闭。Nest SIGTERM 记录消息设施、数据库和Redis清理完成并退出;Docker基础设施保持运行。空闲退出不代表活跃请求排空验收。 + +## 第九批验证记录(2026-10-01) + +- `bun run lint`、`bun run build`、`bun run build:nest`、本批文件 `bunx prettier --check`、`git diff --check` 通过;保留现有前端分包警告。首次静态核对命令误用了Bun stdin选项,只显示帮助;改用Node后实际执行AST核对:组织31组、社交3组方法/路径均匹配,没有缺失或额外入口。 +- 本地Node Nest与Vite复用local2会话。宗门大殿周俸读取200,显示可领1,575灵石;领取POST200,灵石35,916→37,491,刷新后显示「本周周俸已经入账」,余额保持37,491。保留本次真实本地领取与状态。截图:`/tmp/daoyou-nest-verification/sect-stipend.png`。 +- 领取后HUD余额立即更新,但对话仍暂留领取按钮;刷新后才消失。本批没有改变前端或原有资源提交响应,记录此界面现象,尚未对照旧服务运行来归因;不能将其当作重复领取或幂等冲突验收。 +- 同门名录GET200,显示本地道友2与本地道友7两名幽都弟子;贡献榜GET200,local2累积贡献5、排名1,另一成员贡献0;日常任务GET200,显示清扫山门、巡视矿场和灵矿采掘,未接受新任务。 +- 宗门总览设施GET200,舆图正常加载;宝库页面显示「须晋升外门弟子后开放」,未触发购买。角色仍为记名弟子,贡献5、灵石37,491;没有改动成员境界或身份准备验收数据。 +- 匿名任务/成员/聊天查询401,非法分页也先鉴权;匿名周俸领取、任务动作和退役POST/PUT携带畸形JSON均401。 +- 尚待运行验证:组织命令同键重放/异载荷冲突、缺失幂等头、重复周俸、晋升、入宗/转宗、捐献、商店购买、任务接受/提交/领奖、候选材料、分页边界、已登录退役410,以及聊天发送冷却/跨宗门隔离。没有发送测试聊天消息。静态对齐和一次周俸成功不代表上述分支均通过。 +- 本批没有修改共享引擎,不重复全量共享测试;第一阶段两项已知失败保留。未新增服务端/前端单元测试或验收脚本,未执行数据库迁移或部署。默认Hono入口与生产镜像尚未切换。 +- 临时浏览器页及本轮Nest/Vite进程已关闭。Nest SIGTERM记录消息设施、数据库和Redis清理完成并退出;Docker基础设施保持运行。此次不覆盖活跃HTTP/SSE排空。 + +## 第十批验证记录(2026-10-01) + +- `bun run lint`通过;首次全量构建暴露新增Genesis服务的IP类型写为`string | null`,已按现有`getRequestIp`修正为`string | undefined`。重跑`bun run build`及随后`bun run build:nest`通过,保留现有前端分包警告。本批文件`bunx prettier --check`与`git diff --check`通过。 +- TypeScript AST对照七个旧router与新Controller:2+1+1+2+5+6+1=18,方法/路径均匹配。全体Controller实际计数237;此计数不表示所有分支完成运行验收。 +- 只读查询本地数据库确认local1—local8均邮箱已验证、各有一个活跃角色;测试域内没有无活跃角色的账号。现有本地账号背包没有天机逆命符/改天换地符。首次查询误写Better Auth邮箱验证列名,查阅schema后用`emailVerified`正确重查;未执行任何数据库写入或准备临时角色。 +- local2的命格重塑页面正常展示未开启、天机逆命符0张;身份重塑页面正常展示改天换地符0张,开启按钮均禁用。未消耗符箓、修改命格/身份或删除现有角色;此检查只证明状态页面可用。 +- 匿名配额、角色生成、命格生成、角色保存、角色删除、转世上下文及重塑请求均401;POST/PATCH携带畸形JSON也先返回401。 +- 公开`/api/divine-fortune`首次GET200,返回生成内容;再次GET200且`cached:true`,验证生成和缓存读取。未验证上游失败降级与缓存损坏恢复。 +- 尚待验证:已登录生成输入边界/限额、角色生成→命格生成→保存完整流程、命格重抽限额、临时数据过期、已有角色保存冲突、删除成功/跨账号权限、转世上下文,以及两种重塑的开启/草稿/生成/确认/放弃与并发恢复。没有通过删除既有角色、修改账号或造符箓来消除验收前置条件。 +- 本批未改共享引擎,不重复全量共享测试;第一阶段两项已知失败保留。没有新增服务端/前端测试或验收脚本,未迁移数据库或部署;默认Hono入口及生产镜像尚未切换。 +- 本轮临时浏览器页及Nest/Vite进程已关闭。Nest SIGTERM记录消息设施、数据库和Redis清理完成并退出;Docker基础设施保持运行。本次不覆盖活跃生成请求或SSE排空。 + +## 第十一批验证记录(2026-10-01) + +- `bun run lint`、`bun run build`、`bun run build:nest`通过;增加显式旧校验契约后再次运行lint及Nest构建通过。本批`bunx prettier --check`和`git diff --check`通过。AST核对赞助8、Cron10个方法/路径,无遗漏或额外入口。 +- 按Hono技能尝试`bun --env-file=env/local.env x hono request --help`,当前hono包没有可运行CLI。改用一次性终端调用现有`app.request()`核对真实本地路由,未创建测试文件或mock。Webhook超限实际返回500及`{success:false,error:'服务器内部错误'}`;公开名录`page=0`的Zod错误同样返回500。该证据推翻了仅按中间件源码推断400的假设,早先迁移入口的同类错误仍需全面核对,不能宣称协议已全部等价。 +- 本轮Nest进程临时设置本地测试CRON_SECRET,未更改env文件。10个Cron入口无密钥均401,错误密钥401;正确测试密钥调用赞助对账200,`processed:0, skipped:true, reason:sponsorship_provider_disabled`。日志只显示本次对账作业执行;没有触发清理、排行榜发奖、材料生成或摘要邮件。 +- 公开赞助配置200、`provider:null, enabled:false`;公开名录200且为空,重复page参数按首值处理。非法page=0返回与Hono一致的500。匿名个人功德、意图查询、公开设置、下单及认领均401,写请求即使携带畸形JSON也先鉴权。 +- 无效Webhook JSON与空对象均400、`{ec:400,em:'invalid webhook'}`;262144字节请求仍进入原处理器并返回无效Webhook400,262145字节请求由大小限制拒绝并返回旧契约500。使用Content-Length与Transfer-Encoding:chunked分别验证边界,结果一致,没有写入有效订单。 +- local2浏览器功德簿可查看我的功德、天下功德及续添功德页签;个人尚未留名、公开榜为空,赞助未开放且购买按钮禁用。没有改变公开设置、创建下单意图、认领订单或打开外部支付页。 +- 尚待验证:真实签名Webhook及重放/履约幂等、意图归属与支付跳转、认领/公开设置写入、生产缺失密钥与非生产无密钥的运行分支、Cron锁竞争/作业失败/各清理和通知任务。上述生产与外部服务行为不能由禁用提供方的跳过响应证明。 +- 未改共享引擎,不重复全量共享测试;第一阶段两个已知失败保留。没有新增后端/前端单元测试或验收脚本,未迁移数据库或部署,默认Hono入口与生产镜像未切换。 +- 第十一批临时浏览器页及Nest/Vite进程已关闭;随后核对赞助验收日志,确认Nest记录消息设施、数据库和Redis清理完成。Docker基础设施保持运行。 + +## 第十二批验证记录(2026-10-01) + +- `bun run lint`、`bun run build`、`bun run build:nest`、`bunx prettier --check apps/api/src/cultivator`及`git diff --check`通过。前端构建仍有既有分包警告。核对旧路由挂载与新Controller路径,均为POST `/api/cultivator/retreat`、`/api/cultivator/yield`;当前Controller HTTP方法总数257。 +- 本地Node Nest使用既有local2会话。一年闭关POST返回200、text/event-stream,结果及资源状态各一个事件;修为2426→2428,天地灵气240→236,剩余寿元2081→2080年,灵石保持37491。闭关编号`d2f42d6f-7713-4c6b-9e32-9e202e90dcb2`。截图:`/tmp/daoyou-nest-verification/retreat-success.png`。 +- 洞府领取约18.37小时历练收益,POST返回200、text/event-stream,收到result、state及26个chunk,无error事件,LLM叙事成功完成。灵石37491→41141,修为2428→2566,感悟70→88;提示另有6件奖励经邮件投递,未在本轮领取或核验全部邮件附件。历练编号`678a537d-382a-4a38-9778-100296563342`。截图:`/tmp/daoyou-nest-verification/yield-success.png`。 +- 通过浏览器临时CDP Fetch拦截,将页面发起的POST替换为上述已完成请求的编号,向真实本地服务重放;历练重放同时将目标改为已观察到的yield路径。两者均200、state.replayed=true;历练重放只含result/state,不再次生成叙事。刷新确认灵石41141、修为2566、感悟88、天地灵气236、剩余寿元2080年,无重复扣费或发奖。这是协议重放验收,不能等同于两个页面的自然重试流程或并发竞争验收。 +- 匿名两入口携带非法JSON均401。已登录用相同临时拦截提交畸形JSON:Nest闭关400、`{error:'闭关突破参数无效'}`;历练500、`{success:false,error:'服务器内部错误'}`。停止Nest后顺序启动原Hono,在同一浏览器会话对照相同输入,两种状态码和响应体完全一致。未修改鉴权、数据库或响应来获得通过;所有请求拦截已清除。 +- 本地真实闭关成本、历练奖励及历史记录保留。尚待验证:突破/寿元耗尽叙事及历史回调、流式中途断开、准备阶段断开、灵气不足、请求编号冲突、锁竞争与活跃请求停机。本轮不覆盖这些边界。 +- 本批未改共享领域逻辑,不重复全量共享测试;第一阶段两项已知失败保留。未新增服务器/前端测试或一次性验收脚本,未迁移数据库或部署,默认入口与生产镜像尚未切换。 +- 临时浏览器页、Nest、对照Hono及Vite进程已停止;Nest记录shutdown complete,Hono正常退出。Docker基础设施保持运行,空闲退出不代表活跃请求排空验收。 + +## 第十三批验证记录(2026-10-01) + +- `bun run lint`、`bun run build`、`bun run build:nest`、`bunx prettier --check apps/api/src/legacy-items apps/api/src/app.module.ts`及`git diff --check`通过,前端既有分包警告保留。使用实际Hono路由清单与TypeScript AST抽取Nest Controller,11个method/path完全一致;当前HTTP方法总数268。 +- 对两份迁出的服务与HEAD原文件作逐字比较,除`ActiveCultivatorRef`从hono/types改为auth/types之外,业务实现相同。没有修改兑换规则、SQL、锁、事务及资源提交逻辑,旧路由仅更新import。 +- 只读查询本地测试域活跃角色,现有角色均无creation_products来源。local2的旧功法、旧法宝页面正常显示空列表,读取200且Cache-Control为no-store;两类availability均200、available=false。本轮没有创建历史物品或执行成功兑换。 +- 11个入口匿名访问均401;包括畸形JSON的兑换请求,以及已经退役的装配/删除入口。local2已配置管理员,管理员功法统计读取200、stats与owners为空;已通过本地身份配置函数核实其权限,没有调整管理员配置。本轮未覆盖普通已登录用户403。 +- 使用临时浏览器CDP Fetch拦截,将刷新请求定向到真实本地接口,验证两类兑换畸形JSON均400、兑换参数无效;有效结构但不存在来源的兑换均409,携带对应来源已兑换/不存在/不属于你的提示。所有拦截在请求后清除,未伪造服务器响应。旧产物非法type返回400,缺失UUID详情404,旧装配/删除410。 +- 重复type、page与pageSize按首值处理:`type=gongfa&type=invalid&page=2&page=bad&pageSize=5&pageSize=bad`得到200、items为空、page=2、pageSize=5。没有为分页迁移引入更严格的新校验。 +- 停止Nest后顺序启动旧Hono,使用同一浏览器会话对照上述8项错误/退役分支及重复参数查询,状态码、错误文案及分页结果一致;响应均no-store。两套API未同时运行。鉴权前的body限制、未知子路径、大小写/尾斜杠仍属于整体协议收口范围。 +- 待验证:有来源时的完整兑换、自选奖励、背包转仓库、跨角色来源、重复/并发兑换、战斗占用、事务失败回滚及非管理员403。本轮资源与历史物品未发生兑换变更,天地灵气自然恢复至237;不将缺失来源409当作成功兑换或全部归属边界通过。 +- 本批未改共享领域逻辑,不重复全量共享单元测试,第一阶段两项已知失败仍单独记录。未新增服务器/前端测试或一次性验收脚本,未迁移数据库或部署,默认入口与生产镜像未切换。 +- 临时浏览器页、Nest、对照Hono和Vite均已关闭;Nest记录shutdown complete,Hono正常退出。Docker基础设施保持运行。本次仍不覆盖活跃HTTP/SSE排空。 + + +## 第十四批验证记录(2026-10-01) + +- 本批14个入口已并入AdminModule;与第十五批合计25组method/path通过TypeScript AST对照旧路由声明,无缺失或额外入口。当前总计293个Controller HTTP方法;数量不代表全部验收通过。 +- 本地local2会话可进入后台;公告、QQ群、反馈列表、LLM指标和在线人数请求返回200。公告为空,QQ群使用内建默认值,反馈列表为空。账号管理全部5个入口返回403,即使提交畸形JSON仍先拒绝权限;没有变更管理员配置来获取账号管理权限。 +- 25个管理入口均完成真实本地匿名请求检查,全部401,写入口携带畸形JSON仍401。此项证明这些输入下的鉴权顺序,不涵盖超限请求体或所有未知路径。 +- 浏览器临时CDP Fetch拦截将刷新请求改为真实本地协议检查:公告/QQ群畸形JSON400,反馈缺失状态400,有效状态但不存在反馈404。随后停止Nest,顺序启动Hono,QQ群错误、两类反馈错误及账号管理5项403,共8项状态码与响应体一致。所有拦截清除并重载页面恢复正常请求。 +- 未执行账号改绑、封禁、解封、撤销会话;现有测试会话无account-admin权限。未保存公告/QQ群、改变反馈状态、发放反馈奖励或发送通知。普通已登录非管理员403、授权账号管理成功路径和会话Cookie变更仍待验证。 + +## 第十五批验证记录(2026-10-01) + +- `bun run lint`、`bun run build`、随后`bun run build:nest`通过;`bunx prettier --check apps/api/src/admin`与`git diff --check`通过。保留原有前端分包警告。构建日志为`/tmp/daoyou-nest-admin-catalog-build.log`与`/tmp/daoyou-nest-admin-catalog-nest-build.log`。 +- 本地Node Nest配合现有local2会话,声望商店展示3件已下架商品,宗门宝库展示2件已下架商品;蜃楼展示2026-W40、本周第一层金丹阵容。读取成功,未改变现有商品与周配置。 +- 通过真实页面“新增商品→选择道具→符箓→生成并预览”调用奖励生成,POST200,得到归元洗髓符箓的consumable.v1草稿,详情正常展示。随后关闭预览、取消新增商品;未保存或发放。截图:`/tmp/daoyou-nest-verification/admin-reward-preview.png`。 +- 两类商店非法status与无效创建/更新体400;不存在UUID的归档404,有效商品结构但不存在UUID的更新404。重复status按首值archived返回200;蜃楼重复floor按首值1返回200。 +- 奖励生成空对象、蜃楼floor=0与空重生成体均500,保留旧Hono实际全局异常契约。历史周重生成400;当前周以确认不匹配的指纹请求重生成409。仓储在指纹比较失败后直接返回,未生成或写入新配置。 +- 停止Nest后顺序启动Hono,用同一浏览器会话比较12项错误/重复参数/缺失更新/指纹冲突响应及符箓生成成功响应,状态码和响应体一致;两套消费者未并行。临时拦截仅修改请求,不伪造响应;协议探测造成承载页面的临时错误提示已通过清除拦截和重载恢复,不作为自然UI流程覆盖。 +- 尚未运行:商店创建/修改/归档成功路径、蜃楼实际重生成及并发竞争、道装/丹药/灵果生成成功与领域错误分支、普通用户403。没有改共享引擎,不重复全量共享测试;第一阶段两项已知失败保留。 +- 未新增服务端/前端测试或验收脚本,未迁移数据库或部署。默认Hono入口、镜像、剩余后台与开发工具入口以及全局协议/生命周期收口仍待完成。 +- 本轮临时浏览器页、Nest/Hono/Vite进程均已关闭,3001与5174无监听;Docker基础设施保持运行。Nest日志确认消息设施、数据库及Redis清理完成,本次为空闲退出,不替代活跃请求排空验收。 + + +## 第十六批验证记录(2026-10-01) + +- TypeScript AST逐文件核对系统邮件6、材料库10、兑换码3、赞助10组method/path,无缺失或额外入口。首次完整构建发现系统邮件参数input与解构字段重名,修正为request后`bun run build`通过;`bun run lint`、`bun run build:nest`通过。源码复查修正赞助领域错误的动态status转译,确保抛出HttpException,未把错误body作为200/201返回。 +- 29个入口匿名真实请求均401,写入口携带畸形JSON仍401。沿用本地local2会话,系统邮件列表显示4条已停用记录,首条详情200;材料库和每日生成设置读取正常(enabled=true、count=20),兑换码显示2条已停用记录,赞助订单为空。 +- 实际页面创建并更新归档材料`nest-migration-20261001-material`,数据库行ID为`05051677-6ab0-4d58-839c-5bcd9a205927`,两次请求均200。材料名称“Nest迁移验收草”,更新后描述标注本地验收;从创建起即archived,未上架、发放或进入角色背包。重新打开编辑器确认最终状态与描述,截图`/tmp/daoyou-nest-verification/admin-material-archived.png`。归档样本保留供后续迁移验收。 +- 临时浏览器请求拦截向实际本地服务检查36项错误/缺失记录/重复查询分支:系统邮件非法query/ID/body500,缺失详情与发布404、缺失更新/停止409;材料非法输入400、旧接口410、缺失详情/更新/归档404;非法兑换码400与缺失启停404;赞助无效配置400、非法查询400、缺失订单/快照/手动发放角色404,并保留领域code。四类列表重复参数按首值读取200。 +- 停止Nest后顺序运行旧Hono,同会话重放上述36项,状态码及响应体36/36一致。该验证为协议检查,不等于自然UI中的全部异常或并发流程。旧helper曾捕获已关闭标签页,未发起请求;重建为显式传递当前tab/CDP后完成全部检查。拦截已清除并重载页面。 +- 未发布/停止实际邮件,未创建兑换码或改变既有启停状态,未保存赞助设置、调用外部连接检查、处理真实订单、查看真实敏感快照或发放赞助。材料批量生成、定时设置写入、归档专用端点成功路径以及普通用户403待验证。没有新增单元测试或验收脚本,没有运行数据库迁移或部署。 + +## 第十七批与全量路由清单核对(2026-10-01) + +- 从实际本地Hono app.routes提取HTTP入口,过滤中间件并保留认证与退役ALL路由,得到348组method/path。首次AST展开遇到宗门retiredPaths常量,补充常量数组解析后重跑成功;未将失败提取当成通过。 +- 全量对照发现旧背包3个入口及旧战绩退役入口遗漏,已补入LegacyItemsModule。最终Nest有330个Controller方法,展开数组与根路径后346组method/path;与Hono清单相差的4组是由configureHttp处理的认证通配入口和3条原生WS路径,已核对对应处理器/Gateway/动态路径分派。Nest显式多出的旧战绩根路径和蜃楼battle根路径对应旧通配语义。这是静态覆盖证据,不证明全部行为或WebSocket普通HTTP访问响应等价。 +- 开发工具4个入口保留纯本地环境限制。使用同一env/local.env与127.0.0.1数据库/Redis/NATS,在本地进程临时将APP_ENV设为staging、NODE_ENV仍development以验证禁用分支;没有加载env/staging.env或访问真实预发布环境。四个入口全部404且不附开发工具no-store,启动日志没有DevTools注册;未以production启动避免开启本地定时作业,production限制本轮仅源码核对。 +- 原Hono与Nest顺序运行,对照16项匿名/开发工具协议(JSON错误顺序、非法ID、缺失角色、未知dev路径、旧战绩410、旧背包401),状态码、body与Cache-Control全部一致。所有开发工具修改/删除请求使用非法或不存在的角色ID,没有改变角色、清空背包或重置占卜。 +- 同一local2会话另外对照11项旧背包协议:三类分页200、重复type首值、无效类型/材料筛选/排序400、畸形或空丢弃请求500、历史法宝丢弃410、鉴定410,响应体与状态码全部一致。未实际丢弃材料/消耗品。 +- 最终`bun run lint`、`bun run build`、随后`bun run build:nest`通过;本批相关目录Prettier与`git diff --check`通过。日志`/tmp/daoyou-nest-route-closure-build.log`、`/tmp/daoyou-nest-route-closure-nest-build.log`;路由清单产物`/tmp/daoyou-hono-route-inventory.json`、`/tmp/daoyou-nest-route-inventory.json`。 +- 共享规则未改,不重复全量共享测试;第一阶段两项已知失败保留。静态入口补齐后,剩余工作转为早期接口协议偏差、认证/权限/请求隔离、SSE与WS故障恢复、活跃请求排空、开发watch、部署产物和默认入口切换;Hono仍未移除,不能标记迁移完成。 +- 本轮临时浏览器页及Nest/Hono/Vite进程已关闭,3001、5174无监听;最后一个Nest进程日志确认消息设施、数据库与Redis完成清理,Docker基础设施保持运行。 + +## 第十八批:输入与路由匹配协议收口(2026-10-01) + +- 扩展`FirstQuery`支持命名参数,将24个早期Controller中的Express `@Query`替换为首值读取。覆盖分页、筛选、资源查询、战斗事件游标等;没有修改Better Auth的请求解析。Express简单查询解析器的参数数量上限及异常编码尚未对照。 +- 以实际旧服务响应纠正早期错误映射:未被局部处理器捕获的Zod/JSON/未知异常统一保留500、`{success:false,error:'服务器内部错误'}`,不向客户端暴露异常message或开发详情。野外探索、灵兽融合/改名、宗门中间件校验、自动指令等在局部catch之外的校验改用显式`legacy-unhandled`;移除无调用方的`default`校验契约。局部业务catch中的400/409和领域code继续由对应Filter保留。 +- 修复Express默认忽略路径大小写及尾斜杠的问题。首次在`configureHttp`设置未生效,真实对照仍返回200;源码确认ExpressAdapter构造时已经安装中间件并创建Router,遂改为在构造适配器之前配置Express实例。最终公开正常路径、大小写变体、尾斜杠、未知路径和API根路径等9项状态码、Location及响应体与旧Hono一致。非API重定向继续302、空响应体。 +- 使用同一local2浏览器会话、顺序启动Hono与Nest,完成60项参数/异常协议对照:59项状态码与JSON完全一致;任务列表剩余一项仅3个目标的`updatedAt`随真实读取刷新,状态码、业务数据和资源信息一致。覆盖非法与畸形输入、重复参数、领域400及通用500。最终适配器修复后的构建又抽查10项,全部与旧基线一致。匿名5项受保护接口返回401,畸形JSON仍先拒绝未登录身份。 +- 协议请求由页面刷新按钮触发,临时CDP Fetch只改请求目标、方法和输入,不伪造响应;每次清除拦截,最后重载恢复正常页面。这不替代自然游戏流程、并发竞争或成功写入验收。本轮未发送聊天/邮件/反馈,未执行领取、购买、开战或角色配置修改。 +- 11项路径探测中有2项已确认未解决:普通HTTP GET访问`/api/realtime`和擂台socket路径,Nest当前404,旧Hono匿名401;第三条观战socket的普通HTTP语义也需一并处理。不要将原生Gateway存在视为普通HTTP访问协议已兼容。未知受保护子路径的中间件顺序、请求体大小/编码、响应头、认证续期、请求隔离及长连接故障恢复仍需继续收口。 +- `bun run lint`、`bun run build`、随后`bun run build:nest`均通过;`bunx prettier --check apps/api/src`与`git diff --check`通过,保留前端既有分包警告。日志为`/tmp/daoyou-nest-input-{lint,build,nest-build}.log`;对照产物为`/tmp/daoyou-nest-input-audit-comparison.json`和`/tmp/daoyou-nest-path-audit-results.json`。未修改共享规则,不重复全量共享测试,第一阶段两个既有失败保留。 +- 默认入口、Docker、开发watch和Hono移除尚未完成。本批没有部署或数据库迁移,不能标记全面迁移完成。 +- 本轮临时标签页和自启动Hono/Nest/Vite进程已关闭,3001与5174无监听;Nest日志确认shutdown complete。Docker基础设施保留,仍未以空闲退出代替活跃请求排空验收。 + +## 第十九批:WebSocket地址的HTTP访问与连接名额(2026-10-01) + +- 增加通用实时HTTP Controller/Guard,以及擂台参与者、观战socket的HTTP回退,共补齐3条已有地址的普通HTTP行为。通用连接保留Origin→登录→配额顺序;擂台保留活跃角色→Origin→UUID→参与/观战资格顺序。真正的升级仍由原生Gateway处理,不经HTTP Controller。 +- 通用实时服务提取可共用的身份/配额预留,升级入口继续执行独立的API admission;HTTP入口已通过Express的BYOK/限流,不重复执行。Guard逐条透传Set-Cookie及其他认证响应头,并在finally释放普通HTTP请求的临时名额,不创建订阅、在线记录或WS pending项。 +- Hono与Nest顺序运行,同一local2会话完成5项已登录路径对照及3项非法Origin对照,状态码与响应体8/8一致:通用已登录普通GET404,擂台非法UUID400、缺失战斗404;非法Origin保留通用`{success:false,error}`与擂台`{error}`两种403结构。另12项匿名/Origin/非法BYOK组合全部一致,证明已验证输入下的校验顺序。 +- 实际复现旧Hono的失败升级名额泄漏:一次普通GET404之后追加4次请求,结果404、429、429、429。源码中事件工厂在确认升级成功前删除pending reservation,失败升级无法释放名额。Nest回退连续6次均404;这是明确修复的旧缺陷,不宣称其泄漏行为也已等价迁移。旧Hono源文件未修改,重启后进程内残留名额清除。 +- 使用独立本地探测IP发起两次匿名普通HTTP请求,均401,限流remaining从9999到9998,每请求只计一次。只验证正常请求计数,不覆盖全局限流上限、失败Redis和所有握手响应头。 +- 真实游戏页面确认通用WS握手101并收到ready,包含local2角色、player-state/world-chat频道及account/cultivator/sect资源scope。增加同账号页面触发连接限制429;关闭一条已连接的测试页面后,失败页面自动重连101并再次收到ready。该实验验证限额触发与名额释放/自动重连,不证明IP40条边界、心跳超时、背压、资源事件漏读恢复或擂台实战/观战恢复。 +- `bun run lint`、`bun run build`、`bun run build:nest`、`bunx prettier --check apps/api/src/realtime apps/api/src/arena`及`git diff --check`通过;前端既有分包警告保留。构建日志为`/tmp/daoyou-nest-socket-{lint,build,nest-build}.log`,协议记录为`/tmp/daoyou-nest-socket-http-{anonymous,authenticated}.json`,脱敏连接事件为`/tmp/daoyou-nest-socket-live-results.json`。 +- 未发送消息、创建擂台、开战、扣费、领奖或修改角色配置;临时请求拦截均清除。共享引擎未改,不重复全量共享测试,原两个已知失败保留;没有新增测试/验收脚本。后续仍需处理未知受保护路径、原生握手失败响应头/body、认证续期与隔离、长连接恢复及活跃请求停机,再切换默认启动/部署并移除Hono。 +- 本轮3个临时标签页及Hono/Nest/Vite进程均已关闭,3001与5174无监听,Nest记录shutdown complete;Docker基础设施保持运行。这次清理仍不替代活跃HTTP/SSE排空验收。 + +## 第二十批:生命周期与开发 watch(2026-10-01) + +- 调整退出顺序:先停止并等待定时发布,排空HTTP与处理器,再停止消息设施、关闭数据库/Redis。控制器与Better Auth的异步处理均登记在请求工作集合,客户端断开后仍等待处理器结束;30秒关闭剩余HTTP连接,60秒全局退出期限避免无限挂起。消息设施启动等待各消费者启动结果后统一处理失败;停机等待事务relay的恢复/发布以及战斗outbox发布。没有修改战斗规则、事务提交或奖励幂等语义。 +- Nest 12 HTTP应用的`init()`未设置基础应用上下文停机所等待的`initializationPromise`。启动期间先记录SIGTERM/SIGINT及退出期限,显式等待初始化,收到信号时跳过监听并清理;正常监听成功后切换至Nest停机钩子。真实本地进程在路由注册期间分别收到两种信号,均在初始化完成后才清理,最终退出143/130。普通启动健康检查Redis/NATS/messaging均up,随后SIGTERM正常清理。 +- 本地故意连接不存在的NATS端口,启动失败并清理、退出1;另以非法PORT=-1令全部消费者启动后监听失败,同样清理并退出1。最初构建发现`app.close()`公共类型不接受signal参数,已修正并重新构建;失败构建后对旧产物的检查不计入新实现验收。最终新产物重跑上述两种失败路径均通过。消费者部分并行启动失败只作代码检查,未人为破坏本地消息拓扑。 +- 真实HTTP请求使用Expect:100-continue和分段JSON上传。SIGTERM后新连接拒绝,既有请求在约1028ms收到剩余请求体并返回401,之后才停止消息设施。另一次始终不结束上传,约30008ms被关闭,随后正常清理。它们验证传输排空和期限,不代表真实业务SSE取消、结算或断线重试已通过。 +- `dev:api:nest`由首次类型检查/构建、Vite build watch与Node 24 watch组成,Node以SIGTERM停止旧进程。实际触碰源码触发重建,日志确认旧进程完成清理后新进程才启动;重建时持有慢请求,该请求约2033ms返回401后才完成重启。新进程健康检查全部up。首次构建含类型检查,增量只转译,不替代提交前lint/build。 +- 组合命令的非TTY外层Bun单进程SIGINT未向全部子进程传播;随后向本次专属进程组发送SIGINT,等价于终端Ctrl-C,全部已记录子进程退出且端口释放。未新增自制进程管理器或一次性验收脚本;不能把单PID退出等同于整组已关闭。 +- `bun run lint`、`bun run build`、随后`bun run build:nest`、相关文件Prettier与`git diff --check`通过。启动竞态修正后再次运行`bun run build:nest`与`bun run lint`通过。原前端分包警告和共享测试两个既有失败保留;本批未修改共享逻辑,未重复共享单元测试。 +- 日志:`/tmp/daoyou-nest-lifecycle-{lint,full-build,build,runtime,timeout}.log`、`/tmp/daoyou-nest-watch.log`、`/tmp/daoyou-nest-bootstrap-{build,lint,SIGTERM,SIGINT,normal,nats-failure,listen-failure}.log`。未验证60秒强退分支、处理中结算的SSE、活跃奖励发布的停机,以及清理钩子失败时的最终退出状态;不将正常退出日志推广为这些分支通过。 +- 本批实际进程均已退出,Docker基础设施保留。未部署或执行数据库迁移,默认入口与生产产物切换继续在下一阶段处理。 + +## 第二十一批:默认入口与生产构建切换(2026-10-01) + +- `dev:api`和`prd:api`改为类型检查/构建后运行Vite watch与Node watch,分别加载local/staging环境;移除临时Nest命令。`build:server`改用Nest的ESM构建,配置合并到`apps/api/nest-cli.json`,删除临时`vite.nest.config.ts`。服务端仅清理`apps/api/dist`,实际完整构建确认`dist/index.html`与`apps/api/dist/main.js`同时保留。`build:client`/`build:server`稳定入口不变。 +- Docker使用Node 24.18.0,构建阶段使用Bun 1.3.14和冻结bun.lock;生产依赖阶段通过package-lock执行`npm ci --omit=dev`。运行阶段仅复制生产依赖、package.json和Nest产物,以node用户运行,明确SIGTERM。实际本地arm64镜像检查UID1000、Node版本正确,`/app/env`、`/app/src`和Bun可执行文件均不存在。依赖清单与npm lock根节点相符,两套锁安装均成功;本批仅改变脚本,未新增依赖或重生成锁文件。 +- 使用专用本地Docker网络与既有PostgreSQL/Redis/NATS运行镜像,显式覆盖NODE_ENV=development,未启用生产Cron。健康检查200且Redis/NATS/messaging均up;验证码配置200、匿名Better Auth会话查询200/null,背包及实时入口匿名401。容器中发送慢上传请求后执行docker stop,原请求约1050ms返回401,随后消息设施、数据库和Redis按序关闭,容器正常退出。最终默认构建命令生成的镜像又完成健康检查及停机检查。未以此宣称生产模式或amd64平台已经运行验收。 +- 生产Compose健康检查由Bun改为Node,停机宽限期75秒。使用local环境文件只解析Compose配置,blue/green两服务均解析为Node健康检查与1m15s宽限期;没有启动生产Compose。`bash -n scripts/blue-green-app.sh`通过,脚本本身未改。GitHub工作流增加版本标签、完整提交SHA标签及OCI标签,继续写latest;未推送标签、触发工作流或发布镜像。 +- 部署文档补充指定版本/SHA/digest与上一镜像回滚方式;若回滚至旧Bun镜像,需要同步恢复旧Compose健康检查。该操作方案仅源码与配置核对,未在真实OpenResty环境演练蓝绿切换/回滚。 +- 实际执行默认`bun run dev`,现有local2会话在浏览器正常进入洞府。认证、资源、宗门、功德、战斗活动、剧情、任务、战绩、占卜读取均200;WS升级101并收到ready。未执行领取、战斗、聊天、账号变更等业务写入。浏览器页已关闭,向本次专属进程组发送SIGINT后API/Vite/watch父子进程全部退出。 +- `bun run lint`、`bun run build`、两次`docker build -f docker/Dockerfile.app -t daoyou-nest:local .`、相关文件Prettier和`git diff --check`通过,前端既有分包警告保留。日志:`/tmp/daoyou-nest-cutover-{lint,build,docker-build,docker-runtime}.log`、`/tmp/daoyou-nest-docker-{build,runtime}.log`、`/tmp/daoyou-nest-default-dev.log`。共享引擎未改,未重复共享单元测试;原两项已知失败保持单独记录。预发布`prd:api`仅检查命令与环境选择,未连接远程服务执行。 +- GitHub工作流YAML与技能frontmatter已由Bun YAML解析检查。技能Python验证器因本机缺少PyYAML未能执行,未将其记为通过;未为此安装额外依赖。 +- 更新README、AGENTS、开发/测试文档和后端安全技能,使新工作使用Nest入口。旧Hono源码与依赖仍保留,长连接异常与认证边界仍有待验证项;不能标记全面迁移完成。两个临时验收容器均已停止并移除,本地镜像保留,基础设施容器继续运行。 + +## 第二十二批:升级拒绝响应与请求读取(2026-10-01) + +- 真实旧Hono升级请求证实:拒绝握手返回JSON,携带CORS、限流及会话响应头。Nest适配器统一执行升级API admission,随后调用各功能服务的身份/配额检查,成功与失败均透传响应头;不再在服务里重复限流。握手工作登记到请求工作集合,停机等待其结束后才关闭基础设施。 +- 保留通用实时Origin→session与擂台session→active→Origin顺序,擂台Origin错误继续使用`{error}`,其余错误保留`{success:false,error}`。原`ws`拒绝回调会拼接数组头为逗号字符串,且小写content-type无法覆盖内建的同名大写头;首次检查暴露JSON仍标记为text/html,遂使用经过Headers校验的字段序列化失败响应,独立输出各条Set-Cookie。没有读取或记录用户Cookie值。多Cookie续期的真实运行分支尚未触发,当前此项为源码证据。 +- 顺序运行Hono与Nest,对照6项匿名升级(允许/拒绝Origin、非法BYOK、参与者/观战路径)。最终状态码和JSON均一致,CORS/Vary/限流头保留;Content-Type统一为`application/json; charset=utf-8`,不声称与旧服务的有无charset/空格逐字一致。另在仅本地进程临时将IP限额设为1,两次升级分别401、429,限额/remaining均正确,第二次包含Retry-After:60。未修改环境文件或全局Redis限流配置。 +- 真实对照发现原Express raw解析的三类迁移偏差:查询超过1,000项被截断、1MiB新限制先于鉴权返回413、Content-Encoding自动解压或拒绝编码先于业务处理。现使用显式raw-body依赖按原始字节读取,业务读取和JSON解析都放到Guard之后;大小上限按Bun公开类型中记录的默认128MiB保留,Webhook独立保持256KiB。Node querystring设置maxKeys=0,保留既有FirstQuery首值行为。方法级领域Filter把读取超限交给全局413响应。 +- 8项请求体/查询对照全部与旧服务状态码和body一致:超过1,000项的末尾page、编码参数、畸形参数、匿名1.1MB请求、匿名伪gzip头、实际gzip字节、公开接口伪gzip头、Webhook伪gzip头。另验证1.1MB合法JSON但无业务必需字段的开发请求返回400;声明Content-Length为128MiB+1但不发送大体积内容时返回413。未传输128MiB实际数据,不将此项当作完整分块上限验证;413的JSON结构也没有与Bun原生超限body做逐字对照。 +- Webhook262144字节仍进处理器并返回无效Webhook400,262145字节返回旧契约500。匿名受保护请求仅发请求头、不发正文,也立即401,确认不再等待未授权请求体。没有以这些无效输入触发资源发放、创建角色或有效支付订单。 +- 复用local2浏览器会话,临时修改刷新请求检查闭关畸形JSON400、历练畸形JSON500、公告畸形JSON400以及1.1MB缺失公告字段的JSON400,保留既有文案/校验结构;未保存公告。拦截清除后进入洞府,通用WS升级101并收到ready,随后关闭临时页面。 +- 新请求读取方式下重新验证停机:公开开发入口收到分段`{}`无效输入,SIGTERM后约1104ms补齐正文并返回400,再关闭消息设施与数据库;始终不结束正文时约30117ms连接重置,处理器结束后依赖清理完成。此前匿名401慢上传用例不再代表当前实现,因为Guard现在会立即拒绝。此轮仍不代表真实SSE结算取消已通过。 +- 新增raw-body直接依赖并同步bun.lock/package-lock.json。npm锁更新意外移除了5项既有WASM可选依赖记录,已依据HEAD恢复这些无关记录;保留必要依赖标记变化。最终Docker构建中的冻结Bun安装和npm生产安装均成功。 +- `bun run lint`、`bun run build`通过;补充握手工作登记后再次运行`bun run build:server`和lint通过。Prettier/`git diff --check`通过。最终arm64 Docker镜像又完整执行上述8项HTTP与6项升级对照,全部保持预期,并正常停机。未改共享引擎,不重复共享单元测试,原两项既有失败继续保留;未新增测试/验收脚本或执行数据库迁移。 +- 记录:`/tmp/daoyou-{hono,nest}-upgrade-protocol.json`、`/tmp/daoyou-nest-upgrade-after.json`、`/tmp/daoyou-hono-body-query-protocol.json`、`/tmp/daoyou-nest-body-query-{before,after}.json`;最终日志为`/tmp/daoyou-nest-protocol-{lint,build,runtime,boundary,docker-final,docker-runtime}.log`和`/tmp/daoyou-nest-upgrade-final-{build,lint}.log`。只记录脱敏握手信息,不保存完整网络头。 +- 自启动Hono/Nest/Vite进程及验收容器均已关闭/移除,Docker基础设施与本地镜像保留。下一阶段仍需完成旧Hono源码/依赖清理及认证、SSE和长连接恢复的剩余验证,目标尚未完成。 + +## 第二十三批:移除Hono实现与依赖(2026-10-01) + +- 删除旧`src/index.ts`、`apps/api/src/app.ts`、`apps/api/src/routes`、`apps/api/src/lib/hono`、旧Origin适配及内部Cron调度器,共87个本批退役文件。仍被调用的历史物品应用服务保留在`lib/services/legacy`。移除旧入口的TypeScript包含项及无调用的`updateRequestContext`;当前源码、构建入口和项目技能不再导入Hono实现。 +- 删除`hono`与`@hono/vite-build`直接依赖,同步两套锁。ALTCHA包元数据仍声明Hono为可选peer,但最终镜像中两者均无法`require.resolve`,没有重新安装旧运行依赖。Bun冻结安装及Docker中的npm生产依赖安装通过;保留npm锁中五项无关WASM可选依赖记录。 +- 清单包含332个Controller方法、349条展开路由。旧清单348条中Better Auth的`ALL /api/auth/*`由原始Node处理器承接;Nest增加两个退役通配接口的根路径。清单证据为`/tmp/daoyou-nest-retired-route-inventory.json`,静态计数不代表全部方法运行通过。 +- `bun run lint`、`bun run build`及清理孤立函数后的`bun run build:server`通过。全量共享测试为237个文件中235通过、2失败,2403个用例中2401通过、2失败;仍为炼器数量预期和技能内容哈希两个原有基线问题,`packages/shared/src`没有改动。日志为`/tmp/daoyou-nest-retire-tests.log`,不记录成全绿。 +- 最终本地arm64 Docker镜像启动成功,健康检查Redis/NATS/messaging均up。浏览器通过正常退出/密码登录切换至local3,真实`/api/admin/session`返回403;该次登录POST状态未完整捕获,仅以页面身份和会话读取确认成功,后续认证POST证据见下一批。没有修改密码、权限或部署生产。 + +## 第二十四批:异步JSON解析修复与认证/SSE验收(2026-10-01) + +- 在移除Hono后的真实浏览器流程中,local2使用合法UUID领取历练仍返回500。定位到Nest 12的`router-execution-context`不等待自定义参数工厂,`PipesConsumer`直接将Promise交给首个ZodPipe。第二十二批改成异步参数工厂时引入了这个回归;此前错误输入得到预期错误码不能证明合法请求可用,也不再将更早的成功写入记录视为第二十二批版本的成功证据。 +- `JsonBody`现使用同步工厂提供Request,再由首个异步Pipe读取、解析正文,后续校验获得真实JSON。保留137处装饰器调用的接口、解析回退、鉴权顺序和大小限制,不修改领域服务或共享Schema。其他自定义参数工厂经检查均为同步。后端技能补充此Nest 12约束。 +- 浏览器正常退出local3后,local2错误密码POST返回401、正确密码POST返回200,恢复本地道友2会话。另用两个独立临时API会话运行16个并发资源读取,全部匹配各自账号/角色;两次跨角色事件窗口读取均403。两个登录响应各含独立的`better-auth.session_token`,HttpOnly、SameSite=Lax、Path=/;仅记录属性,未输出Cookie值。请求携带不同合法BYOK头,但未观察下游模型调用,不据此宣称BYOK并发隔离已经验证。临时会话最终均正常退出200,浏览器会话保留。 +- 修复前的合法历练请求未生成执行凭据。修复后同一请求`2360b23f-9cf6-4c55-9052-84e46e3949b8`返回200,随后在浏览器响应拦截处制造丢失响应。此时服务端叙事已完成,不能称作生成中取消。以相同编号重试返回200、result和`state.replayed=true`;最终镜像再次重放仍相同。数据库对应`player_journal`只有一条,灵石41141→41479、修为2566→2580、感悟88→90,重放不再增加。该业务当前凭据位于journal,不误用旧`player_mutation_requests`的零行数判定未提交;奖励物品数为0。 +- 通过真实本地API为此前无签的local3正常求签(drawId=`06445505-f6bd-4128-b125-bfad0a9cbbd9`),在解签SSE第一个真实模型`text`事件到达时立即AbortController断开。服务端日志记录上游AbortError并使用预设签文;后续读取确认fallback及rewardGranted均true。只读数据库检查消耗品数量0→1,再以原drawId解签只返回complete、空changes,数量仍为1。保留正常玩法生成的签与奖励,没有重置占卜、造物资或直接改数据库。 +- 合法属性预览200且含preview;闭关畸形JSON400,历练畸形JSON及非法UUID均保持旧500;退役战报请求经解析后在Zod之前返回410,没有发送消息。临时浏览器请求拦截已清除,并重载正常页面。洞府在已提交历练的响应丢失后隐藏重试入口是现有前端行为,本批未改前端;API幂等通过不等于该UI恢复问题已修复。 +- `bun run lint`、`bun run build:server`、`bun run build`及JSON解析文件Prettier通过;重新构建Docker镜像,在新建容器上验证健康检查、合法属性预览和幂等SSE均通过,另8项请求体/查询对照与旧基线相符。共享引擎未改,本批未重复共享测试,沿用第二十三批两项失败的明确记录;未新增服务端单元测试或一次性验收脚本。 +- 构建与运行日志:`/tmp/daoyou-nest-json-pipe-{build,lint,full-build,docker-build,runtime}.log`;协议结果`/tmp/daoyou-nest-json-pipe-body-query.json`;最终页面截图`/tmp/daoyou-nest-json-pipe-browser.png`。会话续期、ALTCHA开启、防重放、其他SSE场景准备阶段取消/结算停机及实时恢复仍未全部完成;不将本批两个场景推广为全部长连接通过。 +- 本轮临时浏览器页已关闭,正常登录的local2会话保留;临时API会话均退出。验收容器已停止并移除,最终日志`/tmp/daoyou-nest-json-pipe-docker-runtime.log`确认消息设施、数据库和Redis完成清理。专属Vite进程组已退出,3001/5174没有监听;本地PostgreSQL/Redis/NATS/Mailpit容器保持运行。没有部署、推送或数据库迁移。 + +## 第二十五批:ALTCHA、会话撤销与实时补读(2026-10-01) + +- 使用第二十四批的最终Nest产物和纯本地基础设施,单独启动绑定127.0.0.1的开发进程。仅该进程临时设置随机ALTCHA密钥,未修改环境文件、认证配置或数据库会话日期。`/api/captcha/config`返回enabled=true,config与challenge都带no-store;未启用生产Cron。 +- 通过真实挑战接口和ALTCHA库求解:缺失载荷、畸形载荷、超长载荷、错误action、篡改签名均400;有效载荷登录200,重放同一载荷400。header和body的captchaPayload两种入口均成功。另一次新挑战由8个并发登录请求使用,恰好1个200、7个400,验证Redis NX防重放在实际并发下生效。所有成功登录的临时会话最终正常退出200,没有输出或保存Cookie值、挑战解答及密钥。 +- 正常退出local3临时会话后,使用该已撤销的原会话请求玩家资源与管理员会话均401,Better Auth会话读取200/null,原生WS升级401。HTTP及WS拒绝均返回session_token、session_data、dont_remember三条独立Set-Cookie,每条Max-Age=0、HttpOnly;WS原始头字段数也为3,确认未逗号拼接。这是实际多Cookie清理路径,不是会话到期续期路径;后者仍未触发。 +- OTP缺少邮箱返回400「缺少邮箱地址」,不存在的邮箱缺少昵称返回400「首次注册请填写昵称」;Better Auth admin根路径和list-users均404。密码重置与发送OTP缺少ALTCHA时均400,没有发送邮件、创建账号或修改凭据。邮件验证码完整成功流程尚未验收。 +- local2通用WS先收到player-state ready,读取角色资源版本492后正常断开。断线期间通过正式解签接口完成既有待领签礼(drawId=`15022f00-6b9e-4c77-b657-8837661dc1a4`,此前已有完整签文,未触发LLM);重连ready身份及scope一致,事件窗口从492补到493,恰好包含一条divination.reward.granted且requiresReload=false。原drawId再请求只回放完成态,窗口仍仅一条奖励事件。保留正常签礼和奖励,不重置数据;此项验证真实API协议恢复,未声称浏览器断网界面或所有擂台恢复已覆盖。 +- local3通用WS收到ready后分别发送二进制帧及513字符文本,均以1003关闭。另一个连接持续不发送应用层心跳回复,约75005ms以4000关闭;实现每25秒检查是否超过50秒,因此不声称恰在50秒截止。关闭后同一会话可以再次收到ready,随后主动正常关闭并退出临时会话。慢消费者背压和所有IP配额边界没有在本批运行验证。 +- 静态审查41个模块、156个带Nest装饰器的类:无静态模块导入环、无重复类Provider声明,无ModuleRef/forwardRef或请求作用域服务。运行启动可解析全部当前依赖;构造器唯一不带@Inject的是显式new并传Schema/错误契约的ZodPipe,不由容器推断依赖。模块只导出SessionService、RequestWorkService、RealtimeService、ArenaRealtimeService,分别用于跨模块鉴权、请求排空和启动时Adapter装配;既有纯函数与应用服务保持框架无关,未为形式统一重写仓储或领域逻辑。 +- 本批未改运行代码,未重跑lint/构建/共享单元测试,沿用第二十四批构建产物;原两项共享测试失败仍单独保留。没有新增测试脚本、执行迁移或部署。 +- `git diff --check`通过。独立本地进程收到SIGTERM后完整清理并退出143,日志`/tmp/daoyou-nest-altcha-runtime.log`含shutdown complete;3001/5174均无监听,基础设施保持运行。临时ALTCHA密钥仅存在于已结束的进程内,正常配置文件和浏览器既有会话未改动。 + +## 第二十六批:擂台恢复与生成中SSE停机(2026-10-01) + +- 对既有已结束战斗发起8次升级后立即断开TCP,随后4条擂台连接均ready,第5条关闭1008;释放一条后替补连接ready,战斗revision仍为9。仅证明这些中止尝试没有耗尽连接名额,无法据此确定每次中止所处的异步握手阶段,也不算待完成握手停机验收。 +- local2与local1通过正常API建房、加入并开始切磋,local3观战;战斗`13094a0b-34fd-443b-9cfb-e11c50b9b926`。参战和观战连接均ready,观战视图不含操控指令。观战断开期间参战者提交AUTO,同requestId及内容重放响应相同且不再推进revision;观战重连后读取revision由0到1。 +- local3正常离开观战后,下一次实际战局广播使其socket关闭1008,后续观战HTTP返回403;参战者持续收到状态广播。两人通过正常AUTO指令完成18回合切磋,local2落败。双方灵石与修行进度(含感悟)和战前一致;只读Redis确认双方占用key为空、战斗presence为0,三个账号房间查询均为空。 +- 回放权限实际复查为参战者local1/local2均200,非参战者local3为404。首轮验证误以为无权限应403,因而客户端检查退出1;查阅既有`arenaReplayV6`确认有意以404隐藏不可访问回放,再按真实契约复查通过。没有把这一验证预期错误改成产品行为变更。 +- local4正常抽取锻造方向签`fd0f2fe6-39db-4814-bd95-0ed5d95286d5`,解签收到首个实际模型text帧时向本轮Nest进程发送SIGTERM。SSE继续返回26个text帧及interpretation、complete,未使用fallback,奖励成功。日志先记录开始HTTP排空,随后解签POST200(2044ms),最后才记录处理器排空、停止消息设施及关闭数据库/Redis,进程退出143。 +- 重启后读到同一签的奖励已提交,再解签只返回complete及空changes;只读核对消耗品数量由0到1且重试后仍为1。该检查验证了客户端仍连接时生成中SSE的停机排空,尚不覆盖客户端先断开再停机、其他SSE业务结算和活跃后台发布故障恢复。 +- 所有临时API会话均已退出,浏览器原有会话保留;没有发送聊天/邮件、变更密码或权限、改写测试夹具。保留正常本地切磋历史与占卜奖励。日志`/tmp/daoyou-nest-arena-recovery-runtime.log`与`/tmp/daoyou-nest-sse-restart-runtime.log`。 +- 本批未改运行源码,复用第二十四批构建产物,不重复lint/构建/共享测试;两个既有共享测试失败仍保留。没有新增测试脚本、数据库迁移或部署。重启后的本地Nest暂留供后续验收,不能把第一进程退出记为所有进程均已清理。 + +## 第二十七批:查询编码兼容(2026-10-01) + +- 直接读取原依赖Hono 4.13.7的查询解析器,与Node querystring对照确认差异:`%E0%A4%A`原样保留而非`�%A`;`%FF%41`整体保留而非`�A`。具名读取`name`时,`%6Eame=first&name=second`返回second;读取整个查询对象时则取first。此前的Express解析加数组首值无法保留这些区别。 +- `FirstQuery`改为解析原URL,保留合法字符解码、加号空格、异常百分号分段回退、对象首值、具名参数的字面键优先级、空键忽略与无参数数量截断。以无原型对象存放参数,避免特殊键影响原型。关闭已无业务调用方的Express查询解析,Better Auth仍直接接收原始请求;同步更新后端技能说明。 +- 新构建运行实际本地HTTP:13项公开具名查询检查通过,覆盖编码别名、重复键、有效与异常编码、空键以及1,100个前置参数;5项已登录旧产物查询确认对象首值和分页。另10组资源查询以真实400响应里的未知资源值对照Hono 4.13.7解码结果,逐项一致,包含不完整UTF-8、合法中文、混合有效/无效编码、空字节及编码重复键。 +- 资源快照检查首次遗漏必填keys,按原契约得到500,导致该次客户端检查退出1;此前13+5项查询已执行通过。补齐`keys=session,profile,currency`后真实读取200,合法`attributes/preview` POST也返回200。没有修改产品来迎合错误检查输入,没有分配属性或改变背包物资;两次临时登录均在finally中退出。 +- `bun run lint`、`bun run build`、两个改动TS文件的`bunx prettier --check`通过;保留现有前端分包警告。共享引擎没有变更,不重复共享单元测试,沿用最近2401通过/2失败的已知状态。本次未重复Docker构建或浏览器全流程,不能将第二十四批镜像称为包含本批修改的新镜像。 +- 构建与运行日志:`/tmp/daoyou-nest-query-final-{lint,build,runtime}.log`。前一遗留验收进程及本批新进程均已SIGTERM退出143,日志确认shutdown complete,3001/5174无监听;Docker基础设施保留。未执行数据库迁移、部署或Git提交。 +- 迁移主体与默认入口已经落地,但上方剩余验收项尚未全部完成,不能将此次局部查询验证宣称为全面迁移验收完成。 + +## 第二十八批:未知路径鉴权与断开后的SSE排空(2026-10-01) + +- 对照Git中旧router的全部`.use('*', …)`范围,确认部分未知路径曾先验证活跃角色/管理员,迁移后直接404。实际修改前请求`/api/combat-v6`及其尾斜杠/深层未知路径、`/api/divination/missing`、迁移工具与四类管理深层路径,均复现缺失兜底鉴权;普通`/api/admin/accounts/no/such/path`原本没有整段鉴权,不纳入修正范围。 +- 新增最后导入的`NotFoundModule`,两个Controller分别声明active与admin,复用全局AccessGuard;仅覆盖旧整段鉴权的9类活跃角色路径(含全部combat-v6子路径)和4类管理路径。已知Controller仍优先,兜底不读JSON、不调用业务服务。狩猎及指定战斗养成路径在鉴权后保留原缓存头;旧迁移入口仍由既有middleware在鉴权前设置no-store。 +- 首轮新产物检查发现`{/*path}`未覆盖只有尾斜杠的路径,检查因此退出1,尚未创建登录会话。调整为`{/{*path}}`并重新构建后完成92项真实HTTP检查:匿名未知/尾斜杠路径401、畸形POST先401、非法Origin先403;活跃角色未知路径404并保留缓存头;local2管理员未知管理路径404,local3普通账号为403。相似前缀、大小写变体、未整体保护的命名空间与未知cron路径继续404。 +- 两个账号读取健康检查、角色资源、战斗活动、占卜、狩猎和两类迁移可用性均200,确认这些已知路由没有被兜底遮挡。没有发送消息、改权限、执行物品迁移或进行这些业务的写操作;临时会话正常退出。新模块没有Provider/构造器/导出,不引入模块依赖环或请求状态,当前共42个模块。 +- local5正常抽取锻造方向签`a0b2084d-2b08-4110-831c-65b1cf4d59e8`。解签收到200及SSE响应头后,客户端未读取任何body帧即Abort,并立即向自启动Nest发SIGTERM;不能据此断言服务器尚未开始生成或socket内没有已缓冲帧。日志顺序为开始排空→占卜AbortError降级→处理器排空→停止消息设施→关闭数据库/Redis,进程退出143。 +- 重启后该签`fallback:true`、`rewardGranted:true`,只读数据库核对消耗品数量0→1;同drawId重试只产生complete,changes为空,数量仍为1。证明本次客户端已断开时,处理器仍完成结算且依赖保持存活;不能推广成其他SSE场景或任意故障时序的验收。保留真实本地签礼,临时local5会话已正常退出。 +- `bun run lint`与完整`bun run build`通过;尾斜杠修正后`bun run build:server`、最终lint、两个改动文件Prettier及`git diff --check`通过。共享领域未改,不重复共享单元测试,既有2401通过/2失败状态保留。本批未重复Docker构建、浏览器全流程或部署,没有新增服务器/前端测试文件或验收脚本。 +- 日志:`/tmp/daoyou-nest-protected-path-{before,after,final-runtime,lint,build,final-build,final-lint}.log`及`/tmp/daoyou-nest-aborted-sse-restart.log`。停机/端口清理结果见本批后续记录;不以被信号终止的前一个进程代替重启进程的清理状态。 +- 重启进程最后也已SIGTERM退出143,日志确认shutdown complete,3001/5174无监听;Docker基础设施保留。最终`git diff --check`通过。本轮完成了两项明确验收缺口,其他未勾选项仍待继续,不标记全面迁移完成。 + +## 第二十九批:Mailpit验证码与真实BYOK并发(2026-10-01) + +- 启动前核对SMTP为`127.0.0.1:11025`、Mailpit API为`127.0.0.1:18025`,数据库为本地`daoyou_local`,没有连接预发布/生产。现有local1—local8均已验证邮箱;本轮经正常邮箱验证码页面新增`local-nest-otp@daoyou.local`,昵称「本地认证验收」,未设置密码或创建角色。 +- 浏览器`localhost:5174`原本已有local1会话,先经正常账号设置退出;`127.0.0.1`的local2会话未动。通过真实表单发送验证码,Mailpit收到仅供本地验收的邮件;错误验证码返回400并显示「验证码错误」,正确验证码返回200,页面进入「尚未凝聚真身」。只读数据库确认新账号`emailVerified:true`且角色数0,资源读取200。没有直接修改邮箱验证状态或认证表。 +- 新账号缺少活跃角色时没有可用的设置退出入口,因此在该本地页面通过实际sign-out API退出200;同一OTP重放返回400。随后经正常密码登录页恢复localhost的local1账号,页面重新显示原角色。保留新OTP账号与Mailpit测试邮件,不删除已有账号或其他邮件。截图`/tmp/daoyou-nest-email-otp-success.png`;验证码、会话Cookie与凭据未输出或保存到验收文件。 +- 两个独立临时会话local6/local7正常抽签,并并发请求解签。local6通过BYOK头使用本地已有阿里云凭据及`qwen3.7-flash`;local7使用明确无效的测试凭据及`deepseek-v4-flash`。真实下游日志分别出现阿里云200、DeepSeek401,没有使用模拟Provider或修改服务器路由表。有效凭据只在进程内读取并发送到本地API,再由既有Provider发送至配置的上游,没有记录请求头。 +- 两次HTTP生成分别在相对0ms/1ms开始,2077ms/186ms结束,时间区间重叠。local6收到24个text帧、`fallback:false`;local7没有text帧、`fallback:true`。结果与各自BYOK选择对应,未串用凭据/模型,也未将无效BYOK默默换成服务器有效凭据。此项验证一次真实并发及两种Provider的隔离,不等同于穷尽所有调度和模型组合。 +- local6签`69ca6a81-8051-4b7d-a2c9-3d7b25e2a72b`、local7签`518ee16b-f199-47a4-99e4-e4e01ffcfe70`均正常结算;只读核对消耗品各从0到1,同drawId重试仅complete且changes为空,数量仍为1。保留正常签礼,两份临时API会话最终均退出。 +- Better Auth当前默认会话有效期7天、续期间隔1天。数据库只读统计存在达到续期时间的历史本地会话,但没有读取其令牌。当前可用的127.0.0.1浏览器会话通过`get-session?disableRefresh=true`读取的创建时间为2026-10-01T09:41:47.441Z,到期时间2026-10-08T09:41:47.440Z,尚未达到续期时间;不能将历史会话计数当作续期成功证据。没有改动会话日期、过期策略或配置来制造通过,续期与密码注册验证链接仍待验收。 +- 本轮没有运行源码变更,复用第二十八批构建,不重复lint/build/共享单元测试或Docker构建。数据库访问均只读,所有业务写入经正式本地API;没有新增测试脚本、数据库迁移或部署。服务器日志`/tmp/daoyou-nest-auth-completion-runtime.log`、前端日志`/tmp/daoyou-nest-auth-completion-web.log`。 +- 临时页面均已关闭,API完成SIGTERM清理并退出143,Vite及其启动进程退出;3001/5174无监听,基础设施保留。localhost恢复local1登录,127.0.0.1继续保留local2登录。整体目标仍待上方剩余验收收口。 + +## 第三十批:待完成握手停机与慢消费者(2026-10-01) + +- 使用现有local2账号及已结束战斗`13094a0b-34fd-443b-9cfb-e11c50b9b926`。核对本地PostgreSQL容器及127.0.0.1:15432映射后,短暂暂停数据库,分别发送通用实时与擂台参战原生升级请求。600ms后两条TCP连接均无响应字节且未关闭;向自启动Nest发送SIGTERM,再等待600ms,进程仍在且未报告清理完成。恢复数据库后两次握手均503并关闭,Nest在10秒内退出143。finally保证数据库恢复;重启后同一战斗读取200,临时会话正常退出。此项使用真实依赖延迟,没有模拟认证或改写战斗数据。 +- 使用local7临时会话建立三条player-state连接,一条Node原生ws客户端调用pause,另两条正常读取。通过正式API读取当前scopeVersion为5的最后一条兼容资源事件,再经现有`publishResourceEvents`及本地NATS Core重新发布100批、每批367份同一已提交事件,约128KB/批。独立Bun内联进程负责调用现有发布服务;未新增资源版本、journal或聊天消息。这是传输行为验收,不代表正常玩法自然产生此批量,也不是性能基准。 +- 暂停客户端恢复读取后收到1013及`backpressure limit`;在它读取关闭帧前,替补连接已ready,确认服务端名额释放。两个正常客户端均保持连接并各收到100条事件消息,API事件窗口与scopeVersion仍为5。全部四条客户端最终关闭,临时会话退出,发布进程flush后关闭NATS连接。 +- 前期探测先因使用不存在的NATS_URL变量退出;随后发现Bun的ws实现不提供所需pause行为,且after=0窗口因旧事件兼容性为空。这两次均未进入发布阶段,已清理连接/会话。最终改用NATS_SERVERS、Node原生ws与当前版本减一的事件窗口后通过,没有为探测错误修改产品代码。 +- 本批没有运行源码修改,复用第二十八批产物,不重复lint、build或共享测试;最近共享测试仍为2401通过/2失败。最终Docker镜像仍需重建以纳入第二十七、二十八批修改。握手日志为`/tmp/daoyou-nest-pending-handshake-runtime.log`,重启日志为`/tmp/daoyou-nest-pending-handshake-restart.log`。未保存一次性验收脚本、执行数据库迁移或部署。 +- 收尾核对重启进程的PID、命令和工作目录后发送SIGTERM;日志确认shutdown complete,进程已退出,3001/5174均无监听。PostgreSQL运行且未暂停,其余本地基础设施保留。Markdown结构检查及`git diff --check`通过。 + +## 第三十一批:最新Docker产物验收(2026-10-01) + +- 执行`docker build -f docker/Dockerfile.app -t daoyou-nest:local .`成功,实际重新执行服务端类型检查与构建,产物包含第二十七批查询解析及第二十八批未知路径鉴权修复。当前本地镜像ID为`sha256:8a38e440a9da05d29bbff6e5aa4a8729840091893f2fc872338b10486d016df9`,平台arm64;没有发布远程镜像。依赖层复用此前冻结Bun及npm生产依赖安装缓存,不宣称本轮重新下载并安装全部依赖。 +- 独立Node检查确认`package-lock.json`根节点dependencies/devDependencies与package.json完全相符。容器内实际检查Node v24.18.0、UID1000、SIGTERM配置,且没有`/app/env`、`/app/src`或Bun可执行文件,package.json无Hono直接依赖。容器运行绑定127.0.0.1:3001,数据库/Redis/NATS仅使用已确认的本地端点;显式development关闭生产Cron,未传入LLM凭据。 +- 首次容器通过健康检查后发现启动命令把允许来源变量写作PUBLIC_WEB_URL,实际配置读取PUBLIC_WEB_ORIGINS。在任何登录前停机并移除该临时容器,改用正确变量重新创建;没有修改环境文件或产品配置。 +- 最终镜像完成22项实际HTTP检查:健康检查中Redis/NATS/messaging均up;匿名未知命名空间及尾斜杠401、畸形JSON先鉴权、非法Origin403、普通未知路径404;具名查询编码别名优先级、重复参数、异常UTF-8以及1100个前置参数符合预期;local2登录后合法属性预览及资源读取200,受保护未知路径404、狩猎深层未知路径保留private/no-store,资源查询对象取首值且保留异常编码。未执行属性分配或其他游戏资源写入。 +- 首轮协议检查错误地把`/api/hunts/missing`当作未知路径,实际上它匹配既有`:eventId`路由并返回400;检查因此退出1,finally正常退出临时会话。查阅Controller后改用`/api/hunts/missing/deep`并完整重跑22项通过。没有把检查预期错误改成业务变更。两轮临时会话均已退出,未输出或保存Cookie及认证响应体。 +- `docker stop -t 75`后容器退出码0,日志依次记录HTTP排空、处理器排空、消息设施停止、数据库/Redis关闭和shutdown complete。本轮没有进行活跃业务结算停机,不能将正常空闲停机推广为其他SSE及后台发布故障恢复通过。日志为`/tmp/daoyou-nest-current-docker-build.log`和`/tmp/daoyou-nest-current-docker-runtime.log`。 +- 本轮仅追加验收文档,没有运行源码变更,未重复lint、完整前端构建或共享测试;共享测试仍保留最近2401通过/2失败记录。未运行数据库迁移、生产Compose或部署。临时验收容器在停机检查后移除,本地基础设施保留。 + +## 第三十二批:NATS重启与消费者/实时订阅恢复(2026-10-01) + +- 启动单独的本地Nest进程(PID12043),复用当前构建;核对NATS容器属于daoyou-local项目及nats服务。整个验收中Nest没有重启,生产Cron未开启。数据库查询使用只读连接;没有修改角色、消费凭据、消息拓扑或数据库会话。 +- 从真实JetStream读取系统邮件受众事件`9a0e64d4-5222-498e-ab12-b664da1e2a9e`,原stream序号2293。数据库已有system-mail-projector-v1消费凭据;代码确认executeDomainEvent先领取唯一消费凭据,已处理时跳过邮件投影。此事件用于验证真实消费者重放,不创建新邮件业务事件。 +- 首轮检查完成一次NATS停止/启动,但客户端finally等待未结束的status迭代器,Node报告unsettled top-level await并退出13,没有形成完整通过证据。随后只读复查:NATS健康、Nest健康200,最后事件序号及ACK仍为2293,没有待处理消息。未将这次检查记为消息消费恢复通过;后续修正客户端清理并完整重跑。 +- 正式重跑:NATS停止时HTTP健康检查503且nats=down,重启后同一Nest进程健康200且messaging=up。重新发布原事件字节,不设置broker去重header,使应用消费凭据必须处理重复事件;保留原业务event.id。新stream序号2294,消费者ACK推进至2294,pending及ack_pending均为0。消费凭据及processed_at、全库邮件数量、资源事件数量、领域死信数量均与发布前相同。原服务端日志记录disconnect/reconnected,没有该次processing failed。此项证明已结算事件的消费恢复与幂等,不声称覆盖新消息中断时的业务结算或事务relay排空。 +- 另用local7临时登录及一条player-state原生WebSocket验证NATS Core。经正式API取得当前版本5的最后一条兼容资源事件,使用既有实时subject/envelope格式发布原事件,socket收到一条player-state.events。再次停止/启动本地NATS,健康检查503→200;同一socket保持OPEN并在恢复后收到第二条相同事件,仅有一个ready帧,没有关闭/重连。API事件窗口及资源版本仍为5,没有资源写入或聊天消息。 +- 所有NATS重启均有finally恢复保障。WebSocket正常关闭,临时local7会话退出,探测客户端连接关闭,本地NATS最终running且healthy。未输出或保存Cookie、认证响应体或凭据,也未保存一次性验收脚本。 +- 本批只追加文档,未改运行源码,未重复lint/build/共享测试;共享测试沿用最近2401通过/2失败的明确记录。日志`/tmp/daoyou-nest-nats-recovery-runtime.log`。没有数据库迁移或部署。 +- 核对PID/命令/工作目录后向本轮Nest发送SIGTERM,进程退出143且日志包含shutdown complete。3001/5174无监听,PostgreSQL/Redis/NATS/Mailpit保持运行。Markdown结构检查及`git diff --check`通过。 + +## 第三十三批:密码注册与邮箱验证链接(2026-10-01) + +- 确认SMTP为本地Mailpit(127.0.0.1:11025)及数据库daoyou_local。复用已有Nest构建并启动Vite;浏览器localhost原有local1会话经账号设置正常退出,127.0.0.1的local2浏览器会话未操作。只读确认目标邮箱不存在后,通过密码注册页面创建`local-nest-password@daoyou.local`,昵称「本地密码认证验收」,沿用项目文档的本地测试密码;没有创建角色或修改已有账号密码。 +- 页面显示验证邮件已发送。数据库只读检查emailVerified=false、角色数0;使用正确密码登录返回403,页面提示邮箱尚未验证并重新发送邮件。Mailpit只读取该新账号的两封验证邮件,没有读取其他收件人的邮件正文。 +- 原邮件验证URL位于127.0.0.1:5174,callbackURL为此次表单所在localhost:5174/game。以真实HTTP请求、手动处理重定向检查原链接,避免改变另一个浏览器域的既有会话;没有改写链接或配置。篡改token返回302且回调错误为INVALID_TOKEN,不签发Cookie,emailVerified仍false。原链接返回302到原callbackURL,数据库变为emailVerified=true,签发HttpOnly、SameSite=Lax、Path=/会话Cookie;使用该Cookie读取实际会话,身份属于新账号且邮箱已验证。 +- API临时会话正常退出200后,重放原验证链接继续302到callbackURL,但不再签发Cookie。该行为符合当前Better Auth已验证用户分支,不把密码验证链接误当作OTP一次性验证码要求重放400。没有输出或保存验证token、Cookie、密码哈希或原认证响应体。 +- 随后在原localhost密码登录页面再次提交,正常进入「尚未凝聚真身」;会话和玩家资源读取200。截图`/tmp/daoyou-nest-password-signup-success.png`。本批完整检查的是浏览器注册/密码登录及实际HTTP验证链接,不声称已演练跨localhost/127.0.0.1域名自动登录跳转;新会话也不是时间到期续期证据。 +- 新账号没有角色,页面暂无退出入口,因此在该页面调用正常sign-out接口退出200。再经正常密码登录恢复localhost的本地道友1,确认原角色洞府显示正常后关闭临时页面。只读终验新账号emailVerified=true、角色0、剩余会话0;保留新测试账号和两封本地验证邮件,没有直接改写认证表或删除数据。 +- 本批无运行源码修改,未重复lint/build/共享测试,最近共享测试2401通过/2失败保持记录。执行只读数据库检查及真实浏览器/HTTP验收,未添加测试文件、一次性脚本、数据库迁移或部署。日志`/tmp/daoyou-nest-password-signup-runtime.log`和`/tmp/daoyou-nest-password-signup-web.log`。 +- 最后核对两个监听进程的命令及工作目录后发送SIGTERM。Nest日志确认shutdown complete,API、Vite及启动命令均已退出,3001/5174无监听;本地基础设施保留。Markdown结构检查和`git diff --check`通过。 + +## 第三十四批:历练发布排空与突破叙事取消(2026-10-01) + +- 只读确认local1历练积累超过24小时,local2炼气后期已有2580修为及90感悟。通过正式本地API进行各一次正常领取/突破,没有调整境界、寿元、物资或随机数来构造成功;数据库探测连接强制只读。以下业务进度与奖励作为正常测试结果保留。 +- local1历练requestId=`a6526666-187c-4370-93f8-425b88b27ff1`。暂停已核对归属的本地NATS容器,领取返回200/SSE;收到第一条真实模型chunk即Abort客户端。资源已正常结算,奖励8件,其事务消息`da55545e-f4a5-4c6e-8ae1-7091d6126492`尚未发布。等到数据库显示publish_attempts=2且published_at为空,证明立即发布与定时relay均尝试处理后,对自启动Nest发送SIGTERM。 +- 日志到达request handlers drained/stopping message infrastructure后,250ms观察期进程仍存活、尚未出现closing database。恢复NATS,Nest完成清理退出。立即发布曾因暂停产生TIMEOUT,relay负责恢复;不将此预期依赖故障写成无错误运行。重启健康后,消息published_at非空,task-projector-v1和yield-reward-projector-v1均有消费凭据。 +- local1灵石474197→483114、修为620→998、感悟49→73;历练奖励邮件由3封到4封,新邮件`c8ff08cc-a57e-479b-9718-d214918f196a`有8件附件。原requestId重试返回相同result和state.replayed=true、无叙事;资源及邮件不再改变,player_journal对应yield_claim操作只有1条。此项实际覆盖一次新奖励消息在发布中断及停机后的恢复,不推广为所有业务消费者事务中断或其他发布器均通过。 +- local2突破requestId=`a5a36bc4-b06e-4091-ac03-7a826c148ebc`正常成功,炼气后期→圆满。收到第一条真实breakthrough-story片段后立即Abort并向Nest发SIGTERM;上游记录AbortError,处理器清理完成后才关闭依赖。重启只读确认突破历史由1条到2条,新增历史保存了2字符已生成叙事;并未等待完整故事或伪造叙事。修为2580→1780、感悟90→105,保留正常突破属性成长。 +- 首轮突破检查在重试时错误地要求完整result与首次完全相同,因重试省略storyType而退出1;它已完成成功突破、取消、停机及历史核对,并正常退出临时会话。查阅现有retreatResultFromJournal确认重试仅返回action/summary,这是原共享契约。随后单独重启并复查原requestId:result与journal契约一致,state.replayed=true、changes为空、无chunk;属性、灵气、境界、修为、感悟及历史均未变化,突破journal恰好1条。没有改产品以迎合错误断言。 +- 日志:`/tmp/daoyou-nest-yield-drain-{runtime,restart}.log`、`/tmp/daoyou-nest-retreat-abort-{runtime,restart}.log`及`/tmp/daoyou-nest-retreat-replay-runtime.log`。所有临时API会话已退出,NATS已恢复running且未暂停;没有操作浏览器既有会话。剩余范围包括普通修炼及黑市取消、寿元耗尽分支、其他后台发布/消费者中断边界,不能将本批两个场景推广为全部长连接验收完成。 +- 本批无运行源码修改,不重复lint/build/共享测试,最近2401通过/2失败状态继续保留。未新增测试文件或一次性脚本,没有数据库迁移或部署。 +- 最后复查进程也已按PID/命令/工作目录确认后SIGTERM退出143,日志包含shutdown complete;全部五次启动均已结束,3001/5174无监听,本地基础设施保留。Markdown结构检查及`git diff --check`通过。 + +## 第三十五批:普通修炼与黑市两阶段取消(2026-10-01) + +- local3通过正常API闭关一年,requestId=`ae24ff21-a641-4c0d-858d-276d6f5d7381`;收到result后断开。年龄18→19、闭关记录0→1,消耗4灵气,对应journal一条。同编号重试replayed=true、changes为空、summary一致,年龄、灵气、修行进度、condition和记录数不再变化。此次普通修炼没有LLM叙事,不记为生成中取消。 +- local1在`TN_YUE_01`与`smiling-keeper`正常进入黑市,session=`1c42e737d19c96986e002f09fb3f8c9939930d52`,复用已有免费入场凭据(cost=0、free=true、replayed=true)。观察生成首次因重复inspection topic失败,既有纠正重试成功;感知生成成功,开场ready、version=1、可交互6次。没有修改提示词、Schema或游戏规则。 +- 首次询问物件纹路且明确不议价/成交,收到第一条真实reply-chunk后断开。Redis会话version=2、turnsUsed=1、pendingTurn清除;NPC正文为预提交的fallback。重试原version=1返回409,不额外消耗轮次。 +- 第二次询问封泥裂纹且不报价。在响应头发出前,只读Redis观察到version=2的pendingTurn,随后断开请求并向自启动Nest发送SIGTERM。原领域服务虽然接收AbortSignal,却未将它传给proposeTurn;本次保留既有语义,完成已预留轮次。日志确认开始排空后准备工作继续完成,处理器结束后才关闭依赖。两次提案均因结构校验失败进入既有fallback;首次实际回复流生成了文本,不能将提案生成记为成功。 +- 重启读取version=3、turnsUsed=2、turnsRemaining=4、pendingTurn清除,累计新增4条消息。正常重新进入返回同一ready会话和版本、免费入场凭据仍为replayed;重试旧version=2返回409,轮次仍为2。随后正常leave(version=3),最终phase=abandoned、version=4、turnsUsed=2,无pendingTurn。货币与背包物品条目数不变。 +- 中断后的收尾复查确认普通修炼年龄19、记录1、journal1,以及上述黑市最终状态。`/tmp/daoyou-nest-black-market-runtime.log`和`/tmp/daoyou-nest-black-market-restart.log`均含shutdown complete;临时API会话已退出,自启动进程已结束,3001/5174无监听。PostgreSQL、Redis、NATS、Mailpit保持运行,NATS未暂停。 +- 四类实际SSE入口已有代表性取消/恢复证据;灵田培育/收获是接收AbortSignal的JSON接口,不计入SSE。寿元耗尽特殊分支未运行,也未为覆盖该分支强制角色死亡。本批没有运行源码修改,未重复lint/build/共享测试;继续保留最近2401通过/2失败的结果。没有新增测试脚本、数据库迁移、部署或Git提交。 + +## 第三十六批:收尾审计与剩余方案(2026-10-01) + +当前结论:迁移实现已落地,整体运行验收尚未全部完成,生产发布未执行。框架迁移可行性已由本地实现、构建、镜像和代表性真实业务验证支持;不能据此承诺性能提升或全量玩法无回归。 + +| 审计范围 | 当前证据 | 结论 | +| --- | --- | --- | +| 功能入口 | 全量旧路由静态核对;当前AppModule接入业务模块,末位NotFoundModule保留旧鉴权范围 | 已迁入;接口清单覆盖不等于全部分支运行通过 | +| 架构与注入 | 42个模块、显式构造器注入;HTTP身份/BYOK通过请求上下文传递;既有共享引擎与应用服务不依赖Nest | 符合本项目采用的Nest架构边界 | +| 认证与协议 | 密码/OTP/邮箱验证、ALTCHA、权限隔离、撤销会话多Cookie、请求体/查询/路径对照 | 已验证所列路径;自然到期续期仍待运行 | +| SSE与WS | 四类SSE取消/恢复;通用实时补读、握手排空、背压;擂台观战恢复、权限撤销、终局与回放 | 已有代表性证据;全量浏览器玩法清单仍未收口 | +| 消息与停机 | Nest生命周期持有Cron/消息设施;relay先排空,依赖后关闭;NATS重启恢复与新历练奖励发布中断恢复 | 消费者事务处理中断尚未实测 | +| 工具链与交付 | dev/prd命令、Vite服务端入口、Node Docker运行产物、Compose停机宽限期、固定镜像标签/回滚文档 | 已切换;arm64本地镜像通过,生产环境及amd64未演练 | + +本次重新核对SessionService、AccessGuard和两类WS的会话响应头传递,以及消费者的事务和ACK顺序:消费凭据与资源变更共用事务,成功处理后才ACK,停机等待活动处理器。该静态证据支持实现设计,不能替代消费者事务中断的运行验证。未改变认证续期策略,也未读取历史会话令牌或修改会话日期。 + +锁文件审计发现`bun.lock`根工作区仍使用历史名称`daoyou-hono`;执行`bun install --lockfile-only --ignore-scripts`后Bun保留该旧元数据,因此仅将根包名对齐为`wanjiedaoyou`,没有更新任何依赖版本。随后`bun install --frozen-lockfile --lockfile-only --ignore-scripts`通过,内联校验确认两份锁文件的根包名、dependencies及devDependencies均与package.json一致,均无已解析的Hono包。ALTCHA依赖元数据中的可选Hono peer声明仍然保留,它不是项目运行依赖,不修改上游元数据。 + +后续按以下顺序收口,不再重复已完成批次或将所有业务规则重测扩展成框架改写: + +1. **会话续期与响应头**:现有可用会话达到真实updateAge后,通过业务HTTP与WS路径检查续期Cookie、有效期及身份保持。当前已知会话创建于2026-10-01 09:41:47 UTC,默认一天后才具备续期条件;以届时实际会话状态为准。重复登录不算续期验证。 +2. **消费者处理中断**:选一条正常本地玩法产生的新消息,在真实处理阶段中断/恢复依赖,核对消费凭据、资源、奖励和ACK;保留发布阶段恢复与消费阶段恢复的证据区别。 +3. **玩法回归收口**:结合第三至十七批的待验证记录,优先补成功写入与恢复路径(如炼器/炼丹、拍卖购买、灵田、黑市成交及多人奖励玩法)。第二十六批已经覆盖擂台终局和回放权限,不重复列为完全空白;擂台切磋不发奖励,也不替代多人奖励玩法。缺少合法数据或权限的路径继续如实标注,不修改业务规则以制造成功。 +4. **发布前验证**:复核两项共享测试失败,完成目标架构/生产配置的镜像验证及灰度/回滚演练。应用框架迁移不包含修正未改动共享规则的测试基线,不将2401通过/2失败改记为全绿;上线沿用固定镜像版本和匹配的Compose配置。 + +本批仅补文档和锁文件根包名,运行源码未变,未重复lint、应用构建、共享测试或Docker构建;最近源码的lint/build通过记录与2401通过/2失败的共享测试记录继续有效。当前本地镜像包含最新运行源码,但构建早于本次锁文件元数据修正。未部署、推送或执行数据库迁移。 + +## 第三十七批:奖励消费者事务中断与重投(2026-10-01) + +- 只读确认local4已有超过24小时未领取的历练,使用正式API领取,requestId=`1d5f3a6a-55de-4b9d-b2eb-ac461c74f63e`。没有修改角色资源、时间或消息凭据。检查前奖励消费者无pending及ACK pending,本地仅本次Nest进程运行。 +- 为观察真实消费者事务,独立本地数据库连接短暂持有`wanjiedaoyou_mails`表的SHARE锁,配置15秒空闲事务超时并以finally执行ROLLBACK。此连接只执行BEGIN/LOCK/ROLLBACK,不写业务行;其余数据库探测连接强制只读。正常历练提交并发布新事件后,`pg_stat_activity`与`pg_locks`确认消费者正在同一事务中等待邮件INSERT,事务XID为21180,已持有消费凭据表的RowExclusiveLock,且等待对象确为本次锁连接。 +- 对自启动Nest PID16543发送SIGKILL,进程退出137,以模拟处理器执行中的进程丢失。首次检查错误地要求仍被表锁阻塞的PostgreSQL连接在释放锁前一秒内消失,导致检查程序退出1;finally释放表锁后再次只读确认所有相关连接已消失。原消息`8fb83afb-a572-4581-a62c-b824b6a42649`已发布、task-projector-v1凭据存在,yield-reward-projector-v1凭据不存在,历练奖励邮件仍为0。证明角色领取事务已提交,而被中断的奖励事务没有留下已提交邮件或消费凭据。 +- 检查程序的自动重启未成功,随后显式重启同一Nest产物(PID16573),健康检查200。实际JetStream显示ACK pending=1、pending=0;保持原两分钟ack_wait配置,不手工ACK、改消费位置或重新发布消息。2026-10-01 11:30:53 UTC完成原消息重投,最终ACK pending=0、pending=0,delivered及ack_floor均为consumer_seq=14、stream_seq=2303。 +- 奖励邮件由0到1,新邮件`255be652-f36b-423b-b9b8-fd3e392a1e60`含8件附件;消息的task-projector-v1与yield-reward-projector-v1消费凭据均存在。灵石4070→6195、修为360→554、感悟0→48,均来自正常领取,重投后未额外增长。 +- 同requestId重试200、replayed=true、不再生成叙事,result与唯一journal中的结果一致。该路径会返回原有5条资源事件,scopeVersion=82;逐项核对事件ID/版本与数据库原事件一致,重试前后角色、邮件、凭据、journal及资源事件完全不变。首次重试检查误要求changes为空而失败,查阅既有`findPlayerMutationRequest`与`PlayerCommandExecutor`确认此路径回放原事件;未修改产品契约。另一次检查误要求账号总会话数为1,因仍有原有会话退出1;最终复查仅清理本次会话,不撤销其他会话。 +- 首次中断检查遗留的临时登录,通过local4正常认证的list-sessions/revoke-session接口,按当前会话之外且创建时间落在本次窗口的唯一会话定位并撤销;未读取数据库会话令牌。其余临时登录均正常sign-out200。最终只读确认local4在本轮开始时间之后的剩余会话数为0,原有会话保留。 +- 所有表锁与检查连接均已释放;重启后的Nest收到SIGTERM,退出143,日志`/tmp/daoyou-nest-consumer-interrupt-restart.log`含shutdown complete。被强制终止的首进程日志为`/tmp/daoyou-nest-consumer-interrupt-runtime.log`,不将SIGKILL记为优雅停机。3001/5174无监听,PostgreSQL/Redis/NATS/Mailpit保持healthy,无相关数据库连接残留。 +- 本批没有运行代码修改、额外测试文件或保存的一次性脚本。使用真实本地API、数据库锁与NATS重投完成检查;未重复lint/build/共享测试,保留2401通过/2失败的最近结果。未部署、提交或迁移数据库。第三十六批的消费者处理中断待办由本批代表性验证补齐,自然会话续期与剩余玩法回归仍未完成。 + +## 第三十八批:炼器成功、页面重试与版本冲突(2026-10-01) + +- 使用当前Nest产物与Vite,在浏览器localhost复用local1既有会话;127.0.0.1的local2会话未动。只读选择已有炼气期法兵图纸与自测玄铁,不发放物资或改写夹具。进入炼器室地火器炉,选剑形、一份玄铁;确认框显示100灵石、7天地灵气、一卷图纸和一份材料。 +- 页面正常确认开炉,requestId=`02158a79-27d8-4d43-b466-813c6cf93503`。真实LLM命名请求200、无重试,服务端POST200。返回道装`0c4775ed-b98f-4a93-8441-db3e78c582e0`「玄铁试锋剑」,10级法兵。背包40格已满且原堆叠仍有余量,产物正常进入storage。 +- 仅对该Fetch响应临时拦截,在确认真实200与产物后丢弃响应,未伪造业务结果。页面出现Failed to fetch及「重试本次开炉」。清除拦截后通过该按钮重试,捕获POST200,请求编号和完整正文均与首次相同;replayed=true,返回产物对象与首次深比较一致。页面显示「玄铁试锋剑」「已收入洞府储藏室」,截图`/tmp/daoyou-nest-forging-retry-success.png`。清除所有拦截并刷新后,页面正常读取剩余图纸3卷、玄铁6份。 +- 数据库只读核对:灵石483114→483014;图纸4→3、revision6→7;玄铁7→6、revision8→9;bag条目保持40、storage条目77→78,产物恰好一件,对应forging journal恰好一条。页面开炉前灵气240/240、开炉后233/240;此前数据库缓存qi为233,不能用缓存前后同值误判为没有扣费,实际事务会先结算自然恢复。 +- 再以临时local1 API会话验证两项冲突:新requestId携带原旧物品revision返回409「物品已变化,请重新备料」;原requestId改写intent返回409「同一幂等键不能用于不同的玩家状态事务」。两者均no-store,前后角色灵石/qi、全部背包与储藏室条目/版本、炼器journal均无变化。临时API会话正常退出200。 +- 检查工具最初使用缺少resourceType的CDP拦截模式,被浏览器拒绝且尚未提交;随后明确限定Fetch。清理采用`Fetch.enable({patterns:[]})`,因为该工具不支持直接Fetch.disable。第一次产物比较使用JSON.stringify,因字段顺序不同返回false,改用深比较确认内容完全相同。一次只读核对误将varchar库存ID数组转成uuid导致查询失败,按实际Schema改为text后完成检查。这些均未引起产品代码修改。 +- 所有临时拦截清除、页面关闭,原浏览器登录保持。Nest与Vite/Bun父子进程均已停止;Nest退出143且`/tmp/daoyou-nest-forging-runtime.log`含shutdown complete,前端日志`/tmp/daoyou-nest-forging-web.log`。保留正常铸造消耗、道装和修仙日志,不回写撤销玩法结果。 +- 本批仅更新验收文档,无运行源码修改,未重复lint/build/共享测试,保留最近2401通过/2失败记录。没有新增测试文件或保存的一次性脚本,未部署、提交或执行数据库迁移。炼器成功与冲突缺口已补,宝库旧物品取出、其他玩法及自然会话续期仍按原清单继续验收。 + +## 第三十九批:随心炼丹与丹方炼丹成功、重试及冲突(2026-10-01) + +- 使用当前Nest产物和Vite,浏览器localhost复用local1既有会话,只读选择已有材料及丹方。没有发放物资、改写夹具或调整随机结果;数据库核对连接强制只读,127.0.0.1的local2浏览器会话保留。 +- 随心炼丹通过页面投入已有药材「10Q新系统奖励」5份,意图为温和补气、调养经脉,确认消耗1840灵石和1灵气。requestId=`dc701402-5c95-411e-a901-74bd4d53ea1f`,POST `/api/craft`返回200,产出凡品「青木归元丹」1枚,物品ID=`ddf420a8-a3b5-4f90-9cc7-d4adbf94ee7c`。背包已满,产物进入storage;灵石483014→481174,材料10→5、revision5→6,页面灵气234→233。对应`alchemy_improvised` journal恰好一条。未返回formulaDiscovery,不将丹方发现确认记为通过。 +- 使用临时API会话重试原随心炼丹请求,返回200、replayed=true、scopeVersion618,产物深比较与journal一致。新requestId携带原材料版本返回409「材料已变化,请刷新并重新投入」;原requestId改写意图返回409「同一幂等键不能用于不同的玩家状态事务」。三次检查前后角色资源、库存、journal与资源事件均不变。现有实现先准备LLM结果再校验材料版本,旧版本检查因此产生实际模型调用;本批未改变此顺序。临时会话正常sign-out200。 +- 丹方炼丹在页面选择已有「青元养脉丹丹方」`0fbed80a-1dd0-4827-a6bc-e59985c60d5e`,投入玄品鸣雷藤与三叶灵参各1份。正常药路推演POST返回200,analysisId=`a4c9ceae-fbc6-4f4b-8f63-d8004f5991b2`,预测玄品1枚。页面确认7360灵石和1灵气后,requestId=`942dae63-adcd-44c4-a6fa-259dc8c6af1a`正式制作返回200,产出玄品「青元养脉丹」1枚,ID=`6d31d900-50a2-42cf-8063-dd0a34017062`。两份材料行消耗后产物进入bag,最终39/40格;灵石481174→473814,页面灵气233→232,丹方熟练度exp3→4、level保持1,对应`alchemy_formula` journal恰好一条。 +- 原丹方请求经实际API重试返回200、replayed=true、scopeVersion620,结果与journal深比较一致;角色、库存、丹方熟练度、journal和资源事件均不再变化,临时会话sign-out200。两次成功结果截图分别为`/tmp/daoyou-nest-alchemy-success.png`和`/tmp/daoyou-nest-formula-success.png`;随心炼丹后刷新成功,丹方成功页已目视核对成丹数量与熟练度。 +- 初次只读快照误用`alchemy:%`匹配操作键,后改为`alchemy_%`及精确操作键核对,未以错误零计数作历史journal结论。没有修改产品以满足检查工具的假设。 +- 页面已关闭,本批未使用Fetch拦截。核对PID、命令及工作目录后停止Nest17555、Vite17558及Bun父进程17556/17557,启动会话均退出143,日志`/tmp/daoyou-nest-alchemy-runtime.log`包含shutdown complete;前端日志为`/tmp/daoyou-nest-alchemy-web.log`。3001/5174无监听,PostgreSQL、Redis、NATS和Mailpit均保持healthy。保留正常炼制消耗与产物,不回写撤销结果。 +- 本批没有运行源码修改,未重复lint/build/共享测试,最近2401通过/2失败记录保持。未新增测试文件或保存一次性脚本,未部署、提交或执行数据库迁移。自然会话续期、拍卖/灵田等剩余玩法及生产发布验收继续按清单收口。 + +## 第四十批:公开拍卖部分成交、售罄与邮件交付(2026-10-01) + +- 复用当前Nest产物及Vite,数据库强制只读选取local1已有地品自测玄铁(6份、revision9),local2已有41479灵石;当前无有效在售货单。通过local1浏览器页面按每份100灵石公开寄售2份,requestId=`dc803544-d1ef-4f95-9df4-72e222f04000`,生成货单`5b5782d7-5310-4000-9f62-703ec589ffd2`。材料变为4份、revision10,没有发放或直接改写准备数据。 +- 打开127.0.0.1页面后,实际身份仍显示local1,与交接记录中local2的假设不符;没有据此购买或切换既有浏览器会话。关闭该临时页,买方改用local2独立临时API会话。因此本批证据为卖方浏览器+买方实际API,不宣称完成双账号浏览器购买流程。首次引用的浏览器变量已不存在,之后使用既有浏览器ID创建页面,没有重新选择浏览器或变更会话。 +- 第一笔购买1份,requestId=`36614644-9249-4822-affe-5104830ea0c5`,返回200;货单仍active且剩余1份,local2灵石41479→41379,local1灵石暂不变化。新增买方物品邮件`97122b3a-f5f8-4108-8982-12b2a6d325a6`(玄铁1份)与卖方成交款邮件`7f662471-9f78-4c1b-b858-cd1ae3ba680d`(97灵石)。卖方刷新页面显示剩余1份,截图`/tmp/daoyou-nest-auction-partial.png`。 +- 第二笔使用新requestId=`1a6a3369-4cc1-4c51-8c69-f45aa09d7cee`购入剩余1份,返回200;货单sold、剩余0,local2灵石41379→41279。新增买方邮件`b4978395-25ae-4bc6-a795-d6855703deac`及卖方邮件`8d1ff772-ee51-49b2-8dcf-1edb55d511e8`,附件各为玄铁1份及97灵石;卖方刷新后我的寄售为0单,没有遗留活跃货单。 +- 两笔购买均各自重试原请求:200、replayed=true、data与原结果深比较一致,数据库角色、库存、货单、邮件、journal及资源事件不变。相同请求编号改quantity=2均返回409;新编号超出剩余量或在售罄后购买均返回404,同样无额外变更。该购买路径没有新增player_journal,实际幂等凭据位于player_mutation_requests;只读确认auction_buy下对应两个`auction-buy::`记录,不将零journal误判为没有幂等持久化。 +- 买方尝试领取本批卖方邮件返回404且状态未变;随后正常领取两封买方邮件,材料数量合计增加2。每封原邮件再次领取均200、replayed=true且全量快照不变。卖方通过页面逐封领取本批两封成交款,不使用一键领取,最终灵石473814→474008。四封邮件均is_claimed=true,总支出200、卖方收入194、手续费6,与页面规则一致。截图`/tmp/daoyou-nest-auction-settled.png`显示两封成交款已领取。 +- 最后只读核对首次误把Better Auth会话列写作snake_case而查询失败;按既有Schema改为带引号的userId/createdAt后通过,未改认证表。三个临时local2 API会话均sign-out200,本批时间窗口内剩余临时会话0;浏览器既有会话保留。 +- 页面已关闭,没有使用网络拦截。核对命令及工作目录后SIGTERM停止Nest18506、Vite18509及Bun父进程18507/18508,启动会话均退出143,日志`/tmp/daoyou-nest-auction-runtime.log`包含shutdown complete,前端日志`/tmp/daoyou-nest-auction-web.log`;3001/5174无监听。保留正常交易及邮件领取结果。 +- 本批仅更新验收文档;执行实际页面、HTTP及只读数据库检查,`git diff --check`与Markdown结构检查通过。运行源码未变,未重复lint/build/共享测试,最近2401通过/2失败记录保持。未新增测试文件或保存一次性脚本,未提交、部署或执行数据库迁移。私有货单可见性、灵兽交易、灵田、黑市成交、自然会话续期等仍按原清单继续收口。 + +## 第四十一批:灵田播种、自然生长与收获验收(2026-10-01) + +- 数据库只读确认local2的6块灵田均为空,已有凡品青木藤籽3份(revision0),各阶段基础时长4分钟。使用内置浏览器既有local2会话,Chrome既有local1会话未动。第四十批只检查了Chrome内的两个地址;本批明确找到内置浏览器中独立的local2,不能仅凭主机名推断浏览器身份。 +- 正常页面播种第1畦,requestId=`field:89812bcb-69eb-466f-94bd-558b1e7923ab`,材料ID=`e86055ae-ab39-446d-b643-40ef878409a6`。种子3→2、revision0→1,开始时间2026-10-01 12:01:22.047 UTC。未发放灵种或回写生长时间。 +- 首阶段选择天地灵气催芽,requestId=`field:1660b6aa-af00-4b7a-a0d8-95f58c195ae8`,页面灵气228→223。真实生成反馈为good,时长169200ms,结束时间12:04:33.878 UTC;田地version4、careCount1。正常等待后页面自动进入蕴灵期。 +- 原播种与首阶段培育请求重试均200、replayed=true、结果与对应journal深比较一致;角色、田地、库存、journal及资源事件前后不变。新编号携旧灵种版本播另一空田409;原编号改田块或培育方式409;生长中再次培育及提前收获409,均无额外消耗或状态变更。首次检查误到player_mutation_requests中查凭据而失败,随后按实际自动journal路径完整重跑通过;两次临时API会话均sign-out200。 +- 蕴灵期正常选择天地灵气催生,requestId=`field:4c29f3c4-999b-41d4-8edc-d0174a4d6785`,页面灵气223→215,真实反馈good,时长169200ms;田地version5、careCount2,结束时间12:08:11.163 UTC。 +- 成型期正常选择凝叶成药,requestId=`field:72c48751-2c87-488d-89d0-0f313cbb8e39`,无需消耗;真实反馈strained,按现有规则时长268800ms,结束时间12:13:33.167 UTC。田地version6、careCount3,保留该实际反馈,没有调整结果以缩短等待。 +- 在生长期间构建本地amd64镜像后,核对自启动Nest18765的命令/工作目录并SIGTERM退出143,日志含shutdown complete。用该镜像在相同本地API端口启动独立容器(development关闭Cron),数据库/Redis/NATS仅连接daoyou-local网络服务。健康检查200且三项设施up;页面刷新仍为原local2身份、成型期及原生长进度。四个原播种/培育请求在容器中重试均200、replayed=true,完整持久状态不变。镜像构建及运行检查另见第四十二批。 +- 自然成熟后通过页面收获,requestId=`field:498c59e3-330c-4c50-b97b-9b06a5b1f4a4`。amd64容器日志确认POST `/api/spirit-field/harvest`返回200(1974ms);实际生成凡品灵草材料「青木凝叶」5份,score65、无变异或降品。产物ID=`7dec54dc-840e-447a-ba6f-c2cb5385daf8`,journal ID=`93abb865-12fa-4199-901a-f98d1e49a041`。背包27→28行,既有库存行数量及版本不变;田地version7、careCount3、harvestCount1,6块田均为空。灵石保持41279,持久化灵气215,页面因自然恢复显示217;收获没有额外货币消耗。 +- 收获后重试播种、三次培育及收获共五个原请求,均200、replayed=true,结果与各自journal深比较一致;完整角色、田地、库存、journal及资源事件快照不变,没有重新种植、重复扣费或多发产物。新编号收获空田409,原收获编号改plotIndex=1返回409,状态均不变。临时API会话均sign-out200。 +- 页面确认收获次数1及空田,再到背包确认「青木凝叶」5份及预览。截图为`/tmp/daoyou-nest-field-success.png`和`/tmp/daoyou-nest-field-product.png`。刷新后的浏览器网络记录未完整保留收获POST,本条HTTP状态证据来自容器日志,请求编号及持久结果来自journal,不记为浏览器网络捕获完整。 +- 页面关闭,原有浏览器登录保留。核对命令及工作目录后停止Vite18768及Bun父进程18766/18767,启动会话退出143;amd64开发容器以`docker stop -t 75`正常退出0,日志含shutdown complete,随后移除。日志为`/tmp/daoyou-nest-field-runtime.log`、`/tmp/daoyou-nest-field-web.log`和`/tmp/daoyou-nest-field-amd64-runtime.log`。没有活动种植或遗留验收进程,保留正常玩法消耗与产物。 + +## 第四十二批:amd64镜像与本地生产模式验收(2026-10-01) + +- 执行`docker build --platform linux/amd64 -f docker/Dockerfile.app -t daoyou-nest:local-amd64 .`成功。构建包含Bun frozen安装、服务端TypeScript检查与Vite构建、`npm ci --omit=dev`;构建日志`/tmp/daoyou-nest-amd64-build.log`。镜像ID=`sha256:1f4c1874edc79887eedd61999e604a07c8f4c385ade52fb19ed721eafc5601f7`,平台linux/amd64,在arm64宿主通过OrbStack模拟运行,并非目标生产服务器实测。 +- 镜像内Node24.18.0、Nest12.1.2、UID1000、停止信号SIGTERM;Nest入口存在,未打包源码、env目录或Bun,没有直接Hono运行依赖。首次通过require读取Nest package.json受包exports限制,改为读取实际文件后确认版本,没有修改应用代码。该镜像在development模式完成第四十一批的恢复、收获及重试。 +- 使用同一镜像、`NODE_ENV=production`、`APP_ENV=local`及本地PostgreSQL/Redis/NATS连接进行独立验收,Cron密钥临时生成且未输出。健康检查200且各设施up;匿名auth/get-session200;拍卖列表401;开发物资接口404;Cron缺少或错误Bearer均401;验证码配置200;未知API404;匿名畸形JSON炼丹请求仍先返回401;非法Origin返回403。共10项检查通过,未调用正确密钥的Cron写入接口。 +- 另启动生产模式缺少CRON_SECRET的本地容器,健康检查200,Cron入口500,符合当前Guard契约。两个生产模式容器均执行`docker stop -t 75`退出0,日志确认shutdown complete并已移除;日志为`/tmp/daoyou-nest-amd64-production-runtime.log`和`/tmp/daoyou-nest-amd64-production-missing-secret-runtime.log`。3001/5174无监听,本地四项基础设施保持healthy。 +- 安装报告6项依赖告警(4 moderate、2 high)。仓库执行只读`npm audit --omit=dev --json`返回1;报告涉及nodemailer9.1.1、undici7.29.0及drizzle-kit/esbuild相关依赖。逐项与HEAD锁文件比较,涉及版本均未随本次迁移变化。没有运行audit fix或升级依赖,也不据告警直接断言应用可被利用;这些属于发布前需要独立评估的既有依赖问题。镜像没有项目锁文件,容器内audit未提供可用清单,以上审计来源为仓库锁文件。 +- 本批没有运行源码修改;镜像构建已执行服务端类型检查/构建,未重复完整客户端构建、lint或共享测试。最近lint/完整build通过,共享测试仍为2401通过/2失败(炼器堆叠预期及技能内容哈希基线),不得记为全绿。没有新增测试文件或保存一次性脚本,未提交、推送、部署或执行数据库迁移。 +- 当前剩余验收为自然会话续期、私有拍卖/灵兽交易、黑市成交、旧宝库取出及多人奖励等尚未覆盖的合法玩法路径。真实生产配置、目标服务器、灰度和回滚仍未执行;本地production模式通过只支持镜像可运行及所列安全分支的结论。 + +## 第四十三批:灵兽拍卖交付与私有寄售缺符回滚(2026-10-01) + +- 复用当前Nest产物和Vite,数据库连接强制只读。local2在内置浏览器、local1在Chrome,各自既有登录保留。只读确认双方已有好友关系;统一背包没有`auction_private_listing`符箓。使用local2已有闲置灵兽「岩甲猪」(物种钢背猪、11级、revision2),ID=`f5cec4c2-d964-4ccd-a581-642a31e9c28b`,没有发放资源或调整编组。 +- 页面按100灵石选择好友local1专属寄售,requestId=`c0869f0b-e777-4763-90ec-fe9c214356a1`,实际POST `/api/auction/list-beast`返回400「缺少随身拍卖行贵宾符」。角色灵石/condition、灵兽、编组、库存、货单、邮件和player_mutation_requests完整快照哈希均与提交前一致。仅证明缺符拒绝及回滚,不将私有货单可见性记为通过。 +- 改为公开寄售,requestId=`223c31f0-7e75-45fe-8874-9005ae147cf3`,页面POST200,生成货单`d15e3a9b-e9fc-4963-93e6-7c0d92265f2e`。灵兽暂离卖方仓,货单快照revision3。临时API会话重试原请求200、replayed=true;原编号改变价格409,新编号重复寄售已经离仓的灵兽409,卖方购买自己的货单403;上述持久快照均未再次变化。 +- 买方Chrome页面确认卖家local2与100灵石后购入,requestId=`0d178fe9-8d5d-4878-a8fd-8107ef096fad`,POST `/api/auction/buy`200。货单sold、剩余0,买方灵石474008→473908,卖方领取前仍41279;生成买方灵兽邮件`b3416632-f8e5-4b8e-bfd2-9444f4f9ef84`和卖方97灵石邮件`aca3bf58-fe3a-461c-8079-ec34e3a48daf`。领取前灵兽仍未归入任何角色仓。原购买请求重试200、replayed=true;同编号改数量409,新编号购买售罄货单404;快照均不变。 +- 双方分别在页面逐封领取本批邮件,没有使用一键领取。卖方灵石41279→41376,买方灵兽仓22→23;灵兽ID和创建时间保留,归属改为local1,revision4,其余individual字段与寄售前逐项深比较完全一致。双方编组及统一库存均不变,没有自动携带或设为首发。两封邮件已领取。 +- 领取后两封邮件原请求各重试200、replayed=true;卖方越权领取买方邮件404;再次重放最初上架与购买请求也均200、replayed=true。角色灵石/condition、灵兽、编组、库存、货单、邮件及mutation凭据完整快照不变,没有重新转移灵兽或重复扣款/交付。四个临时API登录均sign-out200。买方灵兽页面显示11级岩甲猪、成长1.021、寿命1000及原资质,截图`/tmp/daoyou-nest-beast-trade-success.png`;两页控制台未见error。 +- 初次只读查询误写`lineups`列名,按Schema修正为`lineup`后通过;首次输出全量历史快照过长,改为内存深比较及摘要哈希,没有保存验收脚本或改动产品代码。 +- 两个页面已关闭,原浏览器会话保留。核对PID/命令/工作目录后SIGTERM停止Nest20507、Vite20511和Bun父进程20509/20510,启动会话均退出143。日志为`/tmp/daoyou-nest-private-auction-runtime.log`和`/tmp/daoyou-nest-private-auction-web.log`,Nest日志含shutdown complete。保留正常交易、手续费及交付结果,没有活动货单。 +- 本批只更新文档,未改运行源码,未重复lint/build/共享测试;最近2401通过/2失败记录保持。执行浏览器、实际API、只读数据库核对及文档差异/结构检查;未提交、部署或迁移数据库。私有拍卖成功路径仍需合法贵宾符,其他未完成项沿用原验收清单。 + +## 第四十四批:旧宝库材料取出与整批回滚(2026-10-01) + +- 只读查询确认旧consumables表同样没有`auction_private_listing`贵宾符;local1旧材料3种、local2旧材料1种,各2份。复用当前Nest产物及Vite,在Chrome local1和内置浏览器local2的既有会话中验收,没有注入物资。实际旧宝库页面为`/game/cave/storage`,统一储物袋为`/game/inventory`。 +- 正常取出前,用临时local1 API会话检查批量第二项数量不符、同一项重复出现,以及单项取出local2材料:均409。每次前后角色灵石/condition、旧材料/消耗品、统一库存及资源事件完整快照一致。批量首项已经进入事务操作后,后项失败仍完整回滚,没有先发一份新物品或删除旧材料。 +- local1页面点击「取出本页」,实际POST `/api/combat-v6/forging/vault/withdraw-page`200、Cache-Control为no-store。输入为10L验收凝血草`a8518c43-71c6-485b-8714-fd6e56e5eb07`、寒潭白泥`839665b3-0be5-4a3b-94d8-6b7378cdd100`和日志旧附件灵草`8deadb94-6123-41f5-b7b3-748e3c201691`,expectedQuantity均2。三条旧记录移除,转为三条material.v1库存:凝血草入bag(39→40格,ID=`a04f82fb-dd9e-4552-be3e-bf8a7bfcf48c`),白泥和日志灵草入storage(ID分别为`bf66ec98-13b2-47b7-998a-1cb3128590ac`、`90d3a82d-db1e-4427-8bbf-4cf94f292579`)。名称、类别、品质、元素、描述及数量逐项一致,既有库存哈希不变,灵石保持473908。页面进入储藏室确认两种溢出物品各2份。 +- local2页面对「10Q历史草药」点击「全部取出」,POST `/api/combat-v6/forging/vault/withdraw`200;旧ID=`967b2dea-a761-44da-afc6-33a23125be44`、expectedQuantity2。旧行移除,新material.v1 ID=`2f358dc8-3c73-4be8-9c6e-4a6f82d245f6`进入bag(28→29格),事实和数量保留,既有库存不变,灵石保持41376。 +- 两次成功各增加6条资源事件。首次核对只按服务显式changes误预期5条;检查实际事件及ResourceEventCommitter后确认`withCharacterPanelInvalidations`会补充`player.condition / character.panel.changed`,按真实六项重新检查通过。未改产品代码以满足检查假设。 +- 对已取出的同组材料再次批量取出、对local2原单项再次取出,均返回409「物品已变化,请刷新宝库」,完整持久快照及资源事件不再变化。该旧接口没有requestId及replayed响应,依靠来源行/expectedQuantity检查防重复,本批不将其描述为返回原成功结果的幂等重放。 +- 批量成功页截图`/tmp/daoyou-nest-vault-batch-success.png`,储藏室截图`/tmp/daoyou-nest-vault-storage-success.png`。浏览器未见error,两个页面已关闭,四个临时API会话均sign-out200;原浏览器登录保留。核对PID、命令和工作目录后停止Nest21085、Vite21088及Bun父进程21086/21087,启动会话均退出143;日志`/tmp/daoyou-nest-vault-runtime.log`含shutdown complete,前端日志`/tmp/daoyou-nest-vault-web.log`。保留正常取出结果。 +- 本批只改验收文档,未新增测试文件或保存一次性脚本,未重复lint/build/共享测试;最近2401通过/2失败结果保持。执行浏览器、实际HTTP、只读数据库、`git diff --check`及Markdown结构检查。没有部署、提交或数据库迁移。旧消耗品/灵种取出与私有拍卖成功路径尚无合法存量用于本批验收,仍不记为通过。 + +## 第四十五批:黑市成交、交付与持久防重(2026-10-01) + +- 当前Nest产物及Vite,本地Chrome复用local1。页面恢复`TN_YUE_01`笑面掌柜的原会话`1c42e737d19c96986e002f09fb3f8c9939930d52`:重新进入后talking、version5、尚可交谈4次,入场cost0、free=true、replayed=true。保留第三十五批产生的观察和两次交谈结果,本批没有生成新提案或修改货品/定价。 +- 成交前通过临时API分别提交旧version4和错误expectedPrice6231,均409;角色灵石/condition、旧库存、统一库存、mutation凭据及资源事件快照不变。页面实际报价6230,余额473908。 +- 页面「按此价拿下」并确认「成交揭晓」,POST `/api/black-market/TN_YUE_01/sessions/1c42e737d19c96986e002f09fb3f8c9939930d52/commit`输入`{version:5,expectedPrice:6230}`返回200。揭晓地品雷属性灵药「金雷竹心」1份,库存ID=`d7141ef1-d549-4995-ab41-11e4d08d19c7`;背包40格已满,产物进入storage。灵石473908→467678,既有库存行哈希不变,新增一条`currency.black-market.spent`事件(scopeVersion634)。截图`/tmp/daoyou-nest-black-market-deal-success.png`。 +- 本路径通过自动player_journal保存防重结果,operationKey=`black_market_purchase:2026-10-01:TN_YUE_01:smiling-keeper`,journal ID=`00a93441-15c9-4118-9c12-0ae650dda634`,仅一条;player_mutation_requests没有新增。首次核对误期待mutation凭据增加,随后又误用journal不存在的request_id列;根据Schema和CommandExecutors修正后完整验证通过,没有修改应用或数据库结构。 +- 原成交请求重试200、replayed=true,返回结果与journal.event.result深比较一致;角色、库存、journal、mutation凭据及资源事件完整数据库快照不变。Redis会话保持completed且reveal相同,但version6→7、messages6→7:现有`commitBlackMarketPurchase`在重放后仍同步会话并追加成交提示。该领域服务与HEAD无差异,本批保留既有行为;不能据数据库防重宣称所有会话状态完全不变。 +- local2请求成交返回403,来自既有境界门槛,不将其记作已覆盖可入场账号的成交归属校验。local2请求离开local1会话返回404,数据库和Redis会话快照不变。本地其余测试角色均为炼气,无第二个满足黑市门槛的现有角色;没有调整境界以制造验收条件。 +- 页面刷新显示「今日已成交」,点击「查看成交结果」仍显示原物品及余额467678;浏览器未见error。会话已completed,无待处理交谈。五个临时API会话均sign-out200,既有浏览器登录保留,页面关闭。核对PID/命令/工作目录后停止Nest21550、Vite21553及Bun父进程21551/21552,启动会话均退出143。日志`/tmp/daoyou-nest-black-market-deal-runtime.log`含shutdown complete,前端日志`/tmp/daoyou-nest-black-market-deal-web.log`,保留实际成交结果。 +- 本批只更新文档,无运行源码修改;未重复lint/build/共享测试,最近2401通过/2失败记录保持。执行页面、实际API、只读PostgreSQL/Redis核对及文档差异/结构检查。未新增测试文件或保存一次性脚本,没有提交、部署或数据库迁移;其余验收继续按清单收口。 + +## 第四十六批:双人讨伐落败、状态结算与回放权限(2026-10-01) + +- 复用当前Nest产物与Vite,Chrome local1、内置浏览器local2保留既有登录。活动`hunt-v3-82911-0`为金丹50级掠影妖貂,现有角色最高筑基;没有调整属性、注入物资或强制胜利。队伍`d4eeafb2-b7a7-4f3e-bea6-0ed59dff158d`由local1页面创建,招募炼气至筑基,local2页面加入;双方准备状态同步。队员用当前revision3请求开战返回403「只有队长可以开战」。 +- 队长页面发起讨伐,进入战局`2e30acd6-63c0-4ae6-b5d8-d01d97891b2f`,队员从大厅「进入战斗」进入同一战局。队长启用自动;队员未在首回合时限内提交,实际执行默认攻击。第1回合正常落败,双方倒地、队员灵兽战内死亡;两页显示同一战报及「此战未能得胜」。未将队员自动开关的未完成操作记作成功。 +- 只读Redis确认stage=finished、terminalReason=battle-ended、winner=1、reason=wipe,资源策略persistent。数据库中`hunt-reward-v1`、messageId=战局ID的消费凭据恰为1条;回放归档source_type=hunt、battle_type=pve、outcome=side-1、round_count=1,参与者两条。双方战内HP为0;持久condition按既有结算规则保留HP=1,local1法力830、local2法力900,与终局一致,lastBattleAt/lastRecoveryAt更新。双方战斗占用键均已释放。 +- 开战前后对双方灵石/修为进度、153条统一库存、78封邮件及44条player_journal排序后完整内容哈希核对,均不变。local1灵石467678、local2灵石41376,落败没有发放奖励或邮件。两人奖励接口200、status=no-reward,重复读取后仍无新增变更;本批没有重投终局消息,不能据重复GET宣称已运行验证消费者重投幂等。 +- 两名参战者私有回放均200;local3奖励读取403、私有回放404。队长刷新后恢复相同落败战报。截图`/tmp/daoyou-nest-hunt-defeat.png`。终局队伍恢复assembling且双方未准备,队员、队长依次在页面离队;最终两个账号大厅myTeam=null、teams为空,没有遗留队伍或活动战斗。 +- 页面控制台未见error,两个临时页面已关闭,原浏览器登录保留。本批恢复后使用的7个临时API会话均sign-out200。核对PID、命令与工作目录后SIGTERM停止Nest22021、Vite22024及Bun父进程22022/22023,启动会话均退出143;日志`/tmp/daoyou-nest-hunt-runtime.log`含shutdown complete,前端日志`/tmp/daoyou-nest-hunt-web.log`,3001/5174已无监听。保留正常玩法消耗;未直接恢复角色气血。 +- 本批仅更新验收文档,执行浏览器、实际API、只读PostgreSQL/Redis核对、`git diff --check`及Markdown结构检查;未改运行源码或新增测试文件/一次性脚本。未重复lint/build/共享测试,最近lint/build通过及2401通过/2失败记录保持。未提交、推送、部署或迁移数据库。自然会话续期、私有货单成功可见性、旧消耗品/灵种取出、多人胜利发奖与其余未覆盖玩法,以及真实生产配置/灰度/回滚仍未验收。 + +## 第四十七批:发布文档与当前调度定义对齐(2026-10-01) + +- 复核Nest启动、RuntimeService、CronService、请求上下文、会话服务及相关模块导入。继续保持Better Auth原始请求、单例服务不保存请求身份/BYOK、显式构造器注入、HTTP处理排空后关闭消息和数据设施。此次源码审阅没有发现需要新增适配代码的问题;不将审阅计作新的运行验收。 +- `docs/development.md`仍将CI触发条件写成master分支推送,且列有当前不存在的`tower-enemy-sets`、`player-state-events-cleanup` Cron入口。按实际工作流修正为任意Git标签推送、版本/完整SHA/latest镜像标签,并明确工作流不执行服务器蓝绿脚本、没有独立lint/共享测试门禁。 +- 按`internal-cron.controller.ts`与`schedules.ts`重写十项手动入口、command和Asia/Shanghai频率表,补齐材料库和赞助相关调度,移除旧入口示例。说明当前无独立内置调度开关,不能仅添加外部crontab就称为完成调度切换。没有改变任何实际Cron表达式或触发任务。 +- 本地只读PostgreSQL查询发现物品库存在拍卖行贵宾符`ef138167-4841-4a47-83aa-b6008c748c7b`,但status=archived;声望商店与宗门商店的item_snapshot均无`auction_private_listing`。结合此前库存检查,这两个现有商店入口无法提供本轮私有寄售验收所需贵宾符。未解归档、添加货架或发放测试物资,不推广为所有潜在玩法来源都不存在。 +- 内联文档检查逐项比较10个HTTP路径、10个command及UTC转北京时间,全部通过;工作流标签触发及Markdown围栏检查、`bash -n scripts/blue-green-app.sh`、`git diff --check`通过。没有保存一次性检查脚本。仅修改文档,未重复lint/build/共享测试,保留最近2401通过/2失败结果。没有启动应用、部署、提交或执行数据库迁移;剩余运行验收与上线门槛不变。 + +## 第四十八批:丹方自然发现、本人收录与重复确认(2026-10-01) + +- 当前Nest产物与Vite、本地内置浏览器local2既有登录。只读确认没有待确认丹方,该角色持有丹方0份。首次以已有真品紫蕴灵参1份正常炼制,requestId=`b712289c-483f-4a30-984f-e2b6a4be2944`,POST `/api/craft`返回400「本炉药蕴不足,无法凝成丹药」。前后29条统一库存、20条journal、丹方、灵石及持久qi/刷新时间快照一致,未扣除页面所列12800灵石或消耗灵参。 +- 页面改用青纹藤叶3份(`9193e333-bf45-4e09-971d-ae38f2a77cfa`)与第四十一批收获的青木凝叶5份(`7dec54dc-840e-447a-ba6f-c2cb5385daf8`),正常填写恢复气血意图并确认1600灵石、1灵气。requestId=`4f9aca49-add9-49f0-8f1a-2083624b3b7c`,POST200,成丹稳度79,产出凡品青木归元丹共3枚,分别为2枚`ebd35453-e032-4605-8d23-9653bc3b3090`、1枚`6a085d4d-893b-4c80-bdd0-d4fde5f2da01`,均入bag。两种药材行耗尽移除,紫蕴灵参仍1份,背包仍29格。灵石41376→39776;持久qi经自然恢复并支付后为223,页面确认前显示224。未改属性、随机值、判定或发放物资。 +- 实际响应发现「青木归元丹丹方」,family=healing、木属性、两味凡品材料、restore_hp权重1、熟练度0。候选token=`822ef3d5-5425-40d1-9ee2-4c9d6a834cb4`。local1提交该token确认返回404,local2候选Redis内容及角色/库存/日志/丹方快照不变。 +- local2页面「保存丹方」POST `/api/alchemy/formulas/discovery/confirm`200、saved=true,丹方ID=`51859b13-ad34-40ad-a891-f6d8bb7acd44`。丹方从0增至1,候选Redis键删除;确认本身没有再次改变灵石、qi、库存或journal。进入丹方玉简并刷新,仍展示疗伤、木、两味、凡品、Lv.0,余额39776,截图`/tmp/daoyou-nest-formula-discovery-success.png`。 +- 本人重复相同token、accept=true返回404「待确认丹方已散去,可能已过期」;accept=false返回200、saved=false,全部持久快照不再变化。该接口成功后删除候选,并非返回原成功结果的幂等重放,不将404称为replayed成功。没有运行并发确认或修改候选TTL。 +- 已保存后重放原炼丹请求200、state.replayed=true,丹方、库存、journal、灵石及持久qi快照不变,候选键未重建。原炼丹journal恰为1条,ID=`78910813-d505-4f85-bb0c-a278f4fd5bc7`、operationKey=`alchemy_improvised:4f9aca49-add9-49f0-8f1a-2083624b3b7c`;重放响应不含formulaDiscovery,不能宣称包含候选的全部响应与首次完全相同。相关旧应用服务/前端与HEAD无差异,未调整原有发现机制。 +- 第一次确认后快照比较将pg返回的Date与先前序列化ISO字符串直接深比较而失败,尚未创建API会话;统一快照序列化后重验通过,没有修改产品。三个临时API会话均正常sign-out200;临时页面关闭,既有登录保留。核对PID、命令与目录后停止Nest24287、Vite24290及Bun父进程24288/24289,启动会话退出143,日志`/tmp/daoyou-nest-discovery-runtime.log`含shutdown complete,前端日志`/tmp/daoyou-nest-discovery-web.log`;3001/5174无监听,保留正常成丹和丹方。 +- 本批只更新验收文档;执行真实浏览器/API、只读PostgreSQL/Redis、文档结构及`git diff --check`。未新增测试文件或保存一次性脚本,未重复lint/build/共享测试,最近2401通过/2失败记录保持;未提交、部署或迁移数据库。自然会话续期、私有拍卖成功、旧消耗品/灵种取出、多人胜利发奖及其余既定门槛仍未完成。 + +## 第四十九批:已登录非管理员全量后台拒绝与角色归属(2026-10-01) + +- 审阅历史待办后,补齐第十三至十六批普通已登录非管理员403的运行证据。由TypeScript AST读取当前admin和legacy-items Controller,提取`api/admin`下55组method/path,逐项确认类级Access为admin或account-admin且无方法级覆盖。此清单为20 GET、26 POST、6 PUT、3 PATCH;不包含Better Auth认证管理拦截或未知路由。 +- 只读本地数据库取得local3身份,以现有adminAccess函数确认其既不在管理员邮箱也不在用户ID授权范围;正常登录后先验证`/api/admin/session`403,再逐项请求55个方法。动态ID替换为不存在的UUID;35个写方法携带畸形JSON。全部403,响应success=false及对应管理员错误文案,证明这些请求在业务解析/执行前被拒绝。没有修改授权配置、发起真实订单处理或成功发送邮件。 +- 前后对16张相关业务表的全部行排序哈希和数量比较,完全一致:app_settings、feedbacks、item_library、redeem_codes/claims、两类shop_items、sponsorship的admin_actions/checkout_intents/claims/merit_profiles/merit_records/order_snapshots/orders、system_mail_campaigns及tower_enemy_floors。摘要比较不包含登录本身创建并正常退出的临时认证会话;未输出配置内容或凭据。 +- 另以local1、local2各自正常登录,GET `/api/enemies/:id`读取本人活跃角色200且返回ID匹配;读取对方或不存在UUID均404「敌人角色不存在」。补齐第八批已登录成功和归属边界,两账号各3项。三个旧排行榜挑战POST入口`challenge`、`challenge-battle`、`challenge-battle/v5`即使携带畸形JSON也均410,两账号共6项;没有创建新挑战或消耗挑战次数。 +- 首次只读身份查询误用单引号引用Better Auth的user表,语法错误发生在登录前;改为双引号表名后全部检查通过,未改产品代码。三个临时会话均sign-out200,没有打开浏览器或改变原有浏览器登录。 +- 核对PID、命令及目录后SIGTERM停止Nest24815,启动会话退出143,`/tmp/daoyou-nest-admin-deny-runtime.log`含shutdown complete;3001/5174无监听。仅更新验收文档,执行实际HTTP、只读SQL摘要、AST清单、Markdown及`git diff --check`;未保存一次性脚本或新增测试,未重复lint/build/共享测试,保留最近2401通过/2失败结果。未部署、提交或迁移数据库;管理员成功写入、剩余玩法与自然续期等待办继续保留。 + +## 第五十批:宗门清扫任务生命周期与幂等(2026-10-01) + +- local2通过本地内置浏览器正常承接清扫山门,任务记录`7912279e-b157-4a75-afb4-76d88eb4917a`,periodKey=2026-10-01。accept请求键`5eb0bc35-6a8c-40c2-92fe-259b0ea4fa8c`、start请求键`7ddd8950-0e1c-43f7-b202-e57051ff2df6`均200;清扫sessionId=`4264101c-a911-4e66-8995-6cf725a75cb0`,rulesVersion=3。 +- 首次键盘尝试后通过页面「重走」重置棋盘,再依据可见棋盘逐格点击,16步收集4片落叶并到达终点。页面自动提交真实moves,complete以sessionId为幂等键返回200;任务completed、progress=1,尚未领奖时贡献5、灵石39776、修为1780。没有伪造完成请求、改时间或注入棋盘状态。 +- 返回照灯执事处正常复命,claim请求键`eea97527-4bbb-475d-a9ab-b805b9278cf9`返回200。只读数据库确认贡献与累计贡献5→9、灵石39776→40776、修为1780→1830,库存行内容不变;任务claimed_at已写入,本周勤勉periodKey=2026-09-28、progress=1。刷新后功簿仍显示本期已经结清,截图`/tmp/daoyou-nest-sect-task-claimed.png`;清扫完成截图`/tmp/daoyou-nest-sect-sweep-complete.png`。 +- 承接后重放accept200、replayed=true,缺失幂等键400、相同键改变input返回409,前后任务/贡献/灵石/库存快照一致。领奖后重放原accept、start、complete、claim四个请求均200、replayed=true;claim缺失键400、改变input复用键409,前后任务/贡献/修为/灵石/库存快照一致,未重新开启场次或重复发奖。 +- 四个持久幂等凭据位于player_journal,operation_key分别为`sect_task_action:<请求键>`,恰为四条。最初误查player_mutation_requests得到0条,修正核对位置后通过;未修改产品实现。不将本批串行重试推广为并发提交、场次过期或其他宗门任务均已验收。 +- 本批三个临时API会话(含承接前读取任务的会话)均正常sign-out200,临时页面已关闭,原浏览器登录保留。核对PID、命令和目录后停止Nest25249、Vite25252及Bun父进程25250/25251,启动会话退出143;`/tmp/daoyou-nest-sect-task-runtime.log`含shutdown complete,3001/5174无监听。正常玩法任务和奖励保留。 +- 仅更新验收文档;执行浏览器/实际API、只读PostgreSQL、Markdown结构与`git diff --check`,未新增测试文件或保存一次性脚本,未重复lint/build/共享测试。最近lint/build通过、共享测试2401通过/2失败的记录保持。未提交、推送、部署或执行数据库迁移;自然会话续期、部分玩法成功分支、管理员成功写入及目标生产配置/灰度/回滚仍未收口。 + +## 第五十一批:迁移前测试对照与后台草稿写入(2026-10-01) + +- 为复核两项共享测试失败,在独立托管worktree检出迁移前HEAD `c0b267df8ef3a6f4ef38732c79a3020068c2b3dd`,使用其原始package.json与bun.lock执行`bun install --frozen-lockfile`。该目录没有Nest源码,工作区保持干净;没有复用当前迁移目录的node_modules。 +- 在迁移前与当前两个工作区分别执行`bun run test packages/shared/src/forging/forging.test.ts packages/shared/src/engine/combat-v6/content/skill-learning.test.ts`,两边均11通过/2失败:材料堆叠预期`[99, 2]`、实际`[101]`;宗门内容哈希预期`2df399289489cf42dbdbe37fb504a3230c525fbda25c68545faef2572480e279`、实际`3419722218ff4dcf2d494bbee4100ef963a05031aeabb3fd3948b9dbf6779513`。这为两项失败在迁移前已经存在提供直接运行证据;未修改领域规则或断言,也不将全套测试记为通过。 +- 本地Nest使用现有local1管理员正常登录,`/api/admin/session`200。POST `/api/admin/system-mails`创建无附件、仅面向local2的验收草稿`834ac6ab-5626-4ed4-a307-e352257bb168`,200、draft、revision=1。原请求重复200且返回同一记录;同requestId改变内容409。 +- PUT更新草稿200、revision=2;旧revision=1更新409。两个合法请求同时以revision=2更新不同正文,恰为一个200、一个409;最终revision=3。详情与搜索列表均200且显示相同草稿;再重放最初创建请求200,返回当前revision=3,没有覆盖已更新正文或新增记录。 +- 只读数据库确认仅一条该ID记录、created_by=local1、status=draft、published_at为空、deliveredCount=0。前后全部玩家邮件、统一库存与角色表的行数和排序内容哈希一致。未请求publish/stop,没有发送系统邮件或发放奖励;本批仅证明草稿写入及并发版本控制,不证明投递生命周期或其他后台写入。 +- 临时管理员会话sign-out200,未打开浏览器或改动原有登录。核对PID、命令与工作目录后SIGTERM停止Nest26459,启动会话退出143,`/tmp/daoyou-nest-admin-draft-runtime.log`含shutdown complete,3001/5174无监听;验收草稿保留为未发布状态。基线工作区测试已结束并已归档,附件状态为archived_worktree。 +- 发布配置审阅确认Node健康检查、75秒停机宽限期和旧Bun镜像回滚需匹配旧Compose的说明保持一致;`bash -n scripts/blue-green-app.sh`通过。仅更新验收文档,运行源码未改;执行上述两工作区定向测试、真实HTTP、只读SQL、Markdown结构及`git diff --check`,未重复完整lint/build/共享测试或Docker构建,未新增测试脚本、提交、部署或迁移数据库。自然续期仍未达到既有会话的一天阈值;其余玩法及目标生产环境验收继续保留。 + +## 第五十二批:双实例资源广播与旧实例退出后重连(2026-10-01) + +- 当前Nest产物以两个本地进程运行,A监听127.0.0.1:3001,B监听127.0.0.1:3002,共用本地PostgreSQL/Redis/NATS。两个健康检查均200,Redis、NATS、messaging为up;不是生产部署或OpenResty切流。 +- 通过B为local1、local2分别正常创建临时会话。local2在A、B各建立一条`/api/realtime?channels=player-state`连接,local1在B建立隔离对照连接,均收到ready。通过A的正常背包API将local2现有青木归元丹库存`ebd35453-e032-4605-8d23-9653bc3b3090`从slotIndex=1移至空格29,revision=0→1;没有消耗物品或发放测试资源。 +- 资源事件`6b0cd291-410e-49aa-84d2-378d63b58303`在local2的A、B连接均收到,完整事件内容逐项等于HTTP提交响应;local1未收到该事件,证明此实例组合下既有NATS资源广播跨进程送达且保持角色作用域隔离。 +- 核对A的PID、命令与目录后发送SIGTERM。A连接关闭码1006,B连接仍打开,B健康检查200;local2在B新增连接收到ready。通过B将该物品移回slotIndex=1,revision=1→2;资源事件`7d2bb32a-832a-4dc8-9a48-3432b303f61b`在B既有及重连后的local2连接均收到且内容匹配,local1未收到。各参与连接收到的相应事件ID均无重复。 +- 只读PostgreSQL确认所选物品位置、数量和实例内容恢复原状,仅revision及更新时间按两次合法移动更新;该角色其余库存行完整内容不变。不将1006称为正常关闭握手,也不将本批直连端口验证称为零中断发布、擂台长连接恢复或实际代理/DNS切流。 +- 所有临时WebSocket关闭,两个临时认证会话sign-out200;未打开浏览器或改变原有浏览器登录。A27013、B27014最终均退出143,`/tmp/daoyou-nest-overlap-a.log`与`/tmp/daoyou-nest-overlap-b.log`均含shutdown complete,3001/3002无监听。 +- 审阅蓝绿脚本发现开发文档称其「原子切换」,实际通过cp写入配置、nginx -t校验并reload,失败则恢复备份;文档改为描述实际顺序,脚本本身未改。仅更新文档,执行真实HTTP/WS、只读SQL、Markdown结构、`bash -n scripts/blue-green-app.sh`及`git diff --check`;未重复lint/build/共享测试或构建镜像,未保存一次性脚本、提交、推送、部署或迁移数据库。自然会话续期、剩余玩法与生产灰度/回滚继续保留。 + +## 第五十三批:当前源码、路由覆盖与构建交付审计(2026-10-01) + +- 重新执行`bun run lint`与`bun run build`,均退出0;构建包含客户端、服务端TypeScript检查及Vite产物,保留前端大分包提示。日志`/tmp/daoyou-nest-final-audit-lint.log`、`/tmp/daoyou-nest-final-audit-build.log`。本批为交付审计重新构建,没有改变运行源码。 +- TypeScript AST扫描当前Nest源码:42个Module、77个Controller、84个带框架类装饰器的构造器,共362条展开后的HTTP method/path,无重复声明;静态模块imports图无循环。首次检查把手动构造的ZodPipe参数误作DI依赖;核对其调用方式及未注册为Module provider后,确认只有schema和errorContract是调用方显式传入,其余所检查构造器参数均有@Inject。没有为检查器假设改动产品代码。 +- 对照第十七批实际旧Hono清单348条,规范化具名路径参数与框架通配写法后,仅`ALL /api/auth/*`不在Controller清单,已逐行确认由configureHttp的Better Auth原始请求处理器承载。额外声明为旧战绩/蜃楼battle根路径与13条末位鉴权兜底,均对应此前已记录的兼容行为。NotFoundModule仍位于AppModule最后,开发工具仍为条件导入。该静态对照不重新证明所有路由成功业务或通配语义,运行证据以相应批次为准。 +- package.json、package-lock.json根包、bun.lock根workspace的dependencies/devDependencies逐项一致,无Hono直接依赖或构建插件。packages/shared/src、apps/web/src及两个Drizzle迁移目录与HEAD无差异;共享领域、lib与utils中未发现Nest/Hono import。业务服务与规则没有因迁移变成Nest框架依赖。 +- 当前`apps/api/dist/main.js`及镜像`daoyou-nest:local-amd64`内`/app/apps/api/dist/main.js`的SHA-256均为`db2030b36781078ee18a8b38889ff7d31459c703198be94a503eff3f0020115f`。镜像ID仍为`sha256:1f4c1874edc79887eedd61999e604a07c8f4c385ade52fb19ed721eafc5601f7`;只运行sha256sum并自动移除临时容器,没有启动生产模式应用或重新部署。 +- 两项共享测试已由第五十一批原提交/原锁文件运行证明为已有失败,本批未重复共享测试或修改断言。完成Markdown结构及`git diff --check`;3001/3002/5174无监听。本批仅更新验收文档,未提交、推送、部署或执行数据库迁移。 +- 当前证据仍缺少自然updateAge会话续期、私有寄售成功可见性、部分旧物品取出、多人胜利奖励及其余尚未覆盖玩法;不能以静态清单替代。目标生产环境配置、代理切流及回滚尚未执行,已向用户明确询问本轮交付是否包含生产发布,未收到回答前不擅自部署或将其记为通过。 + +## 第五十四批:自动战斗策略持久化与续期条件复核(2026-10-01) + +- 只读检查local1/local2会话元数据,共12条未过期会话,其中8条距updatedAt超过一天;没有读取会话令牌、修改时间或认证配置。这些数据库记录不证明现有浏览器持有对应Cookie。 +- 现有Chrome在127.0.0.1打开正常角色页面,身份为local1;观察get-session及业务资源GET均200且无Set-Cookie,12条既有会话的createdAt/updatedAt/expiresAt前后不变。因此当前Chrome所持会话未触发续期,不把页面已登录或数据库存在旧会话记为续期通过。内置浏览器local2先前新建会话的一天阈值仍需自然等待。 +- local2正常登录后GET `/api/combat-v6/auto-strategy`200,流派`youdu.path.poison_master`,customStrategy=null。通过PUT保存一条使用当前可用技能`youdu.skill.siphon`、target=best、无条件的合法策略,200;随后GET读回内容完全一致,只读数据库确认角色/流派主键下恰为一条记录。重复PUT200,记录仍为一条;该接口为upsert,不称为带请求号的幂等重放。 +- 写入前分别提交错误流派、未知技能、未知战斗资源、不可用状态,均409;11条规则以及额外cultivatorId字段均400。每次拒绝后整个自动策略表内容与基线一致。成功保存后,对错误流派DELETE返回409,当前策略仍保持原保存内容。 +- 对当前流派DELETE重置200,再次DELETE仍200,GET恢复customStrategy=null。整个策略表完整内容恢复测试前基线,其他角色策略未变;local2统一库存、灵石及修为进度快照不变。没有开始新战斗、改变流派或直接写数据库;仅覆盖策略接口与持久化,不证明战内执行或并发流派切换。 +- 两个临时API会话均sign-out200,Chrome临时页面已关闭,既有浏览器登录保留。核对PID、命令与目录后停止Nest28745、Vite28749及Bun父进程28747/28748,启动会话退出143;`/tmp/daoyou-nest-renewal-runtime.log`含shutdown complete,3001/5174无监听,前端日志`/tmp/daoyou-nest-renewal-web.log`。 +- 本批仅更新验收文档,执行真实浏览器/HTTP、只读PostgreSQL、Markdown结构与`git diff --check`。未新增测试文件或保存一次性脚本,未重复第五十三批已通过的lint/build,未修改既有共享测试断言、提交、推送、部署或迁移数据库。自然续期、私有寄售及其余未覆盖玩法、生产范围确认仍待收口。 + +## 第五十五批:跨实例库存写入竞争与版本冲突(2026-10-01) + +- 当前Nest产物以A3001、B3002两个本地进程运行,共用原本地PostgreSQL/Redis/NATS,健康检查均200。local2正常临时登录,使用现有青木归元丹`ebd35453-e032-4605-8d23-9653bc3b3090`,数量2、slotIndex=1、revision=2;只读确认格位29、30均为空。 +- 同时向A、B的POST `/api/combat-v6/inventory`提交同一物品revision=2,分别移动到29、30。A200;B在共享角色锁竞争阶段返回429「操作正在处理中,请稍后重试」,Retry-After=1。实际不是两次均进入事务后才发生版本冲突,不将此结果描述为B409。 +- 数据库确认仅A提交:物品slotIndex=29、revision=3,数量与instance_data不变,角色其余库存行完整内容不变。该并发窗口新增资源事件恰一条`38ef07ab-ae37-44c6-92a6-f980f718e3a8`,ID、scopeVersion、resourceVersion与A响应一致,没有第二条资源提交。 +- 随后分别向A、B串行重试原revision=2请求,均409。前后库存与该角色全部资源事件快照不变。再通过B携带当前revision=3移回原格位1,200、revision=4;物品除合法移动引起的版本与更新时间外恢复原状,其余库存行仍不变。没有改写数据库或注入锁故障。 +- 临时认证会话sign-out200,没有打开浏览器或改变原有浏览器登录。核对PID、命令与目录后SIGTERM停止A28952、B28960,启动会话均退出143;`/tmp/daoyou-nest-concurrent-a.log`、`/tmp/daoyou-nest-concurrent-b.log`均含shutdown complete,3001/3002无监听。 +- 仅更新验收文档,执行真实双进程HTTP、只读PostgreSQL、Markdown结构与`git diff --check`;未新增测试文件或保存一次性脚本,未重复lint/build/共享测试。该检查只证明本次库存竞争及重试路径,不能推广为所有业务锁、失租或数据库故障均已验证。自然续期、剩余合法玩法与生产范围/发布验收仍未完成,未提交、推送、部署或迁移数据库。 + +## 第五十六批:剩余验收前置条件与受阻审计(2026-10-01) + +- 将旧数据/贵宾符核对范围从主要使用的两个角色扩至docs/testing.md列出的local1—local8。只读确认八个账号各有活跃角色;六个炼气后期、一个炼气圆满、一个筑基中期。没有查询或输出认证凭据。 +- 八个角色数量大于0的旧consumables为0、materials中type=seed为0,creation_products记录为0。统一库存中包含auction_private_listing场景的物品为0;物品库有一条该场景且status=archived,reputation_shop_items和sect_shop_items对应快照数均为0。没有解归档、发放物资、插入历史数据或将空结果当作成功取出/兑换。 +- 读取当前纯配置生成的讨伐活动:最低金丹50级,其余依次70至170级,当前周期截至2026-10-01 16:00:00 UTC。结合第四十六批真实落败,不能宣称多人胜利发奖通过;也不将等级差推断为穷举证明任何队伍都无法胜利。 +- 续期条件已在第五十四批复核;生产范围询问仍未收到答复。上述时间、数据和目标环境条件连续多轮尚未变化;当前没有已启动而待完成的应用/测试进程,3001/3002/5174无监听。后续以条件变化恢复对应验收,不重复既有成功样本或扩张为全部业务规则重测。 +- 本批仅只读核对并将前置条件集中至文档顶部;Markdown结构、`git diff --check`通过,未重复lint/build/共享测试。没有运行源码改动、测试脚本、Git提交、生产部署或数据库迁移;目标保留未完成并标记为等待外部条件的受阻状态。 + + +## 第五十七批:Nest CLI 与 workspace 交付(2026-10-02) + +- API、Web、共享规则各有package.json;运行依赖和构建依赖归属对应包,根目录维护检查、迁移与部署命令。Bun使用隔离安装,npm工作区生产安装保留两份锁文件;各workspace的依赖声明与npm锁条目逐项一致,Bun frozen安装通过。 +- API构建入口为 `nest build`,采用CLI 12推荐的原生ESM Rspack构建器。保留显式构造器注入,关闭隐式装饰器类型元数据;Markdown提示词和共享源码打包,API直接依赖外置。旧服务端Vite配置已删除。`nest build --watch`首轮构建通过;Web保持Vite、version.json和独立 `apps/web/dist`。 +- LLM角色、材料及灵种编排迁至API;材料/灵种随机骨架、角色组装及修为/突破计算留在shared。角色纯组装接收随机源,原有AI mock测试调整为纯组装断言;爱发电协议由宿主提供签名函数,共享生产代码不依赖Node内置模块。未改变数值、奖励、库存事实或数据库结构。 +- ConfigModule关闭dotenv发现,提供Zod校验后的不可变环境快照;框架无关基础库使用同一快照,服务端仅此处读取process.env。必需配置、连接池大小和数值边界在启动校验,生产要求cron密钥并禁止本地开发模式。错误只含字段和约束。`PORT=0`及production缺少cron密钥已验证启动失败。 +- DatabaseModule注册既有Drizzle客户端,Runtime在HTTP、处理器和消息排空后关闭同一个pg.Pool。健康检查增加PostgreSQL探测及失败503分支;本地实测 database、redis、nats、messaging全部up。两条迁移流、表结构、事务executor传递与生产TLS设定沿用现状。 +- Docker通过npm workspace仅安装API及shared运行依赖,并省略dev/optional/未显式声明的peer,避免Better Auth可选Vitest peer把测试与前端构建工具带入镜像。非root用户、Node 24、SIGTERM及原75秒停机窗口保留。 +- 检查:`bun run lint`、`bun run typecheck`、`bun run build`、Nest watch构建、`bun install --frozen-lockfile`、完整Docker构建通过。集中共享检查7个文件27项通过;完整共享检查仍为237文件、2401通过/2失败,与调整前完全相同。原失败是forging材料堆叠(101与99+2)及宗门技能内容hash基线;没有修改断言掩盖失败。 +- 浏览器复用本地道友2既有会话,迁移后洞府、资源栏、闭关/突破标签及80.5%概率展示正常,浏览器error日志为空。匿名角色资源请求401,验证码配置200。最终镜像检查确认无Vite、Vitest、Nest CLI与应用源码;裁剪依赖后的镜像再次启动及健康检查通过。生产模式本地镜像健康检查200,未提供Bearer的内部cron401,dev资源入口404;正常SIGTERM排空并退出。生产模式本地定时任务实际执行了一次market-refresh(24项),灵种LLM生成成功,停机等待其完成;该记录只涉及本地专用数据。 +- 跳过:没有修改schema,因此没有生成/执行迁移;没有发布镜像或部署真实生产,没有向staging执行玩法写入;特殊历史存量、私有寄售、多人胜利、自然续期与生产灰度/回滚继续依前述条件验收。 + + +## 第五十八批:pnpm workspace 与 Turborepo(2026-10-02) + +- 根目录固定 `pnpm@10.34.6`,仅保留 `pnpm-lock.yaml`。通过旧npm锁导入依赖解析,再添加Turbo及tsx;删除 `bun.lock`、`package-lock.json`、`bunfig.toml`、Bun类型依赖和LLM fetch的Bun专属兼容属性。赞助页生成使用Node文件API,生成及同步检查通过。旧根 `dist/` 是先前构建产物,已删除;迁空的 `src/` 只剩Finder元数据,已一并移除。API/Web各自生成自己的dist。 +- `pnpm-workspace.yaml`声明三个包,应用内部依赖为 `workspace:*`;禁用自动peer安装并显式补齐Nest CLI的webpack和Phaser插件的graphology-types。未升级应用运行依赖;Better Auth锁条目中的可选Hono peer声明不代表安装Hono,`pnpm why -r hono`无安装记录。 +- `turbo.json`声明build依赖图、dist输出、构建环境缓存键、typecheck依赖图以及不缓存的persistent dev/prd任务。临时更改共享源码并恢复,dry-run证实API/Web构建hash均失效且恢复后一致。lint与共享Vitest继续集中在根目录,不缓存运行结果。 +- API包使用 `nest build` 和 `nest start --watch --no-shell --env-file ...`;Web包使用Vite并禁止隐式dotenv叠加。Node显式加载local/staging文件,Turbo开发任务使用loose环境模式;构建仍为strict并显式声明公开构建变量。没有将服务端密钥纳入构建缓存。 +- Docker采用同一pnpm锁构建,并通过 `pnpm --filter @daoyou/api deploy --prod /out` 生成可搬移目录。最终入口 `node dist/main.js`,uid1000运行,无Bun、Vite、Turbo、Nest CLI;本地production容器健康检查200,PostgreSQL/Redis/NATS/messaging全部up。SIGTERM后先排空请求及消息,再关闭数据库与Redis,出现shutdown complete。 +- `pnpm run dev`同时启动API3001与Web5174;已有本地道友2会话可进入洞府与背包,显示29/40格及资源。源码变动触发Nest重建、旧进程排空和重新启动;结束Turbo任务后3001/3002/5174均无监听。临时浏览器页和本地验收容器已移除,依赖服务与原有用户数据保留。 +- 文档原称 `env/local.env`已纳入Git,实际被忽略。新增仅含专用本地容器凭据的 `env/local.example.env`,首次复制后使用;现有环境文件及其真实密钥未改写或纳入Git。维护命令改用Node+tsx,两条宗门维护命令在本地dry-run均成功且记录数为0;历史验收日志保留原始Bun命令。 +- 检查:`pnpm install --frozen-lockfile`、`pnpm run lint`、`pnpm run typecheck`、`pnpm run build`、`pnpm run sponsors:check`、Docker构建/运行、浏览器读取、模块依赖审计和 `git diff --check`通过。`pnpm run test`为237文件、2401通过/2失败,仍是forging材料堆叠及宗门技能内容hash,未改断言。该次本地启动中auction-expire还遇到持久寄售附件的Zod校验错误;未修改该业务服务或历史数据,不能把健康检查当作该业务分支成功证据。 +- 跳过:未新增API/数据库单测,未改schema或执行数据库迁移,未执行staging测试写入,未推送镜像或部署真实生产,未重跑本批未修改的全部玩法。上方特殊数据/自然续期及生产发布验收继续保留。 diff --git a/docs/player-journal.md b/docs/player-journal.md index 72f572c0..47d55c04 100644 --- a/docs/player-journal.md +++ b/docs/player-journal.md @@ -6,7 +6,7 @@ 数据库迁移为 `drizzle/0060_player_journal.sql`,已合并本轮未上线的讨伐与日志迁移,仅创建最终使用的日志表。 -`wanjiedaoyou_player_journal` 同时保存业务执行凭据和玩家可见的结构化结果,不另建收支明细表。`event` 使用 `src/shared/contracts/playerJournal.ts` 的可辨识联合契约,前端按类型渲染、按资源统一颜色与数字格式。 +`wanjiedaoyou_player_journal` 同时保存业务执行凭据和玩家可见的结构化结果,不另建收支明细表。`event` 使用 `packages/shared/src/contracts/playerJournal.ts` 的可辨识联合契约,前端按类型渲染、按资源统一颜色与数字格式。 - 闭关:年数、实际修为与感悟收益、实际灵气消耗、寿元耗尽结果。 - 突破:成功/失败、前后境界、实际修为与感悟变化、灵气消耗、寿元上限和属性奖励。 diff --git a/docs/sect-authoring-guide.md b/docs/sect-authoring-guide.md index 3f09e980..51b81c9f 100644 --- a/docs/sect-authoring-guide.md +++ b/docs/sect-authoring-guide.md @@ -1,11 +1,11 @@ # 宗门内容接入指南 -普通宗门共享入门文戏、任务、晋升、设施、经济、建设、收益和准入流程,只定义身份、六本心法、神通、流派、参悟节点与玩家可见的入门演出。完整参考实现位于 `src/shared/engine/sect/testing/fixtures/FixtureSectModule.ts`,且不会进入生产目录。 +普通宗门共享入门文戏、任务、晋升、设施、经济、建设、收益和准入流程,只定义身份、六本心法、神通、流派、参悟节点与玩家可见的入门演出。完整参考实现位于 `packages/shared/src/engine/sect/testing/fixtures/FixtureSectModule.ts`,且不会进入生产目录。 ## 目录模板 ```text -src/shared/engine/sect/content// +packages/shared/src/engine/sect/content// SectModule.ts definition.ts ids.ts @@ -227,7 +227,7 @@ export const EMBER_PRESENTATION: SectPresentationTheme = { 对象字段按键覆盖默认值,热点数组整体替换。空字符串、主题宗门 ID 不一致、重复热点和缺少替代文本或热点的自定义地图会在启动时失败。 -最后只在 `src/shared/engine/sect/content/productionRuntime.ts` 的 `PRODUCTION_SECTS` 中加入一个条目: +最后只在 `packages/shared/src/engine/sect/content/productionRuntime.ts` 的 `PRODUCTION_SECTS` 中加入一个条目: ```ts export const PRODUCTION_SECTS = createProductionSectCatalog([ @@ -273,8 +273,8 @@ battle-v5 只理解伤害、治疗、Buff、层数、资源、命中、行动、 ## 验证 ```bash -bunx vitest run src/shared/engine/sect -bunx vitest run src/shared/engine/battle-v5/tests +bunx vitest run packages/shared/src/engine/sect +bunx vitest run packages/shared/src/engine/battle-v5/tests bun run lint bun run test bun run build diff --git a/docs/system-mail-campaigns.md b/docs/system-mail-campaigns.md index 15858a65..2e28a634 100644 --- a/docs/system-mail-campaigns.md +++ b/docs/system-mail-campaigns.md @@ -70,7 +70,7 @@ bun --env-file=env/local.env node_modules/drizzle-kit/bin.cjs migrate - 列表/详情实际投递数一致;四个本地样本最终均停用,已投递记录分别为 1、0、1、1,保留已领取物品和邮件历史。 - 桌面与 360px 条件表单已检查:无横向溢出、正文独立滚动、底部操作栏可见;物品选择复用原有格子与预览。 -检查:`bun run lint`、`bun run build`、`bun run test src/shared/contracts/systemMail.test.ts src/shared/contracts/domainEvents.test.ts src/shared/contracts/adminRewards.test.ts`(27 项)、`git diff --check`。 +检查:`bun run lint`、`bun run build`、`bun run test packages/shared/src/contracts/systemMail.test.ts packages/shared/src/contracts/domainEvents.test.ts packages/shared/src/contracts/adminRewards.test.ts`(27 项)、`git diff --check`。 未执行全仓战斗测试、MQ 故障注入/死信重放或停用竞争压力测试;结束时间延迟消费的边界由共享规则测试与事务/SQL 审查覆盖。没有新增前端、服务端或数据库单元测试。 diff --git a/docs/testing.md b/docs/testing.md index 52b86dec..5879156a 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -4,15 +4,15 @@ ## 1. 纯单元测试 -- 仅放在 `src/shared`,验证确定性、可复用的引擎和领域逻辑。 -- 使用 Vitest;先运行受影响的测试,再根据影响范围运行 `bun run test`。 -- 不引入数据库、Redis、HTTP、认证或第三方服务模拟;不在 `src/server`、`src/react-app` 或 `scripts` 添加单元测试。 +- 仅放在 `packages/shared/src`,验证确定性、可复用的引擎和领域逻辑。 +- 使用 Vitest;先运行受影响的测试,再根据影响范围运行 `pnpm run test`。 +- 不引入数据库、Redis、HTTP、认证或第三方服务模拟;不在 `apps/api/src`、`apps/web/src` 或 `scripts` 添加单元测试。 ## 2. Codex 浏览器/Playwright 模拟 由 Codex 在当次开发任务中操作真实本地页面,不将操作固化为一次性仓库脚本。 -1. 准备 `env/local.env`,执行 `bun run services up -d --wait` 和 `bun run db:migrate`。普通验证使用 `bun run dev`;长流程需关闭 watch 时,在两个终端执行 `APP_ENV=local NODE_ENV=development bun --env-file=env/local.env src/index.ts` 和 `bun run dev:web`。确认页面为 `127.0.0.1:5174`、服务指向专用本地数据;不得使用 `prd` 进行测试写入。 +1. 从 `env/local.example.env` 准备 `env/local.env`(已有文件保留),执行 `pnpm run services up -d --wait` 和 `pnpm run db:migrate`。普通验证使用 `pnpm run dev`;长流程需关闭watch时,先执行`pnpm run build:server`,再在两个终端执行`APP_ENV=local NODE_ENV=development node --env-file=env/local.env apps/api/dist/main.js`和`pnpm run dev:web`。确认页面为 `127.0.0.1:5174`、服务指向专用本地数据;不得使用 `prd` 进行测试写入。 2. 列出本次改动需要验证的用户行为、预期结果和准备条件。通过页面注册/登录,邮件验证使用 Mailpit;优先复用已有测试角色。缺少 LLM 或角色构筑等条件时如实说明,不通过假成功继续。 3. 使用可用的 Codex 浏览器能力或 Playwright,按可访问名称和实际页面定位控件。操作后等待具体界面/网络状态,不依赖固定长时间 sleep。多人测试使用独立浏览器上下文或配置文件,每个角色有独立会话。 4. 同时观察界面、控制台和网络:检查请求次数、错误、权限边界、提交结果及恢复过程。涉及布局时检查桌面和 360px;涉及性能时使用网络面板/Profiler,不维护一次性基准脚本。 @@ -56,7 +56,7 @@ Lint、TypeScript/构建、Prettier 是静态质量检查,不是额外一层 } ``` -新库存材料使用 `type: item`、`definitionId: material.v1`、`instanceData: { name, type, rank, element?, description? }`。传承灵印使用注册的定义 ID;随机道装使用 `{ "type": "equipment", "slot": "weapon", "level": 10 }`,法兵默认生成剑,可加 `weaponType` 指定 `axe/blade/spear/staff/sword/fan/bell/brush/banner`,非武器不得指定。全部定义在 `src/shared/items/definitions`,参数边界见 `src/shared/contracts/forging.ts`。 +新库存材料使用 `type: item`、`definitionId: material.v1`、`instanceData: { name, type, rank, element?, description? }`。传承灵印使用注册的定义 ID;随机道装使用 `{ "type": "equipment", "slot": "weapon", "level": 10 }`,法兵默认生成剑,可加 `weaponType` 指定 `axe/blade/spear/staff/sword/fan/bell/brush/banner`,非武器不得指定。全部定义在 `packages/shared/src/items/definitions`,参数边界见 `packages/shared/src/contracts/forging.ts`。 旧丹药/消耗品验收可提交 `{ "type": "vault-consumable", "facts": { name, type, quality, description, prompt, score, spec }, "quantity": 2 }`,facts 须符合 `ConsumableFactsSchema`,quantity 为 1–3960;沿用历史消耗品发放入口写入洞府宝库,再通过正式取出操作转换。新版消耗品直接发放使用 `type: item`、`definitionId: consumable.v1` 及同一完整 facts 作为 instanceData。该能力仍只在纯本地开放。 @@ -116,7 +116,7 @@ Lint、TypeScript/构建、Prettier 是静态质量检查,不是额外一层 - 灵根最多 8 条,元素须有效且不重复;`baseStrength` 为 0–100 的整数,`marrowWashBonus` 默认 0,两者之和不超过 120。品级由现有仓储规则根据元素及灵根数量推导;不接受客户端传入品级或当前强度。 - 命格最多 3 条;名称 1–100 字、品质须为现有品质,说明可省略(最多 2000 字)。每条指定 1–2 个不重复的效果 ID。 -- 效果 ID 来自 `src/server/lib/services/FateFragmentRegistry.ts`,例如 `retreat-exp-gain`、`retreat-insight-gain`、`natural-recovery`、`toxicity-mitigation`、`system-spirit-stone-surcharge`。数值、标签、说明及 rollMeta 由既有构造器按品质和固定 0.5 分位生成;测试接口允许自由组合现有效果,不模拟创角抽签概率,也不接受自定义效果数值。 +- 效果 ID 来自 `apps/api/src/lib/services/FateFragmentRegistry.ts`,例如 `retreat-exp-gain`、`retreat-insight-gain`、`natural-recovery`、`toxicity-mitigation`、`system-spirit-stone-surcharge`。数值、标签、说明及 rollMeta 由既有构造器按品质和固定 0.5 分位生成;测试接口允许自由组合现有效果,不模拟创角抽签概率,也不接受自定义效果数值。 - 非法结构、越界、重复元素或重复效果返回 400;未知效果 ID 返回 409,整笔事务回滚。响应在 `data.spiritualRoots` / `data.preHeavenFates` 返回本次调整后的运行时结构,便于核对先天与后天展示。 - 后天增益仅作为本地测试数据注入,不推进洗髓等级或执行破限;先天设定页显示 `baseStrength`,肉身修炼页显示后天增益与当前总强度。命格使用真实效果,会影响相应玩法。 - 临时验收前记录原值并恢复;用户明确要求补齐并保留的本地测试角色配置可以保留。外部调用后刷新页面读取新版本。 diff --git a/docs/text-filter-dictionary.md b/docs/text-filter-dictionary.md index c533f422..a1df9641 100644 --- a/docs/text-filter-dictionary.md +++ b/docs/text-filter-dictionary.md @@ -1,6 +1,6 @@ # 初始敏感词库整理说明 -整理日期:2026-09-17。目标文件:`src/server/config/text-filter.dictionary.json`。 +整理日期:2026-09-17。目标文件:`apps/api/src/config/text-filter.dictionary.json`。 这是万界道友对社区词库的筛选修改版,不是上游完整词库。初始版本共 **522 条屏蔽词**,例外词暂为空。词条按原有底量、来源类别的顺序排列;运行时仍只有一个全局屏蔽列表,不增加分类配置或外部依赖。 @@ -58,5 +58,5 @@ - 实际 JSON 可以被服务端加载,522 条词均能命中自身,未发现格式、规范化重复或黑白名单直接冲突。 - 正常示例中的“宗门招募、联系客服、双修功法、炉鼎炼丹、政府公告、网络服务、西藏旅行、回族朋友、毒龙、盘古、六十四卦”保持原文。 -- 对 `src/shared` 非测试 TypeScript 文件中静态 `name`、`title`、`description` 字符串做字面扫描,未发现命中;不代表动态生成文案已经全部验收。 +- 对 `packages/shared/src` 非测试 TypeScript 文件中静态 `name`、`title`、`description` 字符串做字面扫描,未发现命中;不代表动态生成文案已经全部验收。 - 本轮只调整词库和文档,不修改匹配算法或业务接入。未重新进行浏览器聊天验收;上轮本地角色旧数据校验问题仍是已知未验证项。 diff --git a/docs/text-filter.md b/docs/text-filter.md index 860de948..c5c8f3a9 100644 --- a/docs/text-filter.md +++ b/docs/text-filter.md @@ -4,7 +4,7 @@ ## 编辑词库 -文件:`src/server/config/text-filter.dictionary.json`。 +文件:`apps/api/src/config/text-filter.dictionary.json`。 - `blockedWords`:要屏蔽的词,增加或删除一项即可调整。 - `allowedPhrases`:允许包含屏蔽词的完整短语,用于修复误伤;只豁免完全位于短语内的命中,不放行整句话。 @@ -31,8 +31,8 @@ 编辑后执行: ```bash -bun -e 'import { textFilter } from "./src/server/lib/services/textFilter.ts"; console.log(textFilter.mask("这里填写需要检查的文本"));' -bun run test src/shared/text-filter/index.test.ts +bun -e 'import { textFilter } from "./apps/api/src/lib/services/textFilter.ts"; console.log(textFilter.mask("这里填写需要检查的文本"));' +bun run test packages/shared/src/text-filter/index.test.ts bun run build ``` @@ -73,4 +73,4 @@ const { text, changed } = textFilter.mask(input); 角色昵称、公告、社区正文、道具快照中的名称等尚未接入,需要在各自写入入口接入。历史记录不自动重刷;不额外保存玩家原文,删掉屏蔽词也不会恢复已打码内容。 -算法位于 `src/shared/text-filter`,不访问数据库和网络;词库与单例位于服务端,前端不导入词库。以后需要动态维护时替换服务端加载方式,保留业务接口。 +算法位于 `packages/shared/src/text-filter`,不访问数据库和网络;词库与单例位于服务端,前端不导入词库。以后需要动态维护时替换服务端加载方式,保留业务接口。 diff --git a/docs/tianyan-sacred-land-design.md b/docs/tianyan-sacred-land-design.md index 14d07467..96c07917 100644 --- a/docs/tianyan-sacred-land-design.md +++ b/docs/tianyan-sacred-land-design.md @@ -1301,7 +1301,7 @@ Status.Sect.tianyan.ElementSeal.Water - alt:`雨后中宫演法台上,一名新弟子沿石阶走向等候在五行汇流地刻前的执教,五峰殿宇隐于云雾` - 源文件:`docs/assets/tianyan/tianyan-onboarding-master.png` -![天衍圣地入门主视觉](../public/assets/sect/onboarding/tianyan.webp) +![天衍圣地入门主视觉](../apps/web/public/assets/sect/onboarding/tianyan.webp) ### 17.2 五幕脚本 @@ -1406,7 +1406,7 @@ Status.Sect.tianyan.ElementSeal.Water 源文件:`docs/assets/tianyan/tianyan-map-master.png` -![天衍圣地舆图](../public/assets/sect/tianyan-map.webp) +![天衍圣地舆图](../apps/web/public/assets/sect/tianyan-map.webp) 地图视觉已经确立以下事实: @@ -1650,7 +1650,7 @@ V1 仅通过敌方状态标签、通用衍数点阵与结构化战斗日志表 ### 22.1 内容目录 ```text -src/shared/engine/sect/content/tianyan/ +packages/shared/src/engine/sect/content/tianyan/ TianyanSectModule.ts definition.ts ids.ts @@ -1791,7 +1791,7 @@ interface MechanicTriggerBasisRef { ### 22.6 生产注册 -实现完成后只在 `src/shared/engine/sect/content/productionRuntime.ts` 增加: +实现完成后只在 `packages/shared/src/engine/sect/content/productionRuntime.ts` 增加: ```ts { module: TIANYAN_MODULE, presentation: TIANYAN_PRESENTATION } @@ -1801,8 +1801,8 @@ interface MechanicTriggerBasisRef { 现有资产: -- `public/assets/sect/onboarding/tianyan.webp` -- `public/assets/sect/tianyan-map.webp` +- `apps/web/public/assets/sect/onboarding/tianyan.webp` +- `apps/web/public/assets/sect/tianyan-map.webp` --- @@ -1949,9 +1949,9 @@ interface MechanicTriggerBasisRef { ### 23.11 建议验证命令 ```bash -bunx vitest run src/shared/engine/sect/content/tianyan -bunx vitest run src/shared/engine/sect -bunx vitest run src/shared/engine/battle-v5/tests +bunx vitest run packages/shared/src/engine/sect/content/tianyan +bunx vitest run packages/shared/src/engine/sect +bunx vitest run packages/shared/src/engine/battle-v5/tests bun run lint bun run test bun run build diff --git a/docs/tower-completion-audit.md b/docs/tower-completion-audit.md index 7431dac9..9d4470c5 100644 --- a/docs/tower-completion-audit.md +++ b/docs/tower-completion-audit.md @@ -168,14 +168,14 @@ ## 10. 代码依据 -- `src/shared/lib/tower/weekly.ts`:六套组合、三周主套路、周预览。 -- `src/shared/lib/tower/formations.ts`:角色份额、护卫/治疗和禁配。 -- `src/shared/engine/combat-v6/tower/content.ts`:数值、技能、状态与NPC周期编译。 -- `src/shared/engine/combat-v6/tower/host.ts`:满状态投影、NPC命令、快照兼容。 -- `src/shared/lib/tower/helpers.ts`:祝福候选、层数与榜单排序。 -- `src/shared/lib/tower/season.ts`:上海时区周界。 -- `src/shared/lib/tower/data/encounters.json`:境界基准、楼层曲线。 -- `src/server/lib/tower/combatV6.ts`:周发布、挑战推进、恢复与结算。 -- `src/server/lib/tower/leaderboard.ts`:榜单写入与保留期。 -- `src/shared/engine/combat-v6/tower/balance.test.ts`:目前平衡证据的实际覆盖。 +- `packages/shared/src/lib/tower/weekly.ts`:六套组合、三周主套路、周预览。 +- `packages/shared/src/lib/tower/formations.ts`:角色份额、护卫/治疗和禁配。 +- `packages/shared/src/engine/combat-v6/tower/content.ts`:数值、技能、状态与NPC周期编译。 +- `packages/shared/src/engine/combat-v6/tower/host.ts`:满状态投影、NPC命令、快照兼容。 +- `packages/shared/src/lib/tower/helpers.ts`:祝福候选、层数与榜单排序。 +- `packages/shared/src/lib/tower/season.ts`:上海时区周界。 +- `packages/shared/src/lib/tower/data/encounters.json`:境界基准、楼层曲线。 +- `apps/api/src/lib/tower/combatV6.ts`:周发布、挑战推进、恢复与结算。 +- `apps/api/src/lib/tower/leaderboard.ts`:榜单写入与保留期。 +- `packages/shared/src/engine/combat-v6/tower/balance.test.ts`:目前平衡证据的实际覆盖。 - `docs/tower-redesign-spec.md`、`docs/tower-scaling-design.md`:已确认边界与此前验收记录。 diff --git a/docs/tower-health-v8.md b/docs/tower-health-v8.md index 72c990b5..65dd5606 100644 --- a/docs/tower-health-v8.md +++ b/docs/tower-health-v8.md @@ -63,7 +63,7 @@ ## 本次验收记录 -- `bun run test src/shared/engine/combat-v6/tower src/shared/lib/tower --testTimeout=30000`:152 项全部通过。 +- `bun run test packages/shared/src/engine/combat-v6/tower packages/shared/src/lib/tower --testTimeout=30000`:152 项全部通过。 - `bun run lint`、`bun run build`:通过。 - 本地后台重新生成本周 v8,境界切换与末层属性展示:通过。 - 未进行全项目测试与逐场浏览器通关计时;已有纯引擎回归覆盖连续推进,不能替代实机播放体验。 diff --git a/docs/tower-phase-one-design.md b/docs/tower-phase-one-design.md index b4613053..ea06751c 100644 --- a/docs/tower-phase-one-design.md +++ b/docs/tower-phase-one-design.md @@ -198,13 +198,13 @@ 主要改动预计在: -- `src/shared/lib/tower/weekly.ts`、`formations.ts`:完整遭遇清单与编排。 -- `src/shared/engine/combat-v6/tower/content.ts`、`host.ts`:编译冻结敌人包、读取发布内容。 -- `src/shared/contracts/combatV6Tower.ts`:当前周与旧挑战归属。 -- `src/server/lib/drizzle/schema.ts`、主业务迁移:两张表。 +- `packages/shared/src/lib/tower/weekly.ts`、`formations.ts`:完整遭遇清单与编排。 +- `packages/shared/src/engine/combat-v6/tower/content.ts`、`host.ts`:编译冻结敌人包、读取发布内容。 +- `packages/shared/src/contracts/combatV6Tower.ts`:当前周与旧挑战归属。 +- `apps/api/src/lib/drizzle/schema.ts`、主业务迁移:两张表。 - 一个小型塔持久化仓库:读写周记录和领奖状态,透传事务,不建立泛化仓库框架。 -- `src/server/lib/tower/combatV6.ts`:周发布、领奖事务、换周与兼容。 -- `src/react-app/routes/game/tower/route.tsx`:仅同步周归属与奖励展示。 +- `apps/api/src/lib/tower/combatV6.ts`:周发布、领奖事务、换周与兼容。 +- `apps/web/src/routes/game/tower/route.tsx`:仅同步周归属与奖励展示。 纯共享测试:完整遭遇引用合法;禁配;同周及跨周约束;缺历史;多周分布;冻结包与预览一致;更新源配置不改变已有包;领奖状态纯转换的同周保持、向前切换及拒绝回退。 @@ -222,7 +222,7 @@ - 实现:`combat-v6-tower-v4`。发布配置一周一条、SQL领奖一角色一条,完整遭遇编排、冻结敌人、直接硬切旧挑战/榜单与旧资格导入已落地。首次本地发布JSON为130836字符,约128 KiB。 - 迁移:生成并检查 `0052_tower_week_publication.sql`、snapshot和journal,仅创建两张业务表及角色外键;使用 `bun --env-file=env/local.env ./node_modules/drizzle-kit/bin.cjs migrate` 在确认的 `127.0.0.1:15432/daoyou_local` 成功应用。未执行线上迁移或部署。最初通过 `bun x` 调用Drizzle未传递到所需环境,已改为Bun直接运行CLI。 - 静态检查:`bun run lint`、`bun run build`(客户端+服务端)、最后事务参数调整后的服务端TypeScript与文件lint、`git diff --check`通过。 -- 专项:`bun run test src/shared/lib/tower src/shared/engine/combat-v6/tower`,45项通过。覆盖合法候选数量、110周轮转、历史输入顺序确定性、所有候选曝光、冻结包与实际Host、缺层/错误引用拒绝、领奖周前进和回退拒绝,并保留原机制测试。 +- 专项:`bun run test packages/shared/src/lib/tower packages/shared/src/engine/combat-v6/tower`,45项通过。覆盖合法候选数量、110周轮转、历史输入顺序确定性、所有候选曝光、冻结包与实际Host、缺层/错误引用拒绝、领奖周前进和回退拒绝,并保留原机制测试。 - 全量:`bun run test`,1637通过、1失败;失败为既有 `bodyCultivation/pack.test.ts` 的旧文案断言。之后新增的发布校验测试由专项运行覆盖,未为此重复全量。 - 浏览器:复用本地道友2,临时调整到金丹中期及高属性,仅用于流程验证;由真实UI两次从第1层打到第5层,公共战斗、普通双敌、治疗阵容、祝福、结算和重开正常。第一次第5层奖励225灵石、5声望、一份材料,第二次同档没有重复发放。SQL只读核对领奖一行、claims内容及原battleId保持不变、余额保持15651/20,本周配置始终一行。 - 页面:检查周阵容预览、当前周奖励、桌面与360×800奖励弹层。开发服务热重启期间曾出现临时不可用状态,恢复后第二层进度仍在;未将此视为SQL故障注入测试。 diff --git a/docs/tower-phase-three-design.md b/docs/tower-phase-three-design.md index 171ce531..66e9ad21 100644 --- a/docs/tower-phase-three-design.md +++ b/docs/tower-phase-three-design.md @@ -102,7 +102,7 @@ 3. **逐档定标。** 先解决金丹已知离群,再校准元婴至渡劫;形成配置修改与前后对照,补全周/候选覆盖。 4. **真实流程验收。** 正常构筑浏览器通关、双会话竞争、本地服务中断恢复、窄屏及预览一致性;恢复测试准备数据,保留玩法奖励和记录。 -共享确定性逻辑用 `src/shared` 测试。数据库、路由、Redis 和 UI 只用代码审查、lint/build 及本地浏览器/实际服务验证,不添加模拟单测、一次性 E2E 或跑分脚本。 +共享确定性逻辑用 `packages/shared/src` 测试。数据库、路由、Redis 和 UI 只用代码审查、lint/build 及本地浏览器/实际服务验证,不添加模拟单测、一次性 E2E 或跑分脚本。 周边界规则可提取接受显式时间的纯函数用于测试,真实服务验证不修改宿主系统时钟。优先使用已有本地验证能力;若跨周场景确需新增入口,只允许受现有纯本地保护的最小测试准备能力,不把“指定当前周/任意快进”开放给正常 API。精确故障窗口未能复现时如实标记,不把读代码记为实测通过。 diff --git a/docs/tower-phase-two-design.md b/docs/tower-phase-two-design.md index 0f323524..09f3cdb8 100644 --- a/docs/tower-phase-two-design.md +++ b/docs/tower-phase-two-design.md @@ -102,7 +102,7 @@ - 配置仍紧凑,复用连续周确定性和 16 KiB 体积检查,不保存完整 NPC、技能和预览副本。 - 现有五类机制和后半程变体有可解释的策略价值;不要求手动永远胜过 AUTO,不强制所有构筑同一种打法。 - 不引入跨层血蓝压力,不出现正常满状态基准构筑没有应对窗口却稳定被一轮击杀的组合,不强制稀有解控或灵兽。 -- 相关纯共享测试、lint/build、本地完整流程通过。仅在 src/shared 添加符合项目要求的测试,不添加服务端单测或一次性跑分脚本。 +- 相关纯共享测试、lint/build、本地完整流程通过。仅在 packages/shared/src 添加符合项目要求的测试,不添加服务端单测或一次性跑分脚本。 普通 2–3、精英 4–5、首领 6–8 回合仅作诊断起点。记录实际完整流程用时,不凭回合推算时长。既有小样本不代表新增验收已完成或统计胜率结论。 diff --git a/docs/tower-redesign-spec.md b/docs/tower-redesign-spec.md index fd9d4545..40fe5f09 100644 --- a/docs/tower-redesign-spec.md +++ b/docs/tower-redesign-spec.md @@ -22,13 +22,13 @@ | 当前事实 | 入口 | 改造方向 | | --- | --- | --- | -| 敌人技能、被动均为空;精英增加人数,首领增加血量 | `src/shared/engine/combat-v6/tower/content.ts` | 配置具体敌人原型、技能、被动及阵容 | -| 十一种祝福以六维、上限、恢复为主 | `src/shared/lib/tower/data/blessings.json` | 收敛为简单战斗属性增益,删除恢复需求 | -| 入场保存 `run.player`,跨层维护 `run.resources` 与血蓝条 | `src/server/lib/tower/combatV6.ts`、`combat-v6/tower/host.ts` | 每场重新装配当前构筑,移除幻境独立资源继承 | -| 物品、灵兽和构筑修改受整局幻境占用限制 | `src/server/lib/tower/occupancy.ts` 及调用方 | 区分挑战存在与战斗/结算占用,允许安全的层间调整 | -| 页面常驻显示挑战、祝福、奖励和榜单 | `src/react-app/routes/game/tower/route.tsx` | 按状态呈现一个主任务,辅助内容按需打开 | -| 战斗页已复用 `CombatV6Page`、`CombatV6Battle` | `src/react-app/routes/game/tower/battle/route.tsx` | 保留,只同步必要的数据契约与返回状态 | -| 公共 PvE Host 通过 `automaticCommands` 为 NPC 选招 | `src/shared/engine/combat-v6/encounter/host.ts` | 固定机制必须验证实际选招行为 | +| 敌人技能、被动均为空;精英增加人数,首领增加血量 | `packages/shared/src/engine/combat-v6/tower/content.ts` | 配置具体敌人原型、技能、被动及阵容 | +| 十一种祝福以六维、上限、恢复为主 | `packages/shared/src/lib/tower/data/blessings.json` | 收敛为简单战斗属性增益,删除恢复需求 | +| 入场保存 `run.player`,跨层维护 `run.resources` 与血蓝条 | `apps/api/src/lib/tower/combatV6.ts`、`combat-v6/tower/host.ts` | 每场重新装配当前构筑,移除幻境独立资源继承 | +| 物品、灵兽和构筑修改受整局幻境占用限制 | `apps/api/src/lib/tower/occupancy.ts` 及调用方 | 区分挑战存在与战斗/结算占用,允许安全的层间调整 | +| 页面常驻显示挑战、祝福、奖励和榜单 | `apps/web/src/routes/game/tower/route.tsx` | 按状态呈现一个主任务,辅助内容按需打开 | +| 战斗页已复用 `CombatV6Page`、`CombatV6Battle` | `apps/web/src/routes/game/tower/battle/route.tsx` | 保留,只同步必要的数据契约与返回状态 | +| 公共 PvE Host 通过 `automaticCommands` 为 NPC 选招 | `packages/shared/src/engine/combat-v6/encounter/host.ts` | 固定机制必须验证实际选招行为 | 注意:`PveCommandStrategyV1` 中仍有 `skill-rotation` 等历史字段,但类型注释明确当前 Host 统一走公共 AUTO。不能把填写该字段当作已经实现技能轮转。 @@ -303,7 +303,7 @@ - 聚焦共享测试覆盖周表、祝福、七境界二十层编译、敌人周期、封印行动空档、首领阶段、快照 RNG 恢复及金丹参考对打。 - 金丹三条路线各验证 1/5/10 层,关键层遍历六个组合;固定种子 42 共 39 场样本,回合数普通 2–4、精英 4–6、首领 5–8。样本定义见 `combat-v6/tower/balance.test.ts`,不能解释成统计胜率。 - 本地浏览器验证入口/选择/普通层战斗/返回、周表详情、层间改属性后下场生效,战内修改被拒绝;确认残血低蓝开战补满、正常资源同步、运行中无整局 player/resources 副本;战中刷新保留第 2 回合及已消耗气血,桌面与 360px 页面检查,320px 榜单弹层无溢出。普通层败北后资源结算并返回结果页,本地测试角色已恢复原境界、属性与准备性资源。 -- lint、客户端/服务端 build 通过;全量测试 1600 项通过、1 项失败,失败为本轮未修改的 `src/shared/lib/bodyCultivation/pack.test.ts` 旧文案断言。 +- lint、客户端/服务端 build 通过;全量测试 1600 项通过、1 项失败,失败为本轮未修改的 `packages/shared/src/lib/bodyCultivation/pack.test.ts` 旧文案断言。 剩余验收包括多种随机种子、无宠及养成上下沿、15/20 层实战与高境界定标;Redis/SQL 故障重试目前仅完成边界代码审查,未做故障注入验收。 @@ -321,7 +321,7 @@ 本轮浏览器验收:使用本地道友2从正常入口创建 v3 挑战,周表显示单体精英/首领双镜侍/主攻治疗的不同阵容;成员图标能打开说明;通过公共战斗页完成双敌和治疗阵容的目标选择、先杀辅助、剩余敌人继续战斗与终局返回。桌面与 360px 战前布局检查通过。护卫 15%/30% 实际伤害和阵亡时点由共享引擎测试验证,本轮未在浏览器完整推进至第 10 层。 -检查命令:`bun run lint`、`bun run build`、`bun run test src/shared/lib/tower src/shared/engine/combat-v6/tower`(37 项通过)、`bun run test`(本次改动相关检查通过,仍有未修改的炼体 pack 旧文案断言失败)、`git diff --check`。未做高境界/无宠/多种子的平衡验收。 +检查命令:`bun run lint`、`bun run build`、`bun run test packages/shared/src/lib/tower packages/shared/src/engine/combat-v6/tower`(37 项通过)、`bun run test`(本次改动相关检查通过,仍有未修改的炼体 pack 旧文案断言失败)、`git diff --check`。未做高境界/无宠/多种子的平衡验收。 本轮测试挑战已正常结束;准备时修改的角色境界、六维、气血与法力已恢复,实际通过层数与战绩保留。浏览器临时视口和测试页已清理。复用了已运行的本地开发服务,未停止其他任务的服务。 diff --git a/docs/tower-scaling-design.md b/docs/tower-scaling-design.md index d21abca0..70ab6395 100644 --- a/docs/tower-scaling-design.md +++ b/docs/tower-scaling-design.md @@ -213,7 +213,7 @@ G_output(f) = 1 + 0.025 × (f − 1) 金丹首轮基准:生命 1000,物攻 660,法攻 730,物防 220,法防 330,速度参照 255,命中 170,闪避 90,参照物防/法防 450/510。厚血、铁甲、灵障及节奏使用固定修正,不抽随机倍率。 -参考样本见 `src/shared/engine/combat-v6/tower/balance.test.ts`:50 级、自然六维各 60 加 250 分配点,50 级普通装备(种子 100–105)、宗门功法 50,无稀有特效/功法/经脉加成,普通 50 级岩甲豕(种子 42)。物理为凌霄、法术为九劫、控制为幽都勾魂阎罗路线;1/5/10 层携带当时可获得的 1/3/5 次代表性祝福。 +参考样本见 `packages/shared/src/engine/combat-v6/tower/balance.test.ts`:50 级、自然六维各 60 加 250 分配点,50 级普通装备(种子 100–105)、宗门功法 50,无稀有特效/功法/经脉加成,普通 50 级岩甲豕(种子 42)。物理为凌霄、法术为九劫、控制为幽都勾魂阎罗路线;1/5/10 层携带当时可获得的 1/3/5 次代表性祝福。 固定战斗种子 42,公共 AUTO,对三个构筑分别验证普通层和六个精英/首领组合,共 39 场;该批全部获胜,普通 2–4、精英 4–6、首领 5–8 回合。部分法术样本首领战胜后仅剩 66/135 气血,提示对种子与构筑差异仍较敏感。这是回归样本而非正式胜率结论;测试保留开局不被秒杀、基础层可通过及关键层有限回合结束的约束。 @@ -221,7 +221,7 @@ G_output(f) = 1 + 0.025 × (f − 1) ### 多敌阵容预算补充 -多敌首版将既有每层预算分摊给主敌与随从,配置见 `src/shared/lib/tower/formations.ts`。治疗阵容总生命折价 0.9、份额 70/30;双镜侍折价 0.8、份额 70/15/15。输出差值份额分别为 85/15 与 80/10/10,随从使用固定弱攻击、较薄双防,不继承主敌特性或末段强化。镜侍保护仅减免物理/法术伤害,固定伤害路线因此有明确优势。 +多敌首版将既有每层预算分摊给主敌与随从,配置见 `packages/shared/src/lib/tower/formations.ts`。治疗阵容总生命折价 0.9、份额 70/30;双镜侍折价 0.8、份额 70/15/15。输出差值份额分别为 85/15 与 80/10/10,随从使用固定弱攻击、较薄双防,不继承主敌特性或末段强化。镜侍保护仅减免物理/法术伤害,固定伤害路线因此有明确优势。 固定种子 42 的金丹三路线样本已扩大为 69 场,覆盖所有兼容的精英和首领阵容,均可获胜。此批仅是可玩性回归,不替代多种子、不同养成、先拆辅助/直接压主敌两条路径的平衡验收。 diff --git a/docs/tower-strategy-storage.md b/docs/tower-strategy-storage.md index 057d203f..072db08c 100644 --- a/docs/tower-strategy-storage.md +++ b/docs/tower-strategy-storage.md @@ -12,13 +12,13 @@ ## 配置与编译 -仓库内容位于 `src/shared/engine/combat-v6/tower/data/`: +仓库内容位于 `packages/shared/src/engine/combat-v6/tower/data/`: - `enemies.json`:原型修正、独立词条、兼容规则、行动周期、备用动作、说明。 - `mechanics.json` / `skills.ts`:既有 V6 技能和状态及其参数。 - `generation.json`:组合池、辅助配置、普通层、铺垫映射、15/20 层变体、阵容限制。 -境界基准及楼层增长继续复用 `src/shared/lib/tower/data/encounters.json`;祝福节点保存在同目录 `blessings.json`;整组份额保存在 `formations.ts`。 +境界基准及楼层增长继续复用 `packages/shared/src/lib/tower/data/encounters.json`;祝福节点保存在同目录 `blessings.json`;整组份额保存在 `formations.ts`。 `strategy-templates.ts` 只负责发布时展开。先选关键层,再生成 4/9/14/19 层铺垫,最终保存全部策略。`weekly.ts` 保留有限枚举、周序套路轮转、同周不重复、至少一个多敌关键层及近三周软避重。 diff --git a/docs/world-map-v2-proposal.md b/docs/world-map-v2-proposal.md index 4103e7c3..3d6f645b 100644 --- a/docs/world-map-v2-proposal.md +++ b/docs/world-map-v2-proposal.md @@ -18,8 +18,8 @@ Phaser 4 地图现作为统一地图入口;原 DOM 实现及专属组件已删 - `package.json`、本地 `node_modules/phaser/package.json` 均已有 Phaser 4.2.1;本次查询 npm latest 也为 4.2.1,无需为地图先升级引擎。 - 宗门清扫与采掘已有 Phaser runtime,可参考 React 挂载、回调、销毁及错误反馈的接入方式。地图应独立实现相机与输入,不直接复制小游戏固定画幅、横屏或摇杆方案。 -- 改造前的旧地图位于 `src/react-app/routes/game/map/route.tsx`(现已删除),使用 `react-zoom-pan-pinch`、DOM 节点和 SVG 连线,地图尺寸 3056×2143,初始视角含固定偏移,边界限制关闭。 -- 地图数据来自 `src/shared/data/map.json`,经 `src/shared/lib/game/mapSystem.ts` 读取。共有 20 个主节点、41 个附属节点、5 个宗门地标,共 66 个可定位对象。 +- 改造前的旧地图位于 `apps/web/src/routes/game/map/route.tsx`(现已删除),使用 `react-zoom-pan-pinch`、DOM 节点和 SVG 连线,地图尺寸 3056×2143,初始视角含固定偏移,边界限制关闭。 +- 地图数据来自 `packages/shared/src/data/map.json`,经 `packages/shared/src/lib/game/mapSystem.ts` 读取。共有 20 个主节点、41 个附属节点、5 个宗门地标,共 66 个可定位对象。 - `/game/map` 使用沉浸式 `GameMapLayout`,不属于普通 `GameViewportLayout` 正文;应保留地图沉浸壳,不套入主流程卡片页。 - `nodeId` 支持节点直达,`intent=market|dungeon|sect` 保留不同选址语义。野外、历练、坊市和宗门依赖稳定 ID,需要继续共用现有动作规则。 - 当前地图连接的可见消费者主要是地图连线,不能把示意连线直接升级成服务端旅行规则。部分跨区连接是单向配置,不应自动补反向连接。 @@ -110,7 +110,7 @@ React 负责沉浸壳、URL、玩家资源、搜索、详情抽屉与玩法跳 | 数据 | 首期处理 | | --- | --- | -| `src/shared/data/map.json` | 继续作为节点玩法事实来源,保留旧 x/y,原版外观不受影响 | +| `packages/shared/src/data/map.json` | 继续作为节点玩法事实来源,保留旧 x/y,原版外观不受影响 | | 新的区域展示配置 | 保存 region ID、显示名、总览锚点/轮廓、资源键、区域画幅及默认镜头 | | 新的节点展示配置 | 以现有地点 ID 为键,保存区域内归一化坐标、标签偏移和显示优先级 | | 图像与标记资源 | 按世界/区域组织底图;可复用 UI 图标继续走集中图标注册 | @@ -173,7 +173,7 @@ Canvas 内可用程序几何绘制简单交互标记;需要复用现有图标 ## 9. 验证安排与原始方案编写范围 -后续按用户要求不再进行浏览器验收。仅美术交付检查画面、缩小效果、解码、尺寸与压缩质量;代码接入时通过代码检查核对区域归属、直达、版本切换及玩法入口,并运行 `bun run lint`、相关 `bun run test`、`bun run build`。纯共享区域归属、ID 覆盖、坐标范围等确定性逻辑允许在 `src/shared` 添加测试;不在 React、服务端或 scripts 下增加测试框架。下文浏览器结果是历史样板验证记录,不表示后续素材已做页面验收。 +后续按用户要求不再进行浏览器验收。仅美术交付检查画面、缩小效果、解码、尺寸与压缩质量;代码接入时通过代码检查核对区域归属、直达、版本切换及玩法入口,并运行 `bun run lint`、相关 `bun run test`、`bun run build`。纯共享区域归属、ID 覆盖、坐标范围等确定性逻辑允许在 `packages/shared/src` 添加测试;不在 React、服务端或 scripts 下增加测试框架。下文浏览器结果是历史样板验证记录,不表示后续素材已做页面验收。 本次仅新增本方案文档。已执行 `git status --short`、`rg` 定位与代码读取、Python 只读地图统计,以及 npm registry 版本查询;没有安装依赖、修改业务代码、生成正式美术、启动应用或修改数据库。文档检查采用 `git diff --check` 与内容审阅。未运行 lint、单元测试、构建或浏览器性能测试,因为本次没有运行时代码变更;本文不构成性能通过或兼容性通过报告。 @@ -192,7 +192,7 @@ Canvas 内可用程序几何绘制简单交互标记;需要复用现有图标 - 显式节点归属优先于冲突 region;无效参数显示可用地图和提示。新版“关闭地图”固定回洞府,避免把内部区域浏览历史当成外部关闭目标。 - 引擎按路由动态加载,背景按访问层级加载,两个纹理最多约 12 MiB 原始 RGBA 数据(不含解码副本、标签和帧缓冲)。退出销毁 Game 与监听,仅保留当前页会话中的两个镜头快照;刷新不保证保留镜头。 -验证使用专用本地环境 `127.0.0.1:5174`,复用本地测试账号,未启动战斗、购买物品或调整角色数据。通过代码检查、`bun run lint`、`bun run build`,以及 `bun run test src/shared/lib/game/mapAtlas.test.ts src/shared/lib/game/mapSystem.test.ts`(15 项通过)。构建保留现有大包体及不生效动态导入的提示,无构建错误。 +验证使用专用本地环境 `127.0.0.1:5174`,复用本地测试账号,未启动战斗、购买物品或调整角色数据。通过代码检查、`bun run lint`、`bun run build`,以及 `bun run test packages/shared/src/lib/game/mapAtlas.test.ts packages/shared/src/lib/game/mapSystem.test.ts`(15 项通过)。构建保留现有大包体及不生效动态导入的提示,无构建错误。 浏览器已核对 1366×900 桌面和 360×800 窄屏、画布区域点击、拖动不误选、节点搜索、四种 intent、冲突/无效参数、新旧版保留节点切换、其他区域回退、坊市/野外/历练/访宗入口与直接回程、前进后退层级恢复。检查时画布在新版为一个、退出旧版后为零,控制台未见地图错误。 @@ -222,7 +222,7 @@ Canvas 内可用程序几何绘制简单交互标记;需要复用现有图标 - 地点数据仅移除旧画布坐标 `x/y` 和连线 `connections`,同时收敛类型、测试样例及秘境服务兜底对象。地点 ID、归属和玩法配置保持不变,Phaser 锚点仍由 `mapAtlas.ts` 管理。 - 保留 `mapSystem.ts`、`map.json`、`WildNodePreview`、正式地图与全部正式美术资源。宗门内部 `SectMap` 仍使用 `react-zoom-pan-pinch`,因此保留依赖及锁文件。 -验证:`bun run lint`、`bun run build`(客户端与服务端)通过;`bun run test src/shared/lib/game/mapAtlas.test.ts src/shared/lib/game/mapAtlasCategories.test.ts src/shared/lib/game/mapSystem.test.ts src/shared/engine/combat-v6/wild/pack.test.ts` 共 40 项通过。结构化比对确认全部 66 个地点的玩法数据未改变,仅删除旧坐标和连线;源码扫描确认旧组件和旧底图引用已清除,任务服务生成的链接也已迁移。按用户此前约定,本轮未执行浏览器或真机验收;构建仍有既有大包体及无效动态导入提示。 +验证:`bun run lint`、`bun run build`(客户端与服务端)通过;`bun run test packages/shared/src/lib/game/mapAtlas.test.ts packages/shared/src/lib/game/mapAtlasCategories.test.ts packages/shared/src/lib/game/mapSystem.test.ts packages/shared/src/engine/combat-v6/wild/pack.test.ts` 共 40 项通过。结构化比对确认全部 66 个地点的玩法数据未改变,仅删除旧坐标和连线;源码扫描确认旧组件和旧底图引用已清除,任务服务生成的链接也已迁移。按用户此前约定,本轮未执行浏览器或真机验收;构建仍有既有大包体及无效动态导入提示。 ## 13. 文字级联地图(2026-09-20) @@ -230,7 +230,7 @@ Canvas 内可用程序几何绘制简单交互标记;需要复用现有图标 - 文字模式按「区域 → 地点类型 → 地点」浏览,桌面分栏、移动端逐级进入;类型默认展开,可折叠,未开放区域只展示名称。 - 两种模式共享区域/节点数据、类型筛选、搜索、URL 选择状态和 `AtlasNodePanel` 操作入口。切换模式不修改区域、节点和类型参数;玩法返回继续沿用 `mapReturnTo`。 - 文字模式不初始化 Phaser;切离画卷会销毁引擎实例。地图工具栏和系统设置的「游戏设置」均可切换显示模式。 -- 浏览器普通展示偏好统一从 `src/react-app/lib/game-setting.ts` 读取及更新,localStorage key 为 `game-setting`。当前结构:`{"version":1,"mapMode":"atlas"}`,`mapMode` 可为 `atlas` 或 `text`,默认画卷。 +- 浏览器普通展示偏好统一从 `apps/web/src/lib/game-setting.ts` 读取及更新,localStorage key 为 `game-setting`。当前结构:`{"version":1,"mapMode":"atlas"}`,`mapMode` 可为 `atlas` 或 `text`,默认画卷。 - 配置属于当前浏览器,同浏览器账号共用;支持同页面订阅与跨标签页同步。损坏/不支持的配置使用默认值;存储不可用时保留本次会话中的选择。 - 后续普通本地偏好扩展 `GameSettings`、默认值、解析与变更比较,禁止业务组件直接读写该 key。凭据(如模型 API Key)、服务器角色设置及临时玩法状态不并入展示偏好。 diff --git a/docs/wuxiang-layered-forms-redesign.md b/docs/wuxiang-layered-forms-redesign.md index 5e364b73..258bde61 100644 --- a/docs/wuxiang-layered-forms-redesign.md +++ b/docs/wuxiang-layered-forms-redesign.md @@ -1200,7 +1200,7 @@ export interface AbilityModeRuntime { 旧版千行宗门编译器应拆成“公共原语、道途技能、道途节点”三层。无相宗尚未上线,本次直接替换旧目录;但不为了形式上的一技能一文件制造三十余个薄包装。当前六门技能在单一道途内高度共享构筑参数,保留为每道途一个内聚编译器更容易同时审阅 A/B/C 顺序与数值。 ```text -src/shared/engine/sect/content/wuxiang/ +packages/shared/src/engine/sect/content/wuxiang/ WuxiangSectModule.ts definition.ts ids.ts @@ -1484,12 +1484,12 @@ demon.formless.setOneFurnaceUpgrade('three-knocks', 0.85); 每阶段先跑聚焦测试,再扩大范围: ```bash -bunx vitest run src/shared/engine/battle-v5/tests/core/AbilityCosts.test.ts -bunx vitest run src/shared/engine/battle-v5/tests/core/AbilityEffectPlans.test.ts -bunx vitest run src/shared/engine/sect/content/wuxiang/tests/combatProjection.test.ts -bunx vitest run src/shared/engine/sect/content/wuxiang/tests/formCycle.test.ts -bunx vitest run src/shared/engine/sect/content/wuxiang/tests/abilityBehaviorMatrix.test.ts -bunx vitest run src/shared/engine/sect/testing/architecture/architectureGuard.test.ts +bunx vitest run packages/shared/src/engine/battle-v5/tests/core/AbilityCosts.test.ts +bunx vitest run packages/shared/src/engine/battle-v5/tests/core/AbilityEffectPlans.test.ts +bunx vitest run packages/shared/src/engine/sect/content/wuxiang/tests/combatProjection.test.ts +bunx vitest run packages/shared/src/engine/sect/content/wuxiang/tests/formCycle.test.ts +bunx vitest run packages/shared/src/engine/sect/content/wuxiang/tests/abilityBehaviorMatrix.test.ts +bunx vitest run packages/shared/src/engine/sect/testing/architecture/architectureGuard.test.ts ``` 阶段收尾: diff --git a/docs/youdu-sect-design.md b/docs/youdu-sect-design.md index 02821529..37815905 100644 --- a/docs/youdu-sect-design.md +++ b/docs/youdu-sect-design.md @@ -618,7 +618,7 @@ V1 不增加幽都专属战斗 HUD 计数器:能力详情说明“每场一次 ### 23.1 内容目录 ```text -src/shared/engine/sect/content/youdu/ +packages/shared/src/engine/sect/content/youdu/ YouduSectModule.ts definition.ts ids.ts @@ -720,9 +720,9 @@ src/shared/engine/sect/content/youdu/ ### 24.7 建议验证命令 ```bash -bunx vitest run src/shared/engine/sect/content/youdu -bunx vitest run src/shared/engine/sect -bunx vitest run src/shared/engine/battle-v5/tests +bunx vitest run packages/shared/src/engine/sect/content/youdu +bunx vitest run packages/shared/src/engine/sect +bunx vitest run packages/shared/src/engine/battle-v5/tests bun run lint bun run test bun run build diff --git a/drizzle.auth.config.ts b/drizzle.auth.config.ts index 19a243f7..a0445852 100644 --- a/drizzle.auth.config.ts +++ b/drizzle.auth.config.ts @@ -9,7 +9,7 @@ if (!databaseUrl) { export default defineConfig({ out: './drizzle-auth', dialect: 'postgresql', - schema: './src/server/lib/auth/schema.ts', + schema: './apps/api/src/lib/auth/schema.ts', schemaFilter: ['better_auth'], migrations: { schema: 'drizzle', diff --git a/drizzle.config.ts b/drizzle.config.ts index bb7f41f2..0f4f4b0c 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -9,7 +9,7 @@ if (!databaseUrl) { export default defineConfig({ out: './drizzle', dialect: 'postgresql', - schema: './src/server/lib/drizzle/schema.ts', + schema: './apps/api/src/lib/drizzle/schema.ts', tablesFilter: ['wanjiedaoyou_*'], dbCredentials: { url: databaseUrl, diff --git a/env/example.env b/env/example.env index 313dab80..95113531 100644 --- a/env/example.env +++ b/env/example.env @@ -1,6 +1,6 @@ # Server runtime environment file example. # Copy to env/staging.env for preproduction or prepare production runtime configuration. -# Pure local development uses the checked-in env/local.env. +# Pure local development uses env/local.env copied from env/local.example.env. PORT=3000 APP_ENV=staging diff --git a/env/local.example.env b/env/local.example.env new file mode 100644 index 00000000..d6ceab7c --- /dev/null +++ b/env/local.example.env @@ -0,0 +1,27 @@ +# Copy to env/local.env. These credentials are only for docker-compose.local.yml. +# Never use APP_ENV=local against remote data or copy real keys into this template. +NODE_ENV=development +APP_ENV=local +HOST=127.0.0.1 +PORT=3001 +WEB_PORT=5174 +DATABASE_URL=postgresql://daoyou:daoyou-local-only@127.0.0.1:15432/daoyou_local +DB_MAX_CONNECTIONS=4 +REDIS_URL=redis://127.0.0.1:16379/0 +NATS_SERVERS=nats://127.0.0.1:14222 +NATS_USER=app +NATS_PASSWORD=local-nats-password +NATS_CLIENT_NAME=daoyou-local +BETTER_AUTH_URL=http://127.0.0.1:5174 +BETTER_AUTH_SECRET=daoyou-local-only-auth-secret-do-not-use-in-production +PUBLIC_WEB_ORIGINS=http://127.0.0.1:5174,http://localhost:5174 +CRON_SECRET=daoyou-local-only-cron-secret +SMTP_HOST=127.0.0.1 +SMTP_PORT=11025 +SMTP_SECURE=false +SMTP_USER=local +SMTP_PASS=local +MAIL_FROM=Daoyou Local +SPONSORSHIP_FULFILLMENT_ENABLED=false +# Configure provider credentials only in your ignored env/local.env if needed. +LLM_PROVIDER= diff --git a/eslint.config.js b/eslint.config.js index 4200f6d0..5f8031e3 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -5,7 +5,7 @@ import globals from 'globals'; import tseslint from 'typescript-eslint'; export default tseslint.config( - { ignores: ['dist'] }, + { ignores: ['**/dist/**'] }, { extends: [js.configs.recommended, ...tseslint.configs.recommended], files: ['**/*.{ts,tsx}'], @@ -25,6 +25,53 @@ export default tseslint.config( ], }, }, + { + files: ['packages/shared/src/**/*.{ts,tsx}', 'apps/web/src/**/*.{ts,tsx}'], + rules: { + 'no-restricted-imports': [ + 'error', + { + patterns: [ + '@server/*', + '@daoyou/api', + '@daoyou/api/*', + '@daoyou/web', + '@daoyou/web/*', + '**/apps/api/**', + '**/apps/web/**', + ], + }, + ], + }, + }, + { + files: ['packages/shared/src/**/*.{ts,tsx}'], + ignores: ['**/*.test.*', '**/*.spec.*'], + rules: { + 'no-restricted-imports': [ + 'error', + { + patterns: [ + '@server/*', + '@app/*', + '@daoyou/api*', + '@daoyou/web*', + 'node:*', + '**/apps/**', + ], + }, + ], + }, + }, + { + files: ['apps/api/src/**/*.ts'], + rules: { + 'no-restricted-imports': [ + 'error', + { patterns: ['@app/*', '@daoyou/web*', '**/apps/web/**'] }, + ], + }, + }, { files: ['**/*.test.{ts,tsx}', '**/__mocks__/**'], rules: { diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index e797cb2b..00000000 --- a/package-lock.json +++ /dev/null @@ -1,7107 +0,0 @@ -{ - "name": "wanjiedaoyou", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "wanjiedaoyou", - "version": "0.1.0", - "license": "GPL-3.0", - "dependencies": { - "@ai-sdk/alibaba": "^2.0.45", - "@ai-sdk/deepseek": "^3.0.44", - "@better-auth/drizzle-adapter": "^1.7.4", - "@better-auth/i18n": "^1.7.4", - "@microfleet/ioredis-lock": "^6.0.1", - "@tailwindcss/vite": "^4.3.3", - "ai": "^7.0.99", - "altcha": "^3.2.2", - "altcha-lib": "^2.5.0", - "better-auth": "^1.7.4", - "canvas-confetti": "^1.9.4", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "d3-format": "^3.1.2", - "drizzle-orm": "^0.45.2", - "hono": "4.13.7", - "ioredis": "^5.11.1", - "nats": "^2.29.3", - "nodemailer": "^9.1.1", - "pg": "^8.23.0", - "phaser": "^4.2.1", - "phaser4-rex-plugins": "^4.2.0", - "react": "19.3.0", - "react-dom": "19.3.0", - "react-router": "^8.3.1", - "react-zoom-pan-pinch": "^4.2.0", - "tailwind-merge": "^3.7.0", - "tailwindcss": "^4.3.3", - "zod": "^4.6.4" - }, - "devDependencies": { - "@eslint/js": "10.0.1", - "@hono/vite-build": "1.11.1", - "@types/bun": "1.4.2", - "@types/canvas-confetti": "^1.9.0", - "@types/d3-format": "^3.0.4", - "@types/node": "^26.5.1", - "@types/nodemailer": "^8.0.1", - "@types/pg": "^8.23.1", - "@types/react": "19.3.0", - "@types/react-dom": "19.3.0", - "@vitejs/plugin-react": "6.1.1", - "drizzle-kit": "^0.31.10", - "eslint": "10.10.0", - "eslint-plugin-react-hooks": "7.1.1", - "eslint-plugin-react-refresh": "0.5.6", - "globals": "17.12.0", - "prettier": "^3.9.6", - "prettier-plugin-organize-imports": "^4.3.0", - "prettier-plugin-packagejson": "^3.0.2", - "prettier-plugin-tailwindcss": "^0.8.1", - "sponsorkit": "^18.0.0", - "typescript": "^6.0.3", - "typescript-eslint": "8.70.0", - "vite": "^8.3.0", - "vitest": "^4.1.11" - } - }, - "node_modules/@ai-sdk/alibaba": { - "version": "2.0.45", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "4.0.14", - "@ai-sdk/provider-utils": "5.0.40" - }, - "engines": { - "node": ">=22" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@ai-sdk/deepseek": { - "version": "3.0.44", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "4.0.14", - "@ai-sdk/provider-utils": "5.0.40" - }, - "engines": { - "node": ">=22" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@ai-sdk/gateway": { - "version": "4.0.80", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "4.0.14", - "@ai-sdk/provider-utils": "5.0.40", - "@vercel/oidc": "3.2.0" - }, - "engines": { - "node": ">=22" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@ai-sdk/provider": { - "version": "4.0.14", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/@ai-sdk/provider-utils": { - "version": "5.0.40", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "4.0.14", - "@standard-schema/spec": "^1.1.0", - "@workflow/serde": "4.1.0", - "eventsource-parser": "^3.0.8", - "undici": "^7.29.0" - }, - "engines": { - "node": ">=22" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", - "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.29.7", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", - "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", - "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helpers": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", - "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.8", - "@babel/types": "^7.29.8", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", - "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.29.7", - "@babel/helper-validator-option": "^7.29.7", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", - "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", - "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", - "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", - "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", - "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", - "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", - "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", - "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.8" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.29.7", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", - "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", - "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.8", - "@babel/helper-globals": "^7.29.7", - "@babel/parser": "^7.29.8", - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.8", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", - "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@better-auth/core": { - "version": "1.7.4", - "license": "MIT", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.41.1", - "@standard-schema/spec": "^1.1.0", - "zod": "^4.5.4" - }, - "peerDependencies": { - "@better-auth/utils": "0.4.2", - "@better-fetch/fetch": "1.3.1", - "@cloudflare/workers-types": ">=4", - "@opentelemetry/api": "^1.9.0", - "better-call": "1.4.0", - "jose": "^6.1.0", - "kysely": "^0.28.5 || ^0.29.0", - "nanostores": "^1.0.1" - }, - "peerDependenciesMeta": { - "@cloudflare/workers-types": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - } - } - }, - "node_modules/@better-auth/drizzle-adapter": { - "version": "1.7.4", - "license": "MIT", - "peerDependencies": { - "@better-auth/core": "^1.7.4", - "@better-auth/utils": "0.4.2", - "drizzle-orm": "^0.45.2 || >=1.0.0-rc.1 <2.0.0" - }, - "peerDependenciesMeta": { - "drizzle-orm": { - "optional": true - } - } - }, - "node_modules/@better-auth/i18n": { - "version": "1.7.4", - "license": "MIT", - "peerDependencies": { - "@better-auth/core": "^1.7.4", - "better-auth": "^1.7.4" - } - }, - "node_modules/@better-auth/kysely-adapter": { - "version": "1.7.4", - "license": "MIT", - "peerDependencies": { - "@better-auth/core": "^1.7.4", - "@better-auth/utils": "0.4.2", - "kysely": "^0.28.17 || ^0.29.0" - }, - "peerDependenciesMeta": { - "kysely": { - "optional": true - } - } - }, - "node_modules/@better-auth/memory-adapter": { - "version": "1.7.4", - "license": "MIT", - "peerDependencies": { - "@better-auth/core": "^1.7.4", - "@better-auth/utils": "0.4.2" - } - }, - "node_modules/@better-auth/mongo-adapter": { - "version": "1.7.4", - "license": "MIT", - "peerDependencies": { - "@better-auth/core": "^1.7.4", - "@better-auth/utils": "0.4.2", - "mongodb": "^6.0.0 || ^7.0.0" - }, - "peerDependenciesMeta": { - "mongodb": { - "optional": true - } - } - }, - "node_modules/@better-auth/prisma-adapter": { - "version": "1.7.4", - "license": "MIT", - "peerDependencies": { - "@better-auth/core": "^1.7.4", - "@better-auth/utils": "0.4.2", - "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", - "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0" - }, - "peerDependenciesMeta": { - "@prisma/client": { - "optional": true - }, - "prisma": { - "optional": true - } - } - }, - "node_modules/@better-auth/telemetry": { - "version": "1.7.4", - "license": "MIT", - "peerDependencies": { - "@better-auth/core": "^1.7.4", - "@better-auth/utils": "0.4.2", - "@better-fetch/fetch": "1.3.1" - } - }, - "node_modules/@better-auth/utils": { - "version": "0.4.2", - "license": "MIT", - "dependencies": { - "@noble/hashes": "^2.0.1" - } - }, - "node_modules/@better-fetch/fetch": { - "version": "1.3.1", - "license": "MIT" - }, - "node_modules/@cacheable/memory": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@cacheable/utils": "^2.5.0", - "@keyv/bigmap": "^1.3.1", - "hookified": "^1.15.1", - "keyv": "^5.6.0" - } - }, - "node_modules/@cacheable/utils": { - "version": "2.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "hashery": "^1.5.1", - "keyv": "^5.6.0" - } - }, - "node_modules/@drizzle-team/brocli": { - "version": "0.10.2", - "devOptional": true, - "license": "Apache-2.0" - }, - "node_modules/@emnapi/core": { - "version": "2.0.0-alpha.3", - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "2.0.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "2.0.0-alpha.3", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "2.0.1", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@esbuild-kit/core-utils": { - "version": "3.3.2", - "devOptional": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.18.20", - "source-map-support": "^0.5.21" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/esbuild": { - "version": "0.18.20", - "devOptional": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.18.20", - "@esbuild/android-arm64": "0.18.20", - "@esbuild/android-x64": "0.18.20", - "@esbuild/darwin-arm64": "0.18.20", - "@esbuild/darwin-x64": "0.18.20", - "@esbuild/freebsd-arm64": "0.18.20", - "@esbuild/freebsd-x64": "0.18.20", - "@esbuild/linux-arm": "0.18.20", - "@esbuild/linux-arm64": "0.18.20", - "@esbuild/linux-ia32": "0.18.20", - "@esbuild/linux-loong64": "0.18.20", - "@esbuild/linux-mips64el": "0.18.20", - "@esbuild/linux-ppc64": "0.18.20", - "@esbuild/linux-riscv64": "0.18.20", - "@esbuild/linux-s390x": "0.18.20", - "@esbuild/linux-x64": "0.18.20", - "@esbuild/netbsd-x64": "0.18.20", - "@esbuild/openbsd-x64": "0.18.20", - "@esbuild/sunos-x64": "0.18.20", - "@esbuild/win32-arm64": "0.18.20", - "@esbuild/win32-ia32": "0.18.20", - "@esbuild/win32-x64": "0.18.20" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/esbuild/node_modules/@esbuild/darwin-arm64": { - "version": "0.18.20", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/esm-loader": { - "version": "2.6.5", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@esbuild-kit/core-utils": "^3.3.2", - "get-tsconfig": "^4.7.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", - "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.23.5", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^3.0.5", - "debug": "^4.3.1", - "minimatch": "^10.2.4" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.7.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^1.2.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/core": { - "version": "1.2.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/js": { - "version": "10.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "eslint": "^10.0.0" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/@eslint/object-schema": { - "version": "3.0.5", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.7.3", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^1.2.1", - "levn": "^0.4.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@hono/vite-build": { - "version": "1.11.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.14.1" - }, - "peerDependencies": { - "hono": "*" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.2", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/types": "^0.15.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.8", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.2", - "@humanfs/types": "^0.15.0", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/types": { - "version": "0.15.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@img/colour": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.4.tgz", - "integrity": "sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.3.3" - } - }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.4.tgz", - "integrity": "sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.3.3" - } - }, - "node_modules/@img/sharp-freebsd-wasm32": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.4.tgz", - "integrity": "sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "dependencies": { - "@img/sharp-wasm32": "0.35.4" - }, - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.3.tgz", - "integrity": "sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.3.tgz", - "integrity": "sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.3.tgz", - "integrity": "sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==", - "cpu": [ - "arm" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.3.tgz", - "integrity": "sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.3.tgz", - "integrity": "sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.3.tgz", - "integrity": "sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.3.tgz", - "integrity": "sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.3.tgz", - "integrity": "sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.3.tgz", - "integrity": "sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.3.tgz", - "integrity": "sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.4.tgz", - "integrity": "sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==", - "cpu": [ - "arm" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.3.3" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.4.tgz", - "integrity": "sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.3.3" - } - }, - "node_modules/@img/sharp-linux-ppc64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.4.tgz", - "integrity": "sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.3.3" - } - }, - "node_modules/@img/sharp-linux-riscv64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.4.tgz", - "integrity": "sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.3.3" - } - }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.4.tgz", - "integrity": "sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.3.3" - } - }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.4.tgz", - "integrity": "sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.3.3" - } - }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.4.tgz", - "integrity": "sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.3.3" - } - }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.4.tgz", - "integrity": "sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.3.3" - } - }, - "node_modules/@img/sharp-wasm32": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.4.tgz", - "integrity": "sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==", - "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", - "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.11.3" - }, - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-wasm32/node_modules/@emnapi/runtime": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", - "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@img/sharp-webcontainers-wasm32": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.4.tgz", - "integrity": "sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@img/sharp-wasm32": "0.35.4" - }, - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-arm64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.4.tgz", - "integrity": "sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-ia32": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.4.tgz", - "integrity": "sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-x64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.4.tgz", - "integrity": "sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@ioredis/commands": { - "version": "1.10.0", - "license": "MIT" - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@keyv/bigmap": { - "version": "1.3.1", - "dev": true, - "license": "MIT", - "dependencies": { - "hashery": "^1.4.0", - "hookified": "^1.15.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "keyv": "^5.6.0" - } - }, - "node_modules/@keyv/serialize": { - "version": "1.1.1", - "dev": true, - "license": "MIT" - }, - "node_modules/@microfleet/ioredis-lock": { - "version": "6.0.1", - "license": "MIT", - "peerDependencies": { - "ioredis": "~4.x.x || ~5.x.x" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.2.1", - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.3" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=23.5.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", - "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" - } - }, - "node_modules/@noble/ciphers": { - "version": "2.2.0", - "license": "MIT", - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/hashes": { - "version": "2.2.0", - "license": "MIT", - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@opentelemetry/semantic-conventions": { - "version": "1.43.0", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.149.0", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/oxc-project" - } - }, - "node_modules/@quansync/fs": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "quansync": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" - } - }, - "node_modules/@rolldown/binding-android-arm-eabi": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.8.tgz", - "integrity": "sha512-tN5aztYkKCte4i5SIrrz5yK/HMjEuCqCSCJa418jOV8tZ1cBY3YF2otxB1ktPxzsLA1BeTqwapK0bfjxNvHJVw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.8.tgz", - "integrity": "sha512-dIYTWl9XprMUiQFoc55KUyk/oS8SKYH3zFl0LTR7RT0Xj4hgSVyuJcroH8JUu8RcpF8fTB6E0aOwCkZoYPcDSQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.8", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.8.tgz", - "integrity": "sha512-Uk7lRsGhPFHVX/sAUC6D5H9Ol30dFHd6iquokll2th3LpdJ3F5CzQB+7DHn0Ri2mG+U7k2zXiPHDrwZenXhwSA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.8.tgz", - "integrity": "sha512-DjszaTEVogPqA5bYzsEeqDCQxbcp2fexQwKcRspYji2yzR68fCf+e4fx6kBSRDwX5/brZaHw/hWS9+A/+/w9sQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.8.tgz", - "integrity": "sha512-zmwa7FTmdzB6aaEEuuls18H6Ap5JmJPSoPTuXixeJZV6tG40SyLkApQtz1g8ptZtiEKqj9OM0oNLPh1AgvE31Q==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.8.tgz", - "integrity": "sha512-KdYQDPHwJVnbFwdTGMgxsI9SqblBlz6STGM+w1We/d5B8OWWidYH0MwkU/uA1wM5fIpO2MkOVxXrNzzuZhw9ew==", - "cpu": [ - "arm64" - ], - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.8.tgz", - "integrity": "sha512-jFJTifHnNPY+yzOoNZQfSIysrVyXzEQPhPnOUjmD1bcQGHH6s7c8cViKWar8YplQImE5N9JRqMCLrM2CdxOrZA==", - "cpu": [ - "arm64" - ], - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.8.tgz", - "integrity": "sha512-FhiOziBDWPBjbcmRzfLyIJnaP7AVMFXT7YCXPjXxj7wKU3vx24RjrCNN/zjvVa+N2vVoHJwCoUBvsrN/DG3zIA==", - "cpu": [ - "ppc64" - ], - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.8.tgz", - "integrity": "sha512-WnHfADMzOV2Y55wlx1hzzQnar/wDt/VdvWSD99r18Mz9ylNieIGOkRx3UV21h7m/eJvjySYJkO26VvGNFkwsIQ==", - "cpu": [ - "s390x" - ], - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.8.tgz", - "integrity": "sha512-H9tRr5ibfXFVLxbPOseVewewFpl28zcEdjRDt2FTUZU7odxP0gEv1ki4/kGmcGOh78oRwZuuQllGLZ9zTJp84g==", - "cpu": [ - "x64" - ], - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.8.tgz", - "integrity": "sha512-UefiqfM3D6IVNlZ8tSGs9+Ejjud2T+oxO0IHADU45Y+lyEjD2dVFyZHbkfX0LUb5Zugo/oIv1eCO/KVYhgYJYA==", - "cpu": [ - "x64" - ], - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.8.tgz", - "integrity": "sha512-637Ke4kWSy6rp9cxQ9gMOXlxPgIw/c1beASV4M//3+9I4uwBVOOl74G+e3zyU3u19U7RkRl/HuewixZ/Z6+Rjg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.8.tgz", - "integrity": "sha512-xWBkPOF1Q9k/Gv1nQXnVdLxKu74jXppuOM4Z3mnypVUJJJwLsMl7hNJGRAUJoG8A5MgOI1ACKM+wBFxSJzKy4A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.8.tgz", - "integrity": "sha512-uz2ZvfgXbxqNwijjjbxrnvALwpyODDcgc1T1N8N3rf/DXKQmaFwmB4LX4yyjggpwN2obdQLb2rgirX5ffCWYng==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "license": "MIT" - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "license": "MIT" - }, - "node_modules/@tailwindcss/node": { - "version": "4.3.3", - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "enhanced-resolve": "^5.24.1", - "jiti": "^2.7.0", - "lightningcss": "1.32.0", - "magic-string": "^0.30.21", - "source-map-js": "^1.2.1", - "tailwindcss": "4.3.3" - } - }, - "node_modules/@tailwindcss/node/node_modules/jiti": { - "version": "2.7.0", - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/@tailwindcss/node/node_modules/lightningcss": { - "version": "1.32.0", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" - } - }, - "node_modules/@tailwindcss/node/node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/node/node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/node/node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/node/node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/node/node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/node/node_modules/lightningcss/node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.3.3", - "license": "MIT", - "engines": { - "node": ">= 20" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.3.3", - "@tailwindcss/oxide-darwin-arm64": "4.3.3", - "@tailwindcss/oxide-darwin-x64": "4.3.3", - "@tailwindcss/oxide-freebsd-x64": "4.3.3", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", - "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", - "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", - "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", - "@tailwindcss/oxide-linux-x64-musl": "4.3.3", - "@tailwindcss/oxide-wasm32-wasi": "4.3.3", - "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", - "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", - "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.3.3", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", - "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", - "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", - "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", - "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", - "cpu": [ - "arm64" - ], - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", - "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", - "cpu": [ - "arm64" - ], - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", - "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", - "cpu": [ - "x64" - ], - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", - "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", - "cpu": [ - "x64" - ], - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", - "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.11.1", - "@emnapi/runtime": "^1.11.1", - "@emnapi/wasi-threads": "^1.2.2", - "@napi-rs/wasm-runtime": "^1.1.4", - "@tybys/wasm-util": "^0.10.2", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { - "version": "1.11.1", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { - "version": "1.11.1", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { - "version": "1.2.2", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { - "version": "2.8.1", - "inBundle": true, - "license": "0BSD", - "optional": true - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", - "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", - "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.3.3", - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.3.3", - "@tailwindcss/oxide": "4.3.3", - "tailwindcss": "4.3.3" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7 || ^8" - } - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.3", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/bun": { - "version": "1.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "bun-types": "1.4.2" - } - }, - "node_modules/@types/canvas-confetti": { - "version": "1.9.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/chai": { - "version": "5.2.3", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, - "node_modules/@types/d3-format": { - "version": "3.0.4", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@types/esrecurse": { - "version": "4.3.1", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "26.5.1", - "devOptional": true, - "license": "MIT", - "dependencies": { - "undici-types": "~8.9.0" - } - }, - "node_modules/@types/nodemailer": { - "version": "8.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/pg": { - "version": "8.23.1", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "pg-protocol": "*", - "pg-types": "^2.2.0" - } - }, - "node_modules/@types/react": { - "version": "19.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "csstype": "^3.2.2" - } - }, - "node_modules/@types/react-dom": { - "version": "19.3.0", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^19.3.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.70.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.70.0", - "@typescript-eslint/type-utils": "8.70.0", - "@typescript-eslint/utils": "8.70.0", - "@typescript-eslint/visitor-keys": "8.70.0", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.70.0", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.70.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.70.0", - "@typescript-eslint/types": "8.70.0", - "@typescript-eslint/typescript-estree": "8.70.0", - "@typescript-eslint/visitor-keys": "8.70.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.70.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.70.0", - "@typescript-eslint/types": "^8.70.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.70.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.70.0", - "@typescript-eslint/visitor-keys": "8.70.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.70.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.70.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.70.0", - "@typescript-eslint/typescript-estree": "8.70.0", - "@typescript-eslint/utils": "8.70.0", - "debug": "^4.4.3", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.70.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.70.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.70.0", - "@typescript-eslint/tsconfig-utils": "8.70.0", - "@typescript-eslint/types": "8.70.0", - "@typescript-eslint/visitor-keys": "8.70.0", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.8.5", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.70.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.70.0", - "@typescript-eslint/types": "8.70.0", - "@typescript-eslint/typescript-estree": "8.70.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.70.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.70.0", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@vercel/oidc": { - "version": "3.2.0", - "license": "Apache-2.0", - "engines": { - "node": ">= 20" - } - }, - "node_modules/@vitejs/plugin-react": { - "version": "6.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@rolldown/pluginutils": "^1.0.1" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", - "babel-plugin-react-compiler": "^1.0.0", - "oxc-transform-react": "^0.145.0", - "vite": "^8.0.0" - }, - "peerDependenciesMeta": { - "@rolldown/plugin-babel": { - "optional": true - }, - "babel-plugin-react-compiler": { - "optional": true - }, - "oxc-transform-react": { - "optional": true - } - } - }, - "node_modules/@vitest/expect": { - "version": "4.1.11", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.11", - "@vitest/utils": "4.1.11", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "4.1.11", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.11", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/pretty-format": { - "version": "4.1.11", - "devOptional": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "4.1.11", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.11", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "4.1.11", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.11", - "@vitest/utils": "4.1.11", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "4.1.11", - "devOptional": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "4.1.11", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.11", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@workflow/serde": { - "version": "4.1.0", - "license": "Apache-2.0" - }, - "node_modules/acorn": { - "version": "8.16.0", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ai": { - "version": "7.0.99", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/gateway": "4.0.80", - "@ai-sdk/provider": "4.0.14", - "@ai-sdk/provider-utils": "5.0.40" - }, - "engines": { - "node": ">=22" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/ajv": { - "version": "6.15.0", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/altcha": { - "version": "3.2.2", - "license": "MIT", - "dependencies": { - "hash-wasm": "^4.12.0" - } - }, - "node_modules/altcha-lib": { - "version": "2.5.0", - "license": "MIT", - "bin": { - "altcha-lib": "bin/cli.mjs" - }, - "peerDependencies": { - "@fastify/cookie": "^11.0.0", - "@fastify/cors": "^11.0.0", - "@fastify/formbody": "^8.0.0 || ^9.0.0", - "@nestjs/common": "^10.0.0 || ^11.0.0 || ^12.0.0", - "@nestjs/core": "^10.0.0 || ^11.0.0 || ^12.0.0", - "@nestjs/platform-express": "^10.0.0 || ^11.0.0 || ^12.0.0", - "@nestjs/platform-fastify": "^10.0.0 || ^11.0.0 || ^12.0.0", - "@sveltejs/kit": "^2.0.0", - "cookie-parser": "^1.0.0", - "cors": "^2.0.0", - "express": "^4.0.0 || ^5.0.0", - "fastify": "^5.0.0", - "h3": "^2.0.0", - "hono": "^4.0.0" - }, - "peerDependenciesMeta": { - "@fastify/cookie": { - "optional": true - }, - "@fastify/cors": { - "optional": true - }, - "@fastify/formbody": { - "optional": true - }, - "@nestjs/common": { - "optional": true - }, - "@nestjs/core": { - "optional": true - }, - "@nestjs/platform-express": { - "optional": true - }, - "@nestjs/platform-fastify": { - "optional": true - }, - "@sveltejs/kit": { - "optional": true - }, - "cookie-parser": { - "optional": true - }, - "cors": { - "optional": true - }, - "express": { - "optional": true - }, - "fastify": { - "optional": true - }, - "h3": { - "optional": true - }, - "hono": { - "optional": true - } - } - }, - "node_modules/ansis": { - "version": "4.3.1", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "license": "Python-2.0" - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/balanced-match": { - "version": "4.0.4", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.11.23", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.23.tgz", - "integrity": "sha512-le521dGVfxM7yRX0EikCoSz+rOK+hHzdDt/E7mG1jOJB/6WAAUuwVroLwaB7ApaUsz5Q0kFlDXLSA9MheUIfRQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/better-auth": { - "version": "1.7.4", - "license": "MIT", - "dependencies": { - "@better-auth/core": "1.7.4", - "@better-auth/drizzle-adapter": "1.7.4", - "@better-auth/kysely-adapter": "1.7.4", - "@better-auth/memory-adapter": "1.7.4", - "@better-auth/mongo-adapter": "1.7.4", - "@better-auth/prisma-adapter": "1.7.4", - "@better-auth/telemetry": "1.7.4", - "@better-auth/utils": "0.4.2", - "@better-fetch/fetch": "1.3.1", - "@noble/ciphers": "^2.2.0", - "@noble/hashes": "^2.2.0", - "better-call": "1.4.0", - "defu": "^6.1.4", - "jose": "^6.2.3", - "kysely": "^0.28.17 || ^0.29.0", - "nanostores": "^1.3.0", - "zod": "^4.5.4" - }, - "peerDependencies": { - "@lynx-js/react": "*", - "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", - "@sveltejs/kit": "^2.0.0", - "@tanstack/react-start": "^1.0.0", - "@tanstack/solid-start": "^1.0.0", - "better-sqlite3": "^12.0.0", - "drizzle-kit": ">=0.31.4 || >=1.0.0-beta.1", - "drizzle-orm": "^0.45.2 || >=1.0.0-rc.1 <2.0.0", - "mongodb": "^6.0.0 || ^7.0.0", - "mysql2": "^3.0.0", - "next": "^14.0.0 || ^15.0.0 || ^16.0.0", - "pg": "^8.0.0", - "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0", - "solid-js": "^1.0.0", - "svelte": "^4.0.0 || ^5.0.0", - "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", - "vue": "^3.0.0" - }, - "peerDependenciesMeta": { - "@lynx-js/react": { - "optional": true - }, - "@prisma/client": { - "optional": true - }, - "@sveltejs/kit": { - "optional": true - }, - "@tanstack/react-start": { - "optional": true - }, - "@tanstack/solid-start": { - "optional": true - }, - "better-sqlite3": { - "optional": true - }, - "drizzle-kit": { - "optional": true - }, - "drizzle-orm": { - "optional": true - }, - "mongodb": { - "optional": true - }, - "mysql2": { - "optional": true - }, - "next": { - "optional": true - }, - "pg": { - "optional": true - }, - "prisma": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "solid-js": { - "optional": true - }, - "svelte": { - "optional": true - }, - "vitest": { - "optional": true - }, - "vue": { - "optional": true - } - } - }, - "node_modules/better-call": { - "version": "1.4.0", - "license": "MIT", - "dependencies": { - "@better-auth/utils": "^0.5.0", - "@better-fetch/fetch": "^1.3.1", - "rou3": "^0.9.1", - "set-cookie-parser": "^3.1.2" - }, - "peerDependencies": { - "zod": "^4.0.0" - }, - "peerDependenciesMeta": { - "zod": { - "optional": true - } - } - }, - "node_modules/better-call/node_modules/@better-auth/utils": { - "version": "0.5.0", - "license": "MIT", - "dependencies": { - "@noble/hashes": "^2.0.1" - } - }, - "node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/browserslist": { - "version": "4.28.9", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.9.tgz", - "integrity": "sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.11.20", - "caniuse-lite": "^1.0.30001810", - "electron-to-chromium": "^1.5.420", - "node-releases": "^2.0.54", - "update-browserslist-db": "^1.3.2" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "devOptional": true, - "license": "MIT" - }, - "node_modules/bun-types": { - "version": "1.4.2", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/cac": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.19.0" - } - }, - "node_modules/cacheable": { - "version": "2.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@cacheable/memory": "^2.2.0", - "@cacheable/utils": "^2.5.0", - "hookified": "^1.15.0", - "keyv": "^5.6.0", - "qified": "^0.10.1" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001810", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", - "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/canvas-confetti": { - "version": "1.9.4", - "license": "ISC", - "funding": { - "type": "donate", - "url": "https://www.paypal.me/kirilvatev" - } - }, - "node_modules/chai": { - "version": "6.2.2", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/class-variance-authority": { - "version": "0.7.1", - "license": "Apache-2.0", - "dependencies": { - "clsx": "^2.1.1" - }, - "funding": { - "url": "https://polar.sh/cva" - } - }, - "node_modules/clsx": { - "version": "2.1.1", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/cluster-key-slot": { - "version": "1.1.1", - "license": "Apache-2.0", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/consola": { - "version": "3.4.2", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.18.0 || >=16.10.0" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "devOptional": true, - "license": "MIT" - }, - "node_modules/cookie-es": { - "version": "3.1.1", - "license": "MIT" - }, - "node_modules/cross-fetch": { - "version": "4.1.0", - "license": "MIT", - "dependencies": { - "node-fetch": "^2.7.0" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/csstype": { - "version": "3.2.3", - "dev": true, - "license": "MIT" - }, - "node_modules/d3-format": { - "version": "3.1.2", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-hierarchy": { - "version": "3.1.2", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/dagre": { - "version": "0.8.5", - "license": "MIT", - "dependencies": { - "graphlib": "^2.1.8", - "lodash": "^4.17.15" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/defu": { - "version": "6.1.7", - "license": "MIT" - }, - "node_modules/denque": { - "version": "2.1.0", - "license": "Apache-2.0", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/destr": { - "version": "2.0.5", - "dev": true, - "license": "MIT" - }, - "node_modules/detect-indent": { - "version": "7.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/detect-newline": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/drizzle-kit": { - "version": "0.31.10", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@drizzle-team/brocli": "^0.10.2", - "@esbuild-kit/esm-loader": "^2.5.5", - "esbuild": "^0.25.4", - "tsx": "^4.21.0" - }, - "bin": { - "drizzle-kit": "bin.cjs" - } - }, - "node_modules/drizzle-kit/node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/drizzle-kit/node_modules/esbuild": { - "version": "0.25.12", - "devOptional": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, - "node_modules/drizzle-orm": { - "version": "0.45.2", - "license": "Apache-2.0", - "peerDependencies": { - "@aws-sdk/client-rds-data": ">=3", - "@cloudflare/workers-types": ">=4", - "@electric-sql/pglite": ">=0.2.0", - "@libsql/client": ">=0.10.0", - "@libsql/client-wasm": ">=0.10.0", - "@neondatabase/serverless": ">=0.10.0", - "@op-engineering/op-sqlite": ">=2", - "@opentelemetry/api": "^1.4.1", - "@planetscale/database": ">=1.13", - "@prisma/client": "*", - "@tidbcloud/serverless": "*", - "@types/better-sqlite3": "*", - "@types/pg": "*", - "@types/sql.js": "*", - "@upstash/redis": ">=1.34.7", - "@vercel/postgres": ">=0.8.0", - "@xata.io/client": "*", - "better-sqlite3": ">=7", - "bun-types": "*", - "expo-sqlite": ">=14.0.0", - "gel": ">=2", - "knex": "*", - "kysely": "*", - "mysql2": ">=2", - "pg": ">=8", - "postgres": ">=3", - "sql.js": ">=1", - "sqlite3": ">=5" - }, - "peerDependenciesMeta": { - "@aws-sdk/client-rds-data": { - "optional": true - }, - "@cloudflare/workers-types": { - "optional": true - }, - "@electric-sql/pglite": { - "optional": true - }, - "@libsql/client": { - "optional": true - }, - "@libsql/client-wasm": { - "optional": true - }, - "@neondatabase/serverless": { - "optional": true - }, - "@op-engineering/op-sqlite": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@planetscale/database": { - "optional": true - }, - "@prisma/client": { - "optional": true - }, - "@tidbcloud/serverless": { - "optional": true - }, - "@types/better-sqlite3": { - "optional": true - }, - "@types/pg": { - "optional": true - }, - "@types/sql.js": { - "optional": true - }, - "@upstash/redis": { - "optional": true - }, - "@vercel/postgres": { - "optional": true - }, - "@xata.io/client": { - "optional": true - }, - "better-sqlite3": { - "optional": true - }, - "bun-types": { - "optional": true - }, - "expo-sqlite": { - "optional": true - }, - "gel": { - "optional": true - }, - "knex": { - "optional": true - }, - "kysely": { - "optional": true - }, - "mysql2": { - "optional": true - }, - "pg": { - "optional": true - }, - "postgres": { - "optional": true - }, - "prisma": { - "optional": true - }, - "sql.js": { - "optional": true - }, - "sqlite3": { - "optional": true - } - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.427", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.427.tgz", - "integrity": "sha512-n14zb3FdsChZ2BNobqNHAJMcP3ifFv4paox2LvCrfVAQcqGiSURgbJl+PfMpHVCNFkStnNc+RRVtPBTVW5PDgw==", - "dev": true, - "license": "ISC" - }, - "node_modules/enhanced-resolve": { - "version": "5.24.3", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/es-module-lexer": { - "version": "2.1.0", - "devOptional": true, - "license": "MIT" - }, - "node_modules/esbuild": { - "version": "0.27.7", - "devOptional": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.7", - "@esbuild/android-arm": "0.27.7", - "@esbuild/android-arm64": "0.27.7", - "@esbuild/android-x64": "0.27.7", - "@esbuild/darwin-arm64": "0.27.7", - "@esbuild/darwin-x64": "0.27.7", - "@esbuild/freebsd-arm64": "0.27.7", - "@esbuild/freebsd-x64": "0.27.7", - "@esbuild/linux-arm": "0.27.7", - "@esbuild/linux-arm64": "0.27.7", - "@esbuild/linux-ia32": "0.27.7", - "@esbuild/linux-loong64": "0.27.7", - "@esbuild/linux-mips64el": "0.27.7", - "@esbuild/linux-ppc64": "0.27.7", - "@esbuild/linux-riscv64": "0.27.7", - "@esbuild/linux-s390x": "0.27.7", - "@esbuild/linux-x64": "0.27.7", - "@esbuild/netbsd-arm64": "0.27.7", - "@esbuild/netbsd-x64": "0.27.7", - "@esbuild/openbsd-arm64": "0.27.7", - "@esbuild/openbsd-x64": "0.27.7", - "@esbuild/openharmony-arm64": "0.27.7", - "@esbuild/sunos-x64": "0.27.7", - "@esbuild/win32-arm64": "0.27.7", - "@esbuild/win32-ia32": "0.27.7", - "@esbuild/win32-x64": "0.27.7" - } - }, - "node_modules/esbuild/node_modules/@esbuild/aix-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", - "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/android-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", - "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/android-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", - "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/android-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", - "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/darwin-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", - "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", - "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/freebsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", - "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", - "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", - "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", - "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-loong64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", - "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-mips64el": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", - "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", - "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-riscv64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", - "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-s390x": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", - "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/linux-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", - "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", - "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/netbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", - "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", - "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/openbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", - "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", - "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/sunos-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", - "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/win32-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", - "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/win32-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", - "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/esbuild/node_modules/@esbuild/win32-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", - "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "10.10.0", - "dev": true, - "license": "MIT", - "workspaces": [ - "packages/*" - ], - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.2", - "@eslint/config-array": "^0.23.5", - "@eslint/config-helpers": "^0.7.0", - "@eslint/core": "^1.2.1", - "@eslint/plugin-kit": "^0.7.3", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.14.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^9.1.2", - "eslint-visitor-keys": "^5.0.1", - "espree": "^11.2.0", - "esquery": "^1.7.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "11.1.5 || >11.1.6 <12", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "minimatch": "^10.2.5", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "7.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.24.4", - "@babel/parser": "^7.24.4", - "hermes-parser": "^0.25.1", - "zod": "^3.25.0 || ^4.0.0", - "zod-validation-error": "^3.5.0 || ^4.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" - } - }, - "node_modules/eslint-plugin-react-refresh": { - "version": "0.5.6", - "dev": true, - "license": "MIT", - "peerDependencies": { - "eslint": "^9 || ^10" - } - }, - "node_modules/eslint-scope": { - "version": "9.1.2", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@types/esrecurse": "^4.3.1", - "@types/estree": "^1.0.8", - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree": { - "version": "11.2.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.16.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^5.0.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.7.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eventemitter3": { - "version": "5.0.4", - "license": "MIT" - }, - "node_modules/events": { - "version": "3.3.0", - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/eventsource-parser": { - "version": "3.0.8", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/expect-type": { - "version": "1.3.0", - "devOptional": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "dev": true, - "license": "MIT" - }, - "node_modules/fdir": { - "version": "6.5.0", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/file-entry-cache": { - "version": "11.1.5", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^6.1.23" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "6.1.23", - "dev": true, - "license": "MIT", - "dependencies": { - "cacheable": "^2.5.0", - "flatted": "^3.4.2", - "hookified": "^1.15.0" - } - }, - "node_modules/flatted": { - "version": "3.4.2", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-tsconfig": { - "version": "4.14.0", - "devOptional": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/git-hooks-list": { - "version": "4.2.1", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/fisker/git-hooks-list?sponsor=1" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "17.12.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "license": "ISC" - }, - "node_modules/graphlib": { - "version": "2.1.8", - "license": "MIT", - "dependencies": { - "lodash": "^4.17.15" - } - }, - "node_modules/graphology": { - "version": "0.25.4", - "license": "MIT", - "dependencies": { - "events": "^3.3.0", - "obliterator": "^2.0.2" - }, - "peerDependencies": { - "graphology-types": ">=0.24.0" - } - }, - "node_modules/graphology-types": { - "version": "0.24.8", - "license": "MIT", - "peer": true - }, - "node_modules/hash-wasm": { - "version": "4.12.0", - "license": "MIT" - }, - "node_modules/hashery": { - "version": "1.5.1", - "dev": true, - "license": "MIT", - "dependencies": { - "hookified": "^1.15.0" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/hermes-estree": { - "version": "0.25.1", - "dev": true, - "license": "MIT" - }, - "node_modules/hermes-parser": { - "version": "0.25.1", - "dev": true, - "license": "MIT", - "dependencies": { - "hermes-estree": "0.25.1" - } - }, - "node_modules/hono": { - "version": "4.13.7", - "license": "MIT", - "engines": { - "node": ">=16.9.0" - } - }, - "node_modules/hookified": { - "version": "1.15.1", - "dev": true, - "license": "MIT" - }, - "node_modules/i18next": { - "version": "22.5.1", - "funding": [ - { - "type": "individual", - "url": "https://locize.com" - }, - { - "type": "individual", - "url": "https://locize.com/i18next.html" - }, - { - "type": "individual", - "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" - } - ], - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.20.6" - } - }, - "node_modules/i18next-http-backend": { - "version": "3.0.6", - "license": "MIT", - "dependencies": { - "cross-fetch": "4.1.0" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/ioredis": { - "version": "5.11.1", - "license": "MIT", - "dependencies": { - "@ioredis/commands": "1.10.0", - "cluster-key-slot": "1.1.1", - "debug": "4.4.3", - "denque": "2.1.0", - "redis-errors": "1.2.0", - "redis-parser": "3.0.0", - "standard-as-callback": "2.1.0" - }, - "engines": { - "node": ">=12.22.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ioredis" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-plain-obj": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/jose": { - "version": "6.2.3", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", - "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/nodeca" - } - ], - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-schema": { - "version": "0.4.0", - "license": "(AFL-2.1 OR BSD-3-Clause)" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/keyv": { - "version": "5.6.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@keyv/serialize": "^1.1.1" - } - }, - "node_modules/kysely": { - "version": "0.28.17", - "license": "MIT", - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lightningcss": { - "version": "1.33.0", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.33.0", - "lightningcss-darwin-arm64": "1.33.0", - "lightningcss-darwin-x64": "1.33.0", - "lightningcss-freebsd-x64": "1.33.0", - "lightningcss-linux-arm-gnueabihf": "1.33.0", - "lightningcss-linux-arm64-gnu": "1.33.0", - "lightningcss-linux-arm64-musl": "1.33.0", - "lightningcss-linux-x64-gnu": "1.33.0", - "lightningcss-linux-x64-musl": "1.33.0", - "lightningcss-win32-arm64-msvc": "1.33.0", - "lightningcss-win32-x64-msvc": "1.33.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", - "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.33.0", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", - "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", - "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", - "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", - "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", - "cpu": [ - "arm64" - ], - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", - "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", - "cpu": [ - "arm64" - ], - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", - "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", - "cpu": [ - "x64" - ], - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", - "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", - "cpu": [ - "x64" - ], - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", - "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", - "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.18.1", - "license": "MIT" - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/minimatch": { - "version": "10.2.5", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "license": "MIT" - }, - "node_modules/mustache": { - "version": "4.2.0", - "license": "MIT", - "bin": { - "mustache": "bin/mustache" - } - }, - "node_modules/nanoid": { - "version": "3.3.19", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/nanostores": { - "version": "1.3.0", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "engines": { - "node": "^20.0.0 || >=22.0.0" - } - }, - "node_modules/nats": { - "version": "2.29.3", - "license": "Apache-2.0", - "dependencies": { - "nkeys.js": "1.1.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/nkeys.js": { - "version": "1.1.0", - "license": "Apache-2.0", - "dependencies": { - "tweetnacl": "1.0.3" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-fetch-native": { - "version": "1.6.7", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.55", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.55.tgz", - "integrity": "sha512-mIrE/Cw9y+9Au6dS5vDKDhQza9YvG6w+ZrS6X+ZzA7yFW/soAeaups4Qzn1bL6g5FVy8WtP79+0j82oPIbqRjQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/nodemailer": { - "version": "9.1.1", - "license": "MIT-0", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/normalize-url": { - "version": "9.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/obliterator": { - "version": "2.0.5", - "license": "MIT" - }, - "node_modules/obug": { - "version": "2.1.1", - "devOptional": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT" - }, - "node_modules/ofetch": { - "version": "1.5.1", - "dev": true, - "license": "MIT", - "dependencies": { - "destr": "^2.0.5", - "node-fetch-native": "^1.6.7", - "ufo": "^1.6.1" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "7.3.1", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^1.2.1" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate/node_modules/p-limit/node_modules/yocto-queue": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/papaparse": { - "version": "5.6.0", - "license": "MIT" - }, - "node_modules/path-exists": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pathe": { - "version": "2.0.3", - "devOptional": true, - "license": "MIT" - }, - "node_modules/pg": { - "version": "8.23.0", - "license": "MIT", - "dependencies": { - "pg-connection-string": "^2.14.0", - "pg-pool": "^3.14.0", - "pg-protocol": "^1.16.0", - "pg-types": "2.2.0", - "pgpass": "1.0.5" - }, - "engines": { - "node": ">= 16.0.0" - }, - "optionalDependencies": { - "pg-cloudflare": "^1.4.0" - }, - "peerDependencies": { - "pg-native": ">=3.0.1" - }, - "peerDependenciesMeta": { - "pg-native": { - "optional": true - } - } - }, - "node_modules/pg-cloudflare": { - "version": "1.4.0", - "license": "MIT", - "optional": true - }, - "node_modules/pg-connection-string": { - "version": "2.14.0", - "license": "MIT" - }, - "node_modules/pg-int8": { - "version": "1.0.1", - "license": "ISC", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/pg-pool": { - "version": "3.14.0", - "license": "MIT", - "peerDependencies": { - "pg": ">=8.0" - } - }, - "node_modules/pg-protocol": { - "version": "1.15.0", - "devOptional": true, - "license": "MIT" - }, - "node_modules/pg-types": { - "version": "2.2.0", - "license": "MIT", - "dependencies": { - "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pg/node_modules/pg-protocol": { - "version": "1.16.0", - "license": "MIT" - }, - "node_modules/pgpass": { - "version": "1.0.5", - "license": "MIT", - "dependencies": { - "split2": "^4.1.0" - } - }, - "node_modules/phaser": { - "version": "4.2.1", - "license": "MIT", - "dependencies": { - "eventemitter3": "^5.0.4" - } - }, - "node_modules/phaser4-rex-plugins": { - "version": "4.2.0", - "license": "MIT", - "dependencies": { - "dagre": "^0.8.5", - "eventemitter3": "^3.1.2", - "graphology": "^0.25.4", - "i18next": "^22.5.1", - "i18next-http-backend": "^3.0.5", - "js-yaml": "^4.1.0", - "mustache": "^4.2.0", - "papaparse": "^5.4.1", - "tinyqueue": "^3.0.0", - "webfontloader": "^1.6.28" - } - }, - "node_modules/phaser4-rex-plugins/node_modules/eventemitter3": { - "version": "3.1.2", - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.7", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.28", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.18", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postgres-array": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/postgres-bytea": { - "version": "1.0.1", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-date": { - "version": "1.0.7", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-interval": { - "version": "1.2.0", - "license": "MIT", - "dependencies": { - "xtend": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.9.6", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-plugin-organize-imports": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "peerDependencies": { - "prettier": ">=2.0", - "typescript": ">=2.9", - "vue-tsc": "^2.1.0 || 3" - }, - "peerDependenciesMeta": { - "vue-tsc": { - "optional": true - } - } - }, - "node_modules/prettier-plugin-packagejson": { - "version": "3.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "sort-package-json": "^3.6.0" - }, - "peerDependencies": { - "prettier": "^3" - }, - "peerDependenciesMeta": { - "prettier": { - "optional": true - } - } - }, - "node_modules/prettier-plugin-tailwindcss": { - "version": "0.8.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.19" - }, - "peerDependencies": { - "@ianvs/prettier-plugin-sort-imports": "*", - "@prettier/plugin-hermes": "*", - "@prettier/plugin-oxc": "*", - "@prettier/plugin-pug": "*", - "@shopify/prettier-plugin-liquid": "*", - "@trivago/prettier-plugin-sort-imports": "*", - "@zackad/prettier-plugin-twig": "*", - "prettier": "^3.0", - "prettier-plugin-astro": "*", - "prettier-plugin-css-order": "*", - "prettier-plugin-jsdoc": "*", - "prettier-plugin-marko": "*", - "prettier-plugin-multiline-arrays": "*", - "prettier-plugin-organize-attributes": "*", - "prettier-plugin-organize-imports": "*", - "prettier-plugin-sort-imports": "*", - "prettier-plugin-svelte": "*" - }, - "peerDependenciesMeta": { - "@ianvs/prettier-plugin-sort-imports": { - "optional": true - }, - "@prettier/plugin-hermes": { - "optional": true - }, - "@prettier/plugin-oxc": { - "optional": true - }, - "@prettier/plugin-pug": { - "optional": true - }, - "@shopify/prettier-plugin-liquid": { - "optional": true - }, - "@trivago/prettier-plugin-sort-imports": { - "optional": true - }, - "@zackad/prettier-plugin-twig": { - "optional": true - }, - "prettier-plugin-astro": { - "optional": true - }, - "prettier-plugin-css-order": { - "optional": true - }, - "prettier-plugin-jsdoc": { - "optional": true - }, - "prettier-plugin-marko": { - "optional": true - }, - "prettier-plugin-multiline-arrays": { - "optional": true - }, - "prettier-plugin-organize-attributes": { - "optional": true - }, - "prettier-plugin-organize-imports": { - "optional": true - }, - "prettier-plugin-sort-imports": { - "optional": true - }, - "prettier-plugin-svelte": { - "optional": true - } - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/qified": { - "version": "0.10.1", - "dev": true, - "license": "MIT", - "dependencies": { - "hookified": "^2.1.1" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/qified/node_modules/hookified": { - "version": "2.2.0", - "dev": true, - "license": "MIT" - }, - "node_modules/quansync": { - "version": "1.0.0", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/antfu" - }, - { - "type": "individual", - "url": "https://github.com/sponsors/sxzz" - } - ], - "license": "MIT" - }, - "node_modules/react": { - "version": "19.3.0", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "19.3.0", - "license": "MIT", - "dependencies": { - "scheduler": "^0.28.0" - }, - "peerDependencies": { - "react": "^19.3.0" - } - }, - "node_modules/react-router": { - "version": "8.3.1", - "license": "MIT", - "dependencies": { - "cookie-es": "^3.1.1" - }, - "engines": { - "node": ">=22.22.0" - }, - "peerDependencies": { - "react": ">=19.2.7", - "react-dom": ">=19.2.7" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - } - } - }, - "node_modules/react-zoom-pan-pinch": { - "version": "4.2.0", - "license": "MIT", - "engines": { - "node": ">=8", - "npm": ">=5" - }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - } - }, - "node_modules/redis-errors": { - "version": "1.2.0", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/redis-parser": { - "version": "3.0.0", - "license": "MIT", - "dependencies": { - "redis-errors": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "devOptional": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/rolldown": { - "version": "1.2.8", - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.149.0", - "@rolldown/pluginutils": "^1.0.0" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm-eabi": "1.2.8", - "@rolldown/binding-android-arm64": "1.2.8", - "@rolldown/binding-darwin-arm64": "1.2.8", - "@rolldown/binding-darwin-x64": "1.2.8", - "@rolldown/binding-freebsd-x64": "1.2.8", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.8", - "@rolldown/binding-linux-arm64-gnu": "1.2.8", - "@rolldown/binding-linux-arm64-musl": "1.2.8", - "@rolldown/binding-linux-ppc64-gnu": "1.2.8", - "@rolldown/binding-linux-s390x-gnu": "1.2.8", - "@rolldown/binding-linux-x64-gnu": "1.2.8", - "@rolldown/binding-linux-x64-musl": "1.2.8", - "@rolldown/binding-openharmony-arm64": "1.2.8", - "@rolldown/binding-win32-arm64-msvc": "1.2.8", - "@rolldown/binding-win32-x64-msvc": "1.2.8" - } - }, - "node_modules/rou3": { - "version": "0.9.2", - "license": "MIT" - }, - "node_modules/scheduler": { - "version": "0.28.0", - "license": "MIT" - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/set-cookie-parser": { - "version": "3.1.2", - "license": "MIT" - }, - "node_modules/sharp": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.4.tgz", - "integrity": "sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@img/colour": "^1.1.0", - "detect-libc": "^2.1.2", - "semver": "^7.8.5" - }, - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.35.4", - "@img/sharp-darwin-x64": "0.35.4", - "@img/sharp-freebsd-wasm32": "0.35.4", - "@img/sharp-libvips-darwin-arm64": "1.3.3", - "@img/sharp-libvips-darwin-x64": "1.3.3", - "@img/sharp-libvips-linux-arm": "1.3.3", - "@img/sharp-libvips-linux-arm64": "1.3.3", - "@img/sharp-libvips-linux-ppc64": "1.3.3", - "@img/sharp-libvips-linux-riscv64": "1.3.3", - "@img/sharp-libvips-linux-s390x": "1.3.3", - "@img/sharp-libvips-linux-x64": "1.3.3", - "@img/sharp-libvips-linuxmusl-arm64": "1.3.3", - "@img/sharp-libvips-linuxmusl-x64": "1.3.3", - "@img/sharp-linux-arm": "0.35.4", - "@img/sharp-linux-arm64": "0.35.4", - "@img/sharp-linux-ppc64": "0.35.4", - "@img/sharp-linux-riscv64": "0.35.4", - "@img/sharp-linux-s390x": "0.35.4", - "@img/sharp-linux-x64": "0.35.4", - "@img/sharp-linuxmusl-arm64": "0.35.4", - "@img/sharp-linuxmusl-x64": "0.35.4", - "@img/sharp-webcontainers-wasm32": "0.35.4", - "@img/sharp-win32-arm64": "0.35.4", - "@img/sharp-win32-ia32": "0.35.4", - "@img/sharp-win32-x64": "0.35.4" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/sharp/node_modules/semver": { - "version": "7.8.5", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "devOptional": true, - "license": "ISC" - }, - "node_modules/sort-object-keys": { - "version": "2.1.0", - "dev": true, - "license": "MIT" - }, - "node_modules/sort-package-json": { - "version": "3.6.1", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-indent": "^7.0.2", - "detect-newline": "^4.0.1", - "git-hooks-list": "^4.1.1", - "is-plain-obj": "^4.1.0", - "semver": "^7.7.3", - "sort-object-keys": "^2.0.1", - "tinyglobby": "^0.2.15" - }, - "bin": { - "sort-package-json": "cli.js" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/sort-package-json/node_modules/semver": { - "version": "7.7.4", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "devOptional": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "devOptional": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/split2": { - "version": "4.2.0", - "license": "ISC", - "engines": { - "node": ">= 10.x" - } - }, - "node_modules/sponsorkit": { - "version": "18.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansis": "^4.3.1", - "cac": "^7.0.0", - "consola": "^3.4.2", - "d3-hierarchy": "^3.1.2", - "normalize-url": "^9.0.1", - "ofetch": "^1.5.1", - "p-limit": "^7.3.1", - "sharp": "^0.35.3", - "unconfig": "^7.5.0" - }, - "bin": { - "sponsorkit": "dist/cli.mjs" - }, - "engines": { - "node": "^22.18.0 || ^24.11.0 || >=26.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/stackback": { - "version": "0.0.2", - "devOptional": true, - "license": "MIT" - }, - "node_modules/standard-as-callback": { - "version": "2.1.0", - "license": "MIT" - }, - "node_modules/std-env": { - "version": "4.1.0", - "devOptional": true, - "license": "MIT" - }, - "node_modules/tailwind-merge": { - "version": "3.7.0", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/dcastil" - } - }, - "node_modules/tailwindcss": { - "version": "4.3.3", - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.3.3", - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/tinybench": { - "version": "2.9.0", - "devOptional": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "1.1.2", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.17", - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyqueue": { - "version": "3.0.0", - "license": "ISC" - }, - "node_modules/tinyrainbow": { - "version": "3.1.0", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "license": "MIT" - }, - "node_modules/ts-api-utils": { - "version": "2.5.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "license": "0BSD", - "optional": true - }, - "node_modules/tsx": { - "version": "4.21.0", - "devOptional": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.27.0", - "get-tsconfig": "^4.7.5" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/tweetnacl": { - "version": "1.0.3", - "license": "Unlicense" - }, - "node_modules/type-check": { - "version": "0.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/typescript": { - "version": "6.0.3", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-eslint": { - "version": "8.70.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.70.0", - "@typescript-eslint/parser": "8.70.0", - "@typescript-eslint/typescript-estree": "8.70.0", - "@typescript-eslint/utils": "8.70.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/ufo": { - "version": "1.6.4", - "dev": true, - "license": "MIT" - }, - "node_modules/unconfig": { - "version": "7.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@quansync/fs": "^1.0.0", - "defu": "^6.1.4", - "jiti": "^2.6.1", - "quansync": "^1.0.0", - "unconfig-core": "7.5.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/unconfig-core": { - "version": "7.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@quansync/fs": "^1.0.0", - "quansync": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/unconfig/node_modules/jiti": { - "version": "2.7.0", - "dev": true, - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/undici": { - "version": "7.29.0", - "license": "MIT", - "engines": { - "node": ">=20.18.1" - } - }, - "node_modules/undici-types": { - "version": "8.9.0", - "devOptional": true, - "license": "MIT" - }, - "node_modules/update-browserslist-db": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz", - "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/vite": { - "version": "8.3.0", - "license": "MIT", - "dependencies": { - "lightningcss": "^1.33.0", - "picomatch": "^4.0.7", - "postcss": "^8.5.28", - "rolldown": "~1.2.6", - "tinyglobby": "^0.2.17" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.7.1", - "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" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vitest": { - "version": "4.1.11", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "4.1.11", - "@vitest/mocker": "4.1.11", - "@vitest/pretty-format": "4.1.11", - "@vitest/runner": "4.1.11", - "@vitest/snapshot": "4.1.11", - "@vitest/spy": "4.1.11", - "@vitest/utils": "4.1.11", - "es-module-lexer": "^2.0.0", - "expect-type": "^1.3.0", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^4.0.0-rc.1", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.1.0", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.11", - "@vitest/browser-preview": "4.1.11", - "@vitest/browser-webdriverio": "4.1.11", - "@vitest/coverage-istanbul": "4.1.11", - "@vitest/coverage-v8": "4.1.11", - "@vitest/ui": "4.1.11", - "happy-dom": "*", - "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, - "@vitest/coverage-istanbul": { - "optional": true - }, - "@vitest/coverage-v8": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "vite": { - "optional": false - } - } - }, - "node_modules/webfontloader": { - "version": "1.6.28", - "license": "Apache-2.0" - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "devOptional": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/yocto-queue": { - "version": "1.2.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zod": { - "version": "4.6.4", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-validation-error": { - "version": "4.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" - } - } - } -} diff --git a/package.json b/package.json index b4ceb2ee..d98b877e 100644 --- a/package.json +++ b/package.json @@ -5,70 +5,32 @@ "license": "GPL-3.0", "type": "module", "scripts": { - "auth:generate": "bunx drizzle-kit generate --config drizzle.auth.config.ts", - "auth:info": "bunx --bun auth@latest info --config src/server/lib/auth/auth.ts --json", - "auth:migrate": "bunx drizzle-kit migrate --config drizzle.auth.config.ts", - "build": "bun run --sequential build:client build:server", - "build:client": "tsc -b tsconfig.app.json && vite build", - "build:server": "tsc -b tsconfig.node.json && vite build --config vite.server.config.ts", - "db:migrate": "bun --env-file=env/local.env run auth:migrate && bun --env-file=env/local.env x drizzle-kit migrate", - "dev": "bun run --parallel dev:api dev:web", - "dev:api": "APP_ENV=local NODE_ENV=development bun --env-file=env/local.env --watch src/index.ts", - "dev:web": "NODE_ENV=development bun --env-file=env/local.env run vite", + "auth:generate": "drizzle-kit generate --config drizzle.auth.config.ts", + "auth:info": "pnpm dlx auth@latest info --config apps/api/src/lib/auth/auth.ts --json", + "auth:migrate": "drizzle-kit migrate --config drizzle.auth.config.ts", + "build": "turbo run build", + "build:client": "turbo run build --filter=@daoyou/web", + "build:server": "turbo run build --filter=@daoyou/api", + "db:migrate": "node --env-file=env/local.env node_modules/drizzle-kit/bin.cjs migrate --config drizzle.auth.config.ts && node --env-file=env/local.env node_modules/drizzle-kit/bin.cjs migrate", + "dev": "turbo run dev --env-mode=loose", + "dev:api": "turbo run dev --filter=@daoyou/api --env-mode=loose", + "dev:web": "turbo run dev --filter=@daoyou/web --env-mode=loose", "lint": "eslint .", - "prd": "bun run --parallel prd:api prd:web", - "prd:api": "APP_ENV=staging NODE_ENV=development bun --env-file=env/staging.env --watch src/index.ts", - "prd:web": "NODE_ENV=development bun --env-file=env/staging.env run vite", + "prd": "turbo run prd --env-mode=loose", + "prd:api": "turbo run prd --filter=@daoyou/api --env-mode=loose", + "prd:web": "turbo run prd --filter=@daoyou/web --env-mode=loose", "services": "docker compose --env-file env/local.env -f scripts/docker-compose.local.yml", - "sponsors:check": "bun run scripts/render-sponsors.ts --check", + "sponsors:check": "node scripts/render-sponsors.ts --check", "sponsors:fetch": "node --env-file-if-exists=env/staging.env ./node_modules/sponsorkit/dist/cli.mjs --force", - "sponsors:render": "bun run scripts/render-sponsors.ts", - "sponsors:sync": "bun run sponsors:fetch && bun run sponsors:render && bun run sponsors:check", - "test": "vitest run" - }, - "dependencies": { - "@ai-sdk/alibaba": "^2.0.45", - "@ai-sdk/deepseek": "^3.0.44", - "@better-auth/drizzle-adapter": "^1.7.4", - "@better-auth/i18n": "^1.7.4", - "@microfleet/ioredis-lock": "^6.0.1", - "@tailwindcss/vite": "^4.3.3", - "ai": "^7.0.99", - "altcha": "^3.2.2", - "altcha-lib": "^2.5.0", - "better-auth": "^1.7.4", - "canvas-confetti": "^1.9.4", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "d3-format": "^3.1.2", - "drizzle-orm": "^0.45.2", - "hono": "4.13.7", - "ioredis": "^5.11.1", - "nats": "^2.29.3", - "nodemailer": "^9.1.1", - "pg": "^8.23.0", - "phaser": "^4.2.1", - "phaser4-rex-plugins": "^4.2.0", - "react": "19.3.0", - "react-dom": "19.3.0", - "react-router": "^8.3.1", - "react-zoom-pan-pinch": "^4.2.0", - "tailwind-merge": "^3.7.0", - "tailwindcss": "^4.3.3", - "zod": "^4.6.4" + "sponsors:render": "node scripts/render-sponsors.ts", + "sponsors:sync": "pnpm run sponsors:fetch && pnpm run sponsors:render && pnpm run sponsors:check", + "test": "vitest run", + "typecheck": "turbo run typecheck && tsc --noEmit -p tsconfig.node.json" }, "devDependencies": { "@eslint/js": "10.0.1", - "@hono/vite-build": "1.11.1", - "@types/bun": "1.4.2", - "@types/canvas-confetti": "^1.9.0", "@types/d3-format": "^3.0.4", "@types/node": "^26.5.1", - "@types/nodemailer": "^8.0.1", - "@types/pg": "^8.23.1", - "@types/react": "19.3.0", - "@types/react-dom": "19.3.0", - "@vitejs/plugin-react": "6.1.1", "drizzle-kit": "^0.31.10", "eslint": "10.10.0", "eslint-plugin-react-hooks": "7.1.1", @@ -79,9 +41,15 @@ "prettier-plugin-packagejson": "^3.0.2", "prettier-plugin-tailwindcss": "^0.8.1", "sponsorkit": "^18.0.0", + "tsx": "^4.21.0", + "turbo": "2.11.6", "typescript": "^6.0.3", "typescript-eslint": "8.70.0", - "vite": "^8.3.0", "vitest": "^4.1.11" + }, + "packageManager": "pnpm@10.34.6", + "engines": { + "node": ">=24.18.0", + "pnpm": ">=10.34.6 <11" } } diff --git a/packages/shared/package.json b/packages/shared/package.json new file mode 100644 index 00000000..1e2d61ef --- /dev/null +++ b/packages/shared/package.json @@ -0,0 +1,56 @@ +{ + "name": "@daoyou/shared", + "version": "0.1.0", + "private": true, + "type": "module", + "exports": { + "./contracts/resources": "./src/contracts/resources/index.ts", + "./drops": "./src/drops/index.ts", + "./engine/combat-v6/beasts": "./src/engine/combat-v6/beasts/index.ts", + "./engine/combat-v6/build-state": "./src/engine/combat-v6/build-state/index.ts", + "./engine/combat-v6/content": "./src/engine/combat-v6/content/index.ts", + "./engine/combat-v6/core": "./src/engine/combat-v6/core/index.ts", + "./engine/combat-v6/encounter": "./src/engine/combat-v6/encounter/index.ts", + "./engine/combat-v6/equipment": "./src/engine/combat-v6/equipment/index.ts", + "./engine/combat-v6": "./src/engine/combat-v6/index.ts", + "./engine/combat-v6/manuals": "./src/engine/combat-v6/manuals/index.ts", + "./engine/combat-v6/projection": "./src/engine/combat-v6/projection/index.ts", + "./engine/combat-v6/rules-daoyou": "./src/engine/combat-v6/rules-daoyou/index.ts", + "./engine/combat-v6/sect-progression": "./src/engine/combat-v6/sect-progression/index.ts", + "./engine/combat-v6/wild": "./src/engine/combat-v6/wild/index.ts", + "./engine/cultivation/exp-gain-strategies": "./src/engine/cultivation/exp-gain-strategies/index.ts", + "./engine/cultivator": "./src/engine/cultivator/index.ts", + "./engine/sect/content": "./src/engine/sect/content/index.ts", + "./engine/sect/content/jiujie": "./src/engine/sect/content/jiujie/index.ts", + "./engine/sect/content/lingxiao": "./src/engine/sect/content/lingxiao/index.ts", + "./engine/sect/content/tianyan": "./src/engine/sect/content/tianyan/index.ts", + "./engine/sect/content/wuxiang": "./src/engine/sect/content/wuxiang/index.ts", + "./engine/sect/content/youdu": "./src/engine/sect/content/youdu/index.ts", + "./engine/sect/core/domain": "./src/engine/sect/core/domain/index.ts", + "./engine/sect/core": "./src/engine/sect/core/index.ts", + "./engine/sect/core/mining": "./src/engine/sect/core/mining/index.ts", + "./engine/sect/core/organization": "./src/engine/sect/core/organization/index.ts", + "./engine/sect/core/plugin": "./src/engine/sect/core/plugin/index.ts", + "./engine/sect/core/presentation": "./src/engine/sect/core/presentation/index.ts", + "./engine/sect/core/runtime": "./src/engine/sect/core/runtime/index.ts", + "./engine/sect/core/sweep": "./src/engine/sect/core/sweep/index.ts", + "./engine/sect/core/validation": "./src/engine/sect/core/validation/index.ts", + "./engine/sect": "./src/engine/sect/index.ts", + "./engine/shared/tag-domain": "./src/engine/shared/tag-domain/index.ts", + "./engine/spirit-field": "./src/engine/spirit-field/index.ts", + "./inventory": "./src/inventory/index.ts", + "./lib/tower": "./src/lib/tower/index.ts", + "./text-filter": "./src/text-filter/index.ts", + "./*.ts": "./src/*.ts", + "./*": "./src/*.ts" + }, + "scripts": { + "typecheck": "tsc --noEmit -p tsconfig.json" + }, + "dependencies": { + "clsx": "^2.1.1", + "d3-format": "^3.1.2", + "tailwind-merge": "^3.7.0", + "zod": "^4.6.4" + } +} diff --git a/src/shared/artifact-migration/rules.test.ts b/packages/shared/src/artifact-migration/rules.test.ts similarity index 100% rename from src/shared/artifact-migration/rules.test.ts rename to packages/shared/src/artifact-migration/rules.test.ts diff --git a/src/shared/artifact-migration/rules.ts b/packages/shared/src/artifact-migration/rules.ts similarity index 100% rename from src/shared/artifact-migration/rules.ts rename to packages/shared/src/artifact-migration/rules.ts diff --git a/src/shared/combat-v6/arena.test.ts b/packages/shared/src/combat-v6/arena.test.ts similarity index 100% rename from src/shared/combat-v6/arena.test.ts rename to packages/shared/src/combat-v6/arena.test.ts diff --git a/src/shared/combat-v6/arena.ts b/packages/shared/src/combat-v6/arena.ts similarity index 95% rename from src/shared/combat-v6/arena.ts rename to packages/shared/src/combat-v6/arena.ts index 442efe5a..7bb8f0b0 100644 --- a/src/shared/combat-v6/arena.ts +++ b/packages/shared/src/combat-v6/arena.ts @@ -1,25 +1,25 @@ -import { publicUnitAppearances } from '@shared/combat-v6/unit-appearance'; +import { publicUnitAppearances } from '@daoyou/shared/combat-v6/unit-appearance'; import { COMBAT_V6_TRAINING_API_VERSION, type CombatV6DisplayEvent, -} from '@shared/contracts/combatV6'; +} from '@daoyou/shared/contracts/combatV6'; import { ARENA_PUBLIC_VIEW, ARENA_V6_PROTOCOL, type ArenaRuntime, type ArenaSessionView, -} from '@shared/contracts/combatV6Arena'; +} from '@daoyou/shared/contracts/combatV6Arena'; import { createBattle, restoreBattle, type BattleEvent, type BattleState, type Command, -} from '@shared/engine/combat-v6/core'; -import { canCollectCommand } from '@shared/engine/combat-v6/core/units'; -import { DAO_RAGE_RESOURCE_ID } from '@shared/engine/combat-v6/equipment/special-ids'; -import { daoyouRulesetV6 } from '@shared/engine/combat-v6/rules-daoyou'; -import { COMBAT_V6_SEAL_CURVE_ARENA_VERSIONS } from '@shared/engine/combat-v6/version'; +} from '@daoyou/shared/engine/combat-v6/core'; +import { canCollectCommand } from '@daoyou/shared/engine/combat-v6/core/units'; +import { DAO_RAGE_RESOURCE_ID } from '@daoyou/shared/engine/combat-v6/equipment/special-ids'; +import { daoyouRulesetV6 } from '@daoyou/shared/engine/combat-v6/rules-daoyou'; +import { COMBAT_V6_SEAL_CURVE_ARENA_VERSIONS } from '@daoyou/shared/engine/combat-v6/version'; import { huntParticipantSucceeded } from '../hunts/settlement'; import { controlledUnits, validatePetCommand } from './controlled-commands'; import { diffUnits } from './playback'; diff --git a/src/shared/combat-v6/auto-defaults.json b/packages/shared/src/combat-v6/auto-defaults.json similarity index 100% rename from src/shared/combat-v6/auto-defaults.json rename to packages/shared/src/combat-v6/auto-defaults.json diff --git a/src/shared/combat-v6/auto-observation.ts b/packages/shared/src/combat-v6/auto-observation.ts similarity index 100% rename from src/shared/combat-v6/auto-observation.ts rename to packages/shared/src/combat-v6/auto-observation.ts diff --git a/src/shared/combat-v6/auto-policy.ts b/packages/shared/src/combat-v6/auto-policy.ts similarity index 100% rename from src/shared/combat-v6/auto-policy.ts rename to packages/shared/src/combat-v6/auto-policy.ts diff --git a/src/shared/combat-v6/auto-status-options.ts b/packages/shared/src/combat-v6/auto-status-options.ts similarity index 100% rename from src/shared/combat-v6/auto-status-options.ts rename to packages/shared/src/combat-v6/auto-status-options.ts diff --git a/src/shared/combat-v6/auto-strategy.ts b/packages/shared/src/combat-v6/auto-strategy.ts similarity index 100% rename from src/shared/combat-v6/auto-strategy.ts rename to packages/shared/src/combat-v6/auto-strategy.ts diff --git a/src/shared/combat-v6/auto-utility.ts b/packages/shared/src/combat-v6/auto-utility.ts similarity index 100% rename from src/shared/combat-v6/auto-utility.ts rename to packages/shared/src/combat-v6/auto-utility.ts diff --git a/src/shared/combat-v6/auto.test.ts b/packages/shared/src/combat-v6/auto.test.ts similarity index 100% rename from src/shared/combat-v6/auto.test.ts rename to packages/shared/src/combat-v6/auto.test.ts diff --git a/src/shared/combat-v6/auto.ts b/packages/shared/src/combat-v6/auto.ts similarity index 100% rename from src/shared/combat-v6/auto.ts rename to packages/shared/src/combat-v6/auto.ts diff --git a/src/shared/combat-v6/battle-log.test.ts b/packages/shared/src/combat-v6/battle-log.test.ts similarity index 100% rename from src/shared/combat-v6/battle-log.test.ts rename to packages/shared/src/combat-v6/battle-log.test.ts diff --git a/src/shared/combat-v6/battle-log.ts b/packages/shared/src/combat-v6/battle-log.ts similarity index 100% rename from src/shared/combat-v6/battle-log.ts rename to packages/shared/src/combat-v6/battle-log.ts diff --git a/src/shared/combat-v6/beast-skill-presentation.test.ts b/packages/shared/src/combat-v6/beast-skill-presentation.test.ts similarity index 100% rename from src/shared/combat-v6/beast-skill-presentation.test.ts rename to packages/shared/src/combat-v6/beast-skill-presentation.test.ts diff --git a/src/shared/combat-v6/beast-skill-presentation.ts b/packages/shared/src/combat-v6/beast-skill-presentation.ts similarity index 100% rename from src/shared/combat-v6/beast-skill-presentation.ts rename to packages/shared/src/combat-v6/beast-skill-presentation.ts diff --git a/src/shared/combat-v6/controlled-commands.ts b/packages/shared/src/combat-v6/controlled-commands.ts similarity index 100% rename from src/shared/combat-v6/controlled-commands.ts rename to packages/shared/src/combat-v6/controlled-commands.ts diff --git a/src/shared/combat-v6/playback.test.ts b/packages/shared/src/combat-v6/playback.test.ts similarity index 100% rename from src/shared/combat-v6/playback.test.ts rename to packages/shared/src/combat-v6/playback.test.ts diff --git a/src/shared/combat-v6/playback.ts b/packages/shared/src/combat-v6/playback.ts similarity index 100% rename from src/shared/combat-v6/playback.ts rename to packages/shared/src/combat-v6/playback.ts diff --git a/src/shared/combat-v6/presentation-fixtures.ts b/packages/shared/src/combat-v6/presentation-fixtures.ts similarity index 100% rename from src/shared/combat-v6/presentation-fixtures.ts rename to packages/shared/src/combat-v6/presentation-fixtures.ts diff --git a/src/shared/combat-v6/presentation.ts b/packages/shared/src/combat-v6/presentation.ts similarity index 93% rename from src/shared/combat-v6/presentation.ts rename to packages/shared/src/combat-v6/presentation.ts index 111e376e..722f6651 100644 --- a/src/shared/combat-v6/presentation.ts +++ b/packages/shared/src/combat-v6/presentation.ts @@ -1,15 +1,15 @@ -import { diffUnits } from '@shared/combat-v6/playback'; +import { diffUnits } from '@daoyou/shared/combat-v6/playback'; import type { CombatV6TrainingSessionViewV1, CombatV6TrainingUnitViewV1, -} from '@shared/contracts/combatV6'; +} from '@daoyou/shared/contracts/combatV6'; import type { BattleEvent, BattleState, SkillDef, StatusDef, -} from '@shared/engine/combat-v6/core'; -import { effectiveAttrs } from '@shared/engine/combat-v6/core/units'; +} from '@daoyou/shared/engine/combat-v6/core'; +import { effectiveAttrs } from '@daoyou/shared/engine/combat-v6/core/units'; import { combatV6SkillDetails } from './skill-details'; /** Keep the generation RNG output in the authority/archive, never in live playback. */ @@ -30,7 +30,7 @@ export function combatV6Display(skills: SkillDef[], statuses: StatusDef[]) { /** Only units that have appeared, or the viewer's own reserves, may be named publicly. */ export function visibleUnitNames( state: BattleState, - events: readonly import('@shared/engine/combat-v6/core').BattleEvent[], + events: readonly import('@daoyou/shared/engine/combat-v6/core').BattleEvent[], viewerId: string, ) { const summoned = new Set( diff --git a/src/shared/combat-v6/public-build.test.ts b/packages/shared/src/combat-v6/public-build.test.ts similarity index 100% rename from src/shared/combat-v6/public-build.test.ts rename to packages/shared/src/combat-v6/public-build.test.ts diff --git a/src/shared/combat-v6/public-build.ts b/packages/shared/src/combat-v6/public-build.ts similarity index 100% rename from src/shared/combat-v6/public-build.ts rename to packages/shared/src/combat-v6/public-build.ts diff --git a/src/shared/combat-v6/ranking.test.ts b/packages/shared/src/combat-v6/ranking.test.ts similarity index 100% rename from src/shared/combat-v6/ranking.test.ts rename to packages/shared/src/combat-v6/ranking.test.ts diff --git a/src/shared/combat-v6/ranking.ts b/packages/shared/src/combat-v6/ranking.ts similarity index 100% rename from src/shared/combat-v6/ranking.ts rename to packages/shared/src/combat-v6/ranking.ts diff --git a/src/shared/combat-v6/replay-timeline.ts b/packages/shared/src/combat-v6/replay-timeline.ts similarity index 100% rename from src/shared/combat-v6/replay-timeline.ts rename to packages/shared/src/combat-v6/replay-timeline.ts diff --git a/src/shared/combat-v6/replay.test.ts b/packages/shared/src/combat-v6/replay.test.ts similarity index 100% rename from src/shared/combat-v6/replay.test.ts rename to packages/shared/src/combat-v6/replay.test.ts diff --git a/src/shared/combat-v6/replay.ts b/packages/shared/src/combat-v6/replay.ts similarity index 99% rename from src/shared/combat-v6/replay.ts rename to packages/shared/src/combat-v6/replay.ts index 7a3dd447..78c5c9d3 100644 --- a/src/shared/combat-v6/replay.ts +++ b/packages/shared/src/combat-v6/replay.ts @@ -3,7 +3,7 @@ import { COMBAT_V6_REPLAY_VERSION, parseCombatV6Replay, type CombatV6ReplayV1, -} from '@shared/contracts/combatV6Runtime'; +} from '@daoyou/shared/contracts/combatV6Runtime'; import type { CombatV6ReplayTimeline } from '../contracts/combatV6Replay'; import { arenaEvents, projectReplayUnits } from './arena'; import { applyUnitDelta, diffUnits } from './playback'; diff --git a/src/shared/combat-v6/skill-details.test.ts b/packages/shared/src/combat-v6/skill-details.test.ts similarity index 87% rename from src/shared/combat-v6/skill-details.test.ts rename to packages/shared/src/combat-v6/skill-details.test.ts index 6ad73c26..fc0f61b9 100644 --- a/src/shared/combat-v6/skill-details.test.ts +++ b/packages/shared/src/combat-v6/skill-details.test.ts @@ -1,9 +1,9 @@ -import { BEAST_SKILLS } from '@shared/engine/combat-v6/beasts'; -import { EffectType, TargetSide } from '@shared/engine/combat-v6/core/enums'; -import { DAO_EQUIPMENT_ARTS_V1 } from '@shared/engine/combat-v6/equipment/special-content'; +import { BEAST_SKILLS } from '@daoyou/shared/engine/combat-v6/beasts'; +import { EffectType, TargetSide } from '@daoyou/shared/engine/combat-v6/core/enums'; +import { DAO_EQUIPMENT_ARTS_V1 } from '@daoyou/shared/engine/combat-v6/equipment/special-content'; import { describe, expect, it } from 'vitest'; import { combatV6SkillDetails } from './skill-details'; -import { LINGXIAO_COMBAT } from '@shared/engine/combat-v6/content/lingxiao-pack'; +import { LINGXIAO_COMBAT } from '@daoyou/shared/engine/combat-v6/content/lingxiao-pack'; describe('combat skill previews', () => { it('shows combo chances from the authoritative hooks', () => { diff --git a/src/shared/combat-v6/skill-details.ts b/packages/shared/src/combat-v6/skill-details.ts similarity index 97% rename from src/shared/combat-v6/skill-details.ts rename to packages/shared/src/combat-v6/skill-details.ts index 7e67a5d0..89ef5add 100644 --- a/src/shared/combat-v6/skill-details.ts +++ b/packages/shared/src/combat-v6/skill-details.ts @@ -1,14 +1,14 @@ import { BEAST_COMBO_SKILL_IDS, BEAST_SKILL_CONTENT, -} from '@shared/engine/combat-v6/beasts/content'; -import { EffectType, HookName } from '@shared/engine/combat-v6/core/enums'; +} from '@daoyou/shared/engine/combat-v6/beasts/content'; +import { EffectType, HookName } from '@daoyou/shared/engine/combat-v6/core/enums'; import type { SkillDef, SkillEffect, StatusDef, -} from '@shared/engine/combat-v6/core/types'; -import { DAO_EQUIPMENT_ARTS_V1 } from '@shared/engine/combat-v6/equipment/special-content'; +} from '@daoyou/shared/engine/combat-v6/core/types'; +import { DAO_EQUIPMENT_ARTS_V1 } from '@daoyou/shared/engine/combat-v6/equipment/special-content'; const arts = new Map(DAO_EQUIPMENT_ARTS_V1.map((art) => [art.skill.id, art])); function beastPassiveDescription(skill: SkillDef): string | undefined { diff --git a/src/shared/combat-v6/unit-appearance.ts b/packages/shared/src/combat-v6/unit-appearance.ts similarity index 100% rename from src/shared/combat-v6/unit-appearance.ts rename to packages/shared/src/combat-v6/unit-appearance.ts diff --git a/src/shared/config/alchemyConfig.ts b/packages/shared/src/config/alchemyConfig.ts similarity index 93% rename from src/shared/config/alchemyConfig.ts rename to packages/shared/src/config/alchemyConfig.ts index b26e524b..5a7e5640 100644 --- a/src/shared/config/alchemyConfig.ts +++ b/packages/shared/src/config/alchemyConfig.ts @@ -1,4 +1,4 @@ -import type { ElementType, Quality } from '@shared/types/constants'; +import type { ElementType, Quality } from '@daoyou/shared/types/constants'; export const ALCHEMY_ALLOWED_MATERIAL_TYPES = [ 'herb', diff --git a/src/shared/config/alchemyEffectConfig.ts b/packages/shared/src/config/alchemyEffectConfig.ts similarity index 97% rename from src/shared/config/alchemyEffectConfig.ts rename to packages/shared/src/config/alchemyEffectConfig.ts index c9a0c446..5d6f7b1b 100644 --- a/src/shared/config/alchemyEffectConfig.ts +++ b/packages/shared/src/config/alchemyEffectConfig.ts @@ -1,4 +1,4 @@ -import type { Quality } from '@shared/types/constants'; +import type { Quality } from '@daoyou/shared/types/constants'; export interface AlchemyEffectQualityBase { restorePercent: number; diff --git a/src/shared/config/alchemyEssenceConfig.ts b/packages/shared/src/config/alchemyEssenceConfig.ts similarity index 88% rename from src/shared/config/alchemyEssenceConfig.ts rename to packages/shared/src/config/alchemyEssenceConfig.ts index 18af2d8b..4aa5f66e 100644 --- a/src/shared/config/alchemyEssenceConfig.ts +++ b/packages/shared/src/config/alchemyEssenceConfig.ts @@ -1,5 +1,5 @@ -import type { Quality } from '@shared/types/constants'; -import type { PillAppearanceGrade } from '@shared/types/consumable'; +import type { Quality } from '@daoyou/shared/types/constants'; +import type { PillAppearanceGrade } from '@daoyou/shared/types/consumable'; export const MATERIAL_ESSENCE_BY_QUALITY: Record = { 凡品: 8, diff --git a/src/shared/config/alchemyInput.ts b/packages/shared/src/config/alchemyInput.ts similarity index 100% rename from src/shared/config/alchemyInput.ts rename to packages/shared/src/config/alchemyInput.ts diff --git a/src/shared/config/attributeResetTalisman.ts b/packages/shared/src/config/attributeResetTalisman.ts similarity index 100% rename from src/shared/config/attributeResetTalisman.ts rename to packages/shared/src/config/attributeResetTalisman.ts diff --git a/src/shared/config/auctionConfig.test.ts b/packages/shared/src/config/auctionConfig.test.ts similarity index 96% rename from src/shared/config/auctionConfig.test.ts rename to packages/shared/src/config/auctionConfig.test.ts index bc990591..0ebca18a 100644 --- a/src/shared/config/auctionConfig.test.ts +++ b/packages/shared/src/config/auctionConfig.test.ts @@ -1,4 +1,4 @@ -import { buildSpiritFieldSeedMaterialFromPlant } from '@shared/engine/spirit-field/seedMaterial'; +import { buildSpiritFieldSeedMaterialFromPlant } from '@daoyou/shared/engine/spirit-field/seedMaterial'; import { auctionBlockReason, auctionItemPriceCap } from '../contracts/auction'; import { seedFactsOf } from '../items/definitions/seeds'; import { diff --git a/src/shared/config/auctionConfig.ts b/packages/shared/src/config/auctionConfig.ts similarity index 94% rename from src/shared/config/auctionConfig.ts rename to packages/shared/src/config/auctionConfig.ts index 41fdefd0..e0b0fb2a 100644 --- a/src/shared/config/auctionConfig.ts +++ b/packages/shared/src/config/auctionConfig.ts @@ -1,5 +1,5 @@ -import { MAX_PLAYER_ITEM_QUANTITY } from '@shared/config/itemQuantity'; -import { QUALITY_ORDER, type Quality } from '@shared/types/constants'; +import { MAX_PLAYER_ITEM_QUANTITY } from '@daoyou/shared/config/itemQuantity'; +import { QUALITY_ORDER, type Quality } from '@daoyou/shared/types/constants'; export const AUCTION_MIN_QUALITY: Quality = '玄品'; export const AUCTION_MAX_UNIT_PRICE = 9_999_999; diff --git a/src/shared/config/beastCultivation.ts b/packages/shared/src/config/beastCultivation.ts similarity index 83% rename from src/shared/config/beastCultivation.ts rename to packages/shared/src/config/beastCultivation.ts index da2a6d11..45cb62bb 100644 --- a/src/shared/config/beastCultivation.ts +++ b/packages/shared/src/config/beastCultivation.ts @@ -1,4 +1,4 @@ -import type { Quality } from '@shared/types/constants'; +import type { Quality } from '@daoyou/shared/types/constants'; /** 各品质规划区间的中值;丹药品相、契合与槽位倍率由统一炼丹公式处理。 */ export const BEAST_CULTIVATION_BASE_BY_QUALITY: Record = { diff --git a/src/shared/config/consumableSystem.ts b/packages/shared/src/config/consumableSystem.ts similarity index 95% rename from src/shared/config/consumableSystem.ts rename to packages/shared/src/config/consumableSystem.ts index 44a531f4..4c01b11e 100644 --- a/src/shared/config/consumableSystem.ts +++ b/packages/shared/src/config/consumableSystem.ts @@ -1,4 +1,4 @@ -import { QUALITY_ORDER, type Quality, type RealmType } from '@shared/types/constants'; +import { QUALITY_ORDER, type Quality, type RealmType } from '@daoyou/shared/types/constants'; export const REALM_PILL_USAGE_LIMITS: Record = { 炼气: 6, diff --git a/src/shared/config/creationProductLimits.ts b/packages/shared/src/config/creationProductLimits.ts similarity index 100% rename from src/shared/config/creationProductLimits.ts rename to packages/shared/src/config/creationProductLimits.ts diff --git a/src/shared/config/cultivationExpGain.ts b/packages/shared/src/config/cultivationExpGain.ts similarity index 98% rename from src/shared/config/cultivationExpGain.ts rename to packages/shared/src/config/cultivationExpGain.ts index 5289ab3f..072be5bf 100644 --- a/src/shared/config/cultivationExpGain.ts +++ b/packages/shared/src/config/cultivationExpGain.ts @@ -1,4 +1,4 @@ -import type { RealmStage, RealmType } from '@shared/types/constants'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; /** * 各境界的目标完成天数。 diff --git a/src/shared/config/cultivationProgress.ts b/packages/shared/src/config/cultivationProgress.ts similarity index 96% rename from src/shared/config/cultivationProgress.ts rename to packages/shared/src/config/cultivationProgress.ts index 0d6ba2a0..dda52980 100644 --- a/src/shared/config/cultivationProgress.ts +++ b/packages/shared/src/config/cultivationProgress.ts @@ -1,4 +1,4 @@ -import type { RealmStage, RealmType } from '@shared/types/constants'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; /** * 各境界 × 各阶段的单阶段修为上限表。 diff --git a/src/shared/config/cultivationTuning.ts b/packages/shared/src/config/cultivationTuning.ts similarity index 100% rename from src/shared/config/cultivationTuning.ts rename to packages/shared/src/config/cultivationTuning.ts diff --git a/src/shared/config/deployment.ts b/packages/shared/src/config/deployment.ts similarity index 100% rename from src/shared/config/deployment.ts rename to packages/shared/src/config/deployment.ts diff --git a/src/shared/config/identityReshape.test.ts b/packages/shared/src/config/identityReshape.test.ts similarity index 96% rename from src/shared/config/identityReshape.test.ts rename to packages/shared/src/config/identityReshape.test.ts index 4b0bf8cb..a3dbb679 100644 --- a/src/shared/config/identityReshape.test.ts +++ b/packages/shared/src/config/identityReshape.test.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import { IdentityReshapeCandidateSchema } from '@shared/types/identityReshape'; +import { IdentityReshapeCandidateSchema } from '@daoyou/shared/types/identityReshape'; import { IDENTITY_RESHAPE_QUESTIONS, selectIdentityReshapeQuestions, diff --git a/src/shared/config/identityReshape.ts b/packages/shared/src/config/identityReshape.ts similarity index 99% rename from src/shared/config/identityReshape.ts rename to packages/shared/src/config/identityReshape.ts index 4980ab7f..99e59898 100644 --- a/src/shared/config/identityReshape.ts +++ b/packages/shared/src/config/identityReshape.ts @@ -1,7 +1,7 @@ import type { IdentityReshapeAnswer, IdentityReshapeQuestion, -} from '@shared/types/identityReshape'; +} from '@daoyou/shared/types/identityReshape'; export const IDENTITY_RESHAPE_SCENARIO = 'identity_reshape'; export const IDENTITY_RESHAPE_TALISMAN_NAME = '改天换地符'; diff --git a/src/shared/config/innRecovery.ts b/packages/shared/src/config/innRecovery.ts similarity index 93% rename from src/shared/config/innRecovery.ts rename to packages/shared/src/config/innRecovery.ts index d321a7e5..fca65838 100644 --- a/src/shared/config/innRecovery.ts +++ b/packages/shared/src/config/innRecovery.ts @@ -1,5 +1,5 @@ -import { scaleFateAdjustedCost, scaleFateAdjustedValue } from '@shared/lib/fates'; -import { REALM_ORDER, type RealmType } from '@shared/types/constants'; +import { scaleFateAdjustedCost, scaleFateAdjustedValue } from '@daoyou/shared/lib/fates'; +import { REALM_ORDER, type RealmType } from '@daoyou/shared/types/constants'; export const INN_RECOVERY_SPIRIT_STONE_COST_MIN = 500; export const INN_RECOVERY_SPIRIT_STONE_COST_STEP = 500; diff --git a/src/shared/config/itemQuantity.ts b/packages/shared/src/config/itemQuantity.ts similarity index 100% rename from src/shared/config/itemQuantity.ts rename to packages/shared/src/config/itemQuantity.ts diff --git a/src/shared/config/llm.ts b/packages/shared/src/config/llm.ts similarity index 100% rename from src/shared/config/llm.ts rename to packages/shared/src/config/llm.ts diff --git a/src/shared/config/llmRouting.test.ts b/packages/shared/src/config/llmRouting.test.ts similarity index 100% rename from src/shared/config/llmRouting.test.ts rename to packages/shared/src/config/llmRouting.test.ts diff --git a/src/shared/config/llmRouting.ts b/packages/shared/src/config/llmRouting.ts similarity index 100% rename from src/shared/config/llmRouting.ts rename to packages/shared/src/config/llmRouting.ts diff --git a/src/shared/config/marketConfig.ts b/packages/shared/src/config/marketConfig.ts similarity index 100% rename from src/shared/config/marketConfig.ts rename to packages/shared/src/config/marketConfig.ts diff --git a/src/shared/config/pillExpGain.ts b/packages/shared/src/config/pillExpGain.ts similarity index 94% rename from src/shared/config/pillExpGain.ts rename to packages/shared/src/config/pillExpGain.ts index fd5a98a7..cc78069b 100644 --- a/src/shared/config/pillExpGain.ts +++ b/packages/shared/src/config/pillExpGain.ts @@ -1,4 +1,4 @@ -import type { Quality } from '@shared/types/constants'; +import type { Quality } from '@daoyou/shared/types/constants'; /** * 丹药修为收益配置。 diff --git a/src/shared/config/qiSystem.ts b/packages/shared/src/config/qiSystem.ts similarity index 100% rename from src/shared/config/qiSystem.ts rename to packages/shared/src/config/qiSystem.ts diff --git a/src/shared/config/realmProgression.test.ts b/packages/shared/src/config/realmProgression.test.ts similarity index 98% rename from src/shared/config/realmProgression.test.ts rename to packages/shared/src/config/realmProgression.test.ts index e05590ad..58a231c3 100644 --- a/src/shared/config/realmProgression.test.ts +++ b/packages/shared/src/config/realmProgression.test.ts @@ -1,4 +1,4 @@ -import { REALM_VALUES, REALM_STAGE_VALUES } from '@shared/types/constants'; +import { REALM_VALUES, REALM_STAGE_VALUES } from '@daoyou/shared/types/constants'; import { describe, expect, it } from 'vitest'; import { getBreakthroughAttributeGrowthReward, diff --git a/src/shared/config/realmProgression.ts b/packages/shared/src/config/realmProgression.ts similarity index 98% rename from src/shared/config/realmProgression.ts rename to packages/shared/src/config/realmProgression.ts index b6c28228..d2e066b6 100644 --- a/src/shared/config/realmProgression.ts +++ b/packages/shared/src/config/realmProgression.ts @@ -3,7 +3,7 @@ import { REALM_VALUES, type RealmStage, type RealmType, -} from '@shared/types/constants'; +} from '@daoyou/shared/types/constants'; export const BASE_ATTRIBUTE_VALUE = 10; export const ATTRIBUTE_KEY_COUNT = 6; diff --git a/src/shared/config/sectMeridianResetTalisman.ts b/packages/shared/src/config/sectMeridianResetTalisman.ts similarity index 100% rename from src/shared/config/sectMeridianResetTalisman.ts rename to packages/shared/src/config/sectMeridianResetTalisman.ts diff --git a/src/shared/config/sectTransferTalisman.ts b/packages/shared/src/config/sectTransferTalisman.ts similarity index 100% rename from src/shared/config/sectTransferTalisman.ts rename to packages/shared/src/config/sectTransferTalisman.ts diff --git a/src/shared/config/selfCreatedSkillFreeze.ts b/packages/shared/src/config/selfCreatedSkillFreeze.ts similarity index 100% rename from src/shared/config/selfCreatedSkillFreeze.ts rename to packages/shared/src/config/selfCreatedSkillFreeze.ts diff --git a/src/shared/config/skillLimits.ts b/packages/shared/src/config/skillLimits.ts similarity index 100% rename from src/shared/config/skillLimits.ts rename to packages/shared/src/config/skillLimits.ts diff --git a/src/shared/config/socialConfig.ts b/packages/shared/src/config/socialConfig.ts similarity index 100% rename from src/shared/config/socialConfig.ts rename to packages/shared/src/config/socialConfig.ts diff --git a/src/shared/config/talismanScenarios.test.ts b/packages/shared/src/config/talismanScenarios.test.ts similarity index 100% rename from src/shared/config/talismanScenarios.test.ts rename to packages/shared/src/config/talismanScenarios.test.ts diff --git a/src/shared/config/talismanScenarios.ts b/packages/shared/src/config/talismanScenarios.ts similarity index 100% rename from src/shared/config/talismanScenarios.ts rename to packages/shared/src/config/talismanScenarios.ts diff --git a/src/shared/content/guides/alchemy-first-furnace.json b/packages/shared/src/content/guides/alchemy-first-furnace.json similarity index 100% rename from src/shared/content/guides/alchemy-first-furnace.json rename to packages/shared/src/content/guides/alchemy-first-furnace.json diff --git a/src/shared/content/guides/beast-pouch.json b/packages/shared/src/content/guides/beast-pouch.json similarity index 100% rename from src/shared/content/guides/beast-pouch.json rename to packages/shared/src/content/guides/beast-pouch.json diff --git a/src/shared/content/guides/cave-layout.json b/packages/shared/src/content/guides/cave-layout.json similarity index 100% rename from src/shared/content/guides/cave-layout.json rename to packages/shared/src/content/guides/cave-layout.json diff --git a/src/shared/content/guides/forge-first-weapon.json b/packages/shared/src/content/guides/forge-first-weapon.json similarity index 100% rename from src/shared/content/guides/forge-first-weapon.json rename to packages/shared/src/content/guides/forge-first-weapon.json diff --git a/src/shared/content/guides/map-qingxi.json b/packages/shared/src/content/guides/map-qingxi.json similarity index 100% rename from src/shared/content/guides/map-qingxi.json rename to packages/shared/src/content/guides/map-qingxi.json diff --git a/src/shared/content/guides/sect-door.json b/packages/shared/src/content/guides/sect-door.json similarity index 100% rename from src/shared/content/guides/sect-door.json rename to packages/shared/src/content/guides/sect-door.json diff --git a/src/shared/content/performances/arrival-creek.json b/packages/shared/src/content/performances/arrival-creek.json similarity index 100% rename from src/shared/content/performances/arrival-creek.json rename to packages/shared/src/content/performances/arrival-creek.json diff --git a/src/shared/content/performances/arrival-ember.json b/packages/shared/src/content/performances/arrival-ember.json similarity index 100% rename from src/shared/content/performances/arrival-ember.json rename to packages/shared/src/content/performances/arrival-ember.json diff --git a/src/shared/content/performances/arrival-fall.json b/packages/shared/src/content/performances/arrival-fall.json similarity index 100% rename from src/shared/content/performances/arrival-fall.json rename to packages/shared/src/content/performances/arrival-fall.json diff --git a/src/shared/content/performances/arrival-gate.json b/packages/shared/src/content/performances/arrival-gate.json similarity index 100% rename from src/shared/content/performances/arrival-gate.json rename to packages/shared/src/content/performances/arrival-gate.json diff --git a/src/shared/content/performances/arrival-grass.json b/packages/shared/src/content/performances/arrival-grass.json similarity index 100% rename from src/shared/content/performances/arrival-grass.json rename to packages/shared/src/content/performances/arrival-grass.json diff --git a/src/shared/content/performances/arrival-grip.json b/packages/shared/src/content/performances/arrival-grip.json similarity index 100% rename from src/shared/content/performances/arrival-grip.json rename to packages/shared/src/content/performances/arrival-grip.json diff --git a/src/shared/content/performances/arrival-handy.json b/packages/shared/src/content/performances/arrival-handy.json similarity index 100% rename from src/shared/content/performances/arrival-handy.json rename to packages/shared/src/content/performances/arrival-handy.json diff --git a/src/shared/content/performances/arrival-lodge.json b/packages/shared/src/content/performances/arrival-lodge.json similarity index 100% rename from src/shared/content/performances/arrival-lodge.json rename to packages/shared/src/content/performances/arrival-lodge.json diff --git a/src/shared/content/performances/arrival-mouth.json b/packages/shared/src/content/performances/arrival-mouth.json similarity index 100% rename from src/shared/content/performances/arrival-mouth.json rename to packages/shared/src/content/performances/arrival-mouth.json diff --git a/src/shared/content/performances/arrival-pouch.json b/packages/shared/src/content/performances/arrival-pouch.json similarity index 100% rename from src/shared/content/performances/arrival-pouch.json rename to packages/shared/src/content/performances/arrival-pouch.json diff --git a/src/shared/content/performances/arrival-prints.json b/packages/shared/src/content/performances/arrival-prints.json similarity index 100% rename from src/shared/content/performances/arrival-prints.json rename to packages/shared/src/content/performances/arrival-prints.json diff --git a/src/shared/content/performances/arrival-remain.json b/packages/shared/src/content/performances/arrival-remain.json similarity index 100% rename from src/shared/content/performances/arrival-remain.json rename to packages/shared/src/content/performances/arrival-remain.json diff --git a/src/shared/content/performances/arrival-satchel.json b/packages/shared/src/content/performances/arrival-satchel.json similarity index 100% rename from src/shared/content/performances/arrival-satchel.json rename to packages/shared/src/content/performances/arrival-satchel.json diff --git a/src/shared/content/performances/arrival-scent.json b/packages/shared/src/content/performances/arrival-scent.json similarity index 100% rename from src/shared/content/performances/arrival-scent.json rename to packages/shared/src/content/performances/arrival-scent.json diff --git a/src/shared/content/performances/arrival-spring.json b/packages/shared/src/content/performances/arrival-spring.json similarity index 100% rename from src/shared/content/performances/arrival-spring.json rename to packages/shared/src/content/performances/arrival-spring.json diff --git a/src/shared/content/performances/arrival-steady.json b/packages/shared/src/content/performances/arrival-steady.json similarity index 100% rename from src/shared/content/performances/arrival-steady.json rename to packages/shared/src/content/performances/arrival-steady.json diff --git a/src/shared/content/performances/arrival-tracks.json b/packages/shared/src/content/performances/arrival-tracks.json similarity index 100% rename from src/shared/content/performances/arrival-tracks.json rename to packages/shared/src/content/performances/arrival-tracks.json diff --git a/src/shared/content/story/arrival.json b/packages/shared/src/content/story/arrival.json similarity index 100% rename from src/shared/content/story/arrival.json rename to packages/shared/src/content/story/arrival.json diff --git a/src/shared/contracts/account.ts b/packages/shared/src/contracts/account.ts similarity index 82% rename from src/shared/contracts/account.ts rename to packages/shared/src/contracts/account.ts index 102150c6..dd6cb920 100644 --- a/src/shared/contracts/account.ts +++ b/packages/shared/src/contracts/account.ts @@ -1,4 +1,4 @@ -import type { ApiSuccess } from '@shared/contracts/http'; +import type { ApiSuccess } from '@daoyou/shared/contracts/http'; import { z } from 'zod'; export const AccountSetPasswordRequestSchema = z.object({ diff --git a/src/shared/contracts/adminAccounts.ts b/packages/shared/src/contracts/adminAccounts.ts similarity index 100% rename from src/shared/contracts/adminAccounts.ts rename to packages/shared/src/contracts/adminAccounts.ts diff --git a/src/shared/contracts/adminItemGeneration.ts b/packages/shared/src/contracts/adminItemGeneration.ts similarity index 100% rename from src/shared/contracts/adminItemGeneration.ts rename to packages/shared/src/contracts/adminItemGeneration.ts diff --git a/src/shared/contracts/adminOnlineUsers.ts b/packages/shared/src/contracts/adminOnlineUsers.ts similarity index 100% rename from src/shared/contracts/adminOnlineUsers.ts rename to packages/shared/src/contracts/adminOnlineUsers.ts diff --git a/src/shared/contracts/adminRewards.test.ts b/packages/shared/src/contracts/adminRewards.test.ts similarity index 100% rename from src/shared/contracts/adminRewards.test.ts rename to packages/shared/src/contracts/adminRewards.test.ts diff --git a/src/shared/contracts/adminRewards.ts b/packages/shared/src/contracts/adminRewards.ts similarity index 100% rename from src/shared/contracts/adminRewards.ts rename to packages/shared/src/contracts/adminRewards.ts diff --git a/src/shared/contracts/adminTower.ts b/packages/shared/src/contracts/adminTower.ts similarity index 100% rename from src/shared/contracts/adminTower.ts rename to packages/shared/src/contracts/adminTower.ts diff --git a/src/shared/contracts/arena.test.ts b/packages/shared/src/contracts/arena.test.ts similarity index 100% rename from src/shared/contracts/arena.test.ts rename to packages/shared/src/contracts/arena.test.ts diff --git a/src/shared/contracts/arena.ts b/packages/shared/src/contracts/arena.ts similarity index 98% rename from src/shared/contracts/arena.ts rename to packages/shared/src/contracts/arena.ts index 15c93b38..22bec2f0 100644 --- a/src/shared/contracts/arena.ts +++ b/packages/shared/src/contracts/arena.ts @@ -1,4 +1,4 @@ -import type { RealmStage, RealmType } from '@shared/types/constants'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; import { z } from 'zod'; /** Public, non-ranked and non-consuming arena sparring room. */ diff --git a/src/shared/contracts/artifactMigration.ts b/packages/shared/src/contracts/artifactMigration.ts similarity index 100% rename from src/shared/contracts/artifactMigration.ts rename to packages/shared/src/contracts/artifactMigration.ts diff --git a/src/shared/contracts/auction.test.ts b/packages/shared/src/contracts/auction.test.ts similarity index 100% rename from src/shared/contracts/auction.test.ts rename to packages/shared/src/contracts/auction.test.ts diff --git a/src/shared/contracts/auction.ts b/packages/shared/src/contracts/auction.ts similarity index 100% rename from src/shared/contracts/auction.ts rename to packages/shared/src/contracts/auction.ts diff --git a/src/shared/contracts/backgroundCommands.test.ts b/packages/shared/src/contracts/backgroundCommands.test.ts similarity index 100% rename from src/shared/contracts/backgroundCommands.test.ts rename to packages/shared/src/contracts/backgroundCommands.test.ts diff --git a/src/shared/contracts/backgroundCommands.ts b/packages/shared/src/contracts/backgroundCommands.ts similarity index 100% rename from src/shared/contracts/backgroundCommands.ts rename to packages/shared/src/contracts/backgroundCommands.ts diff --git a/src/shared/contracts/battleReplay.ts b/packages/shared/src/contracts/battleReplay.ts similarity index 100% rename from src/shared/contracts/battleReplay.ts rename to packages/shared/src/contracts/battleReplay.ts diff --git a/src/shared/contracts/beastTrade.test.ts b/packages/shared/src/contracts/beastTrade.test.ts similarity index 100% rename from src/shared/contracts/beastTrade.test.ts rename to packages/shared/src/contracts/beastTrade.test.ts diff --git a/src/shared/contracts/beastTrade.ts b/packages/shared/src/contracts/beastTrade.ts similarity index 100% rename from src/shared/contracts/beastTrade.ts rename to packages/shared/src/contracts/beastTrade.ts diff --git a/src/shared/contracts/bodyCultivation.ts b/packages/shared/src/contracts/bodyCultivation.ts similarity index 88% rename from src/shared/contracts/bodyCultivation.ts rename to packages/shared/src/contracts/bodyCultivation.ts index 2cd653bc..76efff75 100644 --- a/src/shared/contracts/bodyCultivation.ts +++ b/packages/shared/src/contracts/bodyCultivation.ts @@ -1,8 +1,8 @@ import type { BodyCultivationRealm, CultivatorCondition, -} from '@shared/types/condition'; -import type { RealmType } from '@shared/types/constants'; +} from '@daoyou/shared/types/condition'; +import type { RealmType } from '@daoyou/shared/types/constants'; import type { ApiSuccess } from './http'; export interface BodyCultivationBreakthroughReadinessData { diff --git a/src/shared/contracts/character-generation.ts b/packages/shared/src/contracts/character-generation.ts similarity index 83% rename from src/shared/contracts/character-generation.ts rename to packages/shared/src/contracts/character-generation.ts index 28ac5137..add2dcfc 100644 --- a/src/shared/contracts/character-generation.ts +++ b/packages/shared/src/contracts/character-generation.ts @@ -1,5 +1,5 @@ -import type { ApiSuccess } from '@shared/contracts/http'; -import type { Cultivator } from '@shared/types/cultivator'; +import type { ApiSuccess } from '@daoyou/shared/contracts/http'; +import type { Cultivator } from '@daoyou/shared/types/cultivator'; export const CHARACTER_GENERATION_DAILY_LIMIT = 6; export const CHARACTER_GENERATION_LIMIT_REACHED_CODE = diff --git a/src/shared/contracts/characterAttributes.ts b/packages/shared/src/contracts/characterAttributes.ts similarity index 88% rename from src/shared/contracts/characterAttributes.ts rename to packages/shared/src/contracts/characterAttributes.ts index 24301b10..07c4971e 100644 --- a/src/shared/contracts/characterAttributes.ts +++ b/packages/shared/src/contracts/characterAttributes.ts @@ -1,4 +1,4 @@ -import type { CharacterPanelV1 } from '@shared/engine/combat-v6/projection'; +import type { CharacterPanelV1 } from '@daoyou/shared/engine/combat-v6/projection'; import { z } from 'zod'; export const AttributeAllocationSchema = z diff --git a/src/shared/contracts/combatActivity.ts b/packages/shared/src/contracts/combatActivity.ts similarity index 100% rename from src/shared/contracts/combatActivity.ts rename to packages/shared/src/contracts/combatActivity.ts diff --git a/src/shared/contracts/combatV6.test.ts b/packages/shared/src/contracts/combatV6.test.ts similarity index 100% rename from src/shared/contracts/combatV6.test.ts rename to packages/shared/src/contracts/combatV6.test.ts diff --git a/src/shared/contracts/combatV6.ts b/packages/shared/src/contracts/combatV6.ts similarity index 97% rename from src/shared/contracts/combatV6.ts rename to packages/shared/src/contracts/combatV6.ts index 782f5696..209be96d 100644 --- a/src/shared/contracts/combatV6.ts +++ b/packages/shared/src/contracts/combatV6.ts @@ -1,14 +1,14 @@ -import type { CombatV6SectId } from '@shared/engine/combat-v6/content'; +import type { CombatV6SectId } from '@daoyou/shared/engine/combat-v6/content'; import type { BattleEvent, CombatV6CommandOptions, CombatV6VersionStamp, Command, -} from '@shared/engine/combat-v6/core'; +} from '@daoyou/shared/engine/combat-v6/core'; import type { CombatV6TrainingTierV1, TrainingEncounterOutcome, -} from '@shared/engine/combat-v6/encounter'; +} from '@daoyou/shared/engine/combat-v6/encounter'; import { z } from 'zod'; export const COMBAT_V6_BUILD_SCHEMA_VERSION = 1 as const; diff --git a/src/shared/contracts/combatV6Arena.ts b/packages/shared/src/contracts/combatV6Arena.ts similarity index 95% rename from src/shared/contracts/combatV6Arena.ts rename to packages/shared/src/contracts/combatV6Arena.ts index c307466b..81301550 100644 --- a/src/shared/contracts/combatV6Arena.ts +++ b/packages/shared/src/contracts/combatV6Arena.ts @@ -1,4 +1,4 @@ -import type { AutoStrategy } from '@shared/combat-v6/auto-strategy'; +import type { AutoStrategy } from '@daoyou/shared/combat-v6/auto-strategy'; import type { BattleEvent, BattleState, @@ -6,7 +6,7 @@ import type { LineupUnit, SkillDef, StatusDef, -} from '@shared/engine/combat-v6/core'; +} from '@daoyou/shared/engine/combat-v6/core'; import { z } from 'zod'; import type { HuntEvent } from '../hunts/config'; import type { HuntRewardSnapshot } from '../rewards/hunt'; diff --git a/src/shared/contracts/combatV6Beasts.test.ts b/packages/shared/src/contracts/combatV6Beasts.test.ts similarity index 100% rename from src/shared/contracts/combatV6Beasts.test.ts rename to packages/shared/src/contracts/combatV6Beasts.test.ts diff --git a/src/shared/contracts/combatV6Beasts.ts b/packages/shared/src/contracts/combatV6Beasts.ts similarity index 100% rename from src/shared/contracts/combatV6Beasts.ts rename to packages/shared/src/contracts/combatV6Beasts.ts diff --git a/src/shared/contracts/combatV6Breakthrough.ts b/packages/shared/src/contracts/combatV6Breakthrough.ts similarity index 100% rename from src/shared/contracts/combatV6Breakthrough.ts rename to packages/shared/src/contracts/combatV6Breakthrough.ts diff --git a/src/shared/contracts/combatV6Dungeon.ts b/packages/shared/src/contracts/combatV6Dungeon.ts similarity index 100% rename from src/shared/contracts/combatV6Dungeon.ts rename to packages/shared/src/contracts/combatV6Dungeon.ts diff --git a/src/shared/contracts/combatV6Manuals.ts b/packages/shared/src/contracts/combatV6Manuals.ts similarity index 100% rename from src/shared/contracts/combatV6Manuals.ts rename to packages/shared/src/contracts/combatV6Manuals.ts diff --git a/src/shared/contracts/combatV6Ranking.ts b/packages/shared/src/contracts/combatV6Ranking.ts similarity index 100% rename from src/shared/contracts/combatV6Ranking.ts rename to packages/shared/src/contracts/combatV6Ranking.ts diff --git a/src/shared/contracts/combatV6Replay.ts b/packages/shared/src/contracts/combatV6Replay.ts similarity index 100% rename from src/shared/contracts/combatV6Replay.ts rename to packages/shared/src/contracts/combatV6Replay.ts diff --git a/src/shared/contracts/combatV6Runtime.ts b/packages/shared/src/contracts/combatV6Runtime.ts similarity index 98% rename from src/shared/contracts/combatV6Runtime.ts rename to packages/shared/src/contracts/combatV6Runtime.ts index c4b4d62a..adb10a8b 100644 --- a/src/shared/contracts/combatV6Runtime.ts +++ b/packages/shared/src/contracts/combatV6Runtime.ts @@ -6,8 +6,8 @@ import type { LineupUnit, SkillDef, StatusDef, -} from '@shared/engine/combat-v6/core'; -import type { CombatV6TrainingRuntimeSnapshotV1 } from '@shared/engine/combat-v6/encounter'; +} from '@daoyou/shared/engine/combat-v6/core'; +import type { CombatV6TrainingRuntimeSnapshotV1 } from '@daoyou/shared/engine/combat-v6/encounter'; import { z } from 'zod'; import { CombatV6ReplayTimelineSchema, diff --git a/src/shared/contracts/combatV6Sect.ts b/packages/shared/src/contracts/combatV6Sect.ts similarity index 100% rename from src/shared/contracts/combatV6Sect.ts rename to packages/shared/src/contracts/combatV6Sect.ts diff --git a/src/shared/contracts/combatV6SectTask.ts b/packages/shared/src/contracts/combatV6SectTask.ts similarity index 100% rename from src/shared/contracts/combatV6SectTask.ts rename to packages/shared/src/contracts/combatV6SectTask.ts diff --git a/src/shared/contracts/combatV6Tower.ts b/packages/shared/src/contracts/combatV6Tower.ts similarity index 100% rename from src/shared/contracts/combatV6Tower.ts rename to packages/shared/src/contracts/combatV6Tower.ts diff --git a/src/shared/contracts/combatV6Wild.ts b/packages/shared/src/contracts/combatV6Wild.ts similarity index 96% rename from src/shared/contracts/combatV6Wild.ts rename to packages/shared/src/contracts/combatV6Wild.ts index 43ef335c..0e6280a7 100644 --- a/src/shared/contracts/combatV6Wild.ts +++ b/packages/shared/src/contracts/combatV6Wild.ts @@ -1,6 +1,6 @@ -import type { WildRuntimeSnapshot } from '@shared/engine/combat-v6/wild/host'; -import { AutoStrategySchema } from '@shared/combat-v6/auto-strategy'; -import type { WildResources } from '@shared/engine/combat-v6/wild/rules'; +import type { WildRuntimeSnapshot } from '@daoyou/shared/engine/combat-v6/wild/host'; +import { AutoStrategySchema } from '@daoyou/shared/combat-v6/auto-strategy'; +import type { WildResources } from '@daoyou/shared/engine/combat-v6/wild/rules'; import { z } from 'zod'; import { DropPoolSchema, type DropPool } from '../drops'; import { BeastSchema, type SummonedBeast } from '../engine/combat-v6/beasts'; diff --git a/src/shared/contracts/devTools.ts b/packages/shared/src/contracts/devTools.ts similarity index 100% rename from src/shared/contracts/devTools.ts rename to packages/shared/src/contracts/devTools.ts diff --git a/src/shared/contracts/divination.ts b/packages/shared/src/contracts/divination.ts similarity index 96% rename from src/shared/contracts/divination.ts rename to packages/shared/src/contracts/divination.ts index 95867f23..576d3a79 100644 --- a/src/shared/contracts/divination.ts +++ b/packages/shared/src/contracts/divination.ts @@ -3,7 +3,7 @@ import { type DivinationDice, type DivinationDirection, type DivinationOmen, -} from '@shared/lib/divination'; +} from '@daoyou/shared/lib/divination'; import { z } from 'zod'; import type { PlayerResourceMutationMeta } from './player'; diff --git a/src/shared/contracts/domainEvents.test.ts b/packages/shared/src/contracts/domainEvents.test.ts similarity index 100% rename from src/shared/contracts/domainEvents.test.ts rename to packages/shared/src/contracts/domainEvents.test.ts diff --git a/src/shared/contracts/domainEvents.ts b/packages/shared/src/contracts/domainEvents.ts similarity index 98% rename from src/shared/contracts/domainEvents.ts rename to packages/shared/src/contracts/domainEvents.ts index b4275a66..9d7a77b7 100644 --- a/src/shared/contracts/domainEvents.ts +++ b/packages/shared/src/contracts/domainEvents.ts @@ -1,10 +1,10 @@ -import { SPIRIT_FIELD_CARE_ACTIONS } from '@shared/engine/spirit-field/types'; +import { SPIRIT_FIELD_CARE_ACTIONS } from '@daoyou/shared/engine/spirit-field/types'; import { QUALITY_VALUES, REALM_STAGE_VALUES, REALM_VALUES, -} from '@shared/types/constants'; -import { ALCHEMY_MODE_VALUES } from '@shared/types/consumable'; +} from '@daoyou/shared/types/constants'; +import { ALCHEMY_MODE_VALUES } from '@daoyou/shared/types/consumable'; import { z } from 'zod'; import { ItemGrantSchema } from '../inventory'; import { InventoryEquipmentSchema } from '../inventory/equipment'; diff --git a/src/shared/contracts/enlightenment.ts b/packages/shared/src/contracts/enlightenment.ts similarity index 100% rename from src/shared/contracts/enlightenment.ts rename to packages/shared/src/contracts/enlightenment.ts diff --git a/src/shared/contracts/feedback.ts b/packages/shared/src/contracts/feedback.ts similarity index 100% rename from src/shared/contracts/feedback.ts rename to packages/shared/src/contracts/feedback.ts diff --git a/src/shared/contracts/forging.ts b/packages/shared/src/contracts/forging.ts similarity index 100% rename from src/shared/contracts/forging.ts rename to packages/shared/src/contracts/forging.ts diff --git a/src/shared/contracts/friends.ts b/packages/shared/src/contracts/friends.ts similarity index 100% rename from src/shared/contracts/friends.ts rename to packages/shared/src/contracts/friends.ts diff --git a/src/shared/contracts/http.ts b/packages/shared/src/contracts/http.ts similarity index 100% rename from src/shared/contracts/http.ts rename to packages/shared/src/contracts/http.ts diff --git a/src/shared/contracts/hunts.ts b/packages/shared/src/contracts/hunts.ts similarity index 100% rename from src/shared/contracts/hunts.ts rename to packages/shared/src/contracts/hunts.ts diff --git a/src/shared/contracts/inscriptions.ts b/packages/shared/src/contracts/inscriptions.ts similarity index 100% rename from src/shared/contracts/inscriptions.ts rename to packages/shared/src/contracts/inscriptions.ts diff --git a/src/shared/contracts/inventory.ts b/packages/shared/src/contracts/inventory.ts similarity index 100% rename from src/shared/contracts/inventory.ts rename to packages/shared/src/contracts/inventory.ts diff --git a/src/shared/contracts/itemExchangeShop.ts b/packages/shared/src/contracts/itemExchangeShop.ts similarity index 100% rename from src/shared/contracts/itemExchangeShop.ts rename to packages/shared/src/contracts/itemExchangeShop.ts diff --git a/src/shared/contracts/mail.test.ts b/packages/shared/src/contracts/mail.test.ts similarity index 100% rename from src/shared/contracts/mail.test.ts rename to packages/shared/src/contracts/mail.test.ts diff --git a/src/shared/contracts/mail.ts b/packages/shared/src/contracts/mail.ts similarity index 100% rename from src/shared/contracts/mail.ts rename to packages/shared/src/contracts/mail.ts diff --git a/src/shared/contracts/manualMigration.ts b/packages/shared/src/contracts/manualMigration.ts similarity index 100% rename from src/shared/contracts/manualMigration.ts rename to packages/shared/src/contracts/manualMigration.ts diff --git a/src/shared/contracts/market.test.ts b/packages/shared/src/contracts/market.test.ts similarity index 100% rename from src/shared/contracts/market.test.ts rename to packages/shared/src/contracts/market.test.ts diff --git a/src/shared/contracts/market.ts b/packages/shared/src/contracts/market.ts similarity index 100% rename from src/shared/contracts/market.ts rename to packages/shared/src/contracts/market.ts diff --git a/src/shared/contracts/player.ts b/packages/shared/src/contracts/player.ts similarity index 83% rename from src/shared/contracts/player.ts rename to packages/shared/src/contracts/player.ts index 4b7ac020..db805da8 100644 --- a/src/shared/contracts/player.ts +++ b/packages/shared/src/contracts/player.ts @@ -1,6 +1,6 @@ -import type { ApiSuccess } from '@shared/contracts/http'; -import type { CultivationProgress, Cultivator } from '@shared/types/cultivator'; -import type { QiProjectionBaseline } from '@shared/types/qi'; +import type { ApiSuccess } from '@daoyou/shared/contracts/http'; +import type { CultivationProgress, Cultivator } from '@daoyou/shared/types/cultivator'; +import type { QiProjectionBaseline } from '@daoyou/shared/types/qi'; import type { SectCombatView } from './combatV6'; import type { ResourceChange, @@ -23,8 +23,8 @@ export type CultivatorInspectionData = Pick< | 'pre_heaven_fates' | 'condition' > & { - combatPanel: import('@shared/engine/combat-v6/projection').CharacterPanelV1 | null; - build: import('@shared/combat-v6/public-build').PublicCombatV6Build | null; + combatPanel: import('@daoyou/shared/engine/combat-v6/projection').CharacterPanelV1 | null; + build: import('@daoyou/shared/combat-v6/public-build').PublicCombatV6Build | null; }; export type PlayerIdentityCultivator = Omit< @@ -72,7 +72,7 @@ export interface PlayerResourceMap { profile: { cultivator: PlayerIdentityCultivator; }; - condition: (NonNullable & { combatV6?: import('@shared/lib/cultivatorDisplay').CombatV6ResourceAuthority }) | undefined; + condition: (NonNullable & { combatV6?: import('@daoyou/shared/lib/cultivatorDisplay').CombatV6ResourceAuthority }) | undefined; progress: CultivationProgress; currency: QiProjectionBaseline & { spiritStones: number; diff --git a/src/shared/contracts/playerJournal.ts b/packages/shared/src/contracts/playerJournal.ts similarity index 97% rename from src/shared/contracts/playerJournal.ts rename to packages/shared/src/contracts/playerJournal.ts index 523d39ff..6110eea1 100644 --- a/src/shared/contracts/playerJournal.ts +++ b/packages/shared/src/contracts/playerJournal.ts @@ -1,8 +1,8 @@ import type { BreakthroughResult, CultivationResult, -} from '@shared/engine/cultivation/CultivationEngine'; -import { REALM_STAGE_VALUES, REALM_VALUES } from '@shared/types/constants'; +} from '@daoyou/shared/engine/cultivation/CultivationEngine'; +import { REALM_STAGE_VALUES, REALM_VALUES } from '@daoyou/shared/types/constants'; import { z } from 'zod'; import type { RetreatResultData } from './retreat'; diff --git a/src/shared/contracts/qi.ts b/packages/shared/src/contracts/qi.ts similarity index 83% rename from src/shared/contracts/qi.ts rename to packages/shared/src/contracts/qi.ts index e190421b..2b8d2c21 100644 --- a/src/shared/contracts/qi.ts +++ b/packages/shared/src/contracts/qi.ts @@ -1,5 +1,5 @@ -import type { QiAction } from '@shared/config/qiSystem'; -import type { QiLogStatus } from '@shared/types/qi'; +import type { QiAction } from '@daoyou/shared/config/qiSystem'; +import type { QiLogStatus } from '@daoyou/shared/types/qi'; export interface QiState { current: number; diff --git a/src/shared/contracts/realtime.ts b/packages/shared/src/contracts/realtime.ts similarity index 78% rename from src/shared/contracts/realtime.ts rename to packages/shared/src/contracts/realtime.ts index 71c67476..be164895 100644 --- a/src/shared/contracts/realtime.ts +++ b/packages/shared/src/contracts/realtime.ts @@ -1,7 +1,7 @@ -import type { ResourceChange } from '@shared/contracts/resources'; -import type { ResourceScope } from '@shared/contracts/resources'; -import type { ArenaRoomStatusV1, ArenaRoomV1 } from '@shared/contracts/arena'; -import type { WorldChatMessageDTO } from '@shared/types/world-chat'; +import type { ResourceChange } from '@daoyou/shared/contracts/resources'; +import type { ResourceScope } from '@daoyou/shared/contracts/resources'; +import type { ArenaRoomStatusV1, ArenaRoomV1 } from '@daoyou/shared/contracts/arena'; +import type { WorldChatMessageDTO } from '@daoyou/shared/types/world-chat'; export const REALTIME_CHANNELS = [ 'world-chat', diff --git a/src/shared/contracts/recycle.ts b/packages/shared/src/contracts/recycle.ts similarity index 100% rename from src/shared/contracts/recycle.ts rename to packages/shared/src/contracts/recycle.ts diff --git a/src/shared/contracts/reputationShop.ts b/packages/shared/src/contracts/reputationShop.ts similarity index 96% rename from src/shared/contracts/reputationShop.ts rename to packages/shared/src/contracts/reputationShop.ts index 3856930a..bd09b31d 100644 --- a/src/shared/contracts/reputationShop.ts +++ b/packages/shared/src/contracts/reputationShop.ts @@ -4,7 +4,7 @@ import { ItemExchangeShopItemMutationSchema, ItemExchangeShopItemStatusSchema, type ItemExchangeShopItemView, -} from '@shared/contracts/itemExchangeShop'; +} from '@daoyou/shared/contracts/itemExchangeShop'; import { z } from 'zod'; export const ReputationShopItemStatusSchema = ItemExchangeShopItemStatusSchema; diff --git a/src/shared/contracts/resources.test.ts b/packages/shared/src/contracts/resources.test.ts similarity index 100% rename from src/shared/contracts/resources.test.ts rename to packages/shared/src/contracts/resources.test.ts diff --git a/src/shared/contracts/resources/bag.test.ts b/packages/shared/src/contracts/resources/bag.test.ts similarity index 100% rename from src/shared/contracts/resources/bag.test.ts rename to packages/shared/src/contracts/resources/bag.test.ts diff --git a/src/shared/contracts/resources/bag.ts b/packages/shared/src/contracts/resources/bag.ts similarity index 100% rename from src/shared/contracts/resources/bag.ts rename to packages/shared/src/contracts/resources/bag.ts diff --git a/src/shared/contracts/resources/core.ts b/packages/shared/src/contracts/resources/core.ts similarity index 100% rename from src/shared/contracts/resources/core.ts rename to packages/shared/src/contracts/resources/core.ts diff --git a/src/shared/contracts/resources/index.ts b/packages/shared/src/contracts/resources/index.ts similarity index 100% rename from src/shared/contracts/resources/index.ts rename to packages/shared/src/contracts/resources/index.ts diff --git a/src/shared/contracts/resources/inventory.ts b/packages/shared/src/contracts/resources/inventory.ts similarity index 98% rename from src/shared/contracts/resources/inventory.ts rename to packages/shared/src/contracts/resources/inventory.ts index b833173d..08e5a9cd 100644 --- a/src/shared/contracts/resources/inventory.ts +++ b/packages/shared/src/contracts/resources/inventory.ts @@ -6,7 +6,7 @@ import { QUALITY_VALUES, REALM_STAGE_VALUES, REALM_VALUES, -} from '@shared/types/constants'; +} from '@daoyou/shared/types/constants'; import { ALCHEMY_COMPOUND_TIER_VALUES, ALCHEMY_PROPERTY_KEY_VALUES, @@ -15,8 +15,8 @@ import { PILL_FAMILY_VALUES, PILL_QUOTA_CATEGORY_VALUES, TALISMAN_SESSION_MODE_VALUES, -} from '@shared/types/consumable'; -import type { Artifact, Consumable, Material } from '@shared/types/cultivator'; +} from '@daoyou/shared/types/consumable'; +import type { Artifact, Consumable, Material } from '@daoyou/shared/types/cultivator'; import { z } from 'zod'; import { applyResourceChange } from './core'; import type { ResourceChange, ResourceDataMap } from './registry'; diff --git a/src/shared/contracts/resources/player.ts b/packages/shared/src/contracts/resources/player.ts similarity index 98% rename from src/shared/contracts/resources/player.ts rename to packages/shared/src/contracts/resources/player.ts index 93730d27..5fbac438 100644 --- a/src/shared/contracts/resources/player.ts +++ b/packages/shared/src/contracts/resources/player.ts @@ -6,9 +6,9 @@ import { REALM_STAGE_VALUES, REALM_VALUES, SPIRITUAL_ROOT_GRADE_VALUES, -} from '@shared/types/constants'; -import { StoryViewSchema, type StoryView } from '@shared/story/schema'; -import type { TaskInstance } from '@shared/types/task'; +} from '@daoyou/shared/types/constants'; +import { StoryViewSchema, type StoryView } from '@daoyou/shared/story/schema'; +import type { TaskInstance } from '@daoyou/shared/types/task'; import { z } from 'zod'; import { SectCombatReadinessSchema } from '../combatV6'; import { BreakthroughBattlePointerSchema } from '../combatV6Breakthrough'; diff --git a/src/shared/contracts/resources/registry.ts b/packages/shared/src/contracts/resources/registry.ts similarity index 100% rename from src/shared/contracts/resources/registry.ts rename to packages/shared/src/contracts/resources/registry.ts diff --git a/src/shared/contracts/resources/sect.ts b/packages/shared/src/contracts/resources/sect.ts similarity index 95% rename from src/shared/contracts/resources/sect.ts rename to packages/shared/src/contracts/resources/sect.ts index 0a12a729..c47a8e72 100644 --- a/src/shared/contracts/resources/sect.ts +++ b/packages/shared/src/contracts/resources/sect.ts @@ -1,7 +1,7 @@ -import { SectDeliveryRequirementSchema } from '@shared/engine/sect/core/organization/taskRequirements'; -import { SectTaskRewardSnapshotSchema } from '@shared/engine/sect/core/organization/taskRewards'; -import { ItemGrantSchema } from '@shared/inventory'; -import { REALM_STAGE_VALUES, REALM_VALUES } from '@shared/types/constants'; +import { SectDeliveryRequirementSchema } from '@daoyou/shared/engine/sect/core/organization/taskRequirements'; +import { SectTaskRewardSnapshotSchema } from '@daoyou/shared/engine/sect/core/organization/taskRewards'; +import { ItemGrantSchema } from '@daoyou/shared/inventory'; +import { REALM_STAGE_VALUES, REALM_VALUES } from '@daoyou/shared/types/constants'; import { z } from 'zod'; import type { SectConstructionMemberData, diff --git a/src/shared/contracts/retreat.ts b/packages/shared/src/contracts/retreat.ts similarity index 94% rename from src/shared/contracts/retreat.ts rename to packages/shared/src/contracts/retreat.ts index 340dd2eb..9f72522b 100644 --- a/src/shared/contracts/retreat.ts +++ b/packages/shared/src/contracts/retreat.ts @@ -1,7 +1,7 @@ import type { BreakthroughResult, CultivationResult, -} from '@shared/engine/cultivation/CultivationEngine'; +} from '@daoyou/shared/engine/cultivation/CultivationEngine'; import { z } from 'zod'; import type { PlayerResourceMutationMeta } from './player'; diff --git a/src/shared/contracts/sect.test.ts b/packages/shared/src/contracts/sect.test.ts similarity index 100% rename from src/shared/contracts/sect.test.ts rename to packages/shared/src/contracts/sect.test.ts diff --git a/src/shared/contracts/sect.ts b/packages/shared/src/contracts/sect.ts similarity index 97% rename from src/shared/contracts/sect.ts rename to packages/shared/src/contracts/sect.ts index 2e33fcce..d2ce4e62 100644 --- a/src/shared/contracts/sect.ts +++ b/packages/shared/src/contracts/sect.ts @@ -1,4 +1,4 @@ -import { MAX_PLAYER_ITEM_QUANTITY } from '@shared/config/itemQuantity'; +import { MAX_PLAYER_ITEM_QUANTITY } from '@daoyou/shared/config/itemQuantity'; import type { CultivatorSectState, SectBattleTargetSummary, @@ -11,8 +11,8 @@ import type { SectSubmissionItemFacts, SectTaskDialoguePresentation, SectTaskRewardSnapshot, -} from '@shared/engine/sect'; -import type { CultivationProgress } from '@shared/types/cultivator'; +} from '@daoyou/shared/engine/sect'; +import type { CultivationProgress } from '@daoyou/shared/types/cultivator'; import { z } from 'zod'; import type { PlayerStateMutationResponse } from './player'; diff --git a/src/shared/contracts/sectShop.ts b/packages/shared/src/contracts/sectShop.ts similarity index 95% rename from src/shared/contracts/sectShop.ts rename to packages/shared/src/contracts/sectShop.ts index d43940f1..2609013f 100644 --- a/src/shared/contracts/sectShop.ts +++ b/packages/shared/src/contracts/sectShop.ts @@ -6,7 +6,7 @@ import { type ItemExchangeShopItemMutation, type ItemExchangeShopItemStatus, type ItemExchangeShopItemView, -} from '@shared/contracts/itemExchangeShop'; +} from '@daoyou/shared/contracts/itemExchangeShop'; import { z } from 'zod'; export const SectShopItemStatusSchema = ItemExchangeShopItemStatusSchema; diff --git a/src/shared/contracts/spiritField.ts b/packages/shared/src/contracts/spiritField.ts similarity index 92% rename from src/shared/contracts/spiritField.ts rename to packages/shared/src/contracts/spiritField.ts index bfa7a9e2..45eb8fde 100644 --- a/src/shared/contracts/spiritField.ts +++ b/packages/shared/src/contracts/spiritField.ts @@ -1,4 +1,4 @@ -import { SPIRIT_FIELD_CULTIVATION_METHODS } from '@shared/engine/spirit-field'; +import { SPIRIT_FIELD_CULTIVATION_METHODS } from '@daoyou/shared/engine/spirit-field'; import { z } from 'zod'; export const SpiritFieldPlotIndexSchema = z.number().int().min(0).max(5); diff --git a/src/shared/contracts/sponsorship.ts b/packages/shared/src/contracts/sponsorship.ts similarity index 92% rename from src/shared/contracts/sponsorship.ts rename to packages/shared/src/contracts/sponsorship.ts index 29527832..c7f24790 100644 --- a/src/shared/contracts/sponsorship.ts +++ b/packages/shared/src/contracts/sponsorship.ts @@ -1,4 +1,4 @@ -import { SPONSORSHIP_TIER_IDS } from '@shared/lib/sponsorship'; +import { SPONSORSHIP_TIER_IDS } from '@daoyou/shared/lib/sponsorship'; import { z } from 'zod'; export const SponsorshipCheckoutRequestSchema = z diff --git a/src/shared/contracts/systemMail.test.ts b/packages/shared/src/contracts/systemMail.test.ts similarity index 100% rename from src/shared/contracts/systemMail.test.ts rename to packages/shared/src/contracts/systemMail.test.ts diff --git a/src/shared/contracts/systemMail.ts b/packages/shared/src/contracts/systemMail.ts similarity index 100% rename from src/shared/contracts/systemMail.ts rename to packages/shared/src/contracts/systemMail.ts diff --git a/src/shared/contracts/task.ts b/packages/shared/src/contracts/task.ts similarity index 89% rename from src/shared/contracts/task.ts rename to packages/shared/src/contracts/task.ts index a13cedea..cd70d1b7 100644 --- a/src/shared/contracts/task.ts +++ b/packages/shared/src/contracts/task.ts @@ -1,7 +1,7 @@ import type { ApiSuccess } from './http'; import type { PlayerStateMutationResponse } from './player'; import type { ResourceReadResponse } from './resources'; -import type { TaskInstance } from '@shared/types/task'; +import type { TaskInstance } from '@daoyou/shared/types/task'; export type TaskListResponse = ResourceReadResponse<'player.tasks'>; diff --git a/src/shared/contracts/world-chat.test.ts b/packages/shared/src/contracts/world-chat.test.ts similarity index 100% rename from src/shared/contracts/world-chat.test.ts rename to packages/shared/src/contracts/world-chat.test.ts diff --git a/src/shared/contracts/world-chat.ts b/packages/shared/src/contracts/world-chat.ts similarity index 100% rename from src/shared/contracts/world-chat.ts rename to packages/shared/src/contracts/world-chat.ts diff --git a/src/shared/data/map.json b/packages/shared/src/data/map.json similarity index 100% rename from src/shared/data/map.json rename to packages/shared/src/data/map.json diff --git a/src/shared/drops/README.md b/packages/shared/src/drops/README.md similarity index 90% rename from src/shared/drops/README.md rename to packages/shared/src/drops/README.md index 609dd742..0dca812c 100644 --- a/src/shared/drops/README.md +++ b/packages/shared/src/drops/README.md @@ -36,4 +36,4 @@ const result = rollDrops(pool, (groupId) => randomStreamFor(groupId)); 业务方决定触发条件、池选择和调用次数,冻结本次配置与具体奖励结果;发放方负责解析奖励、生成个体、幂等与事务。不要在底座中添加胜负、人物等级、背包容量或发奖逻辑。 -首个接入示例为 `src/shared/rewards/wild.ts`,属于业务适配层。它不是底座依赖,也不是其他业务接入时必须继承的基类。 +首个接入示例为 `packages/shared/src/rewards/wild.ts`,属于业务适配层。它不是底座依赖,也不是其他业务接入时必须继承的基类。 diff --git a/src/shared/drops/drops.test.ts b/packages/shared/src/drops/drops.test.ts similarity index 100% rename from src/shared/drops/drops.test.ts rename to packages/shared/src/drops/drops.test.ts diff --git a/src/shared/drops/index.ts b/packages/shared/src/drops/index.ts similarity index 100% rename from src/shared/drops/index.ts rename to packages/shared/src/drops/index.ts diff --git a/src/shared/engine/combat-v6/CHARACTER_COMPATIBILITY.md b/packages/shared/src/engine/combat-v6/CHARACTER_COMPATIBILITY.md similarity index 100% rename from src/shared/engine/combat-v6/CHARACTER_COMPATIBILITY.md rename to packages/shared/src/engine/combat-v6/CHARACTER_COMPATIBILITY.md diff --git a/src/shared/engine/combat-v6/README.md b/packages/shared/src/engine/combat-v6/README.md similarity index 81% rename from src/shared/engine/combat-v6/README.md rename to packages/shared/src/engine/combat-v6/README.md index 82419295..352298f9 100644 --- a/src/shared/engine/combat-v6/README.md +++ b/packages/shared/src/engine/combat-v6/README.md @@ -1,32 +1,32 @@ # combat-v6 数值基线后续治理以 -[`手游数值基线治理计划`](../../../../docs/combat-v6-mobile-numerical-governance-plan.md) +[`手游数值基线治理计划`](../../../../../docs/combat-v6-mobile-numerical-governance-plan.md) 为准:主要参照《梦幻西游》手游,按证据与版本、伤害结算、人物及召唤灵五维、装备范围、技能微调的顺序推进。当前实现仍是旧数值;该计划不代表已完成手游公式对齐。 完整的新体系设计、隔离边界和分阶段迁移路线以 -[`docs/combat-v6-mhxy-redesign-roadmap.md`](../../../../docs/combat-v6-mhxy-redesign-roadmap.md) +[`docs/combat-v6-mhxy-redesign-roadmap.md`](../../../../../docs/combat-v6-mhxy-redesign-roadmap.md) 为 canonical 文档。角色字段的逐项兼容分析见 [`CHARACTER_COMPATIBILITY.md`](./CHARACTER_COMPATIBILITY.md),新版道装领域见 -[`docs/combat-v6-equipment-system-design.md`](../../../../docs/combat-v6-equipment-system-design.md), +[`docs/combat-v6-equipment-system-design.md`](../../../../../docs/combat-v6-equipment-system-design.md), 新版功法领域见 -[`docs/combat-v6-manual-system-design.md`](../../../../docs/combat-v6-manual-system-design.md),Phase 5A 数值见 -[`docs/combat-v6-manual-phase-5a-balance.md`](../../../../docs/combat-v6-manual-phase-5a-balance.md),红尘剑宗完整纵切见 -[`docs/combat-v6-lingxiao-datang-sect-design.md`](../../../../docs/combat-v6-lingxiao-datang-sect-design.md)。 +[`docs/combat-v6-manual-system-design.md`](../../../../../docs/combat-v6-manual-system-design.md),Phase 5A 数值见 +[`docs/combat-v6-manual-phase-5a-balance.md`](../../../../../docs/combat-v6-manual-phase-5a-balance.md),红尘剑宗完整纵切见 +[`docs/combat-v6-lingxiao-datang-sect-design.md`](../../../../../docs/combat-v6-lingxiao-datang-sect-design.md)。 幽都经典双流派纵切见 -[`docs/combat-v6-youdu-classic-sect-design.md`](../../../../docs/combat-v6-youdu-classic-sect-design.md)。 +[`docs/combat-v6-youdu-classic-sect-design.md`](../../../../../docs/combat-v6-youdu-classic-sect-design.md)。 无相禅宗治疗防护纵切见 -[`docs/combat-v6-wuxiang-sect-design.md`](../../../../docs/combat-v6-wuxiang-sect-design.md)。 +[`docs/combat-v6-wuxiang-sect-design.md`](../../../../../docs/combat-v6-wuxiang-sect-design.md)。 天衍圣地河洛九宫纵切见 -[`docs/combat-v6-tianyan-sect-design.md`](../../../../docs/combat-v6-tianyan-sect-design.md)。 +[`docs/combat-v6-tianyan-sect-design.md`](../../../../../docs/combat-v6-tianyan-sect-design.md)。 九劫天宫经典双流派纵切见 -[`docs/combat-v6-jiujie-tiangong-redesign.md`](../../../../docs/combat-v6-jiujie-tiangong-redesign.md)。 +[`docs/combat-v6-jiujie-tiangong-redesign.md`](../../../../../docs/combat-v6-jiujie-tiangong-redesign.md)。 Phase 7A 遭遇与训练 Host 见 -[`docs/combat-v6-phase-7a-training-host-design.md`](../../../../docs/combat-v6-phase-7a-training-host-design.md)。 +[`docs/combat-v6-phase-7a-training-host-design.md`](../../../../../docs/combat-v6-phase-7a-training-host-design.md)。 Phase 7B 权威构筑与训练房接入见 -[`docs/combat-v6-phase-7b-authoritative-training-design.md`](../../../../docs/combat-v6-phase-7b-authoritative-training-design.md)。 +[`docs/combat-v6-phase-7b-authoritative-training-design.md`](../../../../../docs/combat-v6-phase-7b-authoritative-training-design.md)。 Phase 7C Redis权威运行时与回放归档见 -[`docs/combat-v6-phase-7c-redis-runtime-replay-design.md`](../../../../docs/combat-v6-phase-7c-redis-runtime-replay-design.md)。 +[`docs/combat-v6-phase-7c-redis-runtime-replay-design.md`](../../../../../docs/combat-v6-phase-7c-redis-runtime-replay-design.md)。 `core/` 当前是 `/Users/churcht/Documents/GitHub/mhxy-combat-copy/packages/engine/src` @@ -75,6 +75,6 @@ Phase 7C Redis权威运行时与回放归档见 新增 `wild/`:青溪灵草坡固定物种池、5~15级独立面板、确定性1~3只遭遇、可恢复PVE Host与资源结算纯规则。训练与野外共享中立回合编排,旧投影和rules v5保持兼容。服务端已接入每日20次Redis额度、终局Condition消费者和地图探索页;捕捉、宠物个体、奖励和任务消费尚未接入。 -设计见 [Phase 7D专项稿](../../../../docs/combat-v6-phase-7d-wild-encounter-design.md)。部署需执行新增业务迁移并完成认证页面及消息故障验证。 +设计见 [Phase 7D专项稿](../../../../../docs/combat-v6-phase-7d-wild-encounter-design.md)。部署需执行新增业务迁移并完成认证页面及消息故障验证。 -练功房与野外探索使用独立 v6 战斗 UI。引擎可选行动观察回调提供逐行动快照,服务端裁剪展示字段并发送 `delta-v1` 差量,客户端按序同步文字与状态;不改变战斗规则或 RNG。展示范围、基准数据及恢复/发布语义见 [战斗 UI 说明](../../../../docs/combat-v6-battle-ui.md)。 +练功房与野外探索使用独立 v6 战斗 UI。引擎可选行动观察回调提供逐行动快照,服务端裁剪展示字段并发送 `delta-v1` 差量,客户端按序同步文字与状态;不改变战斗规则或 RNG。展示范围、基准数据及恢复/发布语义见 [战斗 UI 说明](../../../../../docs/combat-v6-battle-ui.md)。 diff --git a/src/shared/engine/combat-v6/beasts/allocation.ts b/packages/shared/src/engine/combat-v6/beasts/allocation.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/allocation.ts rename to packages/shared/src/engine/combat-v6/beasts/allocation.ts diff --git a/src/shared/engine/combat-v6/beasts/beasts.test.ts b/packages/shared/src/engine/combat-v6/beasts/beasts.test.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/beasts.test.ts rename to packages/shared/src/engine/combat-v6/beasts/beasts.test.ts diff --git a/src/shared/engine/combat-v6/beasts/birth-skills.test.ts b/packages/shared/src/engine/combat-v6/beasts/birth-skills.test.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/birth-skills.test.ts rename to packages/shared/src/engine/combat-v6/beasts/birth-skills.test.ts diff --git a/src/shared/engine/combat-v6/beasts/classic-skills.test.ts b/packages/shared/src/engine/combat-v6/beasts/classic-skills.test.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/classic-skills.test.ts rename to packages/shared/src/engine/combat-v6/beasts/classic-skills.test.ts diff --git a/src/shared/engine/combat-v6/beasts/codex.test.ts b/packages/shared/src/engine/combat-v6/beasts/codex.test.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/codex.test.ts rename to packages/shared/src/engine/combat-v6/beasts/codex.test.ts diff --git a/src/shared/engine/combat-v6/beasts/codex.ts b/packages/shared/src/engine/combat-v6/beasts/codex.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/codex.ts rename to packages/shared/src/engine/combat-v6/beasts/codex.ts diff --git a/src/shared/engine/combat-v6/beasts/combo-alignment.test.ts b/packages/shared/src/engine/combat-v6/beasts/combo-alignment.test.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/combo-alignment.test.ts rename to packages/shared/src/engine/combat-v6/beasts/combo-alignment.test.ts diff --git a/src/shared/engine/combat-v6/beasts/content.ts b/packages/shared/src/engine/combat-v6/beasts/content.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/content.ts rename to packages/shared/src/engine/combat-v6/beasts/content.ts diff --git a/src/shared/engine/combat-v6/beasts/data/README.md b/packages/shared/src/engine/combat-v6/beasts/data/README.md similarity index 91% rename from src/shared/engine/combat-v6/beasts/data/README.md rename to packages/shared/src/engine/combat-v6/beasts/data/README.md index f9b1abb0..ad0c3aca 100644 --- a/src/shared/engine/combat-v6/beasts/data/README.md +++ b/packages/shared/src/engine/combat-v6/beasts/data/README.md @@ -4,7 +4,7 @@ ## 1. 物种与个体生成:species.json -变异规则见 [变异灵兽](../../../../../../docs/combat-v6-beast-mutation.md):野外独立抽取身份,五项资质和成长统一提高 10%,技能规则不变。`rollBeastTraits` 的可选第三参数为 `isMutant`;洗炼保留身份并从物种范围重抽,新个体必须包含培养身份和初始等级,变异统一生成0级宝宝;不做旧身份推断。 +变异规则见 [变异灵兽](../../../../../../../docs/combat-v6-beast-mutation.md):野外独立抽取身份,五项资质和成长统一提高 10%,技能规则不变。`rollBeastTraits` 的可选第三参数为 `isMutant`;洗炼保留身份并从物种范围重抽,新个体必须包含培养身份和初始等级,变异统一生成0级宝宝;不做旧身份推断。 当前物种包 `formatVersion: 2`、`contentRevision: 11`,包含炼气至化神十八种物种,炼气、元婴、化神各四种,筑基、金丹各三种。讨论中的“结丹”对应游戏实际境界键“金丹”。每条配置包含名称、图标、习性描述、开放境界、携带等级、是否可领取为初始伙伴、五项资质范围、成长范围及出生技能规则。不再提供固定定位或物种加点偏好。 @@ -27,13 +27,13 @@ 化生果只把等级、修为与属性点退回0级状态,保留身份、原始等级、资质、成长、技能、寿命及编组。原始等级为 `L0` 的纯野生重养至 `L0` 期间每级获得3点自由属性,之后每级5点,原有点数亏损不会增加或补回。 -每个候选独立以 1/2 获得,因此满天生技能和零个候选都有非零概率;无必带物种可生成0技能、0格。零格可通过一次传承开启第一格,之后只覆盖已有技能。具体审查见[天生技能机制修订](../../../../../../docs/combat-v6-beast-birth-skills.md)。 +每个候选独立以 1/2 获得,因此满天生技能和零个候选都有非零概率;无必带物种可生成0技能、0格。零格可通过一次传承开启第一格,之后只覆盖已有技能。具体审查见[天生技能机制修订](../../../../../../../docs/combat-v6-beast-birth-skills.md)。 -校验拒绝天生池不在3—6项、必带超过2项、重复技能、核心/候选重叠、未知技能、高低级同族混入、出生格数越界、区间倒置、非法初始伙伴及境界等级不一致。规则明细与后续接入边界见[物种与生成底座定稿](../../../../../../docs/combat-v6-beast-species-generation.md)。 +校验拒绝天生池不在3—6项、必带超过2项、重复技能、核心/候选重叠、未知技能、高低级同族混入、出生格数越界、区间倒置、非法初始伙伴及境界等级不一致。规则明细与后续接入边界见[物种与生成底座定稿](../../../../../../../docs/combat-v6-beast-species-generation.md)。 ## 2. 技能与传承灵印:skills.json -每条技能配置 ID、名称、图标、天赋文案 `flavorText`、是否注册传承灵印物品 `book`、是否为高级技能 `advanced` 和一个 `effect`。天赋文案与从效果参数生成的数值、代价及冲突说明共同展示;传承灵印预览中的技能名称读取同一配置。当前文案定稿见[灵兽技能天赋文案](../../../../../../docs/combat-v6-beast-skill-copy.md)。 +每条技能配置 ID、名称、图标、天赋文案 `flavorText`、是否注册传承灵印物品 `book`、是否为高级技能 `advanced` 和一个 `effect`。天赋文案与从效果参数生成的数值、代价及冲突说明共同展示;传承灵印预览中的技能名称读取同一配置。当前文案定稿见[灵兽技能天赋文案](../../../../../../../docs/combat-v6-beast-skill-copy.md)。 效果参数: @@ -70,7 +70,7 @@ | `speed` | `factor` | 灵兽共用面板中的速度倍率 | | `combo` | `chance`、`coefficient`、`physicalFactor` | 仅普攻触发,物理伤害有全局代价,物理反震阻止追加 | -经典技能第一批新增 18 个、第二批新增 8 个,第三阶段新增 6 个,第四阶段新增 6 个,第六阶段新增 4 个,第七阶段新增 4 个,第八阶段新增 4 个,第九阶段新增 8 个,第十阶段新增 6 个,曾累计69个,本轮移除2个demo技能后共67个可用技能及传承灵印,详见 [累计迁移记录与后续队列](../../../../../../docs/combat-v6-beast-classic-skills.md)。新书已加入清溪野外掉落池;物种修订4将三个初始物种改为独立技能池,具体清单见迁移记录。 +经典技能第一批新增 18 个、第二批新增 8 个,第三阶段新增 6 个,第四阶段新增 6 个,第六阶段新增 4 个,第七阶段新增 4 个,第八阶段新增 4 个,第九阶段新增 8 个,第十阶段新增 6 个,曾累计69个,本轮移除2个demo技能后共67个可用技能及传承灵印,详见 [累计迁移记录与后续队列](../../../../../../../docs/combat-v6-beast-classic-skills.md)。新书已加入清溪野外掉落池;物种修订4将三个初始物种改为独立技能池,具体清单见迁移记录。 数值 0.25 表示 25% 概率,1.1 表示 1.1 倍系数。参与表达式的参数最多六位小数;不接受自由公式或脚本。新机制仍须扩展编译器。 @@ -96,7 +96,7 @@ baseChance + missingHpFactor × (1 - 目标当前气血 / 目标最大气血) + levelDifferenceFactor × (人物等级 - 目标等级) ``` -2026-09-11 按用户提供的手游公式调整,完整规则见 [召唤兽设计](../../../../../../docs/combat-v6-summoned-beast-system-design.md) 第 4 节。每项五维实际值为 `naturalBase + level × naturalPerLevel + 已分配值`,普通当前为 `10 + 等级 + 已分配值`,变异为 `20 + 等级 + 已分配值`。 +2026-09-11 按用户提供的手游公式调整,完整规则见 [召唤兽设计](../../../../../../../docs/combat-v6-summoned-beast-system-design.md) 第 4 节。每项五维实际值为 `naturalBase + level × naturalPerLevel + 已分配值`,普通当前为 `10 + 等级 + 已分配值`,变异为 `20 + 等级 + 已分配值`。 除法防外,每项采用 `floor(等级 × 对应资质 × aptitudeCoefficient + 属性点 × 成长 × attributeCoefficient)`。法防采用法力资质的等级项,加体质、魔力、力量、耐力各自的成长贡献;四项系数在 `magicDef.attributeCoefficients` 中配置。没有旧版的固定面板底值,也不再将资质乘到属性贡献上。两项先求和,最终向下取整一次。 @@ -133,7 +133,7 @@ baseChance + missingHpFactor × (1 - 目标当前气血 / 目标最大气血) Schema 由 `pack.ts` 中对应的 `*PackShape` 生成;测试检查两者一致。配置基线 fixture 保存原三物种、五技能;生成基线覆盖三物种各 128 种子、免费伙伴及 0/10/90/180 级捕捉个体,并对照培养、面板与阵容。五技能在 32 个种子下逐一对照真实内核状态和事件。 ```bash -bun run test src/shared/engine/combat-v6/beasts +bun run test packages/shared/src/engine/combat-v6/beasts bun run lint bun run test bun run build @@ -151,13 +151,13 @@ bun run build ## 洗炼:refinement.json -归元灵露与上品归元灵露的名称、说明、图标、使用境界、消耗数量和寿命恢复策略在本配置中维护;`refinement-config.ts`校验并提供给道具注册、共享洗炼函数与界面。两档共用物种抽取概率,仅适用范围不同。详见[洗炼规则与本地发放](../../../../../../docs/combat-v6-beast-refinement.md)。 +归元灵露与上品归元灵露的名称、说明、图标、使用境界、消耗数量和寿命恢复策略在本配置中维护;`refinement-config.ts`校验并提供给道具注册、共享洗炼函数与界面。两档共用物种抽取概率,仅适用范围不同。详见[洗炼规则与本地发放](../../../../../../../docs/combat-v6-beast-refinement.md)。 ### 灵兽头像 -物种 `icon` 支持 emoji 或 `icon:名称`,由前端统一 `GameIcon` 解析。SVG 注册、资源位置与新增流程见 [统一图标规范](../../../../../../docs/game-icons.md)。头像修订不重抽已有个体。 +物种 `icon` 支持 emoji 或 `icon:名称`,由前端统一 `GameIcon` 解析。SVG 注册、资源位置与新增流程见 [统一图标规范](../../../../../../../docs/game-icons.md)。头像修订不重抽已有个体。 ## 融合:fusion.json -`fusion-config.ts`与`fusion.schema.json`共同校验配置;档位唯一且权重合计100。双宝宝宝宝率`babyChance=0.95`,其他组合假宝宝率`pseudoBabyChance=0.25`,非必带不同技能各按`skillChance=0.5`继承。资质和成长档位、融合上限集中在此文件,出生及变异不受融合上限裁剪。规则与点数预算见[宝宝体系与融合](../../../../../../docs/combat-v6-beast-fusion.md)。融合个体使用独立版本`summoned_beast_fusion_v1`,不受出生技能数上限限制。 +`fusion-config.ts`与`fusion.schema.json`共同校验配置;档位唯一且权重合计100。双宝宝宝宝率`babyChance=0.95`,其他组合假宝宝率`pseudoBabyChance=0.25`,非必带不同技能各按`skillChance=0.5`继承。资质和成长档位、融合上限集中在此文件,出生及变异不受融合上限裁剪。规则与点数预算见[宝宝体系与融合](../../../../../../../docs/combat-v6-beast-fusion.md)。融合个体使用独立版本`summoned_beast_fusion_v1`,不受出生技能数上限限制。 diff --git a/src/shared/engine/combat-v6/beasts/data/fusion.json b/packages/shared/src/engine/combat-v6/beasts/data/fusion.json similarity index 100% rename from src/shared/engine/combat-v6/beasts/data/fusion.json rename to packages/shared/src/engine/combat-v6/beasts/data/fusion.json diff --git a/src/shared/engine/combat-v6/beasts/data/fusion.schema.json b/packages/shared/src/engine/combat-v6/beasts/data/fusion.schema.json similarity index 100% rename from src/shared/engine/combat-v6/beasts/data/fusion.schema.json rename to packages/shared/src/engine/combat-v6/beasts/data/fusion.schema.json diff --git a/src/shared/engine/combat-v6/beasts/data/progression.json b/packages/shared/src/engine/combat-v6/beasts/data/progression.json similarity index 100% rename from src/shared/engine/combat-v6/beasts/data/progression.json rename to packages/shared/src/engine/combat-v6/beasts/data/progression.json diff --git a/src/shared/engine/combat-v6/beasts/data/progression.schema.json b/packages/shared/src/engine/combat-v6/beasts/data/progression.schema.json similarity index 100% rename from src/shared/engine/combat-v6/beasts/data/progression.schema.json rename to packages/shared/src/engine/combat-v6/beasts/data/progression.schema.json diff --git a/src/shared/engine/combat-v6/beasts/data/refinement.json b/packages/shared/src/engine/combat-v6/beasts/data/refinement.json similarity index 100% rename from src/shared/engine/combat-v6/beasts/data/refinement.json rename to packages/shared/src/engine/combat-v6/beasts/data/refinement.json diff --git a/src/shared/engine/combat-v6/beasts/data/refinement.schema.json b/packages/shared/src/engine/combat-v6/beasts/data/refinement.schema.json similarity index 100% rename from src/shared/engine/combat-v6/beasts/data/refinement.schema.json rename to packages/shared/src/engine/combat-v6/beasts/data/refinement.schema.json diff --git a/src/shared/engine/combat-v6/beasts/data/skills.json b/packages/shared/src/engine/combat-v6/beasts/data/skills.json similarity index 100% rename from src/shared/engine/combat-v6/beasts/data/skills.json rename to packages/shared/src/engine/combat-v6/beasts/data/skills.json diff --git a/src/shared/engine/combat-v6/beasts/data/skills.schema.json b/packages/shared/src/engine/combat-v6/beasts/data/skills.schema.json similarity index 100% rename from src/shared/engine/combat-v6/beasts/data/skills.schema.json rename to packages/shared/src/engine/combat-v6/beasts/data/skills.schema.json diff --git a/src/shared/engine/combat-v6/beasts/data/species.json b/packages/shared/src/engine/combat-v6/beasts/data/species.json similarity index 100% rename from src/shared/engine/combat-v6/beasts/data/species.json rename to packages/shared/src/engine/combat-v6/beasts/data/species.json diff --git a/src/shared/engine/combat-v6/beasts/data/species.schema.json b/packages/shared/src/engine/combat-v6/beasts/data/species.schema.json similarity index 100% rename from src/shared/engine/combat-v6/beasts/data/species.schema.json rename to packages/shared/src/engine/combat-v6/beasts/data/species.schema.json diff --git a/src/shared/engine/combat-v6/beasts/feeding.test.ts b/packages/shared/src/engine/combat-v6/beasts/feeding.test.ts similarity index 96% rename from src/shared/engine/combat-v6/beasts/feeding.test.ts rename to packages/shared/src/engine/combat-v6/beasts/feeding.test.ts index 5bdf8217..4675c67a 100644 --- a/src/shared/engine/combat-v6/beasts/feeding.test.ts +++ b/packages/shared/src/engine/combat-v6/beasts/feeding.test.ts @@ -1,5 +1,5 @@ -import { resolveAlchemyEffects } from '@shared/lib/alchemyEffectResolver'; -import type { PillSpec } from '@shared/types/consumable'; +import { resolveAlchemyEffects } from '@daoyou/shared/lib/alchemyEffectResolver'; +import type { PillSpec } from '@daoyou/shared/types/consumable'; import { describe, expect, it } from 'vitest'; import { buildSpiritFruitSpec } from '../../spirit-field/spiritFruit'; import { BEAST_SPECIES } from './content'; diff --git a/src/shared/engine/combat-v6/beasts/feeding.ts b/packages/shared/src/engine/combat-v6/beasts/feeding.ts similarity index 96% rename from src/shared/engine/combat-v6/beasts/feeding.ts rename to packages/shared/src/engine/combat-v6/beasts/feeding.ts index e77f1376..140b19fc 100644 --- a/src/shared/engine/combat-v6/beasts/feeding.ts +++ b/packages/shared/src/engine/combat-v6/beasts/feeding.ts @@ -1,4 +1,4 @@ -import type { ConsumableSpec } from '@shared/types/consumable'; +import type { ConsumableSpec } from '@daoyou/shared/types/consumable'; import { gainBeastExp, nextBeastExp } from './progression'; import type { SummonedBeast } from './schema'; diff --git a/src/shared/engine/combat-v6/beasts/fusion-config.ts b/packages/shared/src/engine/combat-v6/beasts/fusion-config.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/fusion-config.ts rename to packages/shared/src/engine/combat-v6/beasts/fusion-config.ts diff --git a/src/shared/engine/combat-v6/beasts/fusion.test.ts b/packages/shared/src/engine/combat-v6/beasts/fusion.test.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/fusion.test.ts rename to packages/shared/src/engine/combat-v6/beasts/fusion.test.ts diff --git a/src/shared/engine/combat-v6/beasts/fusion.ts b/packages/shared/src/engine/combat-v6/beasts/fusion.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/fusion.ts rename to packages/shared/src/engine/combat-v6/beasts/fusion.ts diff --git a/src/shared/engine/combat-v6/beasts/generation-baseline.test.ts b/packages/shared/src/engine/combat-v6/beasts/generation-baseline.test.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/generation-baseline.test.ts rename to packages/shared/src/engine/combat-v6/beasts/generation-baseline.test.ts diff --git a/src/shared/engine/combat-v6/beasts/generator.ts b/packages/shared/src/engine/combat-v6/beasts/generator.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/generator.ts rename to packages/shared/src/engine/combat-v6/beasts/generator.ts diff --git a/src/shared/engine/combat-v6/beasts/ghost-skills.test.ts b/packages/shared/src/engine/combat-v6/beasts/ghost-skills.test.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/ghost-skills.test.ts rename to packages/shared/src/engine/combat-v6/beasts/ghost-skills.test.ts diff --git a/src/shared/engine/combat-v6/beasts/group-spells.test.ts b/packages/shared/src/engine/combat-v6/beasts/group-spells.test.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/group-spells.test.ts rename to packages/shared/src/engine/combat-v6/beasts/group-spells.test.ts diff --git a/src/shared/engine/combat-v6/beasts/identity.ts b/packages/shared/src/engine/combat-v6/beasts/identity.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/identity.ts rename to packages/shared/src/engine/combat-v6/beasts/identity.ts diff --git a/src/shared/engine/combat-v6/beasts/index.ts b/packages/shared/src/engine/combat-v6/beasts/index.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/index.ts rename to packages/shared/src/engine/combat-v6/beasts/index.ts diff --git a/src/shared/engine/combat-v6/beasts/mutation.test.ts b/packages/shared/src/engine/combat-v6/beasts/mutation.test.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/mutation.test.ts rename to packages/shared/src/engine/combat-v6/beasts/mutation.test.ts diff --git a/src/shared/engine/combat-v6/beasts/pack.test.ts b/packages/shared/src/engine/combat-v6/beasts/pack.test.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/pack.test.ts rename to packages/shared/src/engine/combat-v6/beasts/pack.test.ts diff --git a/src/shared/engine/combat-v6/beasts/pack.ts b/packages/shared/src/engine/combat-v6/beasts/pack.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/pack.ts rename to packages/shared/src/engine/combat-v6/beasts/pack.ts diff --git a/src/shared/engine/combat-v6/beasts/progression.ts b/packages/shared/src/engine/combat-v6/beasts/progression.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/progression.ts rename to packages/shared/src/engine/combat-v6/beasts/progression.ts diff --git a/src/shared/engine/combat-v6/beasts/projection.ts b/packages/shared/src/engine/combat-v6/beasts/projection.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/projection.ts rename to packages/shared/src/engine/combat-v6/beasts/projection.ts diff --git a/src/shared/engine/combat-v6/beasts/refinement-config.ts b/packages/shared/src/engine/combat-v6/beasts/refinement-config.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/refinement-config.ts rename to packages/shared/src/engine/combat-v6/beasts/refinement-config.ts diff --git a/src/shared/engine/combat-v6/beasts/refinement.test.ts b/packages/shared/src/engine/combat-v6/beasts/refinement.test.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/refinement.test.ts rename to packages/shared/src/engine/combat-v6/beasts/refinement.test.ts diff --git a/src/shared/engine/combat-v6/beasts/refinement.ts b/packages/shared/src/engine/combat-v6/beasts/refinement.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/refinement.ts rename to packages/shared/src/engine/combat-v6/beasts/refinement.ts diff --git a/src/shared/engine/combat-v6/beasts/rejuvenation.test.ts b/packages/shared/src/engine/combat-v6/beasts/rejuvenation.test.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/rejuvenation.test.ts rename to packages/shared/src/engine/combat-v6/beasts/rejuvenation.test.ts diff --git a/src/shared/engine/combat-v6/beasts/rejuvenation.ts b/packages/shared/src/engine/combat-v6/beasts/rejuvenation.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/rejuvenation.ts rename to packages/shared/src/engine/combat-v6/beasts/rejuvenation.ts diff --git a/src/shared/engine/combat-v6/beasts/schema.ts b/packages/shared/src/engine/combat-v6/beasts/schema.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/schema.ts rename to packages/shared/src/engine/combat-v6/beasts/schema.ts diff --git a/src/shared/engine/combat-v6/beasts/skill-compiler.ts b/packages/shared/src/engine/combat-v6/beasts/skill-compiler.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/skill-compiler.ts rename to packages/shared/src/engine/combat-v6/beasts/skill-compiler.ts diff --git a/src/shared/engine/combat-v6/beasts/skill-runtime.test.ts b/packages/shared/src/engine/combat-v6/beasts/skill-runtime.test.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/skill-runtime.test.ts rename to packages/shared/src/engine/combat-v6/beasts/skill-runtime.test.ts diff --git a/src/shared/engine/combat-v6/beasts/species-skills.test.ts b/packages/shared/src/engine/combat-v6/beasts/species-skills.test.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/species-skills.test.ts rename to packages/shared/src/engine/combat-v6/beasts/species-skills.test.ts diff --git a/src/shared/engine/combat-v6/beasts/status-skills.test.ts b/packages/shared/src/engine/combat-v6/beasts/status-skills.test.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/status-skills.test.ts rename to packages/shared/src/engine/combat-v6/beasts/status-skills.test.ts diff --git a/src/shared/engine/combat-v6/beasts/stealth-skills.test.ts b/packages/shared/src/engine/combat-v6/beasts/stealth-skills.test.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/stealth-skills.test.ts rename to packages/shared/src/engine/combat-v6/beasts/stealth-skills.test.ts diff --git a/src/shared/engine/combat-v6/beasts/sustain-skills.test.ts b/packages/shared/src/engine/combat-v6/beasts/sustain-skills.test.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/sustain-skills.test.ts rename to packages/shared/src/engine/combat-v6/beasts/sustain-skills.test.ts diff --git a/src/shared/engine/combat-v6/beasts/trait-generator.ts b/packages/shared/src/engine/combat-v6/beasts/trait-generator.ts similarity index 100% rename from src/shared/engine/combat-v6/beasts/trait-generator.ts rename to packages/shared/src/engine/combat-v6/beasts/trait-generator.ts diff --git a/src/shared/engine/combat-v6/breakthrough/host.test.ts b/packages/shared/src/engine/combat-v6/breakthrough/host.test.ts similarity index 98% rename from src/shared/engine/combat-v6/breakthrough/host.test.ts rename to packages/shared/src/engine/combat-v6/breakthrough/host.test.ts index e2b4b3eb..45f05318 100644 --- a/src/shared/engine/combat-v6/breakthrough/host.test.ts +++ b/packages/shared/src/engine/combat-v6/breakthrough/host.test.ts @@ -1,4 +1,4 @@ -import type { CultivatorCondition } from '@shared/types/condition'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; import { describe, expect, it } from 'vitest'; import { generateStarterBeast } from '../beasts'; import { COMBAT_V6_SECT_DEFINITIONS_V4 } from '../content'; diff --git a/src/shared/engine/combat-v6/breakthrough/host.ts b/packages/shared/src/engine/combat-v6/breakthrough/host.ts similarity index 100% rename from src/shared/engine/combat-v6/breakthrough/host.ts rename to packages/shared/src/engine/combat-v6/breakthrough/host.ts diff --git a/src/shared/engine/combat-v6/build-state/build-state.test.ts b/packages/shared/src/engine/combat-v6/build-state/build-state.test.ts similarity index 100% rename from src/shared/engine/combat-v6/build-state/build-state.test.ts rename to packages/shared/src/engine/combat-v6/build-state/build-state.test.ts diff --git a/src/shared/engine/combat-v6/build-state/index.ts b/packages/shared/src/engine/combat-v6/build-state/index.ts similarity index 97% rename from src/shared/engine/combat-v6/build-state/index.ts rename to packages/shared/src/engine/combat-v6/build-state/index.ts index 834ce0e2..ac258291 100644 --- a/src/shared/engine/combat-v6/build-state/index.ts +++ b/packages/shared/src/engine/combat-v6/build-state/index.ts @@ -1,4 +1,4 @@ -import type { SectCombatView } from '@shared/contracts/combatV6'; +import type { SectCombatView } from '@daoyou/shared/contracts/combatV6'; import { COMBAT_V6_SECT_DEFINITIONS, type CombatV6SectId, diff --git a/src/shared/engine/combat-v6/content/authoring-expressions.ts b/packages/shared/src/engine/combat-v6/content/authoring-expressions.ts similarity index 100% rename from src/shared/engine/combat-v6/content/authoring-expressions.ts rename to packages/shared/src/engine/combat-v6/content/authoring-expressions.ts diff --git a/src/shared/engine/combat-v6/content/compiler.ts b/packages/shared/src/engine/combat-v6/content/compiler.ts similarity index 100% rename from src/shared/engine/combat-v6/content/compiler.ts rename to packages/shared/src/engine/combat-v6/content/compiler.ts diff --git a/src/shared/engine/combat-v6/content/content.test.ts b/packages/shared/src/engine/combat-v6/content/content.test.ts similarity index 100% rename from src/shared/engine/combat-v6/content/content.test.ts rename to packages/shared/src/engine/combat-v6/content/content.test.ts diff --git a/src/shared/engine/combat-v6/content/data/README.md b/packages/shared/src/engine/combat-v6/content/data/README.md similarity index 98% rename from src/shared/engine/combat-v6/content/data/README.md rename to packages/shared/src/engine/combat-v6/content/data/README.md index 3261d69e..ca82d9d4 100644 --- a/src/shared/engine/combat-v6/content/data/README.md +++ b/packages/shared/src/engine/combat-v6/content/data/README.md @@ -28,7 +28,7 @@ - 加载检查两流派各 21 个唯一层/槽位、唯一内容 ID、技能/状态/被动引用、未使用被动、表达式语法及部分补丁范围。每次变更仍需运行内容测试和战斗验收。 - 例如“济幽”的 4/3 倍增益在 JSON 使用完整小数 1.3333333333333333,不要为了显示好看而截断精度。 -五宗门现有构筑内容均已接入配置,具体包见下文。代表性浏览器玩法验收与纯内核覆盖范围见[治理规划第37节](../../../../../../docs/combat-v6-build-content-governance-plan.md#37-当前逐项验收矩阵)。外围宗门组织系统仍保留,不能因为存在本包而删除。 +五宗门现有构筑内容均已接入配置,具体包见下文。代表性浏览器玩法验收与纯内核覆盖范围见[治理规划第37节](../../../../../../../docs/combat-v6-build-content-governance-plan.md#37-当前逐项验收矩阵)。外围宗门组织系统仍保留,不能因为存在本包而删除。 `lingxiao-combat.json` 维护红尘剑宗的 10 个主动技能、10 个状态和剑意资源。基础技能顺序、节点授予技能引用与技能学习关系的规则同上;节点/被动参数由流派包维护。 diff --git a/src/shared/engine/combat-v6/content/data/jiujie-combat.json b/packages/shared/src/engine/combat-v6/content/data/jiujie-combat.json similarity index 100% rename from src/shared/engine/combat-v6/content/data/jiujie-combat.json rename to packages/shared/src/engine/combat-v6/content/data/jiujie-combat.json diff --git a/src/shared/engine/combat-v6/content/data/jiujie-combat.schema.json b/packages/shared/src/engine/combat-v6/content/data/jiujie-combat.schema.json similarity index 100% rename from src/shared/engine/combat-v6/content/data/jiujie-combat.schema.json rename to packages/shared/src/engine/combat-v6/content/data/jiujie-combat.schema.json diff --git a/src/shared/engine/combat-v6/content/data/jiujie-paths.json b/packages/shared/src/engine/combat-v6/content/data/jiujie-paths.json similarity index 100% rename from src/shared/engine/combat-v6/content/data/jiujie-paths.json rename to packages/shared/src/engine/combat-v6/content/data/jiujie-paths.json diff --git a/src/shared/engine/combat-v6/content/data/jiujie-paths.schema.json b/packages/shared/src/engine/combat-v6/content/data/jiujie-paths.schema.json similarity index 100% rename from src/shared/engine/combat-v6/content/data/jiujie-paths.schema.json rename to packages/shared/src/engine/combat-v6/content/data/jiujie-paths.schema.json diff --git a/src/shared/engine/combat-v6/content/data/lingxiao-combat.json b/packages/shared/src/engine/combat-v6/content/data/lingxiao-combat.json similarity index 100% rename from src/shared/engine/combat-v6/content/data/lingxiao-combat.json rename to packages/shared/src/engine/combat-v6/content/data/lingxiao-combat.json diff --git a/src/shared/engine/combat-v6/content/data/lingxiao-combat.schema.json b/packages/shared/src/engine/combat-v6/content/data/lingxiao-combat.schema.json similarity index 100% rename from src/shared/engine/combat-v6/content/data/lingxiao-combat.schema.json rename to packages/shared/src/engine/combat-v6/content/data/lingxiao-combat.schema.json diff --git a/src/shared/engine/combat-v6/content/data/lingxiao-paths.json b/packages/shared/src/engine/combat-v6/content/data/lingxiao-paths.json similarity index 100% rename from src/shared/engine/combat-v6/content/data/lingxiao-paths.json rename to packages/shared/src/engine/combat-v6/content/data/lingxiao-paths.json diff --git a/src/shared/engine/combat-v6/content/data/lingxiao-paths.schema.json b/packages/shared/src/engine/combat-v6/content/data/lingxiao-paths.schema.json similarity index 100% rename from src/shared/engine/combat-v6/content/data/lingxiao-paths.schema.json rename to packages/shared/src/engine/combat-v6/content/data/lingxiao-paths.schema.json diff --git a/src/shared/engine/combat-v6/content/data/methods.json b/packages/shared/src/engine/combat-v6/content/data/methods.json similarity index 100% rename from src/shared/engine/combat-v6/content/data/methods.json rename to packages/shared/src/engine/combat-v6/content/data/methods.json diff --git a/src/shared/engine/combat-v6/content/data/methods.schema.json b/packages/shared/src/engine/combat-v6/content/data/methods.schema.json similarity index 100% rename from src/shared/engine/combat-v6/content/data/methods.schema.json rename to packages/shared/src/engine/combat-v6/content/data/methods.schema.json diff --git a/src/shared/engine/combat-v6/content/data/skill-learning.json b/packages/shared/src/engine/combat-v6/content/data/skill-learning.json similarity index 100% rename from src/shared/engine/combat-v6/content/data/skill-learning.json rename to packages/shared/src/engine/combat-v6/content/data/skill-learning.json diff --git a/src/shared/engine/combat-v6/content/data/skill-learning.schema.json b/packages/shared/src/engine/combat-v6/content/data/skill-learning.schema.json similarity index 100% rename from src/shared/engine/combat-v6/content/data/skill-learning.schema.json rename to packages/shared/src/engine/combat-v6/content/data/skill-learning.schema.json diff --git a/src/shared/engine/combat-v6/content/data/tianyan-foundation.json b/packages/shared/src/engine/combat-v6/content/data/tianyan-foundation.json similarity index 100% rename from src/shared/engine/combat-v6/content/data/tianyan-foundation.json rename to packages/shared/src/engine/combat-v6/content/data/tianyan-foundation.json diff --git a/src/shared/engine/combat-v6/content/data/tianyan-foundation.schema.json b/packages/shared/src/engine/combat-v6/content/data/tianyan-foundation.schema.json similarity index 100% rename from src/shared/engine/combat-v6/content/data/tianyan-foundation.schema.json rename to packages/shared/src/engine/combat-v6/content/data/tianyan-foundation.schema.json diff --git a/src/shared/engine/combat-v6/content/data/tianyan-paths.json b/packages/shared/src/engine/combat-v6/content/data/tianyan-paths.json similarity index 100% rename from src/shared/engine/combat-v6/content/data/tianyan-paths.json rename to packages/shared/src/engine/combat-v6/content/data/tianyan-paths.json diff --git a/src/shared/engine/combat-v6/content/data/tianyan-paths.schema.json b/packages/shared/src/engine/combat-v6/content/data/tianyan-paths.schema.json similarity index 100% rename from src/shared/engine/combat-v6/content/data/tianyan-paths.schema.json rename to packages/shared/src/engine/combat-v6/content/data/tianyan-paths.schema.json diff --git a/src/shared/engine/combat-v6/content/data/tianyan-skills.json b/packages/shared/src/engine/combat-v6/content/data/tianyan-skills.json similarity index 100% rename from src/shared/engine/combat-v6/content/data/tianyan-skills.json rename to packages/shared/src/engine/combat-v6/content/data/tianyan-skills.json diff --git a/src/shared/engine/combat-v6/content/data/tianyan-skills.schema.json b/packages/shared/src/engine/combat-v6/content/data/tianyan-skills.schema.json similarity index 100% rename from src/shared/engine/combat-v6/content/data/tianyan-skills.schema.json rename to packages/shared/src/engine/combat-v6/content/data/tianyan-skills.schema.json diff --git a/src/shared/engine/combat-v6/content/data/wuxiang-combat.json b/packages/shared/src/engine/combat-v6/content/data/wuxiang-combat.json similarity index 100% rename from src/shared/engine/combat-v6/content/data/wuxiang-combat.json rename to packages/shared/src/engine/combat-v6/content/data/wuxiang-combat.json diff --git a/src/shared/engine/combat-v6/content/data/wuxiang-combat.schema.json b/packages/shared/src/engine/combat-v6/content/data/wuxiang-combat.schema.json similarity index 100% rename from src/shared/engine/combat-v6/content/data/wuxiang-combat.schema.json rename to packages/shared/src/engine/combat-v6/content/data/wuxiang-combat.schema.json diff --git a/src/shared/engine/combat-v6/content/data/wuxiang-paths.json b/packages/shared/src/engine/combat-v6/content/data/wuxiang-paths.json similarity index 100% rename from src/shared/engine/combat-v6/content/data/wuxiang-paths.json rename to packages/shared/src/engine/combat-v6/content/data/wuxiang-paths.json diff --git a/src/shared/engine/combat-v6/content/data/wuxiang-paths.schema.json b/packages/shared/src/engine/combat-v6/content/data/wuxiang-paths.schema.json similarity index 100% rename from src/shared/engine/combat-v6/content/data/wuxiang-paths.schema.json rename to packages/shared/src/engine/combat-v6/content/data/wuxiang-paths.schema.json diff --git a/src/shared/engine/combat-v6/content/data/youdu-combat.json b/packages/shared/src/engine/combat-v6/content/data/youdu-combat.json similarity index 100% rename from src/shared/engine/combat-v6/content/data/youdu-combat.json rename to packages/shared/src/engine/combat-v6/content/data/youdu-combat.json diff --git a/src/shared/engine/combat-v6/content/data/youdu-combat.schema.json b/packages/shared/src/engine/combat-v6/content/data/youdu-combat.schema.json similarity index 100% rename from src/shared/engine/combat-v6/content/data/youdu-combat.schema.json rename to packages/shared/src/engine/combat-v6/content/data/youdu-combat.schema.json diff --git a/src/shared/engine/combat-v6/content/data/youdu-paths.json b/packages/shared/src/engine/combat-v6/content/data/youdu-paths.json similarity index 100% rename from src/shared/engine/combat-v6/content/data/youdu-paths.json rename to packages/shared/src/engine/combat-v6/content/data/youdu-paths.json diff --git a/src/shared/engine/combat-v6/content/data/youdu-paths.schema.json b/packages/shared/src/engine/combat-v6/content/data/youdu-paths.schema.json similarity index 100% rename from src/shared/engine/combat-v6/content/data/youdu-paths.schema.json rename to packages/shared/src/engine/combat-v6/content/data/youdu-paths.schema.json diff --git a/src/shared/engine/combat-v6/content/index.ts b/packages/shared/src/engine/combat-v6/content/index.ts similarity index 100% rename from src/shared/engine/combat-v6/content/index.ts rename to packages/shared/src/engine/combat-v6/content/index.ts diff --git a/src/shared/engine/combat-v6/content/jiujie-meridians.test.ts b/packages/shared/src/engine/combat-v6/content/jiujie-meridians.test.ts similarity index 100% rename from src/shared/engine/combat-v6/content/jiujie-meridians.test.ts rename to packages/shared/src/engine/combat-v6/content/jiujie-meridians.test.ts diff --git a/src/shared/engine/combat-v6/content/jiujie-pack.test.ts b/packages/shared/src/engine/combat-v6/content/jiujie-pack.test.ts similarity index 100% rename from src/shared/engine/combat-v6/content/jiujie-pack.test.ts rename to packages/shared/src/engine/combat-v6/content/jiujie-pack.test.ts diff --git a/src/shared/engine/combat-v6/content/jiujie-pack.ts b/packages/shared/src/engine/combat-v6/content/jiujie-pack.ts similarity index 98% rename from src/shared/engine/combat-v6/content/jiujie-pack.ts rename to packages/shared/src/engine/combat-v6/content/jiujie-pack.ts index b9150fcf..18522024 100644 --- a/src/shared/engine/combat-v6/content/jiujie-pack.ts +++ b/packages/shared/src/engine/combat-v6/content/jiujie-pack.ts @@ -1,4 +1,4 @@ -import { formatContentPackErrors } from '@shared/lib/content-pack-errors'; +import { formatContentPackErrors } from '@daoyou/shared/lib/content-pack-errors'; import { z } from 'zod'; import { FormulaFamily, diff --git a/src/shared/engine/combat-v6/content/jiujie-path-pack.test.ts b/packages/shared/src/engine/combat-v6/content/jiujie-path-pack.test.ts similarity index 100% rename from src/shared/engine/combat-v6/content/jiujie-path-pack.test.ts rename to packages/shared/src/engine/combat-v6/content/jiujie-path-pack.test.ts diff --git a/src/shared/engine/combat-v6/content/jiujie-path-pack.ts b/packages/shared/src/engine/combat-v6/content/jiujie-path-pack.ts similarity index 98% rename from src/shared/engine/combat-v6/content/jiujie-path-pack.ts rename to packages/shared/src/engine/combat-v6/content/jiujie-path-pack.ts index dcbbf215..6c9adaca 100644 --- a/src/shared/engine/combat-v6/content/jiujie-path-pack.ts +++ b/packages/shared/src/engine/combat-v6/content/jiujie-path-pack.ts @@ -1,4 +1,4 @@ -import { formatContentPackErrors } from '@shared/lib/content-pack-errors'; +import { formatContentPackErrors } from '@daoyou/shared/lib/content-pack-errors'; import { z } from 'zod'; import { ATTR_NAMES, SkillTag, TargetSide } from '../core'; import { validateSectExpressions } from './authoring-expressions'; diff --git a/src/shared/engine/combat-v6/content/jiujie-shapes.ts b/packages/shared/src/engine/combat-v6/content/jiujie-shapes.ts similarity index 100% rename from src/shared/engine/combat-v6/content/jiujie-shapes.ts rename to packages/shared/src/engine/combat-v6/content/jiujie-shapes.ts diff --git a/src/shared/engine/combat-v6/content/jiujie.ts b/packages/shared/src/engine/combat-v6/content/jiujie.ts similarity index 100% rename from src/shared/engine/combat-v6/content/jiujie.ts rename to packages/shared/src/engine/combat-v6/content/jiujie.ts diff --git a/src/shared/engine/combat-v6/content/lingxiao-mechanics.test.ts b/packages/shared/src/engine/combat-v6/content/lingxiao-mechanics.test.ts similarity index 100% rename from src/shared/engine/combat-v6/content/lingxiao-mechanics.test.ts rename to packages/shared/src/engine/combat-v6/content/lingxiao-mechanics.test.ts diff --git a/src/shared/engine/combat-v6/content/lingxiao-pack.test.ts b/packages/shared/src/engine/combat-v6/content/lingxiao-pack.test.ts similarity index 100% rename from src/shared/engine/combat-v6/content/lingxiao-pack.test.ts rename to packages/shared/src/engine/combat-v6/content/lingxiao-pack.test.ts diff --git a/src/shared/engine/combat-v6/content/lingxiao-pack.ts b/packages/shared/src/engine/combat-v6/content/lingxiao-pack.ts similarity index 98% rename from src/shared/engine/combat-v6/content/lingxiao-pack.ts rename to packages/shared/src/engine/combat-v6/content/lingxiao-pack.ts index ae587c9d..2794d4ea 100644 --- a/src/shared/engine/combat-v6/content/lingxiao-pack.ts +++ b/packages/shared/src/engine/combat-v6/content/lingxiao-pack.ts @@ -1,5 +1,5 @@ import { lxEffect as effect, lxHook, lxModifier } from './lingxiao-shapes'; -import { formatContentPackErrors } from '@shared/lib/content-pack-errors'; +import { formatContentPackErrors } from '@daoyou/shared/lib/content-pack-errors'; import { z } from 'zod'; import { ATTR_NAMES, CommandPolicy, EffectType, SkillTag, StatusCategory, TargetMode, TargetSide, UnitKind, type SkillDef, type StatusDef } from '../core'; import { validateSectExpressions } from './authoring-expressions'; diff --git a/src/shared/engine/combat-v6/content/lingxiao-path-pack.test.ts b/packages/shared/src/engine/combat-v6/content/lingxiao-path-pack.test.ts similarity index 100% rename from src/shared/engine/combat-v6/content/lingxiao-path-pack.test.ts rename to packages/shared/src/engine/combat-v6/content/lingxiao-path-pack.test.ts diff --git a/src/shared/engine/combat-v6/content/lingxiao-path-pack.ts b/packages/shared/src/engine/combat-v6/content/lingxiao-path-pack.ts similarity index 99% rename from src/shared/engine/combat-v6/content/lingxiao-path-pack.ts rename to packages/shared/src/engine/combat-v6/content/lingxiao-path-pack.ts index 0865c00b..cf2fb907 100644 --- a/src/shared/engine/combat-v6/content/lingxiao-path-pack.ts +++ b/packages/shared/src/engine/combat-v6/content/lingxiao-path-pack.ts @@ -1,5 +1,5 @@ import { lxEffect as actionEffect, lxHook as hook, lxModifier, lxPanel as panel } from './lingxiao-shapes'; -import { formatContentPackErrors } from '@shared/lib/content-pack-errors'; +import { formatContentPackErrors } from '@daoyou/shared/lib/content-pack-errors'; import { z } from 'zod'; import { BUILTIN_SKILL_ID, EffectType, SkillTag, TargetSide } from '../core'; import { sectSkillLearning } from './skill-learning'; diff --git a/src/shared/engine/combat-v6/content/lingxiao-restoration.test.ts b/packages/shared/src/engine/combat-v6/content/lingxiao-restoration.test.ts similarity index 100% rename from src/shared/engine/combat-v6/content/lingxiao-restoration.test.ts rename to packages/shared/src/engine/combat-v6/content/lingxiao-restoration.test.ts diff --git a/src/shared/engine/combat-v6/content/lingxiao-shapes.ts b/packages/shared/src/engine/combat-v6/content/lingxiao-shapes.ts similarity index 100% rename from src/shared/engine/combat-v6/content/lingxiao-shapes.ts rename to packages/shared/src/engine/combat-v6/content/lingxiao-shapes.ts diff --git a/src/shared/engine/combat-v6/content/lingxiao.ts b/packages/shared/src/engine/combat-v6/content/lingxiao.ts similarity index 100% rename from src/shared/engine/combat-v6/content/lingxiao.ts rename to packages/shared/src/engine/combat-v6/content/lingxiao.ts diff --git a/src/shared/engine/combat-v6/content/meridian-selection.test.ts b/packages/shared/src/engine/combat-v6/content/meridian-selection.test.ts similarity index 100% rename from src/shared/engine/combat-v6/content/meridian-selection.test.ts rename to packages/shared/src/engine/combat-v6/content/meridian-selection.test.ts diff --git a/src/shared/engine/combat-v6/content/meridian-selection.ts b/packages/shared/src/engine/combat-v6/content/meridian-selection.ts similarity index 100% rename from src/shared/engine/combat-v6/content/meridian-selection.ts rename to packages/shared/src/engine/combat-v6/content/meridian-selection.ts diff --git a/src/shared/engine/combat-v6/content/method-pack.test.ts b/packages/shared/src/engine/combat-v6/content/method-pack.test.ts similarity index 100% rename from src/shared/engine/combat-v6/content/method-pack.test.ts rename to packages/shared/src/engine/combat-v6/content/method-pack.test.ts diff --git a/src/shared/engine/combat-v6/content/method-pack.ts b/packages/shared/src/engine/combat-v6/content/method-pack.ts similarity index 96% rename from src/shared/engine/combat-v6/content/method-pack.ts rename to packages/shared/src/engine/combat-v6/content/method-pack.ts index 694e489f..77dc1c20 100644 --- a/src/shared/engine/combat-v6/content/method-pack.ts +++ b/packages/shared/src/engine/combat-v6/content/method-pack.ts @@ -1,4 +1,4 @@ -import { formatContentPackErrors } from '@shared/lib/content-pack-errors'; +import { formatContentPackErrors } from '@daoyou/shared/lib/content-pack-errors'; import { z } from 'zod'; import { ATTR_NAMES } from '../core'; import raw from './data/methods.json'; diff --git a/src/shared/engine/combat-v6/content/skill-learning.test.ts b/packages/shared/src/engine/combat-v6/content/skill-learning.test.ts similarity index 100% rename from src/shared/engine/combat-v6/content/skill-learning.test.ts rename to packages/shared/src/engine/combat-v6/content/skill-learning.test.ts diff --git a/src/shared/engine/combat-v6/content/skill-learning.ts b/packages/shared/src/engine/combat-v6/content/skill-learning.ts similarity index 96% rename from src/shared/engine/combat-v6/content/skill-learning.ts rename to packages/shared/src/engine/combat-v6/content/skill-learning.ts index 9c170903..a502e31e 100644 --- a/src/shared/engine/combat-v6/content/skill-learning.ts +++ b/packages/shared/src/engine/combat-v6/content/skill-learning.ts @@ -1,4 +1,4 @@ -import { formatContentPackErrors } from '@shared/lib/content-pack-errors'; +import { formatContentPackErrors } from '@daoyou/shared/lib/content-pack-errors'; import { z } from 'zod'; import raw from './data/skill-learning.json'; import { SECT_METHODS } from './method-pack'; diff --git a/src/shared/engine/combat-v6/content/tianyan-combat.test.ts b/packages/shared/src/engine/combat-v6/content/tianyan-combat.test.ts similarity index 100% rename from src/shared/engine/combat-v6/content/tianyan-combat.test.ts rename to packages/shared/src/engine/combat-v6/content/tianyan-combat.test.ts diff --git a/src/shared/engine/combat-v6/content/tianyan-foundation.test.ts b/packages/shared/src/engine/combat-v6/content/tianyan-foundation.test.ts similarity index 100% rename from src/shared/engine/combat-v6/content/tianyan-foundation.test.ts rename to packages/shared/src/engine/combat-v6/content/tianyan-foundation.test.ts diff --git a/src/shared/engine/combat-v6/content/tianyan-foundation.ts b/packages/shared/src/engine/combat-v6/content/tianyan-foundation.ts similarity index 98% rename from src/shared/engine/combat-v6/content/tianyan-foundation.ts rename to packages/shared/src/engine/combat-v6/content/tianyan-foundation.ts index 684a951f..a46c61a1 100644 --- a/src/shared/engine/combat-v6/content/tianyan-foundation.ts +++ b/packages/shared/src/engine/combat-v6/content/tianyan-foundation.ts @@ -1,4 +1,4 @@ -import { formatContentPackErrors } from '@shared/lib/content-pack-errors'; +import { formatContentPackErrors } from '@daoyou/shared/lib/content-pack-errors'; import { z } from 'zod'; import { type StatusDef } from '../core'; import { validateSectExpressions } from './authoring-expressions'; diff --git a/src/shared/engine/combat-v6/content/tianyan-path-pack.test.ts b/packages/shared/src/engine/combat-v6/content/tianyan-path-pack.test.ts similarity index 100% rename from src/shared/engine/combat-v6/content/tianyan-path-pack.test.ts rename to packages/shared/src/engine/combat-v6/content/tianyan-path-pack.test.ts diff --git a/src/shared/engine/combat-v6/content/tianyan-path-pack.ts b/packages/shared/src/engine/combat-v6/content/tianyan-path-pack.ts similarity index 98% rename from src/shared/engine/combat-v6/content/tianyan-path-pack.ts rename to packages/shared/src/engine/combat-v6/content/tianyan-path-pack.ts index 6ae1458b..d6c7265e 100644 --- a/src/shared/engine/combat-v6/content/tianyan-path-pack.ts +++ b/packages/shared/src/engine/combat-v6/content/tianyan-path-pack.ts @@ -1,4 +1,4 @@ -import { formatContentPackErrors } from '@shared/lib/content-pack-errors'; +import { formatContentPackErrors } from '@daoyou/shared/lib/content-pack-errors'; import { z } from 'zod'; import { ATTR_NAMES } from '../core'; import { validateSectExpressions } from './authoring-expressions'; diff --git a/src/shared/engine/combat-v6/content/tianyan-reactions.ts b/packages/shared/src/engine/combat-v6/content/tianyan-reactions.ts similarity index 100% rename from src/shared/engine/combat-v6/content/tianyan-reactions.ts rename to packages/shared/src/engine/combat-v6/content/tianyan-reactions.ts diff --git a/src/shared/engine/combat-v6/content/tianyan-shapes.ts b/packages/shared/src/engine/combat-v6/content/tianyan-shapes.ts similarity index 100% rename from src/shared/engine/combat-v6/content/tianyan-shapes.ts rename to packages/shared/src/engine/combat-v6/content/tianyan-shapes.ts diff --git a/src/shared/engine/combat-v6/content/tianyan-skill-pack.test.ts b/packages/shared/src/engine/combat-v6/content/tianyan-skill-pack.test.ts similarity index 100% rename from src/shared/engine/combat-v6/content/tianyan-skill-pack.test.ts rename to packages/shared/src/engine/combat-v6/content/tianyan-skill-pack.test.ts diff --git a/src/shared/engine/combat-v6/content/tianyan-skill-pack.ts b/packages/shared/src/engine/combat-v6/content/tianyan-skill-pack.ts similarity index 98% rename from src/shared/engine/combat-v6/content/tianyan-skill-pack.ts rename to packages/shared/src/engine/combat-v6/content/tianyan-skill-pack.ts index dbfd03ed..36a89d4f 100644 --- a/src/shared/engine/combat-v6/content/tianyan-skill-pack.ts +++ b/packages/shared/src/engine/combat-v6/content/tianyan-skill-pack.ts @@ -1,4 +1,4 @@ -import { formatContentPackErrors } from '@shared/lib/content-pack-errors'; +import { formatContentPackErrors } from '@daoyou/shared/lib/content-pack-errors'; import { z } from 'zod'; import { SkillTag, type SkillDef } from '../core'; import { validateSectExpressions } from './authoring-expressions'; diff --git a/src/shared/engine/combat-v6/content/tianyan.ts b/packages/shared/src/engine/combat-v6/content/tianyan.ts similarity index 100% rename from src/shared/engine/combat-v6/content/tianyan.ts rename to packages/shared/src/engine/combat-v6/content/tianyan.ts diff --git a/src/shared/engine/combat-v6/content/types.ts b/packages/shared/src/engine/combat-v6/content/types.ts similarity index 100% rename from src/shared/engine/combat-v6/content/types.ts rename to packages/shared/src/engine/combat-v6/content/types.ts diff --git a/src/shared/engine/combat-v6/content/wuxiang-meridians.test.ts b/packages/shared/src/engine/combat-v6/content/wuxiang-meridians.test.ts similarity index 100% rename from src/shared/engine/combat-v6/content/wuxiang-meridians.test.ts rename to packages/shared/src/engine/combat-v6/content/wuxiang-meridians.test.ts diff --git a/src/shared/engine/combat-v6/content/wuxiang-pack.test.ts b/packages/shared/src/engine/combat-v6/content/wuxiang-pack.test.ts similarity index 100% rename from src/shared/engine/combat-v6/content/wuxiang-pack.test.ts rename to packages/shared/src/engine/combat-v6/content/wuxiang-pack.test.ts diff --git a/src/shared/engine/combat-v6/content/wuxiang-pack.ts b/packages/shared/src/engine/combat-v6/content/wuxiang-pack.ts similarity index 98% rename from src/shared/engine/combat-v6/content/wuxiang-pack.ts rename to packages/shared/src/engine/combat-v6/content/wuxiang-pack.ts index c56d255a..d80b22ce 100644 --- a/src/shared/engine/combat-v6/content/wuxiang-pack.ts +++ b/packages/shared/src/engine/combat-v6/content/wuxiang-pack.ts @@ -1,4 +1,4 @@ -import { formatContentPackErrors } from '@shared/lib/content-pack-errors'; +import { formatContentPackErrors } from '@daoyou/shared/lib/content-pack-errors'; import { z } from 'zod'; import { FormulaFamily, diff --git a/src/shared/engine/combat-v6/content/wuxiang-path-pack.test.ts b/packages/shared/src/engine/combat-v6/content/wuxiang-path-pack.test.ts similarity index 100% rename from src/shared/engine/combat-v6/content/wuxiang-path-pack.test.ts rename to packages/shared/src/engine/combat-v6/content/wuxiang-path-pack.test.ts diff --git a/src/shared/engine/combat-v6/content/wuxiang-path-pack.ts b/packages/shared/src/engine/combat-v6/content/wuxiang-path-pack.ts similarity index 98% rename from src/shared/engine/combat-v6/content/wuxiang-path-pack.ts rename to packages/shared/src/engine/combat-v6/content/wuxiang-path-pack.ts index af649e4f..9f824dc4 100644 --- a/src/shared/engine/combat-v6/content/wuxiang-path-pack.ts +++ b/packages/shared/src/engine/combat-v6/content/wuxiang-path-pack.ts @@ -1,4 +1,4 @@ -import { formatContentPackErrors } from '@shared/lib/content-pack-errors'; +import { formatContentPackErrors } from '@daoyou/shared/lib/content-pack-errors'; import { z } from 'zod'; import { ATTR_NAMES, SkillTag, TargetSide } from '../core'; import { validateSectExpressions } from './authoring-expressions'; diff --git a/src/shared/engine/combat-v6/content/wuxiang-shapes.ts b/packages/shared/src/engine/combat-v6/content/wuxiang-shapes.ts similarity index 100% rename from src/shared/engine/combat-v6/content/wuxiang-shapes.ts rename to packages/shared/src/engine/combat-v6/content/wuxiang-shapes.ts diff --git a/src/shared/engine/combat-v6/content/wuxiang.ts b/packages/shared/src/engine/combat-v6/content/wuxiang.ts similarity index 100% rename from src/shared/engine/combat-v6/content/wuxiang.ts rename to packages/shared/src/engine/combat-v6/content/wuxiang.ts diff --git a/src/shared/engine/combat-v6/content/youdu-meridians.test.ts b/packages/shared/src/engine/combat-v6/content/youdu-meridians.test.ts similarity index 100% rename from src/shared/engine/combat-v6/content/youdu-meridians.test.ts rename to packages/shared/src/engine/combat-v6/content/youdu-meridians.test.ts diff --git a/src/shared/engine/combat-v6/content/youdu-pack.test.ts b/packages/shared/src/engine/combat-v6/content/youdu-pack.test.ts similarity index 100% rename from src/shared/engine/combat-v6/content/youdu-pack.test.ts rename to packages/shared/src/engine/combat-v6/content/youdu-pack.test.ts diff --git a/src/shared/engine/combat-v6/content/youdu-pack.ts b/packages/shared/src/engine/combat-v6/content/youdu-pack.ts similarity index 98% rename from src/shared/engine/combat-v6/content/youdu-pack.ts rename to packages/shared/src/engine/combat-v6/content/youdu-pack.ts index 218aee50..26b79de6 100644 --- a/src/shared/engine/combat-v6/content/youdu-pack.ts +++ b/packages/shared/src/engine/combat-v6/content/youdu-pack.ts @@ -1,5 +1,5 @@ import { ydTargeting as targeting, ydEffect as effects, ydModifier, ydHook } from './youdu-shapes'; -import { formatContentPackErrors } from '@shared/lib/content-pack-errors'; +import { formatContentPackErrors } from '@daoyou/shared/lib/content-pack-errors'; import { z } from 'zod'; import { ATTR_NAMES, EffectType, SkillTag, StatusCategory, StatusTick, TickKind, type SkillDef, type StatusDef } from '../core'; import { validateSectExpressions } from './authoring-expressions'; diff --git a/src/shared/engine/combat-v6/content/youdu-path-pack.test.ts b/packages/shared/src/engine/combat-v6/content/youdu-path-pack.test.ts similarity index 100% rename from src/shared/engine/combat-v6/content/youdu-path-pack.test.ts rename to packages/shared/src/engine/combat-v6/content/youdu-path-pack.test.ts diff --git a/src/shared/engine/combat-v6/content/youdu-path-pack.ts b/packages/shared/src/engine/combat-v6/content/youdu-path-pack.ts similarity index 98% rename from src/shared/engine/combat-v6/content/youdu-path-pack.ts rename to packages/shared/src/engine/combat-v6/content/youdu-path-pack.ts index ec40b23f..b3b0d705 100644 --- a/src/shared/engine/combat-v6/content/youdu-path-pack.ts +++ b/packages/shared/src/engine/combat-v6/content/youdu-path-pack.ts @@ -1,5 +1,5 @@ import { ydHook as hook, ydModifier } from './youdu-shapes'; -import { formatContentPackErrors } from '@shared/lib/content-pack-errors'; +import { formatContentPackErrors } from '@daoyou/shared/lib/content-pack-errors'; import { z } from 'zod'; import { ATTR_NAMES, SkillTag, TargetSide } from '../core'; import { sectSkillLearning } from './skill-learning'; diff --git a/src/shared/engine/combat-v6/content/youdu-restoration.test.ts b/packages/shared/src/engine/combat-v6/content/youdu-restoration.test.ts similarity index 100% rename from src/shared/engine/combat-v6/content/youdu-restoration.test.ts rename to packages/shared/src/engine/combat-v6/content/youdu-restoration.test.ts diff --git a/src/shared/engine/combat-v6/content/youdu-shapes.ts b/packages/shared/src/engine/combat-v6/content/youdu-shapes.ts similarity index 100% rename from src/shared/engine/combat-v6/content/youdu-shapes.ts rename to packages/shared/src/engine/combat-v6/content/youdu-shapes.ts diff --git a/src/shared/engine/combat-v6/content/youdu.ts b/packages/shared/src/engine/combat-v6/content/youdu.ts similarity index 100% rename from src/shared/engine/combat-v6/content/youdu.ts rename to packages/shared/src/engine/combat-v6/content/youdu.ts diff --git a/src/shared/engine/combat-v6/core/action-status-effects.test.ts b/packages/shared/src/engine/combat-v6/core/action-status-effects.test.ts similarity index 100% rename from src/shared/engine/combat-v6/core/action-status-effects.test.ts rename to packages/shared/src/engine/combat-v6/core/action-status-effects.test.ts diff --git a/src/shared/engine/combat-v6/core/action.ts b/packages/shared/src/engine/combat-v6/core/action.ts similarity index 100% rename from src/shared/engine/combat-v6/core/action.ts rename to packages/shared/src/engine/combat-v6/core/action.ts diff --git a/src/shared/engine/combat-v6/core/barriers.ts b/packages/shared/src/engine/combat-v6/core/barriers.ts similarity index 100% rename from src/shared/engine/combat-v6/core/barriers.ts rename to packages/shared/src/engine/combat-v6/core/barriers.ts diff --git a/src/shared/engine/combat-v6/core/commands.ts b/packages/shared/src/engine/combat-v6/core/commands.ts similarity index 100% rename from src/shared/engine/combat-v6/core/commands.ts rename to packages/shared/src/engine/combat-v6/core/commands.ts diff --git a/src/shared/engine/combat-v6/core/constants.ts b/packages/shared/src/engine/combat-v6/core/constants.ts similarity index 100% rename from src/shared/engine/combat-v6/core/constants.ts rename to packages/shared/src/engine/combat-v6/core/constants.ts diff --git a/src/shared/engine/combat-v6/core/context.ts b/packages/shared/src/engine/combat-v6/core/context.ts similarity index 100% rename from src/shared/engine/combat-v6/core/context.ts rename to packages/shared/src/engine/combat-v6/core/context.ts diff --git a/src/shared/engine/combat-v6/core/damage.ts b/packages/shared/src/engine/combat-v6/core/damage.ts similarity index 100% rename from src/shared/engine/combat-v6/core/damage.ts rename to packages/shared/src/engine/combat-v6/core/damage.ts diff --git a/src/shared/engine/combat-v6/core/effects.ts b/packages/shared/src/engine/combat-v6/core/effects.ts similarity index 100% rename from src/shared/engine/combat-v6/core/effects.ts rename to packages/shared/src/engine/combat-v6/core/effects.ts diff --git a/src/shared/engine/combat-v6/core/enums.ts b/packages/shared/src/engine/combat-v6/core/enums.ts similarity index 100% rename from src/shared/engine/combat-v6/core/enums.ts rename to packages/shared/src/engine/combat-v6/core/enums.ts diff --git a/src/shared/engine/combat-v6/core/errors.ts b/packages/shared/src/engine/combat-v6/core/errors.ts similarity index 100% rename from src/shared/engine/combat-v6/core/errors.ts rename to packages/shared/src/engine/combat-v6/core/errors.ts diff --git a/src/shared/engine/combat-v6/core/expr.ts b/packages/shared/src/engine/combat-v6/core/expr.ts similarity index 100% rename from src/shared/engine/combat-v6/core/expr.ts rename to packages/shared/src/engine/combat-v6/core/expr.ts diff --git a/src/shared/engine/combat-v6/core/hooks.ts b/packages/shared/src/engine/combat-v6/core/hooks.ts similarity index 100% rename from src/shared/engine/combat-v6/core/hooks.ts rename to packages/shared/src/engine/combat-v6/core/hooks.ts diff --git a/src/shared/engine/combat-v6/core/index.ts b/packages/shared/src/engine/combat-v6/core/index.ts similarity index 100% rename from src/shared/engine/combat-v6/core/index.ts rename to packages/shared/src/engine/combat-v6/core/index.ts diff --git a/src/shared/engine/combat-v6/core/math.ts b/packages/shared/src/engine/combat-v6/core/math.ts similarity index 100% rename from src/shared/engine/combat-v6/core/math.ts rename to packages/shared/src/engine/combat-v6/core/math.ts diff --git a/src/shared/engine/combat-v6/core/modifiers.ts b/packages/shared/src/engine/combat-v6/core/modifiers.ts similarity index 100% rename from src/shared/engine/combat-v6/core/modifiers.ts rename to packages/shared/src/engine/combat-v6/core/modifiers.ts diff --git a/src/shared/engine/combat-v6/core/passives.ts b/packages/shared/src/engine/combat-v6/core/passives.ts similarity index 100% rename from src/shared/engine/combat-v6/core/passives.ts rename to packages/shared/src/engine/combat-v6/core/passives.ts diff --git a/src/shared/engine/combat-v6/core/phase-3-primitives.test.ts b/packages/shared/src/engine/combat-v6/core/phase-3-primitives.test.ts similarity index 100% rename from src/shared/engine/combat-v6/core/phase-3-primitives.test.ts rename to packages/shared/src/engine/combat-v6/core/phase-3-primitives.test.ts diff --git a/src/shared/engine/combat-v6/core/pipeline.ts b/packages/shared/src/engine/combat-v6/core/pipeline.ts similarity index 100% rename from src/shared/engine/combat-v6/core/pipeline.ts rename to packages/shared/src/engine/combat-v6/core/pipeline.ts diff --git a/src/shared/engine/combat-v6/core/query.ts b/packages/shared/src/engine/combat-v6/core/query.ts similarity index 100% rename from src/shared/engine/combat-v6/core/query.ts rename to packages/shared/src/engine/combat-v6/core/query.ts diff --git a/src/shared/engine/combat-v6/core/requirements.ts b/packages/shared/src/engine/combat-v6/core/requirements.ts similarity index 100% rename from src/shared/engine/combat-v6/core/requirements.ts rename to packages/shared/src/engine/combat-v6/core/requirements.ts diff --git a/src/shared/engine/combat-v6/core/rng.ts b/packages/shared/src/engine/combat-v6/core/rng.ts similarity index 100% rename from src/shared/engine/combat-v6/core/rng.ts rename to packages/shared/src/engine/combat-v6/core/rng.ts diff --git a/src/shared/engine/combat-v6/core/session.ts b/packages/shared/src/engine/combat-v6/core/session.ts similarity index 100% rename from src/shared/engine/combat-v6/core/session.ts rename to packages/shared/src/engine/combat-v6/core/session.ts diff --git a/src/shared/engine/combat-v6/core/skills.ts b/packages/shared/src/engine/combat-v6/core/skills.ts similarity index 100% rename from src/shared/engine/combat-v6/core/skills.ts rename to packages/shared/src/engine/combat-v6/core/skills.ts diff --git a/src/shared/engine/combat-v6/core/status-restoration.test.ts b/packages/shared/src/engine/combat-v6/core/status-restoration.test.ts similarity index 100% rename from src/shared/engine/combat-v6/core/status-restoration.test.ts rename to packages/shared/src/engine/combat-v6/core/status-restoration.test.ts diff --git a/src/shared/engine/combat-v6/core/status.ts b/packages/shared/src/engine/combat-v6/core/status.ts similarity index 100% rename from src/shared/engine/combat-v6/core/status.ts rename to packages/shared/src/engine/combat-v6/core/status.ts diff --git a/src/shared/engine/combat-v6/core/targeting.ts b/packages/shared/src/engine/combat-v6/core/targeting.ts similarity index 100% rename from src/shared/engine/combat-v6/core/targeting.ts rename to packages/shared/src/engine/combat-v6/core/targeting.ts diff --git a/src/shared/engine/combat-v6/core/types.ts b/packages/shared/src/engine/combat-v6/core/types.ts similarity index 100% rename from src/shared/engine/combat-v6/core/types.ts rename to packages/shared/src/engine/combat-v6/core/types.ts diff --git a/src/shared/engine/combat-v6/core/units.ts b/packages/shared/src/engine/combat-v6/core/units.ts similarity index 100% rename from src/shared/engine/combat-v6/core/units.ts rename to packages/shared/src/engine/combat-v6/core/units.ts diff --git a/src/shared/engine/combat-v6/core/validate.ts b/packages/shared/src/engine/combat-v6/core/validate.ts similarity index 100% rename from src/shared/engine/combat-v6/core/validate.ts rename to packages/shared/src/engine/combat-v6/core/validate.ts diff --git a/src/shared/engine/combat-v6/core/when.ts b/packages/shared/src/engine/combat-v6/core/when.ts similarity index 100% rename from src/shared/engine/combat-v6/core/when.ts rename to packages/shared/src/engine/combat-v6/core/when.ts diff --git a/src/shared/engine/combat-v6/dungeon/dungeon.test.ts b/packages/shared/src/engine/combat-v6/dungeon/dungeon.test.ts similarity index 96% rename from src/shared/engine/combat-v6/dungeon/dungeon.test.ts rename to packages/shared/src/engine/combat-v6/dungeon/dungeon.test.ts index 1ff7eedd..56a6f438 100644 --- a/src/shared/engine/combat-v6/dungeon/dungeon.test.ts +++ b/packages/shared/src/engine/combat-v6/dungeon/dungeon.test.ts @@ -1,13 +1,13 @@ -import { applyUnitDelta } from '@shared/combat-v6/playback'; +import { applyUnitDelta } from '@daoyou/shared/combat-v6/playback'; import { combatV6Playback, combatV6Units, -} from '@shared/combat-v6/presentation'; +} from '@daoyou/shared/combat-v6/presentation'; import { appendDungeonReward, planDungeonReward, -} from '@shared/rewards/dungeon'; -import type { CultivatorCondition } from '@shared/types/condition'; +} from '@daoyou/shared/rewards/dungeon'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; import { describe, expect, it } from 'vitest'; import { COMBAT_V6_SECT_DEFINITIONS_V4, diff --git a/src/shared/engine/combat-v6/dungeon/host.ts b/packages/shared/src/engine/combat-v6/dungeon/host.ts similarity index 100% rename from src/shared/engine/combat-v6/dungeon/host.ts rename to packages/shared/src/engine/combat-v6/dungeon/host.ts diff --git a/src/shared/engine/combat-v6/encounter/compiler.ts b/packages/shared/src/engine/combat-v6/encounter/compiler.ts similarity index 100% rename from src/shared/engine/combat-v6/encounter/compiler.ts rename to packages/shared/src/engine/combat-v6/encounter/compiler.ts diff --git a/src/shared/engine/combat-v6/encounter/content.ts b/packages/shared/src/engine/combat-v6/encounter/content.ts similarity index 100% rename from src/shared/engine/combat-v6/encounter/content.ts rename to packages/shared/src/engine/combat-v6/encounter/content.ts diff --git a/src/shared/engine/combat-v6/encounter/data/README.md b/packages/shared/src/engine/combat-v6/encounter/data/README.md similarity index 100% rename from src/shared/engine/combat-v6/encounter/data/README.md rename to packages/shared/src/engine/combat-v6/encounter/data/README.md diff --git a/src/shared/engine/combat-v6/encounter/data/training.json b/packages/shared/src/engine/combat-v6/encounter/data/training.json similarity index 100% rename from src/shared/engine/combat-v6/encounter/data/training.json rename to packages/shared/src/engine/combat-v6/encounter/data/training.json diff --git a/src/shared/engine/combat-v6/encounter/data/training.schema.json b/packages/shared/src/engine/combat-v6/encounter/data/training.schema.json similarity index 100% rename from src/shared/engine/combat-v6/encounter/data/training.schema.json rename to packages/shared/src/engine/combat-v6/encounter/data/training.schema.json diff --git a/src/shared/engine/combat-v6/encounter/host.ts b/packages/shared/src/engine/combat-v6/encounter/host.ts similarity index 100% rename from src/shared/engine/combat-v6/encounter/host.ts rename to packages/shared/src/engine/combat-v6/encounter/host.ts diff --git a/src/shared/engine/combat-v6/encounter/index.ts b/packages/shared/src/engine/combat-v6/encounter/index.ts similarity index 100% rename from src/shared/engine/combat-v6/encounter/index.ts rename to packages/shared/src/engine/combat-v6/encounter/index.ts diff --git a/src/shared/engine/combat-v6/encounter/pack.test.ts b/packages/shared/src/engine/combat-v6/encounter/pack.test.ts similarity index 100% rename from src/shared/engine/combat-v6/encounter/pack.test.ts rename to packages/shared/src/engine/combat-v6/encounter/pack.test.ts diff --git a/src/shared/engine/combat-v6/encounter/pack.ts b/packages/shared/src/engine/combat-v6/encounter/pack.ts similarity index 98% rename from src/shared/engine/combat-v6/encounter/pack.ts rename to packages/shared/src/engine/combat-v6/encounter/pack.ts index e86bd574..4739a247 100644 --- a/src/shared/engine/combat-v6/encounter/pack.ts +++ b/packages/shared/src/engine/combat-v6/encounter/pack.ts @@ -1,4 +1,4 @@ -import { formatContentPackErrors } from '@shared/lib/content-pack-errors'; +import { formatContentPackErrors } from '@daoyou/shared/lib/content-pack-errors'; import { z } from 'zod'; import { ATTR_NAMES, DamageOrigin, EffectType, FormulaFamily, SkillTag, StatusCategory, StatusTick, TargetSide, TickKind, UnitKind } from '../core'; import { validateSectExpressions } from '../content/authoring-expressions'; diff --git a/src/shared/engine/combat-v6/encounter/preset-enemy.test.ts b/packages/shared/src/engine/combat-v6/encounter/preset-enemy.test.ts similarity index 100% rename from src/shared/engine/combat-v6/encounter/preset-enemy.test.ts rename to packages/shared/src/engine/combat-v6/encounter/preset-enemy.test.ts diff --git a/src/shared/engine/combat-v6/encounter/preset-enemy.ts b/packages/shared/src/engine/combat-v6/encounter/preset-enemy.ts similarity index 100% rename from src/shared/engine/combat-v6/encounter/preset-enemy.ts rename to packages/shared/src/engine/combat-v6/encounter/preset-enemy.ts diff --git a/src/shared/engine/combat-v6/encounter/types.ts b/packages/shared/src/engine/combat-v6/encounter/types.ts similarity index 100% rename from src/shared/engine/combat-v6/encounter/types.ts rename to packages/shared/src/engine/combat-v6/encounter/types.ts diff --git a/src/shared/engine/combat-v6/equipment/art-refinement.test.ts b/packages/shared/src/engine/combat-v6/equipment/art-refinement.test.ts similarity index 99% rename from src/shared/engine/combat-v6/equipment/art-refinement.test.ts rename to packages/shared/src/engine/combat-v6/equipment/art-refinement.test.ts index 98ec8769..700de442 100644 --- a/src/shared/engine/combat-v6/equipment/art-refinement.test.ts +++ b/packages/shared/src/engine/combat-v6/equipment/art-refinement.test.ts @@ -1,4 +1,4 @@ -import { combatV6Units } from '@shared/combat-v6/presentation'; +import { combatV6Units } from '@daoyou/shared/combat-v6/presentation'; import { describe, expect, it } from 'vitest'; import { CommandType, diff --git a/src/shared/engine/combat-v6/equipment/compiler.ts b/packages/shared/src/engine/combat-v6/equipment/compiler.ts similarity index 99% rename from src/shared/engine/combat-v6/equipment/compiler.ts rename to packages/shared/src/engine/combat-v6/equipment/compiler.ts index deaa535c..bf5bb070 100644 --- a/src/shared/engine/combat-v6/equipment/compiler.ts +++ b/packages/shared/src/engine/combat-v6/equipment/compiler.ts @@ -1,7 +1,7 @@ -import { LEVELS_PER_REALM_STAGE } from '@shared/config/realmProgression'; -import { ELEMENT_VALUES } from '@shared/types/constants'; +import { LEVELS_PER_REALM_STAGE } from '@daoyou/shared/config/realmProgression'; +import { ELEMENT_VALUES } from '@daoyou/shared/types/constants'; import { equipmentRealm, isOpenEquipmentLevel } from './realm'; -import type { Attributes } from '@shared/types/cultivator'; +import type { Attributes } from '@daoyou/shared/types/cultivator'; import { EquipmentCrafterNameSchema, ForgedEquipmentDescSchema, diff --git a/src/shared/engine/combat-v6/equipment/content.ts b/packages/shared/src/engine/combat-v6/equipment/content.ts similarity index 100% rename from src/shared/engine/combat-v6/equipment/content.ts rename to packages/shared/src/engine/combat-v6/equipment/content.ts diff --git a/src/shared/engine/combat-v6/equipment/data/README-special-forging.md b/packages/shared/src/engine/combat-v6/equipment/data/README-special-forging.md similarity index 96% rename from src/shared/engine/combat-v6/equipment/data/README-special-forging.md rename to packages/shared/src/engine/combat-v6/equipment/data/README-special-forging.md index 15a99dc4..8374a69a 100644 --- a/src/shared/engine/combat-v6/equipment/data/README-special-forging.md +++ b/packages/shared/src/engine/combat-v6/equipment/data/README-special-forging.md @@ -26,7 +26,7 @@ ## 器诀配置 -完整清单、修仙名、数值及后续8种的证据缺口与确认适配见 [器诀精修规格](../../../../../../docs/combat-v6-equipment-arts-refinement.md)。现共38种(首批30种+后续8种),取代9种Demo器诀;没有旧装备,不新增器诀数据迁移。 +完整清单、修仙名、数值及后续8种的证据缺口与确认适配见 [器诀精修规格](../../../../../../../docs/combat-v6-equipment-arts-refinement.md)。现共38种(首批30种+后续8种),取代9种Demo器诀;没有旧装备,不新增器诀数据迁移。 `arts` 定义稳定ID、修仙名称、参数化 `description`、`skillId`、战意 `rageCost`、目标 `target` 和类型化 `effect`。目标支持自身、友方单体/全体、敌方单体/全体。原版名称与设计解释仅记录在精修规格文档中,不进入运行时配置;玩家界面使用修仙名称。 @@ -78,7 +78,7 @@ Demo器诀历史对照夹具已移除,当前38种器诀以精修数值与实战边界测试为准。G1 的 18 组生成摘要继续覆盖基础、特殊产出和打造流。修改数值属于平衡变更,不应只为测试通过而更新基线。 ```bash -bun run test src/shared/engine/combat-v6/equipment src/shared/forging/forging.test.ts +bun run test packages/shared/src/engine/combat-v6/equipment packages/shared/src/forging/forging.test.ts bun run lint bun run test bun run build @@ -98,7 +98,7 @@ bun run build ## 器诀本轮验证 -2026-09-13:lint、build、全量共享测试通过(165文件/1320测试),diff检查通过。桌面与360×800本地装备说明、穿戴/卸下已验收并清理样本。浏览器实战因现有测试角色未选宗门流派未开展,未变更其流派;机制与命令预检由纯引擎测试覆盖。完整记录见[器诀精修规格](../../../../../../docs/combat-v6-equipment-arts-refinement.md)。 +2026-09-13:lint、build、全量共享测试通过(165文件/1320测试),diff检查通过。桌面与360×800本地装备说明、穿戴/卸下已验收并清理样本。浏览器实战因现有测试角色未选宗门流派未开展,未变更其流派;机制与命令预检由纯引擎测试覆盖。完整记录见[器诀精修规格](../../../../../../../docs/combat-v6-equipment-arts-refinement.md)。 ## 后续8种配置说明 diff --git a/src/shared/engine/combat-v6/equipment/data/README.md b/packages/shared/src/engine/combat-v6/equipment/data/README.md similarity index 98% rename from src/shared/engine/combat-v6/equipment/data/README.md rename to packages/shared/src/engine/combat-v6/equipment/data/README.md index 673d62b8..150db7bb 100644 --- a/src/shared/engine/combat-v6/equipment/data/README.md +++ b/packages/shared/src/engine/combat-v6/equipment/data/README.md @@ -94,7 +94,7 @@ t = min(1, 平均超额品阶 ÷ 2) ## 验证 ```bash -bun run test src/shared/engine/combat-v6/equipment src/shared/forging src/shared/inventory src/shared/rewards +bun run test packages/shared/src/engine/combat-v6/equipment packages/shared/src/forging packages/shared/src/inventory packages/shared/src/rewards bun run lint bun run build ``` diff --git a/src/shared/engine/combat-v6/equipment/data/equipment-base.json b/packages/shared/src/engine/combat-v6/equipment/data/equipment-base.json similarity index 100% rename from src/shared/engine/combat-v6/equipment/data/equipment-base.json rename to packages/shared/src/engine/combat-v6/equipment/data/equipment-base.json diff --git a/src/shared/engine/combat-v6/equipment/data/equipment-base.schema.json b/packages/shared/src/engine/combat-v6/equipment/data/equipment-base.schema.json similarity index 100% rename from src/shared/engine/combat-v6/equipment/data/equipment-base.schema.json rename to packages/shared/src/engine/combat-v6/equipment/data/equipment-base.schema.json diff --git a/src/shared/engine/combat-v6/equipment/data/equipment-forging.json b/packages/shared/src/engine/combat-v6/equipment/data/equipment-forging.json similarity index 100% rename from src/shared/engine/combat-v6/equipment/data/equipment-forging.json rename to packages/shared/src/engine/combat-v6/equipment/data/equipment-forging.json diff --git a/src/shared/engine/combat-v6/equipment/data/equipment-forging.schema.json b/packages/shared/src/engine/combat-v6/equipment/data/equipment-forging.schema.json similarity index 100% rename from src/shared/engine/combat-v6/equipment/data/equipment-forging.schema.json rename to packages/shared/src/engine/combat-v6/equipment/data/equipment-forging.schema.json diff --git a/src/shared/engine/combat-v6/equipment/data/equipment-special.json b/packages/shared/src/engine/combat-v6/equipment/data/equipment-special.json similarity index 100% rename from src/shared/engine/combat-v6/equipment/data/equipment-special.json rename to packages/shared/src/engine/combat-v6/equipment/data/equipment-special.json diff --git a/src/shared/engine/combat-v6/equipment/data/equipment-special.schema.json b/packages/shared/src/engine/combat-v6/equipment/data/equipment-special.schema.json similarity index 100% rename from src/shared/engine/combat-v6/equipment/data/equipment-special.schema.json rename to packages/shared/src/engine/combat-v6/equipment/data/equipment-special.schema.json diff --git a/src/shared/engine/combat-v6/equipment/elements.test.ts b/packages/shared/src/engine/combat-v6/equipment/elements.test.ts similarity index 100% rename from src/shared/engine/combat-v6/equipment/elements.test.ts rename to packages/shared/src/engine/combat-v6/equipment/elements.test.ts diff --git a/src/shared/engine/combat-v6/equipment/equipment.test.ts b/packages/shared/src/engine/combat-v6/equipment/equipment.test.ts similarity index 100% rename from src/shared/engine/combat-v6/equipment/equipment.test.ts rename to packages/shared/src/engine/combat-v6/equipment/equipment.test.ts diff --git a/src/shared/engine/combat-v6/equipment/essence-refinement.test.ts b/packages/shared/src/engine/combat-v6/equipment/essence-refinement.test.ts similarity index 100% rename from src/shared/engine/combat-v6/equipment/essence-refinement.test.ts rename to packages/shared/src/engine/combat-v6/equipment/essence-refinement.test.ts diff --git a/src/shared/engine/combat-v6/equipment/forging-content.ts b/packages/shared/src/engine/combat-v6/equipment/forging-content.ts similarity index 100% rename from src/shared/engine/combat-v6/equipment/forging-content.ts rename to packages/shared/src/engine/combat-v6/equipment/forging-content.ts diff --git a/src/shared/engine/combat-v6/equipment/forging-pack.test.ts b/packages/shared/src/engine/combat-v6/equipment/forging-pack.test.ts similarity index 100% rename from src/shared/engine/combat-v6/equipment/forging-pack.test.ts rename to packages/shared/src/engine/combat-v6/equipment/forging-pack.test.ts diff --git a/src/shared/engine/combat-v6/equipment/forging-pack.ts b/packages/shared/src/engine/combat-v6/equipment/forging-pack.ts similarity index 100% rename from src/shared/engine/combat-v6/equipment/forging-pack.ts rename to packages/shared/src/engine/combat-v6/equipment/forging-pack.ts diff --git a/src/shared/engine/combat-v6/equipment/forging.ts b/packages/shared/src/engine/combat-v6/equipment/forging.ts similarity index 100% rename from src/shared/engine/combat-v6/equipment/forging.ts rename to packages/shared/src/engine/combat-v6/equipment/forging.ts diff --git a/src/shared/engine/combat-v6/equipment/generation-baseline.test.ts b/packages/shared/src/engine/combat-v6/equipment/generation-baseline.test.ts similarity index 100% rename from src/shared/engine/combat-v6/equipment/generation-baseline.test.ts rename to packages/shared/src/engine/combat-v6/equipment/generation-baseline.test.ts diff --git a/src/shared/engine/combat-v6/equipment/generator.ts b/packages/shared/src/engine/combat-v6/equipment/generator.ts similarity index 100% rename from src/shared/engine/combat-v6/equipment/generator.ts rename to packages/shared/src/engine/combat-v6/equipment/generator.ts diff --git a/src/shared/engine/combat-v6/equipment/index.ts b/packages/shared/src/engine/combat-v6/equipment/index.ts similarity index 100% rename from src/shared/engine/combat-v6/equipment/index.ts rename to packages/shared/src/engine/combat-v6/equipment/index.ts diff --git a/src/shared/engine/combat-v6/equipment/inscriptions.test.ts b/packages/shared/src/engine/combat-v6/equipment/inscriptions.test.ts similarity index 100% rename from src/shared/engine/combat-v6/equipment/inscriptions.test.ts rename to packages/shared/src/engine/combat-v6/equipment/inscriptions.test.ts diff --git a/src/shared/engine/combat-v6/equipment/inscriptions.ts b/packages/shared/src/engine/combat-v6/equipment/inscriptions.ts similarity index 100% rename from src/shared/engine/combat-v6/equipment/inscriptions.ts rename to packages/shared/src/engine/combat-v6/equipment/inscriptions.ts diff --git a/src/shared/engine/combat-v6/equipment/numeric-refinement.test.ts b/packages/shared/src/engine/combat-v6/equipment/numeric-refinement.test.ts similarity index 100% rename from src/shared/engine/combat-v6/equipment/numeric-refinement.test.ts rename to packages/shared/src/engine/combat-v6/equipment/numeric-refinement.test.ts diff --git a/src/shared/engine/combat-v6/equipment/pack.test.ts b/packages/shared/src/engine/combat-v6/equipment/pack.test.ts similarity index 100% rename from src/shared/engine/combat-v6/equipment/pack.test.ts rename to packages/shared/src/engine/combat-v6/equipment/pack.test.ts diff --git a/src/shared/engine/combat-v6/equipment/pack.ts b/packages/shared/src/engine/combat-v6/equipment/pack.ts similarity index 100% rename from src/shared/engine/combat-v6/equipment/pack.ts rename to packages/shared/src/engine/combat-v6/equipment/pack.ts diff --git a/src/shared/engine/combat-v6/equipment/realm.test.ts b/packages/shared/src/engine/combat-v6/equipment/realm.test.ts similarity index 92% rename from src/shared/engine/combat-v6/equipment/realm.test.ts rename to packages/shared/src/engine/combat-v6/equipment/realm.test.ts index d01f657e..14640aec 100644 --- a/src/shared/engine/combat-v6/equipment/realm.test.ts +++ b/packages/shared/src/engine/combat-v6/equipment/realm.test.ts @@ -1,6 +1,6 @@ -import { getRealmStageLevel } from '@shared/config/realmProgression'; -import { forgingBoosts } from '@shared/forging/rules'; -import { REALM_VALUES } from '@shared/types/constants'; +import { getRealmStageLevel } from '@daoyou/shared/config/realmProgression'; +import { forgingBoosts } from '@daoyou/shared/forging/rules'; +import { REALM_VALUES } from '@daoyou/shared/types/constants'; import { describe, expect, it } from 'vitest'; import { compileDaoEquipmentLoadoutV1, diff --git a/src/shared/engine/combat-v6/equipment/realm.ts b/packages/shared/src/engine/combat-v6/equipment/realm.ts similarity index 95% rename from src/shared/engine/combat-v6/equipment/realm.ts rename to packages/shared/src/engine/combat-v6/equipment/realm.ts index 3baf3cce..9e18de42 100644 --- a/src/shared/engine/combat-v6/equipment/realm.ts +++ b/packages/shared/src/engine/combat-v6/equipment/realm.ts @@ -1,7 +1,7 @@ import { getLevelRealmStage, getRealmStageLevel, -} from '@shared/config/realmProgression'; +} from '@daoyou/shared/config/realmProgression'; /** 器阶只决定属性档位;同一大境界的道装均在初期开放。 */ export function equipmentRealm(level: number) { diff --git a/src/shared/engine/combat-v6/equipment/special-compiler.ts b/packages/shared/src/engine/combat-v6/equipment/special-compiler.ts similarity index 100% rename from src/shared/engine/combat-v6/equipment/special-compiler.ts rename to packages/shared/src/engine/combat-v6/equipment/special-compiler.ts diff --git a/src/shared/engine/combat-v6/equipment/special-content.ts b/packages/shared/src/engine/combat-v6/equipment/special-content.ts similarity index 100% rename from src/shared/engine/combat-v6/equipment/special-content.ts rename to packages/shared/src/engine/combat-v6/equipment/special-content.ts diff --git a/src/shared/engine/combat-v6/equipment/special-ids.ts b/packages/shared/src/engine/combat-v6/equipment/special-ids.ts similarity index 100% rename from src/shared/engine/combat-v6/equipment/special-ids.ts rename to packages/shared/src/engine/combat-v6/equipment/special-ids.ts diff --git a/src/shared/engine/combat-v6/equipment/special-pack.test.ts b/packages/shared/src/engine/combat-v6/equipment/special-pack.test.ts similarity index 100% rename from src/shared/engine/combat-v6/equipment/special-pack.test.ts rename to packages/shared/src/engine/combat-v6/equipment/special-pack.test.ts diff --git a/src/shared/engine/combat-v6/equipment/special-pack.ts b/packages/shared/src/engine/combat-v6/equipment/special-pack.ts similarity index 100% rename from src/shared/engine/combat-v6/equipment/special-pack.ts rename to packages/shared/src/engine/combat-v6/equipment/special-pack.ts diff --git a/src/shared/engine/combat-v6/equipment/special-runtime.test.ts b/packages/shared/src/engine/combat-v6/equipment/special-runtime.test.ts similarity index 100% rename from src/shared/engine/combat-v6/equipment/special-runtime.test.ts rename to packages/shared/src/engine/combat-v6/equipment/special-runtime.test.ts diff --git a/src/shared/engine/combat-v6/equipment/types.ts b/packages/shared/src/engine/combat-v6/equipment/types.ts similarity index 97% rename from src/shared/engine/combat-v6/equipment/types.ts rename to packages/shared/src/engine/combat-v6/equipment/types.ts index cc4fcb3e..25249114 100644 --- a/src/shared/engine/combat-v6/equipment/types.ts +++ b/packages/shared/src/engine/combat-v6/equipment/types.ts @@ -1,5 +1,5 @@ -import type { Attributes } from '@shared/types/cultivator'; -import type { ElementType } from '@shared/types/constants'; +import type { Attributes } from '@daoyou/shared/types/cultivator'; +import type { ElementType } from '@daoyou/shared/types/constants'; import type { SkillDef, StatusDef } from '../core/index.ts'; import type { DaoWeaponType } from './weapons'; import type { diff --git a/src/shared/engine/combat-v6/equipment/weapons.test.ts b/packages/shared/src/engine/combat-v6/equipment/weapons.test.ts similarity index 100% rename from src/shared/engine/combat-v6/equipment/weapons.test.ts rename to packages/shared/src/engine/combat-v6/equipment/weapons.test.ts diff --git a/src/shared/engine/combat-v6/equipment/weapons.ts b/packages/shared/src/engine/combat-v6/equipment/weapons.ts similarity index 100% rename from src/shared/engine/combat-v6/equipment/weapons.ts rename to packages/shared/src/engine/combat-v6/equipment/weapons.ts diff --git a/src/shared/engine/combat-v6/hunts/balance.test.ts b/packages/shared/src/engine/combat-v6/hunts/balance.test.ts similarity index 100% rename from src/shared/engine/combat-v6/hunts/balance.test.ts rename to packages/shared/src/engine/combat-v6/hunts/balance.test.ts diff --git a/src/shared/engine/combat-v6/hunts/balance.ts b/packages/shared/src/engine/combat-v6/hunts/balance.ts similarity index 100% rename from src/shared/engine/combat-v6/hunts/balance.ts rename to packages/shared/src/engine/combat-v6/hunts/balance.ts diff --git a/src/shared/engine/combat-v6/hunts/content.test.ts b/packages/shared/src/engine/combat-v6/hunts/content.test.ts similarity index 100% rename from src/shared/engine/combat-v6/hunts/content.test.ts rename to packages/shared/src/engine/combat-v6/hunts/content.test.ts diff --git a/src/shared/engine/combat-v6/hunts/content.ts b/packages/shared/src/engine/combat-v6/hunts/content.ts similarity index 100% rename from src/shared/engine/combat-v6/hunts/content.ts rename to packages/shared/src/engine/combat-v6/hunts/content.ts diff --git a/src/shared/engine/combat-v6/index.ts b/packages/shared/src/engine/combat-v6/index.ts similarity index 100% rename from src/shared/engine/combat-v6/index.ts rename to packages/shared/src/engine/combat-v6/index.ts diff --git a/src/shared/engine/combat-v6/manuals/attributes.ts b/packages/shared/src/engine/combat-v6/manuals/attributes.ts similarity index 100% rename from src/shared/engine/combat-v6/manuals/attributes.ts rename to packages/shared/src/engine/combat-v6/manuals/attributes.ts diff --git a/src/shared/engine/combat-v6/manuals/capabilities.ts b/packages/shared/src/engine/combat-v6/manuals/capabilities.ts similarity index 100% rename from src/shared/engine/combat-v6/manuals/capabilities.ts rename to packages/shared/src/engine/combat-v6/manuals/capabilities.ts diff --git a/src/shared/engine/combat-v6/manuals/compiler.ts b/packages/shared/src/engine/combat-v6/manuals/compiler.ts similarity index 98% rename from src/shared/engine/combat-v6/manuals/compiler.ts rename to packages/shared/src/engine/combat-v6/manuals/compiler.ts index 849b2b47..5bcd23e4 100644 --- a/src/shared/engine/combat-v6/manuals/compiler.ts +++ b/packages/shared/src/engine/combat-v6/manuals/compiler.ts @@ -1,4 +1,4 @@ -import { REALM_VALUES, type RealmType } from '@shared/types/constants'; +import { REALM_VALUES, type RealmType } from '@daoyou/shared/types/constants'; import type { CombatV6ProjectionDiagnostic, CultivatorBaseCombatInput, diff --git a/src/shared/engine/combat-v6/manuals/content.ts b/packages/shared/src/engine/combat-v6/manuals/content.ts similarity index 100% rename from src/shared/engine/combat-v6/manuals/content.ts rename to packages/shared/src/engine/combat-v6/manuals/content.ts diff --git a/src/shared/engine/combat-v6/manuals/data/manual-pack.json b/packages/shared/src/engine/combat-v6/manuals/data/manual-pack.json similarity index 100% rename from src/shared/engine/combat-v6/manuals/data/manual-pack.json rename to packages/shared/src/engine/combat-v6/manuals/data/manual-pack.json diff --git a/src/shared/engine/combat-v6/manuals/data/manual-pack.schema.json b/packages/shared/src/engine/combat-v6/manuals/data/manual-pack.schema.json similarity index 100% rename from src/shared/engine/combat-v6/manuals/data/manual-pack.schema.json rename to packages/shared/src/engine/combat-v6/manuals/data/manual-pack.schema.json diff --git a/src/shared/engine/combat-v6/manuals/index.ts b/packages/shared/src/engine/combat-v6/manuals/index.ts similarity index 100% rename from src/shared/engine/combat-v6/manuals/index.ts rename to packages/shared/src/engine/combat-v6/manuals/index.ts diff --git a/src/shared/engine/combat-v6/manuals/learning-limit.test.ts b/packages/shared/src/engine/combat-v6/manuals/learning-limit.test.ts similarity index 100% rename from src/shared/engine/combat-v6/manuals/learning-limit.test.ts rename to packages/shared/src/engine/combat-v6/manuals/learning-limit.test.ts diff --git a/src/shared/engine/combat-v6/manuals/manuals.test.ts b/packages/shared/src/engine/combat-v6/manuals/manuals.test.ts similarity index 100% rename from src/shared/engine/combat-v6/manuals/manuals.test.ts rename to packages/shared/src/engine/combat-v6/manuals/manuals.test.ts diff --git a/src/shared/engine/combat-v6/manuals/mechanism.test.ts b/packages/shared/src/engine/combat-v6/manuals/mechanism.test.ts similarity index 100% rename from src/shared/engine/combat-v6/manuals/mechanism.test.ts rename to packages/shared/src/engine/combat-v6/manuals/mechanism.test.ts diff --git a/src/shared/engine/combat-v6/manuals/mechanism.ts b/packages/shared/src/engine/combat-v6/manuals/mechanism.ts similarity index 100% rename from src/shared/engine/combat-v6/manuals/mechanism.ts rename to packages/shared/src/engine/combat-v6/manuals/mechanism.ts diff --git a/src/shared/engine/combat-v6/manuals/pack.ts b/packages/shared/src/engine/combat-v6/manuals/pack.ts similarity index 100% rename from src/shared/engine/combat-v6/manuals/pack.ts rename to packages/shared/src/engine/combat-v6/manuals/pack.ts diff --git a/src/shared/engine/combat-v6/manuals/presentation.ts b/packages/shared/src/engine/combat-v6/manuals/presentation.ts similarity index 96% rename from src/shared/engine/combat-v6/manuals/presentation.ts rename to packages/shared/src/engine/combat-v6/manuals/presentation.ts index 2b19379c..8adc8c0f 100644 --- a/src/shared/engine/combat-v6/manuals/presentation.ts +++ b/packages/shared/src/engine/combat-v6/manuals/presentation.ts @@ -1,4 +1,4 @@ -import { CHARACTER_ATTRIBUTE_LABELS } from '@shared/lib/characterAttributeLabels'; +import { CHARACTER_ATTRIBUTE_LABELS } from '@daoyou/shared/lib/characterAttributeLabels'; import { manualAttributeValue } from './attributes'; import { manualMechanismValue } from './mechanism'; import type { ManualMechanism } from './pack'; diff --git a/src/shared/engine/combat-v6/manuals/state.ts b/packages/shared/src/engine/combat-v6/manuals/state.ts similarity index 98% rename from src/shared/engine/combat-v6/manuals/state.ts rename to packages/shared/src/engine/combat-v6/manuals/state.ts index 2afdecad..bba99576 100644 --- a/src/shared/engine/combat-v6/manuals/state.ts +++ b/packages/shared/src/engine/combat-v6/manuals/state.ts @@ -1,4 +1,4 @@ -import type { RealmType } from '@shared/types/constants'; +import type { RealmType } from '@daoyou/shared/types/constants'; import { getManualSlotCount, manualSlot, diff --git a/src/shared/engine/combat-v6/manuals/types.ts b/packages/shared/src/engine/combat-v6/manuals/types.ts similarity index 93% rename from src/shared/engine/combat-v6/manuals/types.ts rename to packages/shared/src/engine/combat-v6/manuals/types.ts index 537085b1..910902c4 100644 --- a/src/shared/engine/combat-v6/manuals/types.ts +++ b/packages/shared/src/engine/combat-v6/manuals/types.ts @@ -37,7 +37,7 @@ export interface CharacterManualDefV1 { progressionId: string; mechanism: import('./pack').ManualMechanism; effects: Array<{ - attribute: keyof import('@shared/types/cultivator').Attributes; + attribute: keyof import('@daoyou/shared/types/cultivator').Attributes; valueAt1: number; valuePerLevel: number; }>; @@ -45,7 +45,7 @@ export interface CharacterManualDefV1 { } export interface CharacterManualProjectionV1 { - attributeBonuses: Partial; + attributeBonuses: Partial; skills: SkillDef[]; passiveSkillIds: string[]; skillLevels: Record; diff --git a/src/shared/engine/combat-v6/projection/assembly-baseline.test.ts b/packages/shared/src/engine/combat-v6/projection/assembly-baseline.test.ts similarity index 100% rename from src/shared/engine/combat-v6/projection/assembly-baseline.test.ts rename to packages/shared/src/engine/combat-v6/projection/assembly-baseline.test.ts diff --git a/src/shared/engine/combat-v6/projection/body-cultivation-v6.ts b/packages/shared/src/engine/combat-v6/projection/body-cultivation-v6.ts similarity index 82% rename from src/shared/engine/combat-v6/projection/body-cultivation-v6.ts rename to packages/shared/src/engine/combat-v6/projection/body-cultivation-v6.ts index 0692658f..4f9a3093 100644 --- a/src/shared/engine/combat-v6/projection/body-cultivation-v6.ts +++ b/packages/shared/src/engine/combat-v6/projection/body-cultivation-v6.ts @@ -1,7 +1,7 @@ -import { BODY_CULTIVATION_TRACK_KEYS } from "@shared/lib/bodyCultivation/config" -import { BODY_CULTIVATION_PACK, bodyTrainingLevelCap } from "@shared/lib/bodyCultivation/pack" -import { bodyCultivationBenefits } from "@shared/lib/bodyCultivation/benefits" -import type { BodyCultivationTrackKey } from "@shared/types/condition" +import { BODY_CULTIVATION_TRACK_KEYS } from "@daoyou/shared/lib/bodyCultivation/config" +import { BODY_CULTIVATION_PACK, bodyTrainingLevelCap } from "@daoyou/shared/lib/bodyCultivation/pack" +import { bodyCultivationBenefits } from "@daoyou/shared/lib/bodyCultivation/benefits" +import type { BodyCultivationTrackKey } from "@daoyou/shared/types/condition" import type { CharacterPanelV1 } from "./character-panel-v1.ts" import type { CombatV6BodyCultivationInput, diff --git a/src/shared/engine/combat-v6/projection/character-battle-skills.ts b/packages/shared/src/engine/combat-v6/projection/character-battle-skills.ts similarity index 100% rename from src/shared/engine/combat-v6/projection/character-battle-skills.ts rename to packages/shared/src/engine/combat-v6/projection/character-battle-skills.ts diff --git a/packages/shared/src/engine/combat-v6/projection/character-level.ts b/packages/shared/src/engine/combat-v6/projection/character-level.ts new file mode 100644 index 00000000..88411cd4 --- /dev/null +++ b/packages/shared/src/engine/combat-v6/projection/character-level.ts @@ -0,0 +1,2 @@ +// Combat and permanent attribute budgets use the same realm-to-level mapping. +export { getRealmStageLevel as combatCharacterLevel } from '@daoyou/shared/config/realmProgression'; diff --git a/src/shared/engine/combat-v6/projection/character-panel-v1.ts b/packages/shared/src/engine/combat-v6/projection/character-panel-v1.ts similarity index 95% rename from src/shared/engine/combat-v6/projection/character-panel-v1.ts rename to packages/shared/src/engine/combat-v6/projection/character-panel-v1.ts index 4860aeb4..7b807629 100644 --- a/src/shared/engine/combat-v6/projection/character-panel-v1.ts +++ b/packages/shared/src/engine/combat-v6/projection/character-panel-v1.ts @@ -1,4 +1,4 @@ -import type { Attributes } from "@shared/types/cultivator" +import type { Attributes } from "@daoyou/shared/types/cultivator" export interface CharacterPanelV1 { physicalAtk: number diff --git a/src/shared/engine/combat-v6/projection/compose-character-manuals.ts b/packages/shared/src/engine/combat-v6/projection/compose-character-manuals.ts similarity index 100% rename from src/shared/engine/combat-v6/projection/compose-character-manuals.ts rename to packages/shared/src/engine/combat-v6/projection/compose-character-manuals.ts diff --git a/src/shared/engine/combat-v6/projection/fixtures/before-a1.json b/packages/shared/src/engine/combat-v6/projection/fixtures/before-a1.json similarity index 100% rename from src/shared/engine/combat-v6/projection/fixtures/before-a1.json rename to packages/shared/src/engine/combat-v6/projection/fixtures/before-a1.json diff --git a/src/shared/engine/combat-v6/projection/index.ts b/packages/shared/src/engine/combat-v6/projection/index.ts similarity index 100% rename from src/shared/engine/combat-v6/projection/index.ts rename to packages/shared/src/engine/combat-v6/projection/index.ts diff --git a/src/shared/engine/combat-v6/projection/inscriptions.test.ts b/packages/shared/src/engine/combat-v6/projection/inscriptions.test.ts similarity index 100% rename from src/shared/engine/combat-v6/projection/inscriptions.test.ts rename to packages/shared/src/engine/combat-v6/projection/inscriptions.test.ts diff --git a/src/shared/engine/combat-v6/projection/meridian-equipment.test.ts b/packages/shared/src/engine/combat-v6/projection/meridian-equipment.test.ts similarity index 100% rename from src/shared/engine/combat-v6/projection/meridian-equipment.test.ts rename to packages/shared/src/engine/combat-v6/projection/meridian-equipment.test.ts diff --git a/src/shared/engine/combat-v6/projection/project-character.ts b/packages/shared/src/engine/combat-v6/projection/project-character.ts similarity index 100% rename from src/shared/engine/combat-v6/projection/project-character.ts rename to packages/shared/src/engine/combat-v6/projection/project-character.ts diff --git a/src/shared/engine/combat-v6/projection/project-cultivator-base.ts b/packages/shared/src/engine/combat-v6/projection/project-cultivator-base.ts similarity index 97% rename from src/shared/engine/combat-v6/projection/project-cultivator-base.ts rename to packages/shared/src/engine/combat-v6/projection/project-cultivator-base.ts index 84cf5587..6fffb4a5 100644 --- a/src/shared/engine/combat-v6/projection/project-cultivator-base.ts +++ b/packages/shared/src/engine/combat-v6/projection/project-cultivator-base.ts @@ -1,5 +1,5 @@ -import { REALM_STAGE_VALUES, REALM_VALUES } from '@shared/types/constants'; -import type { Attributes } from '@shared/types/cultivator'; +import { REALM_STAGE_VALUES, REALM_VALUES } from '@daoyou/shared/types/constants'; +import type { Attributes } from '@daoyou/shared/types/cultivator'; import { UnitKind, type LineupUnit } from '../core/index.ts'; import { COMBAT_V6_PHASE_1_VERSIONS } from '../version.ts'; import { combatCharacterLevel } from './character-level'; diff --git a/src/shared/engine/combat-v6/projection/project-cultivator-multi-sect-v3.ts b/packages/shared/src/engine/combat-v6/projection/project-cultivator-multi-sect-v3.ts similarity index 100% rename from src/shared/engine/combat-v6/projection/project-cultivator-multi-sect-v3.ts rename to packages/shared/src/engine/combat-v6/projection/project-cultivator-multi-sect-v3.ts diff --git a/src/shared/engine/combat-v6/projection/project-cultivator-multi-sect-v4.ts b/packages/shared/src/engine/combat-v6/projection/project-cultivator-multi-sect-v4.ts similarity index 100% rename from src/shared/engine/combat-v6/projection/project-cultivator-multi-sect-v4.ts rename to packages/shared/src/engine/combat-v6/projection/project-cultivator-multi-sect-v4.ts diff --git a/src/shared/engine/combat-v6/projection/project-cultivator-multi-sect-v5.ts b/packages/shared/src/engine/combat-v6/projection/project-cultivator-multi-sect-v5.ts similarity index 100% rename from src/shared/engine/combat-v6/projection/project-cultivator-multi-sect-v5.ts rename to packages/shared/src/engine/combat-v6/projection/project-cultivator-multi-sect-v5.ts diff --git a/src/shared/engine/combat-v6/projection/project-cultivator-multi-sect.ts b/packages/shared/src/engine/combat-v6/projection/project-cultivator-multi-sect.ts similarity index 100% rename from src/shared/engine/combat-v6/projection/project-cultivator-multi-sect.ts rename to packages/shared/src/engine/combat-v6/projection/project-cultivator-multi-sect.ts diff --git a/src/shared/engine/combat-v6/projection/project-cultivator-with-equipment-special.ts b/packages/shared/src/engine/combat-v6/projection/project-cultivator-with-equipment-special.ts similarity index 99% rename from src/shared/engine/combat-v6/projection/project-cultivator-with-equipment-special.ts rename to packages/shared/src/engine/combat-v6/projection/project-cultivator-with-equipment-special.ts index 947c0a80..755a94fb 100644 --- a/src/shared/engine/combat-v6/projection/project-cultivator-with-equipment-special.ts +++ b/packages/shared/src/engine/combat-v6/projection/project-cultivator-with-equipment-special.ts @@ -1,4 +1,4 @@ -import type { Attributes } from "@shared/types/cultivator" +import type { Attributes } from "@daoyou/shared/types/cultivator" import { DAO_RAGE_RESOURCE } from "../equipment/special-content.ts" import { compileSectCombatV6, compileSectCombatV6V2, compileSectCombatV6V3, compileCurrentSectCombatV6 } from "../content/index.ts" import { ATTR_NAMES, type AttrName, type CombatV6VersionStamp, type LineupUnit } from "../core/index.ts" diff --git a/src/shared/engine/combat-v6/projection/project-cultivator-with-equipment.ts b/packages/shared/src/engine/combat-v6/projection/project-cultivator-with-equipment.ts similarity index 99% rename from src/shared/engine/combat-v6/projection/project-cultivator-with-equipment.ts rename to packages/shared/src/engine/combat-v6/projection/project-cultivator-with-equipment.ts index 903f3a27..a9129b99 100644 --- a/src/shared/engine/combat-v6/projection/project-cultivator-with-equipment.ts +++ b/packages/shared/src/engine/combat-v6/projection/project-cultivator-with-equipment.ts @@ -1,4 +1,4 @@ -import type { Attributes } from "@shared/types/cultivator" +import type { Attributes } from "@daoyou/shared/types/cultivator" import { compileSectCombatV6 } from "../content/index.ts" import { ATTR_NAMES, diff --git a/src/shared/engine/combat-v6/projection/project-cultivator-with-sect.ts b/packages/shared/src/engine/combat-v6/projection/project-cultivator-with-sect.ts similarity index 100% rename from src/shared/engine/combat-v6/projection/project-cultivator-with-sect.ts rename to packages/shared/src/engine/combat-v6/projection/project-cultivator-with-sect.ts diff --git a/src/shared/engine/combat-v6/projection/project-cultivator-with-training.ts b/packages/shared/src/engine/combat-v6/projection/project-cultivator-with-training.ts similarity index 100% rename from src/shared/engine/combat-v6/projection/project-cultivator-with-training.ts rename to packages/shared/src/engine/combat-v6/projection/project-cultivator-with-training.ts diff --git a/src/shared/engine/combat-v6/projection/project-cultivator.ts b/packages/shared/src/engine/combat-v6/projection/project-cultivator.ts similarity index 100% rename from src/shared/engine/combat-v6/projection/project-cultivator.ts rename to packages/shared/src/engine/combat-v6/projection/project-cultivator.ts diff --git a/src/shared/engine/combat-v6/projection/projection.test.ts b/packages/shared/src/engine/combat-v6/projection/projection.test.ts similarity index 98% rename from src/shared/engine/combat-v6/projection/projection.test.ts rename to packages/shared/src/engine/combat-v6/projection/projection.test.ts index 397afefb..961e452f 100644 --- a/src/shared/engine/combat-v6/projection/projection.test.ts +++ b/packages/shared/src/engine/combat-v6/projection/projection.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest" -import type { Attributes } from "@shared/types/cultivator" -import type { CultivatorCondition } from "@shared/types/condition" +import type { Attributes } from "@daoyou/shared/types/cultivator" +import type { CultivatorCondition } from "@daoyou/shared/types/condition" import { compileCharacterPanelV1, compileBodyCultivationV6, diff --git a/src/shared/engine/combat-v6/projection/types.ts b/packages/shared/src/engine/combat-v6/projection/types.ts similarity index 96% rename from src/shared/engine/combat-v6/projection/types.ts rename to packages/shared/src/engine/combat-v6/projection/types.ts index cf4ed922..c8d491a5 100644 --- a/src/shared/engine/combat-v6/projection/types.ts +++ b/packages/shared/src/engine/combat-v6/projection/types.ts @@ -1,7 +1,7 @@ -import type { Attributes } from "@shared/types/cultivator" -import type { CultivatorCondition } from "@shared/types/condition" -import type { BodyCultivationState } from "@shared/types/condition" -import type { RealmStage, RealmType } from "@shared/types/constants" +import type { Attributes } from "@daoyou/shared/types/cultivator" +import type { CultivatorCondition } from "@daoyou/shared/types/condition" +import type { BodyCultivationState } from "@daoyou/shared/types/condition" +import type { RealmStage, RealmType } from "@daoyou/shared/types/constants" import type { AttrName, CombatV6VersionStamp, diff --git a/src/shared/engine/combat-v6/projection/weapon-types.test.ts b/packages/shared/src/engine/combat-v6/projection/weapon-types.test.ts similarity index 100% rename from src/shared/engine/combat-v6/projection/weapon-types.test.ts rename to packages/shared/src/engine/combat-v6/projection/weapon-types.test.ts diff --git a/src/shared/engine/combat-v6/ranking/battle.test.ts b/packages/shared/src/engine/combat-v6/ranking/battle.test.ts similarity index 100% rename from src/shared/engine/combat-v6/ranking/battle.test.ts rename to packages/shared/src/engine/combat-v6/ranking/battle.test.ts diff --git a/src/shared/engine/combat-v6/ranking/battle.ts b/packages/shared/src/engine/combat-v6/ranking/battle.ts similarity index 100% rename from src/shared/engine/combat-v6/ranking/battle.ts rename to packages/shared/src/engine/combat-v6/ranking/battle.ts diff --git a/src/shared/engine/combat-v6/rules-daoyou/constants.ts b/packages/shared/src/engine/combat-v6/rules-daoyou/constants.ts similarity index 100% rename from src/shared/engine/combat-v6/rules-daoyou/constants.ts rename to packages/shared/src/engine/combat-v6/rules-daoyou/constants.ts diff --git a/src/shared/engine/combat-v6/rules-daoyou/formulas.ts b/packages/shared/src/engine/combat-v6/rules-daoyou/formulas.ts similarity index 100% rename from src/shared/engine/combat-v6/rules-daoyou/formulas.ts rename to packages/shared/src/engine/combat-v6/rules-daoyou/formulas.ts diff --git a/src/shared/engine/combat-v6/rules-daoyou/index.ts b/packages/shared/src/engine/combat-v6/rules-daoyou/index.ts similarity index 100% rename from src/shared/engine/combat-v6/rules-daoyou/index.ts rename to packages/shared/src/engine/combat-v6/rules-daoyou/index.ts diff --git a/src/shared/engine/combat-v6/rules-daoyou/rules-daoyou.test.ts b/packages/shared/src/engine/combat-v6/rules-daoyou/rules-daoyou.test.ts similarity index 100% rename from src/shared/engine/combat-v6/rules-daoyou/rules-daoyou.test.ts rename to packages/shared/src/engine/combat-v6/rules-daoyou/rules-daoyou.test.ts diff --git a/src/shared/engine/combat-v6/sect-progression/data/README.md b/packages/shared/src/engine/combat-v6/sect-progression/data/README.md similarity index 100% rename from src/shared/engine/combat-v6/sect-progression/data/README.md rename to packages/shared/src/engine/combat-v6/sect-progression/data/README.md diff --git a/src/shared/engine/combat-v6/sect-progression/data/progression.json b/packages/shared/src/engine/combat-v6/sect-progression/data/progression.json similarity index 100% rename from src/shared/engine/combat-v6/sect-progression/data/progression.json rename to packages/shared/src/engine/combat-v6/sect-progression/data/progression.json diff --git a/src/shared/engine/combat-v6/sect-progression/data/progression.schema.json b/packages/shared/src/engine/combat-v6/sect-progression/data/progression.schema.json similarity index 100% rename from src/shared/engine/combat-v6/sect-progression/data/progression.schema.json rename to packages/shared/src/engine/combat-v6/sect-progression/data/progression.schema.json diff --git a/src/shared/engine/combat-v6/sect-progression/index.ts b/packages/shared/src/engine/combat-v6/sect-progression/index.ts similarity index 97% rename from src/shared/engine/combat-v6/sect-progression/index.ts rename to packages/shared/src/engine/combat-v6/sect-progression/index.ts index 62b43d0e..d3077a39 100644 --- a/src/shared/engine/combat-v6/sect-progression/index.ts +++ b/packages/shared/src/engine/combat-v6/sect-progression/index.ts @@ -1,7 +1,7 @@ import { canSelectMeridianNode, normalizeMeridianSelection } from '../content/meridian-selection'; -import { getLevelRealmStage } from '@shared/config/realmProgression'; +import { getLevelRealmStage } from '@daoyou/shared/config/realmProgression'; import { SECT_PROGRESSION, configuredMethodCost, configuredMeridianCost, methodLevelCap } from './pack'; -import type { SectV6Action, SectV6Cost } from '@shared/contracts/combatV6Sect'; +import type { SectV6Action, SectV6Cost } from '@daoyou/shared/contracts/combatV6Sect'; import { COMBAT_V6_SECT_DEFINITIONS, compileCurrentSectCombatV6, diff --git a/src/shared/engine/combat-v6/sect-progression/pack.test.ts b/packages/shared/src/engine/combat-v6/sect-progression/pack.test.ts similarity index 100% rename from src/shared/engine/combat-v6/sect-progression/pack.test.ts rename to packages/shared/src/engine/combat-v6/sect-progression/pack.test.ts diff --git a/src/shared/engine/combat-v6/sect-progression/pack.ts b/packages/shared/src/engine/combat-v6/sect-progression/pack.ts similarity index 97% rename from src/shared/engine/combat-v6/sect-progression/pack.ts rename to packages/shared/src/engine/combat-v6/sect-progression/pack.ts index 98184a6d..304f76fb 100644 --- a/src/shared/engine/combat-v6/sect-progression/pack.ts +++ b/packages/shared/src/engine/combat-v6/sect-progression/pack.ts @@ -1,4 +1,4 @@ -import { formatContentPackErrors } from '@shared/lib/content-pack-errors'; +import { formatContentPackErrors } from '@daoyou/shared/lib/content-pack-errors'; import { z } from 'zod'; import raw from './data/progression.json'; diff --git a/src/shared/engine/combat-v6/sect-progression/presentation.ts b/packages/shared/src/engine/combat-v6/sect-progression/presentation.ts similarity index 97% rename from src/shared/engine/combat-v6/sect-progression/presentation.ts rename to packages/shared/src/engine/combat-v6/sect-progression/presentation.ts index 993ffb5f..bb685a16 100644 --- a/src/shared/engine/combat-v6/sect-progression/presentation.ts +++ b/packages/shared/src/engine/combat-v6/sect-progression/presentation.ts @@ -1,4 +1,4 @@ -import { combatV6SkillDetails } from '@shared/combat-v6/skill-details'; +import { combatV6SkillDetails } from '@daoyou/shared/combat-v6/skill-details'; import { COMBAT_V6_SECT_DEFINITIONS, compileCurrentSectCombatV6, diff --git a/src/shared/engine/combat-v6/sect-progression/progression.test.ts b/packages/shared/src/engine/combat-v6/sect-progression/progression.test.ts similarity index 100% rename from src/shared/engine/combat-v6/sect-progression/progression.test.ts rename to packages/shared/src/engine/combat-v6/sect-progression/progression.test.ts diff --git a/src/shared/engine/combat-v6/sect/host.ts b/packages/shared/src/engine/combat-v6/sect/host.ts similarity index 100% rename from src/shared/engine/combat-v6/sect/host.ts rename to packages/shared/src/engine/combat-v6/sect/host.ts diff --git a/src/shared/engine/combat-v6/sect/sect.test.ts b/packages/shared/src/engine/combat-v6/sect/sect.test.ts similarity index 98% rename from src/shared/engine/combat-v6/sect/sect.test.ts rename to packages/shared/src/engine/combat-v6/sect/sect.test.ts index d5a3ff8e..f1869e50 100644 --- a/src/shared/engine/combat-v6/sect/sect.test.ts +++ b/packages/shared/src/engine/combat-v6/sect/sect.test.ts @@ -1,5 +1,5 @@ -import { SectV6TargetSchema } from '@shared/contracts/combatV6SectTask'; -import type { CultivatorCondition } from '@shared/types/condition'; +import { SectV6TargetSchema } from '@daoyou/shared/contracts/combatV6SectTask'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; import { describe, expect, it } from 'vitest'; import { automaticCommands } from '../../../combat-v6/auto'; import { generateStarterBeast } from '../beasts'; diff --git a/src/shared/engine/combat-v6/tower/balance.test.ts b/packages/shared/src/engine/combat-v6/tower/balance.test.ts similarity index 100% rename from src/shared/engine/combat-v6/tower/balance.test.ts rename to packages/shared/src/engine/combat-v6/tower/balance.test.ts diff --git a/src/shared/engine/combat-v6/tower/catalog.ts b/packages/shared/src/engine/combat-v6/tower/catalog.ts similarity index 100% rename from src/shared/engine/combat-v6/tower/catalog.ts rename to packages/shared/src/engine/combat-v6/tower/catalog.ts diff --git a/src/shared/engine/combat-v6/tower/content.ts b/packages/shared/src/engine/combat-v6/tower/content.ts similarity index 100% rename from src/shared/engine/combat-v6/tower/content.ts rename to packages/shared/src/engine/combat-v6/tower/content.ts diff --git a/src/shared/engine/combat-v6/tower/data/enemies.json b/packages/shared/src/engine/combat-v6/tower/data/enemies.json similarity index 100% rename from src/shared/engine/combat-v6/tower/data/enemies.json rename to packages/shared/src/engine/combat-v6/tower/data/enemies.json diff --git a/src/shared/engine/combat-v6/tower/data/generation.json b/packages/shared/src/engine/combat-v6/tower/data/generation.json similarity index 100% rename from src/shared/engine/combat-v6/tower/data/generation.json rename to packages/shared/src/engine/combat-v6/tower/data/generation.json diff --git a/src/shared/engine/combat-v6/tower/data/mechanics.json b/packages/shared/src/engine/combat-v6/tower/data/mechanics.json similarity index 100% rename from src/shared/engine/combat-v6/tower/data/mechanics.json rename to packages/shared/src/engine/combat-v6/tower/data/mechanics.json diff --git a/src/shared/engine/combat-v6/tower/data/skills.ts b/packages/shared/src/engine/combat-v6/tower/data/skills.ts similarity index 100% rename from src/shared/engine/combat-v6/tower/data/skills.ts rename to packages/shared/src/engine/combat-v6/tower/data/skills.ts diff --git a/src/shared/engine/combat-v6/tower/generation.ts b/packages/shared/src/engine/combat-v6/tower/generation.ts similarity index 100% rename from src/shared/engine/combat-v6/tower/generation.ts rename to packages/shared/src/engine/combat-v6/tower/generation.ts diff --git a/src/shared/engine/combat-v6/tower/health.test.ts b/packages/shared/src/engine/combat-v6/tower/health.test.ts similarity index 100% rename from src/shared/engine/combat-v6/tower/health.test.ts rename to packages/shared/src/engine/combat-v6/tower/health.test.ts diff --git a/src/shared/engine/combat-v6/tower/host.ts b/packages/shared/src/engine/combat-v6/tower/host.ts similarity index 100% rename from src/shared/engine/combat-v6/tower/host.ts rename to packages/shared/src/engine/combat-v6/tower/host.ts diff --git a/src/shared/engine/combat-v6/tower/phase-three.test.ts b/packages/shared/src/engine/combat-v6/tower/phase-three.test.ts similarity index 100% rename from src/shared/engine/combat-v6/tower/phase-three.test.ts rename to packages/shared/src/engine/combat-v6/tower/phase-three.test.ts diff --git a/src/shared/engine/combat-v6/tower/phase-two.test.ts b/packages/shared/src/engine/combat-v6/tower/phase-two.test.ts similarity index 100% rename from src/shared/engine/combat-v6/tower/phase-two.test.ts rename to packages/shared/src/engine/combat-v6/tower/phase-two.test.ts diff --git a/src/shared/engine/combat-v6/tower/published.test.ts b/packages/shared/src/engine/combat-v6/tower/published.test.ts similarity index 100% rename from src/shared/engine/combat-v6/tower/published.test.ts rename to packages/shared/src/engine/combat-v6/tower/published.test.ts diff --git a/src/shared/engine/combat-v6/tower/published.ts b/packages/shared/src/engine/combat-v6/tower/published.ts similarity index 100% rename from src/shared/engine/combat-v6/tower/published.ts rename to packages/shared/src/engine/combat-v6/tower/published.ts diff --git a/src/shared/engine/combat-v6/tower/reference-fixtures.ts b/packages/shared/src/engine/combat-v6/tower/reference-fixtures.ts similarity index 100% rename from src/shared/engine/combat-v6/tower/reference-fixtures.ts rename to packages/shared/src/engine/combat-v6/tower/reference-fixtures.ts diff --git a/src/shared/engine/combat-v6/tower/strategy-compiler.ts b/packages/shared/src/engine/combat-v6/tower/strategy-compiler.ts similarity index 100% rename from src/shared/engine/combat-v6/tower/strategy-compiler.ts rename to packages/shared/src/engine/combat-v6/tower/strategy-compiler.ts diff --git a/src/shared/engine/combat-v6/tower/strategy-templates.ts b/packages/shared/src/engine/combat-v6/tower/strategy-templates.ts similarity index 100% rename from src/shared/engine/combat-v6/tower/strategy-templates.ts rename to packages/shared/src/engine/combat-v6/tower/strategy-templates.ts diff --git a/src/shared/engine/combat-v6/tower/strategy-value.test.ts b/packages/shared/src/engine/combat-v6/tower/strategy-value.test.ts similarity index 100% rename from src/shared/engine/combat-v6/tower/strategy-value.test.ts rename to packages/shared/src/engine/combat-v6/tower/strategy-value.test.ts diff --git a/src/shared/engine/combat-v6/tower/strategy.test.ts b/packages/shared/src/engine/combat-v6/tower/strategy.test.ts similarity index 100% rename from src/shared/engine/combat-v6/tower/strategy.test.ts rename to packages/shared/src/engine/combat-v6/tower/strategy.test.ts diff --git a/src/shared/engine/combat-v6/tower/strategy.ts b/packages/shared/src/engine/combat-v6/tower/strategy.ts similarity index 100% rename from src/shared/engine/combat-v6/tower/strategy.ts rename to packages/shared/src/engine/combat-v6/tower/strategy.ts diff --git a/src/shared/engine/combat-v6/tower/tower.test.ts b/packages/shared/src/engine/combat-v6/tower/tower.test.ts similarity index 100% rename from src/shared/engine/combat-v6/tower/tower.test.ts rename to packages/shared/src/engine/combat-v6/tower/tower.test.ts diff --git a/src/shared/engine/combat-v6/version.ts b/packages/shared/src/engine/combat-v6/version.ts similarity index 100% rename from src/shared/engine/combat-v6/version.ts rename to packages/shared/src/engine/combat-v6/version.ts diff --git a/src/shared/engine/combat-v6/wild/content.ts b/packages/shared/src/engine/combat-v6/wild/content.ts similarity index 100% rename from src/shared/engine/combat-v6/wild/content.ts rename to packages/shared/src/engine/combat-v6/wild/content.ts diff --git a/src/shared/engine/combat-v6/wild/data/README.md b/packages/shared/src/engine/combat-v6/wild/data/README.md similarity index 88% rename from src/shared/engine/combat-v6/wild/data/README.md rename to packages/shared/src/engine/combat-v6/wild/data/README.md index 4ae5c397..4c9fe4d8 100644 --- a/src/shared/engine/combat-v6/wild/data/README.md +++ b/packages/shared/src/engine/combat-v6/wild/data/README.md @@ -10,6 +10,6 @@ - `encounter.mutantChance`:每只灵兽独立变异的概率,当前 0.8%,幼崽与成年一致。变异使用独立随机流;预览、战斗与捕获保留身份,资质和成长按灵兽领域规则提高 10%。 - `encounter.allocationSpread`:成年五维加点相对均值的波动范围,当前 30%,上下界按整数点数取整;纯野生初始可支配总点数为等级的3倍,生成时全部分配;捕获后每级正常增加5自由点。幼崽五维额外加点为0、待分配点50,变异统一为0级宝宝。 - `activity.explorationCooldownMs`:两次成功寻觅的最短间隔,当前 1 秒;不再设置每日次数上限。 -- 消耗统一维护于 `src/shared/config/qiSystem.ts` 的 `wild_search`,当前每次 2 点天地灵气。 +- 消耗统一维护于 `packages/shared/src/config/qiSystem.ts` 的 `wild_search`,当前每次 2 点天地灵气。 -寻觅成功即保存完整个体。开战投影和捕获结算沿用这些事实,不再次随机生成。掉落单独维护于 `src/shared/rewards/data/wild.json`。完整流程见 [野外寻觅](../../../../../../docs/combat-v6-wild-seeking.md)。 +寻觅成功即保存完整个体。开战投影和捕获结算沿用这些事实,不再次随机生成。掉落单独维护于 `packages/shared/src/rewards/data/wild.json`。完整流程见 [野外寻觅](../../../../../../../docs/combat-v6-wild-seeking.md)。 diff --git a/src/shared/engine/combat-v6/wild/data/wild.json b/packages/shared/src/engine/combat-v6/wild/data/wild.json similarity index 100% rename from src/shared/engine/combat-v6/wild/data/wild.json rename to packages/shared/src/engine/combat-v6/wild/data/wild.json diff --git a/src/shared/engine/combat-v6/wild/data/wild.schema.json b/packages/shared/src/engine/combat-v6/wild/data/wild.schema.json similarity index 100% rename from src/shared/engine/combat-v6/wild/data/wild.schema.json rename to packages/shared/src/engine/combat-v6/wild/data/wild.schema.json diff --git a/src/shared/engine/combat-v6/wild/generator.ts b/packages/shared/src/engine/combat-v6/wild/generator.ts similarity index 100% rename from src/shared/engine/combat-v6/wild/generator.ts rename to packages/shared/src/engine/combat-v6/wild/generator.ts diff --git a/src/shared/engine/combat-v6/wild/host.test.ts b/packages/shared/src/engine/combat-v6/wild/host.test.ts similarity index 99% rename from src/shared/engine/combat-v6/wild/host.test.ts rename to packages/shared/src/engine/combat-v6/wild/host.test.ts index de1423f7..c9a2e700 100644 --- a/src/shared/engine/combat-v6/wild/host.test.ts +++ b/packages/shared/src/engine/combat-v6/wild/host.test.ts @@ -1,4 +1,4 @@ -import type { CultivatorCondition } from '@shared/types/condition'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; import { describe, expect, it } from 'vitest'; import { WildEncounterSchema, diff --git a/src/shared/engine/combat-v6/wild/host.ts b/packages/shared/src/engine/combat-v6/wild/host.ts similarity index 100% rename from src/shared/engine/combat-v6/wild/host.ts rename to packages/shared/src/engine/combat-v6/wild/host.ts diff --git a/src/shared/engine/combat-v6/wild/index.ts b/packages/shared/src/engine/combat-v6/wild/index.ts similarity index 100% rename from src/shared/engine/combat-v6/wild/index.ts rename to packages/shared/src/engine/combat-v6/wild/index.ts diff --git a/src/shared/engine/combat-v6/wild/mutation.test.ts b/packages/shared/src/engine/combat-v6/wild/mutation.test.ts similarity index 100% rename from src/shared/engine/combat-v6/wild/mutation.test.ts rename to packages/shared/src/engine/combat-v6/wild/mutation.test.ts diff --git a/src/shared/engine/combat-v6/wild/pack.test.ts b/packages/shared/src/engine/combat-v6/wild/pack.test.ts similarity index 98% rename from src/shared/engine/combat-v6/wild/pack.test.ts rename to packages/shared/src/engine/combat-v6/wild/pack.test.ts index 64319d48..b6bc6a49 100644 --- a/src/shared/engine/combat-v6/wild/pack.test.ts +++ b/packages/shared/src/engine/combat-v6/wild/pack.test.ts @@ -1,4 +1,4 @@ -import { getMapNode } from '@shared/lib/game/mapSystem'; +import { getMapNode } from '@daoyou/shared/lib/game/mapSystem'; import { describe, expect, it } from 'vitest'; import { z } from 'zod'; import { BEAST_PROGRESSION, BEAST_SPECIES } from '../beasts/content'; diff --git a/src/shared/engine/combat-v6/wild/pack.ts b/packages/shared/src/engine/combat-v6/wild/pack.ts similarity index 94% rename from src/shared/engine/combat-v6/wild/pack.ts rename to packages/shared/src/engine/combat-v6/wild/pack.ts index cc810128..e18c627d 100644 --- a/src/shared/engine/combat-v6/wild/pack.ts +++ b/packages/shared/src/engine/combat-v6/wild/pack.ts @@ -1,5 +1,5 @@ -import { formatContentPackErrors } from '@shared/lib/content-pack-errors'; -import { REALM_ORDER, type RealmType } from '@shared/types/constants'; +import { formatContentPackErrors } from '@daoyou/shared/lib/content-pack-errors'; +import { REALM_ORDER, type RealmType } from '@daoyou/shared/types/constants'; import { z } from 'zod'; import { BEAST_SPECIES } from '../beasts/content'; import raw from './data/wild.json'; diff --git a/src/shared/engine/combat-v6/wild/rules.ts b/packages/shared/src/engine/combat-v6/wild/rules.ts similarity index 100% rename from src/shared/engine/combat-v6/wild/rules.ts rename to packages/shared/src/engine/combat-v6/wild/rules.ts diff --git a/src/shared/engine/cultivation/CultivationEngine.test.ts b/packages/shared/src/engine/cultivation/CultivationEngine.test.ts similarity index 98% rename from src/shared/engine/cultivation/CultivationEngine.test.ts rename to packages/shared/src/engine/cultivation/CultivationEngine.test.ts index 08ffbf8a..501eee53 100644 --- a/src/shared/engine/cultivation/CultivationEngine.test.ts +++ b/packages/shared/src/engine/cultivation/CultivationEngine.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; -import { COMPREHENSION_INSIGHT_CAP } from '@shared/config/cultivationTuning'; -import type { Cultivator, FateEffectEntry } from '@shared/types/cultivator'; -import { resolveLiveExpCap } from '@server/utils/cultivationUtils'; +import { COMPREHENSION_INSIGHT_CAP } from '@daoyou/shared/config/cultivationTuning'; +import type { Cultivator, FateEffectEntry } from '@daoyou/shared/types/cultivator'; +import { resolveLiveExpCap } from '@daoyou/shared/engine/cultivation/cultivationUtils'; import { attemptBreakthrough, performCultivation } from './CultivationEngine'; function createCultivator(): Cultivator { diff --git a/src/shared/engine/cultivation/CultivationEngine.ts b/packages/shared/src/engine/cultivation/CultivationEngine.ts similarity index 94% rename from src/shared/engine/cultivation/CultivationEngine.ts rename to packages/shared/src/engine/cultivation/CultivationEngine.ts index 947aecbf..066f9b5d 100644 --- a/src/shared/engine/cultivation/CultivationEngine.ts +++ b/packages/shared/src/engine/cultivation/CultivationEngine.ts @@ -1,30 +1,30 @@ -import { COMPREHENSION_INSIGHT_CAP } from '@shared/config/cultivationTuning'; -import { getBreakthroughAttributeGrowthReward } from '@shared/config/realmProgression'; -import { isConditionStatusActive } from '@shared/lib/condition'; +import { COMPREHENSION_INSIGHT_CAP } from '@daoyou/shared/config/cultivationTuning'; +import { getBreakthroughAttributeGrowthReward } from '@daoyou/shared/config/realmProgression'; +import { isConditionStatusActive } from '@daoyou/shared/lib/condition'; import { consumeCultivationBoostStatus, getCultivationBoostRetreatMultiplier, -} from '@shared/lib/cultivationBoost'; -import { evaluateFateContext } from '@shared/lib/fates'; -import { getProtectMeridiansReductionPercent } from '@shared/lib/pillEffectScaling'; +} from '@daoyou/shared/lib/cultivationBoost'; +import { evaluateFateContext } from '@daoyou/shared/lib/fates'; +import { getProtectMeridiansReductionPercent } from '@daoyou/shared/lib/pillEffectScaling'; import type { ConditionStatusInstance, ConditionStatusKey, -} from '@shared/types/condition'; -import { type RealmStage, type RealmType } from '@shared/types/constants'; +} from '@daoyou/shared/types/condition'; +import { type RealmStage, type RealmType } from '@daoyou/shared/types/constants'; import type { Attributes, BreakthroughHistoryEntry, Cultivator, RetreatRecord, -} from '@shared/types/cultivator'; +} from '@daoyou/shared/types/cultivator'; import { calculateBreakthroughChance, getNextStage, LIFESPAN_BONUS_BY_REALM, type BreakthroughModifiers, -} from '@server/utils/breakthroughCalculator'; +} from '@daoyou/shared/engine/cultivation/breakthroughCalculator'; import { calculateCultivationExp, calculateExpLossOnFailure, @@ -35,7 +35,7 @@ import { isBottleneckReached, resolveLiveExpCap, syncBottleneckState, -} from '@server/utils/cultivationUtils'; +} from '@daoyou/shared/engine/cultivation/cultivationUtils'; function clamp(value: number, min: number, max: number): number { return Math.max(min, Math.min(max, value)); diff --git a/src/shared/engine/cultivation/ExpBudgetCalculator.ts b/packages/shared/src/engine/cultivation/ExpBudgetCalculator.ts similarity index 100% rename from src/shared/engine/cultivation/ExpBudgetCalculator.ts rename to packages/shared/src/engine/cultivation/ExpBudgetCalculator.ts diff --git a/src/server/utils/breakthroughCalculator.ts b/packages/shared/src/engine/cultivation/breakthroughCalculator.ts similarity index 97% rename from src/server/utils/breakthroughCalculator.ts rename to packages/shared/src/engine/cultivation/breakthroughCalculator.ts index d380363a..b66d5170 100644 --- a/src/server/utils/breakthroughCalculator.ts +++ b/packages/shared/src/engine/cultivation/breakthroughCalculator.ts @@ -10,11 +10,11 @@ import { REALM_VALUES, type RealmStage, type RealmType, -} from '@shared/types/constants'; +} from '@daoyou/shared/types/constants'; import type { CultivationProgress, Cultivator, -} from '@shared/types/cultivator'; +} from '@daoyou/shared/types/cultivator'; type BreakthroughCultivator = Pick< Cultivator, @@ -26,18 +26,18 @@ type BreakthroughCultivator = Pick< >; import { evaluateFateContext, -} from '@shared/lib/fates'; +} from '@daoyou/shared/lib/fates'; import { getBreakthroughPenalty, isConditionStatusActive, -} from '@shared/lib/condition'; +} from '@daoyou/shared/lib/condition'; import { getBreakthroughFocusBonus, -} from '@shared/lib/pillEffectScaling'; +} from '@daoyou/shared/lib/pillEffectScaling'; import type { ConditionStatusInstance, ConditionStatusKey, -} from '@shared/types/condition'; +} from '@daoyou/shared/types/condition'; import { format } from 'd3-format'; import { calculateExpProgress, getBreakthroughType, isBottleneckReached } from './cultivationUtils'; diff --git a/src/server/utils/cultivationUtils.ts b/packages/shared/src/engine/cultivation/cultivationUtils.ts similarity index 96% rename from src/server/utils/cultivationUtils.ts rename to packages/shared/src/engine/cultivation/cultivationUtils.ts index 80bc3646..2e5262ea 100644 --- a/src/server/utils/cultivationUtils.ts +++ b/packages/shared/src/engine/cultivation/cultivationUtils.ts @@ -1,4 +1,4 @@ -import { EXP_CAP_TABLE } from '@shared/config/cultivationProgress'; +import { EXP_CAP_TABLE } from '@daoyou/shared/config/cultivationProgress'; import { BOTTLENECK_EXP_PENALTY, BOTTLENECK_THRESHOLD, @@ -18,14 +18,14 @@ import { SPIRITUAL_ROOT_BASE, YEARS_MULTIPLIER_BASE, YEARS_MULTIPLIER_SCALE, -} from '@shared/config/cultivationTuning'; -import type { RealmStage, RealmType } from '@shared/types/constants'; +} from '@daoyou/shared/config/cultivationTuning'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; import type { CultivationProgress, Cultivator, SpiritualRoot, -} from '@shared/types/cultivator'; -import { calculateRetreatBaseExp } from '@shared/engine/cultivation/ExpBudgetCalculator'; +} from '@daoyou/shared/types/cultivator'; +import { calculateRetreatBaseExp } from '@daoyou/shared/engine/cultivation/ExpBudgetCalculator'; /** * 从代码配置表实时计算当前境界阶段的修为上限。 diff --git a/src/shared/engine/cultivation/exp-gain-strategies/index.ts b/packages/shared/src/engine/cultivation/exp-gain-strategies/index.ts similarity index 97% rename from src/shared/engine/cultivation/exp-gain-strategies/index.ts rename to packages/shared/src/engine/cultivation/exp-gain-strategies/index.ts index 23356d72..26d8a0e4 100644 --- a/src/shared/engine/cultivation/exp-gain-strategies/index.ts +++ b/packages/shared/src/engine/cultivation/exp-gain-strategies/index.ts @@ -7,16 +7,16 @@ import { REALM_DAILY_EXP_BUDGET, RETREAT_EXP_BUDGET, SYSTEM_REWARD_EXP_BUDGET, -} from '@shared/config/cultivationExpGain'; +} from '@daoyou/shared/config/cultivationExpGain'; import { calculateCultivationExpByCap, calculateCultivationExpByDailyBudget, type CultivationExpCalculationInput, type DailyBudgetExpCalculationInput, type CultivationExpCalculation, -} from '@shared/lib/cultivationExpGain'; -import { buildPillCultivationExpInput } from '@shared/lib/pillCultivationExp'; -import type { RealmStage, RealmType } from '@shared/types/constants'; +} from '@daoyou/shared/lib/cultivationExpGain'; +import { buildPillCultivationExpInput } from '@daoyou/shared/lib/pillCultivationExp'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; import { clampNonNegativeFinite, type BattleVictoryExpContext, diff --git a/src/shared/engine/cultivation/exp-gain-strategies/types.ts b/packages/shared/src/engine/cultivation/exp-gain-strategies/types.ts similarity index 93% rename from src/shared/engine/cultivation/exp-gain-strategies/types.ts rename to packages/shared/src/engine/cultivation/exp-gain-strategies/types.ts index aa0a6553..f015ae06 100644 --- a/src/shared/engine/cultivation/exp-gain-strategies/types.ts +++ b/packages/shared/src/engine/cultivation/exp-gain-strategies/types.ts @@ -1,10 +1,10 @@ -import { EXP_CAP_TABLE } from '@shared/config/cultivationProgress'; +import { EXP_CAP_TABLE } from '@daoyou/shared/config/cultivationProgress'; import type { CultivationExpCalculation, CultivationExpCalculationInput, DailyBudgetExpCalculationInput, -} from '@shared/lib/cultivationExpGain'; -import type { Quality, RealmStage, RealmType } from '@shared/types/constants'; +} from '@daoyou/shared/lib/cultivationExpGain'; +import type { Quality, RealmStage, RealmType } from '@daoyou/shared/types/constants'; export const EXP_GAIN_SCENES = [ 'retreat', diff --git a/packages/shared/src/engine/cultivator/creation/CharacterGenerator.test.ts b/packages/shared/src/engine/cultivator/creation/CharacterGenerator.test.ts new file mode 100644 index 00000000..eeb8134e --- /dev/null +++ b/packages/shared/src/engine/cultivator/creation/CharacterGenerator.test.ts @@ -0,0 +1,63 @@ +import { describe, expect, it } from 'vitest'; +import { buildGeneratedCharacter } from './CharacterGenerator'; +import type { CultivatorAIRawData } from './types'; + +const buildAIData = ( + overrides: Partial = {}, +): CultivatorAIRawData => ({ + name: '林秋', + gender: '男', + origin: '青岚山', + personality: '沉静坚韧', + background: '少年出身山村,偶得残卷,自此踏上修行之路。', + element_preferences: ['金', '木', '水', '火'], + aptitude_score: 78, + balance_notes: '双目有神,命数稳中带锋。', + ...overrides, +}); + +describe('buildGeneratedCharacter', () => { + it('trims extra element preferences', () => { + const { cultivator } = buildGeneratedCharacter( + buildAIData({ element_preferences: ['金', '木', '水', '火', '土'] }), + '偏向剑修的少年', + () => 0.5, + ); + expect(cultivator.spiritual_roots.map((root) => root.element)).toEqual([ + '金', + '木', + '水', + '火', + ]); + }); + it('deduplicates repeated element preferences', () => { + const { cultivator } = buildGeneratedCharacter( + buildAIData({ element_preferences: ['火', '火', '水'] }), + '擅长丹火的修士', + () => 0.5, + ); + expect(cultivator.spiritual_roots.map((root) => root.element)).toEqual([ + '火', + '水', + ]); + }); + it('creates fixed attributes and uses host supplied randomness for age and lifespan', () => { + const { cultivator } = buildGeneratedCharacter( + buildAIData({ aptitude_score: 95 }), + '根骨上佳的修士', + () => 0.5, + ); + expect(cultivator.attributes).toEqual({ + vitality: 15, + strength: 15, + spirit: 15, + endurance: 15, + speed: 15, + willpower: 15, + }); + expect(cultivator.unallocated_attribute_points).toBe(25); + expect(cultivator.age).toBe(17); + expect(cultivator.lifespan).toBe(110); + expect('max' + '_skills' in cultivator).toBe(false); + }); +}); diff --git a/packages/shared/src/engine/cultivator/creation/CharacterGenerator.ts b/packages/shared/src/engine/cultivator/creation/CharacterGenerator.ts new file mode 100644 index 00000000..8d5247fb --- /dev/null +++ b/packages/shared/src/engine/cultivator/creation/CharacterGenerator.ts @@ -0,0 +1,70 @@ +import { getRealmStageUnallocatedAttributeBudget } from '@daoyou/shared/config/realmProgression'; +import type { Cultivator } from '@daoyou/shared/types/cultivator'; +import { normalizeCultivatorAIData, type CultivatorAIRawData } from './types'; +import { generateAttributes, generateSpiritualRoots } from './utils'; + +export function buildGeneratedCharacter( + raw: CultivatorAIRawData, + userInput: string, + rng: () => number, +): { cultivator: Cultivator; balanceNotes: string } { + const data = normalizeCultivatorAIData(raw); + + // 2. 数值化生成 + const attributes = generateAttributes(); + const spiritual_roots = generateSpiritualRoots( + data.aptitude_score, + data.element_preferences, + rng, + ); + + // 4. 其他基础数值 + const age = 14 + Math.floor(rng() * 6); // 14-20岁 + // 寿元:炼气期基础100,分数高加成 + const lifespan = + 80 + Math.floor(rng() * 20) + (data.aptitude_score > 80 ? 20 : 0); + + // 构造完整的 Cultivator 对象 + const cultivator: Cultivator = { + id: '', // Placeholder + name: data.name, + gender: data.gender, + origin: data.origin, + personality: data.personality, + background: data.background, + playerRace: 'human', + raceNarrative: data.race_narrative, + + realm: '炼气', + realm_stage: '初期', + age, + lifespan, + + attributes, + unallocated_attribute_points: getRealmStageUnallocatedAttributeBudget( + '炼气', + '初期', + ), + spiritual_roots, + status: 'active', + spirit_stones: 0, + pre_heaven_fates: [], // 后续流程生成 + inventory: { + artifacts: [], + consumables: [], + materials: [], + }, + equipped: { + weapon: null, + armor: null, + accessory: null, + }, + prompt: userInput, + balance_notes: data.balance_notes, + }; + + return { + cultivator, + balanceNotes: data.balance_notes, + }; +} diff --git a/src/shared/engine/cultivator/creation/types.ts b/packages/shared/src/engine/cultivator/creation/types.ts similarity index 95% rename from src/shared/engine/cultivator/creation/types.ts rename to packages/shared/src/engine/cultivator/creation/types.ts index 936d856a..0987cd79 100644 --- a/src/shared/engine/cultivator/creation/types.ts +++ b/packages/shared/src/engine/cultivator/creation/types.ts @@ -1,4 +1,4 @@ -import { ELEMENT_VALUES, GENDER_VALUES } from '@shared/types/constants'; +import { ELEMENT_VALUES, GENDER_VALUES } from '@daoyou/shared/types/constants'; import { z } from 'zod'; const MAX_ELEMENT_PREFERENCES = 4; diff --git a/src/shared/engine/cultivator/creation/utils.test.ts b/packages/shared/src/engine/cultivator/creation/utils.test.ts similarity index 97% rename from src/shared/engine/cultivator/creation/utils.test.ts rename to packages/shared/src/engine/cultivator/creation/utils.test.ts index a813bc39..3eb794b0 100644 --- a/src/shared/engine/cultivator/creation/utils.test.ts +++ b/packages/shared/src/engine/cultivator/creation/utils.test.ts @@ -1,5 +1,5 @@ import { vi } from 'vitest'; -import { ELEMENT_VALUES } from '@shared/types/constants'; +import { ELEMENT_VALUES } from '@daoyou/shared/types/constants'; import { generateAttributes, generateSpiritualRoots } from './utils'; describe('generateSpiritualRoots', () => { diff --git a/src/shared/engine/cultivator/creation/utils.ts b/packages/shared/src/engine/cultivator/creation/utils.ts similarity index 79% rename from src/shared/engine/cultivator/creation/utils.ts rename to packages/shared/src/engine/cultivator/creation/utils.ts index b7ab29ee..2f18c2a7 100644 --- a/src/shared/engine/cultivator/creation/utils.ts +++ b/packages/shared/src/engine/cultivator/creation/utils.ts @@ -1,6 +1,12 @@ -import { getRealmStageNaturalAttributeValue } from '@shared/config/realmProgression'; -import { ELEMENT_VALUES, type ElementType } from '@shared/types/constants'; -import type { Attributes, SpiritualRoot } from '@shared/types/cultivator'; +import { getRealmStageNaturalAttributeValue } from '@daoyou/shared/config/realmProgression'; +import { + ELEMENT_VALUES, + type ElementType, +} from '@daoyou/shared/types/constants'; +import type { + Attributes, + SpiritualRoot, +} from '@daoyou/shared/types/cultivator'; export function generateAttributes(): Attributes { const naturalValue = getRealmStageNaturalAttributeValue('炼气', '初期'); @@ -17,8 +23,13 @@ export function generateAttributes(): Attributes { export function generateSpiritualRoots( score: number, preferences: readonly string[], + rng: () => number = Math.random, ): SpiritualRoot[] { - const normalizedPreferences = normalizeElementPreferences(preferences, score); + const normalizedPreferences = normalizeElementPreferences( + preferences, + score, + rng, + ); const rootCount = normalizedPreferences.length; const roots: SpiritualRoot[] = normalizedPreferences.map((el) => ({ @@ -32,6 +43,7 @@ export function generateSpiritualRoots( score, rootCount, root.grade, + rng, ); }); @@ -42,6 +54,7 @@ function calculateSpiritualRootStrength( score: number, rootCount: number, grade: SpiritualRoot['grade'], + rng: () => number, ): number { const normalizedScore = Math.max(0, Math.min(100, score)); const baseCap = getBaseStrengthCap(rootCount); @@ -53,7 +66,7 @@ function calculateSpiritualRootStrength( // 波动区间跟随上限变化,确保低分也有一定随机性 const amplitude = Math.max(5, Math.floor(cap * 0.15)); - const delta = Math.floor((Math.random() * 2 - 1) * amplitude); + const delta = Math.floor((rng() * 2 - 1) * amplitude); const minStrength = Math.max(10, Math.floor(cap * 0.25)); return Math.max(minStrength, Math.min(cap, target + delta)); @@ -69,6 +82,7 @@ function getBaseStrengthCap(rootCount: number): number { function normalizeElementPreferences( preferences: readonly string[], score: number, + rng: () => number, ): ElementType[] { const uniqueValid = Array.from( new Set( @@ -83,13 +97,13 @@ function normalizeElementPreferences( } const fallbackCount = score >= 90 ? 1 : score >= 75 ? 2 : score >= 50 ? 3 : 4; - return pickRandomElements(fallbackCount); + return pickRandomElements(fallbackCount, rng); } -function pickRandomElements(count: number): ElementType[] { +function pickRandomElements(count: number, rng: () => number): ElementType[] { const pool = [...ELEMENT_VALUES]; for (let i = pool.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); + const j = Math.floor(rng() * (i + 1)); [pool[i], pool[j]] = [pool[j], pool[i]]; } return pool.slice(0, Math.max(1, Math.min(count, ELEMENT_VALUES.length))); diff --git a/src/shared/engine/cultivator/index.ts b/packages/shared/src/engine/cultivator/index.ts similarity index 100% rename from src/shared/engine/cultivator/index.ts rename to packages/shared/src/engine/cultivator/index.ts diff --git a/src/shared/engine/material/creation/MaterialGenerator.ts b/packages/shared/src/engine/material/creation/MaterialGenerator.ts similarity index 51% rename from src/shared/engine/material/creation/MaterialGenerator.ts rename to packages/shared/src/engine/material/creation/MaterialGenerator.ts index 6cd53c63..b93918ae 100644 --- a/src/shared/engine/material/creation/MaterialGenerator.ts +++ b/packages/shared/src/engine/material/creation/MaterialGenerator.ts @@ -1,131 +1,19 @@ -import { generateAiArray } from '@server/utils/aiClient'; import { MATERIAL_TYPE_VALUES, QUALITY_VALUES, type MaterialType, type Quality, -} from '@shared/types/constants'; +} from '@daoyou/shared/types/constants'; import { - BASE_PRICES, QUALITY_CHANCE_MAP, QUALITY_TO_RANK, QUANTITY_RANGE_MAP, RANK_TO_QUALITY, TYPE_CHANCE_MAP, - TYPE_MULTIPLIERS, } from './config'; -import { getFallbackMaterialPreset } from './fallbackPresets'; -import { - getMaterialGenerationPrompt, - getMaterialGenerationUserPrompt, -} from './prompts'; -import { - MaterialAISchema, - type GeneratedMaterial, - type MaterialRandomOptions, - type MaterialSkeleton, -} from './types'; +import { type MaterialRandomOptions, type MaterialSkeleton } from './types'; export class MaterialGenerator { - /** - * 生成一批随机材料 - * @param count 数量 - * @param options 随机参数配置 - */ - public static async generateRandom( - count: number = 10, - options: MaterialRandomOptions = {}, - ): Promise { - // 1. 生成随机骨架 - const skeletons = this.generateRandomSkeletons(count, options); - // 2. 填充详细信息 (AI) - return this.fillMaterialDetails(skeletons); - } - - /** - * 根据指定的骨架生成材料 (批量) - * 用于系统奖励、副本掉落等确定性场景 - * @param skeletons 指定的骨架列表 - */ - public static async generateFromSkeletons( - skeletons: MaterialSkeleton[], - ): Promise { - if (skeletons.some((skeleton) => skeleton.type === 'seed')) { - throw new Error('灵植种子必须使用 SpiritSeedGenerator 生成'); - } - return this.fillMaterialDetails(skeletons); - } - - // ===== Private Core Logic ===== - - /** - * 核心方法:调用 AI 为骨架填充 Name, Description, Element - */ - private static async fillMaterialDetails( - skeletons: MaterialSkeleton[], - ): Promise { - if (skeletons.length === 0) return []; - - const prompt = getMaterialGenerationPrompt(); - const userPrompt = getMaterialGenerationUserPrompt(skeletons); - try { - const aiResponse = await generateAiArray({ - system: prompt, - prompt: userPrompt, - elementSchema: MaterialAISchema, - name: 'MaterialTextList', - sceneId: 'material-generation', - }); - - // 组合结果 - return skeletons.map((skeleton, index) => { - const aiData = aiResponse.output[index] || { - name: '未知材料', - description: '天道感应模糊...', - element: skeleton.forcedElement || '金', - }; - - // 最终元素:优先使用骨架强制指定的,否则使用 AI 生成的 - const finalElement = skeleton.forcedElement || aiData.element; - - // 计算价格 - const price = this.calculatePrice(skeleton.rank, skeleton.type); - - return { - name: aiData.name, - type: skeleton.type, - rank: skeleton.rank, - element: finalElement, - description: aiData.description, - quantity: skeleton.quantity, - price, - }; - }); - } catch (error) { - console.error('Material Generation Failed:', error); - // AI 失败时仍返回可发放的材料,避免奖励邮件出现空附件 - return this.buildFallbackMaterials(skeletons); - } - } - - private static buildFallbackMaterials( - skeletons: MaterialSkeleton[], - ): GeneratedMaterial[] { - return skeletons.map((skeleton) => { - const preset = getFallbackMaterialPreset(skeleton.type, skeleton.rank); - const finalElement = skeleton.forcedElement || preset.element; - return { - name: preset.name, - type: skeleton.type, - rank: skeleton.rank, - element: finalElement, - description: preset.description, - quantity: skeleton.quantity, - price: this.calculatePrice(skeleton.rank, skeleton.type), - }; - }); - } - public static generateRandomSkeletons( count: number, options: MaterialRandomOptions = {}, @@ -270,16 +158,4 @@ export class MaterialGenerator { } return next; } - - private static calculatePrice(rank: Quality, type: MaterialType): number { - const base = BASE_PRICES[rank]; - const multiplier = TYPE_MULTIPLIERS[type] || 1.0; - const variation = 0.8 + Math.random() * 0.4; // +/- 20% - let price = Math.floor(base * multiplier * variation); - - if (price > 1000) price = Math.floor(price / 100) * 100; - else if (price > 100) price = Math.floor(price / 10) * 10; - - return Math.max(1, price); - } } diff --git a/src/shared/engine/material/creation/config.ts b/packages/shared/src/engine/material/creation/config.ts similarity index 94% rename from src/shared/engine/material/creation/config.ts rename to packages/shared/src/engine/material/creation/config.ts index 04250a54..d65a7b96 100644 --- a/src/shared/engine/material/creation/config.ts +++ b/packages/shared/src/engine/material/creation/config.ts @@ -1,5 +1,5 @@ -import type { MaterialType, Quality } from '@shared/types/constants'; -import { getMaterialTypeLabel } from '@shared/lib/gameConceptDisplay'; +import type { MaterialType, Quality } from '@daoyou/shared/types/constants'; +import { getMaterialTypeLabel } from '@daoyou/shared/lib/gameConceptDisplay'; // 每个品质出现的概率 export const QUALITY_CHANCE_MAP: Record = { diff --git a/src/shared/engine/material/creation/data/marketPresets.json b/packages/shared/src/engine/material/creation/data/marketPresets.json similarity index 100% rename from src/shared/engine/material/creation/data/marketPresets.json rename to packages/shared/src/engine/material/creation/data/marketPresets.json diff --git a/src/shared/engine/material/creation/fallbackPresets.ts b/packages/shared/src/engine/material/creation/fallbackPresets.ts similarity index 99% rename from src/shared/engine/material/creation/fallbackPresets.ts rename to packages/shared/src/engine/material/creation/fallbackPresets.ts index 721ec84d..47050725 100644 --- a/src/shared/engine/material/creation/fallbackPresets.ts +++ b/packages/shared/src/engine/material/creation/fallbackPresets.ts @@ -1,4 +1,4 @@ -import type { ElementType, MaterialType, Quality } from '@shared/types/constants'; +import type { ElementType, MaterialType, Quality } from '@daoyou/shared/types/constants'; export interface FallbackMaterialPreset { name: string; diff --git a/src/shared/engine/material/creation/marketPresets.ts b/packages/shared/src/engine/material/creation/marketPresets.ts similarity index 87% rename from src/shared/engine/material/creation/marketPresets.ts rename to packages/shared/src/engine/material/creation/marketPresets.ts index 220caab1..b4e3f42f 100644 --- a/src/shared/engine/material/creation/marketPresets.ts +++ b/packages/shared/src/engine/material/creation/marketPresets.ts @@ -1,4 +1,4 @@ -import type { ElementType, MaterialType, Quality } from '@shared/types/constants'; +import type { ElementType, MaterialType, Quality } from '@daoyou/shared/types/constants'; import marketData from './data/marketPresets.json'; export interface MarketMaterialPreset { diff --git a/src/shared/engine/material/creation/types.ts b/packages/shared/src/engine/material/creation/types.ts similarity index 93% rename from src/shared/engine/material/creation/types.ts rename to packages/shared/src/engine/material/creation/types.ts index 9e1ff741..22d32407 100644 --- a/src/shared/engine/material/creation/types.ts +++ b/packages/shared/src/engine/material/creation/types.ts @@ -3,8 +3,8 @@ import { type ElementType, type MaterialType, type Quality, -} from '@shared/types/constants'; -import type { MaterialDetails } from '@shared/types/cultivator'; +} from '@daoyou/shared/types/constants'; +import type { MaterialDetails } from '@daoyou/shared/types/cultivator'; import { z } from 'zod'; // AI 生成的部分:包含名称、描述、以及 AI 决定的元素 diff --git a/src/shared/engine/resource/types.ts b/packages/shared/src/engine/resource/types.ts similarity index 98% rename from src/shared/engine/resource/types.ts rename to packages/shared/src/engine/resource/types.ts index 2d1d1973..0f4f8ca9 100644 --- a/src/shared/engine/resource/types.ts +++ b/packages/shared/src/engine/resource/types.ts @@ -3,7 +3,7 @@ import type { Consumable, CultivationProgress, Material, -} from '@shared/types/cultivator'; +} from '@daoyou/shared/types/cultivator'; /** * 资源类型枚举 diff --git a/src/shared/engine/sect/README.md b/packages/shared/src/engine/sect/README.md similarity index 100% rename from src/shared/engine/sect/README.md rename to packages/shared/src/engine/sect/README.md diff --git a/src/shared/engine/sect/content/affairsPresentation.test.ts b/packages/shared/src/engine/sect/content/affairsPresentation.test.ts similarity index 100% rename from src/shared/engine/sect/content/affairsPresentation.test.ts rename to packages/shared/src/engine/sect/content/affairsPresentation.test.ts diff --git a/src/shared/engine/sect/content/index.ts b/packages/shared/src/engine/sect/content/index.ts similarity index 100% rename from src/shared/engine/sect/content/index.ts rename to packages/shared/src/engine/sect/content/index.ts diff --git a/src/shared/engine/sect/content/jiujie/DESIGN.md b/packages/shared/src/engine/sect/content/jiujie/DESIGN.md similarity index 99% rename from src/shared/engine/sect/content/jiujie/DESIGN.md rename to packages/shared/src/engine/sect/content/jiujie/DESIGN.md index 1f19d0c0..d3ecf2df 100644 --- a/src/shared/engine/sect/content/jiujie/DESIGN.md +++ b/packages/shared/src/engine/sect/content/jiujie/DESIGN.md @@ -1,6 +1,6 @@ # 九劫天宫设计稿(battle-v5 历史方案) -> 本文不再作为 combat-v6 实现来源。v6 九劫天宫以 [`docs/combat-v6-jiujie-tiangong-redesign.md`](../../../../../../docs/combat-v6-jiujie-tiangong-redesign.md) 为唯一战斗基线;劫雷、劫债、劫数、行动后处罚与《天威裁决》整技能免疫均已永久退出新体系。 +> 本文不再作为 combat-v6 实现来源。v6 九劫天宫以 [`docs/combat-v6-jiujie-tiangong-redesign.md`](../../../../../../../docs/combat-v6-jiujie-tiangong-redesign.md) 为唯一战斗基线;劫雷、劫债、劫数、行动后处罚与《天威裁决》整技能免疫均已永久退出新体系。 > 本稿记录九劫天宫首版生产规格。目标不是继续增加独立规则,而是把“行为触发型雷罚”压缩成现有宗门体系可以承载的:一套共通资源、一套共通技能底板、两条在同一底板上改变触发来源与兑现方式的道途。TypeScript 定义与专项测试是战斗行为的最终依据。 diff --git a/src/shared/engine/sect/content/jiujie/JiujieSectModule.ts b/packages/shared/src/engine/sect/content/jiujie/JiujieSectModule.ts similarity index 100% rename from src/shared/engine/sect/content/jiujie/JiujieSectModule.ts rename to packages/shared/src/engine/sect/content/jiujie/JiujieSectModule.ts diff --git a/src/shared/engine/sect/content/jiujie/definition.ts b/packages/shared/src/engine/sect/content/jiujie/definition.ts similarity index 100% rename from src/shared/engine/sect/content/jiujie/definition.ts rename to packages/shared/src/engine/sect/content/jiujie/definition.ts diff --git a/src/shared/engine/sect/content/jiujie/ids.ts b/packages/shared/src/engine/sect/content/jiujie/ids.ts similarity index 97% rename from src/shared/engine/sect/content/jiujie/ids.ts rename to packages/shared/src/engine/sect/content/jiujie/ids.ts index 6e3dad48..ac2e1d01 100644 --- a/src/shared/engine/sect/content/jiujie/ids.ts +++ b/packages/shared/src/engine/sect/content/jiujie/ids.ts @@ -1,4 +1,4 @@ -import { GameplayTags } from '@shared/engine/shared/tag-domain'; +import { GameplayTags } from '@daoyou/shared/engine/shared/tag-domain'; export const JIUJIE_SECT_ID = 'jiujie'; export const JIUJIE_CALAMITY = 'sect.jiujie.calamity'; diff --git a/src/shared/engine/sect/content/jiujie/index.ts b/packages/shared/src/engine/sect/content/jiujie/index.ts similarity index 100% rename from src/shared/engine/sect/content/jiujie/index.ts rename to packages/shared/src/engine/sect/content/jiujie/index.ts diff --git a/src/shared/engine/sect/content/jiujie/organization.ts b/packages/shared/src/engine/sect/content/jiujie/organization.ts similarity index 100% rename from src/shared/engine/sect/content/jiujie/organization.ts rename to packages/shared/src/engine/sect/content/jiujie/organization.ts diff --git a/src/shared/engine/sect/content/jiujie/presentation.ts b/packages/shared/src/engine/sect/content/jiujie/presentation.ts similarity index 100% rename from src/shared/engine/sect/content/jiujie/presentation.ts rename to packages/shared/src/engine/sect/content/jiujie/presentation.ts diff --git a/src/shared/engine/sect/content/lingxiao/DESIGN.md b/packages/shared/src/engine/sect/content/lingxiao/DESIGN.md similarity index 100% rename from src/shared/engine/sect/content/lingxiao/DESIGN.md rename to packages/shared/src/engine/sect/content/lingxiao/DESIGN.md diff --git a/src/shared/engine/sect/content/lingxiao/LingxiaoSectModule.ts b/packages/shared/src/engine/sect/content/lingxiao/LingxiaoSectModule.ts similarity index 100% rename from src/shared/engine/sect/content/lingxiao/LingxiaoSectModule.ts rename to packages/shared/src/engine/sect/content/lingxiao/LingxiaoSectModule.ts diff --git a/src/shared/engine/sect/content/lingxiao/definition.ts b/packages/shared/src/engine/sect/content/lingxiao/definition.ts similarity index 100% rename from src/shared/engine/sect/content/lingxiao/definition.ts rename to packages/shared/src/engine/sect/content/lingxiao/definition.ts diff --git a/src/shared/engine/sect/content/lingxiao/ids.ts b/packages/shared/src/engine/sect/content/lingxiao/ids.ts similarity index 100% rename from src/shared/engine/sect/content/lingxiao/ids.ts rename to packages/shared/src/engine/sect/content/lingxiao/ids.ts diff --git a/src/shared/engine/sect/content/lingxiao/index.ts b/packages/shared/src/engine/sect/content/lingxiao/index.ts similarity index 100% rename from src/shared/engine/sect/content/lingxiao/index.ts rename to packages/shared/src/engine/sect/content/lingxiao/index.ts diff --git a/src/shared/engine/sect/content/lingxiao/organization/LingxiaoOrganizationModule.test.ts b/packages/shared/src/engine/sect/content/lingxiao/organization/LingxiaoOrganizationModule.test.ts similarity index 100% rename from src/shared/engine/sect/content/lingxiao/organization/LingxiaoOrganizationModule.test.ts rename to packages/shared/src/engine/sect/content/lingxiao/organization/LingxiaoOrganizationModule.test.ts diff --git a/src/shared/engine/sect/content/lingxiao/organization/LingxiaoOrganizationModule.ts b/packages/shared/src/engine/sect/content/lingxiao/organization/LingxiaoOrganizationModule.ts similarity index 100% rename from src/shared/engine/sect/content/lingxiao/organization/LingxiaoOrganizationModule.ts rename to packages/shared/src/engine/sect/content/lingxiao/organization/LingxiaoOrganizationModule.ts diff --git a/src/shared/engine/sect/content/lingxiao/presentation.ts b/packages/shared/src/engine/sect/content/lingxiao/presentation.ts similarity index 100% rename from src/shared/engine/sect/content/lingxiao/presentation.ts rename to packages/shared/src/engine/sect/content/lingxiao/presentation.ts diff --git a/src/shared/engine/sect/content/onboardingPresentation.test.ts b/packages/shared/src/engine/sect/content/onboardingPresentation.test.ts similarity index 95% rename from src/shared/engine/sect/content/onboardingPresentation.test.ts rename to packages/shared/src/engine/sect/content/onboardingPresentation.test.ts index 86c04561..d8a31b60 100644 --- a/src/shared/engine/sect/content/onboardingPresentation.test.ts +++ b/packages/shared/src/engine/sect/content/onboardingPresentation.test.ts @@ -29,7 +29,7 @@ describe('production sect onboarding presentations', () => { const assetPath = onboarding?.script.backdrop.src.replace(/^\//, ''); expect(assetPath).toBeTruthy(); - expect(existsSync(join(process.cwd(), 'public', assetPath!))).toBe(true); + expect(existsSync(join(process.cwd(), 'apps/web/public', assetPath!))).toBe(true); } }); diff --git a/src/shared/engine/sect/content/productionRuntime.ts b/packages/shared/src/engine/sect/content/productionRuntime.ts similarity index 100% rename from src/shared/engine/sect/content/productionRuntime.ts rename to packages/shared/src/engine/sect/content/productionRuntime.ts diff --git a/src/shared/engine/sect/content/tianyan/TianyanSectModule.ts b/packages/shared/src/engine/sect/content/tianyan/TianyanSectModule.ts similarity index 100% rename from src/shared/engine/sect/content/tianyan/TianyanSectModule.ts rename to packages/shared/src/engine/sect/content/tianyan/TianyanSectModule.ts diff --git a/src/shared/engine/sect/content/tianyan/definition.ts b/packages/shared/src/engine/sect/content/tianyan/definition.ts similarity index 100% rename from src/shared/engine/sect/content/tianyan/definition.ts rename to packages/shared/src/engine/sect/content/tianyan/definition.ts diff --git a/src/shared/engine/sect/content/tianyan/ids.ts b/packages/shared/src/engine/sect/content/tianyan/ids.ts similarity index 100% rename from src/shared/engine/sect/content/tianyan/ids.ts rename to packages/shared/src/engine/sect/content/tianyan/ids.ts diff --git a/src/shared/engine/sect/content/tianyan/index.ts b/packages/shared/src/engine/sect/content/tianyan/index.ts similarity index 100% rename from src/shared/engine/sect/content/tianyan/index.ts rename to packages/shared/src/engine/sect/content/tianyan/index.ts diff --git a/src/shared/engine/sect/content/tianyan/organization.ts b/packages/shared/src/engine/sect/content/tianyan/organization.ts similarity index 100% rename from src/shared/engine/sect/content/tianyan/organization.ts rename to packages/shared/src/engine/sect/content/tianyan/organization.ts diff --git a/src/shared/engine/sect/content/tianyan/presentation.ts b/packages/shared/src/engine/sect/content/tianyan/presentation.ts similarity index 100% rename from src/shared/engine/sect/content/tianyan/presentation.ts rename to packages/shared/src/engine/sect/content/tianyan/presentation.ts diff --git a/src/shared/engine/sect/content/wuxiang/DESIGN.md b/packages/shared/src/engine/sect/content/wuxiang/DESIGN.md similarity index 100% rename from src/shared/engine/sect/content/wuxiang/DESIGN.md rename to packages/shared/src/engine/sect/content/wuxiang/DESIGN.md diff --git a/src/shared/engine/sect/content/wuxiang/WuxiangSectModule.ts b/packages/shared/src/engine/sect/content/wuxiang/WuxiangSectModule.ts similarity index 100% rename from src/shared/engine/sect/content/wuxiang/WuxiangSectModule.ts rename to packages/shared/src/engine/sect/content/wuxiang/WuxiangSectModule.ts diff --git a/src/shared/engine/sect/content/wuxiang/definition.ts b/packages/shared/src/engine/sect/content/wuxiang/definition.ts similarity index 100% rename from src/shared/engine/sect/content/wuxiang/definition.ts rename to packages/shared/src/engine/sect/content/wuxiang/definition.ts diff --git a/src/shared/engine/sect/content/wuxiang/ids.ts b/packages/shared/src/engine/sect/content/wuxiang/ids.ts similarity index 100% rename from src/shared/engine/sect/content/wuxiang/ids.ts rename to packages/shared/src/engine/sect/content/wuxiang/ids.ts diff --git a/src/shared/engine/sect/content/wuxiang/index.ts b/packages/shared/src/engine/sect/content/wuxiang/index.ts similarity index 100% rename from src/shared/engine/sect/content/wuxiang/index.ts rename to packages/shared/src/engine/sect/content/wuxiang/index.ts diff --git a/src/shared/engine/sect/content/wuxiang/organization.ts b/packages/shared/src/engine/sect/content/wuxiang/organization.ts similarity index 100% rename from src/shared/engine/sect/content/wuxiang/organization.ts rename to packages/shared/src/engine/sect/content/wuxiang/organization.ts diff --git a/src/shared/engine/sect/content/wuxiang/presentation.ts b/packages/shared/src/engine/sect/content/wuxiang/presentation.ts similarity index 100% rename from src/shared/engine/sect/content/wuxiang/presentation.ts rename to packages/shared/src/engine/sect/content/wuxiang/presentation.ts diff --git a/src/shared/engine/sect/content/youdu/YouduSectModule.ts b/packages/shared/src/engine/sect/content/youdu/YouduSectModule.ts similarity index 100% rename from src/shared/engine/sect/content/youdu/YouduSectModule.ts rename to packages/shared/src/engine/sect/content/youdu/YouduSectModule.ts diff --git a/src/shared/engine/sect/content/youdu/definition.ts b/packages/shared/src/engine/sect/content/youdu/definition.ts similarity index 100% rename from src/shared/engine/sect/content/youdu/definition.ts rename to packages/shared/src/engine/sect/content/youdu/definition.ts diff --git a/src/shared/engine/sect/content/youdu/ids.ts b/packages/shared/src/engine/sect/content/youdu/ids.ts similarity index 92% rename from src/shared/engine/sect/content/youdu/ids.ts rename to packages/shared/src/engine/sect/content/youdu/ids.ts index 11cc887f..a7e76c93 100644 --- a/src/shared/engine/sect/content/youdu/ids.ts +++ b/packages/shared/src/engine/sect/content/youdu/ids.ts @@ -24,5 +24,5 @@ export const YOUDU_VISIBLE_ABILITY_IDS = [ export const youduAbilityTag = (abilityId: string) => GameplayTags.ABILITY.SECT.ability(YOUDU_SECT_ID, abilityId); -import { GameplayTags } from '@shared/engine/shared/tag-domain'; +import { GameplayTags } from '@daoyou/shared/engine/shared/tag-domain'; diff --git a/src/shared/engine/sect/content/youdu/index.ts b/packages/shared/src/engine/sect/content/youdu/index.ts similarity index 100% rename from src/shared/engine/sect/content/youdu/index.ts rename to packages/shared/src/engine/sect/content/youdu/index.ts diff --git a/src/shared/engine/sect/content/youdu/organization.ts b/packages/shared/src/engine/sect/content/youdu/organization.ts similarity index 100% rename from src/shared/engine/sect/content/youdu/organization.ts rename to packages/shared/src/engine/sect/content/youdu/organization.ts diff --git a/src/shared/engine/sect/content/youdu/presentation.ts b/packages/shared/src/engine/sect/content/youdu/presentation.ts similarity index 100% rename from src/shared/engine/sect/content/youdu/presentation.ts rename to packages/shared/src/engine/sect/content/youdu/presentation.ts diff --git a/src/shared/engine/sect/core/domain/StandardSectRules.ts b/packages/shared/src/engine/sect/core/domain/StandardSectRules.ts similarity index 100% rename from src/shared/engine/sect/core/domain/StandardSectRules.ts rename to packages/shared/src/engine/sect/core/domain/StandardSectRules.ts diff --git a/src/shared/engine/sect/core/domain/contexts.ts b/packages/shared/src/engine/sect/core/domain/contexts.ts similarity index 62% rename from src/shared/engine/sect/core/domain/contexts.ts rename to packages/shared/src/engine/sect/core/domain/contexts.ts index 91eb1014..6665f2fd 100644 --- a/src/shared/engine/sect/core/domain/contexts.ts +++ b/packages/shared/src/engine/sect/core/domain/contexts.ts @@ -2,8 +2,8 @@ import type { PlayerRaceId } from './definitions'; export interface SectAdmissionContext { playerRace: PlayerRaceId; - realm: import('@shared/types/constants').RealmType; - stage: import('@shared/types/constants').RealmStage; + realm: import('@daoyou/shared/types/constants').RealmType; + stage: import('@daoyou/shared/types/constants').RealmStage; } export interface SectAdmissionResult { diff --git a/src/shared/engine/sect/core/domain/definitions.ts b/packages/shared/src/engine/sect/core/domain/definitions.ts similarity index 98% rename from src/shared/engine/sect/core/domain/definitions.ts rename to packages/shared/src/engine/sect/core/domain/definitions.ts index 8a9f4e62..b62d2f45 100644 --- a/src/shared/engine/sect/core/domain/definitions.ts +++ b/packages/shared/src/engine/sect/core/domain/definitions.ts @@ -1,4 +1,4 @@ -import type { RealmStage, RealmType } from '@shared/types/constants'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; export type PlayerRaceId = 'human'; export type SectId = string; diff --git a/src/shared/engine/sect/core/domain/index.ts b/packages/shared/src/engine/sect/core/domain/index.ts similarity index 100% rename from src/shared/engine/sect/core/domain/index.ts rename to packages/shared/src/engine/sect/core/domain/index.ts diff --git a/src/shared/engine/sect/core/domain/organization.ts b/packages/shared/src/engine/sect/core/domain/organization.ts similarity index 92% rename from src/shared/engine/sect/core/domain/organization.ts rename to packages/shared/src/engine/sect/core/domain/organization.ts index 27345b8a..19da23d8 100644 --- a/src/shared/engine/sect/core/domain/organization.ts +++ b/packages/shared/src/engine/sect/core/domain/organization.ts @@ -1,5 +1,5 @@ -import { getRealmStageRank } from '@shared/config/realmProgression'; -import type { RealmStage, RealmType } from '@shared/types/constants'; +import { getRealmStageRank } from '@daoyou/shared/config/realmProgression'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; export const SECT_DISCIPLE_RANKS = [ 'registered', diff --git a/src/shared/engine/sect/core/domain/state.ts b/packages/shared/src/engine/sect/core/domain/state.ts similarity index 100% rename from src/shared/engine/sect/core/domain/state.ts rename to packages/shared/src/engine/sect/core/domain/state.ts diff --git a/src/shared/engine/sect/core/index.ts b/packages/shared/src/engine/sect/core/index.ts similarity index 100% rename from src/shared/engine/sect/core/index.ts rename to packages/shared/src/engine/sect/core/index.ts diff --git a/src/shared/engine/sect/core/mining/MiningGameRules.test.ts b/packages/shared/src/engine/sect/core/mining/MiningGameRules.test.ts similarity index 100% rename from src/shared/engine/sect/core/mining/MiningGameRules.test.ts rename to packages/shared/src/engine/sect/core/mining/MiningGameRules.test.ts diff --git a/src/shared/engine/sect/core/mining/MiningGameRules.ts b/packages/shared/src/engine/sect/core/mining/MiningGameRules.ts similarity index 100% rename from src/shared/engine/sect/core/mining/MiningGameRules.ts rename to packages/shared/src/engine/sect/core/mining/MiningGameRules.ts diff --git a/src/shared/engine/sect/core/mining/MiningRewards.test.ts b/packages/shared/src/engine/sect/core/mining/MiningRewards.test.ts similarity index 100% rename from src/shared/engine/sect/core/mining/MiningRewards.test.ts rename to packages/shared/src/engine/sect/core/mining/MiningRewards.test.ts diff --git a/src/shared/engine/sect/core/mining/MiningRewards.ts b/packages/shared/src/engine/sect/core/mining/MiningRewards.ts similarity index 98% rename from src/shared/engine/sect/core/mining/MiningRewards.ts rename to packages/shared/src/engine/sect/core/mining/MiningRewards.ts index 2f4cdd47..83b4374c 100644 --- a/src/shared/engine/sect/core/mining/MiningRewards.ts +++ b/packages/shared/src/engine/sect/core/mining/MiningRewards.ts @@ -2,7 +2,7 @@ import { QUALITY_ORDER, type Quality, type RealmType, -} from '@shared/types/constants'; +} from '@daoyou/shared/types/constants'; import { SECT_REALM_QUALITY_RULES } from '../organization/taskRequirements'; import type { SectTaskRewardSnapshot } from '../organization/taskRewards'; import type { MiningScoreTier } from './MiningGameRules'; diff --git a/src/shared/engine/sect/core/mining/index.ts b/packages/shared/src/engine/sect/core/mining/index.ts similarity index 100% rename from src/shared/engine/sect/core/mining/index.ts rename to packages/shared/src/engine/sect/core/mining/index.ts diff --git a/src/shared/engine/sect/core/organization/StandardSectCapabilityPolicy.ts b/packages/shared/src/engine/sect/core/organization/StandardSectCapabilityPolicy.ts similarity index 100% rename from src/shared/engine/sect/core/organization/StandardSectCapabilityPolicy.ts rename to packages/shared/src/engine/sect/core/organization/StandardSectCapabilityPolicy.ts diff --git a/src/shared/engine/sect/core/organization/StandardSectOrganizationModule.ts b/packages/shared/src/engine/sect/core/organization/StandardSectOrganizationModule.ts similarity index 99% rename from src/shared/engine/sect/core/organization/StandardSectOrganizationModule.ts rename to packages/shared/src/engine/sect/core/organization/StandardSectOrganizationModule.ts index 4cc64207..9ee75384 100644 --- a/src/shared/engine/sect/core/organization/StandardSectOrganizationModule.ts +++ b/packages/shared/src/engine/sect/core/organization/StandardSectOrganizationModule.ts @@ -1,4 +1,4 @@ -import type { RealmType } from '@shared/types/constants'; +import type { RealmType } from '@daoyou/shared/types/constants'; import { SECT_RANK_METHOD_CAP, type SectDiscipleRank, diff --git a/src/shared/engine/sect/core/organization/construction.test.ts b/packages/shared/src/engine/sect/core/organization/construction.test.ts similarity index 100% rename from src/shared/engine/sect/core/organization/construction.test.ts rename to packages/shared/src/engine/sect/core/organization/construction.test.ts diff --git a/src/shared/engine/sect/core/organization/construction.ts b/packages/shared/src/engine/sect/core/organization/construction.ts similarity index 100% rename from src/shared/engine/sect/core/organization/construction.ts rename to packages/shared/src/engine/sect/core/organization/construction.ts diff --git a/src/shared/engine/sect/core/organization/contracts.ts b/packages/shared/src/engine/sect/core/organization/contracts.ts similarity index 97% rename from src/shared/engine/sect/core/organization/contracts.ts rename to packages/shared/src/engine/sect/core/organization/contracts.ts index 0c085250..afc2d619 100644 --- a/src/shared/engine/sect/core/organization/contracts.ts +++ b/packages/shared/src/engine/sect/core/organization/contracts.ts @@ -1,5 +1,5 @@ -import type { DailyTaskDifficulty } from '@shared/engine/cultivation/exp-gain-strategies/types'; -import type { RealmType } from '@shared/types/constants'; +import type { DailyTaskDifficulty } from '@daoyou/shared/engine/cultivation/exp-gain-strategies/types'; +import type { RealmType } from '@daoyou/shared/types/constants'; import type { SectDiscipleRank, SectRankRequirement, diff --git a/src/shared/engine/sect/core/organization/domain.test.ts b/packages/shared/src/engine/sect/core/organization/domain.test.ts similarity index 100% rename from src/shared/engine/sect/core/organization/domain.test.ts rename to packages/shared/src/engine/sect/core/organization/domain.test.ts diff --git a/src/shared/engine/sect/core/organization/domain.ts b/packages/shared/src/engine/sect/core/organization/domain.ts similarity index 100% rename from src/shared/engine/sect/core/organization/domain.ts rename to packages/shared/src/engine/sect/core/organization/domain.ts diff --git a/src/shared/engine/sect/core/organization/index.ts b/packages/shared/src/engine/sect/core/organization/index.ts similarity index 100% rename from src/shared/engine/sect/core/organization/index.ts rename to packages/shared/src/engine/sect/core/organization/index.ts diff --git a/src/shared/engine/sect/core/organization/promotionDialogue.test.ts b/packages/shared/src/engine/sect/core/organization/promotionDialogue.test.ts similarity index 100% rename from src/shared/engine/sect/core/organization/promotionDialogue.test.ts rename to packages/shared/src/engine/sect/core/organization/promotionDialogue.test.ts diff --git a/src/shared/engine/sect/core/organization/promotionDialogue.ts b/packages/shared/src/engine/sect/core/organization/promotionDialogue.ts similarity index 100% rename from src/shared/engine/sect/core/organization/promotionDialogue.ts rename to packages/shared/src/engine/sect/core/organization/promotionDialogue.ts diff --git a/src/shared/engine/sect/core/organization/sectBenefits.ts b/packages/shared/src/engine/sect/core/organization/sectBenefits.ts similarity index 100% rename from src/shared/engine/sect/core/organization/sectBenefits.ts rename to packages/shared/src/engine/sect/core/organization/sectBenefits.ts diff --git a/src/shared/engine/sect/core/organization/specifications.test.ts b/packages/shared/src/engine/sect/core/organization/specifications.test.ts similarity index 100% rename from src/shared/engine/sect/core/organization/specifications.test.ts rename to packages/shared/src/engine/sect/core/organization/specifications.test.ts diff --git a/src/shared/engine/sect/core/organization/specifications.ts b/packages/shared/src/engine/sect/core/organization/specifications.ts similarity index 93% rename from src/shared/engine/sect/core/organization/specifications.ts rename to packages/shared/src/engine/sect/core/organization/specifications.ts index 9fdf13dd..fd455124 100644 --- a/src/shared/engine/sect/core/organization/specifications.ts +++ b/packages/shared/src/engine/sect/core/organization/specifications.ts @@ -1,7 +1,7 @@ -import { isPillSpec } from '@shared/lib/consumables'; -import type { RealmStage, RealmType } from '@shared/types/constants'; -import { QUALITY_ORDER, type Quality } from '@shared/types/constants'; -import type { ConsumableSpec } from '@shared/types/consumable'; +import { isPillSpec } from '@daoyou/shared/lib/consumables'; +import type { RealmStage, RealmType } from '@daoyou/shared/types/constants'; +import { QUALITY_ORDER, type Quality } from '@daoyou/shared/types/constants'; +import type { ConsumableSpec } from '@daoyou/shared/types/consumable'; import { realmMeetsSectRank, type SectRankRequirement, diff --git a/src/shared/engine/sect/core/organization/stipend.test.ts b/packages/shared/src/engine/sect/core/organization/stipend.test.ts similarity index 95% rename from src/shared/engine/sect/core/organization/stipend.test.ts rename to packages/shared/src/engine/sect/core/organization/stipend.test.ts index ee9941ce..5e8a4eaf 100644 --- a/src/shared/engine/sect/core/organization/stipend.test.ts +++ b/packages/shared/src/engine/sect/core/organization/stipend.test.ts @@ -1,4 +1,4 @@ -import { REALM_VALUES } from '@shared/types/constants'; +import { REALM_VALUES } from '@daoyou/shared/types/constants'; import { describe, expect, it } from 'vitest'; import { calculateStandardSectStipendBase, diff --git a/src/shared/engine/sect/core/organization/stipend.ts b/packages/shared/src/engine/sect/core/organization/stipend.ts similarity index 83% rename from src/shared/engine/sect/core/organization/stipend.ts rename to packages/shared/src/engine/sect/core/organization/stipend.ts index f0acba05..18b3f572 100644 --- a/src/shared/engine/sect/core/organization/stipend.ts +++ b/packages/shared/src/engine/sect/core/organization/stipend.ts @@ -1,5 +1,5 @@ -import { REALM_DAILY_EXP_BUDGET } from '@shared/config/cultivationExpGain'; -import type { RealmType } from '@shared/types/constants'; +import { REALM_DAILY_EXP_BUDGET } from '@daoyou/shared/config/cultivationExpGain'; +import type { RealmType } from '@daoyou/shared/types/constants'; import type { SectDiscipleRank } from '../domain'; export const STANDARD_SECT_STIPEND_CURVE = { diff --git a/src/shared/engine/sect/core/organization/taskAbandon.test.ts b/packages/shared/src/engine/sect/core/organization/taskAbandon.test.ts similarity index 100% rename from src/shared/engine/sect/core/organization/taskAbandon.test.ts rename to packages/shared/src/engine/sect/core/organization/taskAbandon.test.ts diff --git a/src/shared/engine/sect/core/organization/taskAbandon.ts b/packages/shared/src/engine/sect/core/organization/taskAbandon.ts similarity index 100% rename from src/shared/engine/sect/core/organization/taskAbandon.ts rename to packages/shared/src/engine/sect/core/organization/taskAbandon.ts diff --git a/src/shared/engine/sect/core/organization/taskBattleTarget.test.ts b/packages/shared/src/engine/sect/core/organization/taskBattleTarget.test.ts similarity index 100% rename from src/shared/engine/sect/core/organization/taskBattleTarget.test.ts rename to packages/shared/src/engine/sect/core/organization/taskBattleTarget.test.ts diff --git a/src/shared/engine/sect/core/organization/taskBattleTarget.ts b/packages/shared/src/engine/sect/core/organization/taskBattleTarget.ts similarity index 95% rename from src/shared/engine/sect/core/organization/taskBattleTarget.ts rename to packages/shared/src/engine/sect/core/organization/taskBattleTarget.ts index 05986e1a..b6203d39 100644 --- a/src/shared/engine/sect/core/organization/taskBattleTarget.ts +++ b/packages/shared/src/engine/sect/core/organization/taskBattleTarget.ts @@ -1,10 +1,10 @@ -import { SectV6TargetSchema } from '@shared/contracts/combatV6SectTask'; +import { SectV6TargetSchema } from '@daoyou/shared/contracts/combatV6SectTask'; import { REALM_STAGE_VALUES, REALM_VALUES, type RealmStage, type RealmType, -} from '@shared/types/constants'; +} from '@daoyou/shared/types/constants'; import { z } from 'zod'; import type { SectBattleTargetAcquisition } from './contracts'; diff --git a/src/shared/engine/sect/core/organization/taskDialogue.test.ts b/packages/shared/src/engine/sect/core/organization/taskDialogue.test.ts similarity index 100% rename from src/shared/engine/sect/core/organization/taskDialogue.test.ts rename to packages/shared/src/engine/sect/core/organization/taskDialogue.test.ts diff --git a/src/shared/engine/sect/core/organization/taskDialogue.ts b/packages/shared/src/engine/sect/core/organization/taskDialogue.ts similarity index 100% rename from src/shared/engine/sect/core/organization/taskDialogue.ts rename to packages/shared/src/engine/sect/core/organization/taskDialogue.ts diff --git a/src/shared/engine/sect/core/organization/taskOffer.test.ts b/packages/shared/src/engine/sect/core/organization/taskOffer.test.ts similarity index 100% rename from src/shared/engine/sect/core/organization/taskOffer.test.ts rename to packages/shared/src/engine/sect/core/organization/taskOffer.test.ts diff --git a/src/shared/engine/sect/core/organization/taskOffer.ts b/packages/shared/src/engine/sect/core/organization/taskOffer.ts similarity index 95% rename from src/shared/engine/sect/core/organization/taskOffer.ts rename to packages/shared/src/engine/sect/core/organization/taskOffer.ts index 44c7097a..7610c0df 100644 --- a/src/shared/engine/sect/core/organization/taskOffer.ts +++ b/packages/shared/src/engine/sect/core/organization/taskOffer.ts @@ -1,10 +1,10 @@ -import type { DailyTaskDifficulty } from '@shared/engine/cultivation/exp-gain-strategies/types'; +import type { DailyTaskDifficulty } from '@daoyou/shared/engine/cultivation/exp-gain-strategies/types'; import { REALM_STAGE_VALUES, REALM_VALUES, type RealmStage, type RealmType, -} from '@shared/types/constants'; +} from '@daoyou/shared/types/constants'; import { z } from 'zod'; import { MINING_SCORE_TIERS } from '../mining/MiningGameRules'; import { diff --git a/src/shared/engine/sect/core/organization/taskRequirementMatcher.test.ts b/packages/shared/src/engine/sect/core/organization/taskRequirementMatcher.test.ts similarity index 100% rename from src/shared/engine/sect/core/organization/taskRequirementMatcher.test.ts rename to packages/shared/src/engine/sect/core/organization/taskRequirementMatcher.test.ts diff --git a/src/shared/engine/sect/core/organization/taskRequirementMatcher.ts b/packages/shared/src/engine/sect/core/organization/taskRequirementMatcher.ts similarity index 95% rename from src/shared/engine/sect/core/organization/taskRequirementMatcher.ts rename to packages/shared/src/engine/sect/core/organization/taskRequirementMatcher.ts index 9afba67e..135be3a0 100644 --- a/src/shared/engine/sect/core/organization/taskRequirementMatcher.ts +++ b/packages/shared/src/engine/sect/core/organization/taskRequirementMatcher.ts @@ -1,17 +1,17 @@ -import { getLevelRealmStage } from '@shared/config/realmProgression'; -import type { DaoEquipmentSlot } from '@shared/engine/combat-v6/equipment/types'; -import { CULTIVATION_BOOST_STATUS_KEY } from '@shared/lib/cultivationBoost'; +import { getLevelRealmStage } from '@daoyou/shared/config/realmProgression'; +import type { DaoEquipmentSlot } from '@daoyou/shared/engine/combat-v6/equipment/types'; +import { CULTIVATION_BOOST_STATUS_KEY } from '@daoyou/shared/lib/cultivationBoost'; import { QUALITY_ORDER, type ElementType, type MaterialType, type Quality, -} from '@shared/types/constants'; +} from '@daoyou/shared/types/constants'; import type { PillAppearanceGrade, PillFamily, PillSpec, -} from '@shared/types/consumable'; +} from '@daoyou/shared/types/consumable'; import type { SectDeliveryRequirement, SectMaterialDeliveryRequirement, diff --git a/src/shared/engine/sect/core/organization/taskRequirements.test.ts b/packages/shared/src/engine/sect/core/organization/taskRequirements.test.ts similarity index 98% rename from src/shared/engine/sect/core/organization/taskRequirements.test.ts rename to packages/shared/src/engine/sect/core/organization/taskRequirements.test.ts index 1739487e..7bfdc506 100644 --- a/src/shared/engine/sect/core/organization/taskRequirements.test.ts +++ b/packages/shared/src/engine/sect/core/organization/taskRequirements.test.ts @@ -1,9 +1,9 @@ -import { BLUEPRINTS } from '@shared/items/definitions/equipment-blueprints'; +import { BLUEPRINTS } from '@daoyou/shared/items/definitions/equipment-blueprints'; import { QUALITY_ORDER, REALM_STAGE_VALUES, REALM_VALUES, -} from '@shared/types/constants'; +} from '@daoyou/shared/types/constants'; import { describe, expect, it } from 'vitest'; import { SECT_REALM_QUALITY_RULES, diff --git a/src/shared/engine/sect/core/organization/taskRequirements.ts b/packages/shared/src/engine/sect/core/organization/taskRequirements.ts similarity index 95% rename from src/shared/engine/sect/core/organization/taskRequirements.ts rename to packages/shared/src/engine/sect/core/organization/taskRequirements.ts index 309bccd0..8c5fab15 100644 --- a/src/shared/engine/sect/core/organization/taskRequirements.ts +++ b/packages/shared/src/engine/sect/core/organization/taskRequirements.ts @@ -1,11 +1,11 @@ -import { getLevelRealmStage } from '@shared/config/realmProgression'; -import { DAO_EQUIPMENT_SLOTS } from '@shared/engine/combat-v6/equipment/types'; -import { combatCharacterLevel } from '@shared/engine/combat-v6/projection/character-level'; -import type { DailyTaskDifficulty } from '@shared/engine/cultivation/exp-gain-strategies/types'; -import { EQUIPMENT_SLOT_NAMES } from '@shared/items/definitions/equipment-blueprints'; -import { getMaterialTypeLabel } from '@shared/lib/gameConceptDisplay'; -import { getPillAppearanceLabel } from '@shared/lib/pillAppearance'; -import type { RealmStage } from '@shared/types/constants'; +import { getLevelRealmStage } from '@daoyou/shared/config/realmProgression'; +import { DAO_EQUIPMENT_SLOTS } from '@daoyou/shared/engine/combat-v6/equipment/types'; +import { combatCharacterLevel } from '@daoyou/shared/engine/combat-v6/projection/character-level'; +import type { DailyTaskDifficulty } from '@daoyou/shared/engine/cultivation/exp-gain-strategies/types'; +import { EQUIPMENT_SLOT_NAMES } from '@daoyou/shared/items/definitions/equipment-blueprints'; +import { getMaterialTypeLabel } from '@daoyou/shared/lib/gameConceptDisplay'; +import { getPillAppearanceLabel } from '@daoyou/shared/lib/pillAppearance'; +import type { RealmStage } from '@daoyou/shared/types/constants'; import { MATERIAL_TYPE_VALUES, QUALITY_ORDER, @@ -15,13 +15,13 @@ import { type MaterialType, type Quality, type RealmType, -} from '@shared/types/constants'; +} from '@daoyou/shared/types/constants'; import { PILL_APPEARANCE_GRADE_VALUES, PILL_FAMILY_VALUES, type PillAppearanceGrade, type PillFamily, -} from '@shared/types/consumable'; +} from '@daoyou/shared/types/consumable'; import { z } from 'zod'; import type { SectTaskDialogueEmphasis, diff --git a/src/shared/engine/sect/core/organization/taskRewards.test.ts b/packages/shared/src/engine/sect/core/organization/taskRewards.test.ts similarity index 99% rename from src/shared/engine/sect/core/organization/taskRewards.test.ts rename to packages/shared/src/engine/sect/core/organization/taskRewards.test.ts index 05c16342..16d5cf4f 100644 --- a/src/shared/engine/sect/core/organization/taskRewards.test.ts +++ b/packages/shared/src/engine/sect/core/organization/taskRewards.test.ts @@ -1,4 +1,4 @@ -import { REALM_VALUES } from '@shared/types/constants'; +import { REALM_VALUES } from '@daoyou/shared/types/constants'; import { describe, expect, it } from 'vitest'; import { scaleMiningTaskReward } from '../mining/MiningRewards'; import { diff --git a/src/shared/engine/sect/core/organization/taskRewards.ts b/packages/shared/src/engine/sect/core/organization/taskRewards.ts similarity index 96% rename from src/shared/engine/sect/core/organization/taskRewards.ts rename to packages/shared/src/engine/sect/core/organization/taskRewards.ts index 34dbf1c8..9c0ec0f6 100644 --- a/src/shared/engine/sect/core/organization/taskRewards.ts +++ b/packages/shared/src/engine/sect/core/organization/taskRewards.ts @@ -1,14 +1,14 @@ import { DAILY_TASK_EXP_BUDGET, REALM_DAILY_EXP_BUDGET, -} from '@shared/config/cultivationExpGain'; -import type { DailyTaskDifficulty } from '@shared/engine/cultivation/exp-gain-strategies/types'; +} from '@daoyou/shared/config/cultivationExpGain'; +import type { DailyTaskDifficulty } from '@daoyou/shared/engine/cultivation/exp-gain-strategies/types'; import { QUALITY_VALUES, REALM_ORDER, type RealmStage, type RealmType, -} from '@shared/types/constants'; +} from '@daoyou/shared/types/constants'; import { z } from 'zod'; export const STANDARD_SECT_TASK_REWARD_CURVE = { diff --git a/src/shared/engine/sect/core/plugin/StandardSectModule.ts b/packages/shared/src/engine/sect/core/plugin/StandardSectModule.ts similarity index 100% rename from src/shared/engine/sect/core/plugin/StandardSectModule.ts rename to packages/shared/src/engine/sect/core/plugin/StandardSectModule.ts diff --git a/src/shared/engine/sect/core/plugin/contracts.ts b/packages/shared/src/engine/sect/core/plugin/contracts.ts similarity index 100% rename from src/shared/engine/sect/core/plugin/contracts.ts rename to packages/shared/src/engine/sect/core/plugin/contracts.ts diff --git a/src/shared/engine/sect/core/plugin/index.ts b/packages/shared/src/engine/sect/core/plugin/index.ts similarity index 100% rename from src/shared/engine/sect/core/plugin/index.ts rename to packages/shared/src/engine/sect/core/plugin/index.ts diff --git a/src/shared/engine/sect/core/plugin/policies.ts b/packages/shared/src/engine/sect/core/plugin/policies.ts similarity index 100% rename from src/shared/engine/sect/core/plugin/policies.ts rename to packages/shared/src/engine/sect/core/plugin/policies.ts diff --git a/src/shared/engine/sect/core/presentation/facilityDialogue.test.ts b/packages/shared/src/engine/sect/core/presentation/facilityDialogue.test.ts similarity index 100% rename from src/shared/engine/sect/core/presentation/facilityDialogue.test.ts rename to packages/shared/src/engine/sect/core/presentation/facilityDialogue.test.ts diff --git a/src/shared/engine/sect/core/presentation/facilityDialogue.ts b/packages/shared/src/engine/sect/core/presentation/facilityDialogue.ts similarity index 100% rename from src/shared/engine/sect/core/presentation/facilityDialogue.ts rename to packages/shared/src/engine/sect/core/presentation/facilityDialogue.ts diff --git a/src/shared/engine/sect/core/presentation/index.ts b/packages/shared/src/engine/sect/core/presentation/index.ts similarity index 100% rename from src/shared/engine/sect/core/presentation/index.ts rename to packages/shared/src/engine/sect/core/presentation/index.ts diff --git a/src/shared/engine/sect/core/presentation/sectPresentation.test.ts b/packages/shared/src/engine/sect/core/presentation/sectPresentation.test.ts similarity index 100% rename from src/shared/engine/sect/core/presentation/sectPresentation.test.ts rename to packages/shared/src/engine/sect/core/presentation/sectPresentation.test.ts diff --git a/src/shared/engine/sect/core/presentation/sectPresentation.ts b/packages/shared/src/engine/sect/core/presentation/sectPresentation.ts similarity index 99% rename from src/shared/engine/sect/core/presentation/sectPresentation.ts rename to packages/shared/src/engine/sect/core/presentation/sectPresentation.ts index 353034e9..4ecdc6c9 100644 --- a/src/shared/engine/sect/core/presentation/sectPresentation.ts +++ b/packages/shared/src/engine/sect/core/presentation/sectPresentation.ts @@ -1,4 +1,4 @@ -import type { NarrativePerformanceScript } from '@shared/types/narrative'; +import type { NarrativePerformanceScript } from '@daoyou/shared/types/narrative'; import type { SectCapabilityKey } from '../organization/contracts'; export type SectSceneKey = diff --git a/src/shared/engine/sect/core/runtime/SectRegistry.ts b/packages/shared/src/engine/sect/core/runtime/SectRegistry.ts similarity index 100% rename from src/shared/engine/sect/core/runtime/SectRegistry.ts rename to packages/shared/src/engine/sect/core/runtime/SectRegistry.ts diff --git a/src/shared/engine/sect/core/runtime/SectRuntime.ts b/packages/shared/src/engine/sect/core/runtime/SectRuntime.ts similarity index 100% rename from src/shared/engine/sect/core/runtime/SectRuntime.ts rename to packages/shared/src/engine/sect/core/runtime/SectRuntime.ts diff --git a/src/shared/engine/sect/core/runtime/index.ts b/packages/shared/src/engine/sect/core/runtime/index.ts similarity index 100% rename from src/shared/engine/sect/core/runtime/index.ts rename to packages/shared/src/engine/sect/core/runtime/index.ts diff --git a/src/shared/engine/sect/core/sweep/SweepGameRules.test.ts b/packages/shared/src/engine/sect/core/sweep/SweepGameRules.test.ts similarity index 100% rename from src/shared/engine/sect/core/sweep/SweepGameRules.test.ts rename to packages/shared/src/engine/sect/core/sweep/SweepGameRules.test.ts diff --git a/src/shared/engine/sect/core/sweep/SweepGameRules.ts b/packages/shared/src/engine/sect/core/sweep/SweepGameRules.ts similarity index 100% rename from src/shared/engine/sect/core/sweep/SweepGameRules.ts rename to packages/shared/src/engine/sect/core/sweep/SweepGameRules.ts diff --git a/src/shared/engine/sect/core/sweep/index.ts b/packages/shared/src/engine/sect/core/sweep/index.ts similarity index 100% rename from src/shared/engine/sect/core/sweep/index.ts rename to packages/shared/src/engine/sect/core/sweep/index.ts diff --git a/src/shared/engine/sect/core/tests/organization.test.ts b/packages/shared/src/engine/sect/core/tests/organization.test.ts similarity index 100% rename from src/shared/engine/sect/core/tests/organization.test.ts rename to packages/shared/src/engine/sect/core/tests/organization.test.ts diff --git a/src/shared/engine/sect/core/tests/registry.test.ts b/packages/shared/src/engine/sect/core/tests/registry.test.ts similarity index 100% rename from src/shared/engine/sect/core/tests/registry.test.ts rename to packages/shared/src/engine/sect/core/tests/registry.test.ts diff --git a/src/shared/engine/sect/core/validation/SectDefinitionRule.ts b/packages/shared/src/engine/sect/core/validation/SectDefinitionRule.ts similarity index 99% rename from src/shared/engine/sect/core/validation/SectDefinitionRule.ts rename to packages/shared/src/engine/sect/core/validation/SectDefinitionRule.ts index e6a3d250..d610bcbb 100644 --- a/src/shared/engine/sect/core/validation/SectDefinitionRule.ts +++ b/packages/shared/src/engine/sect/core/validation/SectDefinitionRule.ts @@ -1,4 +1,4 @@ -import { getRealmStageRank } from '@shared/config/realmProgression'; +import { getRealmStageRank } from '@daoyou/shared/config/realmProgression'; import { StandardSectRules, sectAbilityMethodId, diff --git a/src/shared/engine/sect/core/validation/SectModuleValidator.ts b/packages/shared/src/engine/sect/core/validation/SectModuleValidator.ts similarity index 100% rename from src/shared/engine/sect/core/validation/SectModuleValidator.ts rename to packages/shared/src/engine/sect/core/validation/SectModuleValidator.ts diff --git a/src/shared/engine/sect/core/validation/SectStateValidator.ts b/packages/shared/src/engine/sect/core/validation/SectStateValidator.ts similarity index 100% rename from src/shared/engine/sect/core/validation/SectStateValidator.ts rename to packages/shared/src/engine/sect/core/validation/SectStateValidator.ts diff --git a/src/shared/engine/sect/core/validation/ValidationPipeline.ts b/packages/shared/src/engine/sect/core/validation/ValidationPipeline.ts similarity index 100% rename from src/shared/engine/sect/core/validation/ValidationPipeline.ts rename to packages/shared/src/engine/sect/core/validation/ValidationPipeline.ts diff --git a/src/shared/engine/sect/core/validation/index.ts b/packages/shared/src/engine/sect/core/validation/index.ts similarity index 100% rename from src/shared/engine/sect/core/validation/index.ts rename to packages/shared/src/engine/sect/core/validation/index.ts diff --git a/src/shared/engine/sect/index.ts b/packages/shared/src/engine/sect/index.ts similarity index 100% rename from src/shared/engine/sect/index.ts rename to packages/shared/src/engine/sect/index.ts diff --git a/src/shared/engine/sect/testing/architecture/architectureGuard.test.ts b/packages/shared/src/engine/sect/testing/architecture/architectureGuard.test.ts similarity index 93% rename from src/shared/engine/sect/testing/architecture/architectureGuard.test.ts rename to packages/shared/src/engine/sect/testing/architecture/architectureGuard.test.ts index a074b9f9..880f4520 100644 --- a/src/shared/engine/sect/testing/architecture/architectureGuard.test.ts +++ b/packages/shared/src/engine/sect/testing/architecture/architectureGuard.test.ts @@ -2,7 +2,7 @@ import { readFileSync, readdirSync, statSync } from 'node:fs'; import { join, relative, resolve } from 'node:path'; import { describe, expect, it } from 'vitest'; -const root = resolve(process.cwd(), 'src/shared/engine/sect'); +const root = resolve(process.cwd(), 'packages/shared/src/engine/sect'); const productionSectTerms = /lingxiao|红尘剑宗|凌霄|wuxiang|无相|tianyan|天衍|youdu|幽都|jiujie|九劫天宫|sect\.(?:lingxiao|wuxiang|tianyan|youdu|jiujie)/i; @@ -74,7 +74,7 @@ describe('宗门插件架构守卫', () => { expect('天衍反应').toMatch(productionSectTerms); expect('sect.jiujie.calamity').toMatch(productionSectTerms); expect('九劫天宫').toMatch(productionSectTerms); - expect("from '@shared/engine/sect/content/tianyan'").toMatch( + expect("from '@daoyou/shared/engine/sect/content/tianyan'").toMatch( forbiddenBattleDependency, ); expect('manualSettlementEffects').toMatch(removedBattleExtensions); @@ -101,9 +101,9 @@ describe('宗门插件架构守卫', () => { it('通用宗门前端不依赖具体宗门或固定内容数量', () => { const frontendRoots = [ - resolve(process.cwd(), 'src/react-app/routes/game/sect'), - resolve(process.cwd(), 'src/react-app/components/feature/sect'), - resolve(process.cwd(), 'src/react-app/lib/sect'), + resolve(process.cwd(), 'apps/web/src/routes/game/sect'), + resolve(process.cwd(), 'apps/web/src/components/feature/sect'), + resolve(process.cwd(), 'apps/web/src/lib/sect'), ]; for (const file of frontendRoots .flatMap(sourceFiles) diff --git a/src/shared/engine/sect/testing/fixtureExtensibility.test.ts b/packages/shared/src/engine/sect/testing/fixtureExtensibility.test.ts similarity index 100% rename from src/shared/engine/sect/testing/fixtureExtensibility.test.ts rename to packages/shared/src/engine/sect/testing/fixtureExtensibility.test.ts diff --git a/src/shared/engine/sect/testing/fixtures/CustomEconomyFixtureSectModule.ts b/packages/shared/src/engine/sect/testing/fixtures/CustomEconomyFixtureSectModule.ts similarity index 100% rename from src/shared/engine/sect/testing/fixtures/CustomEconomyFixtureSectModule.ts rename to packages/shared/src/engine/sect/testing/fixtures/CustomEconomyFixtureSectModule.ts diff --git a/src/shared/engine/sect/testing/fixtures/FixtureSectModule.ts b/packages/shared/src/engine/sect/testing/fixtures/FixtureSectModule.ts similarity index 100% rename from src/shared/engine/sect/testing/fixtures/FixtureSectModule.ts rename to packages/shared/src/engine/sect/testing/fixtures/FixtureSectModule.ts diff --git a/src/shared/engine/shared/tag-domain/GameplayTagContainer.ts b/packages/shared/src/engine/shared/tag-domain/GameplayTagContainer.ts similarity index 100% rename from src/shared/engine/shared/tag-domain/GameplayTagContainer.ts rename to packages/shared/src/engine/shared/tag-domain/GameplayTagContainer.ts diff --git a/src/shared/engine/shared/tag-domain/creationTagDescriptions.ts b/packages/shared/src/engine/shared/tag-domain/creationTagDescriptions.ts similarity index 100% rename from src/shared/engine/shared/tag-domain/creationTagDescriptions.ts rename to packages/shared/src/engine/shared/tag-domain/creationTagDescriptions.ts diff --git a/src/shared/engine/shared/tag-domain/creationTags.ts b/packages/shared/src/engine/shared/tag-domain/creationTags.ts similarity index 100% rename from src/shared/engine/shared/tag-domain/creationTags.ts rename to packages/shared/src/engine/shared/tag-domain/creationTags.ts diff --git a/src/shared/engine/shared/tag-domain/gameplayTags.ts b/packages/shared/src/engine/shared/tag-domain/gameplayTags.ts similarity index 99% rename from src/shared/engine/shared/tag-domain/gameplayTags.ts rename to packages/shared/src/engine/shared/tag-domain/gameplayTags.ts index 8b8b2e39..dcbb0034 100644 --- a/src/shared/engine/shared/tag-domain/gameplayTags.ts +++ b/packages/shared/src/engine/shared/tag-domain/gameplayTags.ts @@ -224,7 +224,7 @@ export const GameplayTags = { }, } as const; -import type { ElementType } from '@shared/types/constants'; +import type { ElementType } from '@daoyou/shared/types/constants'; /** * 将 ElementType 中文字符映射到对应的运行时 Ability.Element.* 标签。 diff --git a/src/shared/engine/shared/tag-domain/guards.ts b/packages/shared/src/engine/shared/tag-domain/guards.ts similarity index 100% rename from src/shared/engine/shared/tag-domain/guards.ts rename to packages/shared/src/engine/shared/tag-domain/guards.ts diff --git a/src/shared/engine/shared/tag-domain/index.ts b/packages/shared/src/engine/shared/tag-domain/index.ts similarity index 100% rename from src/shared/engine/shared/tag-domain/index.ts rename to packages/shared/src/engine/shared/tag-domain/index.ts diff --git a/src/shared/engine/shared/tag-domain/types.ts b/packages/shared/src/engine/shared/tag-domain/types.ts similarity index 100% rename from src/shared/engine/shared/tag-domain/types.ts rename to packages/shared/src/engine/shared/tag-domain/types.ts diff --git a/src/shared/engine/spirit-field/SpiritSeedGenerator.test.ts b/packages/shared/src/engine/spirit-field/SpiritSeedGenerator.test.ts similarity index 100% rename from src/shared/engine/spirit-field/SpiritSeedGenerator.test.ts rename to packages/shared/src/engine/spirit-field/SpiritSeedGenerator.test.ts diff --git a/packages/shared/src/engine/spirit-field/SpiritSeedGenerator.ts b/packages/shared/src/engine/spirit-field/SpiritSeedGenerator.ts new file mode 100644 index 00000000..84e7d2eb --- /dev/null +++ b/packages/shared/src/engine/spirit-field/SpiritSeedGenerator.ts @@ -0,0 +1,56 @@ +import { + QUALITY_VALUES, + type ElementType, + type Quality, +} from '@daoyou/shared/types/constants'; +import { SPIRIT_SEED_QUALITY_CHANCE_MAP } from './config'; +import type { SpiritSeedRandomOptions, SpiritSeedSkeleton } from './types'; +export interface SpiritSeedBatchSpec { + rank: Quality; + quantity: number; + element?: ElementType; + regionTags?: string[]; +} + +function pickWeightedQuality( + options: SpiritSeedRandomOptions, + rng: () => number, +): Quality { + if (options.guaranteedRank) return options.guaranteedRank; + const min = options.rankRange + ? QUALITY_VALUES.indexOf(options.rankRange.min) + : 0; + const max = options.rankRange + ? QUALITY_VALUES.indexOf(options.rankRange.max) + : QUALITY_VALUES.length - 1; + const candidates = QUALITY_VALUES.slice( + Math.min(min, max), + Math.max(min, max) + 1, + ); + const weights = options.qualityChanceMap ?? SPIRIT_SEED_QUALITY_CHANCE_MAP; + const total = candidates.reduce( + (sum, quality) => sum + Math.max(0, weights[quality]), + 0, + ); + let cursor = rng() * total; + for (const quality of candidates) { + cursor -= Math.max(0, weights[quality]); + if (cursor <= 0) return quality; + } + return candidates[candidates.length - 1] ?? '凡品'; +} + +export class SpiritSeedGenerator { + static generateRandomSkeletons( + count: number, + options: SpiritSeedRandomOptions = {}, + rng: () => number = Math.random, + ): SpiritSeedSkeleton[] { + return Array.from({ length: Math.max(0, Math.floor(count)) }, () => ({ + rank: pickWeightedQuality(options, rng), + quantity: 1, + forcedElement: options.specifiedElement, + regionTags: options.regionTags?.slice(0, 8), + })); + } +} diff --git a/src/shared/engine/spirit-field/config.ts b/packages/shared/src/engine/spirit-field/config.ts similarity index 98% rename from src/shared/engine/spirit-field/config.ts rename to packages/shared/src/engine/spirit-field/config.ts index 6e1ee326..69072ac4 100644 --- a/src/shared/engine/spirit-field/config.ts +++ b/packages/shared/src/engine/spirit-field/config.ts @@ -1,4 +1,4 @@ -import type { Quality, RealmType } from '@shared/types/constants'; +import type { Quality, RealmType } from '@daoyou/shared/types/constants'; import type { SpiritFieldMethodDefinition, SpiritFieldStage } from './types'; export const SPIRIT_FIELD_METHODS: readonly SpiritFieldMethodDefinition[] = [ diff --git a/src/shared/engine/spirit-field/index.ts b/packages/shared/src/engine/spirit-field/index.ts similarity index 100% rename from src/shared/engine/spirit-field/index.ts rename to packages/shared/src/engine/spirit-field/index.ts diff --git a/src/shared/engine/spirit-field/marketOfferings.test.ts b/packages/shared/src/engine/spirit-field/marketOfferings.test.ts similarity index 100% rename from src/shared/engine/spirit-field/marketOfferings.test.ts rename to packages/shared/src/engine/spirit-field/marketOfferings.test.ts diff --git a/src/shared/engine/spirit-field/marketOfferings.ts b/packages/shared/src/engine/spirit-field/marketOfferings.ts similarity index 88% rename from src/shared/engine/spirit-field/marketOfferings.ts rename to packages/shared/src/engine/spirit-field/marketOfferings.ts index 31973add..1ec06b6a 100644 --- a/src/shared/engine/spirit-field/marketOfferings.ts +++ b/packages/shared/src/engine/spirit-field/marketOfferings.ts @@ -1,4 +1,4 @@ -import type { MarketLayer } from '@shared/types/market'; +import type { MarketLayer } from '@daoyou/shared/types/market'; /** 根据节点配置把货架比例换算为灵种数量;普通坊市未配置时不强制注入。 */ export function getSpiritFieldMarketSeedSlotCount( diff --git a/src/shared/engine/spirit-field/rules.test.ts b/packages/shared/src/engine/spirit-field/rules.test.ts similarity index 100% rename from src/shared/engine/spirit-field/rules.test.ts rename to packages/shared/src/engine/spirit-field/rules.test.ts diff --git a/src/shared/engine/spirit-field/rules.ts b/packages/shared/src/engine/spirit-field/rules.ts similarity index 99% rename from src/shared/engine/spirit-field/rules.ts rename to packages/shared/src/engine/spirit-field/rules.ts index 5d90bb87..0241b9fc 100644 --- a/src/shared/engine/spirit-field/rules.ts +++ b/packages/shared/src/engine/spirit-field/rules.ts @@ -1,4 +1,4 @@ -import { QUALITY_ORDER, QUALITY_VALUES, REALM_ORDER, type Quality, type RealmType } from '@shared/types/constants'; +import { QUALITY_ORDER, QUALITY_VALUES, REALM_ORDER, type Quality, type RealmType } from '@daoyou/shared/types/constants'; import { SPIRIT_FIELD_METHOD_MAP } from './config'; import { SPIRIT_FIELD_STAGES, type SpiritFieldCultivationMethod, type SpiritFieldHarvestSettlement, type SpiritFieldOutcomeKind, type SpiritFieldPlantSnapshot, type SpiritFieldPlotRuntimeStatus, type SpiritFieldPlotState, type SpiritFieldStageAffinity } from './types'; diff --git a/src/shared/engine/spirit-field/seedMaterial.test.ts b/packages/shared/src/engine/spirit-field/seedMaterial.test.ts similarity index 100% rename from src/shared/engine/spirit-field/seedMaterial.test.ts rename to packages/shared/src/engine/spirit-field/seedMaterial.test.ts diff --git a/src/shared/engine/spirit-field/seedMaterial.ts b/packages/shared/src/engine/spirit-field/seedMaterial.ts similarity index 96% rename from src/shared/engine/spirit-field/seedMaterial.ts rename to packages/shared/src/engine/spirit-field/seedMaterial.ts index f3d61091..14ec83b4 100644 --- a/src/shared/engine/spirit-field/seedMaterial.ts +++ b/packages/shared/src/engine/spirit-field/seedMaterial.ts @@ -1,6 +1,6 @@ -import { CREATION_MATERIAL_SEMANTIC_TAGS } from '@shared/engine/shared/tag-domain'; -import { ELEMENT_VALUES, QUALITY_VALUES, REALM_VALUES, type ElementType, type Quality, type RealmType } from '@shared/types/constants'; -import type { Material } from '@shared/types/cultivator'; +import { CREATION_MATERIAL_SEMANTIC_TAGS } from '@daoyou/shared/engine/shared/tag-domain'; +import { ELEMENT_VALUES, QUALITY_VALUES, REALM_VALUES, type ElementType, type Quality, type RealmType } from '@daoyou/shared/types/constants'; +import type { Material } from '@daoyou/shared/types/cultivator'; import { SPIRIT_FIELD_CULTIVATION_METHODS, SPIRIT_FIELD_OUTCOME_KINDS, SPIRIT_SEED_GROWTH_FORMS, SPIRIT_SEED_GROWTH_TRAITS, SPIRIT_SEED_HABITAT_TAGS, SPIRIT_SEED_HARVEST_PARTS, SPIRIT_SEED_USE_TAGS, type SpiritFieldPlantSnapshot, type SpiritFieldSeedSpec } from './types'; function isRecord(value: unknown): value is Record { return Boolean(value && typeof value === 'object' && !Array.isArray(value)); } diff --git a/src/shared/engine/spirit-field/spiritFruit.test.ts b/packages/shared/src/engine/spirit-field/spiritFruit.test.ts similarity index 96% rename from src/shared/engine/spirit-field/spiritFruit.test.ts rename to packages/shared/src/engine/spirit-field/spiritFruit.test.ts index ca9971ba..7442f1e2 100644 --- a/src/shared/engine/spirit-field/spiritFruit.test.ts +++ b/packages/shared/src/engine/spirit-field/spiritFruit.test.ts @@ -1,4 +1,4 @@ -import { ALCHEMY_EFFECT_BASE_BY_QUALITY } from '@shared/config/alchemyEffectConfig'; +import { ALCHEMY_EFFECT_BASE_BY_QUALITY } from '@daoyou/shared/config/alchemyEffectConfig'; import { describe, expect, it } from 'vitest'; import { buildSpiritFruitSpec } from './spiritFruit'; diff --git a/src/shared/engine/spirit-field/spiritFruit.ts b/packages/shared/src/engine/spirit-field/spiritFruit.ts similarity index 85% rename from src/shared/engine/spirit-field/spiritFruit.ts rename to packages/shared/src/engine/spirit-field/spiritFruit.ts index 75e9cb6a..e285ff18 100644 --- a/src/shared/engine/spirit-field/spiritFruit.ts +++ b/packages/shared/src/engine/spirit-field/spiritFruit.ts @@ -1,8 +1,8 @@ -import { BEAST_CULTIVATION_BASE_BY_QUALITY } from '@shared/config/beastCultivation'; -import { ALCHEMY_EFFECT_BASE_BY_QUALITY } from '@shared/config/alchemyEffectConfig'; -import { buildBreakthroughFocusOperation } from '@shared/lib/pillEffectScaling'; -import type { ConditionOperation, PillFamily, SpiritFruitSpec } from '@shared/types/consumable'; -import type { Quality } from '@shared/types/constants'; +import { BEAST_CULTIVATION_BASE_BY_QUALITY } from '@daoyou/shared/config/beastCultivation'; +import { ALCHEMY_EFFECT_BASE_BY_QUALITY } from '@daoyou/shared/config/alchemyEffectConfig'; +import { buildBreakthroughFocusOperation } from '@daoyou/shared/lib/pillEffectScaling'; +import type { ConditionOperation, PillFamily, SpiritFruitSpec } from '@daoyou/shared/types/consumable'; +import type { Quality } from '@daoyou/shared/types/constants'; const lower = (value: number) => Math.max(1, Math.floor(value * 0.8)); diff --git a/src/shared/engine/spirit-field/types.ts b/packages/shared/src/engine/spirit-field/types.ts similarity index 96% rename from src/shared/engine/spirit-field/types.ts rename to packages/shared/src/engine/spirit-field/types.ts index 819e53e0..3e52c7b6 100644 --- a/src/shared/engine/spirit-field/types.ts +++ b/packages/shared/src/engine/spirit-field/types.ts @@ -1,6 +1,6 @@ -import type { CreationMaterialSemanticTag } from '@shared/engine/shared/tag-domain'; -import type { PillFamily } from '@shared/types/consumable'; -import type { ElementType, MaterialType, Quality, RealmType } from '@shared/types/constants'; +import type { CreationMaterialSemanticTag } from '@daoyou/shared/engine/shared/tag-domain'; +import type { PillFamily } from '@daoyou/shared/types/consumable'; +import type { ElementType, MaterialType, Quality, RealmType } from '@daoyou/shared/types/constants'; export const SPIRIT_FIELD_STAGES = ['germination', 'nourishing', 'forming'] as const; export type SpiritFieldStage = (typeof SPIRIT_FIELD_STAGES)[number]; diff --git a/src/shared/engine/yield/YieldCalculator.test.ts b/packages/shared/src/engine/yield/YieldCalculator.test.ts similarity index 96% rename from src/shared/engine/yield/YieldCalculator.test.ts rename to packages/shared/src/engine/yield/YieldCalculator.test.ts index 0db10155..b5487ee8 100644 --- a/src/shared/engine/yield/YieldCalculator.test.ts +++ b/packages/shared/src/engine/yield/YieldCalculator.test.ts @@ -2,7 +2,7 @@ import { YieldCalculator, YIELD_MATERIAL_QUALITY_CHANCE_BY_REALM, } from './YieldCalculator'; -import { calculateOfflineExp } from '@shared/engine/cultivation/ExpBudgetCalculator'; +import { calculateOfflineExp } from '@daoyou/shared/engine/cultivation/ExpBudgetCalculator'; describe('YieldCalculator', () => { it('calculates cultivator yield from the narrow realm input', () => { diff --git a/src/shared/engine/yield/YieldCalculator.ts b/packages/shared/src/engine/yield/YieldCalculator.ts similarity index 94% rename from src/shared/engine/yield/YieldCalculator.ts rename to packages/shared/src/engine/yield/YieldCalculator.ts index c8615322..53a5b8ee 100644 --- a/src/shared/engine/yield/YieldCalculator.ts +++ b/packages/shared/src/engine/yield/YieldCalculator.ts @@ -1,11 +1,11 @@ -import { calculateOfflineExp } from '@shared/engine/cultivation/ExpBudgetCalculator'; -import type { ResourceOperation } from '@shared/engine/resource/types'; +import { calculateOfflineExp } from '@daoyou/shared/engine/cultivation/ExpBudgetCalculator'; +import type { ResourceOperation } from '@daoyou/shared/engine/resource/types'; import { REALM_YIELD_RATES, type Quality, type RealmStage, type RealmType, -} from '@shared/types/constants'; +} from '@daoyou/shared/types/constants'; export const YIELD_MATERIAL_QUALITY_CHANCE_BY_REALM: Record< RealmType, diff --git a/src/shared/forging/forging.test.ts b/packages/shared/src/forging/forging.test.ts similarity index 100% rename from src/shared/forging/forging.test.ts rename to packages/shared/src/forging/forging.test.ts diff --git a/src/shared/forging/names.ts b/packages/shared/src/forging/names.ts similarity index 100% rename from src/shared/forging/names.ts rename to packages/shared/src/forging/names.ts diff --git a/src/shared/forging/narrative.test.ts b/packages/shared/src/forging/narrative.test.ts similarity index 100% rename from src/shared/forging/narrative.test.ts rename to packages/shared/src/forging/narrative.test.ts diff --git a/src/shared/forging/narrative.ts b/packages/shared/src/forging/narrative.ts similarity index 100% rename from src/shared/forging/narrative.ts rename to packages/shared/src/forging/narrative.ts diff --git a/src/shared/forging/rules.ts b/packages/shared/src/forging/rules.ts similarity index 98% rename from src/shared/forging/rules.ts rename to packages/shared/src/forging/rules.ts index 425c32c9..757d70e0 100644 --- a/src/shared/forging/rules.ts +++ b/packages/shared/src/forging/rules.ts @@ -2,7 +2,7 @@ import { equipmentRealm, isEquipmentLevel, isOpenEquipmentLevel, -} from '@shared/engine/combat-v6/equipment/realm'; +} from '@daoyou/shared/engine/combat-v6/equipment/realm'; import { z } from 'zod'; import type { ForgingBoosts } from '../engine/combat-v6/equipment/forging'; import { DAO_EQUIPMENT_FORGING } from '../engine/combat-v6/equipment/forging-content'; diff --git a/src/shared/guide/catalog.test.ts b/packages/shared/src/guide/catalog.test.ts similarity index 100% rename from src/shared/guide/catalog.test.ts rename to packages/shared/src/guide/catalog.test.ts diff --git a/src/shared/guide/catalog.ts b/packages/shared/src/guide/catalog.ts similarity index 100% rename from src/shared/guide/catalog.ts rename to packages/shared/src/guide/catalog.ts diff --git a/src/shared/guide/interpreter.test.ts b/packages/shared/src/guide/interpreter.test.ts similarity index 100% rename from src/shared/guide/interpreter.test.ts rename to packages/shared/src/guide/interpreter.test.ts diff --git a/src/shared/guide/interpreter.ts b/packages/shared/src/guide/interpreter.ts similarity index 100% rename from src/shared/guide/interpreter.ts rename to packages/shared/src/guide/interpreter.ts diff --git a/src/shared/guide/schema.ts b/packages/shared/src/guide/schema.ts similarity index 100% rename from src/shared/guide/schema.ts rename to packages/shared/src/guide/schema.ts diff --git a/src/shared/hunts/config.ts b/packages/shared/src/hunts/config.ts similarity index 100% rename from src/shared/hunts/config.ts rename to packages/shared/src/hunts/config.ts diff --git a/src/shared/hunts/hunts.test.ts b/packages/shared/src/hunts/hunts.test.ts similarity index 100% rename from src/shared/hunts/hunts.test.ts rename to packages/shared/src/hunts/hunts.test.ts diff --git a/src/shared/hunts/rules.ts b/packages/shared/src/hunts/rules.ts similarity index 100% rename from src/shared/hunts/rules.ts rename to packages/shared/src/hunts/rules.ts diff --git a/src/shared/hunts/settlement.test.ts b/packages/shared/src/hunts/settlement.test.ts similarity index 100% rename from src/shared/hunts/settlement.test.ts rename to packages/shared/src/hunts/settlement.test.ts diff --git a/src/shared/hunts/settlement.ts b/packages/shared/src/hunts/settlement.ts similarity index 100% rename from src/shared/hunts/settlement.ts rename to packages/shared/src/hunts/settlement.ts diff --git a/src/shared/inscriptions/rules.test.ts b/packages/shared/src/inscriptions/rules.test.ts similarity index 100% rename from src/shared/inscriptions/rules.test.ts rename to packages/shared/src/inscriptions/rules.test.ts diff --git a/src/shared/inscriptions/rules.ts b/packages/shared/src/inscriptions/rules.ts similarity index 100% rename from src/shared/inscriptions/rules.ts rename to packages/shared/src/inscriptions/rules.ts diff --git a/src/shared/inventory/alchemy.test.ts b/packages/shared/src/inventory/alchemy.test.ts similarity index 100% rename from src/shared/inventory/alchemy.test.ts rename to packages/shared/src/inventory/alchemy.test.ts diff --git a/src/shared/inventory/alchemy.ts b/packages/shared/src/inventory/alchemy.ts similarity index 100% rename from src/shared/inventory/alchemy.ts rename to packages/shared/src/inventory/alchemy.ts diff --git a/src/shared/inventory/equipment-location.test.ts b/packages/shared/src/inventory/equipment-location.test.ts similarity index 100% rename from src/shared/inventory/equipment-location.test.ts rename to packages/shared/src/inventory/equipment-location.test.ts diff --git a/src/shared/inventory/equipment-location.ts b/packages/shared/src/inventory/equipment-location.ts similarity index 100% rename from src/shared/inventory/equipment-location.ts rename to packages/shared/src/inventory/equipment-location.ts diff --git a/src/shared/inventory/equipment.ts b/packages/shared/src/inventory/equipment.ts similarity index 100% rename from src/shared/inventory/equipment.ts rename to packages/shared/src/inventory/equipment.ts diff --git a/src/shared/inventory/grant-occupancy.test.ts b/packages/shared/src/inventory/grant-occupancy.test.ts similarity index 100% rename from src/shared/inventory/grant-occupancy.test.ts rename to packages/shared/src/inventory/grant-occupancy.test.ts diff --git a/src/shared/inventory/index.ts b/packages/shared/src/inventory/index.ts similarity index 100% rename from src/shared/inventory/index.ts rename to packages/shared/src/inventory/index.ts diff --git a/src/shared/inventory/inventory.test.ts b/packages/shared/src/inventory/inventory.test.ts similarity index 100% rename from src/shared/inventory/inventory.test.ts rename to packages/shared/src/inventory/inventory.test.ts diff --git a/src/shared/inventory/recycle.test.ts b/packages/shared/src/inventory/recycle.test.ts similarity index 100% rename from src/shared/inventory/recycle.test.ts rename to packages/shared/src/inventory/recycle.test.ts diff --git a/src/shared/inventory/recycle.ts b/packages/shared/src/inventory/recycle.ts similarity index 100% rename from src/shared/inventory/recycle.ts rename to packages/shared/src/inventory/recycle.ts diff --git a/src/shared/inventory/recyclePrice.ts b/packages/shared/src/inventory/recyclePrice.ts similarity index 100% rename from src/shared/inventory/recyclePrice.ts rename to packages/shared/src/inventory/recyclePrice.ts diff --git a/src/shared/inventory/sorting.test.ts b/packages/shared/src/inventory/sorting.test.ts similarity index 100% rename from src/shared/inventory/sorting.test.ts rename to packages/shared/src/inventory/sorting.test.ts diff --git a/src/shared/inventory/sorting.ts b/packages/shared/src/inventory/sorting.ts similarity index 100% rename from src/shared/inventory/sorting.ts rename to packages/shared/src/inventory/sorting.ts diff --git a/src/shared/inventory/stack-key.test.ts b/packages/shared/src/inventory/stack-key.test.ts similarity index 100% rename from src/shared/inventory/stack-key.test.ts rename to packages/shared/src/inventory/stack-key.test.ts diff --git a/src/shared/inventory/stack-key.ts b/packages/shared/src/inventory/stack-key.ts similarity index 100% rename from src/shared/inventory/stack-key.ts rename to packages/shared/src/inventory/stack-key.ts diff --git a/src/shared/inventory/test-helpers.ts b/packages/shared/src/inventory/test-helpers.ts similarity index 100% rename from src/shared/inventory/test-helpers.ts rename to packages/shared/src/inventory/test-helpers.ts diff --git a/src/shared/items/alchemyShowcase.test.ts b/packages/shared/src/items/alchemyShowcase.test.ts similarity index 100% rename from src/shared/items/alchemyShowcase.test.ts rename to packages/shared/src/items/alchemyShowcase.test.ts diff --git a/src/shared/items/alchemyShowcase.ts b/packages/shared/src/items/alchemyShowcase.ts similarity index 100% rename from src/shared/items/alchemyShowcase.ts rename to packages/shared/src/items/alchemyShowcase.ts diff --git a/src/shared/items/definitions/beast-books.ts b/packages/shared/src/items/definitions/beast-books.ts similarity index 100% rename from src/shared/items/definitions/beast-books.ts rename to packages/shared/src/items/definitions/beast-books.ts diff --git a/src/shared/items/definitions/beast-rejuvenation.ts b/packages/shared/src/items/definitions/beast-rejuvenation.ts similarity index 100% rename from src/shared/items/definitions/beast-rejuvenation.ts rename to packages/shared/src/items/definitions/beast-rejuvenation.ts diff --git a/src/shared/items/definitions/consumables.ts b/packages/shared/src/items/definitions/consumables.ts similarity index 100% rename from src/shared/items/definitions/consumables.ts rename to packages/shared/src/items/definitions/consumables.ts diff --git a/src/shared/items/definitions/equipment-blueprints.test.ts b/packages/shared/src/items/definitions/equipment-blueprints.test.ts similarity index 100% rename from src/shared/items/definitions/equipment-blueprints.test.ts rename to packages/shared/src/items/definitions/equipment-blueprints.test.ts diff --git a/src/shared/items/definitions/equipment-blueprints.ts b/packages/shared/src/items/definitions/equipment-blueprints.ts similarity index 89% rename from src/shared/items/definitions/equipment-blueprints.ts rename to packages/shared/src/items/definitions/equipment-blueprints.ts index 4a57a13a..986732cb 100644 --- a/src/shared/items/definitions/equipment-blueprints.ts +++ b/packages/shared/src/items/definitions/equipment-blueprints.ts @@ -1,4 +1,4 @@ -import { getLevelRealmStage } from '@shared/config/realmProgression'; +import { getLevelRealmStage } from '@daoyou/shared/config/realmProgression'; import { EQUIPMENT_LEVELS } from '../../engine/combat-v6/equipment/realm'; import { DAO_EQUIPMENT_SLOTS } from '../../engine/combat-v6/equipment/types'; export const EQUIPMENT_SLOT_NAMES = { diff --git a/src/shared/items/definitions/equipment.ts b/packages/shared/src/items/definitions/equipment.ts similarity index 100% rename from src/shared/items/definitions/equipment.ts rename to packages/shared/src/items/definitions/equipment.ts diff --git a/src/shared/items/definitions/inscriptions.ts b/packages/shared/src/items/definitions/inscriptions.ts similarity index 100% rename from src/shared/items/definitions/inscriptions.ts rename to packages/shared/src/items/definitions/inscriptions.ts diff --git a/src/shared/items/definitions/manual-jades.ts b/packages/shared/src/items/definitions/manual-jades.ts similarity index 100% rename from src/shared/items/definitions/manual-jades.ts rename to packages/shared/src/items/definitions/manual-jades.ts diff --git a/src/shared/items/definitions/materials.ts b/packages/shared/src/items/definitions/materials.ts similarity index 100% rename from src/shared/items/definitions/materials.ts rename to packages/shared/src/items/definitions/materials.ts diff --git a/src/shared/items/definitions/seeds.ts b/packages/shared/src/items/definitions/seeds.ts similarity index 100% rename from src/shared/items/definitions/seeds.ts rename to packages/shared/src/items/definitions/seeds.ts diff --git a/src/shared/items/forgingShowcase.test.ts b/packages/shared/src/items/forgingShowcase.test.ts similarity index 100% rename from src/shared/items/forgingShowcase.test.ts rename to packages/shared/src/items/forgingShowcase.test.ts diff --git a/src/shared/items/forgingShowcase.ts b/packages/shared/src/items/forgingShowcase.ts similarity index 100% rename from src/shared/items/forgingShowcase.ts rename to packages/shared/src/items/forgingShowcase.ts diff --git a/src/shared/items/legacy-material.test.ts b/packages/shared/src/items/legacy-material.test.ts similarity index 100% rename from src/shared/items/legacy-material.test.ts rename to packages/shared/src/items/legacy-material.test.ts diff --git a/src/shared/items/legacy-material.ts b/packages/shared/src/items/legacy-material.ts similarity index 100% rename from src/shared/items/legacy-material.ts rename to packages/shared/src/items/legacy-material.ts diff --git a/src/shared/items/libraryMaterialGrant.ts b/packages/shared/src/items/libraryMaterialGrant.ts similarity index 100% rename from src/shared/items/libraryMaterialGrant.ts rename to packages/shared/src/items/libraryMaterialGrant.ts diff --git a/src/shared/items/material.ts b/packages/shared/src/items/material.ts similarity index 100% rename from src/shared/items/material.ts rename to packages/shared/src/items/material.ts diff --git a/src/shared/items/registry.ts b/packages/shared/src/items/registry.ts similarity index 100% rename from src/shared/items/registry.ts rename to packages/shared/src/items/registry.ts diff --git a/src/shared/items/showcase.test.ts b/packages/shared/src/items/showcase.test.ts similarity index 100% rename from src/shared/items/showcase.test.ts rename to packages/shared/src/items/showcase.test.ts diff --git a/src/shared/items/showcase.ts b/packages/shared/src/items/showcase.ts similarity index 100% rename from src/shared/items/showcase.ts rename to packages/shared/src/items/showcase.ts diff --git a/src/shared/items/types.ts b/packages/shared/src/items/types.ts similarity index 100% rename from src/shared/items/types.ts rename to packages/shared/src/items/types.ts diff --git a/src/shared/legacy/products.test.ts b/packages/shared/src/legacy/products.test.ts similarity index 100% rename from src/shared/legacy/products.test.ts rename to packages/shared/src/legacy/products.test.ts diff --git a/src/shared/legacy/products.ts b/packages/shared/src/legacy/products.ts similarity index 100% rename from src/shared/legacy/products.ts rename to packages/shared/src/legacy/products.ts diff --git a/src/shared/lib/afdianProtocol.test.ts b/packages/shared/src/lib/afdianProtocol.test.ts similarity index 83% rename from src/shared/lib/afdianProtocol.test.ts rename to packages/shared/src/lib/afdianProtocol.test.ts index af1638bd..6798698b 100644 --- a/src/shared/lib/afdianProtocol.test.ts +++ b/packages/shared/src/lib/afdianProtocol.test.ts @@ -1,3 +1,4 @@ +import { createHash } from 'node:crypto'; import { describe, expect, it } from 'vitest'; import { buildAfdianCheckoutUrl, @@ -8,12 +9,15 @@ import { describe('Afdian official protocol', () => { it('matches the official API signing example', () => { expect( - createAfdianApiEnvelope({ - userId: 'abc', - token: '123', - params: { a: 333 }, - timestampSeconds: 1_624_339_905, - }), + createAfdianApiEnvelope( + { + userId: 'abc', + token: '123', + params: { a: 333 }, + timestampSeconds: 1_624_339_905, + }, + (text) => createHash('md5').update(text).digest('hex'), + ), ).toEqual({ user_id: 'abc', params: '{"a":333}', diff --git a/src/shared/lib/afdianProtocol.ts b/packages/shared/src/lib/afdianProtocol.ts similarity index 82% rename from src/shared/lib/afdianProtocol.ts rename to packages/shared/src/lib/afdianProtocol.ts index 64642cd8..0751e8d8 100644 --- a/src/shared/lib/afdianProtocol.ts +++ b/packages/shared/src/lib/afdianProtocol.ts @@ -1,5 +1,3 @@ -import { createHash } from 'node:crypto'; - export type AfdianApiEnvelope = { user_id: string; params: string; @@ -16,19 +14,22 @@ export function buildAfdianWebhookSignedText(order: { return `${order.outTradeNo}${order.userId}${order.planId}${order.totalAmount}`; } -export function createAfdianApiEnvelope(input: { - userId: string; - token: string; - params: Record; - timestampSeconds: number; -}): AfdianApiEnvelope { +export function createAfdianApiEnvelope( + input: { + userId: string; + token: string; + params: Record; + timestampSeconds: number; + }, + sign: (text: string) => string, +): AfdianApiEnvelope { const params = JSON.stringify(input.params); const signSource = `${input.token}params${params}ts${input.timestampSeconds}user_id${input.userId}`; return { user_id: input.userId, params, ts: input.timestampSeconds, - sign: createHash('md5').update(signSource).digest('hex'), + sign: sign(signSource), }; } diff --git a/src/shared/lib/alchemyCost.ts b/packages/shared/src/lib/alchemyCost.ts similarity index 100% rename from src/shared/lib/alchemyCost.ts rename to packages/shared/src/lib/alchemyCost.ts diff --git a/src/shared/lib/alchemyEffectResolver.test.ts b/packages/shared/src/lib/alchemyEffectResolver.test.ts similarity index 98% rename from src/shared/lib/alchemyEffectResolver.test.ts rename to packages/shared/src/lib/alchemyEffectResolver.test.ts index 6737117a..6790279e 100644 --- a/src/shared/lib/alchemyEffectResolver.test.ts +++ b/packages/shared/src/lib/alchemyEffectResolver.test.ts @@ -1,4 +1,4 @@ -import { ALCHEMY_EFFECT_BASE_BY_QUALITY } from '@shared/config/alchemyEffectConfig'; +import { ALCHEMY_EFFECT_BASE_BY_QUALITY } from '@daoyou/shared/config/alchemyEffectConfig'; import { describe, expect, it } from 'vitest'; import { normalizeAlchemyEffectRoute, diff --git a/src/shared/lib/alchemyEffectResolver.ts b/packages/shared/src/lib/alchemyEffectResolver.ts similarity index 96% rename from src/shared/lib/alchemyEffectResolver.ts rename to packages/shared/src/lib/alchemyEffectResolver.ts index b0c82709..72d40975 100644 --- a/src/shared/lib/alchemyEffectResolver.ts +++ b/packages/shared/src/lib/alchemyEffectResolver.ts @@ -1,14 +1,14 @@ -import { ALCHEMY_EFFECT_BASE_BY_QUALITY } from '@shared/config/alchemyEffectConfig'; -import { PILL_APPEARANCE_EFFECT_MULTIPLIER } from '@shared/config/alchemyEssenceConfig'; -import { BEAST_CULTIVATION_BASE_BY_QUALITY } from '@shared/config/beastCultivation'; -import type { Quality } from '@shared/types/constants'; +import { ALCHEMY_EFFECT_BASE_BY_QUALITY } from '@daoyou/shared/config/alchemyEffectConfig'; +import { PILL_APPEARANCE_EFFECT_MULTIPLIER } from '@daoyou/shared/config/alchemyEssenceConfig'; +import { BEAST_CULTIVATION_BASE_BY_QUALITY } from '@daoyou/shared/config/beastCultivation'; +import type { Quality } from '@daoyou/shared/types/constants'; import { ALCHEMY_PROPERTY_KEY_VALUES, type AlchemyEffectKey, type AlchemyEffectRoute, type ConditionOperation, type PillAppearanceGrade, -} from '@shared/types/consumable'; +} from '@daoyou/shared/types/consumable'; import { getAlchemyPropertyTrackPath } from './alchemyProperties'; import { buildCultivationBoostPayload } from './cultivationBoost'; import { getPillAppearanceToxicityMultiplier } from './pillAppearance'; diff --git a/src/shared/lib/alchemyFormulaFit.test.ts b/packages/shared/src/lib/alchemyFormulaFit.test.ts similarity index 100% rename from src/shared/lib/alchemyFormulaFit.test.ts rename to packages/shared/src/lib/alchemyFormulaFit.test.ts diff --git a/src/shared/lib/alchemyFormulaFit.ts b/packages/shared/src/lib/alchemyFormulaFit.ts similarity index 93% rename from src/shared/lib/alchemyFormulaFit.ts rename to packages/shared/src/lib/alchemyFormulaFit.ts index f7c4c3e9..40b4d660 100644 --- a/src/shared/lib/alchemyFormulaFit.ts +++ b/packages/shared/src/lib/alchemyFormulaFit.ts @@ -1,4 +1,4 @@ -import type { FormulaFitBand } from '@shared/types/consumable'; +import type { FormulaFitBand } from '@daoyou/shared/types/consumable'; export interface FormulaFitPolicy { /** 兼容既有丹药元数据与评分展示的档位代表值,不是二次契合度计算。 */ diff --git a/src/shared/lib/alchemyMaterialHints.test.ts b/packages/shared/src/lib/alchemyMaterialHints.test.ts similarity index 100% rename from src/shared/lib/alchemyMaterialHints.test.ts rename to packages/shared/src/lib/alchemyMaterialHints.test.ts diff --git a/src/shared/lib/alchemyMaterialHints.ts b/packages/shared/src/lib/alchemyMaterialHints.ts similarity index 98% rename from src/shared/lib/alchemyMaterialHints.ts rename to packages/shared/src/lib/alchemyMaterialHints.ts index 52305cc3..30a817cf 100644 --- a/src/shared/lib/alchemyMaterialHints.ts +++ b/packages/shared/src/lib/alchemyMaterialHints.ts @@ -2,7 +2,7 @@ import type { AlchemyMaterialPropertyVector, AlchemyPropertyKey, WeightedAlchemyProperty, -} from '@shared/types/consumable'; +} from '@daoyou/shared/types/consumable'; import { normalizeWeightedAlchemyProperties } from './alchemyProperties'; interface AlchemyMaterialHintRule { diff --git a/src/shared/lib/alchemyMaterials.ts b/packages/shared/src/lib/alchemyMaterials.ts similarity index 76% rename from src/shared/lib/alchemyMaterials.ts rename to packages/shared/src/lib/alchemyMaterials.ts index 37859d12..b3c4979c 100644 --- a/src/shared/lib/alchemyMaterials.ts +++ b/packages/shared/src/lib/alchemyMaterials.ts @@ -1,8 +1,8 @@ import { ALCHEMY_ALLOWED_MATERIAL_TYPES, type AlchemyMaterialType, -} from '@shared/config/alchemyConfig'; -import type { MaterialType } from '@shared/types/constants'; +} from '@daoyou/shared/config/alchemyConfig'; +import type { MaterialType } from '@daoyou/shared/types/constants'; const ALCHEMY_ALLOWED_MATERIAL_TYPE_SET = new Set( ALCHEMY_ALLOWED_MATERIAL_TYPES, diff --git a/src/shared/lib/alchemyProgress.ts b/packages/shared/src/lib/alchemyProgress.ts similarity index 80% rename from src/shared/lib/alchemyProgress.ts rename to packages/shared/src/lib/alchemyProgress.ts index a9add87e..2ace7c21 100644 --- a/src/shared/lib/alchemyProgress.ts +++ b/packages/shared/src/lib/alchemyProgress.ts @@ -1,6 +1,6 @@ -import { buildInsightGain as buildInsightGainV2 } from '@shared/lib/pillEffectScaling'; -import { calculatePillCultivationExp } from '@shared/lib/pillCultivationExp'; -import type { Quality, RealmStage, RealmType } from '@shared/types/constants'; +import { buildInsightGain as buildInsightGainV2 } from '@daoyou/shared/lib/pillEffectScaling'; +import { calculatePillCultivationExp } from '@daoyou/shared/lib/pillCultivationExp'; +import type { Quality, RealmStage, RealmType } from '@daoyou/shared/types/constants'; export interface CultivationGainSnapshotInput { realm: RealmType; diff --git a/src/shared/lib/alchemyProperties.ts b/packages/shared/src/lib/alchemyProperties.ts similarity index 97% rename from src/shared/lib/alchemyProperties.ts rename to packages/shared/src/lib/alchemyProperties.ts index 36aa2834..3aec3cf7 100644 --- a/src/shared/lib/alchemyProperties.ts +++ b/packages/shared/src/lib/alchemyProperties.ts @@ -1,4 +1,4 @@ -import type { ConditionTrackPath } from '@shared/types/condition'; +import type { ConditionTrackPath } from '@daoyou/shared/types/condition'; import type { AlchemyPropertyKey, CompatibleAlchemyPropertyKey, @@ -6,8 +6,8 @@ import type { LegacyAlchemyPropertyKey, PillFamily, WeightedAlchemyProperty, -} from '@shared/types/consumable'; -import { ALCHEMY_PROPERTY_KEY_VALUES } from '@shared/types/consumable'; +} from '@daoyou/shared/types/consumable'; +import { ALCHEMY_PROPERTY_KEY_VALUES } from '@daoyou/shared/types/consumable'; import { getGameConceptLabel } from './gameConceptDisplay'; export const ALCHEMY_PROPERTY_LABELS: Record = { diff --git a/src/shared/lib/alchemyYield.test.ts b/packages/shared/src/lib/alchemyYield.test.ts similarity index 98% rename from src/shared/lib/alchemyYield.test.ts rename to packages/shared/src/lib/alchemyYield.test.ts index 433c747b..4e1dfaf1 100644 --- a/src/shared/lib/alchemyYield.test.ts +++ b/packages/shared/src/lib/alchemyYield.test.ts @@ -6,8 +6,8 @@ import { calculateRawEssence, rollAlchemyYieldProfile, } from './alchemyYield'; -import { PILL_UNIT_ESSENCE_BY_QUALITY } from '@shared/config/alchemyEssenceConfig'; -import { QUALITY_ORDER } from '@shared/types/constants'; +import { PILL_UNIT_ESSENCE_BY_QUALITY } from '@daoyou/shared/config/alchemyEssenceConfig'; +import { QUALITY_ORDER } from '@daoyou/shared/types/constants'; describe('alchemy yield engine', () => { it('charges one qi per 200 raw essence within the 1 to 20 limit', () => { diff --git a/src/shared/lib/alchemyYield.ts b/packages/shared/src/lib/alchemyYield.ts similarity index 99% rename from src/shared/lib/alchemyYield.ts rename to packages/shared/src/lib/alchemyYield.ts index 5a49f9ac..d476589d 100644 --- a/src/shared/lib/alchemyYield.ts +++ b/packages/shared/src/lib/alchemyYield.ts @@ -7,14 +7,14 @@ import { PILL_APPEARANCE_EFFECT_MULTIPLIER, PILL_CONDENSATION_MULTIPLIER_BY_QUALITY, PILL_UNIT_ESSENCE_BY_QUALITY, -} from '@shared/config/alchemyEssenceConfig'; -import { QUALITY_ORDER, QUALITY_VALUES, type Quality } from '@shared/types/constants'; +} from '@daoyou/shared/config/alchemyEssenceConfig'; +import { QUALITY_ORDER, QUALITY_VALUES, type Quality } from '@daoyou/shared/types/constants'; import type { AlchemyOutputLot, AlchemyYieldProfile, AlchemyYieldDisplayProfile, PillAppearanceGrade, -} from '@shared/types/consumable'; +} from '@daoyou/shared/types/consumable'; export interface AlchemyEssenceMaterial { rank: Quality; diff --git a/src/shared/lib/bagResourceChanges.test.ts b/packages/shared/src/lib/bagResourceChanges.test.ts similarity index 100% rename from src/shared/lib/bagResourceChanges.test.ts rename to packages/shared/src/lib/bagResourceChanges.test.ts diff --git a/src/shared/lib/bagResourceChanges.ts b/packages/shared/src/lib/bagResourceChanges.ts similarity index 100% rename from src/shared/lib/bagResourceChanges.ts rename to packages/shared/src/lib/bagResourceChanges.ts diff --git a/src/shared/lib/blackMarketBelief.test.ts b/packages/shared/src/lib/blackMarketBelief.test.ts similarity index 100% rename from src/shared/lib/blackMarketBelief.test.ts rename to packages/shared/src/lib/blackMarketBelief.test.ts diff --git a/src/shared/lib/blackMarketBelief.ts b/packages/shared/src/lib/blackMarketBelief.ts similarity index 100% rename from src/shared/lib/blackMarketBelief.ts rename to packages/shared/src/lib/blackMarketBelief.ts diff --git a/src/shared/lib/blackMarketMessages.test.ts b/packages/shared/src/lib/blackMarketMessages.test.ts similarity index 100% rename from src/shared/lib/blackMarketMessages.test.ts rename to packages/shared/src/lib/blackMarketMessages.test.ts diff --git a/src/shared/lib/blackMarketMessages.ts b/packages/shared/src/lib/blackMarketMessages.ts similarity index 95% rename from src/shared/lib/blackMarketMessages.ts rename to packages/shared/src/lib/blackMarketMessages.ts index 2e7e46c9..cdab6d2c 100644 --- a/src/shared/lib/blackMarketMessages.ts +++ b/packages/shared/src/lib/blackMarketMessages.ts @@ -1,7 +1,7 @@ import { BLACK_MARKET_INSPECTION_KINDS, type BlackMarketInspectionKind, -} from '@shared/types/blackMarket'; +} from '@daoyou/shared/types/blackMarket'; const LEGACY_INSPECTION_PREFIX = '查验:'; diff --git a/src/shared/lib/blackMarketNegotiation.test.ts b/packages/shared/src/lib/blackMarketNegotiation.test.ts similarity index 100% rename from src/shared/lib/blackMarketNegotiation.test.ts rename to packages/shared/src/lib/blackMarketNegotiation.test.ts diff --git a/src/shared/lib/blackMarketNegotiation.ts b/packages/shared/src/lib/blackMarketNegotiation.ts similarity index 100% rename from src/shared/lib/blackMarketNegotiation.ts rename to packages/shared/src/lib/blackMarketNegotiation.ts diff --git a/src/shared/lib/blackMarketObservations.test.ts b/packages/shared/src/lib/blackMarketObservations.test.ts similarity index 100% rename from src/shared/lib/blackMarketObservations.test.ts rename to packages/shared/src/lib/blackMarketObservations.test.ts diff --git a/src/shared/lib/blackMarketObservations.ts b/packages/shared/src/lib/blackMarketObservations.ts similarity index 80% rename from src/shared/lib/blackMarketObservations.ts rename to packages/shared/src/lib/blackMarketObservations.ts index 01df44bc..644515c2 100644 --- a/src/shared/lib/blackMarketObservations.ts +++ b/packages/shared/src/lib/blackMarketObservations.ts @@ -1,5 +1,5 @@ -import type { BlackMarketObservation } from '@shared/types/blackMarket'; -import { QUALITY_VALUES } from '@shared/types/constants'; +import type { BlackMarketObservation } from '@daoyou/shared/types/blackMarket'; +import { QUALITY_VALUES } from '@daoyou/shared/types/constants'; export interface BlackMarketObservationCandidate extends Omit< BlackMarketObservation, diff --git a/src/shared/lib/blackMarketPricing.test.ts b/packages/shared/src/lib/blackMarketPricing.test.ts similarity index 100% rename from src/shared/lib/blackMarketPricing.test.ts rename to packages/shared/src/lib/blackMarketPricing.test.ts diff --git a/src/shared/lib/blackMarketPricing.ts b/packages/shared/src/lib/blackMarketPricing.ts similarity index 96% rename from src/shared/lib/blackMarketPricing.ts rename to packages/shared/src/lib/blackMarketPricing.ts index 76b76cb1..976f7af0 100644 --- a/src/shared/lib/blackMarketPricing.ts +++ b/packages/shared/src/lib/blackMarketPricing.ts @@ -1,9 +1,9 @@ import { BASE_PRICES, TYPE_MULTIPLIERS, -} from '@shared/engine/material/creation/config'; -import type { BlackMarketNpcId } from '@shared/types/blackMarket'; -import type { MaterialType, Quality } from '@shared/types/constants'; +} from '@daoyou/shared/engine/material/creation/config'; +import type { BlackMarketNpcId } from '@daoyou/shared/types/blackMarket'; +import type { MaterialType, Quality } from '@daoyou/shared/types/constants'; import { blackMarketUnit } from './blackMarketRules'; export type BlackMarketDisposition = diff --git a/src/shared/lib/blackMarketRules.test.ts b/packages/shared/src/lib/blackMarketRules.test.ts similarity index 100% rename from src/shared/lib/blackMarketRules.test.ts rename to packages/shared/src/lib/blackMarketRules.test.ts diff --git a/src/shared/lib/blackMarketRules.ts b/packages/shared/src/lib/blackMarketRules.ts similarity index 96% rename from src/shared/lib/blackMarketRules.ts rename to packages/shared/src/lib/blackMarketRules.ts index 2b401fc5..945e7351 100644 --- a/src/shared/lib/blackMarketRules.ts +++ b/packages/shared/src/lib/blackMarketRules.ts @@ -1,4 +1,4 @@ -import type { BlackMarketRevealRating } from '@shared/types/blackMarket'; +import type { BlackMarketRevealRating } from '@daoyou/shared/types/blackMarket'; export const BLACK_MARKET_MAX_INSPECTIONS = 3; export const BLACK_MARKET_MAX_TURNS = 6; diff --git a/src/shared/lib/bodyCultivation/benefits.ts b/packages/shared/src/lib/bodyCultivation/benefits.ts similarity index 94% rename from src/shared/lib/bodyCultivation/benefits.ts rename to packages/shared/src/lib/bodyCultivation/benefits.ts index 0d437ac4..17c9a2de 100644 --- a/src/shared/lib/bodyCultivation/benefits.ts +++ b/packages/shared/src/lib/bodyCultivation/benefits.ts @@ -1,4 +1,4 @@ -import type { BodyCultivationTrackKey } from '@shared/types/condition'; +import type { BodyCultivationTrackKey } from '@daoyou/shared/types/condition'; import { BODY_CULTIVATION_PACK, BODY_CULTIVATION_TRACK_KEYS } from './pack'; export function bodyCultivationBenefits(levels: Record, maxHp: number, pack = BODY_CULTIVATION_PACK) { diff --git a/src/shared/lib/bodyCultivation/breakthrough.ts b/packages/shared/src/lib/bodyCultivation/breakthrough.ts similarity index 96% rename from src/shared/lib/bodyCultivation/breakthrough.ts rename to packages/shared/src/lib/bodyCultivation/breakthrough.ts index 750a3477..82922175 100644 --- a/src/shared/lib/bodyCultivation/breakthrough.ts +++ b/packages/shared/src/lib/bodyCultivation/breakthrough.ts @@ -2,8 +2,8 @@ import type { BodyCultivationRealm, BodyCultivationState, CultivatorCondition, -} from '@shared/types/condition'; -import type { RealmType } from '@shared/types/constants'; +} from '@daoyou/shared/types/condition'; +import type { RealmType } from '@daoyou/shared/types/constants'; import { BODY_CULTIVATION_REALM_REQUIREMENTS, getNextBodyCultivationRealm, diff --git a/src/shared/lib/bodyCultivation/config.ts b/packages/shared/src/lib/bodyCultivation/config.ts similarity index 96% rename from src/shared/lib/bodyCultivation/config.ts rename to packages/shared/src/lib/bodyCultivation/config.ts index d24115ef..12be70c3 100644 --- a/src/shared/lib/bodyCultivation/config.ts +++ b/packages/shared/src/lib/bodyCultivation/config.ts @@ -5,8 +5,8 @@ import type { ConditionProgressTrack, LegacyTemperingTrackKey, LegacyTemperingTrackPath, -} from '@shared/types/condition'; -import { REALM_ORDER, type RealmType } from '@shared/types/constants'; +} from '@daoyou/shared/types/condition'; +import { REALM_ORDER, type RealmType } from '@daoyou/shared/types/constants'; import { BODY_CULTIVATION_PACK, BODY_CULTIVATION_TRACK_KEYS, bodyCultivationThreshold } from './pack'; export { BODY_CULTIVATION_TRACK_KEYS } from './pack'; diff --git a/src/shared/lib/bodyCultivation/data/README.md b/packages/shared/src/lib/bodyCultivation/data/README.md similarity index 100% rename from src/shared/lib/bodyCultivation/data/README.md rename to packages/shared/src/lib/bodyCultivation/data/README.md diff --git a/src/shared/lib/bodyCultivation/data/body-cultivation.json b/packages/shared/src/lib/bodyCultivation/data/body-cultivation.json similarity index 100% rename from src/shared/lib/bodyCultivation/data/body-cultivation.json rename to packages/shared/src/lib/bodyCultivation/data/body-cultivation.json diff --git a/src/shared/lib/bodyCultivation/data/body-cultivation.schema.json b/packages/shared/src/lib/bodyCultivation/data/body-cultivation.schema.json similarity index 100% rename from src/shared/lib/bodyCultivation/data/body-cultivation.schema.json rename to packages/shared/src/lib/bodyCultivation/data/body-cultivation.schema.json diff --git a/src/shared/lib/bodyCultivation/normalize.test.ts b/packages/shared/src/lib/bodyCultivation/normalize.test.ts similarity index 98% rename from src/shared/lib/bodyCultivation/normalize.test.ts rename to packages/shared/src/lib/bodyCultivation/normalize.test.ts index 1a3eee87..257b7488 100644 --- a/src/shared/lib/bodyCultivation/normalize.test.ts +++ b/packages/shared/src/lib/bodyCultivation/normalize.test.ts @@ -9,8 +9,8 @@ import type { BodyCultivationRealm, BodyCultivationTrackKey, CultivatorCondition, -} from '@shared/types/condition'; -import type { RealmType } from '@shared/types/constants'; +} from '@daoyou/shared/types/condition'; +import type { RealmType } from '@daoyou/shared/types/constants'; function createCondition(): CultivatorCondition { return { diff --git a/src/shared/lib/bodyCultivation/normalize.ts b/packages/shared/src/lib/bodyCultivation/normalize.ts similarity index 98% rename from src/shared/lib/bodyCultivation/normalize.ts rename to packages/shared/src/lib/bodyCultivation/normalize.ts index 0a541000..e70d7ce2 100644 --- a/src/shared/lib/bodyCultivation/normalize.ts +++ b/packages/shared/src/lib/bodyCultivation/normalize.ts @@ -3,7 +3,7 @@ import type { BodyCultivationTrackKey, ConditionProgressTrack, CultivatorCondition, -} from '@shared/types/condition'; +} from '@daoyou/shared/types/condition'; import { BODY_CULTIVATION_TRACK_KEYS, BODY_REALM_LABELS, diff --git a/src/shared/lib/bodyCultivation/pack.test.ts b/packages/shared/src/lib/bodyCultivation/pack.test.ts similarity index 90% rename from src/shared/lib/bodyCultivation/pack.test.ts rename to packages/shared/src/lib/bodyCultivation/pack.test.ts index 9da6101d..cc600d6c 100644 --- a/src/shared/lib/bodyCultivation/pack.test.ts +++ b/packages/shared/src/lib/bodyCultivation/pack.test.ts @@ -4,9 +4,9 @@ import raw from './data/body-cultivation.json'; import schema from './data/body-cultivation.schema.json'; import { BodyCultivationPackShape, BODY_CULTIVATION_TRACK_KEYS, loadBodyCultivationPack, bodyCultivationThreshold } from './pack'; import { bodyCultivationEffectTexts } from './benefits'; -import { compileBodyCultivationV6 } from '@shared/engine/combat-v6/projection/body-cultivation-v6'; -import { compileCharacterPanelV1 } from '@shared/engine/combat-v6/projection/character-panel-v1'; -import type { BodyCultivationState } from '@shared/types/condition'; +import { compileBodyCultivationV6 } from '@daoyou/shared/engine/combat-v6/projection/body-cultivation-v6'; +import { compileCharacterPanelV1 } from '@daoyou/shared/engine/combat-v6/projection/character-panel-v1'; +import type { BodyCultivationState } from '@daoyou/shared/types/condition'; function state(level: number): BodyCultivationState { return { version: 1, realm: 'dao_body', milestones: {}, tracks: Object.fromEntries(BODY_CULTIVATION_TRACK_KEYS.map(key => [key, { level, progress: 0 }])) as BodyCultivationState['tracks'] }; diff --git a/src/shared/lib/bodyCultivation/pack.ts b/packages/shared/src/lib/bodyCultivation/pack.ts similarity index 95% rename from src/shared/lib/bodyCultivation/pack.ts rename to packages/shared/src/lib/bodyCultivation/pack.ts index 240a45a0..87c1c689 100644 --- a/src/shared/lib/bodyCultivation/pack.ts +++ b/packages/shared/src/lib/bodyCultivation/pack.ts @@ -1,7 +1,7 @@ -import { formatContentPackErrors } from '@shared/lib/content-pack-errors'; +import { formatContentPackErrors } from '@daoyou/shared/lib/content-pack-errors'; import { z } from 'zod'; -import { REALM_ORDER, type RealmType } from '@shared/types/constants'; -import type { BodyCultivationRealm, BodyCultivationTrackKey } from '@shared/types/condition'; +import { REALM_ORDER, type RealmType } from '@daoyou/shared/types/constants'; +import type { BodyCultivationRealm, BodyCultivationTrackKey } from '@daoyou/shared/types/condition'; import raw from './data/body-cultivation.json'; export const BODY_CULTIVATION_TRACK_KEYS = ['skin', 'sinew_bone', 'organs', 'qi_blood', 'primordial_spirit'] as const satisfies BodyCultivationTrackKey[]; diff --git a/src/shared/lib/bodyCultivation/ranking.test.ts b/packages/shared/src/lib/bodyCultivation/ranking.test.ts similarity index 94% rename from src/shared/lib/bodyCultivation/ranking.test.ts rename to packages/shared/src/lib/bodyCultivation/ranking.test.ts index 3b0867f4..5268ff97 100644 --- a/src/shared/lib/bodyCultivation/ranking.test.ts +++ b/packages/shared/src/lib/bodyCultivation/ranking.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import type { CultivatorCondition } from '@shared/types/condition'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; import { getBodyCultivationRankingTag } from './ranking'; describe('body cultivation ranking tag', () => { diff --git a/src/shared/lib/bodyCultivation/ranking.ts b/packages/shared/src/lib/bodyCultivation/ranking.ts similarity index 88% rename from src/shared/lib/bodyCultivation/ranking.ts rename to packages/shared/src/lib/bodyCultivation/ranking.ts index 6322382a..8d1b5e18 100644 --- a/src/shared/lib/bodyCultivation/ranking.ts +++ b/packages/shared/src/lib/bodyCultivation/ranking.ts @@ -1,4 +1,4 @@ -import type { CultivatorCondition } from '@shared/types/condition'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; import { BODY_REALM_LABELS } from './config'; import { getBodyCultivationSummary } from './summary'; diff --git a/src/shared/lib/bodyCultivation/summary.ts b/packages/shared/src/lib/bodyCultivation/summary.ts similarity index 97% rename from src/shared/lib/bodyCultivation/summary.ts rename to packages/shared/src/lib/bodyCultivation/summary.ts index 3c02431f..afecbb1f 100644 --- a/src/shared/lib/bodyCultivation/summary.ts +++ b/packages/shared/src/lib/bodyCultivation/summary.ts @@ -5,8 +5,8 @@ import type { BodyCultivationTrackKey, BodyCultivationTrackPath, CultivatorCondition, -} from '@shared/types/condition'; -import type { RealmType } from '@shared/types/constants'; +} from '@daoyou/shared/types/condition'; +import type { RealmType } from '@daoyou/shared/types/constants'; import { BODY_CULTIVATION_TRACK_KEYS, BODY_CULTIVATION_REALM_REQUIREMENTS, diff --git a/src/shared/lib/breakthroughPill.ts b/packages/shared/src/lib/breakthroughPill.ts similarity index 88% rename from src/shared/lib/breakthroughPill.ts rename to packages/shared/src/lib/breakthroughPill.ts index fbfe9c8d..3f79d5c0 100644 --- a/src/shared/lib/breakthroughPill.ts +++ b/packages/shared/src/lib/breakthroughPill.ts @@ -1,6 +1,6 @@ -import { REALM_VALUES, type RealmType } from '@shared/types/constants'; -import type { ConditionOperation, PillSpec } from '@shared/types/consumable'; -import type { Consumable } from '@shared/types/cultivator'; +import { REALM_VALUES, type RealmType } from '@daoyou/shared/types/constants'; +import type { ConditionOperation, PillSpec } from '@daoyou/shared/types/consumable'; +import type { Consumable } from '@daoyou/shared/types/cultivator'; export function getNextMajorRealm(realm: RealmType): RealmType | null { const index = REALM_VALUES.indexOf(realm); diff --git a/src/shared/lib/characterAttributeLabels.ts b/packages/shared/src/lib/characterAttributeLabels.ts similarity index 82% rename from src/shared/lib/characterAttributeLabels.ts rename to packages/shared/src/lib/characterAttributeLabels.ts index cd202154..82b1783d 100644 --- a/src/shared/lib/characterAttributeLabels.ts +++ b/packages/shared/src/lib/characterAttributeLabels.ts @@ -1,4 +1,4 @@ -import type { Attributes } from '@shared/types/cultivator'; +import type { Attributes } from '@daoyou/shared/types/cultivator'; /** 人物基础六维;speed 表示身法,战斗面板中的 speed 另称速度。 */ export const CHARACTER_ATTRIBUTE_LABELS = { diff --git a/src/shared/lib/characterResourceChanges.test.ts b/packages/shared/src/lib/characterResourceChanges.test.ts similarity index 93% rename from src/shared/lib/characterResourceChanges.test.ts rename to packages/shared/src/lib/characterResourceChanges.test.ts index 5eec34b9..1719963b 100644 --- a/src/shared/lib/characterResourceChanges.test.ts +++ b/packages/shared/src/lib/characterResourceChanges.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import type { ResourceChangeDescriptor, ResourceScope } from '@shared/contracts/resources'; +import type { ResourceChangeDescriptor, ResourceScope } from '@daoyou/shared/contracts/resources'; import { withCharacterPanelInvalidations } from './characterResourceChanges'; const scope: ResourceScope = { kind: 'cultivator', id: 'player-1' }; diff --git a/src/shared/lib/characterResourceChanges.ts b/packages/shared/src/lib/characterResourceChanges.ts similarity index 89% rename from src/shared/lib/characterResourceChanges.ts rename to packages/shared/src/lib/characterResourceChanges.ts index dd024c52..333c02ec 100644 --- a/src/shared/lib/characterResourceChanges.ts +++ b/packages/shared/src/lib/characterResourceChanges.ts @@ -1,4 +1,4 @@ -import type { ResourceChangeDescriptor, ResourceScope } from '@shared/contracts/resources'; +import type { ResourceChangeDescriptor, ResourceScope } from '@daoyou/shared/contracts/resources'; type ScopedChange = ResourceChangeDescriptor & { scope: ResourceScope }; diff --git a/src/shared/lib/cn.ts b/packages/shared/src/lib/cn.ts similarity index 100% rename from src/shared/lib/cn.ts rename to packages/shared/src/lib/cn.ts diff --git a/src/shared/lib/condition.test.ts b/packages/shared/src/lib/condition.test.ts similarity index 99% rename from src/shared/lib/condition.test.ts rename to packages/shared/src/lib/condition.test.ts index 0d4fd8c9..82fe2439 100644 --- a/src/shared/lib/condition.test.ts +++ b/packages/shared/src/lib/condition.test.ts @@ -4,7 +4,7 @@ import type { ConditionStatusInstance, ConditionStatusKey, CultivatorCondition, -} from '@shared/types/condition'; +} from '@daoyou/shared/types/condition'; import { getConditionStatusCureTargets, getNextConditionStatusExpiryMs, diff --git a/src/shared/lib/condition.ts b/packages/shared/src/lib/condition.ts similarity index 99% rename from src/shared/lib/condition.ts rename to packages/shared/src/lib/condition.ts index 0fff7926..60bc0f52 100644 --- a/src/shared/lib/condition.ts +++ b/packages/shared/src/lib/condition.ts @@ -5,13 +5,13 @@ import type { ConditionStatusDuration, ConditionStatusInstance, CultivatorCondition, -} from '@shared/types/condition'; +} from '@daoyou/shared/types/condition'; import { getConditionStatusTemplate } from './conditionStatusRegistry'; import { createDefaultBodyCultivationState, normalizeBodyCultivationState, } from './bodyCultivation/normalize'; -import { PILL_TOXICITY_CAP } from '@shared/config/consumableSystem'; +import { PILL_TOXICITY_CAP } from '@daoyou/shared/config/consumableSystem'; export interface PillToxicityStage { key: 'none' | 'light' | 'heavy' | 'critical'; diff --git a/src/shared/lib/conditionStatusRegistry.ts b/packages/shared/src/lib/conditionStatusRegistry.ts similarity index 99% rename from src/shared/lib/conditionStatusRegistry.ts rename to packages/shared/src/lib/conditionStatusRegistry.ts index 7a2960b0..7d214f13 100644 --- a/src/shared/lib/conditionStatusRegistry.ts +++ b/packages/shared/src/lib/conditionStatusRegistry.ts @@ -2,7 +2,7 @@ import type { ConditionStatusInstance, ConditionStatusKey, CultivatorCondition, -} from '@shared/types/condition'; +} from '@daoyou/shared/types/condition'; import { getGameConceptInfo } from './gameConceptDisplay'; export interface ConditionStatusTemplate { diff --git a/src/shared/lib/constants/appSettings.ts b/packages/shared/src/lib/constants/appSettings.ts similarity index 100% rename from src/shared/lib/constants/appSettings.ts rename to packages/shared/src/lib/constants/appSettings.ts diff --git a/src/shared/lib/consumableSpec.ts b/packages/shared/src/lib/consumableSpec.ts similarity index 100% rename from src/shared/lib/consumableSpec.ts rename to packages/shared/src/lib/consumableSpec.ts diff --git a/src/shared/lib/consumables.test.ts b/packages/shared/src/lib/consumables.test.ts similarity index 95% rename from src/shared/lib/consumables.test.ts rename to packages/shared/src/lib/consumables.test.ts index ba4d2b9c..db72fbed 100644 --- a/src/shared/lib/consumables.test.ts +++ b/packages/shared/src/lib/consumables.test.ts @@ -1,4 +1,4 @@ -import type { Consumable } from '@shared/types/cultivator'; +import type { Consumable } from '@daoyou/shared/types/cultivator'; import { describe, expect, it } from 'vitest'; import { buildConsumableStackKey } from './consumables'; diff --git a/src/shared/lib/consumables.ts b/packages/shared/src/lib/consumables.ts similarity index 97% rename from src/shared/lib/consumables.ts rename to packages/shared/src/lib/consumables.ts index 1e060703..38b35352 100644 --- a/src/shared/lib/consumables.ts +++ b/packages/shared/src/lib/consumables.ts @@ -12,8 +12,8 @@ import type { RemoveStatusOperation, RestoreResourceOperation, TalismanSpec, -} from '@shared/types/consumable'; -import type { Consumable } from '@shared/types/cultivator'; +} from '@daoyou/shared/types/consumable'; +import type { Consumable } from '@daoyou/shared/types/cultivator'; function isRecord(value: unknown): value is Record { return !!value && typeof value === 'object' && !Array.isArray(value); diff --git a/src/shared/lib/content-pack-errors.test.ts b/packages/shared/src/lib/content-pack-errors.test.ts similarity index 100% rename from src/shared/lib/content-pack-errors.test.ts rename to packages/shared/src/lib/content-pack-errors.test.ts diff --git a/src/shared/lib/content-pack-errors.ts b/packages/shared/src/lib/content-pack-errors.ts similarity index 100% rename from src/shared/lib/content-pack-errors.ts rename to packages/shared/src/lib/content-pack-errors.ts diff --git a/src/shared/lib/cultivationBoost.ts b/packages/shared/src/lib/cultivationBoost.ts similarity index 92% rename from src/shared/lib/cultivationBoost.ts rename to packages/shared/src/lib/cultivationBoost.ts index 0c9662c5..d5942828 100644 --- a/src/shared/lib/cultivationBoost.ts +++ b/packages/shared/src/lib/cultivationBoost.ts @@ -1,10 +1,10 @@ -import { ALCHEMY_EFFECT_BASE_BY_QUALITY } from '@shared/config/alchemyEffectConfig'; +import { ALCHEMY_EFFECT_BASE_BY_QUALITY } from '@daoyou/shared/config/alchemyEffectConfig'; import type { ConditionStatusInstance, CultivatorCondition, -} from '@shared/types/condition'; -import type { Quality } from '@shared/types/constants'; -import type { AddStatusOperation } from '@shared/types/consumable'; +} from '@daoyou/shared/types/condition'; +import type { Quality } from '@daoyou/shared/types/constants'; +import type { AddStatusOperation } from '@daoyou/shared/types/consumable'; import { isConditionStatusActive } from './condition'; export const CULTIVATION_BOOST_STATUS_KEY = 'cultivation_boost' as const; diff --git a/src/shared/lib/cultivationExpGain.ts b/packages/shared/src/lib/cultivationExpGain.ts similarity index 100% rename from src/shared/lib/cultivationExpGain.ts rename to packages/shared/src/lib/cultivationExpGain.ts diff --git a/src/shared/lib/cultivatorDisplay.test.ts b/packages/shared/src/lib/cultivatorDisplay.test.ts similarity index 94% rename from src/shared/lib/cultivatorDisplay.test.ts rename to packages/shared/src/lib/cultivatorDisplay.test.ts index f8aa535e..bbbc17c8 100644 --- a/src/shared/lib/cultivatorDisplay.test.ts +++ b/packages/shared/src/lib/cultivatorDisplay.test.ts @@ -1,8 +1,8 @@ -import { CHARACTER_MANUALS_V1, manualRule } from '@shared/engine/combat-v6/manuals/content'; -import { DAO_EQUIPMENT_GENERATOR_VERSION, DAO_EQUIPMENT_TEMPLATE_ID, generateDaoEquipmentV1 } from '@shared/engine/combat-v6/equipment'; +import { CHARACTER_MANUALS_V1, manualRule } from '@daoyou/shared/engine/combat-v6/manuals/content'; +import { DAO_EQUIPMENT_GENERATOR_VERSION, DAO_EQUIPMENT_TEMPLATE_ID, generateDaoEquipmentV1 } from '@daoyou/shared/engine/combat-v6/equipment'; import { describe, expect, it } from 'vitest'; -import { COMBAT_V6_SECT_DEFINITIONS_V4 } from '@shared/engine/combat-v6/content'; -import { projectCharacterToCombatV6 } from '@shared/engine/combat-v6/projection'; +import { COMBAT_V6_SECT_DEFINITIONS_V4 } from '@daoyou/shared/engine/combat-v6/content'; +import { projectCharacterToCombatV6 } from '@daoyou/shared/engine/combat-v6/projection'; import { characterResourceMaxima, normalizeCharacterResource, projectCharacterDisplay, projectCharacterDisplaySnapshot, type CharacterDisplayBuild, type CultivatorDisplayInput } from './cultivatorDisplay'; const player: CultivatorDisplayInput = { diff --git a/src/shared/lib/cultivatorDisplay.ts b/packages/shared/src/lib/cultivatorDisplay.ts similarity index 95% rename from src/shared/lib/cultivatorDisplay.ts rename to packages/shared/src/lib/cultivatorDisplay.ts index c0a309ce..5874887a 100644 --- a/src/shared/lib/cultivatorDisplay.ts +++ b/packages/shared/src/lib/cultivatorDisplay.ts @@ -1,14 +1,14 @@ import { CHARACTER_ATTRIBUTE_LABELS } from './characterAttributeLabels'; export { CHARACTER_ATTRIBUTE_LABELS } from './characterAttributeLabels'; -import type { Cultivator } from '@shared/types/cultivator'; -import type { Attributes } from '@shared/types/cultivator'; -import type { CultivatorCondition } from '@shared/types/condition'; +import type { Cultivator } from '@daoyou/shared/types/cultivator'; +import type { Attributes } from '@daoyou/shared/types/cultivator'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; import { projectNaturalRecoveryResources } from './condition'; import { projectCharacterToCombatV6, type CharacterPanelV1, type CharacterCombatInput, -} from '@shared/engine/combat-v6/projection'; +} from '@daoyou/shared/engine/combat-v6/projection'; export type CultivatorDisplayInput = Pick; - endDisposition?: import('@shared/lib/dungeon/settlementPolicy').DungeonEndDisposition; + endDisposition?: import('@daoyou/shared/lib/dungeon/settlementPolicy').DungeonEndDisposition; runId?: string; cultivatorId: string; mapNodeId: string; diff --git a/src/shared/lib/fates.test.ts b/packages/shared/src/lib/fates.test.ts similarity index 97% rename from src/shared/lib/fates.test.ts rename to packages/shared/src/lib/fates.test.ts index 8604eec1..e23012a4 100644 --- a/src/shared/lib/fates.test.ts +++ b/packages/shared/src/lib/fates.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import type { FateEffectType, PreHeavenFate } from '@shared/types/cultivator'; +import type { FateEffectType, PreHeavenFate } from '@daoyou/shared/types/cultivator'; import { evaluateFateContext, getAlchemySpiritStoneMultiplier, diff --git a/src/shared/lib/fates.ts b/packages/shared/src/lib/fates.ts similarity index 99% rename from src/shared/lib/fates.ts rename to packages/shared/src/lib/fates.ts index 258543ab..37496b61 100644 --- a/src/shared/lib/fates.ts +++ b/packages/shared/src/lib/fates.ts @@ -1,7 +1,7 @@ import type { FateEffectEntry, PreHeavenFate, -} from '@shared/types/cultivator'; +} from '@daoyou/shared/types/cultivator'; /** * 命格聚合结果。 diff --git a/src/shared/lib/game/beastMarket.test.ts b/packages/shared/src/lib/game/beastMarket.test.ts similarity index 97% rename from src/shared/lib/game/beastMarket.test.ts rename to packages/shared/src/lib/game/beastMarket.test.ts index b760aa12..e9bd422d 100644 --- a/src/shared/lib/game/beastMarket.test.ts +++ b/packages/shared/src/lib/game/beastMarket.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { BEAST_SUPERIOR_BOOK_SKILL_IDS } from '@shared/engine/combat-v6/beasts/content'; +import { BEAST_SUPERIOR_BOOK_SKILL_IDS } from '@daoyou/shared/engine/combat-v6/beasts/content'; import { BEAST_MARKET_PACK, loadBeastMarketPack, diff --git a/src/shared/lib/game/beastMarket.ts b/packages/shared/src/lib/game/beastMarket.ts similarity index 95% rename from src/shared/lib/game/beastMarket.ts rename to packages/shared/src/lib/game/beastMarket.ts index ff6e2db3..c737ccb1 100644 --- a/src/shared/lib/game/beastMarket.ts +++ b/packages/shared/src/lib/game/beastMarket.ts @@ -1,6 +1,6 @@ -import { BEAST_SUPERIOR_BOOK_SKILL_IDS } from '@shared/engine/combat-v6/beasts/content'; -import { BOOKS } from '@shared/items/definitions/beast-books'; -import type { MarketLayer } from '@shared/types/market'; +import { BEAST_SUPERIOR_BOOK_SKILL_IDS } from '@daoyou/shared/engine/combat-v6/beasts/content'; +import { BOOKS } from '@daoyou/shared/items/definitions/beast-books'; +import type { MarketLayer } from '@daoyou/shared/types/market'; import { z } from 'zod'; import data from './data/beast-market.json'; diff --git a/src/shared/lib/game/data/README.md b/packages/shared/src/lib/game/data/README.md similarity index 100% rename from src/shared/lib/game/data/README.md rename to packages/shared/src/lib/game/data/README.md diff --git a/src/shared/lib/game/data/beast-market.json b/packages/shared/src/lib/game/data/beast-market.json similarity index 100% rename from src/shared/lib/game/data/beast-market.json rename to packages/shared/src/lib/game/data/beast-market.json diff --git a/src/shared/lib/game/mapAtlas.test.ts b/packages/shared/src/lib/game/mapAtlas.test.ts similarity index 100% rename from src/shared/lib/game/mapAtlas.test.ts rename to packages/shared/src/lib/game/mapAtlas.test.ts diff --git a/src/shared/lib/game/mapAtlas.ts b/packages/shared/src/lib/game/mapAtlas.ts similarity index 100% rename from src/shared/lib/game/mapAtlas.ts rename to packages/shared/src/lib/game/mapAtlas.ts diff --git a/src/shared/lib/game/mapAtlasCategories.test.ts b/packages/shared/src/lib/game/mapAtlasCategories.test.ts similarity index 100% rename from src/shared/lib/game/mapAtlasCategories.test.ts rename to packages/shared/src/lib/game/mapAtlasCategories.test.ts diff --git a/src/shared/lib/game/mapAtlasCategories.ts b/packages/shared/src/lib/game/mapAtlasCategories.ts similarity index 100% rename from src/shared/lib/game/mapAtlasCategories.ts rename to packages/shared/src/lib/game/mapAtlasCategories.ts diff --git a/src/shared/lib/game/mapSystem.test.ts b/packages/shared/src/lib/game/mapSystem.test.ts similarity index 98% rename from src/shared/lib/game/mapSystem.test.ts rename to packages/shared/src/lib/game/mapSystem.test.ts index adc72f97..2b1c296f 100644 --- a/src/shared/lib/game/mapSystem.test.ts +++ b/packages/shared/src/lib/game/mapSystem.test.ts @@ -1,4 +1,4 @@ -import { PRODUCTION_SECT_IDS } from '@shared/engine/sect/content'; +import { PRODUCTION_SECT_IDS } from '@daoyou/shared/engine/sect/content'; import { describe, expect, it } from 'vitest'; import type { DungeonDifficultyTier, MapNodeInfo } from './mapSystem'; import { diff --git a/src/shared/lib/game/mapSystem.ts b/packages/shared/src/lib/game/mapSystem.ts similarity index 96% rename from src/shared/lib/game/mapSystem.ts rename to packages/shared/src/lib/game/mapSystem.ts index 25284fc0..fec17b87 100644 --- a/src/shared/lib/game/mapSystem.ts +++ b/packages/shared/src/lib/game/mapSystem.ts @@ -1,6 +1,6 @@ -import type { RealmStage } from '@shared/types/constants'; -import { REALM_ORDER, type RealmType } from '@shared/types/constants'; -import type { MarketLayer, RegionProfileKey } from '@shared/types/market'; +import type { RealmStage } from '@daoyou/shared/types/constants'; +import { REALM_ORDER, type RealmType } from '@daoyou/shared/types/constants'; +import type { MarketLayer, RegionProfileKey } from '@daoyou/shared/types/market'; import mapData from '../../data/map.json'; export type DungeonDifficultyTier = diff --git a/src/shared/lib/game/marketConfig.test.ts b/packages/shared/src/lib/game/marketConfig.test.ts similarity index 100% rename from src/shared/lib/game/marketConfig.test.ts rename to packages/shared/src/lib/game/marketConfig.test.ts diff --git a/src/shared/lib/game/marketConfig.ts b/packages/shared/src/lib/game/marketConfig.ts similarity index 97% rename from src/shared/lib/game/marketConfig.ts rename to packages/shared/src/lib/game/marketConfig.ts index c6f347be..ca997471 100644 --- a/src/shared/lib/game/marketConfig.ts +++ b/packages/shared/src/lib/game/marketConfig.ts @@ -1,12 +1,12 @@ -import type { MapNode } from '@shared/lib/game/mapSystem'; -import { getMapNode } from '@shared/lib/game/mapSystem'; +import type { MapNode } from '@daoyou/shared/lib/game/mapSystem'; +import { getMapNode } from '@daoyou/shared/lib/game/mapSystem'; import { MATERIAL_TYPE_VALUES, REALM_ORDER, type MaterialType, type Quality, type RealmType, -} from '@shared/types/constants'; +} from '@daoyou/shared/types/constants'; import type { MarketAccessRule, MarketAccessState, @@ -14,8 +14,8 @@ import type { RegionProfile, RegionProfileKey, ResolvedLayerConfig, -} from '@shared/types/market'; -import { MARKET_ITEM_COUNT, MARKET_REFRESH_MS } from '@shared/types/market'; +} from '@daoyou/shared/types/market'; +import { MARKET_ITEM_COUNT, MARKET_REFRESH_MS } from '@daoyou/shared/types/market'; import mapData from '../../data/map.json'; export const MARKET_STALE_RETRY_MS = 15000; diff --git a/src/shared/lib/gameConceptDisplay.ts b/packages/shared/src/lib/gameConceptDisplay.ts similarity index 99% rename from src/shared/lib/gameConceptDisplay.ts rename to packages/shared/src/lib/gameConceptDisplay.ts index a92977cc..a1413f46 100644 --- a/src/shared/lib/gameConceptDisplay.ts +++ b/packages/shared/src/lib/gameConceptDisplay.ts @@ -1,5 +1,5 @@ import { CHARACTER_ATTRIBUTE_LABELS } from './characterAttributeLabels'; -import type { ConditionResourceKey } from '@shared/types/condition'; +import type { ConditionResourceKey } from '@daoyou/shared/types/condition'; import type { ConsumableType, ElementType, @@ -7,8 +7,8 @@ import type { MaterialType, SkillType, StatusEffect, -} from '@shared/types/constants'; -import type { Attributes } from '@shared/types/cultivator'; +} from '@daoyou/shared/types/constants'; +import type { Attributes } from '@daoyou/shared/types/cultivator'; export interface GameConceptDisplayInfo { label: string; diff --git a/src/shared/lib/healingPill.ts b/packages/shared/src/lib/healingPill.ts similarity index 72% rename from src/shared/lib/healingPill.ts rename to packages/shared/src/lib/healingPill.ts index 42f69483..f77eaced 100644 --- a/src/shared/lib/healingPill.ts +++ b/packages/shared/src/lib/healingPill.ts @@ -1,5 +1,5 @@ -import { QUALITY_ORDER, type Quality } from '@shared/types/constants'; -import type { ConditionStatusKey } from '@shared/types/condition'; +import { QUALITY_ORDER, type Quality } from '@daoyou/shared/types/constants'; +import type { ConditionStatusKey } from '@daoyou/shared/types/condition'; type HealingCuredStatus = Extract< ConditionStatusKey, diff --git a/src/shared/lib/itemExchangeShop.test.ts b/packages/shared/src/lib/itemExchangeShop.test.ts similarity index 100% rename from src/shared/lib/itemExchangeShop.test.ts rename to packages/shared/src/lib/itemExchangeShop.test.ts diff --git a/src/shared/lib/itemExchangeShop.ts b/packages/shared/src/lib/itemExchangeShop.ts similarity index 100% rename from src/shared/lib/itemExchangeShop.ts rename to packages/shared/src/lib/itemExchangeShop.ts diff --git a/src/shared/lib/itemLibrary.test.ts b/packages/shared/src/lib/itemLibrary.test.ts similarity index 97% rename from src/shared/lib/itemLibrary.test.ts rename to packages/shared/src/lib/itemLibrary.test.ts index 1b404e3e..5d6a0c4a 100644 --- a/src/shared/lib/itemLibrary.test.ts +++ b/packages/shared/src/lib/itemLibrary.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { consumableSchema } from '@shared/contracts/resources/inventory'; +import { consumableSchema } from '@daoyou/shared/contracts/resources/inventory'; import { ItemLibraryConsumablePayloadSchema, ItemLibraryMaterialPayloadSchema, diff --git a/src/shared/lib/itemLibrary.ts b/packages/shared/src/lib/itemLibrary.ts similarity index 98% rename from src/shared/lib/itemLibrary.ts rename to packages/shared/src/lib/itemLibrary.ts index dd4004a5..86ae7573 100644 --- a/src/shared/lib/itemLibrary.ts +++ b/packages/shared/src/lib/itemLibrary.ts @@ -1,4 +1,4 @@ -import type { ResourceOperation } from '@shared/engine/resource/types'; +import type { ResourceOperation } from '@daoyou/shared/engine/resource/types'; import { CONSUMABLE_TYPE_VALUES, ELEMENT_VALUES, @@ -7,15 +7,15 @@ import { QUALITY_VALUES, REALM_STAGE_VALUES, REALM_VALUES, -} from '@shared/types/constants'; +} from '@daoyou/shared/types/constants'; import { ALCHEMY_PROPERTY_KEY_VALUES, PILL_APPEARANCE_GRADE_VALUES, PILL_FAMILY_VALUES, PILL_QUOTA_CATEGORY_VALUES, TALISMAN_SESSION_MODE_VALUES, -} from '@shared/types/consumable'; -import type { MailAttachment } from '@shared/types/mail'; +} from '@daoyou/shared/types/consumable'; +import type { MailAttachment } from '@daoyou/shared/types/mail'; import { z } from 'zod'; import { BeastTransferSchema } from '../contracts/beastTrade'; import { MailInventoryGrantSchema } from '../contracts/mail'; diff --git a/src/shared/lib/marrowWash.test.ts b/packages/shared/src/lib/marrowWash.test.ts similarity index 97% rename from src/shared/lib/marrowWash.test.ts rename to packages/shared/src/lib/marrowWash.test.ts index a56ac610..c79a63e6 100644 --- a/src/shared/lib/marrowWash.test.ts +++ b/packages/shared/src/lib/marrowWash.test.ts @@ -1,4 +1,4 @@ -import type { CultivatorCondition } from '@shared/types/condition'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; import { describe, expect, it } from 'vitest'; import { advanceMarrowWashTowardBreakthrough, diff --git a/src/shared/lib/marrowWash.ts b/packages/shared/src/lib/marrowWash.ts similarity index 97% rename from src/shared/lib/marrowWash.ts rename to packages/shared/src/lib/marrowWash.ts index 259ff8ca..d3d67902 100644 --- a/src/shared/lib/marrowWash.ts +++ b/packages/shared/src/lib/marrowWash.ts @@ -1,5 +1,5 @@ -import type { CultivatorCondition, MarrowWashState } from '@shared/types/condition'; -import type { RealmType } from '@shared/types/constants'; +import type { CultivatorCondition, MarrowWashState } from '@daoyou/shared/types/condition'; +import type { RealmType } from '@daoyou/shared/types/constants'; export const SPIRITUAL_ROOT_EFFECTIVE_STRENGTH_CAP = 120; export const MARROW_WASH_BREAKTHROUGH_QI_COST = 20; diff --git a/src/shared/lib/numberFormat.test.ts b/packages/shared/src/lib/numberFormat.test.ts similarity index 100% rename from src/shared/lib/numberFormat.test.ts rename to packages/shared/src/lib/numberFormat.test.ts diff --git a/src/shared/lib/numberFormat.ts b/packages/shared/src/lib/numberFormat.ts similarity index 100% rename from src/shared/lib/numberFormat.ts rename to packages/shared/src/lib/numberFormat.ts diff --git a/src/shared/lib/persistentState.ts b/packages/shared/src/lib/persistentState.ts similarity index 100% rename from src/shared/lib/persistentState.ts rename to packages/shared/src/lib/persistentState.ts diff --git a/src/shared/lib/pillAppearance.ts b/packages/shared/src/lib/pillAppearance.ts similarity index 89% rename from src/shared/lib/pillAppearance.ts rename to packages/shared/src/lib/pillAppearance.ts index 7b6c49ff..ec6d5039 100644 --- a/src/shared/lib/pillAppearance.ts +++ b/packages/shared/src/lib/pillAppearance.ts @@ -1,8 +1,8 @@ -import { QUALITY_ORDER, type Quality } from '@shared/types/constants'; +import { QUALITY_ORDER, type Quality } from '@daoyou/shared/types/constants'; import type { PillAppearanceGrade, -} from '@shared/types/consumable'; -import { PILL_APPEARANCE_EFFECT_MULTIPLIER } from '@shared/config/alchemyEssenceConfig'; +} from '@daoyou/shared/types/consumable'; +import { PILL_APPEARANCE_EFFECT_MULTIPLIER } from '@daoyou/shared/config/alchemyEssenceConfig'; export interface PillAppearanceConfig { grade: PillAppearanceGrade; diff --git a/src/shared/lib/pillCultivationExp.ts b/packages/shared/src/lib/pillCultivationExp.ts similarity index 83% rename from src/shared/lib/pillCultivationExp.ts rename to packages/shared/src/lib/pillCultivationExp.ts index be65c661..06482866 100644 --- a/src/shared/lib/pillCultivationExp.ts +++ b/packages/shared/src/lib/pillCultivationExp.ts @@ -1,11 +1,11 @@ -import { PILL_EXP_BUDGET } from '@shared/config/pillExpGain'; -import { EXP_CAP_TABLE } from '@shared/config/cultivationProgress'; +import { PILL_EXP_BUDGET } from '@daoyou/shared/config/pillExpGain'; +import { EXP_CAP_TABLE } from '@daoyou/shared/config/cultivationProgress'; import { calculateCultivationExpByCap, type CultivationExpCalculationInput, -} from '@shared/lib/cultivationExpGain'; -import { QUALITY_ORDER } from '@shared/types/constants'; -import type { Quality, RealmStage, RealmType } from '@shared/types/constants'; +} from '@daoyou/shared/lib/cultivationExpGain'; +import { QUALITY_ORDER } from '@daoyou/shared/types/constants'; +import type { Quality, RealmStage, RealmType } from '@daoyou/shared/types/constants'; export interface PillCultivationExpInput { realm: RealmType; diff --git a/src/shared/lib/pillEffectScaling.test.ts b/packages/shared/src/lib/pillEffectScaling.test.ts similarity index 100% rename from src/shared/lib/pillEffectScaling.test.ts rename to packages/shared/src/lib/pillEffectScaling.test.ts diff --git a/src/shared/lib/pillEffectScaling.ts b/packages/shared/src/lib/pillEffectScaling.ts similarity index 91% rename from src/shared/lib/pillEffectScaling.ts rename to packages/shared/src/lib/pillEffectScaling.ts index 4168d8c2..654dc022 100644 --- a/src/shared/lib/pillEffectScaling.ts +++ b/packages/shared/src/lib/pillEffectScaling.ts @@ -1,11 +1,11 @@ -import { ALCHEMY_EFFECT_BASE_BY_QUALITY } from '@shared/config/alchemyEffectConfig'; -import { getPillAppearanceToxicityMultiplier } from '@shared/lib/pillAppearance'; -import type { ConditionStatusInstance } from '@shared/types/condition'; -import type { Quality } from '@shared/types/constants'; +import { ALCHEMY_EFFECT_BASE_BY_QUALITY } from '@daoyou/shared/config/alchemyEffectConfig'; +import { getPillAppearanceToxicityMultiplier } from '@daoyou/shared/lib/pillAppearance'; +import type { ConditionStatusInstance } from '@daoyou/shared/types/condition'; +import type { Quality } from '@daoyou/shared/types/constants'; import type { AddStatusOperation, PillAppearanceGrade, -} from '@shared/types/consumable'; +} from '@daoyou/shared/types/consumable'; export const BREAKTHROUGH_FOCUS_STATUS_KEY = 'breakthrough_focus' as const; export const PROTECT_MERIDIANS_STATUS_KEY = 'protect_meridians' as const; diff --git a/src/shared/lib/pillRecyclePrice.test.ts b/packages/shared/src/lib/pillRecyclePrice.test.ts similarity index 96% rename from src/shared/lib/pillRecyclePrice.test.ts rename to packages/shared/src/lib/pillRecyclePrice.test.ts index 6984f3e0..5b8bdd77 100644 --- a/src/shared/lib/pillRecyclePrice.test.ts +++ b/packages/shared/src/lib/pillRecyclePrice.test.ts @@ -1,18 +1,18 @@ import { ALCHEMY_ALLOWED_MATERIAL_TYPES, type AlchemyMaterialType, -} from '@shared/config/alchemyConfig'; +} from '@daoyou/shared/config/alchemyConfig'; import { MATERIAL_ESSENCE_BY_QUALITY, MATERIAL_ESSENCE_TYPE_MULTIPLIER, MAX_ALCHEMY_EFFECTIVE_ESSENCE_MULTIPLIER, PILL_UNIT_ESSENCE_BY_QUALITY, -} from '@shared/config/alchemyEssenceConfig'; +} from '@daoyou/shared/config/alchemyEssenceConfig'; import { BASE_PRICES, TYPE_MULTIPLIERS, -} from '@shared/engine/material/creation/config'; -import { QUALITY_VALUES, type Quality } from '@shared/types/constants'; +} from '@daoyou/shared/engine/material/creation/config'; +import { QUALITY_VALUES, type Quality } from '@daoyou/shared/types/constants'; import { describe, expect, it } from 'vitest'; import { rollAlchemyYieldProfile, diff --git a/src/shared/lib/pillRecyclePrice.ts b/packages/shared/src/lib/pillRecyclePrice.ts similarity index 87% rename from src/shared/lib/pillRecyclePrice.ts rename to packages/shared/src/lib/pillRecyclePrice.ts index 0a5c54ec..ac590fb3 100644 --- a/src/shared/lib/pillRecyclePrice.ts +++ b/packages/shared/src/lib/pillRecyclePrice.ts @@ -1,18 +1,18 @@ -import { ALCHEMY_ALLOWED_MATERIAL_TYPES } from '@shared/config/alchemyConfig'; +import { ALCHEMY_ALLOWED_MATERIAL_TYPES } from '@daoyou/shared/config/alchemyConfig'; import { MATERIAL_ESSENCE_BY_QUALITY, MATERIAL_ESSENCE_TYPE_MULTIPLIER, MAX_ALCHEMY_EFFECTIVE_ESSENCE_MULTIPLIER, PILL_APPEARANCE_EFFECT_MULTIPLIER, PILL_UNIT_ESSENCE_BY_QUALITY, -} from '@shared/config/alchemyEssenceConfig'; +} from '@daoyou/shared/config/alchemyEssenceConfig'; import { BASE_PRICES, TYPE_MULTIPLIERS, -} from '@shared/engine/material/creation/config'; -import { PILL_QUALITY_BASE_SCORE } from '@shared/lib/pillScore'; -import type { Quality } from '@shared/types/constants'; -import type { PillAppearanceGrade } from '@shared/types/consumable'; +} from '@daoyou/shared/engine/material/creation/config'; +import { PILL_QUALITY_BASE_SCORE } from '@daoyou/shared/lib/pillScore'; +import type { Quality } from '@daoyou/shared/types/constants'; +import type { PillAppearanceGrade } from '@daoyou/shared/types/consumable'; const PILL_RECYCLE_RETURN_FACTOR = 0.6; const SPIRIT_FRUIT_PILL_VALUE_FACTOR = 0.8; diff --git a/src/shared/lib/pillScore.ts b/packages/shared/src/lib/pillScore.ts similarity index 91% rename from src/shared/lib/pillScore.ts rename to packages/shared/src/lib/pillScore.ts index 8c14da49..d7cce8f5 100644 --- a/src/shared/lib/pillScore.ts +++ b/packages/shared/src/lib/pillScore.ts @@ -1,10 +1,10 @@ import { getBreakthroughFocusBonus, getProtectMeridiansReductionPercent, -} from '@shared/lib/pillEffectScaling'; -import { getCultivationBoostPercent } from '@shared/lib/cultivationBoost'; -import { formatScore } from '@shared/lib/scoreFormat'; -import type { Quality } from '@shared/types/constants'; +} from '@daoyou/shared/lib/pillEffectScaling'; +import { getCultivationBoostPercent } from '@daoyou/shared/lib/cultivationBoost'; +import { formatScore } from '@daoyou/shared/lib/scoreFormat'; +import type { Quality } from '@daoyou/shared/types/constants'; import type { AddStatusOperation, AlchemyBatchProfile, @@ -12,9 +12,9 @@ import type { PillAlchemyMeta, PillAppearanceGrade, PillSpec, -} from '@shared/types/consumable'; -import type { Consumable } from '@shared/types/cultivator'; -import { PILL_APPEARANCE_EFFECT_MULTIPLIER } from '@shared/config/alchemyEssenceConfig'; +} from '@daoyou/shared/types/consumable'; +import type { Consumable } from '@daoyou/shared/types/cultivator'; +import { PILL_APPEARANCE_EFFECT_MULTIPLIER } from '@daoyou/shared/config/alchemyEssenceConfig'; export const PILL_QUALITY_BASE_SCORE: Record = { 凡品: 58, diff --git a/src/shared/lib/pillUsageText.ts b/packages/shared/src/lib/pillUsageText.ts similarity index 93% rename from src/shared/lib/pillUsageText.ts rename to packages/shared/src/lib/pillUsageText.ts index c88e344a..b779841a 100644 --- a/src/shared/lib/pillUsageText.ts +++ b/packages/shared/src/lib/pillUsageText.ts @@ -1,9 +1,9 @@ import { CULTIVATION_PILL_USAGE_LIMITS, REALM_PILL_USAGE_LIMITS, -} from '@shared/config/consumableSystem'; -import type { RealmType } from '@shared/types/constants'; -import type { PillQuotaCategory, PillSpec } from '@shared/types/consumable'; +} from '@daoyou/shared/config/consumableSystem'; +import type { RealmType } from '@daoyou/shared/types/constants'; +import type { PillQuotaCategory, PillSpec } from '@daoyou/shared/types/consumable'; export function getRealmPillUsageLimit(realm: RealmType): number { return REALM_PILL_USAGE_LIMITS[realm]; diff --git a/src/shared/lib/qi.test.ts b/packages/shared/src/lib/qi.test.ts similarity index 99% rename from src/shared/lib/qi.test.ts rename to packages/shared/src/lib/qi.test.ts index a9ad6c09..8cf471ce 100644 --- a/src/shared/lib/qi.test.ts +++ b/packages/shared/src/lib/qi.test.ts @@ -2,7 +2,7 @@ import { QI_MAX, QI_NATURAL_RESTORE_INTERVAL_MS, QI_OVERFLOW_MAX, -} from '@shared/config/qiSystem'; +} from '@daoyou/shared/config/qiSystem'; import { projectNaturalQiState } from './qi'; const BASE_MS = Date.parse('2026-07-30T00:00:00.000Z'); diff --git a/src/shared/lib/qi.ts b/packages/shared/src/lib/qi.ts similarity index 99% rename from src/shared/lib/qi.ts rename to packages/shared/src/lib/qi.ts index 1c836efa..864acd87 100644 --- a/src/shared/lib/qi.ts +++ b/packages/shared/src/lib/qi.ts @@ -3,7 +3,7 @@ import { QI_NATURAL_RESTORE_INTERVAL_MS, QI_NATURAL_RESTORE_PER_INTERVAL, QI_OVERFLOW_MAX, -} from '@shared/config/qiSystem'; +} from '@daoyou/shared/config/qiSystem'; export type QiRecoveryStatus = 'recovering' | 'full' | 'overflow' | 'unknown'; diff --git a/src/shared/lib/retiredDraw.test.ts b/packages/shared/src/lib/retiredDraw.test.ts similarity index 100% rename from src/shared/lib/retiredDraw.test.ts rename to packages/shared/src/lib/retiredDraw.test.ts diff --git a/src/shared/lib/retiredDraw.ts b/packages/shared/src/lib/retiredDraw.ts similarity index 100% rename from src/shared/lib/retiredDraw.ts rename to packages/shared/src/lib/retiredDraw.ts diff --git a/src/shared/lib/scoreFormat.ts b/packages/shared/src/lib/scoreFormat.ts similarity index 100% rename from src/shared/lib/scoreFormat.ts rename to packages/shared/src/lib/scoreFormat.ts diff --git a/src/shared/lib/sponsorship.test.ts b/packages/shared/src/lib/sponsorship.test.ts similarity index 100% rename from src/shared/lib/sponsorship.test.ts rename to packages/shared/src/lib/sponsorship.test.ts diff --git a/src/shared/lib/sponsorship.ts b/packages/shared/src/lib/sponsorship.ts similarity index 100% rename from src/shared/lib/sponsorship.ts rename to packages/shared/src/lib/sponsorship.ts diff --git a/src/shared/lib/tower/blessing-pack.test.ts b/packages/shared/src/lib/tower/blessing-pack.test.ts similarity index 96% rename from src/shared/lib/tower/blessing-pack.test.ts rename to packages/shared/src/lib/tower/blessing-pack.test.ts index 61d5397f..9dc0b5f7 100644 --- a/src/shared/lib/tower/blessing-pack.test.ts +++ b/packages/shared/src/lib/tower/blessing-pack.test.ts @@ -1,4 +1,4 @@ -import { projectTowerPlayer } from '@shared/engine/combat-v6/tower/host'; +import { projectTowerPlayer } from '@daoyou/shared/engine/combat-v6/tower/host'; import { describe, expect, it } from 'vitest'; import { z } from 'zod'; import { diff --git a/src/shared/lib/tower/blessing-pack.ts b/packages/shared/src/lib/tower/blessing-pack.ts similarity index 96% rename from src/shared/lib/tower/blessing-pack.ts rename to packages/shared/src/lib/tower/blessing-pack.ts index d9e2d8f7..001cdbde 100644 --- a/src/shared/lib/tower/blessing-pack.ts +++ b/packages/shared/src/lib/tower/blessing-pack.ts @@ -1,4 +1,4 @@ -import { formatContentPackErrors } from '@shared/lib/content-pack-errors'; +import { formatContentPackErrors } from '@daoyou/shared/lib/content-pack-errors'; import { z } from 'zod'; import raw from './data/blessings.json'; diff --git a/src/shared/lib/tower/blessings.ts b/packages/shared/src/lib/tower/blessings.ts similarity index 100% rename from src/shared/lib/tower/blessings.ts rename to packages/shared/src/lib/tower/blessings.ts diff --git a/src/shared/lib/tower/data/README.md b/packages/shared/src/lib/tower/data/README.md similarity index 91% rename from src/shared/lib/tower/data/README.md rename to packages/shared/src/lib/tower/data/README.md index c4782c2c..7b299891 100644 --- a/src/shared/lib/tower/data/README.md +++ b/packages/shared/src/lib/tower/data/README.md @@ -1,6 +1,6 @@ # 幻境内容配置 -当前内容为 `combat-v6-tower-v8`,周策略 schema 3。存储与硬切规则见 [逐层策略存储](../../../../../docs/tower-strategy-storage.md)。 +当前内容为 `combat-v6-tower-v8`,周策略 schema 3。存储与硬切规则见 [逐层策略存储](../../../../../../docs/tower-strategy-storage.md)。 ## 祝福 @@ -10,7 +10,7 @@ ## 敌人与周编排 -生命使用固定 `damagePerRound × 类型 rounds × 楼层增长 × 阵容折价` 的整场预算,再应用成员份额与生命修正,不使用人物生命公式。参考构筑与验证边界见 [v8 血量规则](../../../../../docs/tower-health-v8.md)。 +生命使用固定 `damagePerRound × 类型 rounds × 楼层增长 × 阵容折价` 的整场预算,再应用成员份额与生命修正,不使用人物生命公式。参考构筑与验证边界见 [v8 血量规则](../../../../../../docs/tower-health-v8.md)。 `encounters.json` 是当前境界基准、楼层增长、类型系数、准入及里程碑配置。攻击增长作用于超过参照防御的差值,不读取玩家战力追涨。敌人等级通过该层配置的境界阶段计算。 @@ -26,4 +26,4 @@ 奖励配置仍为 `shared/rewards/data/tower.json`,每角色一个 SQL 本周领奖记录,最多20层 claim。硬切挑战与榜单保留 SQL 领取事实;奖励、claim、正常资源写回及战斗凭据继续共事务。 -配置需构建/重启,不支持同版本周中热更。七境界代表构筑校准与剩余体验限制见 [数值设计](../../../../../docs/tower-scaling-design.md)。 +配置需构建/重启,不支持同版本周中热更。七境界代表构筑校准与剩余体验限制见 [数值设计](../../../../../../docs/tower-scaling-design.md)。 diff --git a/src/shared/lib/tower/data/blessings.json b/packages/shared/src/lib/tower/data/blessings.json similarity index 100% rename from src/shared/lib/tower/data/blessings.json rename to packages/shared/src/lib/tower/data/blessings.json diff --git a/src/shared/lib/tower/data/blessings.schema.json b/packages/shared/src/lib/tower/data/blessings.schema.json similarity index 100% rename from src/shared/lib/tower/data/blessings.schema.json rename to packages/shared/src/lib/tower/data/blessings.schema.json diff --git a/src/shared/lib/tower/data/encounters.json b/packages/shared/src/lib/tower/data/encounters.json similarity index 100% rename from src/shared/lib/tower/data/encounters.json rename to packages/shared/src/lib/tower/data/encounters.json diff --git a/src/shared/lib/tower/data/encounters.schema.json b/packages/shared/src/lib/tower/data/encounters.schema.json similarity index 100% rename from src/shared/lib/tower/data/encounters.schema.json rename to packages/shared/src/lib/tower/data/encounters.schema.json diff --git a/src/shared/lib/tower/encounter-pack.test.ts b/packages/shared/src/lib/tower/encounter-pack.test.ts similarity index 97% rename from src/shared/lib/tower/encounter-pack.test.ts rename to packages/shared/src/lib/tower/encounter-pack.test.ts index 03a87f65..3d8a678e 100644 --- a/src/shared/lib/tower/encounter-pack.test.ts +++ b/packages/shared/src/lib/tower/encounter-pack.test.ts @@ -1,4 +1,4 @@ -import { compileTowerEncounter } from '@shared/engine/combat-v6/tower/content'; +import { compileTowerEncounter } from '@daoyou/shared/engine/combat-v6/tower/content'; import { describe, expect, it } from 'vitest'; import { z } from 'zod'; import raw from './data/encounters.json'; diff --git a/src/shared/lib/tower/encounter-pack.ts b/packages/shared/src/lib/tower/encounter-pack.ts similarity index 96% rename from src/shared/lib/tower/encounter-pack.ts rename to packages/shared/src/lib/tower/encounter-pack.ts index 6ad410b3..3092c775 100644 --- a/src/shared/lib/tower/encounter-pack.ts +++ b/packages/shared/src/lib/tower/encounter-pack.ts @@ -1,4 +1,4 @@ -import { formatContentPackErrors } from '@shared/lib/content-pack-errors'; +import { formatContentPackErrors } from '@daoyou/shared/lib/content-pack-errors'; import { z } from 'zod'; import raw from './data/encounters.json'; diff --git a/src/shared/lib/tower/formations.ts b/packages/shared/src/lib/tower/formations.ts similarity index 100% rename from src/shared/lib/tower/formations.ts rename to packages/shared/src/lib/tower/formations.ts diff --git a/src/shared/lib/tower/helpers.test.ts b/packages/shared/src/lib/tower/helpers.test.ts similarity index 100% rename from src/shared/lib/tower/helpers.test.ts rename to packages/shared/src/lib/tower/helpers.test.ts diff --git a/src/shared/lib/tower/helpers.ts b/packages/shared/src/lib/tower/helpers.ts similarity index 98% rename from src/shared/lib/tower/helpers.ts rename to packages/shared/src/lib/tower/helpers.ts index bc921241..023386a7 100644 --- a/src/shared/lib/tower/helpers.ts +++ b/packages/shared/src/lib/tower/helpers.ts @@ -4,7 +4,7 @@ import { type EnemyRace, type RealmStage, type RealmType, -} from '@shared/types/constants'; +} from '@daoyou/shared/types/constants'; import { TOWER_BLESSINGS_PACK } from './blessing-pack'; import { compileTowerBlessingDefinitions, diff --git a/src/shared/lib/tower/index.ts b/packages/shared/src/lib/tower/index.ts similarity index 100% rename from src/shared/lib/tower/index.ts rename to packages/shared/src/lib/tower/index.ts diff --git a/src/shared/lib/tower/lifecycle.test.ts b/packages/shared/src/lib/tower/lifecycle.test.ts similarity index 100% rename from src/shared/lib/tower/lifecycle.test.ts rename to packages/shared/src/lib/tower/lifecycle.test.ts diff --git a/src/shared/lib/tower/lifecycle.ts b/packages/shared/src/lib/tower/lifecycle.ts similarity index 100% rename from src/shared/lib/tower/lifecycle.ts rename to packages/shared/src/lib/tower/lifecycle.ts diff --git a/src/shared/lib/tower/presentation.ts b/packages/shared/src/lib/tower/presentation.ts similarity index 100% rename from src/shared/lib/tower/presentation.ts rename to packages/shared/src/lib/tower/presentation.ts diff --git a/src/shared/lib/tower/reward-state.test.ts b/packages/shared/src/lib/tower/reward-state.test.ts similarity index 100% rename from src/shared/lib/tower/reward-state.test.ts rename to packages/shared/src/lib/tower/reward-state.test.ts diff --git a/src/shared/lib/tower/reward-state.ts b/packages/shared/src/lib/tower/reward-state.ts similarity index 100% rename from src/shared/lib/tower/reward-state.ts rename to packages/shared/src/lib/tower/reward-state.ts diff --git a/src/shared/lib/tower/season.test.ts b/packages/shared/src/lib/tower/season.test.ts similarity index 100% rename from src/shared/lib/tower/season.test.ts rename to packages/shared/src/lib/tower/season.test.ts diff --git a/src/shared/lib/tower/season.ts b/packages/shared/src/lib/tower/season.ts similarity index 100% rename from src/shared/lib/tower/season.ts rename to packages/shared/src/lib/tower/season.ts diff --git a/src/shared/lib/tower/types.ts b/packages/shared/src/lib/tower/types.ts similarity index 88% rename from src/shared/lib/tower/types.ts rename to packages/shared/src/lib/tower/types.ts index 034ed164..c8f76020 100644 --- a/src/shared/lib/tower/types.ts +++ b/packages/shared/src/lib/tower/types.ts @@ -1,7 +1,7 @@ -import type { ResourceOperation } from '@shared/engine/resource/types'; -import type { CultivatorCondition } from '@shared/types/condition'; -import type { EnemyRace, RealmStage, RealmType } from '@shared/types/constants'; -import type { Cultivator } from '@shared/types/cultivator'; +import type { ResourceOperation } from '@daoyou/shared/engine/resource/types'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; +import type { EnemyRace, RealmStage, RealmType } from '@daoyou/shared/types/constants'; +import type { Cultivator } from '@daoyou/shared/types/cultivator'; import type { TowerBlessingId } from './blessings'; export type TowerRunStatus = diff --git a/src/shared/lib/tower/weekly.ts b/packages/shared/src/lib/tower/weekly.ts similarity index 100% rename from src/shared/lib/tower/weekly.ts rename to packages/shared/src/lib/tower/weekly.ts diff --git a/src/shared/lib/trackConfigRegistry.test.ts b/packages/shared/src/lib/trackConfigRegistry.test.ts similarity index 100% rename from src/shared/lib/trackConfigRegistry.test.ts rename to packages/shared/src/lib/trackConfigRegistry.test.ts diff --git a/src/shared/lib/trackConfigRegistry.ts b/packages/shared/src/lib/trackConfigRegistry.ts similarity index 94% rename from src/shared/lib/trackConfigRegistry.ts rename to packages/shared/src/lib/trackConfigRegistry.ts index d5b8f6de..7cbe3f3d 100644 --- a/src/shared/lib/trackConfigRegistry.ts +++ b/packages/shared/src/lib/trackConfigRegistry.ts @@ -1,5 +1,5 @@ -import type { Attributes } from '@shared/types/cultivator'; -import type { ConditionTrackPath } from '@shared/types/condition'; +import type { Attributes } from '@daoyou/shared/types/cultivator'; +import type { ConditionTrackPath } from '@daoyou/shared/types/condition'; import { BODY_CULTIVATION_TRACK_KEYS, BODY_TRACK_LABELS, diff --git a/src/shared/lib/utils.ts b/packages/shared/src/lib/utils.ts similarity index 100% rename from src/shared/lib/utils.ts rename to packages/shared/src/lib/utils.ts diff --git a/src/shared/manual-migration/rules.test.ts b/packages/shared/src/manual-migration/rules.test.ts similarity index 100% rename from src/shared/manual-migration/rules.test.ts rename to packages/shared/src/manual-migration/rules.test.ts diff --git a/src/shared/manual-migration/rules.ts b/packages/shared/src/manual-migration/rules.ts similarity index 100% rename from src/shared/manual-migration/rules.ts rename to packages/shared/src/manual-migration/rules.ts diff --git a/src/shared/manuals/action.test.ts b/packages/shared/src/manuals/action.test.ts similarity index 100% rename from src/shared/manuals/action.test.ts rename to packages/shared/src/manuals/action.test.ts diff --git a/src/shared/manuals/action.ts b/packages/shared/src/manuals/action.ts similarity index 100% rename from src/shared/manuals/action.ts rename to packages/shared/src/manuals/action.ts diff --git a/src/shared/manuals/enlightenment.test.ts b/packages/shared/src/manuals/enlightenment.test.ts similarity index 100% rename from src/shared/manuals/enlightenment.test.ts rename to packages/shared/src/manuals/enlightenment.test.ts diff --git a/src/shared/manuals/enlightenment.ts b/packages/shared/src/manuals/enlightenment.ts similarity index 100% rename from src/shared/manuals/enlightenment.ts rename to packages/shared/src/manuals/enlightenment.ts diff --git a/src/shared/performance/catalog.ts b/packages/shared/src/performance/catalog.ts similarity index 100% rename from src/shared/performance/catalog.ts rename to packages/shared/src/performance/catalog.ts diff --git a/src/shared/performance/interpreter.test.ts b/packages/shared/src/performance/interpreter.test.ts similarity index 100% rename from src/shared/performance/interpreter.test.ts rename to packages/shared/src/performance/interpreter.test.ts diff --git a/src/shared/performance/interpreter.ts b/packages/shared/src/performance/interpreter.ts similarity index 100% rename from src/shared/performance/interpreter.ts rename to packages/shared/src/performance/interpreter.ts diff --git a/src/shared/performance/schema.ts b/packages/shared/src/performance/schema.ts similarity index 100% rename from src/shared/performance/schema.ts rename to packages/shared/src/performance/schema.ts diff --git a/src/shared/playerJournal.test.ts b/packages/shared/src/playerJournal.test.ts similarity index 100% rename from src/shared/playerJournal.test.ts rename to packages/shared/src/playerJournal.test.ts diff --git a/src/shared/playerJournal.ts b/packages/shared/src/playerJournal.ts similarity index 100% rename from src/shared/playerJournal.ts rename to packages/shared/src/playerJournal.ts diff --git a/src/shared/rewards/data/README.md b/packages/shared/src/rewards/data/README.md similarity index 100% rename from src/shared/rewards/data/README.md rename to packages/shared/src/rewards/data/README.md diff --git a/src/shared/rewards/data/dungeon.json b/packages/shared/src/rewards/data/dungeon.json similarity index 100% rename from src/shared/rewards/data/dungeon.json rename to packages/shared/src/rewards/data/dungeon.json diff --git a/src/shared/rewards/data/dungeon.schema.json b/packages/shared/src/rewards/data/dungeon.schema.json similarity index 100% rename from src/shared/rewards/data/dungeon.schema.json rename to packages/shared/src/rewards/data/dungeon.schema.json diff --git a/src/shared/rewards/data/hunt.json b/packages/shared/src/rewards/data/hunt.json similarity index 100% rename from src/shared/rewards/data/hunt.json rename to packages/shared/src/rewards/data/hunt.json diff --git a/src/shared/rewards/data/story.json b/packages/shared/src/rewards/data/story.json similarity index 100% rename from src/shared/rewards/data/story.json rename to packages/shared/src/rewards/data/story.json diff --git a/src/shared/rewards/data/tower.json b/packages/shared/src/rewards/data/tower.json similarity index 100% rename from src/shared/rewards/data/tower.json rename to packages/shared/src/rewards/data/tower.json diff --git a/src/shared/rewards/data/tower.schema.json b/packages/shared/src/rewards/data/tower.schema.json similarity index 100% rename from src/shared/rewards/data/tower.schema.json rename to packages/shared/src/rewards/data/tower.schema.json diff --git a/src/shared/rewards/data/wild.json b/packages/shared/src/rewards/data/wild.json similarity index 100% rename from src/shared/rewards/data/wild.json rename to packages/shared/src/rewards/data/wild.json diff --git a/src/shared/rewards/data/wild.schema.json b/packages/shared/src/rewards/data/wild.schema.json similarity index 100% rename from src/shared/rewards/data/wild.schema.json rename to packages/shared/src/rewards/data/wild.schema.json diff --git a/src/shared/rewards/data/yield.json b/packages/shared/src/rewards/data/yield.json similarity index 100% rename from src/shared/rewards/data/yield.json rename to packages/shared/src/rewards/data/yield.json diff --git a/src/shared/rewards/dungeon-material-quality.test.ts b/packages/shared/src/rewards/dungeon-material-quality.test.ts similarity index 100% rename from src/shared/rewards/dungeon-material-quality.test.ts rename to packages/shared/src/rewards/dungeon-material-quality.test.ts diff --git a/src/shared/rewards/dungeon-material-quality.ts b/packages/shared/src/rewards/dungeon-material-quality.ts similarity index 100% rename from src/shared/rewards/dungeon-material-quality.ts rename to packages/shared/src/rewards/dungeon-material-quality.ts diff --git a/src/shared/rewards/dungeon-pack.test.ts b/packages/shared/src/rewards/dungeon-pack.test.ts similarity index 100% rename from src/shared/rewards/dungeon-pack.test.ts rename to packages/shared/src/rewards/dungeon-pack.test.ts diff --git a/src/shared/rewards/dungeon-pack.ts b/packages/shared/src/rewards/dungeon-pack.ts similarity index 97% rename from src/shared/rewards/dungeon-pack.ts rename to packages/shared/src/rewards/dungeon-pack.ts index 69610a4a..f2cfde07 100644 --- a/src/shared/rewards/dungeon-pack.ts +++ b/packages/shared/src/rewards/dungeon-pack.ts @@ -1,4 +1,4 @@ -import { formatContentPackErrors } from '@shared/lib/content-pack-errors'; +import { formatContentPackErrors } from '@daoyou/shared/lib/content-pack-errors'; import { z } from 'zod'; import { BEAST_SUPERIOR_BOOK_SKILL_IDS } from '../engine/combat-v6/beasts/content'; import { findItemDefinition } from '../items/registry'; diff --git a/src/shared/rewards/dungeon.test.ts b/packages/shared/src/rewards/dungeon.test.ts similarity index 100% rename from src/shared/rewards/dungeon.test.ts rename to packages/shared/src/rewards/dungeon.test.ts diff --git a/src/shared/rewards/dungeon.ts b/packages/shared/src/rewards/dungeon.ts similarity index 100% rename from src/shared/rewards/dungeon.ts rename to packages/shared/src/rewards/dungeon.ts diff --git a/src/shared/rewards/hunt-material-quality.test.ts b/packages/shared/src/rewards/hunt-material-quality.test.ts similarity index 100% rename from src/shared/rewards/hunt-material-quality.test.ts rename to packages/shared/src/rewards/hunt-material-quality.test.ts diff --git a/src/shared/rewards/hunt-material-quality.ts b/packages/shared/src/rewards/hunt-material-quality.ts similarity index 100% rename from src/shared/rewards/hunt-material-quality.ts rename to packages/shared/src/rewards/hunt-material-quality.ts diff --git a/src/shared/rewards/hunt.test.ts b/packages/shared/src/rewards/hunt.test.ts similarity index 100% rename from src/shared/rewards/hunt.test.ts rename to packages/shared/src/rewards/hunt.test.ts diff --git a/src/shared/rewards/hunt.ts b/packages/shared/src/rewards/hunt.ts similarity index 100% rename from src/shared/rewards/hunt.ts rename to packages/shared/src/rewards/hunt.ts diff --git a/src/shared/rewards/story-pack.ts b/packages/shared/src/rewards/story-pack.ts similarity index 100% rename from src/shared/rewards/story-pack.ts rename to packages/shared/src/rewards/story-pack.ts diff --git a/src/shared/rewards/tower-pack.test.ts b/packages/shared/src/rewards/tower-pack.test.ts similarity index 100% rename from src/shared/rewards/tower-pack.test.ts rename to packages/shared/src/rewards/tower-pack.test.ts diff --git a/src/shared/rewards/tower-pack.ts b/packages/shared/src/rewards/tower-pack.ts similarity index 97% rename from src/shared/rewards/tower-pack.ts rename to packages/shared/src/rewards/tower-pack.ts index 9eccd6ef..dc357a0b 100644 --- a/src/shared/rewards/tower-pack.ts +++ b/packages/shared/src/rewards/tower-pack.ts @@ -1,4 +1,4 @@ -import { formatContentPackErrors } from '@shared/lib/content-pack-errors'; +import { formatContentPackErrors } from '@daoyou/shared/lib/content-pack-errors'; import { z } from 'zod'; import { equipmentRealm, diff --git a/src/shared/rewards/tower.ts b/packages/shared/src/rewards/tower.ts similarity index 100% rename from src/shared/rewards/tower.ts rename to packages/shared/src/rewards/tower.ts diff --git a/src/shared/rewards/wild-pack.test.ts b/packages/shared/src/rewards/wild-pack.test.ts similarity index 100% rename from src/shared/rewards/wild-pack.test.ts rename to packages/shared/src/rewards/wild-pack.test.ts diff --git a/src/shared/rewards/wild-pack.ts b/packages/shared/src/rewards/wild-pack.ts similarity index 100% rename from src/shared/rewards/wild-pack.ts rename to packages/shared/src/rewards/wild-pack.ts diff --git a/src/shared/rewards/wild.test.ts b/packages/shared/src/rewards/wild.test.ts similarity index 100% rename from src/shared/rewards/wild.test.ts rename to packages/shared/src/rewards/wild.test.ts diff --git a/src/shared/rewards/wild.ts b/packages/shared/src/rewards/wild.ts similarity index 100% rename from src/shared/rewards/wild.ts rename to packages/shared/src/rewards/wild.ts diff --git a/src/shared/rewards/yield.test.ts b/packages/shared/src/rewards/yield.test.ts similarity index 100% rename from src/shared/rewards/yield.test.ts rename to packages/shared/src/rewards/yield.test.ts diff --git a/src/shared/rewards/yield.ts b/packages/shared/src/rewards/yield.ts similarity index 100% rename from src/shared/rewards/yield.ts rename to packages/shared/src/rewards/yield.ts diff --git a/src/shared/story/catalog.ts b/packages/shared/src/story/catalog.ts similarity index 100% rename from src/shared/story/catalog.ts rename to packages/shared/src/story/catalog.ts diff --git a/src/shared/story/grants.test.ts b/packages/shared/src/story/grants.test.ts similarity index 100% rename from src/shared/story/grants.test.ts rename to packages/shared/src/story/grants.test.ts diff --git a/src/shared/story/grants.ts b/packages/shared/src/story/grants.ts similarity index 100% rename from src/shared/story/grants.ts rename to packages/shared/src/story/grants.ts diff --git a/src/shared/story/resolve.test.ts b/packages/shared/src/story/resolve.test.ts similarity index 100% rename from src/shared/story/resolve.test.ts rename to packages/shared/src/story/resolve.test.ts diff --git a/src/shared/story/resolve.ts b/packages/shared/src/story/resolve.ts similarity index 100% rename from src/shared/story/resolve.ts rename to packages/shared/src/story/resolve.ts diff --git a/src/shared/story/schema.ts b/packages/shared/src/story/schema.ts similarity index 100% rename from src/shared/story/schema.ts rename to packages/shared/src/story/schema.ts diff --git a/src/shared/story/signals.test.ts b/packages/shared/src/story/signals.test.ts similarity index 100% rename from src/shared/story/signals.test.ts rename to packages/shared/src/story/signals.test.ts diff --git a/src/shared/story/signals.ts b/packages/shared/src/story/signals.ts similarity index 100% rename from src/shared/story/signals.ts rename to packages/shared/src/story/signals.ts diff --git a/src/shared/text-filter/index.test.ts b/packages/shared/src/text-filter/index.test.ts similarity index 100% rename from src/shared/text-filter/index.test.ts rename to packages/shared/src/text-filter/index.test.ts diff --git a/src/shared/text-filter/index.ts b/packages/shared/src/text-filter/index.ts similarity index 100% rename from src/shared/text-filter/index.ts rename to packages/shared/src/text-filter/index.ts diff --git a/src/shared/types/battle.ts b/packages/shared/src/types/battle.ts similarity index 100% rename from src/shared/types/battle.ts rename to packages/shared/src/types/battle.ts diff --git a/src/shared/types/blackMarket.ts b/packages/shared/src/types/blackMarket.ts similarity index 100% rename from src/shared/types/blackMarket.ts rename to packages/shared/src/types/blackMarket.ts diff --git a/src/shared/types/body-json-override.d.ts b/packages/shared/src/types/body-json-override.d.ts similarity index 100% rename from src/shared/types/body-json-override.d.ts rename to packages/shared/src/types/body-json-override.d.ts diff --git a/src/shared/types/condition.ts b/packages/shared/src/types/condition.ts similarity index 100% rename from src/shared/types/condition.ts rename to packages/shared/src/types/condition.ts diff --git a/src/shared/types/constants.ts b/packages/shared/src/types/constants.ts similarity index 100% rename from src/shared/types/constants.ts rename to packages/shared/src/types/constants.ts diff --git a/src/shared/types/consumable.ts b/packages/shared/src/types/consumable.ts similarity index 100% rename from src/shared/types/consumable.ts rename to packages/shared/src/types/consumable.ts diff --git a/src/shared/types/cultivator.ts b/packages/shared/src/types/cultivator.ts similarity index 96% rename from src/shared/types/cultivator.ts rename to packages/shared/src/types/cultivator.ts index c2a0c254..609bd7f3 100644 --- a/src/shared/types/cultivator.ts +++ b/packages/shared/src/types/cultivator.ts @@ -1,12 +1,12 @@ // ===== 新一代修仙底层数据模型 ===== -import type { CultivatorSectState, PlayerRaceId } from '@shared/engine/sect'; +import type { CultivatorSectState, PlayerRaceId } from '@daoyou/shared/engine/sect'; import type { LegacyAbilitySnapshot as AbilityConfig, LegacyAttributeModifier as AttributeModifierConfig, -} from '@shared/legacy/products'; -import type { CultivatorCondition } from '@shared/types/condition'; -import type { ConsumableSpec } from '@shared/types/consumable'; +} from '@daoyou/shared/legacy/products'; +import type { CultivatorCondition } from '@daoyou/shared/types/condition'; +import type { ConsumableSpec } from '@daoyou/shared/types/consumable'; import type { ConsumableType, ElementType, diff --git a/src/shared/types/dictionaries.ts b/packages/shared/src/types/dictionaries.ts similarity index 100% rename from src/shared/types/dictionaries.ts rename to packages/shared/src/types/dictionaries.ts diff --git a/src/shared/types/fateReshape.ts b/packages/shared/src/types/fateReshape.ts similarity index 100% rename from src/shared/types/fateReshape.ts rename to packages/shared/src/types/fateReshape.ts diff --git a/src/shared/types/identityReshape.ts b/packages/shared/src/types/identityReshape.ts similarity index 100% rename from src/shared/types/identityReshape.ts rename to packages/shared/src/types/identityReshape.ts diff --git a/src/shared/types/mail.ts b/packages/shared/src/types/mail.ts similarity index 86% rename from src/shared/types/mail.ts rename to packages/shared/src/types/mail.ts index 93f16812..6be88c6b 100644 --- a/src/shared/types/mail.ts +++ b/packages/shared/src/types/mail.ts @@ -1,4 +1,4 @@ -import type { Artifact, Consumable, Material } from '@shared/types/cultivator'; +import type { Artifact, Consumable, Material } from '@daoyou/shared/types/cultivator'; import type { BeastTradePreview, BeastTransfer } from '../contracts/beastTrade'; import type { ItemGrant } from '../inventory'; diff --git a/src/shared/types/market.ts b/packages/shared/src/types/market.ts similarity index 100% rename from src/shared/types/market.ts rename to packages/shared/src/types/market.ts diff --git a/src/shared/types/narrative.ts b/packages/shared/src/types/narrative.ts similarity index 100% rename from src/shared/types/narrative.ts rename to packages/shared/src/types/narrative.ts diff --git a/src/shared/types/qi.ts b/packages/shared/src/types/qi.ts similarity index 93% rename from src/shared/types/qi.ts rename to packages/shared/src/types/qi.ts index f7d5b47e..04c7d365 100644 --- a/src/shared/types/qi.ts +++ b/packages/shared/src/types/qi.ts @@ -1,4 +1,4 @@ -import type { QiAction } from '@shared/config/qiSystem'; +import type { QiAction } from '@daoyou/shared/config/qiSystem'; export const QI_LOG_STATUS_VALUES = [ 'reserved', diff --git a/src/shared/types/rankings.ts b/packages/shared/src/types/rankings.ts similarity index 100% rename from src/shared/types/rankings.ts rename to packages/shared/src/types/rankings.ts diff --git a/src/shared/types/task.ts b/packages/shared/src/types/task.ts similarity index 97% rename from src/shared/types/task.ts rename to packages/shared/src/types/task.ts index a7027433..4cdaf0e7 100644 --- a/src/shared/types/task.ts +++ b/packages/shared/src/types/task.ts @@ -1,7 +1,7 @@ import type { ConditionStatusKey } from './condition'; import type { RealmType } from './constants'; import type { MailAttachment } from './mail'; -import type { DailyTaskDifficulty } from '@shared/engine/cultivation/exp-gain-strategies/types'; +import type { DailyTaskDifficulty } from '@daoyou/shared/engine/cultivation/exp-gain-strategies/types'; export type TaskCategory = 'breakthrough_major' | 'daily' | 'tutorial'; diff --git a/src/shared/types/world-chat.ts b/packages/shared/src/types/world-chat.ts similarity index 100% rename from src/shared/types/world-chat.ts rename to packages/shared/src/types/world-chat.ts diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json new file mode 100644 index 00000000..b40dcf7b --- /dev/null +++ b/packages/shared/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "noEmit": true, + "types": [], + "paths": { + "@daoyou/shared/*": ["./src/*"] + }, + "lib": ["ES2023", "DOM", "DOM.Iterable"] + }, + "include": ["src"], + "exclude": ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts"] +} diff --git a/packages/shared/tsconfig.test.json b/packages/shared/tsconfig.test.json new file mode 100644 index 00000000..0c26e540 --- /dev/null +++ b/packages/shared/tsconfig.test.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "types": ["node", "vitest/globals"] + }, + "exclude": [] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 00000000..2d591492 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,7626 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: false + excludeLinksFromLockfile: false + injectWorkspacePackages: true + +importers: + + .: + devDependencies: + '@eslint/js': + specifier: 10.0.1 + version: 10.0.1(eslint@10.10.0(jiti@2.7.0)) + '@types/d3-format': + specifier: ^3.0.4 + version: 3.0.4 + '@types/node': + specifier: ^26.5.1 + version: 26.5.1 + drizzle-kit: + specifier: ^0.31.10 + version: 0.31.10 + eslint: + specifier: 10.10.0 + version: 10.10.0(jiti@2.7.0) + eslint-plugin-react-hooks: + specifier: 7.1.1 + version: 7.1.1(eslint@10.10.0(jiti@2.7.0)) + eslint-plugin-react-refresh: + specifier: 0.5.6 + version: 0.5.6(eslint@10.10.0(jiti@2.7.0)) + globals: + specifier: 17.12.0 + version: 17.12.0 + prettier: + specifier: ^3.9.6 + version: 3.9.6 + prettier-plugin-organize-imports: + specifier: ^4.3.0 + version: 4.3.0(prettier@3.9.6)(typescript@6.0.3) + prettier-plugin-packagejson: + specifier: ^3.0.2 + version: 3.0.2(prettier@3.9.6) + prettier-plugin-tailwindcss: + specifier: ^0.8.1 + version: 0.8.1(prettier-plugin-organize-imports@4.3.0(prettier@3.9.6)(typescript@6.0.3))(prettier@3.9.6) + sponsorkit: + specifier: ^18.0.0 + version: 18.0.0(@types/node@26.5.1) + tsx: + specifier: ^4.21.0 + version: 4.21.0 + turbo: + specifier: 2.11.6 + version: 2.11.6 + typescript: + specifier: ^6.0.3 + version: 6.0.3 + typescript-eslint: + specifier: 8.70.0 + version: 8.70.0(eslint@10.10.0(jiti@2.7.0))(typescript@6.0.3) + vitest: + specifier: ^4.1.11 + version: 4.1.11(@types/node@26.5.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.51.2)(tsx@4.21.0) + + apps/api: + dependencies: + '@ai-sdk/alibaba': + specifier: ^2.0.45 + version: 2.0.45(zod@4.6.4) + '@ai-sdk/deepseek': + specifier: ^3.0.44 + version: 3.0.44(zod@4.6.4) + '@better-auth/drizzle-adapter': + specifier: ^1.7.4 + version: 1.7.4(@better-auth/core@1.7.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.4.0(zod@4.6.4))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.2)(drizzle-orm@0.45.2(@types/pg@8.23.1)(kysely@0.28.17)(pg@8.23.0)) + '@better-auth/i18n': + specifier: ^1.7.4 + version: 1.7.4(@better-auth/core@1.7.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.4.0(zod@4.6.4))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(better-auth@1.7.4(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@types/pg@8.23.1)(kysely@0.28.17)(pg@8.23.0))(pg@8.23.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vitest@4.1.11(@types/node@26.5.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.51.2)(tsx@4.21.0))) + '@daoyou/shared': + specifier: workspace:* + version: link:../../packages/shared + '@microfleet/ioredis-lock': + specifier: ^6.0.1 + version: 6.0.1(ioredis@5.11.1) + '@nestjs/common': + specifier: ^12.1.2 + version: 12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/config': + specifier: ^12.0.1 + version: 12.0.1(@nestjs/common@12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2) + '@nestjs/core': + specifier: ^12.1.2 + version: 12.1.2(@nestjs/common@12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@12.1.2)(@nestjs/websockets@12.1.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/platform-express': + specifier: ^12.1.2 + version: 12.1.2(@nestjs/common@12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@12.1.2) + '@nestjs/platform-ws': + specifier: ^12.1.2 + version: 12.1.2(@nestjs/common@12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@12.1.2)(rxjs@7.8.2) + '@nestjs/schedule': + specifier: ^12.0.2 + version: 12.0.2(@nestjs/common@12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@12.1.2) + '@nestjs/websockets': + specifier: ^12.1.2 + version: 12.1.2(@nestjs/common@12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@12.1.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + ai: + specifier: ^7.0.99 + version: 7.0.99(zod@4.6.4) + altcha-lib: + specifier: ^2.5.0 + version: 2.5.0(@nestjs/common@12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@12.1.2)(@nestjs/platform-express@12.1.2)(cors@2.8.6)(express@5.2.1) + better-auth: + specifier: ^1.7.4 + version: 1.7.4(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@types/pg@8.23.1)(kysely@0.28.17)(pg@8.23.0))(pg@8.23.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vitest@4.1.11(@types/node@26.5.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.51.2)(tsx@4.21.0)) + cors: + specifier: ^2.8.6 + version: 2.8.6 + cron: + specifier: ^4.4.0 + version: 4.4.0 + drizzle-orm: + specifier: ^0.45.2 + version: 0.45.2(@types/pg@8.23.1)(kysely@0.28.17)(pg@8.23.0) + express: + specifier: ^5.2.1 + version: 5.2.1 + ioredis: + specifier: ^5.11.1 + version: 5.11.1 + nats: + specifier: ^2.29.3 + version: 2.29.3 + nodemailer: + specifier: ^9.1.1 + version: 9.1.1 + pg: + specifier: ^8.23.0 + version: 8.23.0 + raw-body: + specifier: ^3.0.2 + version: 3.0.2 + reflect-metadata: + specifier: ^0.2.2 + version: 0.2.2 + rxjs: + specifier: ^7.8.2 + version: 7.8.2 + ws: + specifier: ^8.22.0 + version: 8.22.0 + zod: + specifier: ^4.6.4 + version: 4.6.4 + devDependencies: + '@nestjs/cli': + specifier: ^12.0.8 + version: 12.0.8(@rspack/core@1.7.12)(@types/node@26.5.1)(fork-ts-checker-webpack-plugin@9.1.0(typescript@6.0.3)(webpack@5.111.1(esbuild@0.27.7)))(prettier@3.9.6)(tsconfig-paths-webpack-plugin@4.2.0)(webpack-node-externals@3.0.0)(webpack@5.111.1(esbuild@0.27.7)) + '@rspack/core': + specifier: ^1.5.8 + version: 1.7.12 + '@types/cors': + specifier: ^2.8.19 + version: 2.8.19 + '@types/express': + specifier: ^5.0.6 + version: 5.0.6 + '@types/nodemailer': + specifier: ^8.0.1 + version: 8.0.1 + '@types/pg': + specifier: ^8.23.1 + version: 8.23.1 + '@types/ws': + specifier: ^8.18.2 + version: 8.18.2 + fork-ts-checker-webpack-plugin: + specifier: ^9.1.0 + version: 9.1.0(typescript@6.0.3)(webpack@5.111.1(esbuild@0.27.7)) + tsconfig-paths-webpack-plugin: + specifier: ^4.2.0 + version: 4.2.0 + webpack: + specifier: ^5.105.4 + version: 5.111.1(esbuild@0.27.7) + webpack-node-externals: + specifier: ^3.0.0 + version: 3.0.0 + + apps/web: + dependencies: + '@better-auth/i18n': + specifier: ^1.7.4 + version: 1.7.4(@better-auth/core@1.7.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.4.0(zod@4.6.4))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(better-auth@1.7.4(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@types/pg@8.23.1)(kysely@0.28.17)(pg@8.23.0))(pg@8.23.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vitest@4.1.11(@types/node@26.5.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.51.2)(tsx@4.21.0))) + '@better-fetch/fetch': + specifier: ^1.3.1 + version: 1.3.1 + '@daoyou/shared': + specifier: workspace:* + version: link:../../packages/shared + altcha: + specifier: ^3.2.2 + version: 3.2.2 + better-auth: + specifier: ^1.7.4 + version: 1.7.4(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@types/pg@8.23.1)(kysely@0.28.17)(pg@8.23.0))(pg@8.23.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vitest@4.1.11(@types/node@26.5.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.51.2)(tsx@4.21.0)) + canvas-confetti: + specifier: ^1.9.4 + version: 1.9.4 + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + d3-format: + specifier: ^3.1.2 + version: 3.1.2 + phaser: + specifier: ^4.2.1 + version: 4.2.1 + phaser4-rex-plugins: + specifier: ^4.2.0 + version: 4.2.0(graphology-types@0.24.8) + react: + specifier: 19.3.0 + version: 19.3.0 + react-dom: + specifier: 19.3.0 + version: 19.3.0(react@19.3.0) + react-router: + specifier: ^8.3.1 + version: 8.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + react-zoom-pan-pinch: + specifier: ^4.2.0 + version: 4.2.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + zod: + specifier: ^4.6.4 + version: 4.6.4 + devDependencies: + '@tailwindcss/vite': + specifier: ^4.3.3 + version: 4.3.3(vite@8.3.0(@types/node@26.5.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.51.2)(tsx@4.21.0)) + '@types/canvas-confetti': + specifier: ^1.9.0 + version: 1.9.0 + '@types/react': + specifier: 19.3.0 + version: 19.3.0 + '@types/react-dom': + specifier: 19.3.0 + version: 19.3.0(@types/react@19.3.0) + '@vitejs/plugin-react': + specifier: 6.1.1 + version: 6.1.1(vite@8.3.0(@types/node@26.5.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.51.2)(tsx@4.21.0)) + graphology-types: + specifier: ^0.24.8 + version: 0.24.8 + tailwindcss: + specifier: ^4.3.3 + version: 4.3.3 + vite: + specifier: ^8.3.0 + version: 8.3.0(@types/node@26.5.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.51.2)(tsx@4.21.0) + + packages/shared: + dependencies: + clsx: + specifier: ^2.1.1 + version: 2.1.1 + d3-format: + specifier: ^3.1.2 + version: 3.1.2 + tailwind-merge: + specifier: ^3.7.0 + version: 3.7.0 + zod: + specifier: ^4.6.4 + version: 4.6.4 + +packages: + + '@ai-sdk/alibaba@2.0.45': + resolution: {integrity: sha512-vMYkwnqywhh3hBMSLp+U+WcyO631inuhMSzpwrrIYBqGUaSdNVrOaHPPCGjxwvPsPWKNd4gKC0EdOfHcIfDjmA==} + engines: {node: '>=22'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/deepseek@3.0.44': + resolution: {integrity: sha512-yhHGVJFpM5tdhhr33lE6Co8ziu8dNZxk7j5r3L3Pc04NMCTDmnXZ072FY4tqRDsRtIfBEgWqm/zPXNRvdHb44w==} + engines: {node: '>=22'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/gateway@4.0.80': + resolution: {integrity: sha512-6t+07o8lSthpKf64Xb1qHWR2bWvJ3Fd2oFvS9fQc45p31bi2OUqan246e/ojAmZpWCiMPjKyQ4TBr4MYytnTiQ==} + engines: {node: '>=22'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider-utils@5.0.40': + resolution: {integrity: sha512-zsXPwSAQ9mRJ2hvyITaLOYUyuGrBmzFhJXOg4mllGmla1PfNxZcm4GwqMiV2xQaDgcgBMdUGxXkp9xekZZNIkg==} + engines: {node: '>=22'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider@4.0.14': + resolution: {integrity: sha512-yukP2tbcQQErG5gLCMBGvpvb/rM3D3KlTKG6eKKOdNHLHqtNNDEeBxdYrY/JL+O76B2ig5dXY19H/f1HFSvRiQ==} + engines: {node: '>=22'} + + '@angular-devkit/core@22.2.0': + resolution: {integrity: sha512-dELBwXtJWUxFALdEVvdk8qK+XBa17EH5H9NZm+041/esjNi6SaZMusqBgr2PZKq7hD+O2jSCEHbey/hwkz2Awg==} + engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + peerDependencies: + chokidar: ^5.0.0 + peerDependenciesMeta: + chokidar: + optional: true + + '@angular-devkit/schematics-cli@22.2.0': + resolution: {integrity: sha512-BGKAyxEpbZYKtCPwCMv8B9TPYb8qKHUZ2BRi61cfbMGcsrKPU4nHCPlz7Z84yDENuuQERZJh12p95mlkMC69yw==} + engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + hasBin: true + + '@angular-devkit/schematics@22.2.0': + resolution: {integrity: sha512-yhp/LRzKHJOzo2gFm9+H5DKnsT9adi+ItO2pW0pW2B455sqZYE8RJaSku+TSlv+PvWSmbtFsPXNgSRLA6pSk8Q==} + engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.8': + resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.8': + resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.8': + resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} + engines: {node: '>=6.9.0'} + + '@better-auth/core@1.7.4': + resolution: {integrity: sha512-g66dKOMB5PLxoujkxkZeYb6dh5wLhfAx/D+w9XtcO669Dslnyen2naBYqnD1r5ZwnxFKcr6aSrUPaH952V89Lg==} + peerDependencies: + '@better-auth/utils': 0.4.2 + '@better-fetch/fetch': 1.3.1 + '@cloudflare/workers-types': '>=4' + '@opentelemetry/api': ^1.9.0 + better-call: 1.4.0 + jose: ^6.1.0 + kysely: ^0.28.5 || ^0.29.0 + nanostores: ^1.0.1 + peerDependenciesMeta: + '@cloudflare/workers-types': + optional: true + '@opentelemetry/api': + optional: true + + '@better-auth/drizzle-adapter@1.7.4': + resolution: {integrity: sha512-J9JMl0DGYNYwtDj5nq/fzycU9dzPUDEtTNn89KmToUrwPDGfByDgfmiQ9jGK+LN3EnfsG1Pt/dDmRocbnfihlA==} + peerDependencies: + '@better-auth/core': ^1.7.4 + '@better-auth/utils': 0.4.2 + drizzle-orm: ^0.45.2 || >=1.0.0-rc.1 <2.0.0 + peerDependenciesMeta: + drizzle-orm: + optional: true + + '@better-auth/i18n@1.7.4': + resolution: {integrity: sha512-939oFPv6hgR1Bhnr3v3KqH1VsA+KUyY9BAHg0ExsGVHj0Go+lKcrIA1Kl7swFQQCxDaPT+GNA1KicNfMZ4Z0/Q==} + peerDependencies: + '@better-auth/core': ^1.7.4 + better-auth: ^1.7.4 + + '@better-auth/kysely-adapter@1.7.4': + resolution: {integrity: sha512-WHLsTs4ZwTKorifdK6Whol6Zy+0gIzOJEXzWsvzmLtlT1uIZFpd7IPBlDZTrXvIZnxPWJHPIv8/uHqS9QFPZOA==} + peerDependencies: + '@better-auth/core': ^1.7.4 + '@better-auth/utils': 0.4.2 + kysely: ^0.28.17 || ^0.29.0 + peerDependenciesMeta: + kysely: + optional: true + + '@better-auth/memory-adapter@1.7.4': + resolution: {integrity: sha512-xAihOPh4mFjvKLzmu2XNzj7RchDmytA/d/xjrGyps1oHVGCTGDGghlSt/rIT5fagtTn2R9+TBLh5QrZnTvYu9g==} + peerDependencies: + '@better-auth/core': ^1.7.4 + '@better-auth/utils': 0.4.2 + + '@better-auth/mongo-adapter@1.7.4': + resolution: {integrity: sha512-sIdIc8vONXgQxKPfzL4bbk6gO3gdFEDzC5VG8rD18xSFB6j7blBKcuzOofbswbwwbyuspnshoViGRqSoqp2PnA==} + peerDependencies: + '@better-auth/core': ^1.7.4 + '@better-auth/utils': 0.4.2 + mongodb: ^6.0.0 || ^7.0.0 + peerDependenciesMeta: + mongodb: + optional: true + + '@better-auth/prisma-adapter@1.7.4': + resolution: {integrity: sha512-cvsMNaXLcwQiAIxQCx5Gq2VA1reW4RjkC5oLNXNJR4IhgeiqcvYF6qKF6UEC0vV2x3euKz5V/SDY/hblHWwx+w==} + peerDependencies: + '@better-auth/core': ^1.7.4 + '@better-auth/utils': 0.4.2 + '@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0 + prisma: ^5.0.0 || ^6.0.0 || ^7.0.0 + peerDependenciesMeta: + '@prisma/client': + optional: true + prisma: + optional: true + + '@better-auth/telemetry@1.7.4': + resolution: {integrity: sha512-FycnvWXP1gZ8BlSN1lW9L/YYFVqxi7+n6A9i66gJKdoJqREDdmhxBKOaVCbNhNC2UNDAo9PbBm/6J3gv6WmAEA==} + peerDependencies: + '@better-auth/core': ^1.7.4 + '@better-auth/utils': 0.4.2 + '@better-fetch/fetch': 1.3.1 + + '@better-auth/utils@0.4.2': + resolution: {integrity: sha512-AUxrvu+HaaODsUyzDxFgwd/8RZ1yZaYo42LXKSrU2oGgR38pS1ij8nqQKNgtTWoYGpNevNXtCfgTy6loHveW9A==} + + '@better-auth/utils@0.5.0': + resolution: {integrity: sha512-BL8W4EfIZFwlu0r54m3v1ztjDhu6dDe/amLTm0xybmbZaNgYUqhD3SjpAsnq0q8YD6/ki4iwIgxJNLP/N3TxiA==} + + '@better-fetch/fetch@1.3.1': + resolution: {integrity: sha512-ABkD1WhyfPZprKRQI3bhATjeiFuNWC9PXhfGWqL+sg/gKrM977oFrYkdb4msM3hgUGonr7KlOsOFT5TU2rht9g==} + + '@borewit/text-codec@0.2.2': + resolution: {integrity: sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==} + + '@cacheable/memory@2.2.0': + resolution: {integrity: sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==} + + '@cacheable/utils@2.5.0': + resolution: {integrity: sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==} + + '@colors/colors@1.5.0': + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + engines: {node: '>=0.1.90'} + + '@drizzle-team/brocli@0.10.2': + resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} + + '@emnapi/core@1.11.3': + resolution: {integrity: sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==} + + '@emnapi/runtime@1.11.3': + resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} + + '@emnapi/wasi-threads@1.2.3': + resolution: {integrity: sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==} + + '@esbuild-kit/core-utils@3.3.2': + resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} + deprecated: 'Merged into tsx: https://tsx.hirok.io' + + '@esbuild-kit/esm-loader@2.6.5': + resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} + deprecated: 'Merged into tsx: https://tsx.hirok.io' + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.18.20': + resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.18.20': + resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.18.20': + resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.18.20': + resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.18.20': + resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.18.20': + resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.18.20': + resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.18.20': + resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.18.20': + resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.18.20': + resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.18.20': + resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.18.20': + resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.18.20': + resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.18.20': + resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.18.20': + resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.18.20': + resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.18.20': + resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.18.20': + resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.18.20': + resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.18.20': + resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.18.20': + resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.18.20': + resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.23.5': + resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/config-helpers@0.7.0': + resolution: {integrity: sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/core@1.2.1': + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/js@10.0.1': + resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true + + '@eslint/object-schema@3.0.5': + resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/plugin-kit@0.7.3': + resolution: {integrity: sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.35.4': + resolution: {integrity: sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.35.4': + resolution: {integrity: sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [darwin] + + '@img/sharp-freebsd-wasm32@0.35.4': + resolution: {integrity: sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==} + engines: {node: '>=20.9.0'} + os: [freebsd] + + '@img/sharp-libvips-darwin-arm64@1.3.3': + resolution: {integrity: sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.3.3': + resolution: {integrity: sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.3.3': + resolution: {integrity: sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.3.3': + resolution: {integrity: sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-ppc64@1.3.3': + resolution: {integrity: sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-riscv64@1.3.3': + resolution: {integrity: sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.3.3': + resolution: {integrity: sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.3.3': + resolution: {integrity: sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.3.3': + resolution: {integrity: sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.3.3': + resolution: {integrity: sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.35.4': + resolution: {integrity: sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.35.4': + resolution: {integrity: sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==} + engines: {node: '>=20.9.0'} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-ppc64@0.35.4': + resolution: {integrity: sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==} + engines: {node: '>=20.9.0'} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-riscv64@0.35.4': + resolution: {integrity: sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==} + engines: {node: '>=20.9.0'} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.35.4': + resolution: {integrity: sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==} + engines: {node: '>=20.9.0'} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.35.4': + resolution: {integrity: sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.35.4': + resolution: {integrity: sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.35.4': + resolution: {integrity: sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.35.4': + resolution: {integrity: sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==} + engines: {node: '>=20.9.0'} + + '@img/sharp-webcontainers-wasm32@0.35.4': + resolution: {integrity: sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==} + engines: {node: '>=20.9.0'} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.35.4': + resolution: {integrity: sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.35.4': + resolution: {integrity: sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==} + engines: {node: ^20.9.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.35.4': + resolution: {integrity: sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [win32] + + '@inquirer/ansi@2.0.8': + resolution: {integrity: sha512-WpQM+Ti6Z40EFwwt+uL2p4UabT+W179zHp6HhLVOzfbwnVn05IPO/eXIZXGNqcT1jbQ15SujNLzQ39k4QPPxBQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + + '@inquirer/checkbox@5.2.5': + resolution: {integrity: sha512-bRt8J8m+Fot9CXv+zNQGXUq2ET0MggR1fPz7v6edN6MFYmsbfGnMmkmWZJEegMKqrAC8ej/o1sqisHZXZJMAfQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/confirm@6.3.2': + resolution: {integrity: sha512-Xvr/0HggjddPtGppuqVmxhTw+Hr8PvsZ/k0HmOEaAqQEt80OITNkFWnsdNmyT0/eM4Ab+iJLx2R8rctlEyfSVg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@12.0.3': + resolution: {integrity: sha512-wsSy0sznmXwkty+2PzZwx00Cazc/E0r0B7mAzdGROz2Ct+DFZXaK7WDjGZvgjRldxH5ZhFVfF2lgkYrqgOw2KA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/editor@5.3.3': + resolution: {integrity: sha512-YsKkS2q63IiLtaDK/9nqzdComN97SDQrmKiyNggN+ceP4ty+Z6VwyTz3FpjeUWeW1Efss2xHFKCC9sx7hnrsxg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/expand@5.1.5': + resolution: {integrity: sha512-uHuXLmXW+TtIfT/9vSBotypAkqn1n34Ul+CLGPos/xANyO4Ff5xZzkYhbKR4NEcfVK4a9mHQOpwVZzluSHFRGw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/external-editor@3.0.5': + resolution: {integrity: sha512-f3QQJRIX5ZEneBHNUIuPjmbdzHnmRFJA8r2dkcb8q+OM5Uv5KtnuAttQumnrjcBVBM3mcTX1CkmtAkU58VRZxg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@2.0.9': + resolution: {integrity: sha512-EAWgUTGQ/Umgga51dE3B2PUHbufuXarDfg86uVgoSgNHNNQnyFKcOrQLWVqYMghuSyHh8+2HUH0Js9cTC1WAdg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + + '@inquirer/input@5.1.6': + resolution: {integrity: sha512-HtcJhB2QFVXbLuJ5S3syhNbTUVxYvwqV4VRBDkQceBloC9bmTViUoRFP5PbSaDZb3HzfPmpuU/gG4ybVBz4FHA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/number@4.2.3': + resolution: {integrity: sha512-6Yuwh1NGSbu1Lo4N1EWjXs1jKRntLg/ZCwhmeorEHde90v1XxAozdbd4Iu30eOQLW+6h1hp2O9ujNfLSbTPJnA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/password@5.2.2': + resolution: {integrity: sha512-W9zYdyzogK+6110mqwaSJWCBu2yA5Q/OfnGSjjZB1bNpHlmUozXxTl0+QOZBNeVd6Qo81/qT75gW05gLAtITxw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/prompts@8.7.2': + resolution: {integrity: sha512-QoRB4wFIjgH5iOhSjoIKMkTvSHDuV+O3OITlIqAYO0oK5x364GJILXiMBvlPiE+klg7Xx9tq5XVqQHcGUDYYPA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/rawlist@5.3.5': + resolution: {integrity: sha512-1oHky1ONfCOwNrnkQGDE1oaSij/3fI6HFMSf2H/WsGO2lEyDX9My82iggITSy9ddSZ8yk8j9v41OI0fVoSIoaA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/search@4.3.3': + resolution: {integrity: sha512-fyuIU1Nbpvwlikjg3gXwJFDI11+EFjqQ7P+iByfmivIKQ1vmaykNrD/vy5unHuUqUpsOsnvJ25//tPF7E/RBRA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/select@5.2.5': + resolution: {integrity: sha512-9kc15hr8r/kI+3DO/xLog5nOzTz1jqsHXa6JBFzmQKhkoJ8Slda1I1L/uD8ZSZ9tF1yp79wwXe7mclvX1rqR2Q==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/type@4.1.1': + resolution: {integrity: sha512-yJoHYrMnxIsJZCY+0Vb66Dy3he3kL3e2wOBKhoSwWWAzZAY82emlxwgprCtp6yRixvNRNq9ztfRWQYPNr3Go7A==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@ioredis/commands@1.10.0': + resolution: {integrity: sha512-UmeW7z4LfctwoQ5wkhVzgq8tXkreED2xZGpX+Bg+zA+WJFZCT6c062AfCK/Dfk81xZnnwdhJCUMkitihRaoC2Q==} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.11': + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} + + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@keyv/bigmap@1.3.1': + resolution: {integrity: sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==} + engines: {node: '>= 18'} + peerDependencies: + keyv: ^5.6.0 + + '@keyv/serialize@1.1.1': + resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==} + + '@lukeed/csprng@1.1.0': + resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} + engines: {node: '>=8'} + + '@microfleet/ioredis-lock@6.0.1': + resolution: {integrity: sha512-nd4pKn4GMH3N+DrCm9R/AW7nzm4oLxtmX94HvkR94ReJdpDrP54aD0bA5rjVLZarCzTpHMxGDiH8sfsvDtsjTg==} + peerDependencies: + ioredis: ~4.x.x || ~5.x.x + + '@module-federation/error-codes@0.22.0': + resolution: {integrity: sha512-xF9SjnEy7vTdx+xekjPCV5cIHOGCkdn3pIxo9vU7gEZMIw0SvAEdsy6Uh17xaCpm8V0FWvR0SZoK9Ik6jGOaug==} + + '@module-federation/runtime-core@0.22.0': + resolution: {integrity: sha512-GR1TcD6/s7zqItfhC87zAp30PqzvceoeDGYTgF3Vx2TXvsfDrhP6Qw9T4vudDQL3uJRne6t7CzdT29YyVxlgIA==} + + '@module-federation/runtime-tools@0.22.0': + resolution: {integrity: sha512-4ScUJ/aUfEernb+4PbLdhM/c60VHl698Gn1gY21m9vyC1Ucn69fPCA1y2EwcCB7IItseRMoNhdcWQnzt/OPCNA==} + + '@module-federation/runtime@0.22.0': + resolution: {integrity: sha512-38g5iPju2tPC3KHMPxRKmy4k4onNp6ypFPS1eKGsNLUkXgHsPMBFqAjDw96iEcjri91BrahG4XcdyKi97xZzlA==} + + '@module-federation/sdk@0.22.0': + resolution: {integrity: sha512-x4aFNBKn2KVQRuNVC5A7SnrSCSqyfIWmm1DvubjbO9iKFe7ith5niw8dqSFBekYBg2Fwy+eMg4sEFNVvCAdo6g==} + + '@module-federation/webpack-bundler-runtime@0.22.0': + resolution: {integrity: sha512-aM8gCqXu+/4wBmJtVeMeeMN5guw3chf+2i6HajKtQv7SJfxV/f4IyNQJUeUQu9HfiAZHjqtMV5Lvq/Lvh8LdyA==} + + '@napi-rs/wasm-runtime@1.0.7': + resolution: {integrity: sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==} + + '@nestjs/cli@12.0.8': + resolution: {integrity: sha512-iqm+KXb8cnpnlD4hMBf1w2/4i1GSs0DCLI7oyP4MTmwf9KmjTG1SpAjkl/D+kKuUToycBrMF4+eSwyPVdTMGKQ==} + engines: {node: '>= 20.11'} + hasBin: true + peerDependencies: + '@rspack/core': ^1.7.7 || ^2.1.10 + '@swc/cli': ^0.8.0 + '@swc/core': ^1.15.18 + fork-ts-checker-webpack-plugin: ^9.1.0 + ts-loader: ^9.5.4 + tsconfig-paths-webpack-plugin: ^4.2.0 + webpack: ^5.105.4 + webpack-node-externals: ^3.0.0 + peerDependenciesMeta: + '@rspack/core': + optional: true + '@swc/cli': + optional: true + '@swc/core': + optional: true + fork-ts-checker-webpack-plugin: + optional: true + ts-loader: + optional: true + tsconfig-paths-webpack-plugin: + optional: true + webpack: + optional: true + webpack-node-externals: + optional: true + + '@nestjs/common@12.1.2': + resolution: {integrity: sha512-e2tvLcEaG18sJy5IXrdSLv0ZBpR5djWxypCLkLuMAEFCvVPYL1lEIhvAMBkarlg+ZA8tbZN40J0tFurUl9NpdA==} + peerDependencies: + class-transformer: '>=0.4.1' + class-validator: '>=0.13.2' + reflect-metadata: ^0.1.12 || ^0.2.0 + rxjs: ^7.1.0 + peerDependenciesMeta: + class-transformer: + optional: true + class-validator: + optional: true + + '@nestjs/config@12.0.1': + resolution: {integrity: sha512-pxqEtULFi4qXda0TqyR2YFq7o4UoRa7h4K5H0lgFuBMrUGr6x6VVldaJJrowFyh4uvp8dNq9Mie3T+l+uEOTfw==} + peerDependencies: + '@nestjs/common': ^11.0.0 || ^12.0.0 + rxjs: ^7.1.0 + + '@nestjs/core@12.1.2': + resolution: {integrity: sha512-ieNRDv6P6dLsF/wMz9IUjwnyQbj7/Oluq1FXe3Xhje7TFSKPprLc83QniA2I6tjkr7YVDosdL2Dj7CVv9kU8og==} + engines: {node: '>= 20'} + peerDependencies: + '@nestjs/common': ^12.0.0 + '@nestjs/microservices': ^12.0.0 + '@nestjs/platform-express': ^12.0.0 + '@nestjs/websockets': ^12.0.0 + reflect-metadata: ^0.1.12 || ^0.2.0 + rxjs: ^7.1.0 + peerDependenciesMeta: + '@nestjs/microservices': + optional: true + '@nestjs/platform-express': + optional: true + '@nestjs/websockets': + optional: true + + '@nestjs/platform-express@12.1.2': + resolution: {integrity: sha512-//ftPv5+UONizm5zcme/AP91EFo1EsqFFyC2I3HC2w8swYVXadHKW1L3A2mgUuOhM1sdKkd2gzjQmL9504x85Q==} + peerDependencies: + '@nestjs/common': ^12.0.0 + '@nestjs/core': ^12.0.0 + + '@nestjs/platform-ws@12.1.2': + resolution: {integrity: sha512-kJGqXKON62X71e9Di+NAZaxUhAGfYGx35X/GNR0/kk9C7Qj8TgueL1DZtyalRPYBVX/04wcmn8ituOe+l+jJZg==} + peerDependencies: + '@nestjs/common': ^12.0.0 + '@nestjs/websockets': ^12.0.0 + rxjs: ^7.1.0 + + '@nestjs/schedule@12.0.2': + resolution: {integrity: sha512-5iAvJEtk0njbfTrG4JQr+SXw8ZrqgDM1wJH9nUs6tVLCX5pWGd1LJ0a8b089UT1FDeDWaThRRfSLixFz9zztcA==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@nestjs/common': ^11.0.0 || ^12.0.0 + '@nestjs/core': ^11.0.0 || ^12.0.0 + + '@nestjs/schematics@12.0.6': + resolution: {integrity: sha512-2OG0BUhGPJqVvWFhiy+7oaQa4VQUvTS9Dw8YRfvweznl4KS3YyZrdPi4cVGuRAThoEIsbdfguZzSOv1+t8U1tg==} + engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0} + peerDependencies: + prettier: ^3.0.0 + typescript: '>=6.0.0' + peerDependenciesMeta: + prettier: + optional: true + + '@nestjs/websockets@12.1.2': + resolution: {integrity: sha512-iW+97/cYLP1OrEhZKGwT+BW2q79fnfAUJ/mcyaPgcR/ZA9UwvoDw7eN/rv3bE5NaCSOOZl74+hSGRyR1sBd0HA==} + peerDependencies: + '@nestjs/common': ^12.0.0 + '@nestjs/core': ^12.0.0 + '@nestjs/platform-socket.io': ^12.0.0 + reflect-metadata: ^0.1.12 || ^0.2.0 + rxjs: ^7.1.0 + peerDependenciesMeta: + '@nestjs/platform-socket.io': + optional: true + + '@noble/ciphers@2.2.0': + resolution: {integrity: sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA==} + engines: {node: '>= 20.19.0'} + + '@noble/hashes@2.2.0': + resolution: {integrity: sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==} + engines: {node: '>= 20.19.0'} + + '@opentelemetry/semantic-conventions@1.43.0': + resolution: {integrity: sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==} + engines: {node: '>=14'} + + '@oxc-project/types@0.149.0': + resolution: {integrity: sha512-Efcc+iF0j3Bf67YjEqIqWXbX5XddXoK/Mw4K1/JuXwRCZ8N16VR7iT23nlCc9XrveFVh/E5Rqs2StT0V8v9LdA==} + + '@quansync/fs@1.0.0': + resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} + + '@rolldown/binding-android-arm-eabi@1.2.8': + resolution: {integrity: sha512-tN5aztYkKCte4i5SIrrz5yK/HMjEuCqCSCJa418jOV8tZ1cBY3YF2otxB1ktPxzsLA1BeTqwapK0bfjxNvHJVw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@rolldown/binding-android-arm64@1.2.8': + resolution: {integrity: sha512-dIYTWl9XprMUiQFoc55KUyk/oS8SKYH3zFl0LTR7RT0Xj4hgSVyuJcroH8JUu8RcpF8fTB6E0aOwCkZoYPcDSQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.2.8': + resolution: {integrity: sha512-PCSDQGXD2IyTEFrcgPyBM8jJuGmrbCMuoIOXdbEGVemruKACXoLQJrb+A45Z0L5t1RQkdfJprAYPkikbh7dzdA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.2.8': + resolution: {integrity: sha512-Uk7lRsGhPFHVX/sAUC6D5H9Ol30dFHd6iquokll2th3LpdJ3F5CzQB+7DHn0Ri2mG+U7k2zXiPHDrwZenXhwSA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.2.8': + resolution: {integrity: sha512-DjszaTEVogPqA5bYzsEeqDCQxbcp2fexQwKcRspYji2yzR68fCf+e4fx6kBSRDwX5/brZaHw/hWS9+A/+/w9sQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.2.8': + resolution: {integrity: sha512-zmwa7FTmdzB6aaEEuuls18H6Ap5JmJPSoPTuXixeJZV6tG40SyLkApQtz1g8ptZtiEKqj9OM0oNLPh1AgvE31Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.2.8': + resolution: {integrity: sha512-KdYQDPHwJVnbFwdTGMgxsI9SqblBlz6STGM+w1We/d5B8OWWidYH0MwkU/uA1wM5fIpO2MkOVxXrNzzuZhw9ew==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.2.8': + resolution: {integrity: sha512-jFJTifHnNPY+yzOoNZQfSIysrVyXzEQPhPnOUjmD1bcQGHH6s7c8cViKWar8YplQImE5N9JRqMCLrM2CdxOrZA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.2.8': + resolution: {integrity: sha512-FhiOziBDWPBjbcmRzfLyIJnaP7AVMFXT7YCXPjXxj7wKU3vx24RjrCNN/zjvVa+N2vVoHJwCoUBvsrN/DG3zIA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.2.8': + resolution: {integrity: sha512-WnHfADMzOV2Y55wlx1hzzQnar/wDt/VdvWSD99r18Mz9ylNieIGOkRx3UV21h7m/eJvjySYJkO26VvGNFkwsIQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.2.8': + resolution: {integrity: sha512-H9tRr5ibfXFVLxbPOseVewewFpl28zcEdjRDt2FTUZU7odxP0gEv1ki4/kGmcGOh78oRwZuuQllGLZ9zTJp84g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.2.8': + resolution: {integrity: sha512-UefiqfM3D6IVNlZ8tSGs9+Ejjud2T+oxO0IHADU45Y+lyEjD2dVFyZHbkfX0LUb5Zugo/oIv1eCO/KVYhgYJYA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.2.8': + resolution: {integrity: sha512-637Ke4kWSy6rp9cxQ9gMOXlxPgIw/c1beASV4M//3+9I4uwBVOOl74G+e3zyU3u19U7RkRl/HuewixZ/Z6+Rjg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-win32-arm64-msvc@1.2.8': + resolution: {integrity: sha512-xWBkPOF1Q9k/Gv1nQXnVdLxKu74jXppuOM4Z3mnypVUJJJwLsMl7hNJGRAUJoG8A5MgOI1ACKM+wBFxSJzKy4A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.2.8': + resolution: {integrity: sha512-uz2ZvfgXbxqNwijjjbxrnvALwpyODDcgc1T1N8N3rf/DXKQmaFwmB4LX4yyjggpwN2obdQLb2rgirX5ffCWYng==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@rspack/binding-darwin-arm64@1.7.12': + resolution: {integrity: sha512-rbFprJaJiqrmfy8SHth8EsoRS0wg4bXcucwj9NiMzpGFq14Opw8c04iQ6H9BECYzgmN0PKZ9rh41LdVvhdZe4A==} + cpu: [arm64] + os: [darwin] + + '@rspack/binding-darwin-x64@1.7.12': + resolution: {integrity: sha512-jnOp+/UXOJa9xqUb8KXH03sysoO2e4Ij6tw6MqDdmdj8n/A8PQENRPUbW9AwXpPtVDJPus9r4fi7b3+6e4B8Hg==} + cpu: [x64] + os: [darwin] + + '@rspack/binding-linux-arm64-gnu@1.7.12': + resolution: {integrity: sha512-C8owWG+yvo7X0oVLIXetkoJhIFBP1LYNcAQqtgLmJnQLQDklGuP83dKC+zISGQWpjawHfZ1ER96vLgoTrxKZdw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rspack/binding-linux-arm64-musl@1.7.12': + resolution: {integrity: sha512-i51WWI64aRpsfSki6rN0aepPqXkVfS+vZM7+4bWDcmnhUmdMvhIPcYg0QRk3DtyJnu33jqNLM0WHY78k00NyfA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rspack/binding-linux-x64-gnu@1.7.12': + resolution: {integrity: sha512-MSos0FuPEefqo9V92ULd5hggKG29EkSNg1zDcypy0OkpsKh5pfjVxTLYFXgTcVyFoUQQbdG8zFBzYbwmJ8V4ew==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rspack/binding-linux-x64-musl@1.7.12': + resolution: {integrity: sha512-JcAMVKXOnjfpC3coWjCFPWD3Yl8RBw6a+IXQQ8mfRlHaHMIiOv8IfZqx15XRxMUn49CtP7Z0Na8iiAg2aKrcfw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rspack/binding-wasm32-wasi@1.7.12': + resolution: {integrity: sha512-n+ZqP6ZMc0nhOgvadg5VhEs9ojtbES80AcWeFnmGkbzIszvGSO63GKNiRkXtjJ9KFuRzytbbmsCqkUVH+Tywxg==} + cpu: [wasm32] + + '@rspack/binding-win32-arm64-msvc@1.7.12': + resolution: {integrity: sha512-8+h5fYDXYdmugbdfZ+D1y8IQ3rv2EhSfyGP7vBe+bjNyaMa4jWrpucmZbtxojUL1AzaeuHbvMdj9UO/gelk/+g==} + cpu: [arm64] + os: [win32] + + '@rspack/binding-win32-ia32-msvc@1.7.12': + resolution: {integrity: sha512-cDMGwTRSa2p9fNBVe1wTRkF2AEXZ9ARWW36QeC5CkLaI0Ezz8lvhF2+CSOPnhaQ1O1qtn0L0SF+lFnrY+I7xGQ==} + cpu: [ia32] + os: [win32] + + '@rspack/binding-win32-x64-msvc@1.7.12': + resolution: {integrity: sha512-wIqFvlgFqrgUyj/6S/FJcvShnkZOmIeXTfqvheLY67MGq8qd8jb1YimQVKAIrmWB3yuJKUFACI3Ag1UBtEedEA==} + cpu: [x64] + os: [win32] + + '@rspack/binding@1.7.12': + resolution: {integrity: sha512-f4HHuLbvuld8Ba4iB/4ibse5XrKxFrgmM3S4P2AOKnPlekAFlBjmltCuaTL/W2ggYvILaVY+YcFXrEH1rrKeQA==} + + '@rspack/core@1.7.12': + resolution: {integrity: sha512-6CwFIHlhRmXfZoMj3v9MZ1SMTPBn+cHVXeMIeaGp5sufqinKsISbsqHu6ZMJu2wDSmZLdmQJX6zLxkhcAUlhkQ==} + engines: {node: '>=18.12.0'} + peerDependencies: + '@swc/helpers': '>=0.5.1' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@rspack/lite-tapable@1.1.0': + resolution: {integrity: sha512-E2B0JhYFmVAwdDiG14+DW0Di4Ze4Jg10Pc4/lILUrd5DRCaklduz2OvJ5HYQ6G+hd+WTzqQb3QnDNfK4yvAFYw==} + + '@sindresorhus/is@4.6.0': + resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} + engines: {node: '>=10'} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@tailwindcss/node@4.3.3': + resolution: {integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==} + + '@tailwindcss/oxide-android-arm64@4.3.3': + resolution: {integrity: sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.3.3': + resolution: {integrity: sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.3.3': + resolution: {integrity: sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.3.3': + resolution: {integrity: sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': + resolution: {integrity: sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': + resolution: {integrity: sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-arm64-musl@4.3.3': + resolution: {integrity: sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-linux-x64-gnu@4.3.3': + resolution: {integrity: sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-x64-musl@4.3.3': + resolution: {integrity: sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-wasm32-wasi@4.3.3': + resolution: {integrity: sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': + resolution: {integrity: sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.3.3': + resolution: {integrity: sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.3.3': + resolution: {integrity: sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==} + engines: {node: '>= 20'} + + '@tailwindcss/vite@4.3.3': + resolution: {integrity: sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 || ^8 + + '@tokenizer/inflate@0.4.1': + resolution: {integrity: sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==} + engines: {node: '>=18'} + + '@tokenizer/token@0.3.0': + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + + '@turbo/darwin-64@2.11.6': + resolution: {integrity: sha512-nAvJwDBgz/cKoSPhB7YOGPFmDEwCRavFjy0jUsrGqR97cLyJqXU2rZC/zTnw+9J3DkB2gfjpolX7mKsBatMcCQ==} + cpu: [x64] + os: [darwin] + + '@turbo/darwin-arm64@2.11.6': + resolution: {integrity: sha512-odk6UHW9hympllPlezz7oJhPmO40+oU8BUh4zvNuSIqQ1QQll4zo1DN4IrD/3jUzkLefvMJ8UHEHlGq9WdR7tw==} + cpu: [arm64] + os: [darwin] + + '@turbo/linux-64@2.11.6': + resolution: {integrity: sha512-PvXbwMqZPEWdWnJSIJ5D/tugnSOrRvLYKkH7iC8TLGVI+4J5znSLn3Tx1YkH5OUzFEFhxJQyHjtiRqBPbCoVlg==} + cpu: [x64] + os: [android, linux] + + '@turbo/linux-arm64@2.11.6': + resolution: {integrity: sha512-8FKA3SqJk6fTv7Hqrmv2JnJfqiXslTRJYBwiI6eU3QXokVMYFLh1wUywF+w3MPtDfx1O/1otbps5AnijbzY1Kg==} + cpu: [arm64] + os: [android, linux] + + '@turbo/windows-64@2.11.6': + resolution: {integrity: sha512-XPSgTg494cV31u0Uch8zRq/Jnpe8h4muwNpg9IRPrJqADrjjzxzFMtJCtBZTKVaSEDUU/ol8Gr4SYxtiHt2Tzg==} + cpu: [x64] + os: [win32] + + '@turbo/windows-arm64@2.11.6': + resolution: {integrity: sha512-dZ0/ZFH2AU/l1KdqISVkWUSnm0ufWZyiYRLDYCL+rJAhkUDMFhTZkSnYEL2O/m0tOu/Jy/EKAnZfocuyMX3LMQ==} + cpu: [arm64] + os: [win32] + + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + + '@types/canvas-confetti@1.9.0': + resolution: {integrity: sha512-aBGj/dULrimR1XDZLtG9JwxX1b4HPRF6CX9Yfwh3NvstZEm1ZL7RBnel4keCPSqs1ANRu1u2Aoz9R+VmtjYuTg==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/cors@2.8.19': + resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} + + '@types/d3-format@3.0.4': + resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/express-serve-static-core@5.1.3': + resolution: {integrity: sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw==} + + '@types/express@5.0.6': + resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/luxon@3.7.6': + resolution: {integrity: sha512-6KSjliQAXK8ZLgFQO4B7iE4Rpf/B3rItzCFuXezBY/XIAGxOdLd7vRNK9/StRPwiS/yJsVbB7HKpW46B8tcEuQ==} + + '@types/node@26.5.1': + resolution: {integrity: sha512-CzNm2FezW4VR/LjG6yUdiEgLE/rAQ9Slj5gCu/C2VrdcW7I0ahNZ8DRbHT7zOZ6r3ONgd/bsQIeSaoDGrd1C6g==} + + '@types/nodemailer@8.0.1': + resolution: {integrity: sha512-PxpaInm8V1JQDd4j0ds5HfvWQk8JupS1C0Picb96QJsrrRDjBH+DlK7L4ZdNSqNULhiZRQHc40nLVShaGxXAMw==} + + '@types/pg@8.23.1': + resolution: {integrity: sha512-fKVHpikPdg4GKks3JuLEhvwSyvwzF23hnabPy6DD8ljVbC7+6J5dQzdv4arV6jqq57djnMgs1HKBxX4P8aBI3A==} + + '@types/qs@6.15.1': + resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + + '@types/react-dom@19.3.0': + resolution: {integrity: sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==} + peerDependencies: + '@types/react': ^19.3.0 + + '@types/react@19.3.0': + resolution: {integrity: sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==} + + '@types/send@1.2.1': + resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} + + '@types/serve-static@2.2.0': + resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} + + '@types/ws@8.18.2': + resolution: {integrity: sha512-67MQl+fpWKVTT1NYdnmo3U4sc/xPo/zQBncVnI74qmQa0z/b+1g6iYqNmGCPbxO+zz2aklb08a0oHfegiVd0/w==} + + '@typescript-eslint/eslint-plugin@8.70.0': + resolution: {integrity: sha512-/v8HZt6RlyIZxB3ntehELOcUcfxKPVGWXnQdJuHRmzrqgF8nQypcC/oxGW+Ot4VGKDq81XugPKxx0n5PBtf9PA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.70.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.70.0': + resolution: {integrity: sha512-zYvrmj9Yxd63UGaXw+kdt6A0F0s0qveJyuatIM77bYC2DE4pgmg7a50u8LR7PRtXd0x+h+Tl3eXabGm06SWd3Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.70.0': + resolution: {integrity: sha512-hFHbTNqhU9G+2eKFXCBVb1tjFT/LceiJ4+HfLO4pTpDI0KHi6iajpcFFkaSQ9gXmCh7n82A0PthaayEdN6mspQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.70.0': + resolution: {integrity: sha512-8nP3Kwh5hlgZ4FicGvmznAmJe8UL4sdU8tLukrPaMuQmDuk4Y8xYfzu/aYZW4xT2JCgc7H/TpDI5cGlxcWJSqQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.70.0': + resolution: {integrity: sha512-adnkeeNq9Sq1sUf4+FRVc0KdgYghzsgFpZSQVZVvY0LCuUuN0FnQgyGzCJeC4fW1cdXseBAjU2EOqUIjbNcZUw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.70.0': + resolution: {integrity: sha512-NUMKIhYVaVIVLnRL9CRt+VVcuLgSHUCpXn4/+K8wql+vdInUzvx8BjUO1oJ7cG9shjFJKtF8F8Hh2kCh3/KBVw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.70.0': + resolution: {integrity: sha512-asTOIYhDg4zdzOScCyaytrsV3cR6B4ecPQlXw/dJIm7J/MZTtCtfVII9JD8Geh4jTCrK/Xe6cg5UevoleMcoJQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.70.0': + resolution: {integrity: sha512-d9NmHMPEKQ7QCLLm1jI3zmoQBwT5KwFYjXBJ9ymZfKCUU+5rmTRykKAFvH5Qn/ZCds3CEAFS9OC9M/jkl0X2bA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.70.0': + resolution: {integrity: sha512-oZmtKJz/4fufZ2p3+Cn3ijEojcdfR+1zYDH2xKYrEly0dR/Q/1xUPRCOlKGxod78nWlU2UnDe09GZ3TaknBFGA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.70.0': + resolution: {integrity: sha512-BoC8PiO4Hkdo0TVJh9Ntxr5MxPDI7/oFsrygN5ADelFSeXG/qgNuucIGA+L5Z6JpPTE/uRfcTWtscjbUaufepQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vercel/oidc@3.2.0': + resolution: {integrity: sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==} + engines: {node: '>= 20'} + + '@vitejs/plugin-react@6.1.1': + resolution: {integrity: sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + oxc-transform-react: ^0.145.0 + vite: ^8.0.0 + peerDependenciesMeta: + '@rolldown/plugin-babel': + optional: true + babel-plugin-react-compiler: + optional: true + oxc-transform-react: + optional: true + + '@vitest/expect@4.1.11': + resolution: {integrity: sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==} + + '@vitest/mocker@4.1.11': + resolution: {integrity: sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.11': + resolution: {integrity: sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==} + + '@vitest/runner@4.1.11': + resolution: {integrity: sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==} + + '@vitest/snapshot@4.1.11': + resolution: {integrity: sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==} + + '@vitest/spy@4.1.11': + resolution: {integrity: sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==} + + '@vitest/utils@4.1.11': + resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==} + + '@webassemblyjs/ast@1.14.1': + resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} + + '@webassemblyjs/floating-point-hex-parser@1.13.2': + resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==} + + '@webassemblyjs/helper-api-error@1.13.2': + resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==} + + '@webassemblyjs/helper-buffer@1.14.1': + resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==} + + '@webassemblyjs/helper-numbers@1.13.2': + resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==} + + '@webassemblyjs/helper-wasm-bytecode@1.13.2': + resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==} + + '@webassemblyjs/helper-wasm-section@1.14.1': + resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==} + + '@webassemblyjs/ieee754@1.13.2': + resolution: {integrity: sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==} + + '@webassemblyjs/leb128@1.13.2': + resolution: {integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==} + + '@webassemblyjs/utf8@1.13.2': + resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==} + + '@webassemblyjs/wasm-edit@1.14.1': + resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==} + + '@webassemblyjs/wasm-gen@1.14.1': + resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==} + + '@webassemblyjs/wasm-opt@1.14.1': + resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==} + + '@webassemblyjs/wasm-parser@1.14.1': + resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==} + + '@webassemblyjs/wast-printer@1.14.1': + resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} + + '@workflow/serde@4.1.0': + resolution: {integrity: sha512-pav4F2BoirECWR7Nf1TKt+2eETcBj7jj4cBefQ8VXQCA6NPkaKeLfj/zMgi+3zYV5ZIBT4GuUiphsj0/b9hPQQ==} + + '@xtuc/ieee754@1.2.0': + resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} + + '@xtuc/long@4.2.2': + resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ai@7.0.99: + resolution: {integrity: sha512-Ov+3j/nSajaVH5hO8C94wN9wisG5tAJ8HWsLV9d/+nlzrRGUh3oYO3Kp1fRyUNWjAWLSpGLHLPaHCdfxb307Vg==} + engines: {node: '>=22'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + + ajv-keywords@3.5.2: + resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} + peerDependencies: + ajv: ^6.9.1 + + ajv-keywords@5.1.0: + resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} + peerDependencies: + ajv: ^8.8.2 + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + + altcha-lib@2.5.0: + resolution: {integrity: sha512-MiqlwnImoooVTmFzZ8odhpqtCsSl7M/6cmgaX42Tkuvd41e5IVveg6hiw/e9rXSb6dm1RvniPmeURwO64wWTuQ==} + hasBin: true + peerDependencies: + '@fastify/cookie': ^11.0.0 + '@fastify/cors': ^11.0.0 + '@fastify/formbody': ^8.0.0 || ^9.0.0 + '@nestjs/common': ^10.0.0 || ^11.0.0 || ^12.0.0 + '@nestjs/core': ^10.0.0 || ^11.0.0 || ^12.0.0 + '@nestjs/platform-express': ^10.0.0 || ^11.0.0 || ^12.0.0 + '@nestjs/platform-fastify': ^10.0.0 || ^11.0.0 || ^12.0.0 + '@sveltejs/kit': ^2.0.0 + cookie-parser: ^1.0.0 + cors: ^2.0.0 + express: ^4.0.0 || ^5.0.0 + fastify: ^5.0.0 + h3: ^2.0.0 + hono: ^4.0.0 + peerDependenciesMeta: + '@fastify/cookie': + optional: true + '@fastify/cors': + optional: true + '@fastify/formbody': + optional: true + '@nestjs/common': + optional: true + '@nestjs/core': + optional: true + '@nestjs/platform-express': + optional: true + '@nestjs/platform-fastify': + optional: true + '@sveltejs/kit': + optional: true + cookie-parser: + optional: true + cors: + optional: true + express: + optional: true + fastify: + optional: true + h3: + optional: true + hono: + optional: true + + altcha@3.2.2: + resolution: {integrity: sha512-yNITQaRSkRe0u/hMxdzoXFFqxqn9iDt1rJ0reREU3D3uDD3w1oFz6v5C2PMww2XnGThtYUwe8MW+PxsiBglNqw==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.4.0: + resolution: {integrity: sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansis@4.4.0: + resolution: {integrity: sha512-9k3v7xcHwgdO/DruxGIg4HtjvlAZlcnsX/mzqUb1t3NkYnl9kK2UJ+Gq0io+vQf7iT//BD/HB/NBkUR1LWxoeA==} + engines: {node: '>=14'} + + append-field@1.0.0: + resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-timsort@1.0.3: + resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + baseline-browser-mapping@2.11.23: + resolution: {integrity: sha512-le521dGVfxM7yRX0EikCoSz+rOK+hHzdDt/E7mG1jOJB/6WAAUuwVroLwaB7ApaUsz5Q0kFlDXLSA9MheUIfRQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + better-auth@1.7.4: + resolution: {integrity: sha512-rjL9g8b8UvdwK/ZSdawhqYzOm8YegurB6Fuzqtu/zFJamlFrPa+eEhP8Qj2vFWsMzHOk2mb/L4FRYh9pgaU7OA==} + peerDependencies: + '@lynx-js/react': '*' + '@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0 + '@sveltejs/kit': ^2.0.0 + '@tanstack/react-start': ^1.0.0 + '@tanstack/solid-start': ^1.0.0 + better-sqlite3: ^12.0.0 + drizzle-kit: '>=0.31.4 || >=1.0.0-beta.1' + drizzle-orm: ^0.45.2 || >=1.0.0-rc.1 <2.0.0 + mongodb: ^6.0.0 || ^7.0.0 + mysql2: ^3.0.0 + next: ^14.0.0 || ^15.0.0 || ^16.0.0 + pg: ^8.0.0 + prisma: ^5.0.0 || ^6.0.0 || ^7.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + solid-js: ^1.0.0 + svelte: ^4.0.0 || ^5.0.0 + vitest: ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 + vue: ^3.0.0 + peerDependenciesMeta: + '@lynx-js/react': + optional: true + '@prisma/client': + optional: true + '@sveltejs/kit': + optional: true + '@tanstack/react-start': + optional: true + '@tanstack/solid-start': + optional: true + better-sqlite3: + optional: true + drizzle-kit: + optional: true + drizzle-orm: + optional: true + mongodb: + optional: true + mysql2: + optional: true + next: + optional: true + pg: + optional: true + prisma: + optional: true + react: + optional: true + react-dom: + optional: true + solid-js: + optional: true + svelte: + optional: true + vitest: + optional: true + vue: + optional: true + + better-call@1.4.0: + resolution: {integrity: sha512-bBKOT4vv1kZLDgxVePdilk/Jwkn+dtRRsmi3DzHcDP+WnswyVl6dR59l2HEeP/0cB+bDoopASAesWDPIdd/zZA==} + peerDependencies: + zod: ^4.0.0 + peerDependenciesMeta: + zod: + optional: true + + body-parser@2.3.0: + resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} + engines: {node: '>=18'} + + brace-expansion@1.1.21: + resolution: {integrity: sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==} + + brace-expansion@5.0.9: + resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} + engines: {node: 20 || >=22} + + browserslist@4.28.9: + resolution: {integrity: sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + cac@7.0.0: + resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} + engines: {node: '>=20.19.0'} + + cacheable@2.5.0: + resolution: {integrity: sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001810: + resolution: {integrity: sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==} + + canvas-confetti@1.9.4: + resolution: {integrity: sha512-yxQbJkAVrFXWNbTUjPqjF7G+g6pDotOUHGbkZq2NELZUMDpiJ85rIEazVb8GTaAptNW2miJAXbs1BtioA251Pw==} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + + chardet@2.2.0: + resolution: {integrity: sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + + chrome-trace-event@1.0.4: + resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} + engines: {node: '>=6.0'} + + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-spinners@3.4.0: + resolution: {integrity: sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==} + engines: {node: '>=18.20'} + + cli-table3@0.6.5: + resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} + engines: {node: 10.* || >= 12.*} + + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + cluster-key-slot@1.1.1: + resolution: {integrity: sha512-rwHwUfXL40Chm1r08yrhU3qpUvdVlgkKNeyeGPOxnW8/SyVDvgRaed/Uz54AqWNaTCAThlj6QAs3TZcKI0xDEw==} + engines: {node: '>=0.10.0'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@15.0.0: + resolution: {integrity: sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==} + engines: {node: '>=22.12.0'} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + comment-json@5.0.0: + resolution: {integrity: sha512-uiqLcOiVDJtBP8WGkZHEP+FZIhTzP1dxvn59EfoYUi9gqupjrBWVQkO2atDrbnKPwLeotFYDsuNb26uBMqB+hw==} + engines: {node: '>= 6'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + content-type@2.1.0: + resolution: {integrity: sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==} + engines: {node: '>=18'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-es@3.1.1: + resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + + cosmiconfig@8.3.6: + resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + cron@4.4.0: + resolution: {integrity: sha512-fkdfq+b+AHI4cKdhZlppHveI/mgz2qpiYxcm+t5E5TsxX7QrLS1VE0+7GENEk9z0EeGPcpSciGv6ez24duWhwQ==} + engines: {node: '>=18.x'} + + cross-fetch@4.1.0: + resolution: {integrity: sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + + dagre@0.8.5: + resolution: {integrity: sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + defu@6.1.7: + resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + + denque@2.1.0: + resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} + engines: {node: '>=0.10'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + + detect-indent@7.0.2: + resolution: {integrity: sha512-y+8xyqdGLL+6sh0tVeHcfP/QDd8gUgbasolJJpY7NgeQGSZ739bDtSiaiDgtoicy+mtYB81dKLxO9xRhCyIB3A==} + engines: {node: '>=12.20'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + detect-newline@4.0.1: + resolution: {integrity: sha512-qE3Veg1YXzGHQhlA6jzebZN2qVf6NX+A7m7qlhCGG30dJixrAQhYOsJjsnBjJkCSmuOPpCk30145fr8FV0bzog==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + dotenv-expand@13.0.0: + resolution: {integrity: sha512-aBfBS8eYIeXmpHI9ThIlA7/WLq+SLt18iXUZhb52rW89QLKQFoIpPG1bPeewoPZsTyjSSO3T7234FBVUM1V2rA==} + engines: {node: '>=12'} + + dotenv@17.4.2: + resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} + engines: {node: '>=12'} + + dotenv@18.0.3: + resolution: {integrity: sha512-/8CJTyL817qPnfbQrJgOEcrU3fDrJkRIoXBE566wHuvIXKOCHUZhPqGPbCPi9SogjdGnreF0TlvnTgK6J/8zpA==} + engines: {node: '>=12'} + hasBin: true + + drizzle-kit@0.31.10: + resolution: {integrity: sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw==} + hasBin: true + + drizzle-orm@0.45.2: + resolution: {integrity: sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q==} + peerDependencies: + '@aws-sdk/client-rds-data': '>=3' + '@cloudflare/workers-types': '>=4' + '@electric-sql/pglite': '>=0.2.0' + '@libsql/client': '>=0.10.0' + '@libsql/client-wasm': '>=0.10.0' + '@neondatabase/serverless': '>=0.10.0' + '@op-engineering/op-sqlite': '>=2' + '@opentelemetry/api': ^1.4.1 + '@planetscale/database': '>=1.13' + '@prisma/client': '*' + '@tidbcloud/serverless': '*' + '@types/better-sqlite3': '*' + '@types/pg': '*' + '@types/sql.js': '*' + '@upstash/redis': '>=1.34.7' + '@vercel/postgres': '>=0.8.0' + '@xata.io/client': '*' + better-sqlite3: '>=7' + bun-types: '*' + expo-sqlite: '>=14.0.0' + gel: '>=2' + knex: '*' + kysely: '*' + mysql2: '>=2' + pg: '>=8' + postgres: '>=3' + prisma: '*' + sql.js: '>=1' + sqlite3: '>=5' + peerDependenciesMeta: + '@aws-sdk/client-rds-data': + optional: true + '@cloudflare/workers-types': + optional: true + '@electric-sql/pglite': + optional: true + '@libsql/client': + optional: true + '@libsql/client-wasm': + optional: true + '@neondatabase/serverless': + optional: true + '@op-engineering/op-sqlite': + optional: true + '@opentelemetry/api': + optional: true + '@planetscale/database': + optional: true + '@prisma/client': + optional: true + '@tidbcloud/serverless': + optional: true + '@types/better-sqlite3': + optional: true + '@types/pg': + optional: true + '@types/sql.js': + optional: true + '@upstash/redis': + optional: true + '@vercel/postgres': + optional: true + '@xata.io/client': + optional: true + better-sqlite3: + optional: true + bun-types: + optional: true + expo-sqlite: + optional: true + gel: + optional: true + knex: + optional: true + kysely: + optional: true + mysql2: + optional: true + pg: + optional: true + postgres: + optional: true + prisma: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + electron-to-chromium@1.5.427: + resolution: {integrity: sha512-n14zb3FdsChZ2BNobqNHAJMcP3ifFv4paox2LvCrfVAQcqGiSURgbJl+PfMpHVCNFkStnNc+RRVtPBTVW5PDgw==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emojilib@2.4.0: + resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + enhanced-resolve@5.26.0: + resolution: {integrity: sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==} + engines: {node: '>=10.13.0'} + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@2.1.0: + resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + es-toolkit@1.52.0: + resolution: {integrity: sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA==} + + esbuild@0.18.20: + resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-react-hooks@7.1.1: + resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0 + + eslint-plugin-react-refresh@0.5.6: + resolution: {integrity: sha512-uZnh24On2bk478AkaDAKcpRiYhS3qFSaSNvpXxV6/Ek/BBDICkWGG7MnBSfjnIlCVBei5vsLlIeQYxbF22+Udg==} + peerDependencies: + eslint: ^9 || ^10 + + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@10.10.0: + resolution: {integrity: sha512-NPXn6r5zl4uET1DAVPaOwzX3rut4c0wcmw3dWJAfOsTM5+TogXo0DDjz8pwm/hL8cyVNpHqeK4JpN0NjnyFFNw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eventemitter3@3.1.2: + resolution: {integrity: sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==} + + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + eventsource-parser@3.0.8: + resolution: {integrity: sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==} + engines: {node: '>=18.0.0'} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + + fast-uri@3.1.8: + resolution: {integrity: sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==} + + fast-wrap-ansi@0.2.2: + resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@11.1.5: + resolution: {integrity: sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==} + + file-type@22.1.1: + resolution: {integrity: sha512-sEB0oStmeLGtpmSAD88RDNuLsvhv4iC3u0lE25CUvfCULvj49xoQ3l3K525Vn+ZzsZ6DOLajQCV6RmhwwhqiJg==} + engines: {node: '>=22'} + + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@6.1.23: + resolution: {integrity: sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + + fork-ts-checker-webpack-plugin@9.1.0: + resolution: {integrity: sha512-mpafl89VFPJmhnJ1ssH+8wmM2b50n+Rew5x42NeI2U78aRWgtkEtGmctp7iT16UjquJTjorEmIfESj3DxdW84Q==} + engines: {node: '>=14.21.3'} + peerDependencies: + typescript: '>3.6.0' + webpack: ^5.11.0 + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + + fs-monkey@1.1.0: + resolution: {integrity: sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-east-asian-width@1.7.0: + resolution: {integrity: sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==} + engines: {node: '>=18'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + + git-hooks-list@4.2.1: + resolution: {integrity: sha512-WNvqJjOxxs/8ZP9+DWdwWJ7cDsd60NHf39XnD82pDVrKO5q7xfPqpkK6hwEAmBa/ZSEE4IOoR75EzbbIuwGlMw==} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@17.12.0: + resolution: {integrity: sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==} + engines: {node: '>=18'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphlib@2.1.8: + resolution: {integrity: sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==} + + graphology-types@0.24.8: + resolution: {integrity: sha512-hDRKYXa8TsoZHjgEaysSRyPdT6uB78Ci8WnjgbStlQysz7xR52PInxNsmnB7IBOM1BhikxkNyCVEFgmPKnpx3Q==} + + graphology@0.25.4: + resolution: {integrity: sha512-33g0Ol9nkWdD6ulw687viS8YJQBxqG5LWII6FI6nul0pq6iM2t5EKquOTFDbyTblRB3O9I+7KX4xI8u5ffekAQ==} + peerDependencies: + graphology-types: '>=0.24.0' + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hash-wasm@4.12.0: + resolution: {integrity: sha512-+/2B2rYLb48I/evdOIhP+K/DD2ca2fgBjp6O+GBEnCDk2e4rpeXIK8GvIyRPjTezgmWn9gmKwkQjjx6BtqDHVQ==} + + hashery@1.5.1: + resolution: {integrity: sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==} + engines: {node: '>=20'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + + hookified@1.15.1: + resolution: {integrity: sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==} + + hookified@2.2.0: + resolution: {integrity: sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + i18next-http-backend@3.0.6: + resolution: {integrity: sha512-mBOqy8993jtqAoj6XaI1XeC/8/9v6EPS+681ziegrPvTB0DoaCY7PpTS0SpY56qLMoS4OI1TZEM2Zf59zNh05w==} + + i18next@22.5.1: + resolution: {integrity: sha512-8TGPgM3pAD+VRsMtUMNknRz3kzqwp/gPALrWMsDnmC1mKqJwpWyooQRLMcbTwq8z8YwSmuj+ZYvc+xCuEpkssA==} + + iconv-lite@0.7.3: + resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ioredis@5.11.1: + resolution: {integrity: sha512-ehuGcf94bQXhfagULNXrJdfnWO38v070jxSx/qE87Kjzmu2fU7ro5EFAb+OPituLqgfyuQaym5DlrNydW2sJ9A==} + engines: {node: '>=12.22.0'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-interactive@2.0.0: + resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} + engines: {node: '>=12'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + iterare@1.2.1: + resolution: {integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==} + engines: {node: '>=6'} + + jest-worker@27.5.1: + resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} + engines: {node: '>= 10.13.0'} + + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + + jose@6.2.3: + resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.3.2: + resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + + jsonfile@6.2.1: + resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} + + keyv@5.6.0: + resolution: {integrity: sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==} + + kysely@0.28.17: + resolution: {integrity: sha512-nbD8lB9EB3wNdMhOCdx5Li8DxnLbvKByylRLcJ1h+4SkrowVeECAyZlyiKMThF7xFdRz0jSQ2MoJr+wXux2y0Q==} + engines: {node: '>=20.0.0'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + load-esm@1.0.3: + resolution: {integrity: sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA==} + engines: {node: '>=13.2.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + log-symbols@7.0.1: + resolution: {integrity: sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==} + engines: {node: '>=18'} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + luxon@3.7.2: + resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==} + engines: {node: '>=12'} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magic-string@1.4.1: + resolution: {integrity: sha512-8lyCu36ErXR0J9uaGKlKQoiLZKmtI63YGLE8G2o9jyRPdr4X47LusSOwgOJOzcVtp81fTAAjxR7BwKz682Jhow==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + media-typer@1.1.1: + resolution: {integrity: sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==} + engines: {node: '>= 0.8'} + + memfs@3.5.3: + resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} + engines: {node: '>= 4.0.0'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + + minimatch@10.2.6: + resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} + engines: {node: 18 || 20 || >=22} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minimizer-webpack-plugin@5.12.0: + resolution: {integrity: sha512-PCN1x1OzFeFB2GlP91s46q7Gz8Jf16Z7M45bzpoqrYZlh12QeY6tYVM5h299wbwy1DxnQ69VFmmh8Hb2Ff8mbw==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@minify-html/node': '*' + '@napi-rs/image': '*' + '@swc/core': '*' + '@swc/css': '*' + '@swc/html': '*' + clean-css: '*' + cssnano: '*' + csso: '*' + esbuild: '*' + html-minifier-terser: '*' + imagemin: '*' + lightningcss: '*' + postcss: '*' + sharp: '*' + svgo: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@minify-html/node': + optional: true + '@napi-rs/image': + optional: true + '@swc/core': + optional: true + '@swc/css': + optional: true + '@swc/html': + optional: true + clean-css: + optional: true + cssnano: + optional: true + csso: + optional: true + esbuild: + optional: true + html-minifier-terser: + optional: true + imagemin: + optional: true + lightningcss: + optional: true + postcss: + optional: true + sharp: + optional: true + svgo: + optional: true + uglify-js: + optional: true + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + multer@2.4.0: + resolution: {integrity: sha512-7dqa0ZcFfzbefdTuIkzOSMvZWC0J7FLqBOjJUZvDCXShIURWKxAyTT1wHhnE5q19c7jOJf43IYYKjBmZVZmvhg==} + engines: {node: '>= 10.16.0'} + + mustache@4.2.0: + resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} + hasBin: true + + mute-stream@3.0.0: + resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} + engines: {node: ^20.17.0 || >=22.9.0} + + nanoid@3.3.19: + resolution: {integrity: sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + nanostores@1.3.0: + resolution: {integrity: sha512-XPUa/jz+P1oJvN9VBxw4L9MtdFfaH3DAryqPssqhb2kXjmb9npz0dly6rCsgFWOPr4Yg9mTfM3MDZgZZ+7A3lA==} + engines: {node: ^20.0.0 || >=22.0.0} + + nats@2.29.3: + resolution: {integrity: sha512-tOQCRCwC74DgBTk4pWZ9V45sk4d7peoE2njVprMRCBXrhJ5q5cYM7i6W+Uvw2qUrcfOSnuisrX7bEx3b3Wx4QA==} + engines: {node: '>= 14.0.0'} + deprecated: Package moved. Use @nats-io/transport-node from https://github.com/nats-io/nats.js + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + negotiator@1.1.0: + resolution: {integrity: sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==} + engines: {node: '>=18'} + + nkeys.js@1.1.0: + resolution: {integrity: sha512-tB/a0shZL5UZWSwsoeyqfTszONTt4k2YS0tuQioMOD180+MbombYVgzDUYHlx+gejYK6rgf08n/2Df99WY0Sxg==} + engines: {node: '>=10.0.0'} + + node-abort-controller@3.1.1: + resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} + + node-emoji@2.2.0: + resolution: {integrity: sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==} + engines: {node: '>=18'} + + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-releases@2.0.55: + resolution: {integrity: sha512-mIrE/Cw9y+9Au6dS5vDKDhQza9YvG6w+ZrS6X+ZzA7yFW/soAeaups4Qzn1bL6g5FVy8WtP79+0j82oPIbqRjQ==} + engines: {node: '>=18'} + + nodemailer@9.1.1: + resolution: {integrity: sha512-izw9mVKFix6YSnC9eLgV6g1opl9DUlRio9ZNcq+Wu9Ujn2UwF+8Nl0B8nz22kEC+CTZCvinkxwJ0DeFbb6NwcQ==} + engines: {node: '>=6.0.0'} + + normalize-url@9.0.1: + resolution: {integrity: sha512-ARftfC5HdUNu9jJeL8pHj8debUIHA2b91FizCoMzY4lG6dDX13jdvTK0TBe24IBDRf2HvJSzzwEPvmbkQWHRSg==} + engines: {node: '>=20'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + obliterator@2.0.5: + resolution: {integrity: sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==} + + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + + ofetch@1.5.1: + resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + ora@9.4.1: + resolution: {integrity: sha512-6VlU9MLXbjVQD04AZCMX28hVtA5bUoadvUqO76MUCVA0ilwJbMiHsITRPfyVm6p/BC0Av/BXMujx39WCe1LEqw==} + engines: {node: '>=20'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-limit@7.3.1: + resolution: {integrity: sha512-0trZaiG7Y7kN/Egy9a8j47t9osC0Tch4PaIWd9yGF6bvmlk7muExRvGNYb8sXBwEKMoNKsbNN9P8EefuQekE4Q==} + engines: {node: '>=20'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + papaparse@5.6.0: + resolution: {integrity: sha512-N2vuNQAYGK1/4vs6HJX86+VYU6OkiSTgdJz3JQfTk1y51cFCO/U8gnaeTF4iNE4r57Tt0sV47dUua1/19pxO6Q==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pg-cloudflare@1.4.0: + resolution: {integrity: sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==} + + pg-connection-string@2.14.0: + resolution: {integrity: sha512-XwWDGcLRGCXAR8F/AM5bG7Q+A3Wm2s6QeEjlOKZLlH3UYcguiqCWKyWXVag5TLTIjR7oOJUY8kcADaZgWPyLeg==} + + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-pool@3.14.0: + resolution: {integrity: sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==} + peerDependencies: + pg: '>=8.0' + + pg-protocol@1.16.0: + resolution: {integrity: sha512-sILXutLVjCLjcDuOmvhX5e2Z4cS5qG/6Bu3VkpFwdf/633ElGLpEh9bgmuI5I4sqKqkifQiGyiCcx1HdtrK7tg==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + + pg@8.23.0: + resolution: {integrity: sha512-Ip2EQCngowJLGOfCwkFhPXU7/ljlhn6Rxlmy4XYfL2Y+vyRM59+8uR2xqRWKdYmbXmxCFOAmKxBuSUCdF34qLg==} + engines: {node: '>= 16.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + + pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + + phaser4-rex-plugins@4.2.0: + resolution: {integrity: sha512-Wc5pSEbz3iv5FJpxG/TXHzDK5YM3skHjeL4CHjd92h0BvsOCvJ6dxP30M7xVWDsLLPKKLQOFE9L2b8Dez5nwAw==} + + phaser@4.2.1: + resolution: {integrity: sha512-WUNwCPJpdjvZiuT6SgCfYVW8Qw/3j0jJ4ws7P2QkhFLFu74sbGuyHJcbFueGkY/AYO4Pi47bNQXn1OCJeLX//w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + + postcss@8.5.28: + resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} + engines: {node: ^10 || ^12 || >=14} + + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-bytea@1.0.1: + resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} + engines: {node: '>=0.10.0'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-plugin-organize-imports@4.3.0: + resolution: {integrity: sha512-FxFz0qFhyBsGdIsb697f/EkvHzi5SZOhWAjxcx2dLt+Q532bAlhswcXGYB1yzjZ69kW8UoadFBw7TyNwlq96Iw==} + peerDependencies: + prettier: '>=2.0' + typescript: '>=2.9' + vue-tsc: ^2.1.0 || 3 + peerDependenciesMeta: + vue-tsc: + optional: true + + prettier-plugin-packagejson@3.0.2: + resolution: {integrity: sha512-kmoj3hEynXwoHDo8ZhmWAIjRBoQWCDUVackiWfSDWdgD0rS3LGB61T9zoVbume/cotYdCoadUh4sqViAmXvpBQ==} + peerDependencies: + prettier: ^3 + peerDependenciesMeta: + prettier: + optional: true + + prettier-plugin-tailwindcss@0.8.1: + resolution: {integrity: sha512-iaFMYqDsE4ffdDkn5qup0j5f2aCEBFZrdrZnvu9QKTlWx/iGPeQ4HHu7b7fCPMxeo9nwQBiOAh2nSypdFYWJkw==} + engines: {node: '>=20.19'} + peerDependencies: + '@ianvs/prettier-plugin-sort-imports': '*' + '@prettier/plugin-hermes': '*' + '@prettier/plugin-oxc': '*' + '@prettier/plugin-pug': '*' + '@shopify/prettier-plugin-liquid': '*' + '@trivago/prettier-plugin-sort-imports': '*' + '@zackad/prettier-plugin-twig': '*' + prettier: ^3.0 + prettier-plugin-astro: '*' + prettier-plugin-css-order: '*' + prettier-plugin-jsdoc: '*' + prettier-plugin-marko: '*' + prettier-plugin-multiline-arrays: '*' + prettier-plugin-organize-attributes: '*' + prettier-plugin-organize-imports: '*' + prettier-plugin-sort-imports: '*' + prettier-plugin-svelte: '*' + peerDependenciesMeta: + '@ianvs/prettier-plugin-sort-imports': + optional: true + '@prettier/plugin-hermes': + optional: true + '@prettier/plugin-oxc': + optional: true + '@prettier/plugin-pug': + optional: true + '@shopify/prettier-plugin-liquid': + optional: true + '@trivago/prettier-plugin-sort-imports': + optional: true + '@zackad/prettier-plugin-twig': + optional: true + prettier-plugin-astro: + optional: true + prettier-plugin-css-order: + optional: true + prettier-plugin-jsdoc: + optional: true + prettier-plugin-marko: + optional: true + prettier-plugin-multiline-arrays: + optional: true + prettier-plugin-organize-attributes: + optional: true + prettier-plugin-organize-imports: + optional: true + prettier-plugin-sort-imports: + optional: true + prettier-plugin-svelte: + optional: true + + prettier@3.9.6: + resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==} + engines: {node: '>=14'} + hasBin: true + + proxy-addr@2.0.8: + resolution: {integrity: sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==} + engines: {node: '>= 0.10'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + qified@0.10.1: + resolution: {integrity: sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==} + engines: {node: '>=20'} + + qs@6.16.0: + resolution: {integrity: sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==} + engines: {node: '>=0.6'} + + quansync@1.0.0: + resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} + + range-parser@1.3.0: + resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + + react-dom@19.3.0: + resolution: {integrity: sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==} + peerDependencies: + react: ^19.3.0 + + react-router@8.3.1: + resolution: {integrity: sha512-TEOpiO2g0TJHEOJeRVv4amUFun9v1npCKszvcquNvzETUtJ8udV86ah5eFoHT7g26bsBvT6EiIhqulR8eDF++A==} + engines: {node: '>=22.22.0'} + peerDependencies: + react: '>=19.2.7' + react-dom: '>=19.2.7' + peerDependenciesMeta: + react-dom: + optional: true + + react-zoom-pan-pinch@4.2.0: + resolution: {integrity: sha512-QSw1dvr6QGv5zHCUY7SEIY7EWRDbx1y6plylrWf2Nko9KOMW1MEoPC6oJ+Y/qOSeQjhvbkMV0m5bFF7pBxCH4A==} + engines: {node: '>=8', npm: '>=5'} + peerDependencies: + react: '*' + react-dom: '*' + + react@19.3.0: + resolution: {integrity: sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==} + engines: {node: '>=0.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + readdirp@5.1.1: + resolution: {integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==} + engines: {node: '>= 20.19.0'} + + redis-errors@1.2.0: + resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} + engines: {node: '>=4'} + + redis-parser@3.0.0: + resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} + engines: {node: '>=4'} + + reflect-metadata@0.2.2: + resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + + rolldown@1.2.8: + resolution: {integrity: sha512-Z67nTmhZe7anqnM/EjI392w5i/ANUinjip7QYsOyN37oayduxt3ksdX0hf5OOamkAd53BiIHfbfSzfUmzKFQqQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + rou3@0.9.2: + resolution: {integrity: sha512-3SOzvaAg8rkHrXtRjpCvCvbyO5to9oOO27Z/XqHEYXfMRVSw/qMIVdmaOk9W2lcRLtR6dlqTjo9hDeJk70QBYQ==} + + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + scheduler@0.28.0: + resolution: {integrity: sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==} + + schema-utils@3.3.0: + resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} + engines: {node: '>= 10.13.0'} + + schema-utils@4.5.0: + resolution: {integrity: sha512-zJlMCZ0cAR5p/Y4oVpRoqioDMJcGxaXRrQ/4rP4WyR84vc5z/DolXdbvXeDpTwbtocDFr2rhPqHPErDCUtz2kA==} + engines: {node: '>= 10.13.0'} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + + set-cookie-parser@3.1.2: + resolution: {integrity: sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw==} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + sharp@0.35.4: + resolution: {integrity: sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==} + engines: {node: '>=20.9.0'} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + skin-tone@2.0.0: + resolution: {integrity: sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==} + engines: {node: '>=8'} + + sort-object-keys@2.1.0: + resolution: {integrity: sha512-SOiEnthkJKPv2L6ec6HMwhUcN0/lppkeYuN1x63PbyPRrgSPIuBJCiYxYyvWRTtjMlOi14vQUCGUJqS6PLVm8g==} + + sort-package-json@3.6.1: + resolution: {integrity: sha512-Chgejw1+10p2D0U2tB7au1lHtz6TkFnxmvZktyBCRyV0GgmF6nl1IxXxAsPtJVsUyg/fo+BfCMAVVFUVRkAHrQ==} + engines: {node: '>=20'} + hasBin: true + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + source-map@0.8.0: + resolution: {integrity: sha512-d8EqvL+k/SOXCreS/SUzg2ciyHqBBLcN/yuRjFsbvVhHTE2pgei7oAhmPM7kWFbkX6OSMQfUq4KbkF3au9lhYQ==} + engines: {node: '>= 12'} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + sponsorkit@18.0.0: + resolution: {integrity: sha512-9Jr+n4SjU8Qtldq8i0UlwwqBEAVy5JFbIQNXRUIs1N+LPTCnX2/of5oyHYftCLi3KxAODVMmyEdWqkJMTagR6w==} + engines: {node: ^22.18.0 || ^24.11.0 || >=26.0.0} + hasBin: true + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + standard-as-callback@2.1.0: + resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + std-env@4.1.0: + resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + + stdin-discarder@0.3.2: + resolution: {integrity: sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==} + engines: {node: '>=18'} + + streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@8.3.0: + resolution: {integrity: sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==} + engines: {node: '>=20'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strtok3@10.3.5: + resolution: {integrity: sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==} + engines: {node: '>=18'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + tailwind-merge@3.7.0: + resolution: {integrity: sha512-XPPUyAc+cvspz3lHTcR/QgPfW2A0lv/xQNIjX3HGhLR+Nq2lHaLq5MtTesHn8GUr3W3DguT2KT5x3NVgRtYwmA==} + + tailwindcss@4.3.3: + resolution: {integrity: sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==} + + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} + + terser@5.51.2: + resolution: {integrity: sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q==} + engines: {node: '>=10'} + hasBin: true + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.1.2: + resolution: {integrity: sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinyqueue@3.0.0: + resolution: {integrity: sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==} + + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + token-types@6.1.2: + resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==} + engines: {node: '>=14.16'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + tsconfig-paths-webpack-plugin@4.2.0: + resolution: {integrity: sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==} + engines: {node: '>=10.13.0'} + + tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + + turbo@2.11.6: + resolution: {integrity: sha512-UppAr5XEHLfVdVp75nd+tzPp/wVEwS7Ken9aYjMlBnHqXynKn9IcQHd6GDOXBxDViSjpS+SzJcOF86v9Hf8GfQ==} + hasBin: true + + tweetnacl@1.0.3: + resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} + + typescript-eslint@8.70.0: + resolution: {integrity: sha512-P/W5cz70/cQAuKfY3xwQMWWTV7BvJ0mAQmi+9mBcsVPaBUpd6Ohpa+fECv9rBFrQcig86jAiNBFNWUqnTjr4pw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.4: + resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} + + uid@2.0.2: + resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==} + engines: {node: '>=8'} + + uint8array-extras@1.6.0: + resolution: {integrity: sha512-8iAasVS4wUx0gPLjH8Xtz2PeDzTSiy8QiLGu2DT3X5GU+egFEQhpnbtkehbAwjvDcxIERmCYcysiODFmE3Ud0Q==} + engines: {node: '>=18'} + + unconfig-core@7.5.0: + resolution: {integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==} + + unconfig@7.5.0: + resolution: {integrity: sha512-oi8Qy2JV4D3UQ0PsopR28CzdQ3S/5A1zwsUwp/rosSbfhJ5z7b90bIyTwi/F7hCLD4SGcZVjDzd4XoUQcEanvA==} + + undici-types@8.9.0: + resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==} + + undici@7.29.0: + resolution: {integrity: sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==} + engines: {node: '>=20.18.1'} + + unicode-emoji-modifier-base@1.0.0: + resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==} + engines: {node: '>=4'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + update-browserslist-db@1.3.3: + resolution: {integrity: sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vite@8.3.0: + resolution: {integrity: sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.7.1 + 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 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.1.11: + resolution: {integrity: sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.11 + '@vitest/browser-preview': 4.1.11 + '@vitest/browser-webdriverio': 4.1.11 + '@vitest/coverage-istanbul': 4.1.11 + '@vitest/coverage-v8': 4.1.11 + '@vitest/ui': 4.1.11 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + watchpack@2.5.2: + resolution: {integrity: sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==} + engines: {node: '>=10.13.0'} + + webfontloader@1.6.28: + resolution: {integrity: sha512-Egb0oFEga6f+nSgasH3E0M405Pzn6y3/9tOVanv/DLfa1YBIgcv90L18YyWnvXkRbIM17v5Kv6IT2N6g1x5tvQ==} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + webpack-node-externals@3.0.0: + resolution: {integrity: sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==} + engines: {node: '>=6'} + + webpack-sources@3.6.0: + resolution: {integrity: sha512-EIMmPVvNI0CYXyRPp34F2Qk7W37/BZVZIofS6LAUnrNjCVahS+hO4mxwFJDjQjODTGZDtVzLYoN2+1dYkhk9qA==} + engines: {node: '>=10.13.0'} + + webpack@5.111.1: + resolution: {integrity: sha512-cNypaz0RP+S4cvQVi1M/3bRUkvNP0xZpL0TjFx+c6jg64VbxD+2weWDgY9UnjRI6dhZgtaj8DU76GGFcJ79xPQ==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.22.0: + resolution: {integrity: sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + yocto-queue@1.2.2: + resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} + engines: {node: '>=12.20'} + + yoctocolors@2.2.0: + resolution: {integrity: sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==} + engines: {node: '>=18'} + + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + zod@4.6.4: + resolution: {integrity: sha512-AXSD6hvGdvRjajG/l1cC+d6IrhH+sjmPKtYeQdJIK8MFJl3LyClzS+o/YsVC+zQZPupAaeH5skwwm8YqYH7BqA==} + +snapshots: + + '@ai-sdk/alibaba@2.0.45(zod@4.6.4)': + dependencies: + '@ai-sdk/provider': 4.0.14 + '@ai-sdk/provider-utils': 5.0.40(zod@4.6.4) + zod: 4.6.4 + + '@ai-sdk/deepseek@3.0.44(zod@4.6.4)': + dependencies: + '@ai-sdk/provider': 4.0.14 + '@ai-sdk/provider-utils': 5.0.40(zod@4.6.4) + zod: 4.6.4 + + '@ai-sdk/gateway@4.0.80(zod@4.6.4)': + dependencies: + '@ai-sdk/provider': 4.0.14 + '@ai-sdk/provider-utils': 5.0.40(zod@4.6.4) + '@vercel/oidc': 3.2.0 + zod: 4.6.4 + + '@ai-sdk/provider-utils@5.0.40(zod@4.6.4)': + dependencies: + '@ai-sdk/provider': 4.0.14 + '@standard-schema/spec': 1.1.0 + '@workflow/serde': 4.1.0 + eventsource-parser: 3.0.8 + undici: 7.29.0 + zod: 4.6.4 + + '@ai-sdk/provider@4.0.14': + dependencies: + json-schema: 0.4.0 + + '@angular-devkit/core@22.2.0(chokidar@5.0.0)': + dependencies: + ajv: 8.20.0 + ajv-formats: 3.0.1 + jsonc-parser: 3.3.1 + picomatch: 4.0.7 + rxjs: 7.8.2 + source-map: 0.8.0 + optionalDependencies: + chokidar: 5.0.0 + + '@angular-devkit/schematics-cli@22.2.0(@types/node@26.5.1)(chokidar@5.0.0)': + dependencies: + '@angular-devkit/core': 22.2.0(chokidar@5.0.0) + '@angular-devkit/schematics': 22.2.0(chokidar@5.0.0) + '@inquirer/prompts': 8.7.2(@types/node@26.5.1) + transitivePeerDependencies: + - '@types/node' + - chokidar + + '@angular-devkit/schematics@22.2.0(chokidar@5.0.0)': + dependencies: + '@angular-devkit/core': 22.2.0(chokidar@5.0.0) + jsonc-parser: 3.3.1 + magic-string: 1.4.1 + ora: 9.4.1 + rxjs: 7.8.2 + transitivePeerDependencies: + - chokidar + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.8': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.9 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + + '@babel/parser@7.29.8': + dependencies: + '@babel/types': 7.29.8 + + '@babel/runtime@7.29.7': {} + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + + '@babel/traverse@7.29.8': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.8': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@better-auth/core@1.7.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.4.0(zod@4.6.4))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0)': + dependencies: + '@better-auth/utils': 0.4.2 + '@better-fetch/fetch': 1.3.1 + '@opentelemetry/semantic-conventions': 1.43.0 + '@standard-schema/spec': 1.1.0 + better-call: 1.4.0(zod@4.6.4) + jose: 6.2.3 + kysely: 0.28.17 + nanostores: 1.3.0 + zod: 4.6.4 + + '@better-auth/drizzle-adapter@1.7.4(@better-auth/core@1.7.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.4.0(zod@4.6.4))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.2)(drizzle-orm@0.45.2(@types/pg@8.23.1)(kysely@0.28.17)(pg@8.23.0))': + dependencies: + '@better-auth/core': 1.7.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.4.0(zod@4.6.4))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) + '@better-auth/utils': 0.4.2 + optionalDependencies: + drizzle-orm: 0.45.2(@types/pg@8.23.1)(kysely@0.28.17)(pg@8.23.0) + + '@better-auth/i18n@1.7.4(@better-auth/core@1.7.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.4.0(zod@4.6.4))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(better-auth@1.7.4(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@types/pg@8.23.1)(kysely@0.28.17)(pg@8.23.0))(pg@8.23.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vitest@4.1.11(@types/node@26.5.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.51.2)(tsx@4.21.0)))': + dependencies: + '@better-auth/core': 1.7.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.4.0(zod@4.6.4))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) + better-auth: 1.7.4(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@types/pg@8.23.1)(kysely@0.28.17)(pg@8.23.0))(pg@8.23.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vitest@4.1.11(@types/node@26.5.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.51.2)(tsx@4.21.0)) + + '@better-auth/kysely-adapter@1.7.4(@better-auth/core@1.7.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.4.0(zod@4.6.4))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.2)(kysely@0.28.17)': + dependencies: + '@better-auth/core': 1.7.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.4.0(zod@4.6.4))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) + '@better-auth/utils': 0.4.2 + optionalDependencies: + kysely: 0.28.17 + + '@better-auth/memory-adapter@1.7.4(@better-auth/core@1.7.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.4.0(zod@4.6.4))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.2)': + dependencies: + '@better-auth/core': 1.7.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.4.0(zod@4.6.4))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) + '@better-auth/utils': 0.4.2 + + '@better-auth/mongo-adapter@1.7.4(@better-auth/core@1.7.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.4.0(zod@4.6.4))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.2)': + dependencies: + '@better-auth/core': 1.7.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.4.0(zod@4.6.4))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) + '@better-auth/utils': 0.4.2 + + '@better-auth/prisma-adapter@1.7.4(@better-auth/core@1.7.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.4.0(zod@4.6.4))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.2)': + dependencies: + '@better-auth/core': 1.7.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.4.0(zod@4.6.4))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) + '@better-auth/utils': 0.4.2 + + '@better-auth/telemetry@1.7.4(@better-auth/core@1.7.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.4.0(zod@4.6.4))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)': + dependencies: + '@better-auth/core': 1.7.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.4.0(zod@4.6.4))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) + '@better-auth/utils': 0.4.2 + '@better-fetch/fetch': 1.3.1 + + '@better-auth/utils@0.4.2': + dependencies: + '@noble/hashes': 2.2.0 + + '@better-auth/utils@0.5.0': + dependencies: + '@noble/hashes': 2.2.0 + + '@better-fetch/fetch@1.3.1': {} + + '@borewit/text-codec@0.2.2': {} + + '@cacheable/memory@2.2.0': + dependencies: + '@cacheable/utils': 2.5.0 + '@keyv/bigmap': 1.3.1(keyv@5.6.0) + hookified: 1.15.1 + keyv: 5.6.0 + + '@cacheable/utils@2.5.0': + dependencies: + hashery: 1.5.1 + keyv: 5.6.0 + + '@colors/colors@1.5.0': + optional: true + + '@drizzle-team/brocli@0.10.2': {} + + '@emnapi/core@1.11.3': + dependencies: + '@emnapi/wasi-threads': 1.2.3 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.11.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild-kit/core-utils@3.3.2': + dependencies: + esbuild: 0.18.20 + source-map-support: 0.5.21 + + '@esbuild-kit/esm-loader@2.6.5': + dependencies: + '@esbuild-kit/core-utils': 3.3.2 + get-tsconfig: 4.14.0 + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.18.20': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm@0.18.20': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-x64@0.18.20': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.18.20': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.18.20': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.18.20': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.18.20': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.18.20': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm@0.18.20': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.18.20': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.18.20': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.18.20': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.18.20': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.18.20': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.18.20': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-x64@0.18.20': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.18.20': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.18.20': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.18.20': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.18.20': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.18.20': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-x64@0.18.20': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@10.10.0(jiti@2.7.0))': + dependencies: + eslint: 10.10.0(jiti@2.7.0) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.23.5': + dependencies: + '@eslint/object-schema': 3.0.5 + debug: 4.4.3 + minimatch: 10.2.6 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.7.0': + dependencies: + '@eslint/core': 1.2.1 + + '@eslint/core@1.2.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/js@10.0.1(eslint@10.10.0(jiti@2.7.0))': + optionalDependencies: + eslint: 10.10.0(jiti@2.7.0) + + '@eslint/object-schema@3.0.5': {} + + '@eslint/plugin-kit@0.7.3': + dependencies: + '@eslint/core': 1.2.1 + levn: 0.4.1 + + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@img/colour@1.1.0': {} + + '@img/sharp-darwin-arm64@0.35.4': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.3.3 + optional: true + + '@img/sharp-darwin-x64@0.35.4': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.3.3 + optional: true + + '@img/sharp-freebsd-wasm32@0.35.4': + dependencies: + '@img/sharp-wasm32': 0.35.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.3.3': + optional: true + + '@img/sharp-libvips-darwin-x64@1.3.3': + optional: true + + '@img/sharp-libvips-linux-arm64@1.3.3': + optional: true + + '@img/sharp-libvips-linux-arm@1.3.3': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.3.3': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.3.3': + optional: true + + '@img/sharp-libvips-linux-s390x@1.3.3': + optional: true + + '@img/sharp-libvips-linux-x64@1.3.3': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.3.3': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.3.3': + optional: true + + '@img/sharp-linux-arm64@0.35.4': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.3.3 + optional: true + + '@img/sharp-linux-arm@0.35.4': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.3.3 + optional: true + + '@img/sharp-linux-ppc64@0.35.4': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.3.3 + optional: true + + '@img/sharp-linux-riscv64@0.35.4': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.3.3 + optional: true + + '@img/sharp-linux-s390x@0.35.4': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.3.3 + optional: true + + '@img/sharp-linux-x64@0.35.4': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.3.3 + optional: true + + '@img/sharp-linuxmusl-arm64@0.35.4': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.3.3 + optional: true + + '@img/sharp-linuxmusl-x64@0.35.4': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.3.3 + optional: true + + '@img/sharp-wasm32@0.35.4': + dependencies: + '@emnapi/runtime': 1.11.3 + optional: true + + '@img/sharp-webcontainers-wasm32@0.35.4': + dependencies: + '@img/sharp-wasm32': 0.35.4 + optional: true + + '@img/sharp-win32-arm64@0.35.4': + optional: true + + '@img/sharp-win32-ia32@0.35.4': + optional: true + + '@img/sharp-win32-x64@0.35.4': + optional: true + + '@inquirer/ansi@2.0.8': {} + + '@inquirer/checkbox@5.2.5(@types/node@26.5.1)': + dependencies: + '@inquirer/ansi': 2.0.8 + '@inquirer/core': 12.0.3(@types/node@26.5.1) + '@inquirer/figures': 2.0.9 + '@inquirer/type': 4.1.1(@types/node@26.5.1) + optionalDependencies: + '@types/node': 26.5.1 + + '@inquirer/confirm@6.3.2(@types/node@26.5.1)': + dependencies: + '@inquirer/core': 12.0.3(@types/node@26.5.1) + '@inquirer/type': 4.1.1(@types/node@26.5.1) + optionalDependencies: + '@types/node': 26.5.1 + + '@inquirer/core@12.0.3(@types/node@26.5.1)': + dependencies: + '@inquirer/ansi': 2.0.8 + '@inquirer/figures': 2.0.9 + '@inquirer/type': 4.1.1(@types/node@26.5.1) + cli-width: 4.1.0 + fast-wrap-ansi: 0.2.2 + mute-stream: 3.0.0 + signal-exit: 4.1.0 + optionalDependencies: + '@types/node': 26.5.1 + + '@inquirer/editor@5.3.3(@types/node@26.5.1)': + dependencies: + '@inquirer/core': 12.0.3(@types/node@26.5.1) + '@inquirer/external-editor': 3.0.5(@types/node@26.5.1) + '@inquirer/type': 4.1.1(@types/node@26.5.1) + optionalDependencies: + '@types/node': 26.5.1 + + '@inquirer/expand@5.1.5(@types/node@26.5.1)': + dependencies: + '@inquirer/core': 12.0.3(@types/node@26.5.1) + '@inquirer/type': 4.1.1(@types/node@26.5.1) + optionalDependencies: + '@types/node': 26.5.1 + + '@inquirer/external-editor@3.0.5(@types/node@26.5.1)': + dependencies: + chardet: 2.2.0 + iconv-lite: 0.7.3 + optionalDependencies: + '@types/node': 26.5.1 + + '@inquirer/figures@2.0.9': {} + + '@inquirer/input@5.1.6(@types/node@26.5.1)': + dependencies: + '@inquirer/core': 12.0.3(@types/node@26.5.1) + '@inquirer/type': 4.1.1(@types/node@26.5.1) + optionalDependencies: + '@types/node': 26.5.1 + + '@inquirer/number@4.2.3(@types/node@26.5.1)': + dependencies: + '@inquirer/core': 12.0.3(@types/node@26.5.1) + '@inquirer/type': 4.1.1(@types/node@26.5.1) + optionalDependencies: + '@types/node': 26.5.1 + + '@inquirer/password@5.2.2(@types/node@26.5.1)': + dependencies: + '@inquirer/ansi': 2.0.8 + '@inquirer/core': 12.0.3(@types/node@26.5.1) + '@inquirer/type': 4.1.1(@types/node@26.5.1) + optionalDependencies: + '@types/node': 26.5.1 + + '@inquirer/prompts@8.7.2(@types/node@26.5.1)': + dependencies: + '@inquirer/checkbox': 5.2.5(@types/node@26.5.1) + '@inquirer/confirm': 6.3.2(@types/node@26.5.1) + '@inquirer/editor': 5.3.3(@types/node@26.5.1) + '@inquirer/expand': 5.1.5(@types/node@26.5.1) + '@inquirer/input': 5.1.6(@types/node@26.5.1) + '@inquirer/number': 4.2.3(@types/node@26.5.1) + '@inquirer/password': 5.2.2(@types/node@26.5.1) + '@inquirer/rawlist': 5.3.5(@types/node@26.5.1) + '@inquirer/search': 4.3.3(@types/node@26.5.1) + '@inquirer/select': 5.2.5(@types/node@26.5.1) + optionalDependencies: + '@types/node': 26.5.1 + + '@inquirer/rawlist@5.3.5(@types/node@26.5.1)': + dependencies: + '@inquirer/core': 12.0.3(@types/node@26.5.1) + '@inquirer/type': 4.1.1(@types/node@26.5.1) + optionalDependencies: + '@types/node': 26.5.1 + + '@inquirer/search@4.3.3(@types/node@26.5.1)': + dependencies: + '@inquirer/core': 12.0.3(@types/node@26.5.1) + '@inquirer/figures': 2.0.9 + '@inquirer/type': 4.1.1(@types/node@26.5.1) + optionalDependencies: + '@types/node': 26.5.1 + + '@inquirer/select@5.2.5(@types/node@26.5.1)': + dependencies: + '@inquirer/ansi': 2.0.8 + '@inquirer/core': 12.0.3(@types/node@26.5.1) + '@inquirer/figures': 2.0.9 + '@inquirer/type': 4.1.1(@types/node@26.5.1) + optionalDependencies: + '@types/node': 26.5.1 + + '@inquirer/type@4.1.1(@types/node@26.5.1)': + optionalDependencies: + '@types/node': 26.5.1 + + '@ioredis/commands@1.10.0': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/source-map@0.3.11': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/sourcemap-codec@1.6.0': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.6.0 + + '@keyv/bigmap@1.3.1(keyv@5.6.0)': + dependencies: + hashery: 1.5.1 + hookified: 1.15.1 + keyv: 5.6.0 + + '@keyv/serialize@1.1.1': {} + + '@lukeed/csprng@1.1.0': {} + + '@microfleet/ioredis-lock@6.0.1(ioredis@5.11.1)': + dependencies: + ioredis: 5.11.1 + + '@module-federation/error-codes@0.22.0': {} + + '@module-federation/runtime-core@0.22.0': + dependencies: + '@module-federation/error-codes': 0.22.0 + '@module-federation/sdk': 0.22.0 + + '@module-federation/runtime-tools@0.22.0': + dependencies: + '@module-federation/runtime': 0.22.0 + '@module-federation/webpack-bundler-runtime': 0.22.0 + + '@module-federation/runtime@0.22.0': + dependencies: + '@module-federation/error-codes': 0.22.0 + '@module-federation/runtime-core': 0.22.0 + '@module-federation/sdk': 0.22.0 + + '@module-federation/sdk@0.22.0': {} + + '@module-federation/webpack-bundler-runtime@0.22.0': + dependencies: + '@module-federation/runtime': 0.22.0 + '@module-federation/sdk': 0.22.0 + + '@napi-rs/wasm-runtime@1.0.7': + dependencies: + '@emnapi/core': 1.11.3 + '@emnapi/runtime': 1.11.3 + '@tybys/wasm-util': 0.10.3 + optional: true + + '@nestjs/cli@12.0.8(@rspack/core@1.7.12)(@types/node@26.5.1)(fork-ts-checker-webpack-plugin@9.1.0(typescript@6.0.3)(webpack@5.111.1(esbuild@0.27.7)))(prettier@3.9.6)(tsconfig-paths-webpack-plugin@4.2.0)(webpack-node-externals@3.0.0)(webpack@5.111.1(esbuild@0.27.7))': + dependencies: + '@angular-devkit/core': 22.2.0(chokidar@5.0.0) + '@angular-devkit/schematics': 22.2.0(chokidar@5.0.0) + '@angular-devkit/schematics-cli': 22.2.0(@types/node@26.5.1)(chokidar@5.0.0) + '@inquirer/prompts': 8.7.2(@types/node@26.5.1) + '@nestjs/schematics': 12.0.6(chokidar@5.0.0)(prettier@3.9.6)(typescript@6.0.3) + ansis: 4.4.0 + chokidar: 5.0.0 + cli-table3: 0.6.5 + commander: 15.0.0 + jsonc-parser: 3.3.1 + minimatch: 10.2.6 + node-emoji: 2.2.0 + ora: 9.4.1 + tsconfig-paths: 4.2.0 + typescript: 6.0.3 + optionalDependencies: + '@rspack/core': 1.7.12 + fork-ts-checker-webpack-plugin: 9.1.0(typescript@6.0.3)(webpack@5.111.1(esbuild@0.27.7)) + tsconfig-paths-webpack-plugin: 4.2.0 + webpack: 5.111.1(esbuild@0.27.7) + webpack-node-externals: 3.0.0 + transitivePeerDependencies: + - '@types/node' + - prettier + + '@nestjs/common@12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2)': + dependencies: + '@standard-schema/spec': 1.1.0 + file-type: 22.1.1 + iterare: 1.2.1 + load-esm: 1.0.3 + reflect-metadata: 0.2.2 + rxjs: 7.8.2 + tslib: 2.8.1 + uid: 2.0.2 + transitivePeerDependencies: + - supports-color + + '@nestjs/config@12.0.1(@nestjs/common@12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2)': + dependencies: + '@nestjs/common': 12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@standard-schema/spec': 1.1.0 + dotenv: 18.0.3 + dotenv-expand: 13.0.0 + es-toolkit: 1.52.0 + rxjs: 7.8.2 + + '@nestjs/core@12.1.2(@nestjs/common@12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@12.1.2)(@nestjs/websockets@12.1.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + dependencies: + '@nestjs/common': 12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2) + fast-safe-stringify: 2.1.1 + iterare: 1.2.1 + path-to-regexp: 8.4.2 + reflect-metadata: 0.2.2 + rxjs: 7.8.2 + tslib: 2.8.1 + uid: 2.0.2 + optionalDependencies: + '@nestjs/platform-express': 12.1.2(@nestjs/common@12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@12.1.2) + '@nestjs/websockets': 12.1.2(@nestjs/common@12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@12.1.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + + '@nestjs/platform-express@12.1.2(@nestjs/common@12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@12.1.2)': + dependencies: + '@nestjs/common': 12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 12.1.2(@nestjs/common@12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@12.1.2)(@nestjs/websockets@12.1.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + cors: 2.8.6 + express: 5.2.1 + multer: 2.4.0 + path-to-regexp: 8.4.2 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@nestjs/platform-ws@12.1.2(@nestjs/common@12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@12.1.2)(rxjs@7.8.2)': + dependencies: + '@nestjs/common': 12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/websockets': 12.1.2(@nestjs/common@12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@12.1.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + rxjs: 7.8.2 + tslib: 2.8.1 + ws: 8.22.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@nestjs/schedule@12.0.2(@nestjs/common@12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@12.1.2)': + dependencies: + '@nestjs/common': 12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 12.1.2(@nestjs/common@12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@12.1.2)(@nestjs/websockets@12.1.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + cron: 4.4.0 + + '@nestjs/schematics@12.0.6(chokidar@5.0.0)(prettier@3.9.6)(typescript@6.0.3)': + dependencies: + '@angular-devkit/core': 22.2.0(chokidar@5.0.0) + '@angular-devkit/schematics': 22.2.0(chokidar@5.0.0) + comment-json: 5.0.0 + jsonc-parser: 3.3.1 + pluralize: 8.0.0 + typescript: 6.0.3 + optionalDependencies: + prettier: 3.9.6 + transitivePeerDependencies: + - chokidar + + '@nestjs/websockets@12.1.2(@nestjs/common@12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@12.1.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + dependencies: + '@nestjs/common': 12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 12.1.2(@nestjs/common@12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@12.1.2)(@nestjs/websockets@12.1.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + iterare: 1.2.1 + object-hash: 3.0.0 + reflect-metadata: 0.2.2 + rxjs: 7.8.2 + tslib: 2.8.1 + + '@noble/ciphers@2.2.0': {} + + '@noble/hashes@2.2.0': {} + + '@opentelemetry/semantic-conventions@1.43.0': {} + + '@oxc-project/types@0.149.0': {} + + '@quansync/fs@1.0.0': + dependencies: + quansync: 1.0.0 + + '@rolldown/binding-android-arm-eabi@1.2.8': + optional: true + + '@rolldown/binding-android-arm64@1.2.8': + optional: true + + '@rolldown/binding-darwin-arm64@1.2.8': + optional: true + + '@rolldown/binding-darwin-x64@1.2.8': + optional: true + + '@rolldown/binding-freebsd-x64@1.2.8': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.2.8': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.2.8': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.2.8': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.2.8': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.2.8': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.2.8': + optional: true + + '@rolldown/binding-linux-x64-musl@1.2.8': + optional: true + + '@rolldown/binding-openharmony-arm64@1.2.8': + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.2.8': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.2.8': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@rspack/binding-darwin-arm64@1.7.12': + optional: true + + '@rspack/binding-darwin-x64@1.7.12': + optional: true + + '@rspack/binding-linux-arm64-gnu@1.7.12': + optional: true + + '@rspack/binding-linux-arm64-musl@1.7.12': + optional: true + + '@rspack/binding-linux-x64-gnu@1.7.12': + optional: true + + '@rspack/binding-linux-x64-musl@1.7.12': + optional: true + + '@rspack/binding-wasm32-wasi@1.7.12': + dependencies: + '@napi-rs/wasm-runtime': 1.0.7 + optional: true + + '@rspack/binding-win32-arm64-msvc@1.7.12': + optional: true + + '@rspack/binding-win32-ia32-msvc@1.7.12': + optional: true + + '@rspack/binding-win32-x64-msvc@1.7.12': + optional: true + + '@rspack/binding@1.7.12': + optionalDependencies: + '@rspack/binding-darwin-arm64': 1.7.12 + '@rspack/binding-darwin-x64': 1.7.12 + '@rspack/binding-linux-arm64-gnu': 1.7.12 + '@rspack/binding-linux-arm64-musl': 1.7.12 + '@rspack/binding-linux-x64-gnu': 1.7.12 + '@rspack/binding-linux-x64-musl': 1.7.12 + '@rspack/binding-wasm32-wasi': 1.7.12 + '@rspack/binding-win32-arm64-msvc': 1.7.12 + '@rspack/binding-win32-ia32-msvc': 1.7.12 + '@rspack/binding-win32-x64-msvc': 1.7.12 + + '@rspack/core@1.7.12': + dependencies: + '@module-federation/runtime-tools': 0.22.0 + '@rspack/binding': 1.7.12 + '@rspack/lite-tapable': 1.1.0 + + '@rspack/lite-tapable@1.1.0': {} + + '@sindresorhus/is@4.6.0': {} + + '@standard-schema/spec@1.1.0': {} + + '@tailwindcss/node@4.3.3': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.26.0 + jiti: 2.7.0 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.3.3 + + '@tailwindcss/oxide-android-arm64@4.3.3': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.3.3': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.3.3': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.3.3': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.3.3': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.3.3': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.3.3': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.3.3': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.3.3': + optional: true + + '@tailwindcss/oxide@4.3.3': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.3.3 + '@tailwindcss/oxide-darwin-arm64': 4.3.3 + '@tailwindcss/oxide-darwin-x64': 4.3.3 + '@tailwindcss/oxide-freebsd-x64': 4.3.3 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.3 + '@tailwindcss/oxide-linux-arm64-gnu': 4.3.3 + '@tailwindcss/oxide-linux-arm64-musl': 4.3.3 + '@tailwindcss/oxide-linux-x64-gnu': 4.3.3 + '@tailwindcss/oxide-linux-x64-musl': 4.3.3 + '@tailwindcss/oxide-wasm32-wasi': 4.3.3 + '@tailwindcss/oxide-win32-arm64-msvc': 4.3.3 + '@tailwindcss/oxide-win32-x64-msvc': 4.3.3 + + '@tailwindcss/vite@4.3.3(vite@8.3.0(@types/node@26.5.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.51.2)(tsx@4.21.0))': + dependencies: + '@tailwindcss/node': 4.3.3 + '@tailwindcss/oxide': 4.3.3 + tailwindcss: 4.3.3 + vite: 8.3.0(@types/node@26.5.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.51.2)(tsx@4.21.0) + + '@tokenizer/inflate@0.4.1': + dependencies: + debug: 4.4.3 + token-types: 6.1.2 + transitivePeerDependencies: + - supports-color + + '@tokenizer/token@0.3.0': {} + + '@turbo/darwin-64@2.11.6': + optional: true + + '@turbo/darwin-arm64@2.11.6': + optional: true + + '@turbo/linux-64@2.11.6': + optional: true + + '@turbo/linux-arm64@2.11.6': + optional: true + + '@turbo/windows-64@2.11.6': + optional: true + + '@turbo/windows-arm64@2.11.6': + optional: true + + '@tybys/wasm-util@0.10.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 26.5.1 + + '@types/canvas-confetti@1.9.0': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/connect@3.4.38': + dependencies: + '@types/node': 26.5.1 + + '@types/cors@2.8.19': + dependencies: + '@types/node': 26.5.1 + + '@types/d3-format@3.0.4': {} + + '@types/deep-eql@4.0.2': {} + + '@types/esrecurse@4.3.1': {} + + '@types/estree@1.0.8': {} + + '@types/express-serve-static-core@5.1.3': + dependencies: + '@types/node': 26.5.1 + '@types/qs': 6.15.1 + '@types/range-parser': 1.2.7 + '@types/send': 1.2.1 + + '@types/express@5.0.6': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 5.1.3 + '@types/serve-static': 2.2.0 + + '@types/http-errors@2.0.5': {} + + '@types/json-schema@7.0.15': {} + + '@types/luxon@3.7.6': {} + + '@types/node@26.5.1': + dependencies: + undici-types: 8.9.0 + + '@types/nodemailer@8.0.1': + dependencies: + '@types/node': 26.5.1 + + '@types/pg@8.23.1': + dependencies: + '@types/node': 26.5.1 + pg-protocol: 1.16.0 + pg-types: 2.2.0 + + '@types/qs@6.15.1': {} + + '@types/range-parser@1.2.7': {} + + '@types/react-dom@19.3.0(@types/react@19.3.0)': + dependencies: + '@types/react': 19.3.0 + + '@types/react@19.3.0': + dependencies: + csstype: 3.2.3 + + '@types/send@1.2.1': + dependencies: + '@types/node': 26.5.1 + + '@types/serve-static@2.2.0': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 26.5.1 + + '@types/ws@8.18.2': + dependencies: + '@types/node': 26.5.1 + + '@typescript-eslint/eslint-plugin@8.70.0(@typescript-eslint/parser@8.70.0(eslint@10.10.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.10.0(jiti@2.7.0))(typescript@6.0.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.70.0(eslint@10.10.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.70.0 + '@typescript-eslint/type-utils': 8.70.0(eslint@10.10.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.70.0(eslint@10.10.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.70.0 + eslint: 10.10.0(jiti@2.7.0) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.70.0(eslint@10.10.0(jiti@2.7.0))(typescript@6.0.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.70.0 + '@typescript-eslint/types': 8.70.0 + '@typescript-eslint/typescript-estree': 8.70.0(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.70.0 + debug: 4.4.3 + eslint: 10.10.0(jiti@2.7.0) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.70.0(typescript@6.0.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.70.0(typescript@6.0.3) + '@typescript-eslint/types': 8.70.0 + debug: 4.4.3 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.70.0': + dependencies: + '@typescript-eslint/types': 8.70.0 + '@typescript-eslint/visitor-keys': 8.70.0 + + '@typescript-eslint/tsconfig-utils@8.70.0(typescript@6.0.3)': + dependencies: + typescript: 6.0.3 + + '@typescript-eslint/type-utils@8.70.0(eslint@10.10.0(jiti@2.7.0))(typescript@6.0.3)': + dependencies: + '@typescript-eslint/types': 8.70.0 + '@typescript-eslint/typescript-estree': 8.70.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.70.0(eslint@10.10.0(jiti@2.7.0))(typescript@6.0.3) + debug: 4.4.3 + eslint: 10.10.0(jiti@2.7.0) + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.70.0': {} + + '@typescript-eslint/typescript-estree@8.70.0(typescript@6.0.3)': + dependencies: + '@typescript-eslint/project-service': 8.70.0(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.70.0(typescript@6.0.3) + '@typescript-eslint/types': 8.70.0 + '@typescript-eslint/visitor-keys': 8.70.0 + debug: 4.4.3 + minimatch: 10.2.6 + semver: 7.8.5 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.70.0(eslint@10.10.0(jiti@2.7.0))(typescript@6.0.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.10.0(jiti@2.7.0)) + '@typescript-eslint/scope-manager': 8.70.0 + '@typescript-eslint/types': 8.70.0 + '@typescript-eslint/typescript-estree': 8.70.0(typescript@6.0.3) + eslint: 10.10.0(jiti@2.7.0) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.70.0': + dependencies: + '@typescript-eslint/types': 8.70.0 + eslint-visitor-keys: 5.0.1 + + '@vercel/oidc@3.2.0': {} + + '@vitejs/plugin-react@6.1.1(vite@8.3.0(@types/node@26.5.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.51.2)(tsx@4.21.0))': + dependencies: + '@rolldown/pluginutils': 1.0.1 + vite: 8.3.0(@types/node@26.5.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.51.2)(tsx@4.21.0) + + '@vitest/expect@4.1.11': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.11 + '@vitest/utils': 4.1.11 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.11(vite@8.3.0(@types/node@26.5.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.51.2)(tsx@4.21.0))': + dependencies: + '@vitest/spy': 4.1.11 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.3.0(@types/node@26.5.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.51.2)(tsx@4.21.0) + + '@vitest/pretty-format@4.1.11': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.11': + dependencies: + '@vitest/utils': 4.1.11 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.11': + dependencies: + '@vitest/pretty-format': 4.1.11 + '@vitest/utils': 4.1.11 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.11': {} + + '@vitest/utils@4.1.11': + dependencies: + '@vitest/pretty-format': 4.1.11 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + + '@webassemblyjs/ast@1.14.1': + dependencies: + '@webassemblyjs/helper-numbers': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + + '@webassemblyjs/floating-point-hex-parser@1.13.2': {} + + '@webassemblyjs/helper-api-error@1.13.2': {} + + '@webassemblyjs/helper-buffer@1.14.1': {} + + '@webassemblyjs/helper-numbers@1.13.2': + dependencies: + '@webassemblyjs/floating-point-hex-parser': 1.13.2 + '@webassemblyjs/helper-api-error': 1.13.2 + '@xtuc/long': 4.2.2 + + '@webassemblyjs/helper-wasm-bytecode@1.13.2': {} + + '@webassemblyjs/helper-wasm-section@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/wasm-gen': 1.14.1 + + '@webassemblyjs/ieee754@1.13.2': + dependencies: + '@xtuc/ieee754': 1.2.0 + + '@webassemblyjs/leb128@1.13.2': + dependencies: + '@xtuc/long': 4.2.2 + + '@webassemblyjs/utf8@1.13.2': {} + + '@webassemblyjs/wasm-edit@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/helper-wasm-section': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-opt': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + '@webassemblyjs/wast-printer': 1.14.1 + + '@webassemblyjs/wasm-gen@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 + + '@webassemblyjs/wasm-opt@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + + '@webassemblyjs/wasm-parser@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-api-error': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 + + '@webassemblyjs/wast-printer@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@xtuc/long': 4.2.2 + + '@workflow/serde@4.1.0': {} + + '@xtuc/ieee754@1.2.0': {} + + '@xtuc/long@4.2.2': {} + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.1.0 + + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + ai@7.0.99(zod@4.6.4): + dependencies: + '@ai-sdk/gateway': 4.0.80(zod@4.6.4) + '@ai-sdk/provider': 4.0.14 + '@ai-sdk/provider-utils': 5.0.40(zod@4.6.4) + zod: 4.6.4 + + ajv-formats@3.0.1: + dependencies: + ajv: 8.20.0 + + ajv-keywords@3.5.2(ajv@6.15.0): + dependencies: + ajv: 6.15.0 + + ajv-keywords@5.1.0(ajv@8.20.0): + dependencies: + ajv: 8.20.0 + fast-deep-equal: 3.1.3 + + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.8 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + altcha-lib@2.5.0(@nestjs/common@12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@12.1.2)(@nestjs/platform-express@12.1.2)(cors@2.8.6)(express@5.2.1): + optionalDependencies: + '@nestjs/common': 12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 12.1.2(@nestjs/common@12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@12.1.2)(@nestjs/websockets@12.1.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/platform-express': 12.1.2(@nestjs/common@12.1.2(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@12.1.2) + cors: 2.8.6 + express: 5.2.1 + + altcha@3.2.2: + dependencies: + hash-wasm: 4.12.0 + + ansi-regex@5.0.1: {} + + ansi-regex@6.4.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansis@4.4.0: {} + + append-field@1.0.0: {} + + argparse@2.0.1: {} + + array-timsort@1.0.3: {} + + assertion-error@2.0.1: {} + + balanced-match@1.0.2: {} + + balanced-match@4.0.4: {} + + baseline-browser-mapping@2.11.23: {} + + better-auth@1.7.4(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@types/pg@8.23.1)(kysely@0.28.17)(pg@8.23.0))(pg@8.23.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vitest@4.1.11(@types/node@26.5.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.51.2)(tsx@4.21.0)): + dependencies: + '@better-auth/core': 1.7.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.4.0(zod@4.6.4))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) + '@better-auth/drizzle-adapter': 1.7.4(@better-auth/core@1.7.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.4.0(zod@4.6.4))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.2)(drizzle-orm@0.45.2(@types/pg@8.23.1)(kysely@0.28.17)(pg@8.23.0)) + '@better-auth/kysely-adapter': 1.7.4(@better-auth/core@1.7.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.4.0(zod@4.6.4))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.2)(kysely@0.28.17) + '@better-auth/memory-adapter': 1.7.4(@better-auth/core@1.7.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.4.0(zod@4.6.4))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.2) + '@better-auth/mongo-adapter': 1.7.4(@better-auth/core@1.7.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.4.0(zod@4.6.4))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.2) + '@better-auth/prisma-adapter': 1.7.4(@better-auth/core@1.7.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.4.0(zod@4.6.4))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.2) + '@better-auth/telemetry': 1.7.4(@better-auth/core@1.7.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.4.0(zod@4.6.4))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1) + '@better-auth/utils': 0.4.2 + '@better-fetch/fetch': 1.3.1 + '@noble/ciphers': 2.2.0 + '@noble/hashes': 2.2.0 + better-call: 1.4.0(zod@4.6.4) + defu: 6.1.7 + jose: 6.2.3 + kysely: 0.28.17 + nanostores: 1.3.0 + zod: 4.6.4 + optionalDependencies: + drizzle-kit: 0.31.10 + drizzle-orm: 0.45.2(@types/pg@8.23.1)(kysely@0.28.17)(pg@8.23.0) + pg: 8.23.0 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + vitest: 4.1.11(@types/node@26.5.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.51.2)(tsx@4.21.0) + transitivePeerDependencies: + - '@cloudflare/workers-types' + - '@opentelemetry/api' + + better-call@1.4.0(zod@4.6.4): + dependencies: + '@better-auth/utils': 0.5.0 + '@better-fetch/fetch': 1.3.1 + rou3: 0.9.2 + set-cookie-parser: 3.1.2 + optionalDependencies: + zod: 4.6.4 + + body-parser@2.3.0: + dependencies: + bytes: 3.1.2 + content-type: 2.1.0 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + on-finished: 2.4.1 + qs: 6.16.0 + raw-body: 3.0.2 + type-is: 2.1.0 + transitivePeerDependencies: + - supports-color + + brace-expansion@1.1.21: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@5.0.9: + dependencies: + balanced-match: 4.0.4 + + browserslist@4.28.9: + dependencies: + baseline-browser-mapping: 2.11.23 + caniuse-lite: 1.0.30001810 + electron-to-chromium: 1.5.427 + node-releases: 2.0.55 + update-browserslist-db: 1.3.3(browserslist@4.28.9) + + buffer-from@1.1.2: {} + + busboy@1.6.0: + dependencies: + streamsearch: 1.1.0 + + bytes@3.1.2: {} + + cac@7.0.0: {} + + cacheable@2.5.0: + dependencies: + '@cacheable/memory': 2.2.0 + '@cacheable/utils': 2.5.0 + hookified: 1.15.1 + keyv: 5.6.0 + qified: 0.10.1 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001810: {} + + canvas-confetti@1.9.4: {} + + chai@6.2.2: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.6.2: {} + + char-regex@1.0.2: {} + + chardet@2.2.0: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + chokidar@5.0.0: + dependencies: + readdirp: 5.1.1 + + chrome-trace-event@1.0.4: {} + + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-spinners@3.4.0: {} + + cli-table3@0.6.5: + dependencies: + string-width: 4.2.3 + optionalDependencies: + '@colors/colors': 1.5.0 + + cli-width@4.1.0: {} + + clsx@2.1.1: {} + + cluster-key-slot@1.1.1: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@15.0.0: {} + + commander@2.20.3: {} + + comment-json@5.0.0: + dependencies: + array-timsort: 1.0.3 + esprima: 4.0.1 + + concat-map@0.0.1: {} + + consola@3.4.2: {} + + content-disposition@1.1.0: {} + + content-type@1.0.5: {} + + content-type@2.1.0: {} + + convert-source-map@2.0.0: {} + + cookie-es@3.1.1: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cosmiconfig@8.3.6(typescript@6.0.3): + dependencies: + import-fresh: 3.3.1 + js-yaml: 4.3.2 + parse-json: 5.2.0 + path-type: 4.0.0 + optionalDependencies: + typescript: 6.0.3 + + cron@4.4.0: + dependencies: + '@types/luxon': 3.7.6 + luxon: 3.7.2 + + cross-fetch@4.1.0: + dependencies: + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + csstype@3.2.3: {} + + d3-format@3.1.2: {} + + d3-hierarchy@3.1.2: {} + + dagre@0.8.5: + dependencies: + graphlib: 2.1.8 + lodash: 4.18.1 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + deepmerge@4.3.1: {} + + defu@6.1.7: {} + + denque@2.1.0: {} + + depd@2.0.0: {} + + destr@2.0.5: {} + + detect-indent@7.0.2: {} + + detect-libc@2.1.2: {} + + detect-newline@4.0.1: {} + + dotenv-expand@13.0.0: + dependencies: + dotenv: 17.4.2 + + dotenv@17.4.2: {} + + dotenv@18.0.3: {} + + drizzle-kit@0.31.10: + dependencies: + '@drizzle-team/brocli': 0.10.2 + '@esbuild-kit/esm-loader': 2.6.5 + esbuild: 0.25.12 + tsx: 4.21.0 + + drizzle-orm@0.45.2(@types/pg@8.23.1)(kysely@0.28.17)(pg@8.23.0): + optionalDependencies: + '@types/pg': 8.23.1 + kysely: 0.28.17 + pg: 8.23.0 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + + electron-to-chromium@1.5.427: {} + + emoji-regex@8.0.0: {} + + emojilib@2.4.0: {} + + encodeurl@2.0.0: {} + + enhanced-resolve@5.26.0: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.3 + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@2.1.0: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + es-toolkit@1.52.0: {} + + esbuild@0.18.20: + optionalDependencies: + '@esbuild/android-arm': 0.18.20 + '@esbuild/android-arm64': 0.18.20 + '@esbuild/android-x64': 0.18.20 + '@esbuild/darwin-arm64': 0.18.20 + '@esbuild/darwin-x64': 0.18.20 + '@esbuild/freebsd-arm64': 0.18.20 + '@esbuild/freebsd-x64': 0.18.20 + '@esbuild/linux-arm': 0.18.20 + '@esbuild/linux-arm64': 0.18.20 + '@esbuild/linux-ia32': 0.18.20 + '@esbuild/linux-loong64': 0.18.20 + '@esbuild/linux-mips64el': 0.18.20 + '@esbuild/linux-ppc64': 0.18.20 + '@esbuild/linux-riscv64': 0.18.20 + '@esbuild/linux-s390x': 0.18.20 + '@esbuild/linux-x64': 0.18.20 + '@esbuild/netbsd-x64': 0.18.20 + '@esbuild/openbsd-x64': 0.18.20 + '@esbuild/sunos-x64': 0.18.20 + '@esbuild/win32-arm64': 0.18.20 + '@esbuild/win32-ia32': 0.18.20 + '@esbuild/win32-x64': 0.18.20 + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@4.0.0: {} + + eslint-plugin-react-hooks@7.1.1(eslint@10.10.0(jiti@2.7.0)): + dependencies: + '@babel/core': 7.29.7 + '@babel/parser': 7.29.8 + eslint: 10.10.0(jiti@2.7.0) + hermes-parser: 0.25.1 + zod: 4.6.4 + zod-validation-error: 4.0.2(zod@4.6.4) + transitivePeerDependencies: + - supports-color + + eslint-plugin-react-refresh@0.5.6(eslint@10.10.0(jiti@2.7.0)): + dependencies: + eslint: 10.10.0(jiti@2.7.0) + + eslint-scope@9.1.2: + dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.8 + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@10.10.0(jiti@2.7.0): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.10.0(jiti@2.7.0)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.23.5 + '@eslint/config-helpers': 0.7.0 + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.3 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.15.0 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 11.1.5 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + minimatch: 10.2.6 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.7.0 + transitivePeerDependencies: + - supports-color + + espree@11.2.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 5.0.1 + + esprima@4.0.1: {} + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + esutils@2.0.3: {} + + etag@1.8.1: {} + + eventemitter3@3.1.2: {} + + eventemitter3@5.0.4: {} + + events@3.3.0: {} + + eventsource-parser@3.0.8: {} + + expect-type@1.3.0: {} + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.3.0 + content-disposition: 1.1.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.8 + qs: 6.16.0 + range-parser: 1.3.0 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.1.0 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-safe-stringify@2.1.1: {} + + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + + fast-uri@3.1.8: {} + + fast-wrap-ansi@0.2.2: + dependencies: + fast-string-width: 3.0.2 + + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 + + file-entry-cache@11.1.5: + dependencies: + flat-cache: 6.1.23 + + file-type@22.1.1: + dependencies: + '@tokenizer/inflate': 0.4.1 + strtok3: 10.3.5 + token-types: 6.1.2 + uint8array-extras: 1.6.0 + transitivePeerDependencies: + - supports-color + + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@6.1.23: + dependencies: + cacheable: 2.5.0 + flatted: 3.4.2 + hookified: 1.15.1 + + flatted@3.4.2: {} + + fork-ts-checker-webpack-plugin@9.1.0(typescript@6.0.3)(webpack@5.111.1(esbuild@0.27.7)): + dependencies: + '@babel/code-frame': 7.29.7 + chalk: 4.1.2 + chokidar: 4.0.3 + cosmiconfig: 8.3.6(typescript@6.0.3) + deepmerge: 4.3.1 + fs-extra: 10.1.0 + memfs: 3.5.3 + minimatch: 3.1.5 + node-abort-controller: 3.1.1 + schema-utils: 3.3.0 + semver: 7.8.5 + tapable: 2.3.3 + typescript: 6.0.3 + webpack: 5.111.1(esbuild@0.27.7) + + forwarded@0.2.0: {} + + fresh@2.0.0: {} + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.1 + universalify: 2.0.1 + + fs-monkey@1.1.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + get-east-asian-width@1.7.0: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + get-tsconfig@4.14.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + git-hooks-list@4.2.1: {} + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@17.12.0: {} + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + graphlib@2.1.8: + dependencies: + lodash: 4.18.1 + + graphology-types@0.24.8: {} + + graphology@0.25.4(graphology-types@0.24.8): + dependencies: + events: 3.3.0 + graphology-types: 0.24.8 + obliterator: 2.0.5 + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + hash-wasm@4.12.0: {} + + hashery@1.5.1: + dependencies: + hookified: 1.15.1 + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + + hookified@1.15.1: {} + + hookified@2.2.0: {} + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + i18next-http-backend@3.0.6: + dependencies: + cross-fetch: 4.1.0 + transitivePeerDependencies: + - encoding + + i18next@22.5.1: + dependencies: + '@babel/runtime': 7.29.7 + + iconv-lite@0.7.3: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + inherits@2.0.4: {} + + ioredis@5.11.1: + dependencies: + '@ioredis/commands': 1.10.0 + cluster-key-slot: 1.1.1 + debug: 4.4.3 + denque: 2.1.0 + redis-errors: 1.2.0 + redis-parser: 3.0.0 + standard-as-callback: 2.1.0 + transitivePeerDependencies: + - supports-color + + ipaddr.js@1.9.1: {} + + is-arrayish@0.2.1: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-interactive@2.0.0: {} + + is-plain-obj@4.1.0: {} + + is-promise@4.0.0: {} + + is-unicode-supported@2.1.0: {} + + isexe@2.0.0: {} + + iterare@1.2.1: {} + + jest-worker@27.5.1: + dependencies: + '@types/node': 26.5.1 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jiti@2.7.0: {} + + jose@6.2.3: {} + + js-tokens@4.0.0: {} + + js-yaml@4.3.2: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-schema@0.4.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + jsonc-parser@3.3.1: {} + + jsonfile@6.2.1: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + keyv@5.6.0: + dependencies: + '@keyv/serialize': 1.1.1 + + kysely@0.28.17: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-android-arm64@1.33.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.33.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.33.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.33.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.33.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.33.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.33.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.33.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.33.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.33.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + lightningcss@1.33.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + + lines-and-columns@1.2.4: {} + + load-esm@1.0.3: {} + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash@4.18.1: {} + + log-symbols@7.0.1: + dependencies: + is-unicode-supported: 2.1.0 + yoctocolors: 2.2.0 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + luxon@3.7.2: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + + magic-string@1.4.1: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + + math-intrinsics@1.1.0: {} + + media-typer@0.3.0: {} + + media-typer@1.1.1: {} + + memfs@3.5.3: + dependencies: + fs-monkey: 1.1.0 + + merge-descriptors@2.0.0: {} + + merge-stream@2.0.0: {} + + mime-db@1.52.0: {} + + mime-db@1.54.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + mimic-function@5.0.1: {} + + minimatch@10.2.6: + dependencies: + brace-expansion: 5.0.9 + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.21 + + minimist@1.2.8: {} + + minimizer-webpack-plugin@5.12.0(esbuild@0.27.7)(webpack@5.111.1(esbuild@0.27.7)): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + jest-worker: 27.5.1 + schema-utils: 4.5.0 + terser: 5.51.2 + webpack: 5.111.1(esbuild@0.27.7) + optionalDependencies: + esbuild: 0.27.7 + + ms@2.1.3: {} + + multer@2.4.0: + dependencies: + append-field: 1.0.0 + busboy: 1.6.0 + type-is: 1.6.18 + + mustache@4.2.0: {} + + mute-stream@3.0.0: {} + + nanoid@3.3.19: {} + + nanostores@1.3.0: {} + + nats@2.29.3: + dependencies: + nkeys.js: 1.1.0 + + natural-compare@1.4.0: {} + + negotiator@1.1.0: + dependencies: + content-type: 2.1.0 + + nkeys.js@1.1.0: + dependencies: + tweetnacl: 1.0.3 + + node-abort-controller@3.1.1: {} + + node-emoji@2.2.0: + dependencies: + '@sindresorhus/is': 4.6.0 + char-regex: 1.0.2 + emojilib: 2.4.0 + skin-tone: 2.0.0 + + node-fetch-native@1.6.7: {} + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-releases@2.0.55: {} + + nodemailer@9.1.1: {} + + normalize-url@9.0.1: {} + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + object-inspect@1.13.4: {} + + obliterator@2.0.5: {} + + obug@2.1.1: {} + + ofetch@1.5.1: + dependencies: + destr: 2.0.5 + node-fetch-native: 1.6.7 + ufo: 1.6.4 + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + ora@9.4.1: + dependencies: + chalk: 5.6.2 + cli-cursor: 5.0.0 + cli-spinners: 3.4.0 + is-interactive: 2.0.0 + is-unicode-supported: 2.1.0 + log-symbols: 7.0.1 + stdin-discarder: 0.3.2 + string-width: 8.3.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-limit@7.3.1: + dependencies: + yocto-queue: 1.2.2 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + papaparse@5.6.0: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.29.7 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parseurl@1.3.3: {} + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-to-regexp@8.4.2: {} + + path-type@4.0.0: {} + + pathe@2.0.3: {} + + pg-cloudflare@1.4.0: + optional: true + + pg-connection-string@2.14.0: {} + + pg-int8@1.0.1: {} + + pg-pool@3.14.0(pg@8.23.0): + dependencies: + pg: 8.23.0 + + pg-protocol@1.16.0: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.1 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + + pg@8.23.0: + dependencies: + pg-connection-string: 2.14.0 + pg-pool: 3.14.0(pg@8.23.0) + pg-protocol: 1.16.0 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.4.0 + + pgpass@1.0.5: + dependencies: + split2: 4.2.0 + + phaser4-rex-plugins@4.2.0(graphology-types@0.24.8): + dependencies: + dagre: 0.8.5 + eventemitter3: 3.1.2 + graphology: 0.25.4(graphology-types@0.24.8) + i18next: 22.5.1 + i18next-http-backend: 3.0.6 + js-yaml: 4.3.2 + mustache: 4.2.0 + papaparse: 5.6.0 + tinyqueue: 3.0.0 + webfontloader: 1.6.28 + transitivePeerDependencies: + - encoding + - graphology-types + + phaser@4.2.1: + dependencies: + eventemitter3: 5.0.4 + + picocolors@1.1.1: {} + + picomatch@4.0.7: {} + + pluralize@8.0.0: {} + + postcss@8.5.28: + dependencies: + nanoid: 3.3.19 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + postgres-array@2.0.0: {} + + postgres-bytea@1.0.1: {} + + postgres-date@1.0.7: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + + prelude-ls@1.2.1: {} + + prettier-plugin-organize-imports@4.3.0(prettier@3.9.6)(typescript@6.0.3): + dependencies: + prettier: 3.9.6 + typescript: 6.0.3 + + prettier-plugin-packagejson@3.0.2(prettier@3.9.6): + dependencies: + sort-package-json: 3.6.1 + optionalDependencies: + prettier: 3.9.6 + + prettier-plugin-tailwindcss@0.8.1(prettier-plugin-organize-imports@4.3.0(prettier@3.9.6)(typescript@6.0.3))(prettier@3.9.6): + dependencies: + prettier: 3.9.6 + optionalDependencies: + prettier-plugin-organize-imports: 4.3.0(prettier@3.9.6)(typescript@6.0.3) + + prettier@3.9.6: {} + + proxy-addr@2.0.8: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + punycode@2.3.1: {} + + qified@0.10.1: + dependencies: + hookified: 2.2.0 + + qs@6.16.0: + dependencies: + es-define-property: 1.0.1 + side-channel: 1.1.1 + + quansync@1.0.0: {} + + range-parser@1.3.0: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + unpipe: 1.0.0 + + react-dom@19.3.0(react@19.3.0): + dependencies: + react: 19.3.0 + scheduler: 0.28.0 + + react-router@8.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0): + dependencies: + cookie-es: 3.1.1 + react: 19.3.0 + optionalDependencies: + react-dom: 19.3.0(react@19.3.0) + + react-zoom-pan-pinch@4.2.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0): + dependencies: + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + + react@19.3.0: {} + + readdirp@4.1.2: {} + + readdirp@5.1.1: {} + + redis-errors@1.2.0: {} + + redis-parser@3.0.0: + dependencies: + redis-errors: 1.2.0 + + reflect-metadata@0.2.2: {} + + require-from-string@2.0.2: {} + + resolve-from@4.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + rolldown@1.2.8: + dependencies: + '@oxc-project/types': 0.149.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm-eabi': 1.2.8 + '@rolldown/binding-android-arm64': 1.2.8 + '@rolldown/binding-darwin-arm64': 1.2.8 + '@rolldown/binding-darwin-x64': 1.2.8 + '@rolldown/binding-freebsd-x64': 1.2.8 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.8 + '@rolldown/binding-linux-arm64-gnu': 1.2.8 + '@rolldown/binding-linux-arm64-musl': 1.2.8 + '@rolldown/binding-linux-ppc64-gnu': 1.2.8 + '@rolldown/binding-linux-s390x-gnu': 1.2.8 + '@rolldown/binding-linux-x64-gnu': 1.2.8 + '@rolldown/binding-linux-x64-musl': 1.2.8 + '@rolldown/binding-openharmony-arm64': 1.2.8 + '@rolldown/binding-win32-arm64-msvc': 1.2.8 + '@rolldown/binding-win32-x64-msvc': 1.2.8 + + rou3@0.9.2: {} + + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + + safer-buffer@2.1.2: {} + + scheduler@0.28.0: {} + + schema-utils@3.3.0: + dependencies: + '@types/json-schema': 7.0.15 + ajv: 6.15.0 + ajv-keywords: 3.5.2(ajv@6.15.0) + + schema-utils@4.5.0: + dependencies: + '@types/json-schema': 7.0.15 + ajv: 8.20.0 + ajv-formats: 3.0.1 + ajv-keywords: 5.1.0(ajv@8.20.0) + + semver@6.3.1: {} + + semver@7.8.5: {} + + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.3.0 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + set-cookie-parser@3.1.2: {} + + setprototypeof@1.2.0: {} + + sharp@0.35.4(@types/node@26.5.1): + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.8.5 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.35.4 + '@img/sharp-darwin-x64': 0.35.4 + '@img/sharp-freebsd-wasm32': 0.35.4 + '@img/sharp-libvips-darwin-arm64': 1.3.3 + '@img/sharp-libvips-darwin-x64': 1.3.3 + '@img/sharp-libvips-linux-arm': 1.3.3 + '@img/sharp-libvips-linux-arm64': 1.3.3 + '@img/sharp-libvips-linux-ppc64': 1.3.3 + '@img/sharp-libvips-linux-riscv64': 1.3.3 + '@img/sharp-libvips-linux-s390x': 1.3.3 + '@img/sharp-libvips-linux-x64': 1.3.3 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.3 + '@img/sharp-libvips-linuxmusl-x64': 1.3.3 + '@img/sharp-linux-arm': 0.35.4 + '@img/sharp-linux-arm64': 0.35.4 + '@img/sharp-linux-ppc64': 0.35.4 + '@img/sharp-linux-riscv64': 0.35.4 + '@img/sharp-linux-s390x': 0.35.4 + '@img/sharp-linux-x64': 0.35.4 + '@img/sharp-linuxmusl-arm64': 0.35.4 + '@img/sharp-linuxmusl-x64': 0.35.4 + '@img/sharp-webcontainers-wasm32': 0.35.4 + '@img/sharp-win32-arm64': 0.35.4 + '@img/sharp-win32-ia32': 0.35.4 + '@img/sharp-win32-x64': 0.35.4 + '@types/node': 26.5.1 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + siginfo@2.0.0: {} + + signal-exit@4.1.0: {} + + skin-tone@2.0.0: + dependencies: + unicode-emoji-modifier-base: 1.0.0 + + sort-object-keys@2.1.0: {} + + sort-package-json@3.6.1: + dependencies: + detect-indent: 7.0.2 + detect-newline: 4.0.1 + git-hooks-list: 4.2.1 + is-plain-obj: 4.1.0 + semver: 7.8.5 + sort-object-keys: 2.1.0 + tinyglobby: 0.2.17 + + source-map-js@1.2.1: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + source-map@0.8.0: {} + + split2@4.2.0: {} + + sponsorkit@18.0.0(@types/node@26.5.1): + dependencies: + ansis: 4.4.0 + cac: 7.0.0 + consola: 3.4.2 + d3-hierarchy: 3.1.2 + normalize-url: 9.0.1 + ofetch: 1.5.1 + p-limit: 7.3.1 + sharp: 0.35.4(@types/node@26.5.1) + unconfig: 7.5.0 + transitivePeerDependencies: + - '@types/node' + + stackback@0.0.2: {} + + standard-as-callback@2.1.0: {} + + statuses@2.0.2: {} + + std-env@4.1.0: {} + + stdin-discarder@0.3.2: {} + + streamsearch@1.1.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@8.3.0: + dependencies: + get-east-asian-width: 1.7.0 + strip-ansi: 7.2.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.4.0 + + strip-bom@3.0.0: {} + + strtok3@10.3.5: + dependencies: + '@tokenizer/token': 0.3.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + tailwind-merge@3.7.0: {} + + tailwindcss@4.3.3: {} + + tapable@2.3.3: {} + + terser@5.51.2: + dependencies: + '@jridgewell/source-map': 0.3.11 + acorn: 8.16.0 + commander: 2.20.3 + source-map-support: 0.5.21 + + tinybench@2.9.0: {} + + tinyexec@1.1.2: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + + tinyqueue@3.0.0: {} + + tinyrainbow@3.1.0: {} + + toidentifier@1.0.1: {} + + token-types@6.1.2: + dependencies: + '@borewit/text-codec': 0.2.2 + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 + + tr46@0.0.3: {} + + ts-api-utils@2.5.0(typescript@6.0.3): + dependencies: + typescript: 6.0.3 + + tsconfig-paths-webpack-plugin@4.2.0: + dependencies: + chalk: 4.1.2 + enhanced-resolve: 5.26.0 + tapable: 2.3.3 + tsconfig-paths: 4.2.0 + + tsconfig-paths@4.2.0: + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@2.8.1: {} + + tsx@4.21.0: + dependencies: + esbuild: 0.27.7 + get-tsconfig: 4.14.0 + optionalDependencies: + fsevents: 2.3.3 + + turbo@2.11.6: + optionalDependencies: + '@turbo/darwin-64': 2.11.6 + '@turbo/darwin-arm64': 2.11.6 + '@turbo/linux-64': 2.11.6 + '@turbo/linux-arm64': 2.11.6 + '@turbo/windows-64': 2.11.6 + '@turbo/windows-arm64': 2.11.6 + + tweetnacl@1.0.3: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + type-is@2.1.0: + dependencies: + content-type: 2.1.0 + media-typer: 1.1.1 + mime-types: 3.0.2 + + typescript-eslint@8.70.0(eslint@10.10.0(jiti@2.7.0))(typescript@6.0.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.70.0(@typescript-eslint/parser@8.70.0(eslint@10.10.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.10.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.70.0(eslint@10.10.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.70.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.70.0(eslint@10.10.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.10.0(jiti@2.7.0) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + typescript@6.0.3: {} + + ufo@1.6.4: {} + + uid@2.0.2: + dependencies: + '@lukeed/csprng': 1.1.0 + + uint8array-extras@1.6.0: {} + + unconfig-core@7.5.0: + dependencies: + '@quansync/fs': 1.0.0 + quansync: 1.0.0 + + unconfig@7.5.0: + dependencies: + '@quansync/fs': 1.0.0 + defu: 6.1.7 + jiti: 2.7.0 + quansync: 1.0.0 + unconfig-core: 7.5.0 + + undici-types@8.9.0: {} + + undici@7.29.0: {} + + unicode-emoji-modifier-base@1.0.0: {} + + universalify@2.0.1: {} + + unpipe@1.0.0: {} + + update-browserslist-db@1.3.3(browserslist@4.28.9): + dependencies: + browserslist: 4.28.9 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + vary@1.1.2: {} + + vite@8.3.0(@types/node@26.5.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.51.2)(tsx@4.21.0): + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.28 + rolldown: 1.2.8 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 26.5.1 + esbuild: 0.27.7 + fsevents: 2.3.3 + jiti: 2.7.0 + terser: 5.51.2 + tsx: 4.21.0 + + vitest@4.1.11(@types/node@26.5.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.51.2)(tsx@4.21.0): + dependencies: + '@vitest/expect': 4.1.11 + '@vitest/mocker': 4.1.11(vite@8.3.0(@types/node@26.5.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.51.2)(tsx@4.21.0)) + '@vitest/pretty-format': 4.1.11 + '@vitest/runner': 4.1.11 + '@vitest/snapshot': 4.1.11 + '@vitest/spy': 4.1.11 + '@vitest/utils': 4.1.11 + es-module-lexer: 2.1.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.7 + std-env: 4.1.0 + tinybench: 2.9.0 + tinyexec: 1.1.2 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.0 + vite: 8.3.0(@types/node@26.5.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.51.2)(tsx@4.21.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 26.5.1 + transitivePeerDependencies: + - '@vitejs/devtools' + - esbuild + - jiti + - less + - msw + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + + watchpack@2.5.2: + dependencies: + graceful-fs: 4.2.11 + + webfontloader@1.6.28: {} + + webidl-conversions@3.0.1: {} + + webpack-node-externals@3.0.0: {} + + webpack-sources@3.6.0: {} + + webpack@5.111.1(esbuild@0.27.7): + dependencies: + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + browserslist: 4.28.9 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.26.0 + es-module-lexer: 2.1.0 + events: 3.3.0 + graceful-fs: 4.2.11 + mime-db: 1.54.0 + minimizer-webpack-plugin: 5.12.0(esbuild@0.27.7)(webpack@5.111.1(esbuild@0.27.7)) + schema-utils: 4.5.0 + tapable: 2.3.3 + watchpack: 2.5.2 + webpack-sources: 3.6.0 + transitivePeerDependencies: + - '@minify-html/node' + - '@napi-rs/image' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso + - esbuild + - html-minifier-terser + - imagemin + - lightningcss + - postcss + - sharp + - svgo + - uglify-js + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + word-wrap@1.2.5: {} + + wrappy@1.0.2: {} + + ws@8.22.0: {} + + xtend@4.0.2: {} + + yallist@3.1.1: {} + + yocto-queue@0.1.0: {} + + yocto-queue@1.2.2: {} + + yoctocolors@2.2.0: {} + + zod-validation-error@4.0.2(zod@4.6.4): + dependencies: + zod: 4.6.4 + + zod@4.6.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 00000000..b36d520d --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,8 @@ +packages: + - apps/* + - packages/* + +injectWorkspacePackages: true +autoInstallPeers: false +onlyBuiltDependencies: + - esbuild diff --git a/scripts/docker-compose.production.yml b/scripts/docker-compose.production.yml index 0fda34a5..4a78e3b3 100644 --- a/scripts/docker-compose.production.yml +++ b/scripts/docker-compose.production.yml @@ -3,6 +3,7 @@ name: ${COMPOSE_PROJECT_NAME:-daoyou} x-app-common: &app-common image: ${APP_IMAGE:-swkzymlyy/daoyou-app:latest} restart: unless-stopped + stop_grace_period: 75s env_file: - ${ENV_FILE:-/root/daoyou/.env.production} environment: @@ -15,7 +16,7 @@ x-app-common: &app-common test: [ "CMD", - "bun", + "node", "-e", "fetch('http://127.0.0.1:3000/api/health-check').then((res) => process.exit(res.ok ? 0 : 1)).catch(() => process.exit(1))", ] diff --git a/scripts/render-sponsors.ts b/scripts/render-sponsors.ts index 8227b47c..15f8808b 100644 --- a/scripts/render-sponsors.ts +++ b/scripts/render-sponsors.ts @@ -1,3 +1,4 @@ +import { readFile, writeFile } from 'node:fs/promises'; import { resolve } from 'node:path'; type SponsorTier = { @@ -30,7 +31,7 @@ type SponsorData = { }; }; -const repositoryRoot = resolve(import.meta.dir, '..'); +const repositoryRoot = resolve(import.meta.dirname, '..'); const dataPath = resolve(repositoryRoot, 'docs/sponsors.json'); const outputPath = resolve(repositoryRoot, 'SPONSORS.md'); const checkOnly = process.argv.includes('--check'); @@ -256,7 +257,7 @@ function renderSponsors(data: SponsorData): string { '', '感谢每一位帮助《万界道友》持续维护与成长的道友。新的赞助统一通过爱发电进行,既有历史记录继续保留。', '', - '> 本页自动生成,请勿直接编辑。同步爱发电运行 `bun run sponsors:sync`;仅更新历史名单时运行 `bun run sponsors:render`。', + '> 本页自动生成,请勿直接编辑。同步爱发电运行 `pnpm run sponsors:sync`;仅更新历史名单时运行 `pnpm run sponsors:render`。', '', `最后更新:${data.updatedAt}`, '', @@ -308,18 +309,18 @@ function renderSponsors(data: SponsorData): string { return lines.join('\n'); } -const rawData = await Bun.file(dataPath).json(); +const rawData = JSON.parse(await readFile(dataPath, 'utf8')); const rendered = renderSponsors(parseSponsorData(rawData)); if (checkOnly) { - const current = await Bun.file(outputPath).text(); + const current = await readFile(outputPath, 'utf8'); if (current !== rendered) { fail( - 'SPONSORS.md 与 docs/sponsors.json 不同步,请运行 bun run sponsors:render', + 'SPONSORS.md 与 docs/sponsors.json 不同步,请运行 pnpm run sponsors:render', ); } console.log('赞助人数据和 SPONSORS.md 已同步。'); } else { - await Bun.write(outputPath, rendered); + await writeFile(outputPath, rendered, 'utf8'); console.log('已更新 SPONSORS.md。'); } diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index 72e6ff63..00000000 --- a/src/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { websocket } from 'hono/bun'; -import app from './server/app'; -import { registerInternalCronJobs } from './server/lib/jobs/internalCronScheduler'; -import { - registerMessageInfrastructure, - shutdownMessageInfrastructure, -} from './server/lib/mq/domainEventRegistry'; - -await registerMessageInfrastructure(); -registerInternalCronJobs({ enabled: import.meta.env.PROD }); - -let shuttingDown = false; -async function shutdown(signal: NodeJS.Signals) { - if (shuttingDown) return; - shuttingDown = true; - console.info('[runtime] graceful shutdown started', { signal }); - await shutdownMessageInfrastructure(); - process.exit(0); -} - -process.once('SIGTERM', () => void shutdown('SIGTERM')); -process.once('SIGINT', () => void shutdown('SIGINT')); - -export default { - hostname: process.env.HOST, - port: Number(process.env.PORT ?? 3000), - fetch(request: Request, server: unknown) { - return app.fetch(request, { server }); - }, - websocket: { - ...websocket, - backpressureLimit: 1_048_576, - closeOnBackpressureLimit: true, - }, -}; diff --git a/src/server/app.ts b/src/server/app.ts deleted file mode 100644 index 1467d977..00000000 --- a/src/server/app.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { handleAuthRequest } from '@server/lib/auth/hono'; -import { apiIpRateLimit } from '@server/lib/hono/apiIpRateLimit'; -import { jsonError, redisLockErrorResponse } from '@server/lib/hono/middleware'; -import { requestLogger } from '@server/lib/hono/requestLogger'; -import type { AppEnv } from '@server/lib/hono/types'; -import { runWithContext } from '@server/lib/http/context'; -import { apiCorsOptions } from '@server/lib/http/cors'; -import { unsafeRequestOriginGuard } from '@server/lib/http/originGuard'; -import apiRouter from '@server/routes/api'; -import internalRouter from '@server/routes/internal'; -import { LlmByokConfigSchema } from '@shared/config/llm'; -import { Hono } from 'hono'; -import { cors } from 'hono/cors'; - -const app = new Hono(); - -app.use('/api/*', requestLogger()); -app.use('/internal/*', requestLogger()); - -app.use('*', async (context, next) => runWithContext(context, next)); - -app.use('/api/*', cors(apiCorsOptions)); -app.use('/api/*', unsafeRequestOriginGuard()); - -app.use('*', async (context, next) => { - const provider = context.req.header('x-llm-provider'); - const apiKey = context.req.header('x-llm-api-key'); - const model = context.req.header('x-llm-model'); - - if (provider !== undefined || apiKey !== undefined || model !== undefined) { - const parsed = LlmByokConfigSchema.safeParse({ provider, apiKey, model }); - if (!parsed.success) { - return context.json( - { - success: false, - error: 'LLM 配置不完整或格式无效', - }, - 400, - ); - } - context.set('llmConfig', parsed.data); - } - - await next(); -}); - -app.use('/api/*', apiIpRateLimit()); -app.all('/api/auth/*', handleAuthRequest); -app.use('/api/*', jsonError()); -app.use('/internal/*', jsonError()); - -app.route('/api', apiRouter); -app.route('/internal', internalRouter); - -app.notFound((c) => { - const path = c.req.path; - if ( - path === '/api' || - path.startsWith('/api/') || - path === '/internal' || - path.startsWith('/internal/') - ) { - return c.json({ success: false, error: '接口不存在' }, 404); - } - return c.redirect('https://client.daoyou.org'); -}); - -app.onError((error, c) => { - const lockErrorResponse = redisLockErrorResponse(error); - if (lockErrorResponse) { - return lockErrorResponse; - } - console.error('Unhandled Hono error:', error); - return c.json( - { - success: false, - error: '服务器内部错误', - }, - 500, - ); -}); - -export default app; diff --git a/src/server/lib/auth/types.ts b/src/server/lib/auth/types.ts deleted file mode 100644 index d9d01585..00000000 --- a/src/server/lib/auth/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type AuthUser = { - id: string; - email: string; - name: string; -}; diff --git a/src/server/lib/hono/apiIpRateLimit.ts b/src/server/lib/hono/apiIpRateLimit.ts deleted file mode 100644 index 303e46cc..00000000 --- a/src/server/lib/hono/apiIpRateLimit.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { getRequestIp } from '@server/lib/http/requestIp'; -import { - checkApiIpRateLimit, - type ApiIpRateLimitResult, -} from '@server/lib/redis/apiIpRateLimiter'; -import type { AppEnv } from './types'; -import type { Context, MiddlewareHandler } from 'hono'; - -function applyRateLimitHeaders( - context: Context, - result: ApiIpRateLimitResult, -) { - context.header('X-RateLimit-Limit', String(result.limit)); - context.header('X-RateLimit-Remaining', String(result.remaining)); - context.header( - 'X-RateLimit-Reset', - String(Math.ceil(result.resetAt.getTime() / 1000)), - ); -} - -function applyRateLimitHeadersToResponse( - response: Response, - result: ApiIpRateLimitResult, -) { - response.headers.set('X-RateLimit-Limit', String(result.limit)); - response.headers.set('X-RateLimit-Remaining', String(result.remaining)); - response.headers.set( - 'X-RateLimit-Reset', - String(Math.ceil(result.resetAt.getTime() / 1000)), - ); -} - -export function apiIpRateLimit(): MiddlewareHandler { - return async (context, next) => { - if (context.req.path === '/api/health-check') { - await next(); - return; - } - - const ip = getRequestIp(context); - if (!ip) { - await next(); - return; - } - - let result: ApiIpRateLimitResult; - try { - result = await checkApiIpRateLimit(ip); - } catch (error) { - console.warn('[api-rate-limit] redis check failed; allowing request', error); - await next(); - return; - } - - applyRateLimitHeaders(context, result); - - if (!result.allowed) { - const response = Response.json( - { - success: false, - error: '请求过于频繁,请稍后再试', - }, - { status: 429 }, - ); - applyRateLimitHeadersToResponse(response, result); - response.headers.set('Retry-After', String(result.retryAfterSeconds)); - context.res = response; - return; - } - - await next(); - }; -} diff --git a/src/server/lib/hono/chat.ts b/src/server/lib/hono/chat.ts deleted file mode 100644 index 5917a573..00000000 --- a/src/server/lib/hono/chat.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { MiddlewareHandler } from 'hono'; -import type { AppEnv } from './types'; - -/** Retired clients receive a clear response before cooldown or persistence. */ -export const rejectRetiredBattleShare: MiddlewareHandler = async (c, next) => { - const body = await c.req.json().catch(() => undefined); - if (body?.messageType === 'battle_showcase') { - return c.json({ success: false, error: '旧版战报分享已停用' }, 410); - } - await next(); -}; diff --git a/src/server/lib/hono/middleware.ts b/src/server/lib/hono/middleware.ts deleted file mode 100644 index 526f2d7c..00000000 --- a/src/server/lib/hono/middleware.ts +++ /dev/null @@ -1,366 +0,0 @@ -import { - isAdminIdentity, - isAdminUserId, - isLegacyAdminEmail, -} from '@server/lib/auth/adminAccess'; -import { auth } from '@server/lib/auth/auth'; -import type { AuthUser } from '@server/lib/auth/types'; -import { getExecutor } from '@server/lib/drizzle/db'; -import { cultivators } from '@server/lib/drizzle/schema'; -import type { ActiveCultivatorRef, AppEnv } from '@server/lib/hono/types'; -import { redis } from '@server/lib/redis'; -import { CombatV6MutationLockedError } from '@server/lib/services/combat-v6/CombatV6MutationGuard'; -import { - isRedisLockContention, - LockAcquisitionError, - RedisLeaseLostError, -} from '@server/lib/redis/lock'; -import { and, eq } from 'drizzle-orm'; -import type { Context, MiddlewareHandler } from 'hono'; -import { ZodError, type ZodType } from 'zod'; - -const ACTIVE_REF_REDIS_TTL_SECONDS = 600; - -type CachedActiveCultivatorRef = ActiveCultivatorRef & { - cachedAt: string; -}; - -function activeRefCacheKey(userId: string): string { - return `active-cultivator:user:${userId}`; -} - -async function readActiveRefRedisCache( - userId: string, -): Promise { - if (!process.env.REDIS_URL) { - return null; - } - - try { - const raw = await redis.get(activeRefCacheKey(userId)); - if (!raw) { - return null; - } - - const parsed = JSON.parse(raw) as CachedActiveCultivatorRef; - if ( - parsed.userId !== userId || - !parsed.cultivatorId || - parsed.status !== 'active' - ) { - return null; - } - - const ref: ActiveCultivatorRef = { - userId: parsed.userId, - cultivatorId: parsed.cultivatorId, - status: 'active', - }; - return ref; - } catch (error) { - console.warn('[active-cultivator-ref] redis read failed', error); - return null; - } -} - -async function writeActiveRefRedisCache(ref: ActiveCultivatorRef) { - if (!process.env.REDIS_URL) { - return; - } - - try { - const payload: CachedActiveCultivatorRef = { - ...ref, - cachedAt: new Date().toISOString(), - }; - await redis.set( - activeRefCacheKey(ref.userId), - JSON.stringify(payload), - 'EX', - ACTIVE_REF_REDIS_TTL_SECONDS, - ); - } catch (error) { - console.warn('[active-cultivator-ref] redis write failed', error); - } -} - -export async function invalidateActiveCultivatorRef(userId: string) { - if (!process.env.REDIS_URL) { - return; - } - - try { - await redis.del(activeRefCacheKey(userId)); - } catch (error) { - console.warn('[active-cultivator-ref] redis invalidate failed', error); - } -} - -async function resolveActiveCultivatorRef( - user: AuthUser, -): Promise { - const redisCached = await readActiveRefRedisCache(user.id); - if (redisCached) { - return redisCached; - } - - const executor = getExecutor(); - const row = await executor.query.cultivators.findFirst({ - columns: { - id: true, - userId: true, - status: true, - }, - where: and( - eq(cultivators.userId, user.id), - eq(cultivators.status, 'active'), - ), - }); - - if (!row) { - return null; - } - - const ref: ActiveCultivatorRef = { - userId: row.userId, - cultivatorId: row.id, - status: 'active', - }; - await writeActiveRefRedisCache(ref); - return ref; -} - -function toErrorMessage(error: unknown, fallback: string): string { - if (error instanceof Error && error.message) { - return error.message; - } - - return fallback; -} - -export function applyAuthHeaders( - context: Context, - headers?: Headers | null, -) { - if (!headers) { - return; - } - - headers.forEach((value, key) => { - context.header(key, value, { - append: key.toLowerCase() === 'set-cookie', - }); - }); -} - -async function resolveUser(context: Context): Promise { - const existingUser = context.get('user'); - - if (existingUser) { - return existingUser; - } - - const session = await auth.api.getSession({ - headers: context.req.raw.headers, - returnHeaders: true, - }); - - applyAuthHeaders(context, session.headers); - - if (!session.response?.user) { - return null; - } - - return { - id: session.response.user.id, - email: session.response.user.email, - name: session.response.user.name, - }; -} - -export function isAdminEmail(email?: string | null): boolean { - return isLegacyAdminEmail(email); -} - -export function errorBody( - message: string, - status = 500, - details?: unknown, -): Response { - const body: { success: false; error: string; details?: unknown } = { - success: false, - error: message, - }; - - if (process.env.NODE_ENV === 'development' && details) { - body.details = details instanceof Error ? details.message : details; - } - - return Response.json(body, { status }); -} - -export function redisLockErrorResponse(error: unknown): Response | null { - if(error instanceof CombatV6MutationLockedError) return Response.json({success:false,code:error.code,error:error.message},{status:409}); - if (isRedisLockContention(error)) { - const response = errorBody('操作正在处理中,请稍后重试', 429); - response.headers.set('Retry-After', '1'); - return response; - } - if (error instanceof LockAcquisitionError) { - const response = errorBody('Redis 协调服务暂不可用,请稍后重试', 503); - response.headers.set('Retry-After', '1'); - return response; - } - if (error instanceof RedisLeaseLostError) { - const response = errorBody(error.message, 503); - response.headers.set('Retry-After', '1'); - return response; - } - if ( - typeof error === 'object' && - error !== null && - 'code' in error && - error.code === '55P03' - ) { - const response = errorBody('数据库事务繁忙,请稍后重试', 503); - response.headers.set('Retry-After', '1'); - return response; - } - return null; -} - -export function jsonError(): MiddlewareHandler { - return async (context, next) => { - try { - await next(); - } catch (error) { - if (error instanceof ZodError) { - context.res = Response.json( - { - success: false, - error: error.issues[0]?.message || '参数错误', - details: error.issues, - }, - { status: 400 }, - ); - return; - } - const lockErrorResponse = redisLockErrorResponse(error); - if (lockErrorResponse) { - context.res = lockErrorResponse; - return; - } - - console.error('Unhandled API error:', error); - context.res = errorBody( - toErrorMessage(error, '服务器内部错误'), - 500, - error, - ); - } - }; -} - -export function requireUser(): MiddlewareHandler { - return async (context, next) => { - const user = await resolveUser(context); - - if (!user) { - context.res = errorBody('未授权访问', 401); - return; - } - - context.set('user', user); - await next(); - }; -} - -export function requireAdmin(): MiddlewareHandler { - return async (context, next) => { - const user = await resolveUser(context); - - if (!user) { - context.res = errorBody('未授权访问', 401); - return; - } - - if (!isAdminIdentity(user)) { - context.res = errorBody('无管理员权限', 403); - return; - } - - context.set('user', user); - await next(); - }; -} - -export function requireBetterAuthAdmin(): MiddlewareHandler { - return async (context, next) => { - const user = await resolveUser(context); - - if (!user) { - context.res = errorBody('未授权访问', 401); - return; - } - - if (!isAdminUserId(user.id)) { - context.res = errorBody( - '账号管理需要在 ADMIN_USER_IDS 中配置当前管理员用户 ID', - 403, - ); - return; - } - - context.set('user', user); - await next(); - }; -} - -export function requireActiveCultivatorRef(): MiddlewareHandler { - return async (context, next) => { - const user = context.get('user') ?? (await resolveUser(context)); - - if (!user) { - context.res = errorBody('未授权访问', 401); - return; - } - - const existingRef = context.get('activeCultivatorRef'); - const ref = existingRef ?? (await resolveActiveCultivatorRef(user)); - - if (!ref) { - context.res = errorBody('当前没有活跃角色', 404); - return; - } - - context.set('user', user); - context.set('activeCultivatorRef', ref); - await next(); - }; -} - -export function validateJson(schema: TSchema) { - return (async (context: Context, next) => { - const rawBody = await context.req.json().catch(() => undefined); - const parsed = schema.parse(rawBody); - context.set('validatedJson', parsed); - await next(); - }) satisfies MiddlewareHandler; -} - -export function validateQuery(schema: TSchema) { - return (async (context: Context, next) => { - const query = context.req.query(); - const parsed = schema.parse(query); - context.set('validatedQuery', parsed); - await next(); - }) satisfies MiddlewareHandler; -} - -export function getValidatedJson(context: Context): T { - return context.get('validatedJson') as T; -} - -export function getValidatedQuery(context: Context): T { - return context.get('validatedQuery') as T; -} diff --git a/src/server/lib/hono/requestLogger.ts b/src/server/lib/hono/requestLogger.ts deleted file mode 100644 index 008441c2..00000000 --- a/src/server/lib/hono/requestLogger.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { MiddlewareHandler } from 'hono'; - -const SLOW_REQUEST_THRESHOLD_MS = 1_000; -const SKIPPED_PATHS = new Set(['/api/health-check']); - -export const requestLogger = (): MiddlewareHandler => { - return async (context, next) => { - const startedAt = performance.now(); - - await next(); - - const path = context.req.path; - if (SKIPPED_PATHS.has(path)) return; - - const durationMs = Math.round(performance.now() - startedAt); - const status = context.res.status; - if (status < 400 && durationMs < SLOW_REQUEST_THRESHOLD_MS) return; - - console.info( - `[HTTP] ${context.req.method.padEnd(7)} ${path} → ${status} (${durationMs}ms)`, - ); - }; -}; diff --git a/src/server/lib/hono/response.ts b/src/server/lib/hono/response.ts deleted file mode 100644 index 17276303..00000000 --- a/src/server/lib/hono/response.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { AppEnv } from '@server/lib/hono/types'; -import type { Context } from 'hono'; -import type { ContentfulStatusCode } from 'hono/utils/http-status'; - -export function jsonWithStatus( - context: Context, - body: T, - status: number, -) { - context.status(status as ContentfulStatusCode); - return context.json(body); -} diff --git a/src/server/lib/hono/streaming.ts b/src/server/lib/hono/streaming.ts deleted file mode 100644 index 7de6d324..00000000 --- a/src/server/lib/hono/streaming.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type { AppEnv } from '@server/lib/hono/types'; -import type { Context } from 'hono'; -import { streamSSE, type SSEStreamingApi } from 'hono/streaming'; - -export type SseEventHandler = ( - stream: SSEStreamingApi, - isAborted: () => boolean, - signal: AbortSignal, -) => Promise; - -/** - * Wraps Hono's streamSSE with a shared client-disconnect signal. - * - * The returned handler receives an isAborted() helper that flips to true when - * either the incoming request is aborted or Hono cancels the response stream. - * Callers should use it to stop streaming work that no longer has a consumer. - */ -export function streamSseEvents( - c: Context, - handler: SseEventHandler, -): Response { - const controller = new AbortController(); - let aborted = false; - const markAborted = () => { - aborted = true; - controller.abort(); - }; - - if (c.req.raw.signal.aborted) { - aborted = true; - controller.abort(); - } else { - c.req.raw.signal.addEventListener('abort', markAborted, { once: true }); - } - - return streamSSE(c, async (stream) => { - stream.onAbort(markAborted); - try { - await handler(stream, () => aborted, controller.signal); - } finally { - c.req.raw.signal.removeEventListener('abort', markAborted); - controller.abort(); - } - }); -} diff --git a/src/server/lib/hono/types.ts b/src/server/lib/hono/types.ts deleted file mode 100644 index 2a7e736e..00000000 --- a/src/server/lib/hono/types.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { AuthUser } from '@server/lib/auth/types'; -import type { LlmByokConfig } from '@shared/config/llm'; -export type ActiveCultivatorRef = { - userId: string; - cultivatorId: string; - status: 'active'; -}; - -export type AppVariables = { - user: AuthUser; - activeCultivatorRef: ActiveCultivatorRef; - llmConfig: LlmByokConfig; - validatedJson: unknown; - validatedQuery: unknown; -}; - -export type AppEnv = { - Variables: Partial; -}; diff --git a/src/server/lib/http/context.ts b/src/server/lib/http/context.ts deleted file mode 100644 index fa8bb705..00000000 --- a/src/server/lib/http/context.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { AsyncLocalStorage } from 'node:async_hooks'; -import type { Context } from 'hono'; - -const contextStore = new AsyncLocalStorage(); - -export function runWithContext(context: Context, fn: () => Promise | T) { - return contextStore.run(context, fn); -} - -export function getCurrentContext(): Context { - const context = contextStore.getStore(); - - if (!context) { - throw new Error('No active Hono request context'); - } - - return context; -} diff --git a/src/server/lib/http/originGuard.ts b/src/server/lib/http/originGuard.ts deleted file mode 100644 index a68f90dd..00000000 --- a/src/server/lib/http/originGuard.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { AppEnv } from '@server/lib/hono/types'; -import type { MiddlewareHandler } from 'hono'; -import { isAllowedPublicWebOrigin, normalizeOrigin } from './origins'; - -const UNSAFE_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE']); - -function getApiSelfOrigin() { - return normalizeOrigin(process.env.BETTER_AUTH_URL); -} - -function isAllowedWriteOrigin(origin: string | undefined | null) { - if (!origin) { - return true; - } - - const normalized = normalizeOrigin(origin); - if (!normalized) { - return false; - } - - const selfOrigin = getApiSelfOrigin(); - return normalized === selfOrigin || isAllowedPublicWebOrigin(normalized); -} - -export function unsafeRequestOriginGuard(): MiddlewareHandler { - return async (context, next) => { - if (!UNSAFE_METHODS.has(context.req.method.toUpperCase())) { - await next(); - return; - } - - const origin = context.req.header('origin'); - // Cookie-authenticated writes and browser requests must identify their source. - // Non-browser, cookie-free callers (e.g. webhooks) retain their own auth boundary. - const requiresOrigin = - context.req.header('cookie') !== undefined || - context.req.header('sec-fetch-site') !== undefined; - if ((!origin && requiresOrigin) || !isAllowedWriteOrigin(origin)) { - return context.json({ success: false, error: 'Forbidden origin' }, 403); - } - - await next(); - }; -} - -export const originGuardInternals = { - isAllowedWriteOrigin, -}; diff --git a/src/server/lib/jobs/internalCronScheduler.ts b/src/server/lib/jobs/internalCronScheduler.ts deleted file mode 100644 index a5a21058..00000000 --- a/src/server/lib/jobs/internalCronScheduler.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { publishScheduledBackgroundCommand } from '@server/lib/mq/backgroundCommandPublisher'; -import type { BackgroundCommandType } from '@shared/contracts/backgroundCommands'; - -const AUCTION_EXPIRE_SCHEDULE = '*/2 * * * *'; -// Bun.cron uses UTC for cron expressions. 16:00 UTC equals 00:00 Asia/Shanghai. -const RANK_REWARDS_SCHEDULE = '0 16 * * *'; -// Market refresh: every 5 minutes to pre-generate listings before 15-min cycle ends -const MARKET_REFRESH_SCHEDULE = '*/5 * * * *'; -const RESOURCE_REPLAY_CLEANUP_SCHEDULE = '30 18 * * *'; -const EXPIRED_DATA_CLEANUP_SCHEDULE = '45 18 * * *'; -// 17:00 UTC equals 01:00 Asia/Shanghai. -const MATERIAL_LIBRARY_DAILY_GENERATION_SCHEDULE = '0 17 * * *'; -const SPONSORSHIP_RECONCILE_SCHEDULE = '*/10 * * * *'; -const SPONSORSHIP_DEEP_RECONCILE_SCHEDULE = '15 19 * * *'; -const SPONSORSHIP_CLEANUP_SCHEDULE = '30 19 * * *'; -const SPONSORSHIP_ADMIN_DIGEST_SCHEDULE = '0 1 * * *'; - -let schedulerRegistered = false; -let scheduledTasks: Bun.CronJob[] = []; - -async function runScheduledJob( - commandType: BackgroundCommandType, -): Promise { - try { - await publishScheduledBackgroundCommand(commandType); - } catch (error) { - console.error(`[cron] publish ${commandType} failed`, error); - } -} - -export function registerInternalCronJobs( - options: { - enabled?: boolean; - } = {}, -): Bun.CronJob[] { - const { enabled = process.env.NODE_ENV === 'production' } = options; - - if (!enabled || schedulerRegistered) { - return scheduledTasks; - } - - scheduledTasks = [ - Bun.cron(AUCTION_EXPIRE_SCHEDULE, () => runScheduledJob('auction.expire')), - Bun.cron(RANK_REWARDS_SCHEDULE, () => - runScheduledJob('ranking.rewards.distribute'), - ), - Bun.cron(MARKET_REFRESH_SCHEDULE, () => runScheduledJob('market.refresh')), - Bun.cron(RESOURCE_REPLAY_CLEANUP_SCHEDULE, () => - runScheduledJob('resource-replay.cleanup'), - ), - Bun.cron(EXPIRED_DATA_CLEANUP_SCHEDULE, () => - runScheduledJob('expired-data.cleanup'), - ), - Bun.cron(MATERIAL_LIBRARY_DAILY_GENERATION_SCHEDULE, () => - runScheduledJob('material-library.generate'), - ), - Bun.cron(SPONSORSHIP_RECONCILE_SCHEDULE, () => - runScheduledJob('sponsorship.reconcile'), - ), - Bun.cron(SPONSORSHIP_DEEP_RECONCILE_SCHEDULE, () => - runScheduledJob('sponsorship.deep-reconcile'), - ), - Bun.cron(SPONSORSHIP_CLEANUP_SCHEDULE, () => - runScheduledJob('sponsorship.cleanup'), - ), - Bun.cron(SPONSORSHIP_ADMIN_DIGEST_SCHEDULE, () => - runScheduledJob('sponsorship.admin-digest'), - ), - ]; - schedulerRegistered = true; - - console.info('[cron] registered Bun cron jobs', { - auctionExpire: AUCTION_EXPIRE_SCHEDULE, - rankRewardsUtc: RANK_REWARDS_SCHEDULE, - rankRewardsLocal: '00:00 Asia/Shanghai', - marketRefresh: MARKET_REFRESH_SCHEDULE, - resourceReplayCleanupUtc: RESOURCE_REPLAY_CLEANUP_SCHEDULE, - expiredDataCleanupUtc: EXPIRED_DATA_CLEANUP_SCHEDULE, - materialLibraryDailyGenerationUtc: - MATERIAL_LIBRARY_DAILY_GENERATION_SCHEDULE, - materialLibraryDailyGenerationLocal: '01:00 Asia/Shanghai', - sponsorshipReconcile: SPONSORSHIP_RECONCILE_SCHEDULE, - sponsorshipDeepReconcileUtc: SPONSORSHIP_DEEP_RECONCILE_SCHEDULE, - sponsorshipCleanupUtc: SPONSORSHIP_CLEANUP_SCHEDULE, - sponsorshipAdminDigestUtc: SPONSORSHIP_ADMIN_DIGEST_SCHEDULE, - }); - - return scheduledTasks; -} diff --git a/src/server/lib/mq/transactionalMessageRelay.ts b/src/server/lib/mq/transactionalMessageRelay.ts deleted file mode 100644 index e51caedf..00000000 --- a/src/server/lib/mq/transactionalMessageRelay.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { listPendingTransactionalMessages } from '@server/lib/repositories/transactionalMessageRepository'; -import { publishTransactionalMessage } from './transactionalMessagePublisher'; - -const RECOVERY_INTERVAL_MS = 5_000; -const RECOVERY_BATCH_SIZE = 100; - -let recoveryTimer: ReturnType | undefined; -let recoveryRunning = false; - -async function recoverPendingMessages(): Promise { - if (recoveryRunning) return; - recoveryRunning = true; - try { - const messages = await listPendingTransactionalMessages( - RECOVERY_BATCH_SIZE, - ); - for (const message of messages) { - try { - await publishTransactionalMessage(message.id); - } catch (error) { - console.error('[transactional-message] publish failed', { - messageId: message.id, - messageKey: message.messageKey, - error, - }); - } - } - } finally { - recoveryRunning = false; - } -} - -export function startTransactionalMessageRelay(): void { - if (recoveryTimer) return; - - void recoverPendingMessages().catch((error) => { - console.error('[transactional-message] initial recovery failed', error); - }); - recoveryTimer = setInterval(() => { - void recoverPendingMessages().catch((error) => { - console.error('[transactional-message] recovery failed', error); - }); - }, RECOVERY_INTERVAL_MS); - recoveryTimer.unref(); -} - -export function stopTransactionalMessageRelay(): void { - if (!recoveryTimer) return; - clearInterval(recoveryTimer); - recoveryTimer = undefined; -} diff --git a/src/server/routes/api/account.router.ts b/src/server/routes/api/account.router.ts deleted file mode 100644 index d18ec69b..00000000 --- a/src/server/routes/api/account.router.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { auth } from '@server/lib/auth/auth'; -import { - getValidatedJson, - requireUser, - validateJson, -} from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { - AccountSetPasswordRequestSchema, - type AccountSetPasswordRequest, - type AccountSetPasswordResponse, -} from '@shared/contracts/account'; -import { Hono } from 'hono'; - -const router = new Hono(); - -router.post( - '/password', - requireUser(), - validateJson(AccountSetPasswordRequestSchema), - async (c) => { - const { newPassword } = getValidatedJson(c); - const result = await auth.api.setPassword({ - body: { newPassword }, - headers: c.req.raw.headers, - }); - - const payload: AccountSetPasswordResponse = { - success: true, - data: { - status: result.status, - }, - }; - - return c.json(payload); - }, -); - -export default router; diff --git a/src/server/routes/api/admin/announcement.router.ts b/src/server/routes/api/admin/announcement.router.ts deleted file mode 100644 index 5947a7ae..00000000 --- a/src/server/routes/api/admin/announcement.router.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { requireAdmin } from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { - getAuthPageAnnouncement, - upsertAppSetting, -} from '@server/lib/repositories/appSettingsRepository'; -import { APP_SETTING_KEYS } from '@shared/lib/constants/appSettings'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const PatchBodySchema = z.object({ - announcement: z.string(), -}); - -const router = new Hono(); - -router.get('/', requireAdmin(), async (c) => { - const announcement = await getAuthPageAnnouncement(); - - return c.json({ - announcement: announcement ?? '', - }); -}); - -router.patch('/', requireAdmin(), async (c) => { - const user = c.get('user'); - if (!user) { - return c.json({ error: '未授权访问' }, 401); - } - - const body = await c.req.json().catch(() => null); - const parsed = PatchBodySchema.safeParse(body); - - if (!parsed.success) { - return c.json({ error: '参数错误', details: parsed.error.flatten() }, 400); - } - - const announcement = parsed.data.announcement.trim(); - - await upsertAppSetting({ - key: APP_SETTING_KEYS.authPageAnnouncement, - value: announcement, - updatedBy: user.id, - }); - - return c.json({ - ok: true, - announcement, - }); -}); - -export default router; diff --git a/src/server/routes/api/admin/community-qrcode.router.ts b/src/server/routes/api/admin/community-qrcode.router.ts deleted file mode 100644 index 4cc0ca81..00000000 --- a/src/server/routes/api/admin/community-qrcode.router.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { - getAppSetting, - getResolvedCommunityQqGroupNumber, - upsertAppSetting, -} from '@server/lib/repositories/appSettingsRepository'; -import { requireAdmin } from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { - APP_SETTING_KEYS, -} from '@shared/lib/constants/appSettings'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const PatchBodySchema = z.object({ - groupNumber: z - .string() - .trim() - .regex(/^\d{5,12}$/, '请输入 5 到 12 位数字 QQ 群号'), -}); - -const router = new Hono(); - -router.get('/', requireAdmin(), async (c) => { - const resolved = await getResolvedCommunityQqGroupNumber(); - const stored = await getAppSetting(APP_SETTING_KEYS.communityQqGroupNumber); - - return c.json({ - groupNumber: resolved, - customized: Boolean(stored), - storedGroupNumber: stored, - }); -}); - -router.patch('/', requireAdmin(), async (c) => { - const user = c.get('user'); - if (!user) { - return c.json({ error: '未授权访问' }, 401); - } - - const body = await c.req.json().catch(() => null); - const parsed = PatchBodySchema.safeParse(body); - - if (!parsed.success) { - return c.json( - { error: '参数错误', details: parsed.error.flatten() }, - 400, - ); - } - - await upsertAppSetting({ - key: APP_SETTING_KEYS.communityQqGroupNumber, - value: parsed.data.groupNumber, - updatedBy: user.id, - }); - - const resolved = await getResolvedCommunityQqGroupNumber(); - return c.json({ ok: true, groupNumber: resolved }); -}); - -export default router; diff --git a/src/server/routes/api/admin/feedback.router.ts b/src/server/routes/api/admin/feedback.router.ts deleted file mode 100644 index 903256ee..00000000 --- a/src/server/routes/api/admin/feedback.router.ts +++ /dev/null @@ -1,241 +0,0 @@ -import { getExecutor } from '@server/lib/drizzle/db'; -import { cultivators } from '@server/lib/drizzle/schema'; -import { - findFeedbackById, - findFeedbacks, - updateFeedbackStatus, - type FeedbackStatus, - type FeedbackType, -} from '@server/lib/repositories/feedbackRepository'; -import { requireAdmin } from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { MailService } from '@server/lib/services/MailService'; -import { - updateSpiritStones, -} from '@server/lib/services/cultivator/CultivatorStateRepository'; -import { and, eq, inArray } from 'drizzle-orm'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const VALID_STATUSES: FeedbackStatus[] = [ - 'pending', - 'processing', - 'resolved', - 'closed', -]; - -const VALID_TYPES: FeedbackType[] = ['bug', 'feature', 'balance', 'other']; - -const STATUS_LABELS: Record = { - pending: '待处理', - processing: '处理中', - resolved: '已解决', - closed: '已关闭', -}; - -const TYPE_LABELS: Record = { - bug: 'Bug 反馈', - feature: '功能建议', - balance: '游戏平衡', - other: '其他意见', -}; - -const UpdateFeedbackStatusSchema = z.object({ - status: z.enum(['pending', 'processing', 'resolved', 'closed']), - adminMessage: z.string().trim().max(1000).optional(), -}); - -const FEEDBACK_RESOLVE_REWARD = 8000; - -function buildFeedbackStatusMailContent(params: { - feedbackType: FeedbackType; - status: FeedbackStatus; - adminMessage?: string; - feedbackContent: string; -}) { - const message = params.adminMessage?.trim(); - - const lines = [ - '你提交的反馈工单状态已更新。', - '', - `反馈类型:${TYPE_LABELS[params.feedbackType]}`, - `当前状态:${STATUS_LABELS[params.status]}`, - '', - '── 你的反馈内容 ──', - params.feedbackContent, - '──────────────', - '', - `管理员留言:${message || '感谢你的反馈,我们会持续跟进。'}`, - ]; - - if (params.status === 'resolved') { - lines.push( - '', - `奖励发放:你的问题已解决,特发放 ${FEEDBACK_RESOLVE_REWARD} 灵石作为奖励。`, - ); - } - - return lines.join('\n'); -} - -const router = new Hono(); - -router.get('/', requireAdmin(), async (c) => { - const q = getExecutor(); - const page = Number.parseInt(c.req.query('page') ?? '1', 10); - const limit = Number.parseInt(c.req.query('limit') ?? '20', 10); - const status = c.req.query('status') as FeedbackStatus | undefined; - const type = c.req.query('type') as FeedbackType | undefined; - const search = c.req.query('search')?.trim() || undefined; - - const { feedbacks, total } = await findFeedbacks({ - status: status && VALID_STATUSES.includes(status) ? status : undefined, - type: type && VALID_TYPES.includes(type) ? type : undefined, - search, - page, - limit, - }); - - const cultivatorIds = Array.from( - new Set( - feedbacks - .map((feedback) => feedback.cultivatorId) - .filter((id): id is string => Boolean(id)), - ), - ); - - const cultivatorMap = new Map< - string, - { name: string | null; realm: string | null } - >(); - - if (cultivatorIds.length > 0) { - const cultivatorRows = await q - .select({ - id: cultivators.id, - name: cultivators.name, - realm: cultivators.realm, - }) - .from(cultivators) - .where(inArray(cultivators.id, cultivatorIds)); - - for (const row of cultivatorRows) { - cultivatorMap.set(row.id, { - name: row.name, - realm: row.realm, - }); - } - } - - const enrichedFeedbacks = feedbacks.map((feedback) => { - const cultivator = feedback.cultivatorId - ? cultivatorMap.get(feedback.cultivatorId) - : null; - - return { - ...feedback, - cultivatorName: cultivator?.name ?? null, - cultivatorRealm: cultivator?.realm ?? null, - }; - }); - - return c.json({ - feedbacks: enrichedFeedbacks, - total, - page, - limit, - }); -}); - -router.patch('/:id/status', requireAdmin(), async (c) => { - const id = c.req.param('id'); - const body = await c.req.json().catch(() => null); - const parsed = UpdateFeedbackStatusSchema.safeParse(body); - - if (!parsed.success) { - return c.json( - { - success: false, - error: parsed.error.issues[0]?.message ?? '参数错误', - }, - 400, - ); - } - - const { status, adminMessage } = parsed.data; - const existing = await findFeedbackById(id); - - if (!existing) { - return c.json({ success: false, error: '反馈不存在' }, 404); - } - - const hasStatusChanged = existing.status !== status; - const updated = await updateFeedbackStatus(id, status); - - if (!updated) { - return c.json({ success: false, error: '更新失败' }, 500); - } - - let notifiedUser = false; - let rewardGranted = false; - if (hasStatusChanged) { - const q = getExecutor(); - const fallbackCultivator = existing.cultivatorId - ? null - : await q.query.cultivators.findFirst({ - where: and( - eq(cultivators.userId, existing.userId), - eq(cultivators.status, 'active'), - ), - columns: { id: true }, - }); - - const recipientCultivatorId = - existing.cultivatorId ?? fallbackCultivator?.id; - - if (recipientCultivatorId) { - // 发放奖励:仅当状态变为 resolved 时固定发放 8000 灵石 - if (status === 'resolved') { - try { - await updateSpiritStones( - existing.userId, - recipientCultivatorId, - FEEDBACK_RESOLVE_REWARD, - ); - rewardGranted = true; - } catch (err) { - console.warn( - `Failed to grant feedback reward for feedback ${id}: ${err instanceof Error ? err.message : String(err)}`, - ); - } - } - - await MailService.sendNewRewardMail( - recipientCultivatorId, - '反馈工单状态更新', - buildFeedbackStatusMailContent({ - feedbackType: existing.type as FeedbackType, - status, - adminMessage, - feedbackContent: existing.content, - }), - [], - ); - notifiedUser = true; - } else { - console.warn( - `Feedback ${id} status updated but no cultivator found for user ${existing.userId}`, - ); - } - } - - return c.json({ - success: true, - feedback: updated, - statusChanged: hasStatusChanged, - notifiedUser, - rewardGranted, - }); -}); - -export default router; diff --git a/src/server/routes/api/admin/index.ts b/src/server/routes/api/admin/index.ts deleted file mode 100644 index 5cfdae4b..00000000 --- a/src/server/routes/api/admin/index.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { requireAdmin } from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import accountsRouter from '@server/routes/api/admin/accounts.router'; -import announcementRouter from '@server/routes/api/admin/announcement.router'; -import communityGroupRouter from '@server/routes/api/admin/community-qrcode.router'; -import feedbackRouter from '@server/routes/api/admin/feedback.router'; -import itemLibraryRouter from '@server/routes/api/admin/item-library.router'; -import llmMetricsRouter from '@server/routes/api/admin/llm-metrics.router'; -import onlineUsersRouter from '@server/routes/api/admin/online-users.router'; -import redeemCodesRouter from '@server/routes/api/admin/redeem-codes.router'; -import reputationShopRouter from '@server/routes/api/admin/reputation-shop.router'; -import sectShopRouter from '@server/routes/api/admin/sect-shop.router'; -import sponsorshipRouter from '@server/routes/api/admin/sponsorship.router'; -import systemMailsRouter from '@server/routes/api/admin/system-mails.router'; -import towerEnemySetsRouter from '@server/routes/api/admin/tower-enemy-sets.router'; -import { Hono } from 'hono'; -import { manualMigrationAdminRouter } from '../manual-migration'; -import rewardItemsRouter from './reward-items.router'; - -const router = new Hono(); - -router.get('/session', requireAdmin(), (c) => { - const user = c.get('user'); - return c.json({ - success: true, - userId: user?.id ?? '', - email: user?.email ?? '', - }); -}); - -router.route('/accounts', accountsRouter); -router.route('/feedback', feedbackRouter); -router.route('/system-mails', systemMailsRouter); -router.route('/announcement', announcementRouter); -router.route('/item-library', itemLibraryRouter); -router.route('/reward-items', rewardItemsRouter); -router.route('/redeem-codes', redeemCodesRouter); -router.route('/reputation-shop', reputationShopRouter); -router.route('/sect-shop', sectShopRouter); -router.route('/manual-migration', manualMigrationAdminRouter); -router.route('/sponsorship', sponsorshipRouter); -router.route('/community-group', communityGroupRouter); -router.route('/llm-metrics', llmMetricsRouter); -router.route('/online-users', onlineUsersRouter); -router.route('/tower-enemy-sets', towerEnemySetsRouter); - -export default router; diff --git a/src/server/routes/api/admin/item-library.router.ts b/src/server/routes/api/admin/item-library.router.ts deleted file mode 100644 index dc0a44dc..00000000 --- a/src/server/routes/api/admin/item-library.router.ts +++ /dev/null @@ -1,253 +0,0 @@ -import { requireAdmin } from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { - getItemLibraryDailyMaterialGenerationSettings, - upsertItemLibraryDailyMaterialGenerationSettings, -} from '@server/lib/repositories/appSettingsRepository'; -import { - archiveItemLibraryEntry, - createItemLibraryEntry, - findItemLibraryById, - listItemLibrary, - normalizeItemLibraryFilters, - updateItemLibraryEntry, -} from '@server/lib/repositories/itemLibraryRepository'; -import { - generateMaterialLibraryEntries, - generateSpiritSeedLibraryEntries, -} from '@server/lib/services/MaterialLibraryService'; -import { ItemLibraryDailyMaterialGenerationSettingsSchema } from '@shared/lib/constants/appSettings'; -import { - CreateItemLibraryEntrySchema, - ItemLibraryListQuerySchema, - ItemLibraryMaterialGenerateSchema, - ItemLibrarySpiritSeedGenerateSchema, - UpdateItemLibraryEntrySchema, -} from '@shared/lib/itemLibrary'; -import { Hono } from 'hono'; - -const router = new Hono(); - -function isUniqueViolation(error: unknown): boolean { - if (!error || typeof error !== 'object') return false; - const maybe = error as { code?: string }; - return maybe.code === '23505'; -} - -router.get('/', requireAdmin(), async (c) => { - const parsed = ItemLibraryListQuerySchema.safeParse({ - status: c.req.query('status') || undefined, - type: 'material', - materialType: c.req.query('materialType') || undefined, - quality: c.req.query('quality') || undefined, - q: c.req.query('q') || undefined, - itemIds: c.req.query('itemIds') || undefined, - page: c.req.query('page') || undefined, - pageSize: c.req.query('pageSize') || undefined, - }); - - if (!parsed.success) { - return c.json({ error: '参数错误', details: parsed.error.flatten() }, 400); - } - - const result = await listItemLibrary( - normalizeItemLibraryFilters(parsed.data), - ); - return c.json(result); -}); - -router.post('/materials/generate', requireAdmin(), async (c) => { - const user = c.get('user'); - if (!user) { - return c.json({ error: '未授权访问' }, 401); - } - - const body = await c.req.json().catch(() => null); - const parsed = ItemLibraryMaterialGenerateSchema.safeParse(body); - if (!parsed.success) { - return c.json({ error: '参数错误', details: parsed.error.flatten() }, 400); - } - - try { - const items = await generateMaterialLibraryEntries({ - request: parsed.data, - userId: user.id, - }); - return c.json({ success: true, items, generated: items.length }); - } catch (error) { - return c.json( - { error: error instanceof Error ? error.message : '批量生成材料失败' }, - 500, - ); - } -}); - -router.post('/seeds/generate', requireAdmin(), async (c) => { - const user = c.get('user'); - if (!user) { - return c.json({ error: '未授权访问' }, 401); - } - - const body = await c.req.json().catch(() => null); - const parsed = ItemLibrarySpiritSeedGenerateSchema.safeParse(body); - if (!parsed.success) { - return c.json({ error: '参数错误', details: parsed.error.flatten() }, 400); - } - - try { - const items = await generateSpiritSeedLibraryEntries({ - request: parsed.data, - userId: user.id, - }); - return c.json({ success: true, items, generated: items.length }); - } catch (error) { - return c.json( - { error: error instanceof Error ? error.message : '批量生成灵种失败' }, - 500, - ); - } -}); - -router.get( - '/materials/daily-generation-settings', - requireAdmin(), - async (c) => { - const settings = await getItemLibraryDailyMaterialGenerationSettings(); - return c.json({ settings }); - }, -); - -router.put( - '/materials/daily-generation-settings', - requireAdmin(), - async (c) => { - const user = c.get('user'); - if (!user) { - return c.json({ error: '未授权访问' }, 401); - } - - const body = await c.req.json().catch(() => null); - const parsed = - ItemLibraryDailyMaterialGenerationSettingsSchema.safeParse(body); - if (!parsed.success) { - return c.json( - { error: '参数错误', details: parsed.error.flatten() }, - 400, - ); - } - - await upsertItemLibraryDailyMaterialGenerationSettings({ - settings: parsed.data, - updatedBy: user.id, - }); - - return c.json({ success: true, settings: parsed.data }); - }, -); - -router.post('/', requireAdmin(), async (c) => { - const user = c.get('user'); - if (!user) { - return c.json({ error: '未授权访问' }, 401); - } - - const body = await c.req.json().catch(() => null); - if (body?.type !== 'material') - return c.json({ error: '旧法宝与消耗品库已停用,请直接配置新版奖励' }, 410); - const parsed = CreateItemLibraryEntrySchema.safeParse(body); - - if (!parsed.success) { - return c.json({ error: '参数错误', details: parsed.error.flatten() }, 400); - } - - try { - const entry = await createItemLibraryEntry({ - entry: parsed.data, - userId: user.id, - }); - return c.json({ success: true, item: entry }); - } catch (error) { - if (isUniqueViolation(error)) { - return c.json({ error: '道具 ID 已存在' }, 409); - } - return c.json( - { error: error instanceof Error ? error.message : '创建道具失败' }, - 400, - ); - } -}); - -router.put('/:id', requireAdmin(), async (c) => { - const user = c.get('user'); - if (!user) { - return c.json({ error: '未授权访问' }, 401); - } - - const id = c.req.param('id'); - const existing = await findItemLibraryById(id); - if (existing && existing.type !== 'material') - return c.json({ error: '旧法宝与消耗品库已停用,请直接配置新版奖励' }, 410); - const body = await c.req.json().catch(() => null); - if (body?.type !== 'material') - return c.json({ error: '旧法宝与消耗品库已停用,请直接配置新版奖励' }, 410); - const parsed = UpdateItemLibraryEntrySchema.safeParse(body); - - if (!parsed.success) { - return c.json({ error: '参数错误', details: parsed.error.flatten() }, 400); - } - - try { - const item = await updateItemLibraryEntry({ - id, - entry: parsed.data, - userId: user.id, - }); - - if (!item) { - return c.json({ error: '道具不存在' }, 404); - } - - return c.json({ success: true, item }); - } catch (error) { - return c.json( - { error: error instanceof Error ? error.message : '更新道具失败' }, - 400, - ); - } -}); - -router.post('/artifact/preview', requireAdmin(), (c) => - c.json({ error: '旧法宝与消耗品库已停用,请直接配置新版奖励' }, 410), -); - -router.post('/:id/archive', requireAdmin(), async (c) => { - const user = c.get('user'); - if (!user) { - return c.json({ error: '未授权访问' }, 401); - } - - const existing = await findItemLibraryById(c.req.param('id')); - if (!existing || existing.type !== 'material') - return c.json({ error: '材料不存在' }, 404); - - const item = await archiveItemLibraryEntry({ - id: c.req.param('id'), - userId: user.id, - }); - - if (!item || item.type !== 'material') { - return c.json({ error: '道具不存在' }, 404); - } - - return c.json({ success: true, item }); -}); - -router.get('/:id', requireAdmin(), async (c) => { - const item = await findItemLibraryById(c.req.param('id')); - if (!item || item.type !== 'material') { - return c.json({ error: '道具不存在' }, 404); - } - return c.json({ item }); -}); - -export default router; diff --git a/src/server/routes/api/admin/llm-metrics.router.ts b/src/server/routes/api/admin/llm-metrics.router.ts deleted file mode 100644 index ab0171ca..00000000 --- a/src/server/routes/api/admin/llm-metrics.router.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { requireAdmin } from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { getLlmMetricsSnapshot } from '@server/lib/llm/metricsStore'; -import { Hono } from 'hono'; - -const router = new Hono(); - -router.get('/', requireAdmin(), async (c) => { - const limit = Number.parseInt(c.req.query('limit') ?? '300', 10); - const sceneId = c.req.query('sceneId')?.trim() || undefined; - - const snapshot = await getLlmMetricsSnapshot({ - limit: Number.isFinite(limit) ? limit : 300, - sceneId, - }); - - return c.json({ - success: true, - data: snapshot, - }); -}); - -export default router; diff --git a/src/server/routes/api/admin/online-users.router.ts b/src/server/routes/api/admin/online-users.router.ts deleted file mode 100644 index 781336f7..00000000 --- a/src/server/routes/api/admin/online-users.router.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { requireAdmin } from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { getOnlineUsersSnapshot } from '@server/lib/services/onlinePresenceService'; -import { Hono } from 'hono'; - -const router = new Hono(); - -router.get('/', requireAdmin(), async (c) => { - const snapshot = await getOnlineUsersSnapshot(); - - return c.json({ - success: true, - data: snapshot, - }); -}); - -export default router; diff --git a/src/server/routes/api/admin/redeem-codes.router.ts b/src/server/routes/api/admin/redeem-codes.router.ts deleted file mode 100644 index d4b38f5d..00000000 --- a/src/server/routes/api/admin/redeem-codes.router.ts +++ /dev/null @@ -1,271 +0,0 @@ -import { getExecutor } from '@server/lib/drizzle/db'; -import { redeemCodes } from '@server/lib/drizzle/schema'; -import { requireAdmin } from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { - generateRedeemCode, - isValidRedeemCodeFormat, - normalizeRedeemCode, -} from '@server/lib/redeem/code'; -import { describeRedeemCodeReward } from '@server/lib/redeem/reward'; -import { - RewardSelectionsSchema, - rewardAttachments as buildRewardAttachments, -} from '@shared/contracts/adminRewards'; -import type { MailAttachment } from '@shared/types/mail'; -import { and, desc, eq, type SQL } from 'drizzle-orm'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const INVENTORY_SNAPSHOT_REWARD_PRESET_ID = '__inventory_v1_snapshot__'; -const ITEM_LIBRARY_SNAPSHOT_REWARD_PRESET_ID = '__item_library_snapshot__'; -const LEGACY_REWARD_CATALOG_SNAPSHOT_PRESET_ID = '__reward_catalog_snapshot__'; - -function isSnapshotRewardPresetId(value: string | null | undefined): boolean { - return ( - value === INVENTORY_SNAPSHOT_REWARD_PRESET_ID || - value === ITEM_LIBRARY_SNAPSHOT_REWARD_PRESET_ID || - value === LEGACY_REWARD_CATALOG_SNAPSHOT_PRESET_ID - ); -} - -const CreateRedeemCodeSchema = z - .object({ - code: z.string().trim().max(64).optional(), - rewardSelections: RewardSelectionsSchema.min(1, '至少选择一项奖励'), - mailTitle: z.string().trim().min(1).max(200), - mailContent: z.string().trim().min(1).max(10000), - totalLimit: z.number().int().min(1).max(100000000).nullable().optional(), - startsAt: z.string().trim().optional(), - endsAt: z.string().trim().optional(), - }) - .superRefine((value, ctx) => { - if (value.startsAt && Number.isNaN(new Date(value.startsAt).getTime())) { - ctx.addIssue({ - code: 'custom', - path: ['startsAt'], - message: '开始时间格式错误', - }); - } - if (value.endsAt && Number.isNaN(new Date(value.endsAt).getTime())) { - ctx.addIssue({ - code: 'custom', - path: ['endsAt'], - message: '结束时间格式错误', - }); - } - if (value.startsAt && value.endsAt) { - const startsAt = new Date(value.startsAt).getTime(); - const endsAt = new Date(value.endsAt).getTime(); - if ( - !Number.isNaN(startsAt) && - !Number.isNaN(endsAt) && - startsAt > endsAt - ) { - ctx.addIssue({ - code: 'custom', - path: ['endsAt'], - message: '结束时间必须晚于开始时间', - }); - } - } - }); - -function isUniqueViolation(error: unknown): boolean { - if (!error || typeof error !== 'object') return false; - const maybe = error as { code?: string }; - return maybe.code === '23505'; -} - -async function createWithAutoCode(params: { - rewardAttachments: MailAttachment[]; - mailTitle: string; - mailContent: string; - totalLimit: number | null; - startsAt: Date | null; - endsAt: Date | null; - userId: string; -}) { - const q = getExecutor(); - - for (let attempt = 0; attempt < 8; attempt += 1) { - const code = generateRedeemCode(); - try { - const [inserted] = await q - .insert(redeemCodes) - .values({ - code, - rewardPresetId: INVENTORY_SNAPSHOT_REWARD_PRESET_ID, - rewardAttachments: params.rewardAttachments, - mailTitle: params.mailTitle, - mailContent: params.mailContent, - totalLimit: params.totalLimit, - startsAt: params.startsAt, - endsAt: params.endsAt, - status: 'active', - createdBy: params.userId, - updatedBy: params.userId, - }) - .returning(); - return inserted; - } catch (error) { - if (isUniqueViolation(error)) continue; - throw error; - } - } - - throw new Error('自动生成兑换码失败,请重试'); -} - -const router = new Hono(); - -router.get('/', requireAdmin(), async (c) => { - const q = getExecutor(); - const status = c.req.query('status'); - const whereConditions: SQL[] = []; - - if (status === 'active' || status === 'disabled') { - whereConditions.push(eq(redeemCodes.status, status)); - } - - const items = await q.query.redeemCodes.findMany({ - where: whereConditions.length > 0 ? and(...whereConditions) : undefined, - orderBy: [desc(redeemCodes.createdAt)], - }); - - const rows = items.map((item) => { - const hasRewardAttachments = - item.rewardAttachments !== null && item.rewardAttachments !== undefined; - let rewardSummary: string[]; - let rewardSource: 'snapshot' | 'expired_legacy' | 'broken_snapshot'; - - if (hasRewardAttachments) { - try { - rewardSummary = describeRedeemCodeReward(item); - rewardSource = 'snapshot'; - } catch { - rewardSummary = ['奖励配置异常']; - rewardSource = 'broken_snapshot'; - } - } else if (isSnapshotRewardPresetId(item.rewardPresetId)) { - rewardSummary = ['奖励快照异常']; - rewardSource = 'broken_snapshot'; - } else { - rewardSummary = ['旧版兑换码(已失效)']; - rewardSource = 'expired_legacy'; - } - - return { - ...item, - rewardSummary, - rewardSource, - }; - }); - - return c.json({ redeemCodes: rows }); -}); - -router.post('/', requireAdmin(), async (c) => { - const user = c.get('user'); - if (!user) { - return c.json({ error: '未授权访问' }, 401); - } - - const q = getExecutor(); - const body = await c.req.json().catch(() => null); - const parsed = CreateRedeemCodeSchema.safeParse(body); - - if (!parsed.success) { - return c.json({ error: '参数错误', details: parsed.error.flatten() }, 400); - } - - const startsAt = parsed.data.startsAt ? new Date(parsed.data.startsAt) : null; - const endsAt = parsed.data.endsAt ? new Date(parsed.data.endsAt) : null; - const totalLimit = parsed.data.totalLimit ?? null; - const manualCode = parsed.data.code - ? normalizeRedeemCode(parsed.data.code) - : ''; - const rewardAttachments = buildRewardAttachments( - parsed.data.rewardSelections, - ); - - try { - if (manualCode) { - if (!isValidRedeemCodeFormat(manualCode)) { - return c.json( - { error: '兑换码格式错误,仅支持 6-64 位大写字母数字' }, - 400, - ); - } - - const [inserted] = await q - .insert(redeemCodes) - .values({ - code: manualCode, - rewardPresetId: INVENTORY_SNAPSHOT_REWARD_PRESET_ID, - rewardAttachments, - mailTitle: parsed.data.mailTitle, - mailContent: parsed.data.mailContent, - totalLimit, - startsAt, - endsAt, - status: 'active', - createdBy: user.id, - updatedBy: user.id, - }) - .returning(); - - return c.json({ success: true, redeemCode: inserted }); - } - - const inserted = await createWithAutoCode({ - rewardAttachments, - mailTitle: parsed.data.mailTitle, - mailContent: parsed.data.mailContent, - totalLimit, - startsAt, - endsAt, - userId: user.id, - }); - - return c.json({ success: true, redeemCode: inserted }); - } catch (error) { - if (isUniqueViolation(error)) { - return c.json({ error: '兑换码已存在' }, 409); - } - - console.error('Create redeem code error:', error); - return c.json({ error: '创建兑换码失败' }, 500); - } -}); - -router.post('/:id/toggle', requireAdmin(), async (c) => { - const user = c.get('user'); - if (!user) { - return c.json({ error: '未授权访问' }, 401); - } - - const q = getExecutor(); - const id = c.req.param('id'); - const item = await q.query.redeemCodes.findFirst({ - where: eq(redeemCodes.id, id), - }); - - if (!item) { - return c.json({ error: '兑换码不存在' }, 404); - } - - const nextStatus = item.status === 'active' ? 'disabled' : 'active'; - const [updated] = await q - .update(redeemCodes) - .set({ - status: nextStatus, - updatedBy: user.id, - }) - .where(eq(redeemCodes.id, item.id)) - .returning(); - - return c.json({ success: true, redeemCode: updated }); -}); - -export default router; diff --git a/src/server/routes/api/admin/reputation-shop.router.ts b/src/server/routes/api/admin/reputation-shop.router.ts deleted file mode 100644 index cabcd0cb..00000000 --- a/src/server/routes/api/admin/reputation-shop.router.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { requireAdmin } from '@server/lib/hono/middleware'; -import { jsonWithStatus } from '@server/lib/hono/response'; -import type { AppEnv } from '@server/lib/hono/types'; -import { - archiveReputationShopItem, - createReputationShopItem, - listReputationShopItems, - ReputationShopError, - updateReputationShopItem, -} from '@server/lib/services/ReputationShopService'; -import { - ReputationShopItemMutationSchema, - ReputationShopListQuerySchema, -} from '@shared/contracts/reputationShop'; -import { Hono } from 'hono'; - -const router = new Hono(); - -router.get('/', requireAdmin(), async (c) => { - const parsed = ReputationShopListQuerySchema.safeParse({ - status: c.req.query('status') || undefined, - }); - - if (!parsed.success) { - return c.json({ error: '参数错误', details: parsed.error.flatten() }, 400); - } - - const items = await listReputationShopItems({ - status: parsed.data.status, - }); - return c.json({ items }); -}); - -router.post('/', requireAdmin(), async (c) => { - const user = c.get('user'); - if (!user) { - return c.json({ error: '未授权访问' }, 401); - } - - const body = await c.req.json().catch(() => null); - const parsed = ReputationShopItemMutationSchema.safeParse(body); - if (!parsed.success) { - return c.json({ error: '参数错误', details: parsed.error.flatten() }, 400); - } - - try { - const item = await createReputationShopItem({ - input: parsed.data, - userId: user.id, - }); - return c.json({ success: true, item }); - } catch (error) { - if (error instanceof ReputationShopError) { - return jsonWithStatus(c, { error: error.message }, error.status); - } - return c.json( - { error: error instanceof Error ? error.message : '创建商品失败' }, - 400, - ); - } -}); - -router.put('/:id', requireAdmin(), async (c) => { - const user = c.get('user'); - if (!user) { - return c.json({ error: '未授权访问' }, 401); - } - - const body = await c.req.json().catch(() => null); - const parsed = ReputationShopItemMutationSchema.safeParse(body); - if (!parsed.success) { - return c.json({ error: '参数错误', details: parsed.error.flatten() }, 400); - } - - try { - const item = await updateReputationShopItem({ - id: c.req.param('id'), - input: parsed.data, - userId: user.id, - }); - if (!item) { - return c.json({ error: '商品不存在' }, 404); - } - return c.json({ success: true, item }); - } catch (error) { - if (error instanceof ReputationShopError) { - return jsonWithStatus(c, { error: error.message }, error.status); - } - return c.json( - { error: error instanceof Error ? error.message : '更新商品失败' }, - 400, - ); - } -}); - -router.post('/:id/archive', requireAdmin(), async (c) => { - const user = c.get('user'); - if (!user) { - return c.json({ error: '未授权访问' }, 401); - } - - const item = await archiveReputationShopItem({ - id: c.req.param('id'), - userId: user.id, - }); - if (!item) { - return c.json({ error: '商品不存在' }, 404); - } - - return c.json({ success: true, item }); -}); - -export default router; diff --git a/src/server/routes/api/admin/sect-shop.router.ts b/src/server/routes/api/admin/sect-shop.router.ts deleted file mode 100644 index 9f2068d5..00000000 --- a/src/server/routes/api/admin/sect-shop.router.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { requireAdmin } from '@server/lib/hono/middleware'; -import { jsonWithStatus } from '@server/lib/hono/response'; -import type { AppEnv } from '@server/lib/hono/types'; -import { - archiveSectShopItem, - createSectShopItem, - listSectShopItems, - SectShopError, - updateSectShopItem, -} from '@server/lib/services/SectShopService'; -import { - SectShopItemMutationSchema, - SectShopListQuerySchema, -} from '@shared/contracts/sectShop'; -import { Hono } from 'hono'; - -const router = new Hono(); - -router.get('/', requireAdmin(), async (c) => { - const parsed = SectShopListQuerySchema.safeParse({ - status: c.req.query('status') || undefined, - }); - if (!parsed.success) { - return c.json({ error: '参数错误', details: parsed.error.flatten() }, 400); - } - return c.json({ - items: await listSectShopItems({ status: parsed.data.status }), - }); -}); - -router.post('/', requireAdmin(), async (c) => { - const user = c.get('user'); - if (!user) return c.json({ error: '未授权访问' }, 401); - const parsed = SectShopItemMutationSchema.safeParse( - await c.req.json().catch(() => null), - ); - if (!parsed.success) { - return c.json({ error: '参数错误', details: parsed.error.flatten() }, 400); - } - try { - return c.json({ - success: true, - item: await createSectShopItem({ - input: parsed.data, - userId: user.id, - }), - }); - } catch (error) { - if (error instanceof SectShopError) { - return jsonWithStatus(c, { error: error.message }, error.status); - } - return c.json( - { error: error instanceof Error ? error.message : '创建商品失败' }, - 400, - ); - } -}); - -router.put('/:id', requireAdmin(), async (c) => { - const user = c.get('user'); - if (!user) return c.json({ error: '未授权访问' }, 401); - const parsed = SectShopItemMutationSchema.safeParse( - await c.req.json().catch(() => null), - ); - if (!parsed.success) { - return c.json({ error: '参数错误', details: parsed.error.flatten() }, 400); - } - try { - const item = await updateSectShopItem({ - id: c.req.param('id'), - input: parsed.data, - userId: user.id, - }); - if (!item) return c.json({ error: '商品不存在' }, 404); - return c.json({ success: true, item }); - } catch (error) { - if (error instanceof SectShopError) { - return jsonWithStatus(c, { error: error.message }, error.status); - } - return c.json( - { error: error instanceof Error ? error.message : '更新商品失败' }, - 400, - ); - } -}); - -router.post('/:id/archive', requireAdmin(), async (c) => { - const user = c.get('user'); - if (!user) return c.json({ error: '未授权访问' }, 401); - const item = await archiveSectShopItem({ - id: c.req.param('id'), - userId: user.id, - }); - if (!item) return c.json({ error: '商品不存在' }, 404); - return c.json({ success: true, item }); -}); - -export default router; diff --git a/src/server/routes/api/admin/sponsorship.router.ts b/src/server/routes/api/admin/sponsorship.router.ts deleted file mode 100644 index fa3a98d9..00000000 --- a/src/server/routes/api/admin/sponsorship.router.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { requireAdmin } from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { getAfdianSponsorshipConfig } from '@server/lib/repositories/appSettingsRepository'; -import { - getSponsorshipOrderForAdmin, - grantManualSponsorshipMerit, - listSponsorshipOrdersForAdmin, - retrySponsorshipOrderAsAdmin, - revealSponsorshipSnapshot, - revokeSponsorshipOrderAsAdmin, - rotateSponsorshipClaimAsAdmin, - SponsorshipApplicationError, - updateSponsorshipConfigAsAdmin, -} from '@server/lib/services/SponsorshipApplicationService'; -import { requireSponsorshipProvider } from '@server/lib/sponsorship/providerRegistry'; -import { - AfdianSponsorshipConfigSchema, - SPONSORSHIP_TIER_IDS, -} from '@shared/lib/sponsorship'; -import { Hono, type Context } from 'hono'; -import { z } from 'zod'; - -const TierConfigSchema = z - .object({ - planId: z.string().trim().max(80), - minimumAmountFen: z.number().int().min(1).max(100_000_000), - }) - .strict(); - -const ConfigUpdateSchema = z - .object({ - tiers: z.object({ - faint_light: TierConfigSchema, - fellow_traveler: TierConfigSchema, - night_guardian: TierConfigSchema, - immortality_witness: TierConfigSchema, - }), - }) - .strict(); - -const ManualGrantSchema = z - .object({ - cultivatorId: z.uuid(), - tier: z.enum(SPONSORSHIP_TIER_IDS), - supportedAt: z.string().datetime().optional(), - publicListing: z.boolean().default(true), - sendMail: z.boolean().default(true), - }) - .strict(); - -const router = new Hono(); -router.use('*', requireAdmin()); - -function sponsorshipErrorResponse(c: Context, error: unknown) { - if (error instanceof SponsorshipApplicationError) { - return c.json({ error: error.message, code: error.code }, error.status); - } - throw error; -} - -router.get('/config', async (c) => c.json(await getAfdianSponsorshipConfig())); - -router.put('/config', async (c) => { - const user = c.get('user')!; - const parsed = ConfigUpdateSchema.safeParse( - await c.req.json().catch(() => null), - ); - if (!parsed.success) - return c.json({ error: '参数错误', details: parsed.error.flatten() }, 400); - const current = await getAfdianSponsorshipConfig(); - const acceptingCheckout = Object.values(parsed.data.tiers).some( - (tier) => tier.planId.length > 0, - ); - const ordersAcceptedAfter = acceptingCheckout - ? current.acceptingCheckout && current.ordersAcceptedAfter - ? current.ordersAcceptedAfter - : new Date().toISOString() - : current.ordersAcceptedAfter; - const nextConfig = AfdianSponsorshipConfigSchema.safeParse({ - ...current, - acceptingCheckout, - ordersAcceptedAfter, - tiers: parsed.data.tiers, - }); - if (!nextConfig.success) { - return c.json( - { error: '档位配置错误', details: nextConfig.error.flatten() }, - 400, - ); - } - await updateSponsorshipConfigAsAdmin({ - config: nextConfig.data, - adminUserId: user.id, - }); - return c.json({ success: true }); -}); - -router.post('/ping', async (c) => { - try { - await requireSponsorshipProvider().ping(); - return c.json({ success: true }); - } catch { - return c.json({ error: '爱发电连接测试失败,请检查服务端配置' }, 502); - } -}); - -router.get('/orders', async (c) => { - const parsed = z - .object({ - page: z.coerce.number().int().min(1).default(1), - pageSize: z.coerce.number().int().min(1).max(100).default(50), - filter: z - .enum(['all', 'attention', 'awaiting_claim', 'fulfilled', 'revoked']) - .default('all'), - }) - .safeParse(c.req.query()); - if (!parsed.success) return c.json({ error: '查询参数错误' }, 400); - const result = await listSponsorshipOrdersForAdmin(parsed.data); - return c.json({ - orders: result.items, - total: result.total, - page: result.page, - pageSize: result.pageSize, - }); -}); - -router.get('/orders/:id', async (c) => { - const id = z.uuid().safeParse(c.req.param('id')); - if (!id.success) return c.json({ error: '订单不存在' }, 404); - const detail = await getSponsorshipOrderForAdmin(id.data); - return detail ? c.json(detail) : c.json({ error: '订单不存在' }, 404); -}); - -router.post('/orders/:id/retry', async (c) => { - const id = z.uuid().safeParse(c.req.param('id')); - if (!id.success) return c.json({ error: '订单不存在' }, 404); - try { - await retrySponsorshipOrderAsAdmin(id.data, c.get('user')!.id); - return c.json({ success: true }); - } catch (error) { - return sponsorshipErrorResponse(c, error); - } -}); - -router.post('/orders/:id/revoke', async (c) => { - const id = z.uuid().safeParse(c.req.param('id')); - if (!id.success) return c.json({ error: '订单不存在' }, 404); - try { - await revokeSponsorshipOrderAsAdmin(id.data, c.get('user')!.id); - return c.json({ success: true }); - } catch (error) { - return sponsorshipErrorResponse(c, error); - } -}); - -router.post('/orders/:id/rotate-claim', async (c) => { - const id = z.uuid().safeParse(c.req.param('id')); - if (!id.success) return c.json({ error: '订单不存在' }, 404); - try { - await rotateSponsorshipClaimAsAdmin(id.data, c.get('user')!.id); - return c.json({ success: true }); - } catch (error) { - return sponsorshipErrorResponse(c, error); - } -}); - -router.post('/snapshots/:id/reveal', async (c) => { - const id = z.uuid().safeParse(c.req.param('id')); - if (!id.success) return c.json({ error: '快照不存在' }, 404); - const snapshot = await revealSponsorshipSnapshot({ - snapshotId: id.data, - adminUserId: c.get('user')!.id, - }); - return snapshot === null - ? c.json({ error: '快照不存在' }, 404) - : c.json({ snapshot }); -}); - -router.post('/manual-grants', async (c) => { - const parsed = ManualGrantSchema.safeParse( - await c.req.json().catch(() => null), - ); - if (!parsed.success) - return c.json({ error: '参数错误', details: parsed.error.flatten() }, 400); - try { - await grantManualSponsorshipMerit({ - ...parsed.data, - supportedAt: parsed.data.supportedAt - ? new Date(parsed.data.supportedAt) - : new Date(), - adminUserId: c.get('user')!.id, - }); - return c.json({ success: true }, 201); - } catch (error) { - return sponsorshipErrorResponse(c, error); - } -}); - -export default router; diff --git a/src/server/routes/api/admin/system-mails.router.ts b/src/server/routes/api/admin/system-mails.router.ts deleted file mode 100644 index f7b3f212..00000000 --- a/src/server/routes/api/admin/system-mails.router.ts +++ /dev/null @@ -1,214 +0,0 @@ -import { db } from '@server/lib/drizzle/db'; -import { mails, systemMailCampaigns } from '@server/lib/drizzle/schema'; -import { - getValidatedJson, - getValidatedQuery, - requireAdmin, - validateJson, - validateQuery, -} from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { SystemMailInputSchema } from '@shared/contracts/systemMail'; -import { and, desc, eq, ilike, sql } from 'drizzle-orm'; -import { Hono } from 'hono'; -import { createHash } from 'node:crypto'; -import { z } from 'zod'; - -const router = new Hono(); -router.use('*', requireAdmin()); -const CreateSchema = z - .object({ requestId: z.uuid(), input: SystemMailInputSchema }) - .strict(); -const UpdateSchema = z - .object({ - revision: z.number().int().positive(), - input: SystemMailInputSchema, - }) - .strict(); -const RevisionSchema = z - .object({ revision: z.number().int().positive() }) - .strict(); -const ListSchema = z.object({ - page: z.coerce.number().int().min(1).default(1), - search: z.string().trim().max(200).default(''), -}); -const countDeliveries = sql`(select count(*)::int from ${mails} delivered where delivered.system_mail_campaign_id = ${systemMailCampaigns}.id)`; - -function values(input: z.infer) { - return { - ...input, - startsAt: new Date(input.startsAt), - endsAt: new Date(input.endsAt), - }; -} - -router.get('/', validateQuery(ListSchema), async (c) => { - const { page, search } = getValidatedQuery>(c); - const rows = await db - .select({ - id: systemMailCampaigns.id, - title: systemMailCampaigns.title, - status: systemMailCampaigns.status, - revision: systemMailCampaigns.revision, - conditions: systemMailCampaigns.conditions, - startsAt: systemMailCampaigns.startsAt, - endsAt: systemMailCampaigns.endsAt, - createdAt: systemMailCampaigns.createdAt, - publishedAt: systemMailCampaigns.publishedAt, - deliveredCount: countDeliveries, - }) - .from(systemMailCampaigns) - .where(search ? ilike(systemMailCampaigns.title, `%${search}%`) : undefined) - .orderBy(desc(systemMailCampaigns.createdAt), desc(systemMailCampaigns.id)) - .limit(21) - .offset((page - 1) * 20); - return c.json({ items: rows.slice(0, 20), hasMore: rows.length > 20 }); -}); - -router.get('/:id', async (c) => { - const id = z.uuid().parse(c.req.param('id')); - const [row] = await db - .select() - .from(systemMailCampaigns) - .where(eq(systemMailCampaigns.id, id)); - if (!row) return c.json({ error: '邮件发布记录不存在' }, 404); - const [count] = await db - .select({ count: sql`count(*)::int` }) - .from(mails) - .where(eq(mails.systemMailCampaignId, id)); - return c.json({ - id: row.id, - title: row.title, - content: row.content, - rewardSelections: row.rewardSelections, - conditions: row.conditions, - startsAt: row.startsAt, - endsAt: row.endsAt, - status: row.status, - revision: row.revision, - createdAt: row.createdAt, - publishedAt: row.publishedAt, - deliveredCount: count?.count ?? 0, - }); -}); - -router.post('/', validateJson(CreateSchema), async (c) => { - const { requestId, input } = - getValidatedJson>(c); - const creationFingerprint = createHash('sha256') - .update(JSON.stringify(input)) - .digest('hex'); - await db - .insert(systemMailCampaigns) - .values({ - ...values(input), - id: requestId, - creationFingerprint, - createdBy: c.get('user')!.id, - }) - .onConflictDoNothing({ target: systemMailCampaigns.id }); - const [row] = await db - .select() - .from(systemMailCampaigns) - .where(eq(systemMailCampaigns.id, requestId)); - if (!row || row.creationFingerprint !== creationFingerprint) - return c.json({ error: '此请求已用于其他内容,请重新打开编辑器' }, 409); - return c.json({ id: row.id, revision: row.revision, status: row.status }); -}); - -router.put('/:id', validateJson(UpdateSchema), async (c) => { - const id = z.uuid().parse(c.req.param('id')); - const { revision, input } = getValidatedJson>(c); - const [row] = await db - .update(systemMailCampaigns) - .set({ ...values(input), revision: revision + 1 }) - .where( - and( - eq(systemMailCampaigns.id, id), - eq(systemMailCampaigns.status, 'draft'), - eq(systemMailCampaigns.revision, revision), - ), - ) - .returning({ - id: systemMailCampaigns.id, - revision: systemMailCampaigns.revision, - }); - if (!row) - return c.json({ error: '草稿已变化或已发布,请关闭后重新打开' }, 409); - return c.json(row); -}); - -router.post('/:id/publish', validateJson(RevisionSchema), async (c) => { - const id = z.uuid().parse(c.req.param('id')); - const { revision } = getValidatedJson>(c); - const result = await db.transaction(async (tx) => { - const [row] = await tx - .select() - .from(systemMailCampaigns) - .where(eq(systemMailCampaigns.id, id)) - .for('update'); - if (!row) return 'missing'; - if (row.status === 'published' && row.revision === revision + 1) - return 'ok'; - if (row.status !== 'draft' || row.revision !== revision) return 'changed'; - if (row.endsAt.getTime() <= Date.now()) return 'expired'; - SystemMailInputSchema.parse({ - title: row.title, - content: row.content, - rewardSelections: row.rewardSelections, - conditions: row.conditions, - startsAt: row.startsAt.toISOString(), - endsAt: row.endsAt.toISOString(), - }); - await tx - .update(systemMailCampaigns) - .set({ - status: 'published', - publishedAt: new Date(), - revision: revision + 1, - }) - .where(eq(systemMailCampaigns.id, id)); - return 'ok'; - }); - if (result === 'missing') return c.json({ error: '邮件发布记录不存在' }, 404); - if (result !== 'ok') - return c.json( - { - error: - result === 'expired' - ? '投递结束时间已过,请修改草稿' - : '草稿已变化,请重新打开核对', - }, - 409, - ); - return c.json({ success: true }); -}); - -router.post('/:id/stop', validateJson(RevisionSchema), async (c) => { - const id = z.uuid().parse(c.req.param('id')); - const { revision } = getValidatedJson>(c); - // UPDATE conflicts with the consumer's SHARE lock: after this commits, - // no new delivery transaction can observe the campaign as published. - const [row] = await db - .update(systemMailCampaigns) - .set({ status: 'stopped', revision: revision + 1 }) - .where( - and( - eq(systemMailCampaigns.id, id), - eq(systemMailCampaigns.status, 'published'), - eq(systemMailCampaigns.revision, revision), - ), - ) - .returning({ id: systemMailCampaigns.id }); - if (!row) { - const [current] = await db - .select({ status: systemMailCampaigns.status }) - .from(systemMailCampaigns) - .where(eq(systemMailCampaigns.id, id)); - if (current?.status !== 'stopped') - return c.json({ error: '发布记录已变化,请重新打开' }, 409); - } - return c.json({ success: true }); -}); - -export default router; diff --git a/src/server/routes/api/admin/tower-enemy-sets.router.ts b/src/server/routes/api/admin/tower-enemy-sets.router.ts deleted file mode 100644 index 53ea1a29..00000000 --- a/src/server/routes/api/admin/tower-enemy-sets.router.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { - getValidatedJson, - getValidatedQuery, - requireAdmin, - validateJson, - validateQuery, -} from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { - listTowerPublishedWeeks, - parseTowerWeekRecord, - readTowerWeekRecord, - regenerateTowerWeek, - towerWeekFingerprint, -} from '@server/lib/repositories/towerRepository'; -import type { AdminTowerView } from '@shared/contracts/adminTower'; -import { - publishedTowerEncounter, - publishedTowerPreviews, -} from '@shared/engine/combat-v6/tower/published'; -import { - TOWER_ELIGIBLE_REALMS, - TOWER_MAX_FLOOR, - TOWER_MIN_REALM, -} from '@shared/lib/tower/helpers'; -import { - getNextTowerSeasonMeta, - getTowerSeasonMeta, -} from '@shared/lib/tower/season'; -import { REALM_VALUES } from '@shared/types/constants'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const SeasonKeySchema = z - .string() - .regex(/^\d{4}-W(?:0[1-9]|[1-4]\d|5[0-3])@Asia\/Shanghai$/); -const QuerySchema = z.object({ - seasonKey: SeasonKeySchema.optional(), - realm: z - .enum(REALM_VALUES) - .refine((realm) => TOWER_ELIGIBLE_REALMS.includes(realm), '蜃楼境界未开放') - .default(TOWER_MIN_REALM), - floor: z.coerce.number().int().min(1).max(TOWER_MAX_FLOOR).default(1), -}); -const RegenerateSchema = z.strictObject({ - seasonKey: SeasonKeySchema, - expectedFingerprint: z - .string() - .regex(/^[a-f0-9]{64}$/) - .nullable(), -}); -const router = new Hono(); -router.use('*', requireAdmin()); - -router.get('/', validateQuery(QuerySchema), async (c) => { - const query = getValidatedQuery>(c); - const currentSeason = getTowerSeasonMeta(); - const nextSeason = getNextTowerSeasonMeta( - new Date(currentSeason.seasonStartedAt), - ); - const seasonKey = query.seasonKey ?? currentSeason.seasonKey; - const [weeks, row] = await Promise.all([ - listTowerPublishedWeeks(), - readTowerWeekRecord(seasonKey), - ]); - // Read and validate this exact snapshot, keeping its fingerprint and display consistent. - const pack = parseTowerWeekRecord(row); - const summarize = (item: (typeof weeks)[number]) => ({ - seasonKey: item.seasonKey, - schemaVersion: item.schemaVersion, - contentVersion: item.contentVersion, - generatorVersion: item.generatorVersion, - publishedAt: item.createdAt.toISOString(), - }); - const data: AdminTowerView = { - currentSeason, - nextSeason, - seasonKey, - realm: query.realm, - floor: query.floor, - weeks: weeks.map(summarize), - fingerprint: towerWeekFingerprint(row), - published: row ? summarize(row) : null, - configuration: pack - ? { - season: pack.season, - previews: publishedTowerPreviews(pack), - encounter: publishedTowerEncounter(pack, query.realm, query.floor), - } - : null, - }; - c.header('Cache-Control', 'no-store'); - return c.json({ success: true, data }); -}); - -router.post('/regenerate', validateJson(RegenerateSchema), async (c) => { - const input = getValidatedJson>(c); - const current = getTowerSeasonMeta(); - const next = getNextTowerSeasonMeta(new Date(current.seasonStartedAt)); - const season = [current, next].find( - (item) => item.seasonKey === input.seasonKey, - ); - if (!season) return c.json({ error: '仅可重新生成本周或下周配置' }, 400); - const replaced = await regenerateTowerWeek(season, input.expectedFingerprint); - if (!replaced) - return c.json({ error: '周配置已变化,请刷新后重新生成' }, 409); - return c.json({ success: true }); -}); -export default router; diff --git a/src/server/routes/api/alchemy-formulas.router.ts b/src/server/routes/api/alchemy-formulas.router.ts deleted file mode 100644 index baee5fc3..00000000 --- a/src/server/routes/api/alchemy-formulas.router.ts +++ /dev/null @@ -1,222 +0,0 @@ -import { requireActiveCultivatorRef } from '@server/lib/hono/middleware'; -import { jsonWithStatus } from '@server/lib/hono/response'; -import type { AppEnv } from '@server/lib/hono/types'; -import { - assertAlchemyMaterialVersions, - readAlchemyMaterials, -} from '@server/lib/services/alchemy/AlchemyInventory'; -import { - analyzeFormulaMaterials, - confirmDiscoveryCandidate, - deleteCultivatorFormula, - listCultivatorFormulasPage, -} from '@server/lib/services/AlchemyFormulaService'; -import { AlchemyServiceError } from '@server/lib/services/AlchemyServiceError'; -import { - ALCHEMY_INPUT_CONSTRAINTS, - ALCHEMY_MAX_DOSE, -} from '@shared/config/alchemyInput'; -import { PILL_FAMILY_VALUES } from '@shared/types/consumable'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const router = new Hono(); -router.get('/materials', requireActiveCultivatorRef(), async (c) => - c.json({ - success: true, - data: await readAlchemyMaterials( - c.get('activeCultivatorRef')!.cultivatorId, - ), - }), -); -const { minQuantityPerMaterial } = ALCHEMY_INPUT_CONSTRAINTS; - -const DiscoveryConfirmSchema = z.object({ - token: z.string().uuid(), - accept: z.boolean(), -}); -const FormulaIdParamSchema = z.object({ - formulaId: z.string().uuid(), -}); -const FormulaAnalyzeSchema = z.object({ - materialIds: z.array(z.string()).min(1).max(6), - materialVersions: z.record(z.string(), z.string().max(10000)), - materialQuantities: z - .record( - z.string(), - z.number().int().min(minQuantityPerMaterial).max(ALCHEMY_MAX_DOSE), - ) - .optional(), -}); -const FormulaListQuerySchema = z.object({ - page: z.coerce.number().int().min(1).default(1), - pageSize: z.coerce.number().int().min(1).max(5).default(5), - search: z.string().trim().max(40).optional(), - family: z.enum(PILL_FAMILY_VALUES).optional(), -}); - -router.get('/formulas', requireActiveCultivatorRef(), async (c) => { - const cultivator = c.get('activeCultivatorRef'); - if (!cultivator) { - return c.json({ error: '当前没有活跃角色' }, 404); - } - - try { - const query = FormulaListQuerySchema.parse({ - page: c.req.query('page'), - pageSize: c.req.query('pageSize'), - search: c.req.query('search') || undefined, - family: c.req.query('family') || undefined, - }); - const result = await listCultivatorFormulasPage( - cultivator.cultivatorId, - query, - ); - return c.json({ - success: true, - data: { - formulas: result.formulas, - pagination: result.pagination, - }, - }); - } catch (error) { - if (error instanceof z.ZodError) { - return c.json( - { error: error.issues[0]?.message || '请求参数格式错误' }, - 400, - ); - } - if (error instanceof AlchemyServiceError) { - return jsonWithStatus(c, { error: error.message }, error.status); - } - return c.json({ error: '丹方列表读取失败,请稍后再试。' }, 500); - } -}); - -router.delete( - '/formulas/:formulaId', - requireActiveCultivatorRef(), - async (c) => { - const cultivator = c.get('activeCultivatorRef'); - if (!cultivator) { - return c.json({ error: '当前没有活跃角色' }, 404); - } - - try { - const { formulaId } = FormulaIdParamSchema.parse(c.req.param()); - await deleteCultivatorFormula(cultivator.cultivatorId, formulaId); - - return c.json({ - success: true, - message: '丹方已删除', - }); - } catch (error) { - if (error instanceof z.ZodError) { - return c.json( - { error: error.issues[0]?.message || '请求参数格式错误' }, - 400, - ); - } - if (error instanceof AlchemyServiceError) { - return jsonWithStatus( - c, - { error: error.message, ...(error.details ?? {}) }, - error.status, - ); - } - return c.json({ error: '丹方删除失败,请稍后再试。' }, 500); - } - }, -); - -router.post( - '/formulas/:formulaId/analyze', - requireActiveCultivatorRef(), - async (c) => { - const cultivator = c.get('activeCultivatorRef'); - if (!cultivator) { - return c.json({ error: '当前没有活跃角色' }, 404); - } - - try { - const { formulaId } = FormulaIdParamSchema.parse(c.req.param()); - const { materialIds, materialQuantities, materialVersions } = - FormulaAnalyzeSchema.parse(await c.req.json()); - await assertAlchemyMaterialVersions( - cultivator.cultivatorId, - materialIds, - materialVersions, - ); - const result = await analyzeFormulaMaterials( - cultivator.cultivatorId, - formulaId, - materialIds, - materialQuantities, - ); - - return c.json({ - success: true, - data: result, - }); - } catch (error) { - if (error instanceof z.ZodError) { - return c.json( - { error: error.issues[0]?.message || '请求参数格式错误' }, - 400, - ); - } - if (error instanceof AlchemyServiceError) { - return jsonWithStatus( - c, - { error: error.message, ...(error.details ?? {}) }, - error.status, - ); - } - return c.json({ error: '推演药路失败,请稍后再试。' }, 500); - } - }, -); - -router.post( - '/formulas/discovery/confirm', - requireActiveCultivatorRef(), - async (c) => { - const cultivator = c.get('activeCultivatorRef'); - if (!cultivator) { - return c.json({ error: '当前没有活跃角色' }, 404); - } - - try { - const { token, accept } = DiscoveryConfirmSchema.parse( - await c.req.json(), - ); - const result = await confirmDiscoveryCandidate( - cultivator.cultivatorId, - token, - accept, - ); - - return c.json({ - success: true, - data: result, - }); - } catch (error) { - if (error instanceof z.ZodError) { - return c.json( - { error: error.issues[0]?.message || '请求参数格式错误' }, - 400, - ); - } - if (error instanceof AlchemyServiceError) { - return jsonWithStatus( - c, - { error: error.message, ...(error.details ?? {}) }, - error.status, - ); - } - return c.json({ error: '丹方确认失败,请稍后再试。' }, 500); - } - }, -); - -export default router; diff --git a/src/server/routes/api/arena.router.ts b/src/server/routes/api/arena.router.ts deleted file mode 100644 index f67f04cf..00000000 --- a/src/server/routes/api/arena.router.ts +++ /dev/null @@ -1,316 +0,0 @@ -import { getExecutor } from '@server/lib/drizzle/db'; -import { cultivators } from '@server/lib/drizzle/schema'; -import { - getValidatedJson, - requireActiveCultivatorRef, - validateJson, -} from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { ArenaBattleStartOrchestrator } from '@server/lib/services/ArenaBattleStartOrchestrator'; -import { publishArenaRoomChanges } from '@server/lib/services/arenaRoomBroadcaster'; -import { ArenaRoomService } from '@server/lib/services/ArenaRoomService'; -import { CombatV6BuildError } from '@server/lib/services/combat-v6/CombatV6BuildService'; -import { - ArenaCreateRoomSchema, - ArenaJoinRoomSchema, - ArenaReadyCommandSchema, - ArenaStartCommandSchema, - type ArenaRoomV1, -} from '@shared/contracts/arena'; -import { REALM_STAGE_VALUES, REALM_VALUES } from '@shared/types/constants'; -import { and, eq } from 'drizzle-orm'; -import { Hono, type Context } from 'hono'; -import { z } from 'zod'; - -const RoomIdSchema = z - .string() - .min(1) - .max(120) - .regex(/^arena-[A-Za-z0-9-]+$/); -const RealmSchema = z.enum(REALM_VALUES); -const RealmStageSchema = z.enum(REALM_STAGE_VALUES); - -const router = new Hono(); -const rooms = new ArenaRoomService(); -const starts = new ArenaBattleStartOrchestrator(rooms); -const EmptyCommandSchema = z.object({}).strict(); - -router.get('/room', requireActiveCultivatorRef(), async (c) => { - const identity = await requireArenaIdentity(c); - if (identity instanceof Response) return identity; - return c.json({ - room: await rooms.getRoomForCultivator(identity.cultivatorId), - }); -}); - -router.get('/rooms/:roomId', requireActiveCultivatorRef(), async (c) => { - const roomId = RoomIdSchema.parse(c.req.param('roomId')); - const identity = await requireArenaIdentity(c); - if (identity instanceof Response) return identity; - const room = await rooms.getRoom(roomId); - if (!room) return c.json({ error: '擂台房间不存在或已过期' }, 404); - const participant = room.teams.alpha - .concat(room.teams.beta, room.spectators ?? []) - .some( - (seat) => - seat.userId === identity.userId && - seat.cultivatorId === identity.cultivatorId, - ); - if (!participant) return c.json({ error: '你不在此擂台房间中' }, 403); - return c.json({ room }); -}); - -router.post( - '/rooms', - requireActiveCultivatorRef(), - validateJson(ArenaCreateRoomSchema), - async (c) => { - const identity = await requireArenaIdentity(c); - if (identity instanceof Response) return identity; - try { - const room = await rooms.createRoom({ - userId: identity.userId, - cultivatorId: identity.cultivatorId, - displayName: identity.displayName, - realm: identity.realm, - realmStage: identity.realmStage, - }); - publishRoom(room); - return c.json({ room }, 201); - } catch (error) { - return arenaError(c, error); - } - }, -); - -router.post( - '/rooms/join', - requireActiveCultivatorRef(), - validateJson(ArenaJoinRoomSchema), - async (c) => { - const identity = await requireArenaIdentity(c); - if (identity instanceof Response) return identity; - const body = getValidatedJson<{ - inviteCode: string; - role: 'participant' | 'spectator'; - }>(c); - try { - const room = await rooms.joinRoom({ - ...identity, - inviteCode: body.inviteCode, - role: body.role, - }); - publishRoom(room); - return c.json({ room }); - } catch (error) { - return arenaError(c, error); - } - }, -); - -router.post( - '/rooms/:roomId/ready', - requireActiveCultivatorRef(), - validateJson(ArenaReadyCommandSchema), - async (c) => { - const roomId = RoomIdSchema.parse(c.req.param('roomId')); - const identity = await requireArenaMember(c, roomId); - if (identity instanceof Response) return identity; - const { ready } = getValidatedJson<{ ready: boolean }>(c); - try { - const room = await rooms.setReady(roomId, identity.userId, ready); - publishRoom(room); - return c.json({ room }); - } catch (error) { - return arenaError(c, error); - } - }, -); - -router.post( - '/rooms/:roomId/touch', - requireActiveCultivatorRef(), - validateJson(EmptyCommandSchema), - async (c) => { - const roomId = RoomIdSchema.parse(c.req.param('roomId')); - const identity = await requireArenaMember(c, roomId); - if (identity instanceof Response) return identity; - try { - const room = await rooms.touch(roomId, identity.userId); - publishRoom(room); - return c.json({ room }); - } catch (error) { - return arenaError(c, error); - } - }, -); - -router.post( - '/rooms/:roomId/switch-team', - requireActiveCultivatorRef(), - validateJson(EmptyCommandSchema), - async (c) => { - const roomId = RoomIdSchema.parse(c.req.param('roomId')); - const identity = await requireArenaMember(c, roomId); - if (identity instanceof Response) return identity; - try { - const room = await rooms.switchTeam(roomId, identity.userId); - publishRoom(room); - return c.json({ room }); - } catch (error) { - return arenaError(c, error); - } - }, -); - -router.post( - '/rooms/:roomId/start', - requireActiveCultivatorRef(), - validateJson(ArenaStartCommandSchema), - async (c) => { - const roomId = RoomIdSchema.parse(c.req.param('roomId')); - const identity = await requireArenaMember(c, roomId); - if (identity instanceof Response) return identity; - const { requestId } = getValidatedJson<{ requestId: string }>(c); - try { - const result = await starts.start({ - roomId, - hostUserId: identity.userId, - requestId, - }); - publishRoom(result.room); - return c.json( - { - room: result.room, - pending: result.pending, - ...(result.room.battleMatchId - ? { battleMatchId: result.room.battleMatchId } - : {}), - }, - result.pending ? 202 : 200, - ); - } catch (error) { - const current = await rooms.getRoom(roomId).catch(() => null); - if (current) publishRoom(current); - if ( - error instanceof Error && - /Battle matchmaker|Battle session|fetch failed|timed out/i.test( - error.message, - ) - ) { - console.error('[arena-start] battle orchestration failed', { - roomId, - error, - }); - return c.json({ error: '战斗服务暂不可用,开擂状态已保留' }, 503); - } - return arenaError(c, error); - } - }, -); - -router.post( - '/rooms/:roomId/leave', - requireActiveCultivatorRef(), - validateJson(EmptyCommandSchema), - async (c) => { - const roomId = RoomIdSchema.parse(c.req.param('roomId')); - const identity = await requireArenaMember(c, roomId); - if (identity instanceof Response) return identity; - try { - const previous = await rooms.getRoom(roomId); - if (!previous) return c.json({ error: '擂台房间不存在或已过期' }, 404); - const room = await rooms.leave(roomId, identity.userId); - publishArenaRoomChanges(arenaUserIds(previous), { - roomId, - revision: room?.revision ?? previous.revision + 1, - status: room?.status ?? 'cancelled', - }); - return c.json({ room: null }); - } catch (error) { - return arenaError(c, error); - } - }, -); - -async function requireArenaIdentity(c: Context) { - const user = c.get('user'); - const active = c.get('activeCultivatorRef'); - if (!user || !active) return c.json({ error: '当前没有可用的活跃角色' }, 404); - const row = await getExecutor().query.cultivators.findFirst({ - columns: { - id: true, - userId: true, - name: true, - realm: true, - realm_stage: true, - }, - where: and( - eq(cultivators.id, active.cultivatorId), - eq(cultivators.userId, user.id), - eq(cultivators.status, 'active'), - ), - }); - if (!row) return c.json({ error: '当前没有可用的活跃角色' }, 404); - const realm = RealmSchema.safeParse(row.realm); - const realmStage = RealmStageSchema.safeParse(row.realm_stage); - if (!realm.success || !realmStage.success) { - return c.json({ error: '当前角色境界数据无效' }, 500); - } - return { - userId: row.userId, - cultivatorId: row.id, - displayName: row.name, - realm: realm.data, - realmStage: realmStage.data, - }; -} - -async function requireArenaMember(c: Context, roomId: string) { - const identity = await requireArenaIdentity(c); - if (identity instanceof Response) return identity; - const room = await rooms.getRoom(roomId); - if (!room) return c.json({ error: '擂台房间不存在或已过期' }, 404); - const member = room.teams.alpha - .concat(room.teams.beta, room.spectators ?? []) - .some( - (seat) => - seat.userId === identity.userId && - seat.cultivatorId === identity.cultivatorId, - ); - if (!member) return c.json({ error: '当前活跃修士不在此擂台房间中' }, 403); - return identity; -} - -function arenaError(c: Context, error: unknown) { - if (error instanceof CombatV6BuildError) { - return c.json({ error: error.message, code: error.code }, error.status); - } - const message = error instanceof Error ? error.message : '擂台房间操作失败'; - if (/不存在|过期|邀请码无效/.test(message)) { - return c.json({ error: message }, 404); - } - if ( - /已经|已满|不能|需要|准备|房主|不接受|不在|不可用|状态已变化/.test(message) - ) { - return c.json({ error: message }, 409); - } - throw error; -} - -function arenaUserIds(room: ArenaRoomV1): string[] { - return room.teams.alpha - .concat(room.teams.beta, room.spectators ?? []) - .map((seat) => seat.userId); -} - -function publishRoom(room: ArenaRoomV1): void { - publishArenaRoomChanges(arenaUserIds(room), { - roomId: room.roomId, - revision: room.revision, - status: room.status, - room, - }); -} - -export default router; diff --git a/src/server/routes/api/artifact-migration/index.ts b/src/server/routes/api/artifact-migration/index.ts deleted file mode 100644 index af1c3445..00000000 --- a/src/server/routes/api/artifact-migration/index.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { - getValidatedJson, - redisLockErrorResponse, - requireActiveCultivatorRef, - validateJson, -} from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { InventoryError } from '@server/lib/services/InventoryService'; -import { ExchangeArtifactSchema } from '@shared/contracts/artifactMigration'; -import { InventoryRuleError } from '@shared/inventory'; -import { Hono } from 'hono'; -import { z } from 'zod'; -import { - artifactMigrationAvailability, - exchangeArtifactMigration, - readArtifactMigration, -} from './service'; -function router() { - const app = new Hono(); - app.use('*', async (c, next) => { - c.header('Cache-Control', 'no-store'); - await next(); - }); - app.onError((error, c) => { - const lock = redisLockErrorResponse(error); - if (lock) return lock; - if (error instanceof z.ZodError) - return c.json({ success: false, error: '兑换参数无效' }, 400); - if (error instanceof InventoryError || error instanceof InventoryRuleError) - return c.json({ success: false, error: error.message }, 409); - console.error('[artifact-migration]', error); - return c.json( - { success: false, error: '结果暂未确认,请刷新旧法宝列表并核对背包' }, - 500, - ); - }); - return app; -} -export const artifactMigrationRouter = router(); -artifactMigrationRouter.use('*', requireActiveCultivatorRef()); -artifactMigrationRouter.get('/availability', async (c) => - c.json({ - success: true, - data: await artifactMigrationAvailability(c.get('activeCultivatorRef')!), - }), -); -artifactMigrationRouter.get('/', async (c) => - c.json({ - success: true, - data: await readArtifactMigration(c.get('activeCultivatorRef')!), - }), -); -artifactMigrationRouter.post( - '/exchange', - validateJson(ExchangeArtifactSchema), - async (c) => - c.json({ - success: true, - ...(await exchangeArtifactMigration( - c.get('activeCultivatorRef')!, - getValidatedJson(c), - )), - }), -); diff --git a/src/server/routes/api/auction.router.ts b/src/server/routes/api/auction.router.ts deleted file mode 100644 index 40c049cc..00000000 --- a/src/server/routes/api/auction.router.ts +++ /dev/null @@ -1,317 +0,0 @@ -import { - redisLockErrorResponse, - requireActiveCultivatorRef, -} from '@server/lib/hono/middleware'; -import { jsonWithStatus } from '@server/lib/hono/response'; -import type { AppEnv } from '@server/lib/hono/types'; -import * as auctionRepository from '@server/lib/repositories/auctionRepository'; -import { - buyAuctionListing, - cancelAuctionListing, - listAuctionBeast, - listAuctionItem, -} from '@server/lib/services/AuctionApplicationService'; -import { - AuctionServiceError, - publicAuctionListing, -} from '@server/lib/services/AuctionService'; -import { BeastError } from '@server/lib/services/combat-v6/BeastMutationGuard'; -import { PlayerCommandIdempotencyError } from '@server/lib/services/CommandExecutors'; -import { InventoryError } from '@server/lib/services/InventoryService'; -import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; -import { - AUCTION_ITEM_TYPES, - AuctionBeastListSchema, - AuctionBuySchema, - AuctionListSchema, -} from '@shared/contracts/auction'; -import { QUALITY_VALUES } from '@shared/types/constants'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const ListingsSchema = z.object({ - scope: z.enum(['all', 'mine']).default('all'), - assetType: z.enum(['item', 'beast']).optional(), - itemType: z.enum(AUCTION_ITEM_TYPES).optional(), - itemCategory: z.string().trim().min(1).max(50).optional(), - itemQuality: z.enum(QUALITY_VALUES).optional(), - itemName: z.string().trim().min(1).max(200).optional(), - sellerName: z.string().trim().min(1).max(100).optional(), - minPrice: z.number().int().min(0).optional(), - maxPrice: z.number().int().min(0).optional(), - sortBy: z.enum(['price_asc', 'price_desc', 'latest']).optional(), - page: z.number().int().min(1).optional(), - limit: z.number().int().min(1).max(100).optional(), -}); - -const statusMap: Record = { - INSUFFICIENT_FUNDS: 400, - LISTING_NOT_FOUND: 404, - LISTING_EXPIRED: 400, - NOT_OWNER: 403, - MAX_LISTINGS: 400, - ITEM_NOT_FOUND: 404, - CONCURRENT_PURCHASE: 429, - INVALID_ITEM_TYPE: 400, - INVALID_PRICE: 400, - INVALID_QUANTITY: 400, - INVALID_ITEM_QUALITY: 400, - SAME_OWNER: 403, - INVALID_VISIBILITY: 400, - TARGET_NOT_FRIEND: 403, - MISSING_TALISMAN: 400, - NOT_TARGET_BUYER: 403, -}; - -function getAuctionErrorStatus(error: AuctionServiceError): number { - return statusMap[error.code] || 400; -} - -const router = new Hono(); - -router.get('/listings', requireActiveCultivatorRef(), async (c) => { - const cultivator = c.get('activeCultivatorRef'); - if (!cultivator) { - return c.json({ error: '未授权访问' }, 401); - } - - try { - const params = ListingsSchema.parse({ - scope: c.req.query('scope') || undefined, - assetType: c.req.query('assetType') || undefined, - itemType: c.req.query('itemType') || undefined, - itemCategory: c.req.query('itemCategory') || undefined, - itemQuality: c.req.query('itemQuality') || undefined, - itemName: c.req.query('itemName') || undefined, - sellerName: c.req.query('sellerName') || undefined, - minPrice: c.req.query('minPrice') - ? Number(c.req.query('minPrice')) - : undefined, - maxPrice: c.req.query('maxPrice') - ? Number(c.req.query('maxPrice')) - : undefined, - sortBy: c.req.query('sortBy') || undefined, - page: c.req.query('page') ? Number(c.req.query('page')) : undefined, - limit: c.req.query('limit') ? Number(c.req.query('limit')) : undefined, - }); - - if (params.assetType === 'beast' || params.itemType === 'beast') - params.itemQuality = undefined; - const result = await auctionRepository.findActiveListings({ - ...params, - viewerCultivatorId: cultivator.cultivatorId, - }); - const page = params.page || 1; - const limit = params.limit || 20; - const totalPages = Math.ceil(result.total / limit); - - return c.json({ - listings: result.listings.map(publicAuctionListing), - pagination: { - page, - limit, - total: result.total, - totalPages, - hasMore: page < totalPages, - }, - }); - } catch (error) { - if (error instanceof z.ZodError) { - return c.json({ error: '参数错误', details: error.issues }, 400); - } - - console.error('Auction Listings API Error:', error); - return c.json({ error: '获取拍卖列表失败' }, 500); - } -}); - -router.post('/buy', requireActiveCultivatorRef(), async (c) => { - const user = c.get('user'); - const cultivator = c.get('activeCultivatorRef'); - if (!user || !cultivator) { - return c.json({ error: '未授权访问' }, 401); - } - - try { - const { listingId, quantity, requestId } = AuctionBuySchema.parse( - await c.req.json(), - ); - - const committed = await buyAuctionListing({ - actor: { - userId: user.id, - cultivatorId: cultivator.cultivatorId, - }, - listingId, - quantity, - requestId, - }); - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - const lockErrorResponse = redisLockErrorResponse(error); - if (lockErrorResponse) return lockErrorResponse; - if (error instanceof z.ZodError) { - return c.json({ error: '参数错误', details: error.issues }, 400); - } - - if ( - error instanceof InventoryError || - error instanceof PlayerCommandIdempotencyError - ) - return c.json({ error: error.message }, 409); - if (error instanceof AuctionServiceError) { - return jsonWithStatus( - c, - { error: error.message }, - getAuctionErrorStatus(error), - ); - } - - console.error('Auction Buy API Error:', error); - return c.json({ error: '购买失败,请稍后重试' }, 500); - } -}); - -router.post('/list', requireActiveCultivatorRef(), async (c) => { - const user = c.get('user'); - const cultivator = c.get('activeCultivatorRef'); - if (!user || !cultivator) { - return c.json({ error: '未授权访问' }, 401); - } - - const request = AuctionListSchema.safeParse( - await c.req.json().catch(() => undefined), - ); - if (!request.success) { - return c.json({ error: '参数错误', details: request.error.issues }, 400); - } - - try { - const { - requestId, - revision, - itemId, - price, - quantity, - visibility, - targetCultivatorId, - } = request.data; - - const committed = await listAuctionItem({ - actor: { - userId: user.id, - cultivatorId: cultivator.cultivatorId, - }, - requestId, - revision, - itemId, - price, - quantity, - visibility, - targetCultivatorId, - }); - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - const lockErrorResponse = redisLockErrorResponse(error); - if (lockErrorResponse) return lockErrorResponse; - if ( - error instanceof InventoryError || - error instanceof PlayerCommandIdempotencyError - ) - return c.json({ error: error.message }, 409); - if (error instanceof AuctionServiceError) { - return jsonWithStatus( - c, - { error: error.message }, - getAuctionErrorStatus(error), - ); - } - - console.error('Auction List API Error:', error); - return c.json({ error: '上架失败,请稍后重试' }, 500); - } -}); - -router.post('/list-beast', requireActiveCultivatorRef(), async (c) => { - const user = c.get('user'); - const cultivator = c.get('activeCultivatorRef'); - if (!user || !cultivator) { - return c.json({ error: '未授权访问' }, 401); - } - - const request = AuctionBeastListSchema.safeParse( - await c.req.json().catch(() => undefined), - ); - if (!request.success) { - return c.json({ error: '参数错误', details: request.error.issues }, 400); - } - - try { - const committed = await listAuctionBeast({ - ...request.data, - actor: { userId: user.id, cultivatorId: cultivator.cultivatorId }, - }); - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - const lockErrorResponse = redisLockErrorResponse(error); - if (lockErrorResponse) return lockErrorResponse; - if ( - error instanceof BeastError || - error instanceof InventoryError || - error instanceof PlayerCommandIdempotencyError - ) - return c.json({ error: error.message }, 409); - if (error instanceof AuctionServiceError) { - return jsonWithStatus( - c, - { error: error.message }, - getAuctionErrorStatus(error), - ); - } - - console.error('Auction List API Error:', error); - return c.json({ error: '上架失败,请稍后重试' }, 500); - } -}); - -router.delete('/:id', requireActiveCultivatorRef(), async (c) => { - const user = c.get('user'); - const cultivator = c.get('activeCultivatorRef'); - if (!user || !cultivator) { - return c.json({ error: '未授权访问' }, 401); - } - - try { - const parsedId = z.uuid().safeParse(c.req.param('id')); - if (!parsedId.success) return c.json({ error: '货单标识无效' }, 400); - const listingId = parsedId.data; - const committed = await cancelAuctionListing({ - actor: { - userId: user.id, - cultivatorId: cultivator.cultivatorId, - }, - listingId, - }); - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - const lockErrorResponse = redisLockErrorResponse(error); - if (lockErrorResponse) return lockErrorResponse; - if ( - error instanceof InventoryError || - error instanceof PlayerCommandIdempotencyError - ) - return c.json({ error: error.message }, 409); - if (error instanceof AuctionServiceError) { - return jsonWithStatus( - c, - { error: error.message }, - getAuctionErrorStatus(error), - ); - } - - console.error('Auction Cancel API Error:', error); - return c.json({ error: '下架失败,请稍后重试' }, 500); - } -}); - -export default router; diff --git a/src/server/routes/api/battle-records.router.ts b/src/server/routes/api/battle-records.router.ts deleted file mode 100644 index 2db437e2..00000000 --- a/src/server/routes/api/battle-records.router.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { AppEnv } from '@server/lib/hono/types'; -import { Hono } from 'hono'; - -const router = new Hono(); -// Direct record and share URLs are retired; historical rows stay intact. -router.all('*', (c) => - c.json( - { - success: false, - error: '旧版战绩已停止查看,请前往新版战绩。', - code: 'LEGACY_BATTLE_HISTORY_RETIRED', - }, - 410, - ), -); -export default router; diff --git a/src/server/routes/api/black-market.router.ts b/src/server/routes/api/black-market.router.ts deleted file mode 100644 index 139b2d56..00000000 --- a/src/server/routes/api/black-market.router.ts +++ /dev/null @@ -1,229 +0,0 @@ -import { - redisLockErrorResponse, - requireActiveCultivatorRef, -} from '@server/lib/hono/middleware'; -import { jsonWithStatus } from '@server/lib/hono/response'; -import { streamSseEvents } from '@server/lib/hono/streaming'; -import type { AppEnv } from '@server/lib/hono/types'; -import { blackMarketConversationService } from '@server/lib/services/black-market/BlackMarketConversationService'; -import { - BlackMarketServiceError, - commitBlackMarketPurchase, - completeBlackMarketReply, - getBlackMarketOverview, - leaveBlackMarketSession, - openBlackMarketSession, - prepareBlackMarketInteraction, -} from '@server/lib/services/black-market/BlackMarketService'; -import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; -import { - QiInsufficientError, - QiServiceError, -} from '@server/lib/services/QiService'; -import { BLACK_MARKET_NPC_IDS } from '@shared/types/blackMarket'; -import { Hono, type Context } from 'hono'; -import { z } from 'zod'; - -const OpenSessionSchema = z.object({ - npcId: z.enum(BLACK_MARKET_NPC_IDS), -}); - -const InteractSchema = z - .object({ - message: z.string().trim().min(1).max(240).optional(), - offeredPrice: z.number().int().min(1).max(2_000_000_000).optional(), - version: z.number().int().min(1), - }) - .superRefine((value, context) => { - if (!value.message && !value.offeredPrice) { - context.addIssue({ - code: 'custom', - message: '请说点什么,或给出灵石报价', - }); - } - }); - -const LeaveSchema = z.object({ version: z.number().int().min(1) }); -const CommitSchema = z.object({ - version: z.number().int().min(1), - expectedPrice: z.number().int().min(1).max(2_000_000_000), -}); - -const router = new Hono(); -router.use('*', requireActiveCultivatorRef()); - -function actor(c: Context) { - const active = c.get('activeCultivatorRef'); - if (!active) throw new BlackMarketServiceError(404, '当前没有活跃角色'); - return { userId: active.userId, cultivatorId: active.cultivatorId }; -} - -function errorResponse(c: Context, error: unknown) { - const lockResponse = redisLockErrorResponse(error); - if (lockResponse) return lockResponse; - if (error instanceof z.ZodError) { - return c.json({ error: error.issues[0]?.message || '参数错误' }, 400); - } - if (error instanceof BlackMarketServiceError) { - return jsonWithStatus(c, { error: error.message }, error.status); - } - if (error instanceof QiInsufficientError) { - return c.json( - { - error: error.code, - message: error.message, - required: error.required, - current: error.current, - action: error.action, - }, - 409, - ); - } - if (error instanceof QiServiceError) { - return jsonWithStatus(c, { error: error.message }, error.status); - } - console.error('black market api error:', error); - return c.json({ error: '黑市暂时闭门,请稍后再来' }, 500); -} - -router.get('/:nodeId', async (c) => { - try { - return c.json( - await getBlackMarketOverview({ - actor: actor(c), - nodeId: c.req.param('nodeId'), - }), - ); - } catch (error) { - return errorResponse(c, error); - } -}); - -router.post('/:nodeId/sessions', async (c) => { - try { - const parsed = OpenSessionSchema.parse(await c.req.json()); - const opened = await openBlackMarketSession({ - actor: actor(c), - nodeId: c.req.param('nodeId'), - npcId: parsed.npcId, - }); - return c.json(toPlayerStateMutationResponse(opened)); - } catch (error) { - return errorResponse(c, error); - } -}); - -router.post('/:nodeId/sessions/:sessionId/interact', async (c) => { - try { - const command = InteractSchema.parse(await c.req.json()); - const prepared = await prepareBlackMarketInteraction({ - actor: actor(c), - nodeId: c.req.param('nodeId'), - sessionId: c.req.param('sessionId'), - command, - abortSignal: c.req.raw.signal, - }); - return streamSseEvents( - c, - async (stream, isAborted, signal) => { - await stream.writeSSE({ - data: JSON.stringify({ - type: 'resolved', - result: prepared.result, - messageId: prepared.messageId, - gesture: prepared.gesture, - fallbackBody: prepared.fallbackBody, - }), - }); - if (isAborted()) { - return; - } - - let body = ''; - try { - const reply = blackMarketConversationService.streamTurnReply({ - context: prepared.replyContext, - proposal: prepared.proposal, - negotiationOutcome: prepared.negotiationOutcome, - abortSignal: signal, - }); - for await (const chunk of reply.textStream) { - if (isAborted()) { - throw new Error('black market reply stream disconnected'); - } - body += chunk; - await stream.writeSSE({ - data: JSON.stringify({ - type: 'reply-chunk', - messageId: prepared.messageId, - text: chunk, - }), - }); - } - body = body.trim(); - if (!body) throw new Error('empty black market reply'); - if (isAborted()) { - throw new Error('black market reply stream disconnected'); - } - await completeBlackMarketReply({ - sessionId: prepared.sessionId, - messageId: prepared.messageId, - body, - }); - await stream.writeSSE({ - data: JSON.stringify({ - type: 'reply-complete', - messageId: prepared.messageId, - body, - }), - }); - } catch (error) { - console.warn('[black-market] reply stream fallback', { error }); - await stream.writeSSE({ - data: JSON.stringify({ - type: 'reply-error', - messageId: prepared.messageId, - fallbackBody: prepared.fallbackBody, - }), - }); - } - }, - ); - } catch (error) { - return errorResponse(c, error); - } -}); - -router.post('/:nodeId/sessions/:sessionId/commit', async (c) => { - try { - const parsed = CommitSchema.parse(await c.req.json()); - const committed = await commitBlackMarketPurchase({ - actor: actor(c), - nodeId: c.req.param('nodeId'), - sessionId: c.req.param('sessionId'), - version: parsed.version, - expectedPrice: parsed.expectedPrice, - }); - return c.json(toPlayerStateMutationResponse(await committed)); - } catch (error) { - return errorResponse(c, error); - } -}); - -router.post('/:nodeId/sessions/:sessionId/leave', async (c) => { - try { - const parsed = LeaveSchema.parse(await c.req.json()); - return c.json( - await leaveBlackMarketSession({ - actor: actor(c), - nodeId: c.req.param('nodeId'), - sessionId: c.req.param('sessionId'), - version: parsed.version, - }), - ); - } catch (error) { - return errorResponse(c, error); - } -}); - -export default router; diff --git a/src/server/routes/api/captcha.router.ts b/src/server/routes/api/captcha.router.ts deleted file mode 100644 index 52752d9f..00000000 --- a/src/server/routes/api/captcha.router.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { - createAltchaChallenge, - isAltchaAction, - isAltchaServerEnabled, -} from '@server/lib/auth/altcha'; -import type { AppEnv } from '@server/lib/hono/types'; -import { Hono } from 'hono'; - -const router = new Hono(); - -router.get('/config', (c) => { - c.header('Cache-Control', 'no-store'); - return c.json({ enabled: isAltchaServerEnabled() }); -}); - -router.get('/challenge', async (c) => { - const action = c.req.query('action') ?? ''; - if (!isAltchaAction(action)) { - return c.json({ success: false, error: '无效的人机验证场景' }, 400); - } - - if (!isAltchaServerEnabled()) { - return c.json({ success: false, error: '人机验证服务未配置' }, 503); - } - - try { - const challenge = await createAltchaChallenge(action); - c.header('Cache-Control', 'no-store'); - return c.json(challenge); - } catch (error) { - console.error('[altcha] challenge creation failed', error); - return c.json({ success: false, error: '人机验证服务暂不可用' }, 503); - } -}); - -export default router; diff --git a/src/server/routes/api/combat-v6-arena.router.ts b/src/server/routes/api/combat-v6-arena.router.ts deleted file mode 100644 index 315d85a5..00000000 --- a/src/server/routes/api/combat-v6-arena.router.ts +++ /dev/null @@ -1,241 +0,0 @@ -import { - requireActiveCultivatorRef, - validateJson, -} from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { isAllowedRealtimeOrigin } from '@server/lib/http/realtimeOrigin'; -import { - arenaSocketState, - subscribeArenaSpectators, - subscribeArenaV6, -} from '@server/lib/services/combat-v6/CombatV6ArenaBroadcast'; -import { - arenaReplayV6, - ArenaV6Error, - ownedArenaV6, - submitArenaV6, - watchedArenaV6, -} from '@server/lib/services/combat-v6/CombatV6ArenaService'; -import { CombatV6ArenaStore } from '@server/lib/services/combat-v6/CombatV6ArenaStore'; -import { arenaView } from '@shared/combat-v6/arena'; -import { - ArenaV6SubmitSchema, - type ArenaV6Submit, -} from '@shared/contracts/combatV6Arena'; -import { Hono } from 'hono'; -import { upgradeWebSocket } from 'hono/bun'; -import { z } from 'zod'; - -const router = new Hono(); -const store = new CombatV6ArenaStore(); -const connectionCounts = new Map(); -router.use('*', requireActiveCultivatorRef()); -router.onError((error, c) => { - if (error instanceof ArenaV6Error) - return c.json({ success: false, error: error.message }, error.status); - if (error instanceof z.ZodError) - return c.json({ success: false, error: '请求参数无效' }, 400); - console.error('[arena-v6] request failed', error); - return c.json({ success: false, error: '战斗请求失败,请刷新重试' }, 500); -}); -router.on('GET', ['/:battleId', '/:battleId/watch'], async (c) => { - const id = z.uuid().parse(c.req.param('battleId')); - c.header('Cache-Control', 'private, no-store'); - const { runtime, participant } = await ( - c.req.path.endsWith('/watch') ? watchedArenaV6 : ownedArenaV6 - )(id, c.get('activeCultivatorRef')!); - const cursor = z.coerce - .number() - .int() - .min(-1) - .optional() - .parse(c.req.query('afterEventSeq')); - const last = runtime.lastResults[participant.unitId]; - const incremental = - last?.revision === runtime.revision && - last.playback?.fromEventSeq === cursor; - const view = incremental - ? { - ...last, - serverNow: Date.now(), - events: last.events.filter((e) => e.seq > cursor!), - } - : arenaView(runtime, participant.unitId, Date.now()); - const unchanged = cursor === view.latestEventSeq; - return c.json({ - success: true, - data: { - session: unchanged ? { ...view, events: [], playback: undefined } : view, - full: !incremental && !unchanged, - }, - }); -}); -router.post( - '/:battleId/commands', - validateJson(ArenaV6SubmitSchema), - async (c) => { - const id = z.uuid().parse(c.req.param('battleId')); - return c.json({ - success: true, - data: await submitArenaV6( - id, - c.get('activeCultivatorRef')!, - c.get('validatedJson') as ArenaV6Submit, - ), - }); - }, -); -router.get('/:battleId/replay', async (c) => - c.json({ - success: true, - data: await arenaReplayV6( - z.uuid().parse(c.req.param('battleId')), - c.get('activeCultivatorRef')!, - ), - }), -); -router.on( - 'GET', - ['/:battleId/socket', '/:battleId/watch/socket'], - async (c, next) => { - if (!isAllowedRealtimeOrigin(c.req.header('origin'))) - return c.json({ error: 'Origin forbidden' }, 403); - const id = z.uuid().parse(c.req.param('battleId')); - await ( - c.req.path.endsWith('/watch/socket') ? watchedArenaV6 : ownedArenaV6 - )(id, c.get('activeCultivatorRef')!); - return next(); - }, - upgradeWebSocket((c) => { - const id = c.req.param('battleId')!; - const actor = c.get('activeCultivatorRef')!; - const spectator = c.req.path.endsWith('/watch/socket'); - const authorize = spectator ? watchedArenaV6 : ownedArenaV6; - const connection = crypto.randomUUID(); - let viewerId: string | undefined; - let dispose: (() => void) | undefined; - let heartbeat: ReturnType | undefined; - let closed = false; - let admitted = false; - let lastPong = Date.now(); - let lastRevision = -1; - let chain = Promise.resolve(); - const cleanup = () => { - closed = true; - if (admitted) { - admitted = false; - const count = (connectionCounts.get(actor.userId) ?? 1) - 1; - if (count) connectionCounts.set(actor.userId, count); - else connectionCounts.delete(actor.userId); - } - clearInterval(heartbeat); - dispose?.(); - if (viewerId && !spectator) - void store - .disconnect(`${id}:${viewerId}`, connection) - .catch((error) => - console.warn('[arena-v6] disconnect failed', error), - ); - }; - return { - async onOpen(_event, ws) { - try { - if ((connectionCounts.get(actor.userId) ?? 0) >= 4) { - ws.close(1008, 'too many connections'); - return; - } - admitted = true; - connectionCounts.set( - actor.userId, - (connectionCounts.get(actor.userId) ?? 0) + 1, - ); - const owned = await authorize(id, actor); - viewerId = owned.participant.unitId; - dispose = spectator - ? await subscribeArenaSpectators(id, { - ...actor, - send: (message) => { - if (closed) return; - if (ws.raw.getBufferedAmount() > 512000) { - ws.close(4001, 'slow consumer'); - cleanup(); - } else ws.send(message); - }, - close: () => { - ws.close(1008, 'spectator access ended'); - cleanup(); - }, - }) - : await subscribeArenaV6(id, () => { - chain = chain - .then(async () => { - if (closed) return; - const { runtime } = await authorize(id, actor); - if (runtime.revision <= lastRevision) return; - lastRevision = runtime.revision; - ws.send( - JSON.stringify(arenaSocketState(runtime, viewerId!)), - ); - }) - .catch(() => { - if (!closed) ws.close(1011, 'reconnect'); - }); - }); - if (closed) { - dispose(); - return; - } - if (!spectator) await store.touch(`${id}:${viewerId}`, connection); - if (closed) { - if (!spectator) - await store.disconnect(`${id}:${viewerId}`, connection); - return; - } - ws.send(JSON.stringify({ type: 'ready', serverNow: Date.now() })); - heartbeat = setInterval(() => { - if (Date.now() - lastPong > 50000) { - ws.close(4000, 'heartbeat timeout'); - cleanup(); - return; - } - ws.send(JSON.stringify({ type: 'ping', serverNow: Date.now() })); - // NATS Core is best effort: periodically repair a missed terminal push. - chain = chain - .then(async () => { - if (closed) return; - const { runtime } = await authorize(id, actor); - ws.send( - JSON.stringify({ - type: 'resync', - revision: runtime.revision, - }), - ); - }) - .catch(() => { - if (!closed) ws.close(1011, 'reconnect'); - }); - }, 25000); - } catch { - cleanup(); - ws.close(1011, 'reconnect'); - } - }, - onMessage(event, ws) { - if (typeof event.data !== 'string' || event.data.length > 128) { - ws.close(1008, 'invalid message'); - return; - } - if (event.data === 'pong') { - lastPong = Date.now(); - if (viewerId && !spectator) - void store - .touch(`${id}:${viewerId}`, connection) - .catch(() => ws.close(1011, 'reconnect')); - } - }, - onClose: cleanup, - onError: cleanup, - }; - }), -); -export default router; diff --git a/src/server/routes/api/combat-v6-auto-strategy.router.ts b/src/server/routes/api/combat-v6-auto-strategy.router.ts deleted file mode 100644 index 68317278..00000000 --- a/src/server/routes/api/combat-v6-auto-strategy.router.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { - getValidatedJson, - requireActiveCultivatorRef, - validateJson, -} from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { - AutoStrategyError, - readCombatAutoStrategy, - resetCombatAutoStrategy, - saveCombatAutoStrategy, -} from '@server/lib/services/combat-v6/CombatV6AutoStrategyService'; -import { CombatV6BuildError } from '@server/lib/services/combat-v6/CombatV6BuildService'; -import { SaveAutoStrategySchema } from '@shared/combat-v6/auto-strategy'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const router = new Hono(); -const mutation = z.strictObject({ - pathId: z.string().min(1).max(160), - strategy: SaveAutoStrategySchema, -}); -const reset = z.strictObject({ pathId: z.string().min(1).max(160) }); -router.use('*', requireActiveCultivatorRef()); -router.use('*', async (c, next) => { - c.header('Cache-Control', 'no-store'); - await next(); -}); -router.onError((error, c) => { - if (error instanceof AutoStrategyError || error instanceof CombatV6BuildError) - return c.json({ success: false, error: error.message }, 409); - if (error instanceof z.ZodError) - return c.json({ success: false, error: '策略格式无效' }, 400); - console.error('[combat-auto-strategy]', error); - return c.json({ success: false, error: '策略操作失败,请稍后重试' }, 500); -}); -router.get('/', async (c) => - c.json({ - success: true, - data: await readCombatAutoStrategy( - c.get('activeCultivatorRef')!.cultivatorId, - ), - }), -); -router.put('/', validateJson(mutation), async (c) => { - const { pathId, strategy } = getValidatedJson>(c); - return c.json({ - success: true, - data: await saveCombatAutoStrategy( - c.get('activeCultivatorRef')!.cultivatorId, - pathId, - strategy, - ), - }); -}); -router.delete('/', validateJson(reset), async (c) => { - const { pathId } = getValidatedJson>(c); - return c.json({ - success: true, - data: await resetCombatAutoStrategy( - c.get('activeCultivatorRef')!.cultivatorId, - pathId, - ), - }); -}); -export default router; diff --git a/src/server/routes/api/combat-v6-breakthrough.router.ts b/src/server/routes/api/combat-v6-breakthrough.router.ts deleted file mode 100644 index 4e737398..00000000 --- a/src/server/routes/api/combat-v6-breakthrough.router.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { - redisLockErrorResponse, - requireActiveCultivatorRef, -} from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { - changeBreakthroughBattle, - getBreakthroughBattle, -} from '@server/lib/services/combat-v6/CombatV6BreakthroughService'; -import { CombatAutoRequestSchema } from '@shared/combat-v6/auto'; -import { - CombatV6TrainingCommandRequestSchema, - CombatV6TrainingEventsQuerySchema, - CombatV6TrainingRevisionRequestSchema, -} from '@shared/contracts/combatV6'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const router = new Hono(); -router.use('*', requireActiveCultivatorRef()); -router.onError((error, c) => { - const lock = redisLockErrorResponse(error); - if (lock) return lock; - return c.json( - { error: error instanceof z.ZodError ? '突破战斗参数无效' : error.message }, - error instanceof z.ZodError ? 400 : 409, - ); -}); -router.get('/tasks/:taskId/sessions/current', async (c) => - c.json({ - success: true, - data: await getBreakthroughBattle( - c.get('activeCultivatorRef')!.cultivatorId, - z.uuid().parse(c.req.param('taskId')), - ), - }), -); -router.get('/tasks/:taskId/sessions/:id', async (c) => { - const query = CombatV6TrainingEventsQuerySchema.parse(c.req.query()); - return c.json({ - success: true, - data: await getBreakthroughBattle( - c.get('activeCultivatorRef')!.cultivatorId, - z.uuid().parse(c.req.param('taskId')), - z.uuid().parse(c.req.param('id')), - query.afterEventSeq, - ), - }); -}); -router.put('/tasks/:taskId/sessions/:id/commands/:unitId', async (c) => { - const input = CombatV6TrainingCommandRequestSchema.parse(await c.req.json()); - return c.json({ - success: true, - data: await changeBreakthroughBattle( - c.get('activeCultivatorRef')!, - z.uuid().parse(c.req.param('taskId')), - z.uuid().parse(c.req.param('id')), - input.expectedRevision, - { unitId: c.req.param('unitId'), commands: input.commands }, - ), - }); -}); -router.post('/tasks/:taskId/sessions/:id/resolve', async (c) => { - const input = CombatV6TrainingRevisionRequestSchema.parse(await c.req.json()); - return c.json({ - success: true, - data: await changeBreakthroughBattle( - c.get('activeCultivatorRef')!, - z.uuid().parse(c.req.param('taskId')), - z.uuid().parse(c.req.param('id')), - input.expectedRevision, - ), - }); -}); -router.post('/tasks/:taskId/sessions/:id/auto', async (c) => { - const input = CombatAutoRequestSchema.parse(await c.req.json()); - return c.json({ - success: true, - data: await changeBreakthroughBattle( - c.get('activeCultivatorRef')!, - z.uuid().parse(c.req.param('taskId')), - z.uuid().parse(c.req.param('id')), - input.expectedRevision, - undefined, - input.round, - ), - }); -}); -export default router; diff --git a/src/server/routes/api/combat-v6-manuals.router.ts b/src/server/routes/api/combat-v6-manuals.router.ts deleted file mode 100644 index 08f31b13..00000000 --- a/src/server/routes/api/combat-v6-manuals.router.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { - getValidatedJson, - redisLockErrorResponse, - requireActiveCultivatorRef, - validateJson, -} from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { InventoryError } from '@server/lib/services/InventoryService'; -import { - mutateManuals, - readManuals, -} from '@server/lib/services/combat-v6/CombatV6ManualService'; -import { ManualActionSchema } from '@shared/contracts/combatV6Manuals'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const router = new Hono(); -router.use('*', requireActiveCultivatorRef()); -router.use('*', async (c, next) => { - c.header('Cache-Control', 'no-store'); - await next(); -}); -router.onError((error, c) => { - const lock = redisLockErrorResponse(error); - if (lock) return lock; - if (error instanceof z.ZodError) - return c.json({ success: false, error: '请求参数无效' }, 400); - if (error instanceof InventoryError) - return c.json({ success: false, error: error.message }, 409); - console.error('[manuals] request failed', error); - return c.json( - { success: false, error: '请求未完成,请刷新核对道印与玉简' }, - 500, - ); -}); -router.get('/', async (c) => - c.json({ - success: true, - data: await readManuals(c.get('activeCultivatorRef')!.cultivatorId), - }), -); -router.post('/', validateJson(ManualActionSchema), async (c) => - c.json({ - success: true, - ...(await mutateManuals( - c.get('activeCultivatorRef')!.cultivatorId, - getValidatedJson(c), - )), - }), -); -export default router; diff --git a/src/server/routes/api/combat-v6-sect-tasks.router.ts b/src/server/routes/api/combat-v6-sect-tasks.router.ts deleted file mode 100644 index 5eb3bbe9..00000000 --- a/src/server/routes/api/combat-v6-sect-tasks.router.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { - redisLockErrorResponse, - requireActiveCultivatorRef, -} from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { - changeSectTaskBattle, - getSectTaskBattle, -} from '@server/lib/services/combat-v6/CombatV6SectTaskService'; -import { CombatAutoRequestSchema } from '@shared/combat-v6/auto'; -import { - CombatV6TrainingCommandRequestSchema, - CombatV6TrainingEventsQuerySchema, - CombatV6TrainingRevisionRequestSchema, -} from '@shared/contracts/combatV6'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const router = new Hono(); -router.use('*', requireActiveCultivatorRef()); -router.onError((error, c) => { - const lock = redisLockErrorResponse(error); - if (lock) return lock; - return c.json( - { error: error instanceof z.ZodError ? '宗门战斗参数无效' : error.message }, - error instanceof z.ZodError ? 400 : 409, - ); -}); -router.get('/sessions/current', async (c) => - c.json({ - success: true, - data: await getSectTaskBattle(c.get('activeCultivatorRef')!.cultivatorId), - }), -); -router.get('/sessions/:id', async (c) => { - const query = CombatV6TrainingEventsQuerySchema.parse(c.req.query()); - return c.json({ - success: true, - data: await getSectTaskBattle( - c.get('activeCultivatorRef')!.cultivatorId, - z.uuid().parse(c.req.param('id')), - query.afterEventSeq, - ), - }); -}); -router.put('/sessions/:id/commands/:unitId', async (c) => { - const input = CombatV6TrainingCommandRequestSchema.parse(await c.req.json()); - return c.json({ - success: true, - data: await changeSectTaskBattle( - c.get('activeCultivatorRef')!, - z.uuid().parse(c.req.param('id')), - input.expectedRevision, - { unitId: c.req.param('unitId'), commands: input.commands }, - ), - }); -}); -router.post('/sessions/:id/resolve', async (c) => { - const input = CombatV6TrainingRevisionRequestSchema.parse(await c.req.json()); - return c.json({ - success: true, - data: await changeSectTaskBattle( - c.get('activeCultivatorRef')!, - z.uuid().parse(c.req.param('id')), - input.expectedRevision, - ), - }); -}); -router.post('/sessions/:id/auto', async (c) => { - const input = CombatAutoRequestSchema.parse(await c.req.json()); - return c.json({ - success: true, - data: await changeSectTaskBattle( - c.get('activeCultivatorRef')!, - z.uuid().parse(c.req.param('id')), - input.expectedRevision, - undefined, - input.round, - ), - }); -}); -export default router; diff --git a/src/server/routes/api/combat-v6-sect.router.ts b/src/server/routes/api/combat-v6-sect.router.ts deleted file mode 100644 index 07ecf1f1..00000000 --- a/src/server/routes/api/combat-v6-sect.router.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { - getValidatedJson, - redisLockErrorResponse, - requireActiveCultivatorRef, - validateJson, -} from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { InventoryError } from '@server/lib/services/InventoryService'; -import { CombatV6BuildError } from '@server/lib/services/combat-v6/CombatV6BuildService'; -import { - mutateSectV6, - readSectV6, -} from '@server/lib/services/combat-v6/CombatV6SectService'; -import { SectV6ActionSchema } from '@shared/contracts/combatV6Sect'; -import { SectV6RuleError } from '@shared/engine/combat-v6/sect-progression'; -import { Hono } from 'hono'; -import { z } from 'zod'; -const router = new Hono(); -router.use('*', requireActiveCultivatorRef()); -router.use('*', async (c, next) => { - c.header('Cache-Control', 'no-store'); - await next(); -}); -router.onError((error, c) => { - const lock = redisLockErrorResponse(error); - if (lock) return lock; - if (error instanceof z.ZodError) - return c.json({ success: false, error: '参数无效' }, 400); - if ( - error instanceof InventoryError || - error instanceof SectV6RuleError || - error instanceof CombatV6BuildError - ) - return c.json({ success: false, error: error.message }, 409); - console.error('[sect-v6]', error); - return c.json( - { success: false, error: '请求未完成,请刷新核对传承与资源' }, - 500, - ); -}); -router.get('/', async (c) => - c.json({ - success: true, - data: await readSectV6(c.get('activeCultivatorRef')!.cultivatorId), - }), -); -router.post('/', validateJson(SectV6ActionSchema), async (c) => - c.json({ - success: true, - ...(await mutateSectV6( - c.get('activeCultivatorRef')!.cultivatorId, - getValidatedJson(c), - )), - }), -); -export default router; diff --git a/src/server/routes/api/combat-v6-shares.router.ts b/src/server/routes/api/combat-v6-shares.router.ts deleted file mode 100644 index 7a32dd4e..00000000 --- a/src/server/routes/api/combat-v6-shares.router.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { AppEnv } from '@server/lib/hono/types'; -import { findSharedCombatV6Replay } from '@server/lib/repositories/combatV6ReplayRepository'; -import { combatV6ReplayView } from '@shared/combat-v6/replay'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const router = new Hono(); - -router.get('/:shareCode', async (c) => { - const parsed = z.uuid().safeParse(c.req.param('shareCode')); - if (!parsed.success) return c.json({ success: false, error: '分享链接无效' }, 404); - const archive = await findSharedCombatV6Replay(parsed.data); - const viewer = archive?.replay?.participants.find( - (participant) => participant.cultivatorId === archive.shareViewerCultivatorId, - ); - if (!archive?.replay || !viewer) return c.json({ success: false, error: '战斗回放不存在' }, 404); - c.header('Cache-Control', 'public, max-age=60'); - return c.json({ - success: true, - data: combatV6ReplayView(archive.replay, viewer.cultivatorId, viewer.userId), - }); -}); - -export default router; diff --git a/src/server/routes/api/combat-v6.router.ts b/src/server/routes/api/combat-v6.router.ts deleted file mode 100644 index ba5bca94..00000000 --- a/src/server/routes/api/combat-v6.router.ts +++ /dev/null @@ -1,746 +0,0 @@ -import { - QiInsufficientError, - QiServiceError, -} from '@server/lib/services/QiService'; -import { PlayerCommandIdempotencyError } from '@server/lib/services/CommandExecutors'; -import { db } from '@server/lib/drizzle/db'; -import { - getValidatedJson, - redisLockErrorResponse, - requireActiveCultivatorRef, - validateJson, -} from '@server/lib/hono/middleware'; -import { jsonWithStatus } from '@server/lib/hono/response'; -import type { AppEnv } from '@server/lib/hono/types'; -import { readBeastRoster } from '@server/lib/repositories/combatV6BeastRepository'; -import { - findOwnedCombatV6Replay, - createCombatV6ReplayShare, - listOwnedCombatV6Replays, -} from '@server/lib/repositories/combatV6ReplayRepository'; -import { - InventoryError, - mutateInventory, - readInventory, -} from '@server/lib/services/InventoryService'; -import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; -import { readResourceWithMeta } from '@server/lib/services/ResourceReadService'; -import { readCombatActivityNotice } from '@server/lib/services/combat-v6/CombatActivityNotice'; -import { CombatV6ArenaStore } from '@server/lib/services/combat-v6/CombatV6ArenaStore'; -import { - BeastError, - allocateBeastPoints, - claimStarterBeast, - fuseOwnedBeasts, - readBeastFusion, - releaseBeast, - renameBeast, - restBeast, - updateBeastLineup, -} from '@server/lib/services/combat-v6/CombatV6BeastService'; -import { - CombatV6BuildError, - getSectCombatView, - selectInitialSectPath, -} from '@server/lib/services/combat-v6/CombatV6BuildService'; -import { - COMBAT_V6_TRAINING_CONTENT_VIEW, - CombatV6TrainingSessionError, - combatV6TrainingSessionStore, -} from '@server/lib/services/combat-v6/CombatV6TrainingSessionService'; -import { - WildError, - wildSessions, -} from '@server/lib/services/combat-v6/CombatV6WildSessionService'; -import { CombatAutoRequestSchema } from '@shared/combat-v6/auto'; -import { combatV6ReplayView } from '@shared/combat-v6/replay'; -import { - COMBAT_V6_REPLAY_ERROR_CODE, - SectPathSelectionRequestSchema, - CombatV6ReplayParamsSchema, - CombatV6TrainingCommandParamsSchema, - CombatV6TrainingCommandRequestSchema, - CombatV6TrainingCreateRequestSchema, - CombatV6TrainingEventsQuerySchema, - CombatV6TrainingRevisionRequestSchema, - CombatV6TrainingSessionParamsSchema, -} from '@shared/contracts/combatV6'; -import { - BeastAllocateSchema, - BeastClaimSchema, - BeastFusionRequestSchema, - BeastLineupRequestSchema, - BeastRenameSchema, - BeastRestSchema, -} from '@shared/contracts/combatV6Beasts'; -import { CombatV6HistoryQuerySchema } from '@shared/contracts/combatV6Replay'; -import { - WildExploreRequestSchema, - WildStartRequestSchema, -} from '@shared/contracts/combatV6Wild'; -import { - InventoryActionSchema, - InventoryQuerySchema, -} from '@shared/contracts/inventory'; -import { TrainingHostError } from '@shared/engine/combat-v6/encounter'; -import { InventoryRuleError } from '@shared/inventory'; -import { Hono, type Context } from 'hono'; -import { z } from 'zod'; - -const router = new Hono(); -const arenaReplayStore = new CombatV6ArenaStore(); -router.use('*', requireActiveCultivatorRef()); -router.get('/activity', async (c) => - c.json({ - success: true, - data: await readCombatActivityNotice( - c.get('activeCultivatorRef')!.cultivatorId, - ), - }), -); -router.post('/wild/sessions/:id/auto', async (c) => { - const input = CombatAutoRequestSchema.parse(await c.req.json()); - const id = z.uuid().parse(c.req.param('id')); - try { - return c.json({ - success: true, - data: await wildSessions.resolve( - c.get('activeCultivatorRef')!, - id, - input.expectedRevision, - input.round, - ), - }); - } catch (error) { - return c.json( - { error: error instanceof Error ? error.message : '自动指令提交失败' }, - 409, - ); - } -}); - -router.post('/training/sessions/:id/auto', async (c) => { - const input = CombatAutoRequestSchema.parse(await c.req.json()); - const id = z.uuid().parse(c.req.param('id')); - try { - return c.json({ - success: true, - data: await combatV6TrainingSessionStore.resolve( - c.get('activeCultivatorRef')!, - id, - input.expectedRevision, - input.round, - ), - }); - } catch (error) { - return c.json( - { error: error instanceof Error ? error.message : '自动指令提交失败' }, - 409, - ); - } -}); - -function actor(c: Context) { - const ref = c.get('activeCultivatorRef'); - if (!ref) { - throw new CombatV6BuildError( - 'COMBAT_V6_ACTIVE_MEMBERSHIP_REQUIRED', - '当前没有活跃角色', - 404, - ); - } - return { userId: ref.userId, cultivatorId: ref.cultivatorId }; -} - -function errorResponse(c: Context, error: unknown) { - if (error instanceof QiInsufficientError) - return c.json( - { - success: false, - code: error.code, - error: '天地灵气不足,待自然恢复或使用恢复符箓后再试。', - }, - 409, - ); - if (error instanceof QiServiceError || error instanceof PlayerCommandIdempotencyError) - return jsonWithStatus(c, { success: false, error: error.message }, error.status); - - if (error instanceof InventoryError || error instanceof InventoryRuleError) - return c.json({ success: false, error: error.message }, 409); - if (error instanceof BeastError) - return c.json({ success: false, error: error.message }, error.status); - const coordinationError = redisLockErrorResponse(error); - if (coordinationError) return coordinationError; - if (error instanceof TrainingHostError) - return c.json( - { - success: false, - code: 'WILD_COMMAND_NOT_ALLOWED', - error: error.message, - }, - 400, - ); - if (error instanceof z.ZodError) { - return c.json( - { - success: false, - code: 'INVALID_REQUEST', - error: error.issues[0]?.message ?? '参数错误', - details: error.issues, - }, - 400, - ); - } - if ( - error instanceof CombatV6BuildError || - error instanceof WildError || - error instanceof CombatV6TrainingSessionError - ) { - return jsonWithStatus( - c, - { success: false, code: error.code, error: error.message }, - error.status, - ); - } - console.error('combat-v6 api error:', error); - return c.json( - { - success: false, - code: 'COMBAT_V6_INTERNAL_ERROR', - error: '练功房暂不可用,请稍后再试', - }, - 500, - ); -} - -router.get('/inventory', async (c) => { - c.header('Cache-Control', 'no-store'); - try { - const query = InventoryQuerySchema.parse(c.req.query()); - const owner = actor(c).cultivatorId; - if (query.location === 'bag' && query.kind === 'all' && !query.search) { - return c.json(await readResourceWithMeta( - { kind: 'cultivator', id: owner }, - 'inventory.bag', - (tx) => readInventory(owner, query, tx), - )); - } - return c.json({ - success: true, - data: await readInventory(owner, query), - }); - } catch (error) { - return errorResponse(c, error); - } -}); -router.post('/inventory', async (c) => { - try { - return c.json({ - success: true, - ...(await mutateInventory( - actor(c).cultivatorId, - InventoryActionSchema.parse(await c.req.json()), - )), - }); - } catch (error) { - return errorResponse(c, error); - } -}); -router.get('/beasts', async (c) => { - c.header('Cache-Control', 'no-store'); - try { - return c.json({ - success: true, - data: await readBeastRoster(actor(c).cultivatorId, db), - }); - } catch (error) { - return errorResponse(c, error); - } -}); -router.get('/beasts/fusions/:requestId', async (c) => { - c.header('Cache-Control', 'no-store'); - try { - return c.json({ - success: true, - data: await readBeastFusion( - actor(c).cultivatorId, - z.uuid().parse(c.req.param('requestId')), - ), - }); - } catch (error) { - return errorResponse(c, error); - } -}); -router.post( - '/beasts/fuse', - validateJson(BeastFusionRequestSchema), - async (c) => { - try { - const input = - getValidatedJson>(c); - return c.json({ - success: true, - data: await fuseOwnedBeasts(actor(c).cultivatorId, input), - }); - } catch (error) { - if (error instanceof BeastError) - return c.json( - { - success: false, - error: error.message, - code: 'BEAST_FUSION_REJECTED', - }, - 409, - ); - return errorResponse(c, error); - } - }, -); - -router.post('/beasts/claim', async (c) => { - try { - const input = BeastClaimSchema.parse(await c.req.json()); - return c.json({ - success: true, - data: await claimStarterBeast(actor(c).cultivatorId, input.speciesId), - }); - } catch (error) { - return errorResponse(c, error); - } -}); -router.put('/beasts/lineup', async (c) => { - try { - return c.json({ - success: true, - data: await updateBeastLineup( - actor(c).cultivatorId, - BeastLineupRequestSchema.parse(await c.req.json()), - ), - }); - } catch (error) { - return errorResponse(c, error); - } -}); -router.post('/beasts/rest', async (c) => { - try { - const input = BeastRestSchema.parse(await c.req.json()); - return c.json({ - success: true, - data: await restBeast( - actor(c).cultivatorId, - input.beastId, - input.expectedRevision, - ), - }); - } catch (error) { - return errorResponse(c, error); - } -}); - -router.post('/beasts/allocate', async (c) => { - try { - const input = BeastAllocateSchema.parse(await c.req.json()); - return c.json({ - success: true, - data: await allocateBeastPoints( - actor(c).cultivatorId, - input.beastId, - input.expectedRevision, - input.points, - ), - }); - } catch (error) { - return errorResponse(c, error); - } -}); -router.post('/beasts/rename', validateJson(BeastRenameSchema), async (c) => { - try { - const input = getValidatedJson>(c); - return c.json({ - success: true, - data: await renameBeast( - actor(c).cultivatorId, - input.beastId, - input.expectedRevision, - input.name, - ), - }); - } catch (error) { - return errorResponse(c, error); - } -}); -router.post('/beasts/release', async (c) => { - try { - const input = BeastRestSchema.parse(await c.req.json()); - return c.json({ - success: true, - data: await releaseBeast( - actor(c).cultivatorId, - input.beastId, - input.expectedRevision, - ), - }); - } catch (error) { - return errorResponse(c, error); - } -}); - -router.get('/sect/state', async (c) => { - try { - const current = actor(c); - return c.json( - await readResourceWithMeta( - { kind: 'cultivator', id: current.cultivatorId }, - 'player.sect-combat', - (tx) => getSectCombatView(current.cultivatorId, tx), - ), - ); - } catch (error) { - return errorResponse(c, error); - } -}); - -router.post('/sect/path', async (c) => { - try { - const input = SectPathSelectionRequestSchema.parse( - await c.req.json(), - ); - return c.json( - toPlayerStateMutationResponse( - await selectInitialSectPath(actor(c), input), - ), - ); - } catch (error) { - return errorResponse(c, error); - } -}); - -router.get('/training/content', (c) => - c.json({ success: true, data: COMBAT_V6_TRAINING_CONTENT_VIEW }), -); - -router.get('/replays/:battleId', async (c) => { - try { - c.header('Cache-Control', 'private, no-store'); - const params = CombatV6ReplayParamsSchema.parse(c.req.param()); - const current = actor(c); - const archived = await findOwnedCombatV6Replay( - params.battleId, - current.cultivatorId, - ); - if ( - archived?.replay && - archived.replay.participants.some( - (p) => - p.cultivatorId === current.cultivatorId && - p.userId === current.userId, - ) - ) - return c.json({ - success: true, - data: combatV6ReplayView( - archived.replay, - current.cultivatorId, - current.userId, - ), - }); - const arena = await arenaReplayStore.get(params.battleId); - if ( - arena?.stage === 'finished' && - arena.participants.some( - (p) => - p.userId === current.userId && - p.cultivatorId === current.cultivatorId, - ) - ) - return c.json( - { - success: false, - code: COMBAT_V6_REPLAY_ERROR_CODE.Pending, - error: '战斗回放正在归档,请稍后重试', - }, - 202, - ); - return c.json( - { - success: false, - code: COMBAT_V6_REPLAY_ERROR_CODE.NotFound, - error: '战斗回放不存在', - }, - 404, - ); - } catch (error) { - return errorResponse(c, error); - } -}); - -router.post('/replays/:battleId/share', async (c) => { - const params = CombatV6ReplayParamsSchema.parse(c.req.param()); - const current = actor(c); - const shareCode = await createCombatV6ReplayShare(params.battleId, current.cultivatorId, current.userId); - if (!shareCode) return c.json({ success: false, error: '战斗回放不存在' }, 404); - return c.json({ success: true, data: { shareCode } }); -}); - -router.get('/replays', async (c) => { - try { - const query = CombatV6HistoryQuerySchema.parse(c.req.query()); - c.header('Cache-Control', 'private, no-store'); - return c.json({ - success: true, - data: await listOwnedCombatV6Replays(actor(c).cultivatorId, query), - }); - } catch (error) { - return errorResponse(c, error); - } -}); - -router.get('/training/sessions/current', async (c) => { - try { - const query = CombatV6TrainingEventsQuerySchema.parse(c.req.query()); - return c.json({ - success: true, - data: await combatV6TrainingSessionStore.current( - actor(c), - query.afterEventSeq, - ), - }); - } catch (error) { - return errorResponse(c, error); - } -}); - -router.post('/training/sessions', async (c) => { - try { - const input = CombatV6TrainingCreateRequestSchema.parse(await c.req.json()); - return c.json({ - success: true, - data: await combatV6TrainingSessionStore.create( - actor(c), - input.encounterId, - input.tier, - ), - }); - } catch (error) { - return errorResponse(c, error); - } -}); - -router.get('/training/sessions/:sessionId', async (c) => { - try { - const params = CombatV6TrainingSessionParamsSchema.parse(c.req.param()); - const query = CombatV6TrainingEventsQuerySchema.parse(c.req.query()); - return c.json({ - success: true, - data: await combatV6TrainingSessionStore.get( - actor(c), - params.sessionId, - query.afterEventSeq, - ), - }); - } catch (error) { - return errorResponse(c, error); - } -}); - -router.put('/training/sessions/:sessionId/commands/:unitId', async (c) => { - try { - const params = CombatV6TrainingCommandParamsSchema.parse(c.req.param()); - const input = CombatV6TrainingCommandRequestSchema.parse( - await c.req.json(), - ); - return c.json({ - success: true, - data: await combatV6TrainingSessionStore.submit( - actor(c), - params.sessionId, - input.expectedRevision, - params.unitId, - input.commands, - ), - }); - } catch (error) { - return errorResponse(c, error); - } -}); - -router.post('/training/sessions/:sessionId/resolve', async (c) => { - try { - const params = CombatV6TrainingSessionParamsSchema.parse(c.req.param()); - const input = CombatV6TrainingRevisionRequestSchema.parse( - await c.req.json(), - ); - return c.json({ - success: true, - data: await combatV6TrainingSessionStore.resolve( - actor(c), - params.sessionId, - input.expectedRevision, - ), - }); - } catch (error) { - return errorResponse(c, error); - } -}); - -router.delete('/training/sessions/:sessionId', async (c) => { - try { - const params = CombatV6TrainingSessionParamsSchema.parse(c.req.param()); - const input = CombatV6TrainingRevisionRequestSchema.parse( - await c.req.json(), - ); - return c.json({ - success: true, - data: await combatV6TrainingSessionStore.abandon( - actor(c), - params.sessionId, - input.expectedRevision, - ), - }); - } catch (error) { - return errorResponse(c, error); - } -}); - -router.get('/training/sessions/:sessionId/trace', async (c) => { - if (process.env.NODE_ENV === 'production') { - return c.json( - { - success: false, - code: 'TRAINING_SESSION_NOT_FOUND', - error: '训练会话不存在', - }, - 404, - ); - } - try { - const params = CombatV6TrainingSessionParamsSchema.parse(c.req.param()); - return c.json({ - success: true, - data: await combatV6TrainingSessionStore.trace( - actor(c), - params.sessionId, - ), - }); - } catch (error) { - return errorResponse(c, error); - } -}); - -router.get('/wild/regions/:nodeId', async (c) => { - try { - return c.json({ - success: true, - data: await wildSessions.region(actor(c), c.req.param('nodeId')), - }); - } catch (error) { - return errorResponse(c, error); - } -}); -router.post( - '/wild/explorations', - validateJson(WildExploreRequestSchema), - async (c) => { - try { - const input = getValidatedJson>(c); - return c.json(toPlayerStateMutationResponse( - await wildSessions.explore(actor(c), input.nodeId, input.requestId), - )); - } catch (error) { - return errorResponse(c, error); - } - }, -); -router.post('/wild/sessions', validateJson(WildStartRequestSchema), async (c) => { - try { - const input = getValidatedJson>(c); - return c.json({ - success: true, - data: await wildSessions.start(actor(c), input.encounterId), - }); - } catch (error) { - return errorResponse(c, error); - } -}); -router.get('/wild/sessions/current', async (c) => { - try { - return c.json({ - success: true, - data: await wildSessions.current(actor(c)), - }); - } catch (error) { - return errorResponse(c, error); - } -}); -router.get('/wild/sessions/:sessionId', async (c) => { - try { - const { sessionId } = CombatV6TrainingSessionParamsSchema.parse( - c.req.param(), - ); - const query = CombatV6TrainingEventsQuerySchema.parse(c.req.query()); - return c.json({ - success: true, - data: await wildSessions.get(actor(c), sessionId, query.afterEventSeq), - }); - } catch (error) { - return errorResponse(c, error); - } -}); -router.put('/wild/sessions/:sessionId/commands/:unitId', async (c) => { - try { - const p = CombatV6TrainingCommandParamsSchema.parse(c.req.param()); - const input = CombatV6TrainingCommandRequestSchema.parse( - await c.req.json(), - ); - return c.json({ - success: true, - data: await wildSessions.submit( - actor(c), - p.sessionId, - input.expectedRevision, - p.unitId, - input.commands, - ), - }); - } catch (error) { - return errorResponse(c, error); - } -}); -router.post('/wild/sessions/:sessionId/resolve', async (c) => { - try { - const p = CombatV6TrainingSessionParamsSchema.parse(c.req.param()); - const input = CombatV6TrainingRevisionRequestSchema.parse( - await c.req.json(), - ); - return c.json({ - success: true, - data: await wildSessions.resolve( - actor(c), - p.sessionId, - input.expectedRevision, - ), - }); - } catch (error) { - return errorResponse(c, error); - } -}); -router.delete('/wild/sessions/:sessionId', async (c) => { - try { - const p = CombatV6TrainingSessionParamsSchema.parse(c.req.param()); - const input = CombatV6TrainingRevisionRequestSchema.parse( - await c.req.json(), - ); - return c.json({ - success: true, - data: await wildSessions.abandon( - actor(c), - p.sessionId, - input.expectedRevision, - ), - }); - } catch (error) { - return errorResponse(c, error); - } -}); - -export default router; diff --git a/src/server/routes/api/community.router.ts b/src/server/routes/api/community.router.ts deleted file mode 100644 index 4c9a945f..00000000 --- a/src/server/routes/api/community.router.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type { AppEnv } from '@server/lib/hono/types'; -import { - getAuthPageAnnouncement, - getResolvedCommunityQqGroupNumber, -} from '@server/lib/repositories/appSettingsRepository'; -import { Hono } from 'hono'; - -const router = new Hono(); - -router.get('/qq-group', async (c) => { - let groupNumber: string; - try { - groupNumber = await getResolvedCommunityQqGroupNumber(); - } catch { - return c.json( - { success: false, error: 'QQ 群配置暂不可用,请稍后重试' }, - 503, - ); - } - - return c.json({ - success: true, - groupNumber, - joinHint: '请复制群号后前往 QQ 搜索并申请加群', - }); -}); - -router.get('/announcement', async (c) => { - let announcement: string | null; - try { - announcement = await getAuthPageAnnouncement(); - } catch { - return c.json( - { success: false, error: '公告配置暂不可用,请稍后重试' }, - 503, - ); - } - - return c.json({ - success: true, - announcement, - }); -}); - -export default router; diff --git a/src/server/routes/api/craft.router.ts b/src/server/routes/api/craft.router.ts deleted file mode 100644 index 006015c8..00000000 --- a/src/server/routes/api/craft.router.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { JournalRequestSchema } from '@shared/contracts/playerJournal'; -import { PlayerCommandIdempotencyError } from '@server/lib/services/CommandExecutors'; -import { - redisLockErrorResponse, - requireActiveCultivatorRef, -} from '@server/lib/hono/middleware'; -import { jsonWithStatus } from '@server/lib/hono/response'; -import type { AppEnv } from '@server/lib/hono/types'; -import { assertAlchemyMaterialVersions } from '@server/lib/services/alchemy/AlchemyInventory'; -import { previewFormulaCraft } from '@server/lib/services/AlchemyFormulaService'; -import { - AlchemyServiceError, - previewAlchemySelection, -} from '@server/lib/services/alchemyServiceV2'; -import { - CraftCommandError, - executeCraftCommand, -} from '@server/lib/services/CraftApplicationService'; -import { readCraftReadinessFacts } from '@server/lib/services/cultivator/CultivatorFactsReader'; -import { getPlayerPreHeavenFates } from '@server/lib/services/cultivator/CultivatorProfileRepository'; -import { QiServiceError } from '@server/lib/services/QiService'; -import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; -import { ALCHEMY_MAX_DOSE } from '@shared/config/alchemyInput'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const quantities = z.record( - z.string(), - z.number().int().min(1).max(ALCHEMY_MAX_DOSE), -); -const CraftSchema = z - .object({ - craftType: z.literal('alchemy'), - alchemyMode: z.enum(['improvised', 'formula']).default('improvised'), - materialIds: z - .array(z.string().min(1).max(160)) - .min(1) - .max(6) - .refine((ids) => new Set(ids).size === ids.length, '材料不能重复'), - materialVersions: z.record(z.string(), z.string().max(10000)), - materialQuantities: quantities.optional(), - userPrompt: z.string().trim().max(300).optional(), - formulaId: z.uuid().optional(), - analysisId: z.uuid().optional(), - }) - .strict(); -const router = new Hono(); -const retired = new Set(['refine', 'create_skill', 'create_gongfa']); -const retiredMessage = '旧功法、神通及装备生产已停用,历史物品保留在洞府宝库'; -router.use('*', requireActiveCultivatorRef()); -router.onError((error, c) => { - const lock = redisLockErrorResponse(error); - if (lock) return lock; - if (error instanceof z.ZodError || error instanceof SyntaxError) - return c.json({ success: false, error: '请求参数无效' }, 400); - if ( - error instanceof AlchemyServiceError || - error instanceof CraftCommandError || - error instanceof PlayerCommandIdempotencyError || - error instanceof QiServiceError - ) - return jsonWithStatus( - c, - { success: false, error: error.message }, - error.status, - ); - console.error('[alchemy] request failed', error); - return c.json({ success: false, error: '炼丹请求失败,请重新核对材料' }, 500); -}); -router.get('/', async (c) => { - const craftType = c.req.query('craftType'); - if (craftType && retired.has(craftType)) - return c.json({ error: retiredMessage }, 410); - const input = CraftSchema.parse({ - craftType, - alchemyMode: c.req.query('alchemyMode'), - materialIds: (c.req.query('materialIds') ?? '').split(','), - materialQuantities: c.req.query('materialQuantities') - ? JSON.parse(c.req.query('materialQuantities')!) - : undefined, - formulaId: c.req.query('formulaId'), - materialVersions: c.req.query('materialVersions') - ? JSON.parse(c.req.query('materialVersions')!) - : {}, - }); - const owner = c.get('activeCultivatorRef')!.cultivatorId; - await assertAlchemyMaterialVersions( - owner, - input.materialIds, - input.materialVersions, - ); - const [facts, fates] = await Promise.all([ - readCraftReadinessFacts(owner), - getPlayerPreHeavenFates(c.get('user')!.id, owner), - ]); - if (input.alchemyMode === 'formula' && !input.formulaId) - return c.json({ error: '请选择丹方' }, 400); - const data = - input.alchemyMode === 'formula' - ? await previewFormulaCraft( - owner, - input.formulaId!, - input.materialIds, - facts.spiritStones, - fates ?? [], - input.materialQuantities, - ) - : await previewAlchemySelection( - owner, - facts.spiritStones, - input.materialIds, - fates ?? [], - input.materialQuantities, - ); - return c.json({ success: true, data }); -}); -router.post('/', async (c) => { - const body = await c.req.json(); - if (body && retired.has(body.craftType)) - return c.json({ error: retiredMessage }, 410); - const input = CraftSchema.extend(JournalRequestSchema.shape).parse(body); - return c.json( - toPlayerStateMutationResponse( - await executeCraftCommand({ - userId: c.get('user')!.id, - cultivatorId: c.get('activeCultivatorRef')!.cultivatorId, - input, - }), - ), - ); -}); -router.all('/pending', (c) => c.json({ error: retiredMessage }, 410)); -router.all('/confirm', (c) => c.json({ error: retiredMessage }, 410)); -export default router; diff --git a/src/server/routes/api/cultivator.router.ts b/src/server/routes/api/cultivator.router.ts deleted file mode 100644 index cb7deb36..00000000 --- a/src/server/routes/api/cultivator.router.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { AppEnv } from '@server/lib/hono/types'; -import { Hono } from 'hono'; -import conditionRouter from './cultivator/condition.router'; -import inventoryRouter from './cultivator/inventory.router'; -import mailRouter from './cultivator/mail.router'; -import profileRouter from './cultivator/profile.router'; -import retreatRouter from './cultivator/retreat.router'; -import yieldRouter from './cultivator/yield.router'; - -const router = new Hono(); - -router.route('/', profileRouter); -router.route('/', conditionRouter); -router.route('/inventory', inventoryRouter); -router.route('/mail', mailRouter); -router.route('/retreat', retreatRouter); -router.route('/yield', yieldRouter); - -export default router; diff --git a/src/server/routes/api/cultivator/condition.router.ts b/src/server/routes/api/cultivator/condition.router.ts deleted file mode 100644 index c64e1283..00000000 --- a/src/server/routes/api/cultivator/condition.router.ts +++ /dev/null @@ -1,211 +0,0 @@ -import { - redisLockErrorResponse, - requireActiveCultivatorRef, -} from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { AttributeResetServiceError } from '@server/lib/services/AttributeResetService'; -import { - getBodyCultivationBreakthroughPreviewData, - loadPlayerBodyCultivationFacts, -} from '@server/lib/services/BodyCultivationBreakthroughService'; -import { - breakthroughBodyCultivation, - breakthroughCultivatorMarrowWash, - consumeCultivatorConsumable, - recoverCultivatorAtInn, -} from '@server/lib/services/CultivatorConditionApplicationService'; -import { - QiInsufficientError, - QiServiceError, -} from '@server/lib/services/QiService'; -import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; -import type { BodyCultivationBreakthroughReadinessResponse } from '@shared/contracts/bodyCultivation'; -import { Hono, type Context } from 'hono'; -import { z } from 'zod'; - -const ConsumeSchema = z.object({ - consumableId: z.string().uuid(), - revision: z.number().int().nonnegative().optional(), - quantity: z.number().int().min(1).max(99).default(1), -}); -const BodyCultivationBreakthroughSchema = z.object({}).strict(); - -function qiErrorResponse(c: Context, error: unknown) { - if (error instanceof QiInsufficientError) { - return c.json( - { - error: error.code, - message: error.message, - required: error.required, - current: error.current, - action: error.action, - }, - 409, - ); - } - if (error instanceof QiServiceError) { - return c.json({ error: error.message }, error.status as 400 | 404 | 409); - } - return null; -} - -const conditionRouter = new Hono(); - -conditionRouter.post('/consume', requireActiveCultivatorRef(), async (c) => { - const user = c.get('user'); - const cultivator = c.get('activeCultivatorRef'); - if (!user || !cultivator) { - return c.json({ success: false, error: '未授权访问' }, 401); - } - const parsed = ConsumeSchema.safeParse(await c.req.json()); - if (!parsed.success) { - return c.json({ success: false, error: '请求参数格式错误' }, 400); - } - try { - const committed = await consumeCultivatorConsumable({ - actor: { - userId: user.id, - cultivatorId: cultivator.cultivatorId, - }, - consumableId: parsed.data.consumableId, - revision: parsed.data.revision, - quantity: parsed.data.quantity, - }); - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - const lockErrorResponse = redisLockErrorResponse(error); - if (lockErrorResponse) return lockErrorResponse; - if (error instanceof AttributeResetServiceError) { - return c.json({ error: error.message }, error.status as 400 | 404); - } - const qiResponse = qiErrorResponse(c, error); - if (qiResponse) return qiResponse; - return c.json( - { - success: false, - error: error instanceof Error ? error.message : '使用失败', - }, - 400, - ); - } -}); - -conditionRouter.post( - '/inn-recovery', - requireActiveCultivatorRef(), - async (c) => { - const user = c.get('user'); - const cultivator = c.get('activeCultivatorRef'); - if (!user || !cultivator) { - return c.json({ success: false, error: '未授权访问' }, 401); - } - try { - const committed = await recoverCultivatorAtInn({ - actor: { - userId: user.id, - cultivatorId: cultivator.cultivatorId, - }, - }); - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - if ( - !(error instanceof Error) || - !error.message.startsWith('囊中羞涩,灵石不足') - ) { - throw error; - } - return c.json({ success: false, error: error.message }, 400); - } - }, -); - -conditionRouter.get( - '/body-cultivation/breakthrough', - requireActiveCultivatorRef(), - async (c) => { - const user = c.get('user'); - const activeCultivator = c.get('activeCultivatorRef'); - if (!user || !activeCultivator) { - return c.json({ success: false, error: '未授权访问' }, 401); - } - const cultivator = await loadPlayerBodyCultivationFacts( - user.id, - activeCultivator.cultivatorId, - ); - if (!cultivator) { - return c.json({ success: false, error: '角色不存在' }, 404); - } - const response: BodyCultivationBreakthroughReadinessResponse = { - success: true, - data: getBodyCultivationBreakthroughPreviewData(cultivator), - }; - return c.json(response); - }, -); - -conditionRouter.post( - '/body-cultivation/breakthrough', - requireActiveCultivatorRef(), - async (c) => { - const user = c.get('user'); - const activeCultivator = c.get('activeCultivatorRef'); - if (!user || !activeCultivator) { - return c.json({ success: false, error: '未授权访问' }, 401); - } - try { - BodyCultivationBreakthroughSchema.parse( - await c.req.json().catch(() => ({})), - ); - const committed = await breakthroughBodyCultivation({ - actor: { - userId: user.id, - cultivatorId: activeCultivator.cultivatorId, - }, - }); - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - const lockErrorResponse = redisLockErrorResponse(error); - if (lockErrorResponse) return lockErrorResponse; - return c.json( - { - success: false, - error: error instanceof Error ? error.message : '肉身进阶失败', - }, - 400, - ); - } - }, -); - -conditionRouter.post( - '/marrow-wash/breakthrough', - requireActiveCultivatorRef(), - async (c) => { - const user = c.get('user'); - const activeCultivator = c.get('activeCultivatorRef'); - if (!user || !activeCultivator) { - return c.json({ success: false, error: '未授权访问' }, 401); - } - try { - const committed = await breakthroughCultivatorMarrowWash({ - actor: { - userId: user.id, - cultivatorId: activeCultivator.cultivatorId, - }, - }); - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - const lockErrorResponse = redisLockErrorResponse(error); - if (lockErrorResponse) return lockErrorResponse; - const qiResponse = qiErrorResponse(c, error); - if (qiResponse) return qiResponse; - const message = error instanceof Error ? error.message : '洗髓破限失败'; - return c.json( - { success: false, error: message }, - message === '角色不存在' ? 404 : 400, - ); - } - }, -); - -export default conditionRouter; diff --git a/src/server/routes/api/cultivator/inventory.router.ts b/src/server/routes/api/cultivator/inventory.router.ts deleted file mode 100644 index 1544aa61..00000000 --- a/src/server/routes/api/cultivator/inventory.router.ts +++ /dev/null @@ -1,215 +0,0 @@ -import { requireActiveCultivatorRef } from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { discardInventoryItem } from '@server/lib/services/InventoryApplicationService'; -import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; -import { readResourceWithMeta } from '@server/lib/services/ResourceReadService'; -import { getPaginatedInventoryByType } from '@server/lib/services/cultivator/CultivatorInventoryRepository'; -import { - ELEMENT_VALUES, - MATERIAL_TYPE_VALUES, - QUALITY_VALUES, - type ElementType, - type MaterialType, - type Quality, -} from '@shared/types/constants'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const DiscardSchema = z.object({ - itemId: z.string(), - itemType: z.enum(['artifact', 'consumable', 'material']), -}); - -function parseList( - raw: string | null, - values: readonly T[], - label: string, -): T[] | undefined { - if (!raw) return undefined; - const parsed = raw - .split(',') - .map((value) => value.trim()) - .filter(Boolean) as T[]; - if (parsed.length === 0) return undefined; - const allowed = new Set(values); - if (parsed.some((value) => !allowed.has(value))) { - throw new Error(`无效的${label},支持:${values.join(', ')}`); - } - return parsed; -} - -const inventoryRouter = new Hono(); - -inventoryRouter.get('/', requireActiveCultivatorRef(), async (c) => { - const user = c.get('user'); - const ref = c.get('activeCultivatorRef'); - if (!user || !ref) { - return c.json({ success: false, error: '未授权访问' }, 401); - } - const type = c.req.query('type'); - if (!type) { - return c.json({ success: false, error: '必须指定背包类型和分页参数' }, 400); - } - if (!['artifacts', 'materials', 'consumables'].includes(type)) { - return c.json( - { - success: false, - error: '无效的背包类型,仅支持 artifacts | materials | consumables', - }, - 400, - ); - } - - const page = Math.max(1, parseInt(c.req.query('page') || '1', 10)); - const pageSize = Math.min( - 100, - Math.max(1, parseInt(c.req.query('pageSize') || '20', 10)), - ); - const consumableKind = c.req.query('consumableKind'); - if (consumableKind && consumableKind !== 'pill') { - return c.json({ success: false, error: '无效的消耗品分类' }, 400); - } - let materialTypes: MaterialType[] | undefined; - let excludeMaterialTypes: MaterialType[] | undefined; - let materialRanks: Quality[] | undefined; - let materialElements: ElementType[] | undefined; - try { - materialTypes = parseList( - c.req.query('materialTypes') ?? null, - MATERIAL_TYPE_VALUES, - '材料类型', - ); - excludeMaterialTypes = parseList( - c.req.query('excludeMaterialTypes') ?? null, - MATERIAL_TYPE_VALUES, - '材料类型', - ); - materialRanks = parseList( - c.req.query('materialRanks') ?? null, - QUALITY_VALUES, - '材料品级', - ); - materialElements = parseList( - c.req.query('materialElements') ?? null, - ELEMENT_VALUES, - '材料属性', - ); - } catch (error) { - return c.json( - { - success: false, - error: error instanceof Error ? error.message : '材料类型参数解析失败', - }, - 400, - ); - } - - const validSortBy = [ - 'createdAt', - 'rank', - 'type', - 'element', - 'quantity', - 'name', - ] as const; - const validSortOrder = ['asc', 'desc'] as const; - const materialSortBy = c.req.query('materialSortBy'); - const materialSortOrder = c.req.query('materialSortOrder'); - if ( - materialSortBy && - !validSortBy.includes(materialSortBy as (typeof validSortBy)[number]) - ) { - return c.json( - { - success: false, - error: `无效的排序字段,支持:${validSortBy.join(', ')}`, - }, - 400, - ); - } - if ( - materialSortOrder && - !validSortOrder.includes( - materialSortOrder as (typeof validSortOrder)[number], - ) - ) { - return c.json( - { - success: false, - error: `无效的排序方向,支持:${validSortOrder.join(', ')}`, - }, - 400, - ); - } - - const options = { - page, - pageSize, - materialTypes, - excludeMaterialTypes, - materialRanks, - materialElements, - materialSortBy: materialSortBy as (typeof validSortBy)[number] | undefined, - materialSortOrder: materialSortOrder as 'asc' | 'desc' | undefined, - consumableKind: consumableKind as 'pill' | undefined, - }; - const scope = { kind: 'cultivator' as const, id: ref.cultivatorId }; - if (type === 'artifacts') { - return c.json( - await readResourceWithMeta(scope, 'inventory.artifacts', (q) => - getPaginatedInventoryByType( - user.id, - ref.cultivatorId, - { ...options, type: 'artifacts' }, - q, - ), - ), - ); - } - if (type === 'materials') { - return c.json( - await readResourceWithMeta(scope, 'inventory.materials', (q) => - getPaginatedInventoryByType( - user.id, - ref.cultivatorId, - { ...options, type: 'materials' }, - q, - ), - ), - ); - } - return c.json( - await readResourceWithMeta(scope, 'inventory.consumables', (q) => - getPaginatedInventoryByType( - user.id, - ref.cultivatorId, - { ...options, type: 'consumables' }, - q, - ), - ), - ); -}); - -inventoryRouter.post('/discard', requireActiveCultivatorRef(), async (c) => { - const user = c.get('user'); - const cultivator = c.get('activeCultivatorRef'); - if (!user || !cultivator) { - return c.json({ success: false, error: '未授权访问' }, 401); - } - const { itemId, itemType } = DiscardSchema.parse(await c.req.json()); - if (itemType === 'artifact') { - return c.json({ success: false, error: '历史法宝已停用,仅支持查看' }, 410); - } - const committed = await discardInventoryItem({ - actor: { userId: user.id, cultivatorId: cultivator.cultivatorId }, - itemId, - itemType, - }); - return c.json(toPlayerStateMutationResponse(committed)); -}); - -inventoryRouter.post('/identify', requireActiveCultivatorRef(), (c) => - c.json({ error: '未鉴定材料已弃用,无法鉴定或迁移' }, 410), -); - -export default inventoryRouter; diff --git a/src/server/routes/api/cultivator/mail.router.ts b/src/server/routes/api/cultivator/mail.router.ts deleted file mode 100644 index 2626381d..00000000 --- a/src/server/routes/api/cultivator/mail.router.ts +++ /dev/null @@ -1,208 +0,0 @@ -import { JournalRequestSchema } from '@shared/contracts/playerJournal'; -import { scheduleSystemMailObservation } from '@server/lib/services/SystemMailService'; -import { - getExecutor, - type DbExecutor, - type DbTransaction, -} from '@server/lib/drizzle/db'; -import { mails } from '@server/lib/drizzle/schema'; -import { - redisLockErrorResponse, - requireActiveCultivatorRef, - validateJson, - getValidatedJson, -} from '@server/lib/hono/middleware'; -import { jsonWithStatus } from '@server/lib/hono/response'; -import type { AppEnv } from '@server/lib/hono/types'; -import { BeastError } from '@server/lib/services/combat-v6/BeastMutationGuard'; -import { PlayerCommandIdempotencyError } from '@server/lib/services/CommandExecutors'; -import { InventoryError } from '@server/lib/services/InventoryService'; -import { publicMailAttachment } from '@server/lib/services/MailInventory'; -import type { MailAttachment } from '@server/lib/services/MailService'; -import { - claimAllCultivatorMail, - claimCultivatorMail, - markAllCultivatorMailRead, - markCultivatorMailRead, - PlayerMailCommandError, - sendCultivatorMail, -} from '@server/lib/services/PlayerMailApplicationService'; -import { PlayerMailServiceError } from '@server/lib/services/PlayerMailService'; -import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; -import { SendMailSchema } from '@shared/contracts/mail'; -import { and, desc, eq, sql } from 'drizzle-orm'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const MailIdSchema = z.object({ mailId: z.string() }); - -async function countUnreadMail( - cultivatorId: string, - q: DbExecutor | DbTransaction = getExecutor(), -): Promise { - const [result] = await q - .select({ count: sql`count(*)::int` }) - .from(mails) - .where(and(eq(mails.cultivatorId, cultivatorId), eq(mails.isRead, false))); - return Number(result?.count ?? 0); -} - -const mailRouter = new Hono(); - -mailRouter.get('/', requireActiveCultivatorRef(), async (c) => { - const ref = c.get('activeCultivatorRef'); - if (!ref) return c.json({ error: '当前没有活跃角色' }, 404); - scheduleSystemMailObservation(ref.cultivatorId, 'mailbox'); - const pageRaw = parseInt(c.req.query('page') || '1', 10); - const pageSizeRaw = parseInt(c.req.query('pageSize') || '20', 10); - const page = Number.isNaN(pageRaw) ? 1 : Math.max(1, pageRaw); - const pageSize = Number.isNaN(pageSizeRaw) - ? 20 - : Math.min(100, Math.max(1, pageSizeRaw)); - const userMails = await getExecutor().query.mails.findMany({ - where: eq(mails.cultivatorId, ref.cultivatorId), - orderBy: [desc(mails.createdAt)], - limit: pageSize + 1, - offset: (page - 1) * pageSize, - }); - const hasMore = userMails.length > pageSize; - return c.json({ - mails: (hasMore ? userMails.slice(0, pageSize) : userMails).map((mail) => { - const attachments = Array.isArray(mail.attachments) - ? (mail.attachments as MailAttachment[]) - : []; - return { - ...mail, - attachments: attachments.map(publicMailAttachment), - }; - }), - pagination: { page, pageSize, hasMore }, - }); -}); - -mailRouter.post('/send', requireActiveCultivatorRef(), async (c) => { - const user = c.get('user'); - const cultivator = c.get('activeCultivatorRef'); - if (!user || !cultivator) return c.json({ error: '未授权访问' }, 401); - try { - const parsed = SendMailSchema.parse(await c.req.json()); - const committed = await sendCultivatorMail({ - actor: { - userId: user.id, - cultivatorId: cultivator.cultivatorId, - }, - recipientCultivatorId: parsed.recipientCultivatorId, - content: parsed.content, - requestId: parsed.requestId, - attachment: parsed.attachment, - }); - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - const lockErrorResponse = redisLockErrorResponse(error); - if (lockErrorResponse) return lockErrorResponse; - if (error instanceof z.ZodError) { - return c.json({ error: '参数错误', details: error.issues }, 400); - } - if ( - error instanceof InventoryError || - error instanceof PlayerCommandIdempotencyError - ) - return c.json({ error: error.message }, 409); - if (error instanceof PlayerMailServiceError) { - return jsonWithStatus(c, { error: error.message }, error.status); - } - console.error('mail send api error:', error); - return c.json({ error: '发送传音失败' }, 500); - } -}); - -mailRouter.post('/claim', requireActiveCultivatorRef(), async (c) => { - const user = c.get('user'); - const cultivator = c.get('activeCultivatorRef'); - if (!user || !cultivator) return c.json({ error: '未授权访问' }, 401); - const { mailId } = MailIdSchema.parse(await c.req.json()); - try { - const committed = await claimCultivatorMail({ - actor: { - userId: user.id, - cultivatorId: cultivator.cultivatorId, - }, - mailId, - }); - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - if (error instanceof BeastError) - return c.json({ error: error.message }, 409); - const lockErrorResponse = redisLockErrorResponse(error); - if (lockErrorResponse) return lockErrorResponse; - if (error instanceof PlayerMailCommandError) { - return c.json({ error: error.message }, error.status); - } - throw error; - } -}); - -mailRouter.post('/claim-all', requireActiveCultivatorRef(), validateJson(JournalRequestSchema), async (c) => { - const user = c.get('user'); - const cultivator = c.get('activeCultivatorRef'); - if (!user || !cultivator) return c.json({ error: '未授权访问' }, 401); - try { - const committed = await claimAllCultivatorMail({ - requestId: getValidatedJson<{ requestId: string }>(c).requestId, - actor: { - userId: user.id, - cultivatorId: cultivator.cultivatorId, - }, - }); - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - if (error instanceof BeastError) - return c.json({ error: error.message }, 409); - const lockErrorResponse = redisLockErrorResponse(error); - if (lockErrorResponse) return lockErrorResponse; - throw error; - } -}); - -mailRouter.post('/read', requireActiveCultivatorRef(), async (c) => { - const user = c.get('user'); - const cultivator = c.get('activeCultivatorRef'); - if (!user || !cultivator) return c.json({ error: '未授权访问' }, 401); - const { mailId } = MailIdSchema.parse(await c.req.json()); - try { - const committed = await markCultivatorMailRead({ - actor: { - userId: user.id, - cultivatorId: cultivator.cultivatorId, - }, - mailId, - }); - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - if (error instanceof PlayerMailCommandError) { - return c.json({ error: error.message }, error.status); - } - throw error; - } -}); - -mailRouter.post('/read-all', requireActiveCultivatorRef(), async (c) => { - const user = c.get('user'); - const cultivator = c.get('activeCultivatorRef'); - if (!user || !cultivator) return c.json({ error: '未授权访问' }, 401); - const committed = await markAllCultivatorMailRead({ - actor: { - userId: user.id, - cultivatorId: cultivator.cultivatorId, - }, - }); - return c.json(toPlayerStateMutationResponse(committed)); -}); - -mailRouter.get('/unread-count', requireActiveCultivatorRef(), async (c) => { - const ref = c.get('activeCultivatorRef'); - if (!ref) return c.json({ error: '当前没有活跃角色' }, 404); - return c.json({ count: await countUnreadMail(ref.cultivatorId) }); -}); - -export default mailRouter; diff --git a/src/server/routes/api/cultivator/profile.router.ts b/src/server/routes/api/cultivator/profile.router.ts deleted file mode 100644 index 810d763c..00000000 --- a/src/server/routes/api/cultivator/profile.router.ts +++ /dev/null @@ -1,268 +0,0 @@ -import { db } from '@server/lib/drizzle/db'; -import { getValidatedJson, validateJson } from '@server/lib/hono/middleware'; -import { loadPlayerRetreatFacts } from '@server/lib/services/cultivator/CultivatorConditionFactsReader'; -import { - AttributeAllocationSchema, - type AttributeAllocationRequest, - type AttributePreviewData, -} from '@shared/contracts/characterAttributes'; -import { - CHARACTER_ATTRIBUTE_LABELS, - projectCharacterDisplay, -} from '@shared/lib/cultivatorDisplay'; - -import { - redisLockErrorResponse, - requireActiveCultivatorRef, -} from '@server/lib/hono/middleware'; -import { jsonWithStatus } from '@server/lib/hono/response'; -import type { AppEnv } from '@server/lib/hono/types'; -import { - isValidRedeemCodeFormat, - normalizeRedeemCode, -} from '@server/lib/redeem/code'; -import { AttributeResetServiceError } from '@server/lib/services/AttributeResetService'; - -import { - allocateCultivatorAttributes, - reincarnateActiveCultivator, - resetCultivatorAttributes, - updateCultivatorTitle, -} from '@server/lib/services/CultivatorProfileApplicationService'; -import { QiService } from '@server/lib/services/QiService'; -import { - claimRedeemCode, - RedeemClaimError, -} from '@server/lib/services/RedeemCodeApplicationService'; -import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const TitleSchema = z.object({ - title: z.string().min(2).max(8).optional().nullable(), -}); - -const ClaimRedeemCodeSchema = z.object({ - code: z.string().trim().min(1).max(64), -}); - -function isUniqueViolation(error: unknown): boolean { - if (!error || typeof error !== 'object') { - return false; - } - - return (error as { code?: string }).code === '23505'; -} - -function parsePositiveInt( - rawValue: string | undefined, - fallback: number, -): number { - if (!rawValue) return fallback; - const parsed = Number.parseInt(rawValue, 10); - return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback; -} - -const router = new Hono(); - -router.post('/active-reincarnate', requireActiveCultivatorRef(), async (c) => { - const user = c.get('user'); - const cultivator = c.get('activeCultivatorRef'); - if (!user || !cultivator) { - return c.json({ error: '当前没有活跃角色' }, 404); - } - - const committed = await reincarnateActiveCultivator({ - actor: { userId: user.id, cultivatorId: cultivator.cultivatorId }, - }); - - return c.json(toPlayerStateMutationResponse(committed)); -}); - -router.post('/equip', requireActiveCultivatorRef(), (c) => - c.json({ error: '旧产物装配已停用' }, 410), -); - -router.get('/qi/logs', requireActiveCultivatorRef(), async (c) => { - const cultivator = c.get('activeCultivatorRef'); - if (!cultivator) { - return c.json({ error: '当前没有活跃角色' }, 404); - } - - const page = parsePositiveInt(c.req.query('page'), 1); - const pageSize = Math.min(100, parsePositiveInt(c.req.query('pageSize'), 20)); - const data = await QiService.listLogs(cultivator.cultivatorId, { - page, - pageSize, - }); - return c.json({ success: true, data }); -}); - -router.post('/title', requireActiveCultivatorRef(), async (c) => { - const user = c.get('user'); - const cultivator = c.get('activeCultivatorRef'); - if (!user || !cultivator) { - return c.json({ error: '未授权访问' }, 401); - } - - const { title } = TitleSchema.parse(await c.req.json()); - const committed = await updateCultivatorTitle({ - userId: user.id, - cultivatorId: cultivator.cultivatorId, - title: title || null, - }); - - return c.json(toPlayerStateMutationResponse(committed)); -}); - -router.post( - '/attributes/preview', - requireActiveCultivatorRef(), - validateJson(AttributeAllocationSchema), - async (c) => { - const actor = c.get('activeCultivatorRef')!; - const delta = getValidatedJson(c); - const result = await db.transaction( - async (tx) => { - const facts = await loadPlayerRetreatFacts( - actor.userId, - actor.cultivatorId, - tx, - ); - if (!facts) return { error: '角色不存在', status: 404 as const }; - const keys = Object.keys( - CHARACTER_ATTRIBUTE_LABELS, - ) as (keyof typeof facts.attributes)[]; - const spent = keys.reduce((sum, key) => sum + delta[key], 0); - if (spent > facts.unallocated_attribute_points) - return { error: '未分配属性点不足', status: 400 as const }; - const attributes = { ...facts.attributes }; - for (const key of keys) attributes[key] += delta[key]; - const data: AttributePreviewData = { - current: facts.combatV6ResourceAuthority.attrs, - preview: projectCharacterDisplay( - { ...facts, attributes }, - facts.combatV6ResourceAuthority.build, - ), - }; - return { data }; - }, - { isolationLevel: 'repeatable read', accessMode: 'read only' }, - ); - if ('error' in result) - return c.json({ error: result.error }, result.status); - return c.json({ success: true, data: result.data }); - }, -); - -router.post('/attributes/allocate', requireActiveCultivatorRef(), async (c) => { - const user = c.get('user'); - const cultivator = c.get('activeCultivatorRef'); - if (!user || !cultivator) { - return c.json({ error: '未授权访问' }, 401); - } - - const parsed = AttributeAllocationSchema.safeParse(await c.req.json()); - if (!parsed.success) { - return c.json({ error: '参数错误', details: parsed.error.flatten() }, 400); - } - - try { - const delta = { - vitality: parsed.data.vitality, - strength: parsed.data.strength, - spirit: parsed.data.spirit, - endurance: parsed.data.endurance, - speed: parsed.data.speed, - willpower: parsed.data.willpower, - }; - const committed = await allocateCultivatorAttributes({ - actor: { - userId: user.id, - cultivatorId: cultivator.cultivatorId, - }, - delta, - }); - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - const lockErrorResponse = redisLockErrorResponse(error); - if (lockErrorResponse) return lockErrorResponse; - const message = error instanceof Error ? error.message : '属性分配失败'; - if ( - [ - '角色不存在', - '未分配属性点不足', - '属性不能低于基础值', - '属性总点数超过当前境界预算', - ].includes(message) - ) { - return c.json({ error: message }, message === '角色不存在' ? 404 : 400); - } - throw error; - } -}); - -router.post('/attributes/reset', requireActiveCultivatorRef(), async (c) => { - const user = c.get('user'); - const cultivator = c.get('activeCultivatorRef'); - if (!user || !cultivator) { - return c.json({ error: '未授权访问' }, 401); - } - - try { - const committed = await resetCultivatorAttributes({ - userId: user.id, - cultivatorId: cultivator.cultivatorId, - }); - - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - const lockErrorResponse = redisLockErrorResponse(error); - if (lockErrorResponse) return lockErrorResponse; - if (error instanceof AttributeResetServiceError) { - return c.json({ error: error.message }, error.status as 400 | 404); - } - throw error; - } -}); - -router.post('/redeem-code/claim', requireActiveCultivatorRef(), async (c) => { - const user = c.get('user'); - const cultivator = c.get('activeCultivatorRef'); - if (!user || !cultivator) { - return c.json({ error: '未授权访问' }, 401); - } - - const parsed = ClaimRedeemCodeSchema.safeParse(await c.req.json()); - if (!parsed.success) { - return c.json({ error: '参数错误', details: parsed.error.flatten() }, 400); - } - - const normalizedCode = normalizeRedeemCode(parsed.data.code); - if (!isValidRedeemCodeFormat(normalizedCode)) { - return c.json({ error: '兑换码格式错误,仅支持 6-64 位大写字母数字' }, 400); - } - - try { - const committed = await claimRedeemCode({ - userId: user.id, - cultivatorId: cultivator.cultivatorId, - code: normalizedCode, - }); - - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - const lockErrorResponse = redisLockErrorResponse(error); - if (lockErrorResponse) return lockErrorResponse; - if (isUniqueViolation(error)) { - return c.json({ error: '该兑换码你已使用过' }, 400); - } - if (error instanceof RedeemClaimError) { - return jsonWithStatus(c, { error: error.message }, error.status); - } - console.error('Redeem claim error:', error); - return c.json({ error: '兑换失败,请稍后重试' }, 500); - } -}); - -export default router; diff --git a/src/server/routes/api/cultivator/retreat.router.ts b/src/server/routes/api/cultivator/retreat.router.ts deleted file mode 100644 index 91c45c45..00000000 --- a/src/server/routes/api/cultivator/retreat.router.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { - redisLockErrorResponse, - requireActiveCultivatorRef, - validateJson, -} from '@server/lib/hono/middleware'; -import { JournalIdempotencyError } from '@server/lib/repositories/playerJournalRepository'; -import { streamSseEvents } from '@server/lib/hono/streaming'; -import type { AppEnv } from '@server/lib/hono/types'; -import { - executeRetreatCommand, - RetreatCommandError, - type RetreatStorySource, -} from '@server/lib/services/RetreatApplicationService'; -import { - QiInsufficientError, - QiServiceError, -} from '@server/lib/services/QiService'; -import { streamAiText } from '@server/utils/aiClient'; -import { - getBreakthroughStoryPrompt, - getLifespanExhaustedStoryPrompt, -} from '@server/utils/prompts'; -import type { PlayerResourceMutationMeta } from '@shared/contracts/player'; -import { RetreatRequestSchema, type RetreatResultData } from '@shared/contracts/retreat'; -import { Hono, type Context } from 'hono'; -import { z } from 'zod'; - -function qiErrorResponse(c: Context, error: unknown) { - if (error instanceof QiInsufficientError) { - return c.json( - { - error: error.code, - message: error.message, - required: error.required, - current: error.current, - action: error.action, - }, - 409, - ); - } - if (error instanceof QiServiceError) { - return c.json({ error: error.message }, error.status as 400 | 404 | 409); - } - return null; -} - -function createRetreatStreamResponse( - c: Context, - args: { - result: RetreatResultData; - state?: PlayerResourceMutationMeta; - storySource: RetreatStorySource; - onStoryComplete?: (story: string) => Promise | void; - }, -): Response { - return streamSseEvents(c, async (stream, _isAborted, signal) => { - await stream.writeSSE({ - data: JSON.stringify({ type: 'result', data: args.result }), - }); - if (args.state && (args.state.changes.length || args.state.replayed)) { - await stream.writeSSE({ - data: JSON.stringify({ type: 'state', state: args.state }), - }); - } - if (!args.storySource) { - return; - } - - let accumulatedStory = ''; - try { - const prompt = - args.storySource.type === 'breakthrough' - ? getBreakthroughStoryPrompt(args.storySource.payload) - : getLifespanExhaustedStoryPrompt(args.storySource.payload); - const aiStreamResult = streamAiText({ - system: prompt[0], - prompt: prompt[1], - abortSignal: signal, - sceneId: - args.storySource.type === 'breakthrough' - ? 'breakthrough-story' - : 'lifespan-exhausted', - }); - for await (const chunk of aiStreamResult.textStream) { - accumulatedStory += chunk; - await stream.writeSSE({ - data: JSON.stringify({ type: 'chunk', text: chunk }), - }); - } - } catch (error) { - console.error('Retreat story stream error:', error); - await stream.writeSSE({ - data: JSON.stringify({ - type: 'error', - error: '天机推演中断,此番结果已然落定。', - }), - }); - } finally { - try { - await args.onStoryComplete?.(accumulatedStory); - } catch (persistError) { - console.error('Retreat story persist error:', persistError); - } - } - }); -} - -const retreatRouter = new Hono(); -retreatRouter.onError((error, c) => { - if (error instanceof z.ZodError) return c.json({ error: '闭关突破参数无效' }, 400); - console.error('闭关突破请求失败:', error); - return c.json({ error: '闭关突破暂时无法完成' }, 500); -}); - -retreatRouter.post('/', requireActiveCultivatorRef(), validateJson(RetreatRequestSchema), async (c) => { - const user = c.get('user'); - const activeCultivator = c.get('activeCultivatorRef'); - if (!user || !activeCultivator) { - return c.json({ error: '未授权访问' }, 401); - } - - try { - const input = c.get('validatedJson') as import('@shared/contracts/retreat').RetreatRequest; - const execution = await executeRetreatCommand({ - userId: user.id, - cultivatorId: activeCultivator.cultivatorId, - action: input.action, - years: input.action === 'cultivate' ? input.years : 0, - requestId: input.requestId, - }); - return createRetreatStreamResponse(c, { - result: execution.committed.result, - state: execution.committed.state, - storySource: execution.storySource, - onStoryComplete: execution.onStoryComplete, - }); - } catch (error) { - const lockErrorResponse = redisLockErrorResponse(error); - if (lockErrorResponse) return lockErrorResponse; - const qiResponse = qiErrorResponse(c, error); - if (qiResponse) return qiResponse; - if (error instanceof JournalIdempotencyError) { - return c.json({ error: error.message }, 409); - } - if (error instanceof RetreatCommandError) { - return c.json( - { success: false, error: error.message, ...error.payload }, - error.status, - ); - } - console.error('闭关突破 API 错误:', error); - throw error; - } -}); - -export default retreatRouter; diff --git a/src/server/routes/api/cultivator/yield.router.ts b/src/server/routes/api/cultivator/yield.router.ts deleted file mode 100644 index 3b82a3b7..00000000 --- a/src/server/routes/api/cultivator/yield.router.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { JournalRequestSchema } from '@shared/contracts/playerJournal'; -import { - redisLockErrorResponse, - requireActiveCultivatorRef, - validateJson, - getValidatedJson, -} from '@server/lib/hono/middleware'; -import { streamSseEvents } from '@server/lib/hono/streaming'; -import type { AppEnv } from '@server/lib/hono/types'; -import { - executeYieldCommand, - YieldCommandError, -} from '@server/lib/services/YieldApplicationService'; -import { renderPrompt } from '@server/lib/prompts'; -import { streamAiText } from '@server/utils/aiClient'; -import { getGameConceptLabel } from '@shared/lib/gameConceptDisplay'; -import { Hono } from 'hono'; - -const yieldRouter = new Hono(); - -yieldRouter.post('/', requireActiveCultivatorRef(), validateJson(JournalRequestSchema), async (c) => { - const user = c.get('user'); - const activeCultivator = c.get('activeCultivatorRef'); - if (!user || !activeCultivator) { - return c.json({ success: false, error: '未授权访问' }, 401); - } - - try { - const { committed, result } = await executeYieldCommand({ - userId: user.id, - cultivatorId: activeCultivator.cultivatorId, - requestId: getValidatedJson<{ requestId: string }>(c).requestId, - }); - return streamSseEvents(c, async (stream, _isAborted, signal) => { - await stream.writeSSE({ - data: JSON.stringify({ type: 'result', data: committed.result }), - }); - if (committed.state.changes.length > 0 || committed.state.replayed) { - await stream.writeSSE({ - data: JSON.stringify({ type: 'state', state: committed.state }), - }); - } - - if (committed.state.replayed) return; - const { system, user: prompt } = renderPrompt('yield-story', { - cultivatorRealm: result.cultivatorRealm, - cultivatorName: result.cultivatorName, - amount: result.amount, - extraYieldText: (() => { - const extra = [ - result.expGain ? `修为精进 ${result.expGain} 点` : '', - result.insightGain - ? `${getGameConceptLabel('comprehension_insight')} ${result.insightGain} 点` - : '', - ] - .filter(Boolean) - .join(';'); - return extra ? `;${extra}` : ''; - })(), - }); - - try { - const aiStreamResult = streamAiText({ - system, - prompt, - abortSignal: signal, - sceneId: 'yield-story', - }); - for await (const chunk of aiStreamResult.textStream) { - await stream.writeSSE({ - data: JSON.stringify({ type: 'chunk', text: chunk }), - }); - } - } catch (error) { - console.error('Stream processing error:', error); - await stream.writeSSE({ - data: JSON.stringify({ type: 'error', error: '天机推演中断...' }), - }); - } - }); - } catch (error) { - const lockErrorResponse = redisLockErrorResponse(error); - if (lockErrorResponse) return lockErrorResponse; - if (error instanceof YieldCommandError) { - return c.json( - { success: false, error: error.message }, - error.status, - ); - } - throw error; - } -}); - -export default yieldRouter; diff --git a/src/server/routes/api/cultivators.router.ts b/src/server/routes/api/cultivators.router.ts deleted file mode 100644 index e635445c..00000000 --- a/src/server/routes/api/cultivators.router.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { requireUser } from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { isRedisLockContention } from '@server/lib/redis/lock'; -import { deleteCultivatorCommand } from '@server/lib/services/CultivatorProfileApplicationService'; -import { - getLastDeadCultivatorSummary, -} from '@server/lib/services/cultivator/CultivatorProfileRepository'; -import { Hono } from 'hono'; - -const router = new Hono(); - -router.delete('/', requireUser(), async (c) => { - const user = c.get('user'); - if (!user) { - return c.json({ error: '未授权访问' }, 401); - } - - const cultivatorId = c.req.query('id'); - if (!cultivatorId) { - return c.json({ error: '请提供角色ID' }, 400); - } - - try { - const success = await deleteCultivatorCommand({ - userId: user.id, - cultivatorId, - }); - if (!success) { - return c.json({ error: '删除角色失败或角色不存在' }, 404); - } - return c.json({ - success: true, - message: '角色删除成功', - }); - } catch (error) { - if (isRedisLockContention(error)) { - return c.json({ error: '角色正在执行其他操作,请稍后重试' }, 429); - } - throw error; - } -}); - -router.get('/reincarnate-context', requireUser(), async (c) => { - const user = c.get('user'); - if (!user) { - return c.json({ error: '未授权访问' }, 401); - } - - try { - const summary = await getLastDeadCultivatorSummary(user.id); - return c.json({ - success: true, - data: summary ?? null, - }); - } catch (err) { - console.error('获取转世上下文 API 错误:', err); - return c.json( - { - error: - process.env.NODE_ENV === 'development' - ? err instanceof Error - ? err.message - : '获取转世上下文失败' - : '获取转世上下文失败,请稍后再试', - }, - 500, - ); - } -}); - -export default router; diff --git a/src/server/routes/api/dev-resources.router.ts b/src/server/routes/api/dev-resources.router.ts deleted file mode 100644 index b1abc7c5..00000000 --- a/src/server/routes/api/dev-resources.router.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { redisLockErrorResponse } from '@server/lib/hono/middleware'; -import { patchDevCultivator } from '@server/lib/services/DevCultivatorService'; -import { resetDevDivination } from '@server/lib/services/DevDivinationService'; -import { clearDevInventoryBag } from '@server/lib/services/DevInventoryService'; -import { DivinationError } from '@server/lib/services/DivinationService'; -import { grantDevResources } from '@server/lib/services/ForgingService'; -import { InventoryError } from '@server/lib/services/InventoryService'; -import { QiServiceError } from '@server/lib/services/QiService'; -import { allowsLocalDevTools } from '@shared/config/deployment'; -import { DevCultivatorPatchSchema } from '@shared/contracts/devTools'; -import { DevGrantSchema } from '@shared/contracts/forging'; -import { InventoryRuleError } from '@shared/inventory'; -import { Hono } from 'hono'; -import { z } from 'zod'; -const router = new Hono(); -// Deliberately unauthenticated: this route is absent outside explicit local deployment. -router.use('*', async (c, next) => { - if (!allowsLocalDevTools(process.env.APP_ENV, process.env.NODE_ENV)) - return c.notFound(); - c.header('Cache-Control', 'no-store'); - await next(); -}); -router.post('/resources', async (c) => { - try { - return c.json({ - success: true, - ...(await grantDevResources(DevGrantSchema.parse(await c.req.json()))), - }); - } catch (error) { - const lock = redisLockErrorResponse(error); - if (lock) return lock; - if (error instanceof z.ZodError) - return c.json({ success: false, error: '发放参数无效' }, 400); - if ( - error instanceof InventoryError || - error instanceof InventoryRuleError || - error instanceof QiServiceError - ) - return c.json({ success: false, error: error.message }, 409); - throw error; - } -}); -router.delete('/cultivators/:id/inventory/bag', async (c) => { - try { - return c.json({ - success: true, - ...(await clearDevInventoryBag(z.uuid().parse(c.req.param('id')))), - }); - } catch (error) { - const lock = redisLockErrorResponse(error); - if (lock) return lock; - if (error instanceof z.ZodError) - return c.json({ success: false, error: '角色 ID 无效' }, 400); - if (error instanceof InventoryError) - return c.json({ success: false, error: error.message }, 409); - throw error; - } -}); -router.delete('/cultivators/:id/divination', async (c) => { - try { - return c.json({ - success: true, - ...(await resetDevDivination(z.uuid().parse(c.req.param('id')))), - }); - } catch (error) { - const lock = redisLockErrorResponse(error); - if (lock) return lock; - if (error instanceof z.ZodError) - return c.json({ success: false, error: '角色 ID 无效' }, 400); - if (error instanceof DivinationError) - return c.json({ success: false, error: error.message }, error.status); - throw error; - } -}); -router.patch('/cultivators/:id', async (c) => { - try { - return c.json({ - success: true, - ...(await patchDevCultivator( - z.uuid().parse(c.req.param('id')), - DevCultivatorPatchSchema.parse(await c.req.json()), - )), - }); - } catch (error) { - const lock = redisLockErrorResponse(error); - if (lock) return lock; - if (error instanceof z.ZodError) - return c.json({ success: false, error: '角色调整参数无效' }, 400); - if (error instanceof InventoryError) - return c.json({ success: false, error: error.message }, 409); - throw error; - } -}); -export default router; diff --git a/src/server/routes/api/divination.router.ts b/src/server/routes/api/divination.router.ts deleted file mode 100644 index 4ef08992..00000000 --- a/src/server/routes/api/divination.router.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { - redisLockErrorResponse, - requireActiveCultivatorRef, - validateJson, -} from '@server/lib/hono/middleware'; -import { streamSseEvents } from '@server/lib/hono/streaming'; -import type { AppEnv } from '@server/lib/hono/types'; -import { - DivinationError, - drawDivination, - interpretDivination, - readDivination, -} from '@server/lib/services/DivinationService'; -import { InventoryError } from '@server/lib/services/InventoryService'; -import { - DivinationDrawSchema, - DivinationInterpretSchema, - type DivinationStreamEvent, -} from '@shared/contracts/divination'; -import { Hono } from 'hono'; - -const router = new Hono(); -router.use('*', requireActiveCultivatorRef()); -router.get('/', async (c) => - c.json(await readDivination(c.get('activeCultivatorRef')!)), -); -router.post('/draw', validateJson(DivinationDrawSchema), async (c) => { - try { - const input = DivinationDrawSchema.parse(await c.req.json()); - return c.json( - await drawDivination(c.get('activeCultivatorRef')!, input.direction), - ); - } catch (error) { - const lock = redisLockErrorResponse(error); - if (lock) return lock; - if (error instanceof DivinationError) - return c.json({ error: error.message }, error.status); - throw error; - } -}); -router.post( - '/interpret', - validateJson(DivinationInterpretSchema), - async (c) => { - const { drawId } = DivinationInterpretSchema.parse(await c.req.json()); - const actor = c.get('activeCultivatorRef')!; - return streamSseEvents(c, async (stream, isAborted, signal) => { - const emit = async (event: DivinationStreamEvent) => { - if (isAborted()) return; - try { - await stream.writeSSE({ data: JSON.stringify(event) }); - } catch { - /* 解读与发奖不依赖客户端是否收到最后一个事件。 */ - } - }; - try { - await interpretDivination(actor, drawId, signal, emit); - } catch (error) { - console.warn('[divination] interpretation incomplete', { - drawId, - error, - }); - const lock = redisLockErrorResponse(error); - await emit({ - type: 'error', - message: lock - ? '此签正在处理,请稍后重新查看。' - : error instanceof DivinationError || - error instanceof InventoryError - ? error.message - : '解签暂未完成,请稍后继续。', - }); - } - }); - }, -); -export default router; diff --git a/src/server/routes/api/divine-fortune.router.ts b/src/server/routes/api/divine-fortune.router.ts deleted file mode 100644 index 0a0ef4b1..00000000 --- a/src/server/routes/api/divine-fortune.router.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { redis } from '@server/lib/redis'; -import { parseRedisJson } from '@server/lib/redis/json'; -import type { AppEnv } from '@server/lib/hono/types'; -import { generateAiObject } from '@server/utils/aiClient'; -import { - getDivineFortunePrompt, - getRandomFallbackFortune, -} from '@server/utils/divineFortune'; -import { - DivineFortuneSchema, - type DivineFortune, -} from '@shared/lib/divineFortune'; -import { Hono } from 'hono'; - -const CACHE_KEY = 'divine_fortune_data'; -const CACHE_TTL = 60 * 60 * 24; - -const router = new Hono(); - -router.get('/', async (c) => { - try { - const cachedRaw = await redis.get(CACHE_KEY); - const cachedFortune = parseRedisJson(cachedRaw, CACHE_KEY); - if (cachedRaw && !cachedFortune) { - await redis.del(CACHE_KEY); - } - - if (cachedFortune) { - return c.json({ - success: true, - data: cachedFortune, - cached: true, - }); - } - - const [systemPrompt, userPrompt] = getDivineFortunePrompt(); - const aiResponse = await generateAiObject({ - system: systemPrompt, - prompt: userPrompt, - schema: DivineFortuneSchema, - name: 'DivineFortune', - sceneId: 'divine-fortune', - }); - - const fortune = aiResponse.output; - await redis.set(CACHE_KEY, JSON.stringify(fortune), 'EX', CACHE_TTL); - - return c.json({ - success: true, - data: fortune, - }); - } catch (error) { - console.error('天机推演 API 错误:', error); - return c.json({ - success: true, - data: getRandomFallbackFortune(), - fallback: true, - }); - } -}); - -export default router; diff --git a/src/server/routes/api/dungeon.router.ts b/src/server/routes/api/dungeon.router.ts deleted file mode 100644 index d9d8d227..00000000 --- a/src/server/routes/api/dungeon.router.ts +++ /dev/null @@ -1,570 +0,0 @@ -import { getExecutor } from '@server/lib/drizzle/db'; -import { dungeonHistories } from '@server/lib/drizzle/schema'; -import { - changeDungeonBattle, - getDungeonBattle, -} from '@server/lib/dungeon/combatV6'; -import { - checkDungeonLimit, - getDungeonLimitConfig, -} from '@server/lib/dungeon/dungeonLimiter'; -import { DungeonFlowError } from '@server/lib/dungeon/service_v2'; -import { - redisLockErrorResponse, - requireActiveCultivatorRef, -} from '@server/lib/hono/middleware'; -import { jsonWithStatus } from '@server/lib/hono/response'; -import type { AppEnv } from '@server/lib/hono/types'; -import { - DungeonStartError, - executeDungeonCommand, - readDungeonState, -} from '@server/lib/services/DungeonApplicationService'; -import { - QiInsufficientError, - QiServiceError, -} from '@server/lib/services/QiService'; -import { CombatAutoRequestSchema } from '@shared/combat-v6/auto'; -import { - CombatV6TrainingCommandRequestSchema, - CombatV6TrainingEventsQuerySchema, - CombatV6TrainingRevisionRequestSchema, -} from '@shared/contracts/combatV6'; -import { - DungeonActionRequestSchema, - DungeonBeginBattleRequestSchema, - DungeonFlowRequestSchema, -} from '@shared/contracts/combatV6Dungeon'; -import { desc, eq, sql } from 'drizzle-orm'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const StartSchema = z.object({ - mapNodeId: z.string().min(1), -}); - -const RecoverSchema = DungeonFlowRequestSchema.extend({ - action: z.enum([ - 'retry', - 'retry_continue', - 'retry_settle', - 'safe_retreat', - 'force_quit', - ]), -}); - -const router = new Hono(); -const historyRouter = new Hono(); -const limitRouter = new Hono(); -const lootingRouter = new Hono(); -const battleRouter = new Hono(); -battleRouter.post( - '/sessions/:id/auto', - requireActiveCultivatorRef(), - async (c) => { - const input = CombatAutoRequestSchema.parse(await c.req.json()); - const id = z.uuid().parse(c.req.param('id')); - try { - return c.json({ - success: true, - data: await changeDungeonBattle( - c.get('activeCultivatorRef')!, - id, - input.expectedRevision, - undefined, - input.round, - ), - }); - } catch (error) { - return c.json( - { error: error instanceof Error ? error.message : '自动指令提交失败' }, - 409, - ); - } - }, -); - -battleRouter.get( - '/sessions/current', - requireActiveCultivatorRef(), - async (c) => { - return c.json({ - success: true, - data: await getDungeonBattle(c.get('activeCultivatorRef')!.cultivatorId), - }); - }, -); -battleRouter.get('/sessions/:id', requireActiveCultivatorRef(), async (c) => { - const query = CombatV6TrainingEventsQuerySchema.parse(c.req.query()); - const data = await getDungeonBattle( - c.get('activeCultivatorRef')!.cultivatorId, - z.uuid().parse(c.req.param('id')), - query.afterEventSeq, - ); - return data - ? c.json({ success: true, data }) - : c.json({ error: '战斗不存在' }, 404); -}); -battleRouter.put( - '/sessions/:id/commands/:unitId', - requireActiveCultivatorRef(), - async (c) => { - const input = CombatV6TrainingCommandRequestSchema.parse( - await c.req.json(), - ); - try { - const data = await changeDungeonBattle( - { - userId: c.get('user')!.id, - cultivatorId: c.get('activeCultivatorRef')!.cultivatorId, - }, - z.uuid().parse(c.req.param('id')), - input.expectedRevision, - { unitId: c.req.param('unitId'), commands: input.commands }, - ); - return c.json({ success: true, data }); - } catch (error) { - return c.json( - { error: error instanceof Error ? error.message : '提交失败' }, - 409, - ); - } - }, -); -battleRouter.post( - '/sessions/:id/resolve', - requireActiveCultivatorRef(), - async (c) => { - const input = CombatV6TrainingRevisionRequestSchema.parse( - await c.req.json(), - ); - try { - const data = await changeDungeonBattle( - { - userId: c.get('user')!.id, - cultivatorId: c.get('activeCultivatorRef')!.cultivatorId, - }, - z.uuid().parse(c.req.param('id')), - input.expectedRevision, - ); - return c.json({ success: true, data }); - } catch (error) { - return c.json( - { error: error instanceof Error ? error.message : '结算失败' }, - 409, - ); - } - }, -); - -const BattleIdBodySchema = z.object({ - battleId: z.string().min(1), - requestId: z.string().min(1).max(120).optional(), -}); - -router.post('/start', requireActiveCultivatorRef(), async (c) => { - const user = c.get('user'); - const cultivator = c.get('activeCultivatorRef'); - if (!user || !cultivator) { - return c.json({ error: '未授权访问' }, 401); - } - - const { mapNodeId } = StartSchema.parse(await c.req.json()); - - try { - return c.json( - await executeDungeonCommand({ - userId: user.id, - cultivatorId: cultivator.cultivatorId, - command: { kind: 'start', mapNodeId }, - }), - ); - } catch (error) { - const lockErrorResponse = redisLockErrorResponse(error); - if (lockErrorResponse) return lockErrorResponse; - if (error instanceof DungeonStartError) { - return jsonWithStatus( - c, - { - error: error.message, - ...(error.readiness ? { readiness: error.readiness } : {}), - }, - error.status, - ); - } - if (error instanceof DungeonFlowError) { - return jsonWithStatus( - c, - { error: error.message, code: error.code }, - error.status, - ); - } - if (error instanceof QiInsufficientError) { - return c.json( - { - error: error.code, - message: error.message, - required: error.required, - current: error.current, - action: error.action, - }, - 409, - ); - } - if (error instanceof QiServiceError) { - return jsonWithStatus(c, { error: error.message }, error.status); - } - throw error; - } -}); - -router.get('/state', requireActiveCultivatorRef(), async (c) => { - const cultivator = c.get('activeCultivatorRef'); - if (!cultivator) { - return c.json({ error: '当前没有活跃角色' }, 404); - } - - const query = z - .object({ runId: z.uuid().optional() }) - .strict() - .parse(c.req.query()); - try { - const state = await readDungeonState(cultivator.cultivatorId, query.runId); - return c.json({ state }); - } catch (error) { - const lockErrorResponse = redisLockErrorResponse(error); - if (lockErrorResponse) return lockErrorResponse; - throw error; - } -}); - -router.post('/action', requireActiveCultivatorRef(), async (c) => { - try { - const cultivator = c.get('activeCultivatorRef'); - const user = c.get('user'); - if (!user || !cultivator) { - return c.json({ error: '未授权访问' }, 401); - } - - const input = DungeonActionRequestSchema.parse(await c.req.json()); - return c.json( - await executeDungeonCommand({ - userId: user.id, - cultivatorId: cultivator.cultivatorId, - command: { kind: 'action', ...input }, - }), - ); - } catch (error) { - const lockErrorResponse = redisLockErrorResponse(error); - if (lockErrorResponse) return lockErrorResponse; - if (error instanceof DungeonFlowError) { - return jsonWithStatus( - c, - { error: error.message, code: error.code }, - error.status, - ); - } - const message = error instanceof Error ? error.message : '副本推进失败'; - if (error instanceof z.ZodError) - return c.json({ error: '探索请求无效,请刷新后重试' }, 400); - if (error instanceof DungeonStartError) - return c.json({ error: message }, 409); - const status = - /不足|没有符合条件|资源消耗失败|所选物品|提交的材料|提交数量|选择需要提交/.test( - message, - ) - ? 409 - : 500; - return c.json({ error: message }, status); - } -}); - -router.post('/recover', requireActiveCultivatorRef(), async (c) => { - try { - const cultivator = c.get('activeCultivatorRef'); - const user = c.get('user'); - if (!user || !cultivator) { - return c.json({ error: '未授权访问' }, 401); - } - - const input = RecoverSchema.parse(await c.req.json()); - return c.json( - await executeDungeonCommand({ - userId: user.id, - cultivatorId: cultivator.cultivatorId, - command: { kind: 'recover', ...input }, - }), - ); - } catch (error) { - const lockErrorResponse = redisLockErrorResponse(error); - if (lockErrorResponse) return lockErrorResponse; - if (error instanceof DungeonFlowError) { - return jsonWithStatus( - c, - { error: error.message, code: error.code }, - error.status, - ); - } - const message = error instanceof Error ? error.message : '副本恢复失败'; - return c.json( - { error: message }, - error instanceof z.ZodError - ? 400 - : error instanceof DungeonStartError - ? 409 - : 500, - ); - } -}); - -router.post('/quit', requireActiveCultivatorRef(), async (c) => { - const user = c.get('user'); - const cultivator = c.get('activeCultivatorRef'); - if (!user || !cultivator) { - return c.json({ error: '未授权访问' }, 401); - } - - try { - return c.json( - await executeDungeonCommand({ - userId: user.id, - cultivatorId: cultivator.cultivatorId, - command: { - kind: 'quit', - ...DungeonFlowRequestSchema.parse(await c.req.json()), - }, - }), - ); - } catch (error) { - const lockErrorResponse = redisLockErrorResponse(error); - if (lockErrorResponse) return lockErrorResponse; - if (error instanceof DungeonFlowError) { - return jsonWithStatus( - c, - { error: error.message, code: error.code }, - error.status, - ); - } - if (error instanceof DungeonStartError) - return c.json({ error: error.message }, 409); - throw error; - } -}); - -historyRouter.get('/', requireActiveCultivatorRef(), async (c) => { - const cultivator = c.get('activeCultivatorRef'); - if (!cultivator) { - return c.json({ error: '当前没有活跃角色' }, 404); - } - - const page = Math.max(1, parseInt(c.req.query('page') || '1', 10)); - const pageSize = Math.min( - 50, - Math.max(1, parseInt(c.req.query('pageSize') || '10', 10)), - ); - const offset = (page - 1) * pageSize; - - const countResult = await getExecutor() - .select({ count: sql`count(*)` }) - .from(dungeonHistories) - .where(eq(dungeonHistories.cultivatorId, cultivator.cultivatorId)); - - const total = Number(countResult[0]?.count || 0); - const totalPages = Math.ceil(total / pageSize); - const records = await getExecutor() - .select({ - id: dungeonHistories.id, - theme: dungeonHistories.theme, - result: dungeonHistories.result, - log: dungeonHistories.log, - realGains: dungeonHistories.realGains, - createdAt: dungeonHistories.createdAt, - }) - .from(dungeonHistories) - .where(eq(dungeonHistories.cultivatorId, cultivator.cultivatorId)) - .orderBy(desc(dungeonHistories.createdAt)) - .limit(pageSize) - .offset(offset); - - return c.json({ - success: true, - data: { - records, - pagination: { - page, - pageSize, - total, - totalPages, - }, - }, - }); -}); - -limitRouter.get('/', requireActiveCultivatorRef(), async (c) => { - const cultivator = c.get('activeCultivatorRef'); - if (!cultivator) { - return c.json({ error: '当前没有活跃角色' }, 404); - } - - const limit = await checkDungeonLimit(cultivator.cultivatorId); - const config = getDungeonLimitConfig(); - return c.json({ - success: true, - data: { - ...limit, - dailyLimit: config.dailyLimit, - }, - }); -}); - -lootingRouter.post('/continue', requireActiveCultivatorRef(), async (c) => { - try { - const cultivator = c.get('activeCultivatorRef'); - const user = c.get('user'); - if (!user || !cultivator) { - return c.json({ error: '未授权访问' }, 401); - } - - return c.json( - await executeDungeonCommand({ - userId: user.id, - cultivatorId: cultivator.cultivatorId, - command: { - kind: 'looting-continue', - ...DungeonFlowRequestSchema.parse(await c.req.json()), - }, - }), - ); - } catch (error) { - const lockErrorResponse = redisLockErrorResponse(error); - if (lockErrorResponse) return lockErrorResponse; - if (error instanceof DungeonFlowError) { - return jsonWithStatus( - c, - { error: error.message, code: error.code }, - error.status, - ); - } - const message = error instanceof Error ? error.message : '副本推进失败'; - return c.json( - { error: message }, - error instanceof z.ZodError - ? 400 - : error instanceof DungeonStartError - ? 409 - : 500, - ); - } -}); - -lootingRouter.post('/escape', requireActiveCultivatorRef(), async (c) => { - try { - const cultivator = c.get('activeCultivatorRef'); - const user = c.get('user'); - if (!user || !cultivator) { - return c.json({ error: '未授权访问' }, 401); - } - - return c.json( - await executeDungeonCommand({ - userId: user.id, - cultivatorId: cultivator.cultivatorId, - command: { - kind: 'looting-escape', - ...DungeonFlowRequestSchema.parse(await c.req.json()), - }, - }), - ); - } catch (error) { - const lockErrorResponse = redisLockErrorResponse(error); - if (lockErrorResponse) return lockErrorResponse; - if (error instanceof DungeonFlowError) { - return jsonWithStatus( - c, - { error: error.message, code: error.code }, - error.status, - ); - } - const message = error instanceof Error ? error.message : '副本结算失败'; - return c.json( - { error: message }, - error instanceof z.ZodError - ? 400 - : error instanceof DungeonStartError - ? 409 - : 500, - ); - } -}); - -battleRouter.get('/probe', requireActiveCultivatorRef(), (c) => - c.json({ error: '旧查探入口已停用' }, 410), -); - -battleRouter.post('/begin', requireActiveCultivatorRef(), async (c) => { - const { encounterId } = DungeonBeginBattleRequestSchema.parse( - await c.req.json(), - ); - try { - return c.json( - await executeDungeonCommand({ - ...c.get('activeCultivatorRef')!, - command: { kind: 'battle-begin', encounterId }, - }), - ); - } catch (error) { - const locked = redisLockErrorResponse(error); - if (locked) return locked; - if (error instanceof DungeonFlowError) - return c.json({ error: error.message }, 409); - throw error; - } -}); - -battleRouter.post('/abandon', requireActiveCultivatorRef(), (c) => - c.json({ error: '旧放弃入口已停用,请使用战斗内逃跑' }, 410), -); - -battleRouter.post('/complete', requireActiveCultivatorRef(), async (c) => { - try { - const cultivator = c.get('activeCultivatorRef'); - const user = c.get('user'); - if (!user || !cultivator) { - return c.json({ error: '未授权访问' }, 401); - } - - const { battleId, requestId } = BattleIdBodySchema.parse( - await c.req.json(), - ); - const responsePayload = await executeDungeonCommand({ - userId: user.id, - cultivatorId: cultivator.cultivatorId, - command: { kind: 'battle-execute', battleId, requestId }, - }); - return c.json(responsePayload); - } catch (error) { - const lockErrorResponse = redisLockErrorResponse(error); - if (lockErrorResponse) return lockErrorResponse; - if (error instanceof DungeonFlowError) { - return jsonWithStatus( - c, - { error: error.message, code: error.code }, - error.status, - ); - } - const message = error instanceof Error ? error.message : '遭遇战执行失败'; - const status = /遭遇战|修真者/.test(message) ? 404 : 500; - return c.json({ error: message }, status); - } -}); - -battleRouter.post('/execute/v5', requireActiveCultivatorRef(), (c) => - c.json({ error: '旧战斗已停用,请刷新页面' }, 410), -); - -router.route('/history', historyRouter); -router.route('/limit', limitRouter); -router.route('/looting', lootingRouter); -router.route('/battle', battleRouter); - -export default router; diff --git a/src/server/routes/api/enlightenment.router.ts b/src/server/routes/api/enlightenment.router.ts deleted file mode 100644 index 2cb779c1..00000000 --- a/src/server/routes/api/enlightenment.router.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { - getValidatedJson, - redisLockErrorResponse, - requireActiveCultivatorRef, - validateJson, -} from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { PlayerCommandIdempotencyError } from '@server/lib/services/CommandExecutors'; -import { - enlightenManual, - readEnlightenment, -} from '@server/lib/services/EnlightenmentService'; -import { InventoryError } from '@server/lib/services/InventoryService'; -import { QiServiceError } from '@server/lib/services/QiService'; -import { EnlightenmentRequestSchema } from '@shared/contracts/enlightenment'; -import { InventoryRuleError } from '@shared/inventory'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const router = new Hono(); -router.use('*', requireActiveCultivatorRef()); -router.use('*', async (c, next) => { - c.header('Cache-Control', 'no-store'); - await next(); -}); -router.onError((error, c) => { - const lock = redisLockErrorResponse(error); - if (lock) return lock; - if (error instanceof z.ZodError) - return c.json({ success: false, error: '典籍参数无效' }, 400); - if ( - error instanceof InventoryError || - error instanceof InventoryRuleError || - error instanceof QiServiceError || - error instanceof PlayerCommandIdempotencyError - ) - return c.json( - { success: false, code: 'ENLIGHTENMENT_REJECTED', error: error.message }, - 409, - ); - console.error('[enlightenment] request failed', error); - return c.json( - { success: false, error: '参悟结果暂未确认,请重试核对本次结果' }, - 500, - ); -}); -router.get('/', async (c) => - c.json({ - success: true, - data: await readEnlightenment(c.get('activeCultivatorRef')!), - }), -); -router.post('/', validateJson(EnlightenmentRequestSchema), async (c) => - c.json({ - success: true, - ...(await enlightenManual( - c.get('activeCultivatorRef')!, - getValidatedJson(c), - )), - }), -); -export default router; diff --git a/src/server/routes/api/fate-reshape.router.ts b/src/server/routes/api/fate-reshape.router.ts deleted file mode 100644 index 5bc7a74f..00000000 --- a/src/server/routes/api/fate-reshape.router.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { - redisLockErrorResponse, - requireActiveCultivatorRef, -} from '@server/lib/hono/middleware'; -import { jsonWithStatus } from '@server/lib/hono/response'; -import type { AppEnv } from '@server/lib/hono/types'; -import { - FateReshapeService, - FateReshapeServiceError, -} from '@server/lib/services/FateReshapeService'; -import { - confirmFateReshapeCommand, - startFateReshapeCommand, -} from '@server/lib/services/FateReshapeApplicationService'; -import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const ConfirmSchema = z.object({ - selectedIndices: z.array(z.number().int().nonnegative()).length(3), -}); - -const router = new Hono(); - -router.get('/session', requireActiveCultivatorRef(), async (c) => { - const cultivator = c.get('activeCultivatorRef'); - if (!cultivator) { - return c.json({ success: false, error: '当前没有活跃角色' }, 404); - } - - try { - const [session, talismanCount] = await Promise.all([ - FateReshapeService.getSession(cultivator.cultivatorId), - FateReshapeService.getAvailableTalismanCount(cultivator.cultivatorId), - ]); - - return c.json({ - success: true, - data: { - session, - talismanCount, - }, - }); - } catch (error) { - return c.json( - { - success: false, - error: error instanceof Error ? error.message : '获取命格重塑状态失败', - }, - 400, - ); - } -}); - -router.post('/session', requireActiveCultivatorRef(), async (c) => { - const user = c.get('user'); - const cultivator = c.get('activeCultivatorRef'); - if (!user || !cultivator) { - return c.json({ success: false, error: '未授权访问' }, 401); - } - - try { - const committed = await startFateReshapeCommand({ - userId: user.id, - cultivatorId: cultivator.cultivatorId, - }); - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - const lockErrorResponse = redisLockErrorResponse(error); - if (lockErrorResponse) return lockErrorResponse; - const status = - error instanceof FateReshapeServiceError ? error.status : 400; - return jsonWithStatus( - c, - { - success: false, - error: error instanceof Error ? error.message : '开启命格重塑失败', - }, - status, - ); - } -}); - -router.post('/reroll', requireActiveCultivatorRef(), async (c) => { - const cultivator = c.get('activeCultivatorRef'); - if (!cultivator) { - return c.json({ success: false, error: '当前没有活跃角色' }, 404); - } - - try { - const session = await FateReshapeService.rerollSession(cultivator.cultivatorId); - return c.json({ - success: true, - data: { session }, - }); - } catch (error) { - const status = - error instanceof FateReshapeServiceError ? error.status : 400; - return jsonWithStatus( - c, - { - success: false, - error: error instanceof Error ? error.message : '命格重抽失败', - }, - status, - ); - } -}); - -router.post('/confirm', requireActiveCultivatorRef(), async (c) => { - const user = c.get('user'); - const cultivator = c.get('activeCultivatorRef'); - if (!user || !cultivator) { - return c.json({ success: false, error: '未授权访问' }, 401); - } - - try { - const parsed = ConfirmSchema.safeParse(await c.req.json()); - if (!parsed.success) { - return c.json({ success: false, error: '请求参数格式错误' }, 400); - } - - const committed = await confirmFateReshapeCommand({ - userId: user.id, - cultivatorId: cultivator.cultivatorId, - selectedIndices: parsed.data.selectedIndices, - }); - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - const lockErrorResponse = redisLockErrorResponse(error); - if (lockErrorResponse) return lockErrorResponse; - const status = - error instanceof FateReshapeServiceError ? error.status : 400; - return jsonWithStatus( - c, - { - success: false, - error: error instanceof Error ? error.message : '确认命格重塑失败', - }, - status, - ); - } -}); - -router.post('/abandon', requireActiveCultivatorRef(), async (c) => { - const cultivator = c.get('activeCultivatorRef'); - if (!cultivator) { - return c.json({ success: false, error: '当前没有活跃角色' }, 404); - } - - try { - await FateReshapeService.abandonSession(cultivator.cultivatorId); - return c.json({ success: true }); - } catch (error) { - const status = - error instanceof FateReshapeServiceError ? error.status : 400; - return jsonWithStatus( - c, - { - success: false, - error: error instanceof Error ? error.message : '放弃命格重塑失败', - }, - status, - ); - } -}); - -export default router; diff --git a/src/server/routes/api/feedback.router.ts b/src/server/routes/api/feedback.router.ts deleted file mode 100644 index e516925b..00000000 --- a/src/server/routes/api/feedback.router.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { getExecutor } from '@server/lib/drizzle/db'; -import { cultivators } from '@server/lib/drizzle/schema'; -import { - requireUser, - validateJson, - getValidatedJson, -} from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { createFeedback } from '@server/lib/repositories/feedbackRepository'; -import { FeedbackCreateRequestSchema } from '@shared/contracts/feedback'; -import { and, eq } from 'drizzle-orm'; -import { Hono } from 'hono'; - -const router = new Hono(); - -router.post('/', requireUser(), validateJson(FeedbackCreateRequestSchema), async (c) => { - try { - const user = c.get('user'); - if (!user) { - return c.json({ success: false, error: '未授权访问' }, 401); - } - - const q = getExecutor(); - const { type, content } = getValidatedJson<{ - type: 'bug' | 'feature' | 'balance' | 'other'; - content: string; - }>(c); - - const activeCultivator = await q.query.cultivators.findFirst({ - where: and(eq(cultivators.userId, user.id), eq(cultivators.status, 'active')), - }); - - const feedback = await createFeedback({ - userId: user.id, - cultivatorId: activeCultivator?.id ?? null, - type, - content, - }); - - return c.json({ - success: true, - data: { - feedbackId: feedback.id, - }, - }); - } catch (error) { - console.error('Create feedback error:', error); - return c.json({ success: false, error: '提交反馈失败,请稍后重试' }, 500); - } -}); - -export default router; diff --git a/src/server/routes/api/forging.router.ts b/src/server/routes/api/forging.router.ts deleted file mode 100644 index e6431678..00000000 --- a/src/server/routes/api/forging.router.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { - getValidatedJson, - getValidatedQuery, - redisLockErrorResponse, - requireActiveCultivatorRef, - validateJson, - validateQuery, -} from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { PlayerCommandIdempotencyError } from '@server/lib/services/CommandExecutors'; -import { - forgeEquipment, - readForge, - readVault, - withdrawMaterial, - withdrawVaultPage, -} from '@server/lib/services/ForgingService'; -import { InventoryError } from '@server/lib/services/InventoryService'; -import { QiServiceError } from '@server/lib/services/QiService'; -import { - ForgeRequestSchema, - VaultQuerySchema, - WithdrawMaterialSchema, - WithdrawVaultPageSchema, -} from '@shared/contracts/forging'; -import { InventoryRuleError } from '@shared/inventory'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const router = new Hono(); -router.use('*', requireActiveCultivatorRef()); -router.use('*', async (c, next) => { - c.header('Cache-Control', 'no-store'); - await next(); -}); -router.onError((error, c) => { - const lock = redisLockErrorResponse(error); - if (lock) return lock; - if (error instanceof z.ZodError) - return c.json({ success: false, error: '请求参数或材料数据无效' }, 400); - if ( - error instanceof InventoryError || - error instanceof InventoryRuleError || - error instanceof QiServiceError || - error instanceof PlayerCommandIdempotencyError - ) - return c.json({ success: false, error: error.message }, 409); - console.error('[forging] request failed', error); - return c.json( - { success: false, error: '请求未完成,请核对物品状态后重试' }, - 500, - ); -}); -router.get('/', async (c) => - c.json({ - success: true, - data: await readForge(c.get('activeCultivatorRef')!.cultivatorId), - }), -); -router.post('/', validateJson(ForgeRequestSchema), async (c) => - c.json({ - success: true, - ...(await forgeEquipment( - c.get('activeCultivatorRef')!.cultivatorId, - getValidatedJson(c), - c.get('activeCultivatorRef')!.userId, - )), - }), -); -router.get('/vault', validateQuery(VaultQuerySchema), async (c) => - c.json({ - success: true, - data: await readVault( - c.get('activeCultivatorRef')!.cultivatorId, - getValidatedQuery(c), - ), - }), -); -router.post( - '/vault/withdraw', - validateJson(WithdrawMaterialSchema), - async (c) => - c.json({ - success: true, - ...(await withdrawMaterial( - c.get('activeCultivatorRef')!.cultivatorId, - getValidatedJson(c), - )), - }), -); -router.post( - '/vault/withdraw-page', - validateJson(WithdrawVaultPageSchema), - async (c) => - c.json({ - success: true, - ...(await withdrawVaultPage( - c.get('activeCultivatorRef')!.cultivatorId, - getValidatedJson(c), - )), - }), -); -export default router; diff --git a/src/server/routes/api/friends.router.ts b/src/server/routes/api/friends.router.ts deleted file mode 100644 index 5891b976..00000000 --- a/src/server/routes/api/friends.router.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { - getValidatedQuery, - requireActiveCultivatorRef, - validateQuery, -} from '@server/lib/hono/middleware'; -import { jsonWithStatus } from '@server/lib/hono/response'; -import { acquireRedisCooldown } from '@server/lib/redis/cooldownLimiter'; -import type { AppEnv } from '@server/lib/hono/types'; -import { FRIEND_SEARCH_COOLDOWN_SECONDS } from '@shared/config/socialConfig'; -import { - addFriendPair, - FriendServiceError, - getInviteTarget, - listFriends, - removeFriendPair, - searchActiveCultivatorsByExactName, -} from '@server/lib/services/FriendService'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const CultivatorIdSchema = z.object({ - cultivatorId: z.string().uuid(), -}); - -const FriendSearchQuerySchema = z.object({ - name: z.string().trim().min(1).max(100), -}); - -type FriendSearchQuery = z.infer; - -const router = new Hono(); - -router.get('/', requireActiveCultivatorRef(), async (c) => { - const cultivator = c.get('activeCultivatorRef'); - if (!cultivator) { - return c.json({ error: '未授权访问' }, 401); - } - - const friends = await listFriends(cultivator.cultivatorId); - return c.json({ friends }); -}); - -router.get( - '/search', - requireActiveCultivatorRef(), - validateQuery(FriendSearchQuerySchema), - async (c) => { - const cultivator = c.get('activeCultivatorRef'); - if (!cultivator) { - return c.json({ error: '未授权访问' }, 401); - } - - const cooldown = await acquireRedisCooldown({ - key: `friends:search:cooldown:${cultivator.cultivatorId}`, - cooldownSeconds: FRIEND_SEARCH_COOLDOWN_SECONDS, - allowWhenRedisUnavailable: true, - }); - if (!cooldown.allowed) { - return c.json( - { error: `请 ${cooldown.remainingSeconds} 秒后再搜索` }, - 429, - ); - } - - const { name } = getValidatedQuery(c); - const results = await searchActiveCultivatorsByExactName( - cultivator.cultivatorId, - name, - ); - return c.json({ results }); - }, -); - -router.get('/invite/:cultivatorId', requireActiveCultivatorRef(), async (c) => { - const cultivator = c.get('activeCultivatorRef'); - if (!cultivator) { - return c.json({ error: '未授权访问' }, 401); - } - - try { - const { cultivatorId } = CultivatorIdSchema.parse({ - cultivatorId: c.req.param('cultivatorId'), - }); - const result = await getInviteTarget(cultivator.cultivatorId, cultivatorId); - return c.json(result); - } catch (error) { - if (error instanceof z.ZodError) { - return c.json({ error: '参数错误', details: error.issues }, 400); - } - if (error instanceof FriendServiceError) { - return jsonWithStatus(c, { error: error.message }, error.status); - } - console.error('Friend invite API error:', error); - return c.json({ error: '查询道友失败' }, 500); - } -}); - -router.post('/:cultivatorId', requireActiveCultivatorRef(), async (c) => { - const cultivator = c.get('activeCultivatorRef'); - if (!cultivator) { - return c.json({ error: '未授权访问' }, 401); - } - - try { - const { cultivatorId } = CultivatorIdSchema.parse({ - cultivatorId: c.req.param('cultivatorId'), - }); - const friend = await addFriendPair(cultivator.cultivatorId, cultivatorId); - return c.json({ friend, message: '已加入好友名录' }); - } catch (error) { - if (error instanceof z.ZodError) { - return c.json({ error: '参数错误', details: error.issues }, 400); - } - if (error instanceof FriendServiceError) { - return jsonWithStatus(c, { error: error.message }, error.status); - } - console.error('Friend add API error:', error); - return c.json({ error: '添加道友失败' }, 500); - } -}); - -router.delete('/:cultivatorId', requireActiveCultivatorRef(), async (c) => { - const cultivator = c.get('activeCultivatorRef'); - if (!cultivator) { - return c.json({ error: '未授权访问' }, 401); - } - - try { - const { cultivatorId } = CultivatorIdSchema.parse({ - cultivatorId: c.req.param('cultivatorId'), - }); - await removeFriendPair(cultivator.cultivatorId, cultivatorId); - return c.json({ message: '已移出好友名录' }); - } catch (error) { - if (error instanceof z.ZodError) { - return c.json({ error: '参数错误', details: error.issues }, 400); - } - console.error('Friend delete API error:', error); - return c.json({ error: '移除道友失败' }, 500); - } -}); - -export default router; diff --git a/src/server/routes/api/generate-character.router.ts b/src/server/routes/api/generate-character.router.ts deleted file mode 100644 index f16eb478..00000000 --- a/src/server/routes/api/generate-character.router.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { requireUser } from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { getRequestIp } from '@server/lib/http/requestIp'; -import { - consumeCharacterGenerationQuota, - getCharacterGenerationQuota, -} from '@server/lib/redis/characterGenerationLimiter'; -import { saveTempCharacter } from '@server/lib/repositories/redisCultivatorRepository'; -import { generateCultivatorFromAI } from '@server/utils/characterEngine'; -import { normalizeFreeformLlmInput } from '@server/utils/llmPayload'; -import { - CHARACTER_GENERATION_LIMIT_REACHED_CODE, - type CharacterGenerationQuota, - type CharacterGenerationQuotaResponse, - type GenerateCharacterResponse, -} from '@shared/contracts/character-generation'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const MIN_PROMPT_LENGTH = 2; -const MAX_PROMPT_LENGTH = 200; - -const GenerateCharacterSchema = z.object({ - userInput: z.string(), -}); - -const countChars = (input: string): number => Array.from(input).length; - -function getQuotaExceededMessage(quota: CharacterGenerationQuota): string { - switch (quota.limitedBy) { - case 'email': - return '该邮箱今日角色推演次数已用尽(每日限 6 次),请明日再试。'; - case 'ip': - return '当前网络今日角色推演次数已用尽(每日限 6 次),请明日再试。'; - case 'both': - return '该邮箱与当前网络今日角色推演次数均已用尽,请明日再试。'; - default: - return '今日角色推演次数已用尽,请明日再试。'; - } -} - -const router = new Hono(); - -router.get('/quota', requireUser(), async (c) => { - const user = c.get('user'); - if (!user) { - return c.json({ success: false, error: '未授权访问' }, 401); - } - - const quota = await getCharacterGenerationQuota({ - email: user.email, - ip: getRequestIp(c), - }); - - return c.json({ - success: true, - data: { - quota, - }, - }); -}); - -router.post('/', requireUser(), async (c) => { - const user = c.get('user'); - if (!user) { - return c.json({ success: false, error: '未授权访问' }, 401); - } - - const parsed = GenerateCharacterSchema.safeParse(await c.req.json()); - if (!parsed.success) { - return c.json( - { - success: false, - error: '请求参数格式错误,请重新输入角色描述。', - }, - 400, - ); - } - - const userInput = normalizeFreeformLlmInput(parsed.data.userInput); - const promptLength = countChars(userInput); - - if (promptLength < MIN_PROMPT_LENGTH) { - return c.json( - { - success: false, - error: `角色描述至少需要 ${MIN_PROMPT_LENGTH} 个字。`, - }, - 400, - ); - } - - if (promptLength > MAX_PROMPT_LENGTH) { - return c.json( - { - success: false, - error: `角色描述过长(当前 ${promptLength} 字,最多 ${MAX_PROMPT_LENGTH} 字)。`, - code: 'PROMPT_TOO_LONG', - details: { - currentLength: promptLength, - maxLength: MAX_PROMPT_LENGTH, - }, - }, - 422, - ); - } - - const quotaResult = await consumeCharacterGenerationQuota({ - email: user.email, - ip: getRequestIp(c), - }); - - if (!quotaResult.allowed) { - return c.json( - { - success: false, - code: CHARACTER_GENERATION_LIMIT_REACHED_CODE, - error: getQuotaExceededMessage(quotaResult.quota), - quota: quotaResult.quota, - }, - 429, - ); - } - - const { cultivator } = await generateCultivatorFromAI(userInput); - const tempCultivatorId = await saveTempCharacter(cultivator); - - return c.json({ - success: true, - data: { - cultivator, - tempCultivatorId, - quota: quotaResult.quota, - }, - }); -}); - -export default router; diff --git a/src/server/routes/api/generate-fates.router.ts b/src/server/routes/api/generate-fates.router.ts deleted file mode 100644 index 5e1f99e1..00000000 --- a/src/server/routes/api/generate-fates.router.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { requireUser } from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { FATE_REROLL_LIMIT } from '@server/lib/services/FateConfig'; -import { FateEngine } from '@server/lib/services/FateEngine'; -import { - checkAndIncrementReroll, - getTempCharacter, - getTempFates, - saveTempFates, -} from '@server/lib/repositories/redisCultivatorRepository'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const GenerateFatesSchema = z.object({ - tempId: z.string().min(1), -}); - -const router = new Hono(); - -router.post('/', requireUser(), async (c) => { - const parsed = GenerateFatesSchema.safeParse(await c.req.json()); - if (!parsed.success) { - return c.json({ success: false, error: '请求参数格式错误' }, 400); - } - - const { tempId } = parsed.data; - const cultivator = await getTempCharacter(tempId); - if (!cultivator) { - return c.json({ success: false, error: '角色推演已过期,请重新生成。' }, 404); - } - - const previousFates = await getTempFates(tempId); - let remainingRerolls = FATE_REROLL_LIMIT; - - if (previousFates && previousFates.length > 0) { - const rerollCheck = await checkAndIncrementReroll(tempId, FATE_REROLL_LIMIT); - if (!rerollCheck.allowed) { - return c.json( - { - success: false, - error: `逆天改命次数已尽(最多 ${FATE_REROLL_LIMIT} 次)`, - }, - 400, - ); - } - - remainingRerolls = rerollCheck.remaining; - } - - const fates = await FateEngine.generateCandidatePool(); - await saveTempFates(tempId, fates); - - return c.json({ - success: true, - data: { - fates, - remainingRerolls, - }, - }); -}); - -export default router; diff --git a/src/server/routes/api/hunts.router.ts b/src/server/routes/api/hunts.router.ts deleted file mode 100644 index 6811f331..00000000 --- a/src/server/routes/api/hunts.router.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { - requireActiveCultivatorRef, - validateJson, -} from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { isRedisLockContention } from '@server/lib/redis/lock'; -import { ArenaV6Error } from '@server/lib/services/combat-v6/CombatV6ArenaService'; -import { huntBattleReward } from '@server/lib/services/hunts/HuntRewardProjector'; -import { - commandHuntTeam, - createHuntTeam, - huntLobby, - joinHuntTeam, -} from '@server/lib/services/hunts/HuntTeamService'; -import { - HuntCreateTeamSchema, - HuntEventIdSchema, - HuntTeamCommandSchema, - type HuntTeamCommand, -} from '@shared/contracts/hunts'; -import { huntEventsAt } from '@shared/hunts/config'; -import { Hono } from 'hono'; -import { z } from 'zod'; -const router = new Hono(); -router.use('*', requireActiveCultivatorRef()); -router.use('*', async (c, next) => { - c.header('Cache-Control', 'private, no-store'); - await next(); -}); -router.onError((error, c) => { - if (error instanceof ArenaV6Error) - return c.json({ success: false, error: error.message }, error.status); - if (error instanceof z.ZodError) - return c.json( - { success: false, error: '未能完成操作,请刷新后再试' }, - 400, - ); - if (isRedisLockContention(error)) - return c.json( - { success: false, error: '队伍正忙,请稍后再试' }, - 409, - ); - console.error('[hunt] request failed', error); - return c.json( - { success: false, error: '暂时联系不上讨伐队伍,请稍后再试' }, - 500, - ); -}); -router.get('/', (c) => - c.json({ - success: true, - data: { events: huntEventsAt(Date.now()), serverNow: Date.now() }, - }), -); -router.get('/battles/:battleId/reward', async (c) => - c.json({ - success: true, - data: await huntBattleReward( - z.uuid().parse(c.req.param('battleId')), - c.get('activeCultivatorRef')!, - ), - }), -); -router.get('/:eventId', async (c) => - c.json({ - success: true, - data: await huntLobby( - HuntEventIdSchema.parse(c.req.param('eventId')), - c.get('activeCultivatorRef')!, - ), - }), -); -router.post('/teams', validateJson(HuntCreateTeamSchema), async (c) => - c.json({ - success: true, - data: await createHuntTeam( - c.get('activeCultivatorRef')!, - c.get('validatedJson') as z.infer, - ), - }), -); -router.post( - '/:eventId/join', - validateJson(z.object({ teamId: z.uuid().optional() }).strict()), - async (c) => - c.json({ - success: true, - data: await joinHuntTeam( - c.get('activeCultivatorRef')!, - HuntEventIdSchema.parse(c.req.param('eventId')), - (c.get('validatedJson') as { teamId?: string }).teamId, - ), - }), -); -router.post('/teams/:teamId', validateJson(HuntTeamCommandSchema), async (c) => - c.json({ - success: true, - data: await commandHuntTeam( - c.get('activeCultivatorRef')!, - z.uuid().parse(c.req.param('teamId')), - c.get('validatedJson') as HuntTeamCommand, - ), - }), -); -export default router; diff --git a/src/server/routes/api/identity-reshape.router.ts b/src/server/routes/api/identity-reshape.router.ts deleted file mode 100644 index 5af005f5..00000000 --- a/src/server/routes/api/identity-reshape.router.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { - redisLockErrorResponse, - requireActiveCultivatorRef, -} from '@server/lib/hono/middleware'; -import { jsonWithStatus } from '@server/lib/hono/response'; -import type { AppEnv } from '@server/lib/hono/types'; -import { - abandonIdentityReshape, - confirmIdentityReshape, - generateIdentityReshape, - getIdentityReshapeSession, - getIdentityReshapeTalismanCount, - IdentityReshapeServiceError, - saveIdentityReshapeDraft, - startIdentityReshape, -} from '@server/lib/services/IdentityReshapeService'; -import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; -import { - IDENTITY_RESHAPE_DESCRIPTION_MAX_LENGTH, - IDENTITY_RESHAPE_DESCRIPTION_MIN_LENGTH, -} from '@shared/config/identityReshape'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const AnswerSchema = z.object({ - questionId: z.string().min(1).max(80), - optionId: z.string().min(1).max(20), -}); -const DraftSchema = z.object({ - answers: z.array(AnswerSchema).max(3), - description: z.string().trim().max(IDENTITY_RESHAPE_DESCRIPTION_MAX_LENGTH), -}); -const GenerateSchema = DraftSchema.extend({ - answers: z.array(AnswerSchema).length(3), - description: z - .string() - .trim() - .min(IDENTITY_RESHAPE_DESCRIPTION_MIN_LENGTH) - .max(IDENTITY_RESHAPE_DESCRIPTION_MAX_LENGTH), -}); - -const router = new Hono(); - -function respondError(c: Parameters[0], error: unknown) { - const lockResponse = redisLockErrorResponse(error); - if (lockResponse) return lockResponse; - return jsonWithStatus( - c, - { - success: false, - error: error instanceof Error ? error.message : '改天换地失败', - }, - error instanceof IdentityReshapeServiceError ? error.status : 400, - ); -} - -router.get('/session', requireActiveCultivatorRef(), async (c) => { - const cultivator = c.get('activeCultivatorRef'); - if (!cultivator) - return c.json({ success: false, error: '当前没有活跃角色' }, 404); - try { - const [session, talismanCount] = await Promise.all([ - getIdentityReshapeSession(cultivator.cultivatorId), - getIdentityReshapeTalismanCount(cultivator.cultivatorId), - ]); - return c.json({ success: true, data: { session, talismanCount } }); - } catch (error) { - return respondError(c, error); - } -}); - -router.post('/session', requireActiveCultivatorRef(), async (c) => { - const user = c.get('user'); - const cultivator = c.get('activeCultivatorRef'); - if (!user || !cultivator) - return c.json({ success: false, error: '未授权访问' }, 401); - try { - const result = await startIdentityReshape({ - userId: user.id, - cultivatorId: cultivator.cultivatorId, - }); - if (result.committed) { - return c.json( - toPlayerStateMutationResponse({ - ...result.committed, - result: { - session: result.session, - talismanCount: result.talismanCount, - }, - }), - ); - } - return c.json({ - success: true, - data: { session: result.session, talismanCount: result.talismanCount }, - }); - } catch (error) { - return respondError(c, error); - } -}); - -router.patch('/session', requireActiveCultivatorRef(), async (c) => { - const cultivator = c.get('activeCultivatorRef'); - if (!cultivator) - return c.json({ success: false, error: '当前没有活跃角色' }, 404); - const parsed = DraftSchema.safeParse(await c.req.json()); - if (!parsed.success) - return c.json({ success: false, error: '问答草稿格式错误' }, 400); - try { - const session = await saveIdentityReshapeDraft({ - cultivatorId: cultivator.cultivatorId, - ...parsed.data, - }); - return c.json({ success: true, data: { session } }); - } catch (error) { - return respondError(c, error); - } -}); - -router.post('/generate', requireActiveCultivatorRef(), async (c) => { - const cultivator = c.get('activeCultivatorRef'); - if (!cultivator) - return c.json({ success: false, error: '当前没有活跃角色' }, 404); - const parsed = GenerateSchema.safeParse(await c.req.json()); - if (!parsed.success) - return c.json({ success: false, error: '请完成问答并填写身世描述' }, 400); - try { - const session = await generateIdentityReshape({ - cultivatorId: cultivator.cultivatorId, - ...parsed.data, - }); - return c.json({ success: true, data: { session } }); - } catch (error) { - return respondError(c, error); - } -}); - -router.post('/confirm', requireActiveCultivatorRef(), async (c) => { - const user = c.get('user'); - const cultivator = c.get('activeCultivatorRef'); - if (!user || !cultivator) - return c.json({ success: false, error: '未授权访问' }, 401); - try { - const committed = await confirmIdentityReshape({ - userId: user.id, - cultivatorId: cultivator.cultivatorId, - }); - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - return respondError(c, error); - } -}); - -router.post('/abandon', requireActiveCultivatorRef(), async (c) => { - const cultivator = c.get('activeCultivatorRef'); - if (!cultivator) - return c.json({ success: false, error: '当前没有活跃角色' }, 404); - try { - await abandonIdentityReshape(cultivator.cultivatorId); - return c.json({ success: true }); - } catch (error) { - return respondError(c, error); - } -}); - -export default router; diff --git a/src/server/routes/api/index.ts b/src/server/routes/api/index.ts deleted file mode 100644 index 1dcd44e7..00000000 --- a/src/server/routes/api/index.ts +++ /dev/null @@ -1,149 +0,0 @@ -import huntsRouter from './hunts.router'; -import playerJournalRouter from './player-journal.router'; -import { artifactMigrationRouter } from './artifact-migration'; -import { manualMigrationRouter } from './manual-migration'; -import divinationRouter from './divination.router'; -import type { AppEnv } from '@server/lib/hono/types'; -import { getMessageInfrastructureHealthStatus } from '@server/lib/mq/domainEventRegistry'; -import { getNatsHealthStatus } from '@server/lib/nats'; -import { getRedisHealthStatus } from '@server/lib/redis'; -import accountRouter from '@server/routes/api/account.router'; -import adminRouter from '@server/routes/api/admin'; -import alchemyFormulasRouter from '@server/routes/api/alchemy-formulas.router'; -import arenaRouter from '@server/routes/api/arena.router'; -import auctionRouter from '@server/routes/api/auction.router'; -import battleRecordsRouter from '@server/routes/api/battle-records.router'; -import blackMarketRouter from '@server/routes/api/black-market.router'; -import captchaRouter from '@server/routes/api/captcha.router'; -import combatV6ArenaRouter from '@server/routes/api/combat-v6-arena.router'; -import combatV6AutoStrategyRouter from '@server/routes/api/combat-v6-auto-strategy.router'; -import combatV6Router from '@server/routes/api/combat-v6.router'; -import combatV6SharesRouter from '@server/routes/api/combat-v6-shares.router'; -import communityRouter from '@server/routes/api/community.router'; -import craftRouter from '@server/routes/api/craft.router'; -import cultivatorRouter from '@server/routes/api/cultivator.router'; -import cultivatorsRouter from '@server/routes/api/cultivators.router'; -import divineFortuneRouter from '@server/routes/api/divine-fortune.router'; -import dungeonRouter from '@server/routes/api/dungeon.router'; -import enemiesRouter from '@server/routes/api/enemies.router'; -import fateReshapeRouter from '@server/routes/api/fate-reshape.router'; -import feedbackRouter from '@server/routes/api/feedback.router'; -import friendsRouter from '@server/routes/api/friends.router'; -import generateCharacterRouter from '@server/routes/api/generate-character.router'; -import generateFatesRouter from '@server/routes/api/generate-fates.router'; -import identityReshapeRouter from '@server/routes/api/identity-reshape.router'; -import marketRouter from '@server/routes/api/market.router'; -import productsRouter from '@server/routes/api/products.router'; -import rankingsRouter from '@server/routes/api/rankings.router'; -import realtimeRouter from '@server/routes/api/realtime.router'; -import reputationShopRouter from '@server/routes/api/reputation-shop.router'; -import saveCharacterRouter from '@server/routes/api/save-character.router'; -import sectsRouter from '@server/routes/api/sects.router'; -import spiritFieldRouter from '@server/routes/api/spirit-field.router'; -import sponsorshipRouter from '@server/routes/api/sponsorship.router'; -import storyRouter from '@server/routes/api/story.router'; -import tasksRouter from '@server/routes/api/tasks.router'; -import towerRouter from '@server/routes/api/tower.router'; -import worldChatRouter from '@server/routes/api/world-chat.router'; -import playerRouter from '@server/routes/player.router'; -import { allowsLocalDevTools } from '@shared/config/deployment'; -import { Hono } from 'hono'; -import manualsRouter from './combat-v6-manuals.router'; -import sectV6Router from './combat-v6-sect.router'; -import sectTasksV6Router from './combat-v6-sect-tasks.router'; -import breakthroughV6Router from './combat-v6-breakthrough.router'; -import devResourcesRouter from './dev-resources.router'; -import forgingRouter from './forging.router'; -import enlightenmentRouter from './enlightenment.router'; -import inscriptionsRouter from './inscriptions.router'; - -const apiRouter = new Hono(); - -apiRouter.get('/health-check', async (c) => { - const [redis, nats] = await Promise.all([ - getRedisHealthStatus(), - getNatsHealthStatus(), - ]); - const messaging = getMessageInfrastructureHealthStatus(); - if (redis === 'down' || nats === 'down' || messaging === 'down') { - return c.json( - { - success: false, - error: - redis === 'down' - ? 'Redis unavailable' - : nats === 'down' - ? 'NATS unavailable' - : 'Message infrastructure unavailable', - redis, - nats, - messaging, - }, - 503, - ); - } - - return c.json({ - success: true, - message: 'OK', - redis, - nats, - messaging, - }); -}); - -apiRouter.route('/player', playerRouter); -apiRouter.route('/player-journal', playerJournalRouter); -apiRouter.route('/account', accountRouter); -apiRouter.route('/admin', adminRouter); -apiRouter.route('/alchemy', alchemyFormulasRouter); -apiRouter.route('/auction', auctionRouter); -apiRouter.route('/arena', arenaRouter); -apiRouter.route('/hunts', huntsRouter); -apiRouter.route('/battle-records', battleRecordsRouter); -apiRouter.route('/black-market', blackMarketRouter); -apiRouter.route('/captcha', captchaRouter); -apiRouter.route('/community', communityRouter); -apiRouter.route('/combat-v6/arena', combatV6ArenaRouter); -apiRouter.route('/combat-v6/auto-strategy', combatV6AutoStrategyRouter); -apiRouter.route('/combat-v6/forging', forgingRouter); -apiRouter.route('/combat-v6/enlightenment', enlightenmentRouter); -apiRouter.route('/combat-v6/inscriptions', inscriptionsRouter); -apiRouter.route('/combat-v6/manuals', manualsRouter); -apiRouter.route('/manual-migration', manualMigrationRouter); -apiRouter.route('/artifact-migration', artifactMigrationRouter); -apiRouter.route('/combat-v6/sect', sectV6Router); -apiRouter.route('/combat-v6/sect-tasks', sectTasksV6Router); -apiRouter.route('/combat-v6/breakthrough', breakthroughV6Router); -if (allowsLocalDevTools(process.env.APP_ENV, process.env.NODE_ENV)) - apiRouter.route('/dev', devResourcesRouter); -apiRouter.route('/combat-v6', combatV6Router); -apiRouter.route('/combat-v6-shares', combatV6SharesRouter); -apiRouter.route('/craft', craftRouter); -apiRouter.route('/cultivator', cultivatorRouter); -apiRouter.route('/cultivators', cultivatorsRouter); -apiRouter.route('/divine-fortune', divineFortuneRouter); -apiRouter.route('/divination', divinationRouter); -apiRouter.route('/dungeon', dungeonRouter); -apiRouter.route('/enemies', enemiesRouter); -apiRouter.route('/fate-reshape', fateReshapeRouter); -apiRouter.route('/identity-reshape', identityReshapeRouter); -apiRouter.route('/feedback', feedbackRouter); -apiRouter.route('/friends', friendsRouter); -apiRouter.route('/generate-character', generateCharacterRouter); -apiRouter.route('/generate-fates', generateFatesRouter); -apiRouter.route('/market', marketRouter); -apiRouter.route('/rankings', rankingsRouter); -apiRouter.route('/realtime', realtimeRouter); -apiRouter.route('/reputation-shop', reputationShopRouter); -apiRouter.route('/save-character', saveCharacterRouter); -apiRouter.route('/tasks', tasksRouter); -apiRouter.route('/story', storyRouter); -apiRouter.route('/tower', towerRouter); -apiRouter.route('/sects', sectsRouter); -apiRouter.route('/sponsorship', sponsorshipRouter); -apiRouter.route('/spirit-field', spiritFieldRouter); -apiRouter.route('/v2/products', productsRouter); -apiRouter.route('/world-chat', worldChatRouter); - -export default apiRouter; diff --git a/src/server/routes/api/inscriptions.router.ts b/src/server/routes/api/inscriptions.router.ts deleted file mode 100644 index 0f1ec94f..00000000 --- a/src/server/routes/api/inscriptions.router.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { - getValidatedJson, - redisLockErrorResponse, - requireActiveCultivatorRef, - validateJson, -} from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { PlayerCommandIdempotencyError } from '@server/lib/services/CommandExecutors'; -import { - mutateInscriptions, - readInscriptions, -} from '@server/lib/services/InscriptionService'; -import { InventoryError } from '@server/lib/services/InventoryService'; -import { - QiInsufficientError, - QiServiceError, -} from '@server/lib/services/QiService'; -import { InscriptionRequestSchema } from '@shared/contracts/inscriptions'; -import { InventoryRuleError } from '@shared/inventory'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const router = new Hono(); -router.use('*', requireActiveCultivatorRef()); -router.use('*', async (c, next) => { - c.header('Cache-Control', 'no-store'); - await next(); -}); -router.onError((error, c) => { - const lock = redisLockErrorResponse(error); - if (lock) return lock; - if (error instanceof z.ZodError) - return c.json( - { success: false, code: 'INSCRIPTION_REJECTED', error: '阵纹参数无效' }, - 400, - ); - if ( - error instanceof InventoryError || - error instanceof InventoryRuleError || - error instanceof QiServiceError || - error instanceof QiInsufficientError || - error instanceof PlayerCommandIdempotencyError - ) - return c.json( - { success: false, code: 'INSCRIPTION_REJECTED', error: error.message }, - 409, - ); - console.error('[inscriptions] request failed', error); - return c.json({ success: false, error: '本次结果暂未确认,请重试核对' }, 500); -}); -router.get('/', async (c) => - c.json({ - success: true, - data: await readInscriptions(c.get('activeCultivatorRef')!), - }), -); -router.post('/', validateJson(InscriptionRequestSchema), async (c) => - c.json({ - success: true, - ...(await mutateInscriptions( - c.get('activeCultivatorRef')!, - getValidatedJson(c), - )), - }), -); -export default router; diff --git a/src/server/routes/api/manual-migration/index.ts b/src/server/routes/api/manual-migration/index.ts deleted file mode 100644 index 37020fd1..00000000 --- a/src/server/routes/api/manual-migration/index.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { - getValidatedJson, - redisLockErrorResponse, - requireActiveCultivatorRef, - requireAdmin, - validateJson, -} from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { InventoryError } from '@server/lib/services/InventoryService'; -import { ExchangeManualSchema } from '@shared/contracts/manualMigration'; -import { InventoryRuleError } from '@shared/inventory'; -import { Hono } from 'hono'; -import { z } from 'zod'; -import { - exchangeManualMigration, - manualMigrationAvailability, - readManualMigration, - readManualMigrationAdmin, -} from './service'; -function router() { - const app = new Hono(); - app.use('*', async (c, next) => { - c.header('Cache-Control', 'no-store'); - await next(); - }); - app.onError((error, c) => { - const lock = redisLockErrorResponse(error); - if (lock) return lock; - if (error instanceof z.ZodError) - return c.json({ success: false, error: '兑换参数无效' }, 400); - if (error instanceof InventoryError || error instanceof InventoryRuleError) - return c.json({ success: false, error: error.message }, 409); - console.error('[manual-migration]', error); - return c.json( - { success: false, error: '结果暂未确认,请刷新旧功法列表并核对背包' }, - 500, - ); - }); - return app; -} -export const manualMigrationRouter = router(); -manualMigrationRouter.use('*', requireActiveCultivatorRef()); -manualMigrationRouter.get('/availability', async (c) => - c.json({ - success: true, - data: await manualMigrationAvailability(c.get('activeCultivatorRef')!), - }), -); -manualMigrationRouter.get('/', async (c) => - c.json({ - success: true, - data: await readManualMigration(c.get('activeCultivatorRef')!), - }), -); -manualMigrationRouter.post( - '/exchange', - validateJson(ExchangeManualSchema), - async (c) => - c.json({ - success: true, - ...(await exchangeManualMigration( - c.get('activeCultivatorRef')!, - getValidatedJson(c), - )), - }), -); -export const manualMigrationAdminRouter = router(); -manualMigrationAdminRouter.use('*', requireAdmin()); -manualMigrationAdminRouter.get('/', async (c) => - c.json({ success: true, data: await readManualMigrationAdmin() }), -); diff --git a/src/server/routes/api/market.router.ts b/src/server/routes/api/market.router.ts deleted file mode 100644 index c9a2c9dc..00000000 --- a/src/server/routes/api/market.router.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { - redisLockErrorResponse, - requireActiveCultivatorRef, -} from '@server/lib/hono/middleware'; -import { jsonWithStatus } from '@server/lib/hono/response'; -import type { AppEnv } from '@server/lib/hono/types'; -import { PlayerCommandIdempotencyError } from '@server/lib/services/CommandExecutors'; -import { purchaseMarketItems } from '@server/lib/services/MarketApplicationService'; -import { MarketRecycleError } from '@server/lib/services/MarketRecycleService'; -import { - getMarketListings, - MarketServiceError, - resolveLayer, - resolveNodeId, -} from '@server/lib/services/MarketService'; -import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; -import { readCultivatorRealm } from '@server/lib/services/cultivator/CultivatorFactsReader'; -import { getPlayerPreHeavenFates } from '@server/lib/services/cultivator/CultivatorProfileRepository'; -import { MarketBuySchema } from '@shared/contracts/market'; -import type { PreHeavenFate } from '@shared/types/cultivator'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -import { - confirmBagRecycle, - previewBagRecycle, -} from '@server/lib/services/BagRecycleService'; -import { RecycleRequestSchema } from '@shared/contracts/recycle'; - -const router = new Hono(); - -async function loadMarketFates(cultivator: { - cultivatorId: string; - userId: string; -}): Promise { - return ( - (await getPlayerPreHeavenFates( - cultivator.userId, - cultivator.cultivatorId, - )) ?? [] - ); -} - -router.post('/recycle', requireActiveCultivatorRef(), async (c) => { - const ref = c.get('activeCultivatorRef')!; - try { - const input = RecycleRequestSchema.parse(await c.req.json()); - if (input.phase === 'preview') - return c.json({ - success: true, - data: await previewBagRecycle(ref.cultivatorId, input.items), - }); - const committed = await confirmBagRecycle( - { userId: ref.userId, cultivatorId: ref.cultivatorId }, - input.quoteId, - ); - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - const lockResponse = redisLockErrorResponse(error); - if (lockResponse) return lockResponse; - if (error instanceof z.ZodError) - return c.json({ error: error.issues[0]?.message ?? '参数格式错误' }, 400); - if (error instanceof MarketRecycleError) - return jsonWithStatus(c, { error: error.message }, error.status); - throw error; - } -}); - -router.get('/:nodeId', requireActiveCultivatorRef(), async (c) => { - const cultivator = c.get('activeCultivatorRef'); - if (!cultivator) { - return c.json({ error: '当前没有活跃角色' }, 404); - } - - try { - const nodeId = resolveNodeId(c.req.param('nodeId')); - const layer = resolveLayer(c.req.query('layer')); - if (layer === 'black') { - throw new MarketServiceError(410, '黑市已经移入暗巷,请从坊市入口前往'); - } - const [{ realm }, fates] = await Promise.all([ - readCultivatorRealm(cultivator.cultivatorId), - loadMarketFates(cultivator), - ]); - const result = await getMarketListings({ - nodeId, - layer, - userId: cultivator.userId, - cultivatorRealm: realm, - fates, - }); - - return c.json(result); - } catch (error) { - if (error instanceof MarketServiceError) { - return jsonWithStatus(c, { error: error.message }, error.status); - } - - console.error('Market node API error:', error); - return c.json({ error: 'Failed to fetch market listings' }, 500); - } -}); - -router.post('/:nodeId/buy', requireActiveCultivatorRef(), async (c) => { - const cultivator = c.get('activeCultivatorRef'); - if (!cultivator) { - return c.json({ error: '当前没有活跃角色' }, 404); - } - - try { - const input = MarketBuySchema.parse(await c.req.json()); - const committed = await purchaseMarketItems({ - actor: { - userId: cultivator.userId, - cultivatorId: cultivator.cultivatorId, - }, - nodeId: resolveNodeId(c.req.param('nodeId')), - input, - }); - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - const lockErrorResponse = redisLockErrorResponse(error); - if (lockErrorResponse) return lockErrorResponse; - if ( - error instanceof MarketServiceError || - error instanceof PlayerCommandIdempotencyError - ) { - return jsonWithStatus(c, { error: error.message }, error.status); - } - if (error instanceof z.ZodError) { - return c.json({ error: error.issues[0]?.message || '参数错误' }, 400); - } - - console.error('Market buy API error:', error); - return c.json({ error: '购买失败' }, 500); - } -}); - -export default router; diff --git a/src/server/routes/api/player-journal.router.ts b/src/server/routes/api/player-journal.router.ts deleted file mode 100644 index 49c38d2d..00000000 --- a/src/server/routes/api/player-journal.router.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { - getValidatedQuery, - requireActiveCultivatorRef, - validateQuery, -} from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { listPlayerJournal } from '@server/lib/repositories/playerJournalRepository'; -import { PlayerJournalQuerySchema } from '@shared/contracts/playerJournal'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const router = new Hono(); -router.onError((error, c) => { - if (error instanceof z.ZodError) - return c.json({ error: '日志查询参数无效' }, 400); - console.error('修仙日志查询失败:', error); - return c.json({ error: '修仙日志暂时无法读取' }, 500); -}); -router.get( - '/', - requireActiveCultivatorRef(), - validateQuery(PlayerJournalQuerySchema), - async (c) => { - const { cultivatorId } = c.get('activeCultivatorRef')!; - const query = - getValidatedQuery>(c); - c.header('Cache-Control', 'no-store'); - return c.json({ - success: true, - data: await listPlayerJournal(cultivatorId, query), - }); - }, -); -export default router; diff --git a/src/server/routes/api/products.router.ts b/src/server/routes/api/products.router.ts deleted file mode 100644 index c1cda9db..00000000 --- a/src/server/routes/api/products.router.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { requireActiveCultivatorRef } from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import * as creationProductRepository from '@server/lib/repositories/creationProductRepository'; - -import type { LegacyProductType as CreationProductType } from '@shared/legacy/products'; - -import { Hono } from 'hono'; - -const VALID_TYPES = new Set(['skill', 'gongfa', 'artifact']); - -const router = new Hono(); - -router.get('/', requireActiveCultivatorRef(), async (c) => { - const ref = c.get('activeCultivatorRef'); - if (!ref) { - return c.json({ error: '当前没有活跃角色' }, 404); - } - - const type = c.req.query('type'); - if (!type || !VALID_TYPES.has(type)) { - return c.json( - { error: '请指定有效的产物类型 (skill|gongfa|artifact)' }, - 400, - ); - } - - const page = Math.max(1, parseInt(c.req.query('page') || '1', 10)); - const pageSize = Math.min( - 100, - Math.max(1, parseInt(c.req.query('pageSize') || '20', 10)), - ); - const [total, products] = await Promise.all([ - creationProductRepository.countByType( - ref.cultivatorId, - type as CreationProductType, - ), - creationProductRepository.findByTypeAndCultivatorPage( - ref.cultivatorId, - type as CreationProductType, - { page, pageSize }, - ), - ]); - const totalPages = Math.ceil(total / pageSize); - - return c.json({ - success: true, - data: { - items: products, - pagination: { - page, - pageSize, - total, - totalPages, - hasMore: page < totalPages, - }, - }, - }); -}); - -router.post('/equip', requireActiveCultivatorRef(), (c) => - c.json({ error: '旧产物装配已停用' }, 410), -); - -router.get('/:id', requireActiveCultivatorRef(), async (c) => { - const cultivator = c.get('activeCultivatorRef'); - if (!cultivator) { - return c.json({ error: '当前没有活跃角色' }, 404); - } - - const product = await creationProductRepository.findById(c.req.param('id')); - if (!product || product.cultivatorId !== cultivator.cultivatorId) { - return c.json({ error: '产物不存在' }, 404); - } - - return c.json({ success: true, data: product }); -}); - -router.delete('/:id', requireActiveCultivatorRef(), (c) => - c.json({ error: '历史产物直接删除已停用' }, 410), -); - -export default router; diff --git a/src/server/routes/api/rankings.router.ts b/src/server/routes/api/rankings.router.ts deleted file mode 100644 index c54f60bb..00000000 --- a/src/server/routes/api/rankings.router.ts +++ /dev/null @@ -1,295 +0,0 @@ -import { getExecutor } from '@server/lib/drizzle/db'; -import { cultivators, inventoryItems } from '@server/lib/drizzle/schema'; -import { - redisLockErrorResponse, - requireActiveCultivatorRef, -} from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { - getCultivatorRank, - getRankingList, - getRemainingChallenges, -} from '@server/lib/redis/rankings'; -import { InventoryError } from '@server/lib/services/InventoryService'; -import { CombatV6BuildError } from '@server/lib/services/combat-v6/CombatV6BuildService'; -import { - pendingRanking, - RankingV6Error, - runRankingChallenge, -} from '@server/lib/services/combat-v6/CombatV6RankingService'; -import { loadCultivatorInspectionData } from '@server/lib/services/cultivator/CultivatorCombatProjectionReader'; -import { readCultivatorRealm } from '@server/lib/services/cultivator/CultivatorFactsReader'; -import { RankingChallengeSchema } from '@shared/contracts/combatV6Ranking'; -import { ConsumableFactsSchema } from '@shared/items/definitions/consumables'; -import { - QUALITY_VALUES, - REALM_VALUES, - type RealmType, -} from '@shared/types/constants'; -import type { - ItemRankingEntry, - WealthRankingEntry, -} from '@shared/types/rankings'; -import { and, desc, eq, inArray, sql } from 'drizzle-orm'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const router = new Hono(); -const publicRouter = new Hono(); -const challengeRouter = new Hono(); - -function parseRealmQuery(raw: string | undefined | null): RealmType | null { - if (!raw) return null; - return REALM_VALUES.includes(raw as RealmType) ? (raw as RealmType) : null; -} - -publicRouter.get('/', async (c) => { - try { - const realm = parseRealmQuery(c.req.query('realm')) ?? '炼气'; - const rankings = await getRankingList(realm); - return c.json({ - success: true, - data: rankings, - realm, - }); - } catch (error) { - console.error('获取排行榜 API 错误:', error); - const errorMessage = - process.env.NODE_ENV === 'development' - ? error instanceof Error - ? error.message - : '获取排行榜失败,请稍后重试' - : '获取排行榜失败,请稍后重试'; - - return c.json({ error: errorMessage }, 500); - } -}); - -publicRouter.get('/items', async (c) => { - try { - const type = c.req.query('type'); - if (type && ['artifact', 'skill', 'technique'].includes(type)) - return c.json( - { success: false, error: '旧装备、功法、神通榜单已停用' }, - 410, - ); - if (type !== 'elixir') { - return c.json({ success: false, error: '无效的榜单类型' }, 400); - } - - const score = sql`(${inventoryItems.instanceData}->>'score')::double precision`; - const groupId = sql`concat('pill:', ${inventoryItems.cultivatorId}, ':', md5(${inventoryItems.instanceData}::text))`; - const rows = await getExecutor() - .select({ - id: groupId, - ownerName: cultivators.name, - facts: inventoryItems.instanceData, - quantity: sql`sum(${inventoryItems.quantity})::integer`, - }) - .from(inventoryItems) - .innerJoin(cultivators, eq(inventoryItems.cultivatorId, cultivators.id)) - .where( - and( - eq(inventoryItems.definitionId, 'consumable.v1'), - inArray(inventoryItems.location, ['bag', 'storage']), - sql`${inventoryItems.instanceData}->>'type' = '丹药'`, - sql`${inventoryItems.instanceData}->'spec'->>'kind' = 'pill'`, - inArray( - sql`${inventoryItems.instanceData}->>'quality'`, - QUALITY_VALUES.slice(2), - ), - ), - ) - .groupBy( - inventoryItems.cultivatorId, - cultivators.name, - inventoryItems.instanceData, - ) - .orderBy(desc(score), groupId) - .limit(100); - const items: ItemRankingEntry[] = rows.map((row, index) => { - const facts = ConsumableFactsSchema.parse(row.facts); - return { - id: row.id, - rank: index + 1, - name: facts.name, - itemType: 'elixir', - type: facts.type, - quality: facts.quality, - ownerName: row.ownerName, - score: facts.score, - description: facts.description, - quantity: row.quantity, - spec: facts.spec, - }; - }); - - return c.json({ - success: true, - data: items, - }); - } catch (error) { - console.error('获取排行榜失败:', error); - return c.json({ success: false, error: '获取排行榜失败' }, 500); - } -}); - -publicRouter.get('/wealth', async (c) => { - try { - const limit = 100; - const rows = await getExecutor() - .select({ - id: cultivators.id, - name: cultivators.name, - title: cultivators.title, - realm: cultivators.realm, - realmStage: cultivators.realm_stage, - age: cultivators.age, - origin: cultivators.origin, - spiritStones: cultivators.spirit_stones, - }) - .from(cultivators) - .where(eq(cultivators.status, 'active')) - .orderBy(desc(cultivators.spirit_stones)) - .limit(limit); - - const items: WealthRankingEntry[] = rows.map((row, index) => ({ - id: row.id, - rank: index + 1, - rankingType: 'wealth', - name: row.name, - title: row.title, - realm: row.realm, - realm_stage: row.realmStage, - age: row.age, - origin: row.origin, - spiritStones: row.spiritStones, - })); - - return c.json({ - success: true, - data: items, - }); - } catch (error) { - console.error('获取财富榜失败:', error); - return c.json({ success: false, error: '获取财富榜失败' }, 500); - } -}); - -challengeRouter.get('/my-rank', requireActiveCultivatorRef(), async (c) => { - const cultivator = c.get('activeCultivatorRef'); - if (!cultivator) { - return c.json({ error: '当前没有活跃角色' }, 404); - } - - const own = await readCultivatorRealm(cultivator.cultivatorId); - const realm = parseRealmQuery(c.req.query('realm')) ?? own.realm; - const rank = await getCultivatorRank(realm, cultivator.cultivatorId); - const remainingChallenges = await getRemainingChallenges( - cultivator.cultivatorId, - ); - - return c.json({ - success: true, - data: { - rank, - realm, - remainingChallenges, - }, - }); -}); - -challengeRouter.post('/probe', requireActiveCultivatorRef(), async (c) => { - try { - const { targetId } = (await c.req.json()) as { targetId?: string }; - if (!targetId || typeof targetId !== 'string') { - return c.json({ error: '请提供有效的目标角色ID' }, 400); - } - - const inspection = await loadCultivatorInspectionData(targetId); - if (!inspection) { - return c.json({ error: '目标角色不存在或不可查探' }, 404); - } - - return c.json({ - success: true, - data: { cultivator: inspection }, - }); - } catch (error) { - console.error('神识查探错误:', error); - const errorMessage = - process.env.NODE_ENV === 'development' - ? error instanceof Error - ? error.message - : '神识查探失败' - : '神识查探失败,请稍后重试'; - - return c.json({ error: errorMessage }, 500); - } -}); - -for (const path of [ - '/challenge', - '/challenge-battle', - '/challenge-battle/v5', -]) { - challengeRouter.post(path, requireActiveCultivatorRef(), (c) => - c.json( - { success: false, error: '旧天骄榜战斗已停用,请刷新使用新版挑战' }, - 410, - ), - ); -} -challengeRouter.get( - '/challenge/current', - requireActiveCultivatorRef(), - async (c) => { - c.header('Cache-Control', 'no-store'); - return c.json({ - success: true, - data: await pendingRanking(c.get('activeCultivatorRef')!.cultivatorId), - }); - }, -); -challengeRouter.post( - '/challenge-battle/v6', - requireActiveCultivatorRef(), - async (c) => { - c.header('Cache-Control', 'no-store'); - try { - const data = await runRankingChallenge( - { - userId: c.get('user')!.id, - cultivatorId: c.get('activeCultivatorRef')!.cultivatorId, - }, - RankingChallengeSchema.parse(await c.req.json()), - ); - return c.json({ success: true, data }); - } catch (error) { - const lock = redisLockErrorResponse(error); - if (lock) return lock; - if (error instanceof z.ZodError) - return c.json({ success: false, error: '挑战参数无效' }, 400); - if (error instanceof InventoryError) - return c.json( - { success: false, error: '请先结束当前战斗与结算,再发起天骄榜挑战' }, - 409, - ); - if ( - error instanceof RankingV6Error || - error instanceof CombatV6BuildError - ) - return c.json({ success: false, error: error.message }, 409); - console.error('[ranking-v6] challenge failed', error); - return c.json( - { success: false, error: '挑战尚未完成,请使用原请求重试恢复' }, - 500, - ); - } - }, -); - -router.route('/', publicRouter); -router.route('/', challengeRouter); - -export default router; diff --git a/src/server/routes/api/realtime.router.ts b/src/server/routes/api/realtime.router.ts deleted file mode 100644 index 456d5c22..00000000 --- a/src/server/routes/api/realtime.router.ts +++ /dev/null @@ -1,358 +0,0 @@ -import { requireUser } from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { db } from '@server/lib/drizzle/db'; -import { cultivators, sectMemberships } from '@server/lib/drizzle/schema'; -import { isAllowedRealtimeOrigin } from '@server/lib/http/realtimeOrigin'; -import { - recordRealtimeConnectionClose, - recordRealtimeConnectionHeartbeat, - recordRealtimeConnectionOpen, -} from '@server/lib/services/onlinePresenceService'; -import { subscribeArenaRoomChanges } from '@server/lib/services/arenaRoomBroadcaster'; -import { subscribeResourceEvents } from '@server/lib/services/playerStateBroadcaster'; -import { subscribeSectChatMessages } from '@server/lib/services/sectChatBroadcaster'; -import { subscribeWorldChatMessages } from '@server/lib/services/worldChatBroadcaster'; -import { - REALTIME_CHANNELS, - type RealtimeChannel, - type RealtimeServerEvent, -} from '@shared/contracts/realtime'; -import type { ResourceScope } from '@shared/contracts/resources'; -import { and, eq } from 'drizzle-orm'; -import { Hono, type Context, type MiddlewareHandler } from 'hono'; -import { upgradeWebSocket } from 'hono/bun'; -import type { WSContext } from 'hono/ws'; - -const router = new Hono(); -const HEARTBEAT_INTERVAL_MS = 25_000; -const MAX_MISSED_HEARTBEATS = 2; -const HEARTBEAT_TIMEOUT_MS = HEARTBEAT_INTERVAL_MS * MAX_MISSED_HEARTBEATS; -const HEARTBEAT_TIMEOUT_CLOSE_CODE = 4_000; -const HEARTBEAT_TIMEOUT_CLOSE_REASON = 'heartbeat timeout'; -const MAX_MESSAGE_BYTES = 512; -const MAX_CONNECTIONS_PER_USER = 3; -const MAX_CONNECTIONS_PER_CULTIVATOR = 3; -const MAX_CONNECTIONS_PER_IP = 40; - -const userConnectionCounts = new Map(); -const cultivatorConnectionCounts = new Map(); -const ipConnectionCounts = new Map(); - -type ConnectionIdentity = { - userId: string; - cultivatorId: string | null; - sectId: string | null; - ip: string; -}; - -type ConnectionReservation = ConnectionIdentity & { - release: () => void; -}; - -const pendingConnectionReservations = new WeakMap< - Context, - ConnectionReservation ->(); - -function incrementCount(map: Map, key: string) { - map.set(key, (map.get(key) ?? 0) + 1); -} - -function decrementCount(map: Map, key: string) { - const next = (map.get(key) ?? 0) - 1; - if (next <= 0) { - map.delete(key); - return; - } - map.set(key, next); -} - -function getClientIp(c: Context) { - return ( - c.req.header('cf-connecting-ip') || - c.req.header('x-forwarded-for')?.split(',')[0]?.trim() || - c.req.header('x-real-ip') || - 'unknown' - ); -} - -function parseChannels(raw: string | undefined): RealtimeChannel[] { - if (!raw) { - return [...REALTIME_CHANNELS]; - } - - const allowed = new Set(REALTIME_CHANNELS); - const channels = raw - .split(',') - .map((item) => item.trim()) - .filter((item): item is RealtimeChannel => - allowed.has(item as RealtimeChannel), - ); - - return channels.length > 0 - ? Array.from(new Set(channels)) - : [...REALTIME_CHANNELS]; -} - -function reserveConnection( - identity: ConnectionIdentity, -): ConnectionReservation | null { - if ( - (userConnectionCounts.get(identity.userId) ?? 0) >= - MAX_CONNECTIONS_PER_USER || - (identity.cultivatorId !== null && - (cultivatorConnectionCounts.get(identity.cultivatorId) ?? 0) >= - MAX_CONNECTIONS_PER_CULTIVATOR) || - (ipConnectionCounts.get(identity.ip) ?? 0) >= MAX_CONNECTIONS_PER_IP - ) { - return null; - } - - incrementCount(userConnectionCounts, identity.userId); - if (identity.cultivatorId) { - incrementCount(cultivatorConnectionCounts, identity.cultivatorId); - } - incrementCount(ipConnectionCounts, identity.ip); - let released = false; - - return { - ...identity, - release() { - if (released) { - return; - } - released = true; - decrementCount(userConnectionCounts, identity.userId); - if (identity.cultivatorId) { - decrementCount(cultivatorConnectionCounts, identity.cultivatorId); - } - decrementCount(ipConnectionCounts, identity.ip); - }, - }; -} - -const requireRealtimeOrigin = (async (c, next) => { - if (!isAllowedRealtimeOrigin(c.req.header('origin'))) { - return c.json({ success: false, error: 'Forbidden origin' }, 403); - } - await next(); -}) satisfies MiddlewareHandler; - -const reserveRealtimeConnection = (async (c, next) => { - const user = c.get('user'); - if (!user) { - return c.json({ success: false, error: '未授权访问' }, 401); - } - - const active = await db.query.cultivators.findFirst({ - columns: { id: true }, - where: and( - eq(cultivators.userId, user.id), - eq(cultivators.status, 'active'), - ), - }); - - const reservation = reserveConnection({ - userId: user.id, - cultivatorId: active?.id ?? null, - sectId: - active - ? ( - await db.query.sectMemberships.findFirst({ - columns: { sectId: true }, - where: and( - eq(sectMemberships.cultivatorId, active.id), - eq(sectMemberships.status, 'active'), - ), - }) - )?.sectId ?? null - : null, - ip: getClientIp(c), - }); - if (!reservation) { - return c.json({ success: false, error: '实时连接过多' }, 429); - } - - pendingConnectionReservations.set(c, reservation); - try { - await next(); - } catch (error) { - pendingConnectionReservations.delete(c); - reservation.release(); - throw error; - } - - if (pendingConnectionReservations.get(c) === reservation) { - pendingConnectionReservations.delete(c); - reservation.release(); - } -}) satisfies MiddlewareHandler; - -router.get( - '/', - requireRealtimeOrigin, - requireUser(), - reserveRealtimeConnection, - upgradeWebSocket((rawContext) => { - const c = rawContext as Context; - const reservation = pendingConnectionReservations.get(c); - if (!reservation) { - throw new Error('Realtime connection reservation is missing'); - } - pendingConnectionReservations.delete(c); - - const channels = parseChannels(c.req.query('channels')); - const { cultivatorId } = reservation; - const resourceScopes: ResourceScope[] = [ - { kind: 'account', id: reservation.userId }, - ...(cultivatorId - ? ([{ kind: 'cultivator', id: cultivatorId }] satisfies ResourceScope[]) - : []), - ...(reservation.sectId - ? ([{ kind: 'sect', id: reservation.sectId }] satisfies ResourceScope[]) - : []), - ]; - let lastClientActivityAt = Date.now(); - let heartbeat: ReturnType | undefined; - let closed = false; - const unsubscribers: Array<() => void> = []; - const cleanup = () => { - if (closed) { - return; - } - closed = true; - if (heartbeat) { - clearInterval(heartbeat); - } - for (const unsubscribe of unsubscribers) { - try { - unsubscribe(); - } catch (error) { - console.warn('[realtime] unsubscribe failed', { - cultivatorId, - error, - }); - } - } - reservation.release(); - if (cultivatorId) recordRealtimeConnectionClose(cultivatorId); - }; - const closeConnection = (ws: WSContext, code: number, reason: string) => { - if (closed) { - return; - } - try { - ws.close(code, reason); - } catch (error) { - console.warn('[realtime] websocket close failed', { - cultivatorId, - error, - }); - } - cleanup(); - }; - const sendEnvelope = (ws: WSContext, event: RealtimeServerEvent) => { - if (closed) { - return false; - } - try { - ws.send(JSON.stringify(event)); - return true; - } catch (error) { - console.warn('[realtime] websocket send failed', { - cultivatorId, - error, - }); - closeConnection(ws, 1_011, 'websocket send failed'); - return false; - } - }; - - return { - onOpen(_event, ws) { - lastClientActivityAt = Date.now(); - if (cultivatorId) recordRealtimeConnectionOpen(cultivatorId); - - if (channels.includes('player-state')) { - unsubscribers.push( - subscribeResourceEvents(resourceScopes, (changes) => { - sendEnvelope(ws, { - type: 'player-state.events', - payload: { changes }, - }); - }), - ); - } - if (channels.includes('world-chat')) { - unsubscribers.push( - subscribeWorldChatMessages((message) => { - sendEnvelope(ws, { - type: 'world-chat.message', - payload: message, - }); - }), - ); - if (reservation.sectId) { - unsubscribers.push( - subscribeSectChatMessages(reservation.sectId, (message) => { - sendEnvelope(ws, { - type: 'world-chat.message', - payload: message, - }); - }), - ); - } - } - if (channels.includes('arena-room')) { - unsubscribers.push( - subscribeArenaRoomChanges(reservation.userId, (payload) => { - sendEnvelope(ws, { - type: 'arena-room.changed', - payload, - }); - }), - ); - } - - heartbeat = setInterval(() => { - const timeSinceLastClientActivity = Date.now() - lastClientActivityAt; - if (timeSinceLastClientActivity > HEARTBEAT_TIMEOUT_MS) { - closeConnection( - ws, - HEARTBEAT_TIMEOUT_CLOSE_CODE, - HEARTBEAT_TIMEOUT_CLOSE_REASON, - ); - return; - } - if (cultivatorId) recordRealtimeConnectionHeartbeat(cultivatorId); - sendEnvelope(ws, { - type: 'ping', - payload: { serverTime: new Date().toISOString() }, - }); - }, HEARTBEAT_INTERVAL_MS); - sendEnvelope(ws, { - type: 'ready', - payload: { cultivatorId, channels, resourceScopes }, - }); - }, - onMessage(event, ws) { - const raw = event.data; - if (typeof raw !== 'string' || raw.length > MAX_MESSAGE_BYTES) { - closeConnection(ws, 1_003, 'invalid client message'); - return; - } - - // Any bounded client frame proves that this connection is still alive. - lastClientActivityAt = Date.now(); - }, - onClose() { - cleanup(); - }, - onError() { - cleanup(); - }, - }; - }), -); - -export default router; diff --git a/src/server/routes/api/reputation-shop.router.ts b/src/server/routes/api/reputation-shop.router.ts deleted file mode 100644 index 2af68394..00000000 --- a/src/server/routes/api/reputation-shop.router.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { - redisLockErrorResponse, - requireActiveCultivatorRef, -} from '@server/lib/hono/middleware'; -import { jsonWithStatus } from '@server/lib/hono/response'; -import type { AppEnv } from '@server/lib/hono/types'; -import { PlayerCommandIdempotencyError } from '@server/lib/services/CommandExecutors'; -import { readCultivatorReputation } from '@server/lib/services/cultivator/CultivatorFactsReader'; -import { purchaseReputationShopItemCommand } from '@server/lib/services/ReputationShopApplicationService'; -import { - listReputationShopItems, - ReputationShopError, -} from '@server/lib/services/ReputationShopService'; -import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; -import { - ReputationShopBuyBodySchema, - ReputationShopBuyParamsSchema, -} from '@shared/contracts/reputationShop'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const router = new Hono(); - -router.get('/', requireActiveCultivatorRef(), async (c) => { - const cultivator = c.get('activeCultivatorRef'); - if (!cultivator) { - return c.json({ error: '当前没有活跃角色' }, 404); - } - - const items = await listReputationShopItems({ - cultivatorId: cultivator.cultivatorId, - userVisibleOnly: true, - }); - const { reputation } = await readCultivatorReputation( - cultivator.cultivatorId, - ); - - return c.json({ - items, - reputation, - }); -}); - -router.post('/:id/buy', requireActiveCultivatorRef(), async (c) => { - const user = c.get('user'); - const cultivator = c.get('activeCultivatorRef'); - if (!user || !cultivator) { - return c.json({ error: '未授权访问' }, 401); - } - - try { - const params = ReputationShopBuyParamsSchema.parse({ - id: c.req.param('id'), - }); - const body = ReputationShopBuyBodySchema.parse(await c.req.json()); - const committed = await purchaseReputationShopItemCommand({ - id: params.id, - requestId: body.requestId, - userId: user.id, - cultivatorId: cultivator.cultivatorId, - }); - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - const lockErrorResponse = redisLockErrorResponse(error); - if (lockErrorResponse) return lockErrorResponse; - if ( - error instanceof ReputationShopError || - error instanceof PlayerCommandIdempotencyError - ) { - return jsonWithStatus(c, { error: error.message }, error.status); - } - if (error instanceof z.ZodError) { - return c.json({ error: '参数错误', details: error.flatten() }, 400); - } - - console.error('reputation shop buy error:', error); - return c.json({ error: '兑换失败,请稍后再试' }, 500); - } -}); - -export default router; diff --git a/src/server/routes/api/save-character.router.ts b/src/server/routes/api/save-character.router.ts deleted file mode 100644 index 7f16d1f3..00000000 --- a/src/server/routes/api/save-character.router.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { requireUser } from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { - createCultivatorFromTemp, - CultivatorCreationCommandError, -} from '@server/lib/services/CultivatorCreationApplicationService'; -import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const SaveCharacterSchema = z.object({ - tempCultivatorId: z.string(), - selectedFateIndices: z.array(z.number()).length(3), -}); - -const router = new Hono(); - -router.post('/', requireUser(), async (c) => { - const user = c.get('user'); - if (!user) { - return c.json({ error: '未授权访问' }, 401); - } - - const { tempCultivatorId, selectedFateIndices } = SaveCharacterSchema.parse( - await c.req.json(), - ); - - try { - const committed = await createCultivatorFromTemp({ - userId: user.id, - tempCultivatorId, - selectedFateIndices, - }); - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - if (error instanceof CultivatorCreationCommandError) { - return c.json({ error: error.message }, 400); - } - throw error; - } -}); - -export default router; diff --git a/src/server/routes/api/sect-social.router.ts b/src/server/routes/api/sect-social.router.ts deleted file mode 100644 index d90f6c14..00000000 --- a/src/server/routes/api/sect-social.router.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { getExecutor } from '@server/lib/drizzle/db'; -import { - getValidatedJson, - getValidatedQuery, - requireActiveCultivatorRef, - validateJson, - validateQuery, -} from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { rejectRetiredBattleShare } from '@server/lib/hono/chat'; -import { checkAndAcquireSectChatCooldown } from '@server/lib/redis/worldChatLimiter'; -import { - createSectChatMessage, - listSectChatMessages, -} from '@server/lib/repositories/sectChatRepository'; -import { - countSectMembersAboveLifetimeContribution, - findSectContributionRankingMember, - listTopSectContributionRanking, -} from '@server/lib/repositories/sectOrganizationRepository'; -import { findMembership } from '@server/lib/repositories/sectRepository'; -import { - ChatMessageApplicationError, - createCultivatorChatMessage, -} from '@server/lib/services/chatMessageApplication'; -import { readResourceWithResolvedScope } from '@server/lib/services/ResourceReadService'; -import { - SectChatListQuerySchema, - WorldChatCreateMessageSchema, - type SectChatListQuery, - type WorldChatCreateMessageRequest, -} from '@shared/contracts/world-chat'; -import type { - SectContributionRankingData, - SectContributionRankingEntry, -} from '@shared/contracts/sect'; -import type { SectDiscipleRank, SectOffice } from '@shared/engine/sect'; -import { Hono } from 'hono'; - -const router = new Hono(); - -router.get( - '/chat/messages', - requireActiveCultivatorRef(), - validateQuery(SectChatListQuerySchema), - async (c) => { - const ref = c.get('activeCultivatorRef'); - if (!ref) return c.json({ success: false, error: '当前没有活跃角色' }, 404); - const membership = await findMembership(ref.cultivatorId, getExecutor()); - if (!membership) { - return c.json({ success: false, error: '尚未拜入宗门' }, 404); - } - const query = getValidatedQuery(c); - const result = await listSectChatMessages({ - sectId: membership.sectId, - page: query.page, - pageSize: query.pageSize, - }); - return c.json({ - success: true, - data: result.messages, - pagination: { - page: query.page, - pageSize: query.pageSize, - hasMore: result.hasMore, - }, - }); - }, -); - -router.post( - '/chat/messages', - requireActiveCultivatorRef(), - rejectRetiredBattleShare, - validateJson(WorldChatCreateMessageSchema), - async (c) => { - const user = c.get('user'); - const ref = c.get('activeCultivatorRef'); - if (!user || !ref) { - return c.json({ success: false, error: '当前没有活跃角色' }, 404); - } - const membership = await findMembership(ref.cultivatorId, getExecutor()); - if (!membership) { - return c.json({ success: false, error: '尚未拜入宗门' }, 404); - } - try { - const message = await createCultivatorChatMessage({ - request: getValidatedJson(c), - userId: user.id, - cultivatorId: ref.cultivatorId, - channel: 'sect', - sectId: membership.sectId, - acquireCooldown: checkAndAcquireSectChatCooldown, - persist: createSectChatMessage, - }); - return c.json({ success: true, data: message }); - } catch (error) { - if (error instanceof ChatMessageApplicationError) { - return c.json( - { - success: false, - error: error.message, - ...(error.remainingSeconds - ? { remainingSeconds: error.remainingSeconds } - : {}), - }, - error.status, - ); - } - console.error('[sect-chat] create message failed', error); - return c.json({ success: false, error: '发送失败,请稍后重试' }, 500); - } - }, -); - -router.get( - '/contribution-ranking', - requireActiveCultivatorRef(), - async (c) => { - const ref = c.get('activeCultivatorRef'); - if (!ref) return c.json({ success: false, error: '当前没有活跃角色' }, 404); - try { - return c.json( - await readResourceWithResolvedScope( - 'sect.contribution-ranking', - async (q) => { - const membership = await findMembership(ref.cultivatorId, q); - if (!membership) { - throw new Error('SECT_MEMBERSHIP_REQUIRED'); - } - const rows = await listTopSectContributionRanking( - membership.sectId, - q, - ); - let previousContribution: number | undefined; - let rank = 0; - const entries = rows.map((row, index): SectContributionRankingEntry => { - if (row.lifetimeContribution !== previousContribution) { - rank = index + 1; - previousContribution = row.lifetimeContribution; - } - return { - rank, - cultivatorId: row.cultivatorId, - name: row.name, - discipleRank: row.discipleRank as SectDiscipleRank, - office: row.office as SectOffice, - contribution: row.lifetimeContribution, - }; - }); - let currentMember = entries.find( - (entry) => entry.cultivatorId === ref.cultivatorId, - ); - if (!currentMember) { - const currentRow = await findSectContributionRankingMember( - membership.sectId, - ref.cultivatorId, - q, - ); - if (!currentRow) { - throw new Error('SECT_MEMBERSHIP_REQUIRED'); - } - currentMember = { - rank: - (await countSectMembersAboveLifetimeContribution( - membership.sectId, - currentRow.lifetimeContribution, - q, - )) + 1, - cultivatorId: currentRow.cultivatorId, - name: currentRow.name, - discipleRank: - currentRow.discipleRank as SectDiscipleRank, - office: currentRow.office as SectOffice, - contribution: currentRow.lifetimeContribution, - }; - } - return { - scope: { kind: 'sect', id: membership.sectId }, - data: { - metric: 'lifetime_contribution', - generatedAt: new Date().toISOString(), - entries: entries.slice(0, 20), - currentMember, - } satisfies SectContributionRankingData, - }; - }, - ), - ); - } catch (error) { - if ( - error instanceof Error && - error.message === 'SECT_MEMBERSHIP_REQUIRED' - ) { - return c.json({ success: false, error: '尚未拜入宗门' }, 404); - } - console.error('[sect-ranking] read failed', error); - return c.json({ success: false, error: '贡献榜读取失败' }, 500); - } - }, -); - -export default router; diff --git a/src/server/routes/api/sects.router.ts b/src/server/routes/api/sects.router.ts deleted file mode 100644 index 393f0329..00000000 --- a/src/server/routes/api/sects.router.ts +++ /dev/null @@ -1,613 +0,0 @@ -import { getExecutor } from '@server/lib/drizzle/db'; -import { cultivators } from '@server/lib/drizzle/schema'; -import { - getValidatedJson, - getValidatedQuery, - redisLockErrorResponse, - requireActiveCultivatorRef, - validateJson, - validateQuery, -} from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { - findMembership, -} from '@server/lib/repositories/sectRepository'; -import { - PlayerCommandIdempotencyError, - type CommittedCommand, -} from '@server/lib/services/CommandExecutors'; -import { InventoryError } from '@server/lib/services/InventoryService'; -import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; -import { - readResourceWithMeta, - readResourceWithResolvedScope, -} from '@server/lib/services/ResourceReadService'; -import { SectError } from '@server/lib/services/SectError'; -import { SectShopError } from '@server/lib/services/SectShopService'; -import { sectOrganizationFacade } from '@server/lib/services/sect-organization'; -import { - createPostgresSectConstructionQueryContext, - createPostgresSectEconomyContext, - createPostgresSectMembershipQueryContext, - createPostgresSectQueryContext, -} from '@server/lib/services/sect-organization/PostgresSectOrganizationAdapters'; -import { executeSectConstructionDonationCommand } from '@server/lib/services/sect-organization/SectConstructionCommand'; -import { - executeSectShopPurchaseCommand, - executeSectStipendClaimCommand, -} from '@server/lib/services/sect-organization/SectEconomyCommand'; -import { - executeSectJoinCommand, - executeSectPromotionCommand, -} from '@server/lib/services/sect-organization/SectMembershipCommand'; -import { executeSectTaskActionCommand } from '@server/lib/services/sect-organization/SectTaskCommand'; -import { previewSectTransfer } from '@server/lib/services/sect-organization/SectTransferApplicationService'; -import { executeSectTransferCommand } from '@server/lib/services/sect-organization/SectTransferCommand'; -import { - SectDonationRequestSchema, - SectIdempotencyKeySchema, - SectMembersQuerySchema, - SectTaskActionRequestSchema, - SectTransferPreviewQuerySchema, - SectTransferRequestSchema, - type SectContextData, -} from '@shared/contracts/sect'; -import { SectShopBuyParamsSchema } from '@shared/contracts/sectShop'; -import type { SectDiscipleRank, SectRuntime } from '@shared/engine/sect'; -import { productionSectRuntime } from '@shared/engine/sect/content'; -import type { RealmStage, RealmType } from '@shared/types/constants'; -import { eq } from 'drizzle-orm'; -import { Hono, type Context } from 'hono'; -import { createHash } from 'node:crypto'; -import sectSocialRouter from './sect-social.router'; - -function canonicalize(value: unknown): string { - if (Array.isArray(value)) return `[${value.map(canonicalize).join(',')}]`; - if (value && typeof value === 'object') - return `{${Object.entries(value as Record) - .sort(([left], [right]) => left.localeCompare(right)) - .map(([key, item]) => `${JSON.stringify(key)}:${canonicalize(item)}`) - .join(',')}}`; - return JSON.stringify(value); -} - -function requireIdempotency( - c: Context, - source: string, - payload: unknown, -) { - const parsed = SectIdempotencyKeySchema.safeParse( - c.req.header('Idempotency-Key'), - ); - if (!parsed.success) - throw new SectError( - 'SECT_ORGANIZATION_INVALID', - '缺少有效的 Idempotency-Key 请求头', - 400, - ); - return { - key: parsed.data, - fingerprint: createHash('sha256') - .update( - `${source}:${c.req.method}:${c.req.path}:${canonicalize(payload)}`, - ) - .digest('hex'), - }; -} - -function failure(c: Context, error: unknown) { - const lockErrorResponse = redisLockErrorResponse(error); - if (lockErrorResponse) return lockErrorResponse; - if (error instanceof PlayerCommandIdempotencyError) - return c.json( - { success: false as const, error: error.message, code: error.code }, - 409, - ); - if (error instanceof SectError) - return c.json( - { success: false as const, error: error.message, code: error.code }, - error.status as 400 | 403 | 409, - ); - if (error instanceof SectShopError) - return c.json( - { success: false as const, error: error.message }, - error.status as 400 | 404 | 500, - ); - if (error instanceof InventoryError) - return c.json({ success: false as const, error: error.message }, 409); - console.error('[sects]', error); - return c.json({ success: false as const, error: '宗门事务处理失败' }, 500); -} - -export function createSectsRouter( - dependencies: { - organizationFacade?: Pick; - runtime?: SectRuntime; - } = {}, -) { - const router = new Hono(); - const organizationFacade = - dependencies.organizationFacade ?? sectOrganizationFacade; - const runtime = dependencies.runtime ?? productionSectRuntime; - const admission = (q: Parameters[0]) => - organizationFacade.admission(q, runtime); - - router.get('/current/context', requireActiveCultivatorRef(), async (c) => { - const ref = c.get('activeCultivatorRef'); - if (!ref) return c.json({ success: false, error: '当前没有活跃角色' }, 404); - try { - return c.json( - await readResourceWithMeta( - { kind: 'cultivator', id: ref.cultivatorId }, - 'sect.membership', - async (q) => { - const membership = await findMembership(ref.cultivatorId, q); - if (!membership) { - throw new SectError( - 'SECT_MEMBERSHIP_REQUIRED', - '尚未拜入宗门', - 404, - ); - } - const organization = runtime.registry.require( - membership.sectId, - ).organization; - return { - sectId: membership.sectId, - membershipId: membership.id, - status: membership.status as SectContextData['status'], - joinedAt: membership.joinedAt?.toISOString(), - discipleRank: - membership.discipleRank as SectContextData['discipleRank'], - contribution: membership.contribution, - lifetimeContribution: membership.lifetimeContribution, - office: membership.office as SectContextData['office'], - promotedAt: membership.promotedAt?.toISOString(), - permissions: organization.capabilities.snapshot( - membership.discipleRank as SectDiscipleRank, - ), - configVersion: membership.configVersion, - } satisfies SectContextData; - }, - ), - ); - } catch (error) { - return failure(c, error); - } - }); - - router.get( - '/current/infrastructure', - requireActiveCultivatorRef(), - async (c) => { - const ref = c.get('activeCultivatorRef'); - if (!ref) - return c.json({ success: false, error: '当前没有活跃角色' }, 404); - try { - return c.json( - await readResourceWithResolvedScope( - 'sect.infrastructure', - async (q) => { - const membership = await findMembership(ref.cultivatorId, q); - if (!membership) - throw new SectError( - 'SECT_MEMBERSHIP_REQUIRED', - '尚未拜入宗门', - 404, - ); - return { - scope: { kind: 'sect', id: membership.sectId }, - data: await sectOrganizationFacade.membership.getInfrastructureResource( - ref.cultivatorId, - createPostgresSectMembershipQueryContext({ q, runtime }), - ), - }; - }, - ), - ); - } catch (error) { - return failure(c, error); - } - }, - ); - - router.get('/current/stipend', requireActiveCultivatorRef(), async (c) => { - const ref = c.get('activeCultivatorRef'); - if (!ref) return c.json({ success: false, error: '当前没有活跃角色' }, 404); - try { - const q = getExecutor(); - const cultivator = await q.query.cultivators.findFirst({ - columns: { id: true, realm: true }, - where: eq(cultivators.id, ref.cultivatorId), - }); - if (!cultivator) - throw new SectError('SECT_MEMBERSHIP_REQUIRED', '角色不存在', 404); - const data = await sectOrganizationFacade.membership.getStipendResource( - { - id: cultivator.id, - realm: cultivator.realm as RealmType, - }, - createPostgresSectMembershipQueryContext({ q, runtime }), - ); - return c.json({ success: true as const, data }); - } catch (error) { - return failure(c, error); - } - }); - - router.get( - '/current/promotion-evaluation', - requireActiveCultivatorRef(), - async (c) => { - const ref = c.get('activeCultivatorRef'); - if (!ref) - return c.json({ success: false, error: '当前没有活跃角色' }, 404); - try { - const q = getExecutor(); - const cultivator = await q.query.cultivators.findFirst({ - columns: { id: true, realm: true, realm_stage: true }, - where: eq(cultivators.id, ref.cultivatorId), - }); - if (!cultivator) - throw new SectError('SECT_MEMBERSHIP_REQUIRED', '角色不存在', 404); - const data = - await sectOrganizationFacade.membership.getPromotionEvaluationResource( - { - id: cultivator.id, - realm: cultivator.realm as RealmType, - realm_stage: cultivator.realm_stage as RealmStage, - }, - createPostgresSectMembershipQueryContext({ q, runtime }), - ); - return c.json({ success: true as const, data }); - } catch (error) { - return failure(c, error); - } - }, - ); - - router.get('/current/tasks', requireActiveCultivatorRef(), async (c) => { - const ref = c.get('activeCultivatorRef'); - if (!ref) return c.json({ success: false, error: '当前没有活跃角色' }, 404); - try { - return c.json( - await readResourceWithMeta( - { kind: 'cultivator', id: ref.cultivatorId }, - 'sect.tasks', - (q) => - sectOrganizationFacade.tasks.queries.execute( - { cultivatorId: ref.cultivatorId }, - createPostgresSectQueryContext({ q, runtime }), - ), - ), - ); - } catch (error) { - return failure(c, error); - } - }); - - router.get( - '/current/tasks/:taskId/submission-candidates', - requireActiveCultivatorRef(), - async (c) => { - const ref = c.get('activeCultivatorRef'); - if (!ref) - return c.json({ success: false, error: '当前没有活跃角色' }, 404); - const taskId = c.req.param('taskId'); - if (!taskId || taskId.length > 64) - return c.json({ success: false, error: '任务编号无效' }, 400); - try { - return c.json({ - success: true, - data: await sectOrganizationFacade.tasks.submissions.execute( - { - cultivatorId: ref.cultivatorId, - taskId, - }, - createPostgresSectQueryContext({ - q: getExecutor(), - runtime, - }), - ), - }); - } catch (error) { - return failure(c, error); - } - }, - ); - - router.get('/current/shop', requireActiveCultivatorRef(), async (c) => { - const ref = c.get('activeCultivatorRef'); - if (!ref) return c.json({ success: false, error: '当前没有活跃角色' }, 404); - try { - return c.json( - await readResourceWithMeta( - { kind: 'cultivator', id: ref.cultivatorId }, - 'sect.shop', - (q) => - sectOrganizationFacade.economy.getShop( - ref.cultivatorId, - createPostgresSectEconomyContext({ q, runtime }), - ), - ), - ); - } catch (error) { - return failure(c, error); - } - }); - - router.get( - '/current/construction-member', - requireActiveCultivatorRef(), - async (c) => { - const ref = c.get('activeCultivatorRef'); - const user = c.get('user'); - if (!user || !ref) - return c.json({ success: false, error: '当前没有活跃角色' }, 404); - try { - return c.json( - await readResourceWithMeta( - { kind: 'cultivator', id: ref.cultivatorId }, - 'sect.construction-member', - (q) => - sectOrganizationFacade.construction.getConstructionMember( - user.id, - ref.cultivatorId, - createPostgresSectConstructionQueryContext({ q, runtime }), - ), - ), - ); - } catch (error) { - return failure(c, error); - } - }, - ); - - router.get( - '/current/members', - requireActiveCultivatorRef(), - validateQuery(SectMembersQuerySchema), - async (c) => { - const ref = c.get('activeCultivatorRef'); - if (!ref) - return c.json({ success: false, error: '当前没有活跃角色' }, 404); - try { - const query = getValidatedQuery<{ page: number; pageSize: number }>(c); - return c.json( - await readResourceWithResolvedScope('sect.members', async (q) => { - const membership = await findMembership(ref.cultivatorId, q); - if (!membership) - throw new SectError( - 'SECT_MEMBERSHIP_REQUIRED', - '尚未拜入宗门', - 404, - ); - return { - scope: { kind: 'sect', id: membership.sectId }, - data: await sectOrganizationFacade.membership.listMembers( - ref.cultivatorId, - query.page, - query.pageSize, - createPostgresSectMembershipQueryContext({ q, runtime }), - ), - }; - }), - ); - } catch (error) { - return failure(c, error); - } - }, - ); - - const organizationCommandMutation = async ( - c: Context, - source: string, - fingerprintPayload: unknown = null, - run: (args: { - userId: string; - cultivatorId: string; - source: string; - idempotency: { key: string; fingerprint: string }; - runtime: SectRuntime; - }) => Promise>, - ) => { - const user = c.get('user'); - const ref = c.get('activeCultivatorRef'); - if (!user || !ref) - return c.json({ success: false, error: '当前没有活跃角色' }, 404); - try { - const committed = await run({ - userId: user.id, - cultivatorId: ref.cultivatorId, - source, - idempotency: requireIdempotency(c, source, fingerprintPayload), - runtime, - }); - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - return failure(c, error); - } - }; - - router.post( - '/current/tasks/:taskId/actions/:actionKey', - requireActiveCultivatorRef(), - validateJson(SectTaskActionRequestSchema), - async (c) => { - const body = getValidatedJson<{ input: Record }>(c); - if ( - c.req.param('taskId').length > 64 || - c.req.param('actionKey').length > 64 - ) - return c.json({ success: false, error: '任务操作编号无效' }, 400); - const requestId = c.req.header('Idempotency-Key') ?? ''; - const user = c.get('user'); - const ref = c.get('activeCultivatorRef'); - if (!user || !ref) - return c.json({ success: false, error: '当前没有活跃角色' }, 404); - return organizationCommandMutation( - c, - 'sect_task_action', - { - taskId: c.req.param('taskId'), - actionKey: c.req.param('actionKey'), - input: body.input, - }, - (args) => - executeSectTaskActionCommand({ - ...args, - taskId: c.req.param('taskId'), - actionKey: c.req.param('actionKey'), - requestId, - input: body.input, - }), - ); - }, - ); - - router.post('/current/promotion', requireActiveCultivatorRef(), async (c) => - organizationCommandMutation( - c, - 'sect_promotion', - null, - executeSectPromotionCommand, - ), - ); - - router.post( - '/current/shop/:id/buy', - requireActiveCultivatorRef(), - async (c) => { - const parsed = SectShopBuyParamsSchema.safeParse({ - id: c.req.param('id'), - }); - if (!parsed.success) - return c.json({ success: false, error: '商品编号无效' }, 400); - return organizationCommandMutation( - c, - 'sect_shop_purchase', - parsed.data, - (args) => - executeSectShopPurchaseCommand({ - ...args, - itemId: parsed.data.id, - }), - ); - }, - ); - - router.post( - '/current/construction/donate', - requireActiveCultivatorRef(), - validateJson(SectDonationRequestSchema), - async (c) => { - const body = getValidatedJson<{ - facilityKey: string; - spiritStones: number; - }>(c); - return organizationCommandMutation( - c, - 'sect_construction_donate', - body, - (args) => executeSectConstructionDonationCommand({ ...args, ...body }), - ); - }, - ); - - router.post( - '/current/stipend/claim', - requireActiveCultivatorRef(), - async (c) => { - const user = c.get('user'); - const ref = c.get('activeCultivatorRef'); - if (!user || !ref) - return c.json({ success: false, error: '当前没有活跃角色' }, 404); - return organizationCommandMutation( - c, - 'sect_stipend_claim', - null, - executeSectStipendClaimCommand, - ); - }, - ); - - router.get( - '/current/transfer/preview', - requireActiveCultivatorRef(), - validateQuery(SectTransferPreviewQuerySchema), - async (c) => { - const ref = c.get('activeCultivatorRef'); - if (!ref) - return c.json({ success: false, error: '当前没有活跃角色' }, 404); - try { - const query = getValidatedQuery<{ - targetSectId: string; - reversePaths: boolean; - }>(c); - return c.json({ - success: true as const, - data: await previewSectTransfer({ - cultivatorId: ref.cultivatorId, - ...query, - runtime, - q: getExecutor(), - }), - }); - } catch (error) { - return failure(c, error); - } - }, - ); - - router.post( - '/current/transfer', - requireActiveCultivatorRef(), - validateJson(SectTransferRequestSchema), - async (c) => { - const body = getValidatedJson<{ - targetSectId: string; - reversePaths: boolean; - consumableId?: string; - }>(c); - return organizationCommandMutation(c, 'sect_transfer', body, (args) => - executeSectTransferCommand({ ...args, ...body }), - ); - }, - ); - - router.post('/:sectId/join', requireActiveCultivatorRef(), async (c) => { - const sectId = c.req.param('sectId'); - return organizationCommandMutation(c, 'sect_join', { sectId }, (args) => - executeSectJoinCommand({ ...args, sectId, admission }), - ); - }); - - // Legacy combat progression is retired; social and organization routes remain intact. - router.on( - ['POST', 'PUT'], - [ - '/current/methods/:methodId/train', - '/current/paths/:pathId/layers/:layerId/unlock', - '/current/paths/:pathId/activate', - '/current/paths/:pathId/meridian-loadouts/:slot', - '/current/paths/:pathId/meridian-loadouts/:slot/activate', - '/current/ability-loadout', - '/current/paths/:pathId/tactic', - ], - requireActiveCultivatorRef(), - (c) => - c.json( - { - success: false, - error: '旧宗门战斗养成已停用,请刷新使用新版心法与经脉', - }, - 410, - ), - ); - - router.route('/current', sectSocialRouter); - - return router; -} - -export default createSectsRouter(); diff --git a/src/server/routes/api/spirit-field.router.ts b/src/server/routes/api/spirit-field.router.ts deleted file mode 100644 index 850b53b8..00000000 --- a/src/server/routes/api/spirit-field.router.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { - redisLockErrorResponse, - requireActiveCultivatorRef, -} from '@server/lib/hono/middleware'; -import { jsonWithStatus } from '@server/lib/hono/response'; -import type { AppEnv } from '@server/lib/hono/types'; -import { PlayerCommandIdempotencyError } from '@server/lib/services/CommandExecutors'; -import { InventoryError } from '@server/lib/services/InventoryService'; -import { - QiInsufficientError, - QiServiceError, -} from '@server/lib/services/QiService'; -import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; -import { - SpiritFieldServiceError, - claimSpiritFieldStarterSeeds, - cultivateSpiritField, - getSpiritFieldSnapshot, - harvestSpiritField, - sowSpiritField, -} from '@server/lib/services/spirit-field/SpiritFieldService'; -import { - SpiritFieldCultivateRequestSchema, - SpiritFieldHarvestRequestSchema, - SpiritFieldSowRequestSchema, -} from '@shared/contracts/spiritField'; -import { Hono, type Context } from 'hono'; -import { z } from 'zod'; - -const router = new Hono(); -router.use('*', requireActiveCultivatorRef()); - -function actor(c: Context) { - const active = c.get('activeCultivatorRef'); - if (!active) { - throw new SpiritFieldServiceError('当前没有活跃角色', 404); - } - return { userId: active.userId, cultivatorId: active.cultivatorId }; -} - -function errorResponse(c: Context, error: unknown) { - const lockResponse = redisLockErrorResponse(error); - if (lockResponse) return lockResponse; - - if ( - error instanceof InventoryError || - error instanceof PlayerCommandIdempotencyError - ) - return c.json({ success: false, error: error.message }, 409); - if (error instanceof z.ZodError) { - return c.json( - { - success: false, - error: error.issues[0]?.message || '参数错误', - details: error.issues, - }, - 400, - ); - } - - if (error instanceof SpiritFieldServiceError) { - return jsonWithStatus( - c, - { success: false, error: error.message }, - error.status, - ); - } - - if (error instanceof QiInsufficientError) { - return c.json( - { - success: false, - error: error.code, - message: error.message, - required: error.required, - current: error.current, - action: error.action, - }, - 409, - ); - } - - if (error instanceof QiServiceError) { - return jsonWithStatus( - c, - { success: false, error: error.message }, - error.status, - ); - } - - console.error('spirit field api error:', error); - return c.json({ success: false, error: '灵田灵机暂乱,请稍后再试' }, 500); -} - -router.get('/', async (c) => { - try { - return c.json({ - success: true, - data: await getSpiritFieldSnapshot(actor(c)), - }); - } catch (error) { - return errorResponse(c, error); - } -}); - -router.post('/starter', async (c) => { - try { - const committed = await claimSpiritFieldStarterSeeds(actor(c)); - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - return errorResponse(c, error); - } -}); - -router.post('/sow', async (c) => { - try { - const input = SpiritFieldSowRequestSchema.parse(await c.req.json()); - const committed = await sowSpiritField(actor(c), input); - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - return errorResponse(c, error); - } -}); - -router.post('/cultivate', async (c) => { - try { - const input = SpiritFieldCultivateRequestSchema.parse(await c.req.json()); - const committed = await cultivateSpiritField( - actor(c), - input, - c.req.raw.signal, - ); - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - return errorResponse(c, error); - } -}); - -router.post('/harvest', async (c) => { - try { - const input = SpiritFieldHarvestRequestSchema.parse(await c.req.json()); - const committed = await harvestSpiritField( - actor(c), - input, - c.req.raw.signal, - ); - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - return errorResponse(c, error); - } -}); - -export default router; diff --git a/src/server/routes/api/sponsorship.router.ts b/src/server/routes/api/sponsorship.router.ts deleted file mode 100644 index 0302d84c..00000000 --- a/src/server/routes/api/sponsorship.router.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { - getValidatedJson, - getValidatedQuery, - requireActiveCultivatorRef, - validateJson, - validateQuery, -} from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { - claimSponsorshipOrder, - createSponsorshipCheckoutIntent, - getCheckoutIntentStatus, - getCultivatorMerit, - getSponsorshipClientConfig, - listPublicMeritProfiles, - recordAfdianWebhook, - SponsorshipApplicationError, - updateMeritVisibility, -} from '@server/lib/services/SponsorshipApplicationService'; -import { - SponsorshipCheckoutRequestSchema, - SponsorshipClaimRequestSchema, - SponsorshipPublicQuerySchema, - SponsorshipVisibilityRequestSchema, - type SponsorshipCheckoutRequest, - type SponsorshipClaimRequest, -} from '@shared/contracts/sponsorship'; -import { Hono, type Context } from 'hono'; -import { bodyLimit } from 'hono/body-limit'; -import { z } from 'zod'; - -const router = new Hono(); - -function sponsorshipErrorResponse(c: Context, error: unknown) { - if (error instanceof SponsorshipApplicationError) { - return c.json({ error: error.message, code: error.code }, error.status); - } - throw error; -} - -router.post( - '/providers/afdian/webhook', - bodyLimit({ maxSize: 256 * 1_024 }), - async (c) => { - try { - await recordAfdianWebhook(await c.req.json()); - return c.json({ ec: 200, em: '' }); - } catch (error) { - console.warn('[sponsorship-webhook] rejected', { - error: error instanceof Error ? error.message : 'unknown', - }); - return c.json({ ec: 400, em: 'invalid webhook' }, 400); - } - }, -); - -router.get('/config', async (c) => c.json(await getSponsorshipClientConfig())); - -router.get( - '/public', - validateQuery(SponsorshipPublicQuerySchema), - async (c) => { - const query = getValidatedQuery<{ page: number; pageSize: number }>(c); - return c.json(await listPublicMeritProfiles(query.page, query.pageSize)); - }, -); - -router.get('/me', requireActiveCultivatorRef(), async (c) => { - const ref = c.get('activeCultivatorRef')!; - return c.json(await getCultivatorMerit(ref.cultivatorId)); -}); - -router.patch( - '/me/visibility', - requireActiveCultivatorRef(), - validateJson(SponsorshipVisibilityRequestSchema), - async (c) => { - const ref = c.get('activeCultivatorRef')!; - const body = getValidatedJson<{ isPublic: boolean }>(c); - const updated = await updateMeritVisibility( - ref.cultivatorId, - body.isPublic, - ); - return c.json({ success: true, updated }); - }, -); - -router.post( - '/checkout-intents', - requireActiveCultivatorRef(), - validateJson(SponsorshipCheckoutRequestSchema), - async (c) => { - const user = c.get('user')!; - const ref = c.get('activeCultivatorRef')!; - const body = getValidatedJson(c); - try { - return c.json( - await createSponsorshipCheckoutIntent({ - userId: user.id, - cultivatorId: ref.cultivatorId, - ...body, - }), - 201, - ); - } catch (error) { - return sponsorshipErrorResponse(c, error); - } - }, -); - -router.get('/checkout-intents/:id', requireActiveCultivatorRef(), async (c) => { - const user = c.get('user')!; - const id = z.uuid().safeParse(c.req.param('id')); - if (!id.success) return c.json({ error: '下单意图不存在' }, 404); - const status = await getCheckoutIntentStatus({ - id: id.data, - userId: user.id, - }); - return status ? c.json(status) : c.json({ error: '下单意图不存在' }, 404); -}); - -router.post( - '/claims', - requireActiveCultivatorRef(), - validateJson(SponsorshipClaimRequestSchema), - async (c) => { - const user = c.get('user')!; - const ref = c.get('activeCultivatorRef')!; - const body = getValidatedJson(c); - try { - await claimSponsorshipOrder({ - userId: user.id, - cultivatorId: ref.cultivatorId, - ...body, - }); - return c.json({ success: true }); - } catch (error) { - return sponsorshipErrorResponse(c, error); - } - }, -); - -export default router; diff --git a/src/server/routes/api/story.router.ts b/src/server/routes/api/story.router.ts deleted file mode 100644 index 111cf6b5..00000000 --- a/src/server/routes/api/story.router.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { requireActiveCultivatorRef } from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { readResourceWithMeta } from '@server/lib/services/ResourceReadService'; -import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; -import { - completeStoryGuideCommand, - completeStoryPerformanceCommand, -} from '@server/lib/services/StoryApplicationService'; -import { StoryService } from '@server/lib/services/StoryService'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const CompleteSchema = z - .object({ - outcome: z.string().trim().min(1).max(40), - }) - .strict(); - -const router = new Hono(); - -router.get('/', requireActiveCultivatorRef(), async (c) => { - const ref = c.get('activeCultivatorRef'); - if (!ref) return c.json({ error: '当前没有活跃角色' }, 404); - return c.json( - await readResourceWithMeta( - { kind: 'cultivator', id: ref.cultivatorId }, - 'player.story', - (tx) => StoryService.read(ref.cultivatorId, tx), - ), - ); -}); - -router.post('/performances/:scriptId/complete', requireActiveCultivatorRef(), async (c) => { - const user = c.get('user'); - const ref = c.get('activeCultivatorRef'); - if (!user || !ref) return c.json({ error: '当前没有活跃角色' }, 404); - const scriptId = c.req.param('scriptId'); - const parsed = CompleteSchema.safeParse(await c.req.json().catch(() => null)); - if (!parsed.success) { - return c.json( - { error: parsed.error.issues[0]?.message || '演出结果无效' }, - 400, - ); - } - const { outcome } = parsed.data; - - try { - const committed = await completeStoryPerformanceCommand({ - userId: user.id, - cultivatorId: ref.cultivatorId, - scriptId, - outcome, - }); - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - const message = error instanceof Error ? error.message : '演出没能记下'; - const status = message.includes('当前没有这场演出') ? 409 : 400; - return c.json({ error: message }, status); - } -}); - -router.post('/guides/:lessonId/complete', requireActiveCultivatorRef(), async (c) => { - const user = c.get('user'); - const ref = c.get('activeCultivatorRef'); - if (!user || !ref) return c.json({ error: '当前没有活跃角色' }, 404); - const lessonId = c.req.param('lessonId'); - if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(lessonId) || lessonId.length > 80) { - return c.json({ error: '没有这场教学' }, 404); - } - - try { - const committed = await completeStoryGuideCommand({ - userId: user.id, - cultivatorId: ref.cultivatorId, - lessonId, - }); - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - const message = error instanceof Error ? error.message : '这课没能记下'; - const status = message.includes('当前没有这场教学') - ? 409 - : message.includes('没有这场教学') - ? 404 - : 400; - return c.json({ error: message }, status); - } -}); - -export default router; diff --git a/src/server/routes/api/tasks.router.ts b/src/server/routes/api/tasks.router.ts deleted file mode 100644 index eec0c84a..00000000 --- a/src/server/routes/api/tasks.router.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { requireActiveCultivatorRef } from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { - toPlayerStateMutationResponse, -} from '@server/lib/services/ResourceMutationResponse'; -import { readResourceWithMeta } from '@server/lib/services/ResourceReadService'; -import { - claimTaskRewardCommand, -} from '@server/lib/services/TaskApplicationService'; -import { TaskService } from '@server/lib/services/TaskService'; -import { startBreakthroughBattle } from '@server/lib/services/combat-v6/CombatV6BreakthroughService'; -import { redisLockErrorResponse } from '@server/lib/hono/middleware'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const ListQuerySchema = z.object({ - status: z.enum(['active', 'completed']).optional(), -}); - -const router = new Hono(); - -router.get('/', requireActiveCultivatorRef(), async (c) => { - const ref = c.get('activeCultivatorRef'); - if (!ref) { - return c.json({ error: '当前没有活跃角色' }, 404); - } - - try { - const { status } = ListQuerySchema.parse(c.req.query()); - return c.json( - await readResourceWithMeta( - { kind: 'cultivator', id: ref.cultivatorId }, - 'player.tasks', - (tx) => - TaskService.readCultivatorTasks(ref.cultivatorId, status, tx), - ), - ); - } catch (error) { - if (error instanceof z.ZodError) { - return c.json({ error: error.issues[0]?.message || '查询参数错误' }, 400); - } - console.error('获取任务列表失败:', error); - return c.json({ error: '获取任务列表失败,请稍后再试' }, 500); - } -}); - -router.get('/:id', requireActiveCultivatorRef(), async (c) => { - const ref = c.get('activeCultivatorRef'); - if (!ref) { - return c.json({ error: '当前没有活跃角色' }, 404); - } - - try { - const task = await TaskService.getCultivatorTask( - ref.cultivatorId, - c.req.param('id'), - ); - if (!task) { - return c.json({ error: '任务不存在' }, 404); - } - - return c.json({ - success: true, - data: { - task, - }, - }); - } catch (error) { - console.error('获取任务详情失败:', error); - return c.json({ error: '获取任务详情失败,请稍后再试' }, 500); - } -}); - -router.post('/:id/challenge', requireActiveCultivatorRef(), async (c) => { - const user = c.get('user'); - const ref = c.get('activeCultivatorRef'); - if (!user || !ref) { - return c.json({ error: '当前没有活跃角色' }, 404); - } - - try { - const session = await startBreakthroughBattle({ - userId: user.id, - cultivatorId: ref.cultivatorId, - }, z.uuid().parse(c.req.param('id'))); - return c.json({ success: true, data: session }); - } catch (error) { - const lock = redisLockErrorResponse(error); - if (lock) return lock; - const message = - error instanceof Error ? error.message : '试炼失败,请稍后再试'; - const status = - message === '任务不存在' - ? 404 - : message.includes('没有可执行') - ? 409 - : 400; - return c.json({ error: message }, status); - } -}); - -router.post('/:id/claim-reward', requireActiveCultivatorRef(), async (c) => { - const user = c.get('user'); - const ref = c.get('activeCultivatorRef'); - if (!user || !ref) { - return c.json({ error: '当前没有活跃角色' }, 404); - } - - try { - const committed = await claimTaskRewardCommand({ - userId: user.id, - cultivatorId: ref.cultivatorId, - taskId: c.req.param('id'), - }); - - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - const message = - error instanceof Error ? error.message : '领取奖励失败,请稍后再试'; - const status = - message === '任务不存在' - ? 404 - : message.includes('尚未完成') || message.includes('已经领取') - ? 409 - : 400; - return c.json({ error: message }, status); - } -}); - -export default router; diff --git a/src/server/routes/api/tower.router.ts b/src/server/routes/api/tower.router.ts deleted file mode 100644 index 7d9a5408..00000000 --- a/src/server/routes/api/tower.router.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { - redisLockErrorResponse, - requireActiveCultivatorRef, -} from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { InventoryError } from '@server/lib/services/InventoryService'; -import { CombatV6BuildError } from '@server/lib/services/combat-v6/CombatV6BuildService'; -import { - advanceTower, - changeTowerBattle, - completeTower, - getTowerBattle, - getTowerView, - startTower, - TowerV6Error, -} from '@server/lib/tower/combatV6'; -import { getTowerLeaderboard } from '@server/lib/tower/leaderboard'; -import { CombatAutoRequestSchema } from '@shared/combat-v6/auto'; -import { - CombatV6TrainingCommandRequestSchema, - CombatV6TrainingEventsQuerySchema, - CombatV6TrainingRevisionRequestSchema, -} from '@shared/contracts/combatV6'; -import { TOWER_BLESSING_IDS } from '@shared/lib/tower/blessings'; -import { TOWER_ELIGIBLE_REALMS } from '@shared/lib/tower/helpers'; -import { getTowerSeasonMeta } from '@shared/lib/tower/season'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const router = new Hono(); -router.use('*', requireActiveCultivatorRef()); -router.post('/battle/sessions/:id/auto', async (c) => { - const input = CombatAutoRequestSchema.parse(await c.req.json()); - const id = z.uuid().parse(c.req.param('id')); - try { - return c.json({ - success: true, - data: await changeTowerBattle( - c.get('activeCultivatorRef')!, - id, - input.expectedRevision, - undefined, - input.round, - ), - }); - } catch (error) { - return c.json( - { error: error instanceof Error ? error.message : '自动指令提交失败' }, - 409, - ); - } -}); - -router.onError((error, c) => { - const lock = redisLockErrorResponse(error); - if (lock) return lock; - if (error instanceof z.ZodError) - return c.json({ error: '幻境请求参数无效' }, 400); - if ( - error instanceof TowerV6Error || - error instanceof InventoryError || - error instanceof CombatV6BuildError - ) - return c.json({ error: error.message }, 409); - console.error('[tower-v6] request failed', error); - return c.json({ error: '幻境操作失败,请刷新后重试' }, 500); -}); -router.get('/state', async (c) => - c.json({ - success: true, - data: await getTowerView(c.get('activeCultivatorRef')!.cultivatorId), - }), -); -router.post('/start', async (c) => - c.json({ - success: true, - data: await startTower(c.get('activeCultivatorRef')!.cultivatorId), - }), -); -const actionSchema = z.object({ - runId: z.uuid(), - revision: z.number().int().min(0), - action: z.enum(['battle', 'blessing', 'leave', 'complete']), - blessingId: z.enum(TOWER_BLESSING_IDS).optional(), -}); -router.post('/action', async (c) => { - const input = actionSchema.parse(await c.req.json()); - const owner = c.get('activeCultivatorRef')!.cultivatorId; - const data = - input.action === 'complete' - ? await completeTower( - { userId: c.get('user')!.id, cultivatorId: owner }, - input, - ) - : await advanceTower({ userId: c.get('user')!.id, cultivatorId: owner }, input); - return c.json({ success: true, data }); -}); -router.get('/leaderboard', async (c) => { - const realm = z.enum(TOWER_ELIGIBLE_REALMS).parse(c.req.query('realm')); - const season = getTowerSeasonMeta(); - return c.json({ - success: true, - data: await getTowerLeaderboard({ - seasonKey: season.seasonKey, - seasonEndAt: season.seasonEndsAt, - realm, - limit: 30, - selfCultivatorId: c.get('activeCultivatorRef')!.cultivatorId, - }), - }); -}); -router.get('/battle/sessions/current', async (c) => - c.json({ - success: true, - data: await getTowerBattle(c.get('activeCultivatorRef')!.cultivatorId), - }), -); -router.get('/battle/sessions/:id', async (c) => { - const query = CombatV6TrainingEventsQuerySchema.parse(c.req.query()); - const data = await getTowerBattle( - c.get('activeCultivatorRef')!.cultivatorId, - z.uuid().parse(c.req.param('id')), - query.afterEventSeq, - ); - return data - ? c.json({ success: true, data }) - : c.json({ error: '战斗不存在' }, 404); -}); -router.put('/battle/sessions/:id/commands/:unitId', async (c) => { - const input = CombatV6TrainingCommandRequestSchema.parse(await c.req.json()); - const data = await changeTowerBattle( - { - userId: c.get('user')!.id, - cultivatorId: c.get('activeCultivatorRef')!.cultivatorId, - }, - z.uuid().parse(c.req.param('id')), - input.expectedRevision, - { unitId: c.req.param('unitId'), commands: input.commands }, - ); - return c.json({ success: true, data }); -}); -router.post('/battle/sessions/:id/resolve', async (c) => { - const input = CombatV6TrainingRevisionRequestSchema.parse(await c.req.json()); - const data = await changeTowerBattle( - { - userId: c.get('user')!.id, - cultivatorId: c.get('activeCultivatorRef')!.cultivatorId, - }, - z.uuid().parse(c.req.param('id')), - input.expectedRevision, - ); - return c.json({ success: true, data }); -}); -router.all('/battle/*', (c) => - c.json({ error: '旧幻境战斗已下线,请刷新页面' }, 410), -); -router.post('/reset', (c) => - c.json({ error: '旧幻境接口已下线,请刷新页面' }, 410), -); -router.post('/blessing/choose', (c) => - c.json({ error: '旧幻境接口已下线,请刷新页面' }, 410), -); -export default router; diff --git a/src/server/routes/api/world-chat.router.ts b/src/server/routes/api/world-chat.router.ts deleted file mode 100644 index fb1283cd..00000000 --- a/src/server/routes/api/world-chat.router.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { - getValidatedJson, - getValidatedQuery, - requireActiveCultivatorRef, - validateJson, - validateQuery, -} from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { rejectRetiredBattleShare } from '@server/lib/hono/chat'; -import { checkAndAcquireCooldown } from '@server/lib/redis/worldChatLimiter'; -import { - createMessage, - listLatestMessages, - listMessages, -} from '@server/lib/repositories/worldChatRepository'; -import { - ChatMessageApplicationError, - createCultivatorChatMessage, -} from '@server/lib/services/chatMessageApplication'; -import { - WorldChatCreateMessageSchema, - WorldChatListQuerySchema, - type WorldChatCreateMessageRequest, - type WorldChatListQuery, -} from '@shared/contracts/world-chat'; -import { Hono } from 'hono'; - -const router = new Hono(); - -router.get('/messages', validateQuery(WorldChatListQuerySchema), async (c) => { - const { channel, limit, page, pageSize } = - getValidatedQuery(c); - - if (limit) { - const messages = await listLatestMessages(limit, channel); - return c.json({ success: true, data: messages }); - } - - const currentPage = page || 1; - const currentPageSize = pageSize || 20; - const result = await listMessages({ - channel, - page: currentPage, - pageSize: currentPageSize, - }); - return c.json({ - success: true, - data: result.messages, - pagination: { - page: currentPage, - pageSize: currentPageSize, - hasMore: result.hasMore, - }, - }); -}); - -router.post( - '/messages', - requireActiveCultivatorRef(), - rejectRetiredBattleShare, - validateJson(WorldChatCreateMessageSchema), - async (c) => { - const user = c.get('user'); - const cultivator = c.get('activeCultivatorRef'); - if (!user || !cultivator) { - return c.json({ success: false, error: '未授权访问' }, 401); - } - - try { - const message = await createCultivatorChatMessage({ - request: getValidatedJson(c), - userId: user.id, - cultivatorId: cultivator.cultivatorId, - channel: 'world', - sectId: null, - acquireCooldown: checkAndAcquireCooldown, - persist: (input) => createMessage({ ...input, channel: 'world' }), - }); - return c.json({ success: true, data: message }); - } catch (error) { - if (error instanceof ChatMessageApplicationError) { - return c.json( - { - success: false, - error: error.message, - ...(error.remainingSeconds - ? { remainingSeconds: error.remainingSeconds } - : {}), - }, - error.status, - ); - } - console.error('Create world chat message error:', error); - return c.json({ success: false, error: '发送失败,请稍后重试' }, 500); - } - }, -); - -export default router; diff --git a/src/server/routes/internal/cron.router.ts b/src/server/routes/internal/cron.router.ts deleted file mode 100644 index facbe360..00000000 --- a/src/server/routes/internal/cron.router.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { - runAuctionExpireJob, - runExpiredDataCleanupJob, - runMarketRefreshCronJob, - runMaterialLibraryDailyGenerationJob, - runResourceReplayCleanupJob, - runRankRewardsJob, - runSponsorshipCleanupJob, - runSponsorshipReconcileJob, - runSponsorshipAdminDigestJob, -} from '@server/lib/jobs/internalCron'; -import type { AppEnv } from '@server/lib/hono/types'; -import { Hono } from 'hono'; - -function getCronAuthorizationError( - request: Request, -): { error: string; status: 401 | 500 } | null { - const cronSecret = process.env.CRON_SECRET; - if (!cronSecret) { - if (process.env.NODE_ENV === 'production') { - return { - error: 'CRON_SECRET is required in production', - status: 500, - }; - } - - return null; - } - - if (request.headers.get('authorization') !== `Bearer ${cronSecret}`) { - return { - error: 'Unauthorized', - status: 401, - }; - } - - return null; -} - -async function handleCronRequest( - request: Request, - runJob: () => Promise, -): Promise { - const authError = getCronAuthorizationError(request); - if (authError) { - return Response.json( - { success: false, error: authError.error }, - { status: authError.status }, - ); - } - - try { - return Response.json(await runJob()); - } catch { - return Response.json( - { success: false, error: 'Cron job execution failed' }, - { status: 500 }, - ); - } -} - -const router = new Hono(); - -router.get('/auction-expire', (c) => - handleCronRequest(c.req.raw, runAuctionExpireJob), -); - -router.get('/rank-rewards', (c) => - handleCronRequest(c.req.raw, runRankRewardsJob), -); - -router.get('/market-refresh', (c) => - handleCronRequest(c.req.raw, runMarketRefreshCronJob), -); - -router.get('/resource-replay-cleanup', (c) => - handleCronRequest(c.req.raw, runResourceReplayCleanupJob), -); - -router.get('/expired-data-cleanup', (c) => - handleCronRequest(c.req.raw, runExpiredDataCleanupJob), -); - -router.get('/material-library-daily-generation', (c) => - handleCronRequest(c.req.raw, runMaterialLibraryDailyGenerationJob), -); - -router.get('/sponsorship-reconcile', (c) => - handleCronRequest(c.req.raw, () => runSponsorshipReconcileJob(false)), -); - -router.get('/sponsorship-deep-reconcile', (c) => - handleCronRequest(c.req.raw, () => runSponsorshipReconcileJob(true)), -); - -router.get('/sponsorship-cleanup', (c) => - handleCronRequest(c.req.raw, runSponsorshipCleanupJob), -); - -router.get('/sponsorship-admin-digest', (c) => - handleCronRequest(c.req.raw, runSponsorshipAdminDigestJob), -); - -export default router; diff --git a/src/server/routes/internal/index.ts b/src/server/routes/internal/index.ts deleted file mode 100644 index c34daa23..00000000 --- a/src/server/routes/internal/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { AppEnv } from '@server/lib/hono/types'; -import cronRouter from '@server/routes/internal/cron.router'; -import { Hono } from 'hono'; - -const internalRouter = new Hono(); - -internalRouter.route('/cron', cronRouter); - -export default internalRouter; diff --git a/src/server/routes/player.router.ts b/src/server/routes/player.router.ts deleted file mode 100644 index 06e91659..00000000 --- a/src/server/routes/player.router.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { db } from '@server/lib/drizzle/db'; -import { - getValidatedQuery, - requireUser, - validateQuery, -} from '@server/lib/hono/middleware'; -import type { AppEnv } from '@server/lib/hono/types'; -import { - readResourceEventWindow, - RESOURCE_EVENT_PAGE_LIMIT, -} from '@server/lib/repositories/playerStateRepository'; -import { cultivators, sectMemberships } from '@server/lib/drizzle/schema'; -import { - parsePlayerResourceKeys, - readPlayerResourcesSnapshot, -} from '@server/lib/services/PlayerResourceReaderService'; -import type { - PlayerResourceEventsResponse, - PlayerResourcesResponse, -} from '@shared/contracts/player'; -import { - RESOURCE_SCOPE_KINDS, - requiresResourceEventReload, - type ResourceScope, -} from '@shared/contracts/resources'; -import { and, eq } from 'drizzle-orm'; -import { Hono } from 'hono'; -import { z } from 'zod'; - -const router = new Hono(); -const PlayerResourcesQuerySchema = z.object({ - keys: z.string().min(1).max(256), -}); -const PlayerResourceEventsQuerySchema = z.object({ - after: z.coerce.number().int().nonnegative().default(0), - scopeKind: z.enum(RESOURCE_SCOPE_KINDS), - scopeId: z.string().min(1).max(128), -}); - -router.get( - '/resources', - requireUser(), - validateQuery(PlayerResourcesQuerySchema), - async (c) => { - const user = c.get('user'); - if (!user) return c.json({ success: false, error: '未授权访问' }, 401); - let keys; - try { - keys = parsePlayerResourceKeys( - getValidatedQuery<{ keys: string }>(c).keys, - ); - } catch (error) { - return c.json( - { - success: false, - error: error instanceof Error ? error.message : '玩家资源 keys 无效', - }, - 400, - ); - } - const payload: PlayerResourcesResponse = { - success: true, - data: await readPlayerResourcesSnapshot({ userId: user.id, keys }), - }; - return c.json(payload); - }, -); - -router.get( - '/resources/events', - requireUser(), - validateQuery(PlayerResourceEventsQuerySchema), - async (c) => { - const user = c.get('user'); - if (!user) return c.json({ success: false, error: '未授权访问' }, 401); - const active = await db.query.cultivators.findFirst({ - columns: { id: true }, - where: and( - eq(cultivators.userId, user.id), - eq(cultivators.status, 'active'), - ), - }); - const { after, scopeKind, scopeId } = getValidatedQuery<{ - after: number; - scopeKind: ResourceScope['kind']; - scopeId: string; - }>(c); - const scope = { kind: scopeKind, id: scopeId } satisfies ResourceScope; - if ( - !(await canReadScope( - { userId: user.id, cultivatorId: active?.id ?? null }, - scope, - )) - ) { - return c.json({ success: false, error: '无权读取该资源作用域' }, 403); - } - const window = await readResourceEventWindow(scope, after); - const requiresReload = - window.hasIncompatibleEvents || - requiresResourceEventReload(window, after, RESOURCE_EVENT_PAGE_LIMIT); - const payload: PlayerResourceEventsResponse = { - success: true, - data: { - after, - scope, - currentScopeVersion: window.currentScopeVersion, - earliestAvailableVersion: window.earliestAvailableVersion, - changes: window.changes.slice(0, RESOURCE_EVENT_PAGE_LIMIT), - requiresReload, - }, - }; - return c.json(payload); - }, -); - -export default router; - -async function canReadScope( - ref: { userId: string; cultivatorId: string | null }, - scope: ResourceScope, -): Promise { - if (scope.kind === 'account') return scope.id === ref.userId; - if (scope.kind === 'cultivator') { - return Boolean(ref.cultivatorId && scope.id === ref.cultivatorId); - } - if (scope.kind === 'global') return scope.id === 'global'; - if (!ref.cultivatorId) return false; - const membership = await db.query.sectMemberships.findFirst({ - columns: { id: true }, - where: and( - eq(sectMemberships.cultivatorId, ref.cultivatorId), - eq(sectMemberships.sectId, scope.id), - eq(sectMemberships.status, 'active'), - ), - }); - return Boolean(membership); -} diff --git a/src/shared/engine/combat-v6/projection/character-level.ts b/src/shared/engine/combat-v6/projection/character-level.ts deleted file mode 100644 index 3aab0b11..00000000 --- a/src/shared/engine/combat-v6/projection/character-level.ts +++ /dev/null @@ -1,2 +0,0 @@ -// Combat and permanent attribute budgets use the same realm-to-level mapping. -export { getRealmStageLevel as combatCharacterLevel } from '@shared/config/realmProgression'; diff --git a/src/shared/engine/cultivator/creation/CharacterGenerator.test.ts b/src/shared/engine/cultivator/creation/CharacterGenerator.test.ts deleted file mode 100644 index a395cd2f..00000000 --- a/src/shared/engine/cultivator/creation/CharacterGenerator.test.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest'; -import type { CultivatorAIRawData } from './types'; -import { CharacterGenerator } from './CharacterGenerator'; -import { CultivatorAIRawSchema } from './types'; - -const { generateAiObjectMock } = vi.hoisted(() => ({ - generateAiObjectMock: vi.fn(), -})); - -vi.mock('@server/utils/aiClient', () => ({ - generateAiObject: generateAiObjectMock, -})); - -const buildAIData = ( - overrides: Partial = {}, -): CultivatorAIRawData => ({ - name: '林秋', - gender: '男', - origin: '青岚山', - personality: '沉静坚韧', - background: '少年出身山村,偶得残卷,自此踏上修行之路。', - element_preferences: ['金', '木', '水', '火'], - aptitude_score: 78, - balance_notes: '双目有神,命数稳中带锋。', - ...overrides, -}); - -describe('CharacterGenerator', () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - it('uses the tolerant AI schema and trims extra element preferences', async () => { - generateAiObjectMock.mockResolvedValueOnce({ - output: buildAIData({ - element_preferences: ['金', '木', '水', '火', '土'], - }), - }); - - const { cultivator } = await CharacterGenerator.generate('偏向剑修的少年'); - - expect(generateAiObjectMock).toHaveBeenCalledWith( - expect.objectContaining({ - system: expect.any(String), - prompt: expect.any(String), - schema: CultivatorAIRawSchema, - name: '修仙真形骨架', - sceneId: 'character-generation', - }), - ); - expect(cultivator.spiritual_roots.map((root) => root.element)).toEqual([ - '金', - '木', - '水', - '火', - ]); - }); - - it('deduplicates repeated element preferences before generating roots', async () => { - generateAiObjectMock.mockResolvedValueOnce({ - output: buildAIData({ - element_preferences: ['火', '火', '水'], - }), - }); - - const { cultivator } = await CharacterGenerator.generate('擅长丹火的修士'); - - expect(cultivator.spiritual_roots.map((root) => root.element)).toEqual([ - '火', - '水', - ]); - }); - - it('creates fixed base attributes without a persisted skill cap field', async () => { - generateAiObjectMock.mockResolvedValueOnce({ - output: buildAIData({ - aptitude_score: 95, - }), - }); - - const { cultivator } = await CharacterGenerator.generate('根骨上佳的修士'); - - expect(cultivator.attributes).toEqual({ - vitality: 15, - strength: 15, - spirit: 15, - endurance: 15, - speed: 15, - willpower: 15, - }); - expect(cultivator.unallocated_attribute_points).toBe(25); - const removedSkillCapKey = 'max' + '_skills'; - expect(removedSkillCapKey in cultivator).toBe(false); - }); -}); diff --git a/src/shared/engine/cultivator/creation/CharacterGenerator.ts b/src/shared/engine/cultivator/creation/CharacterGenerator.ts deleted file mode 100644 index dcf7218f..00000000 --- a/src/shared/engine/cultivator/creation/CharacterGenerator.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { getRealmStageUnallocatedAttributeBudget } from '@shared/config/realmProgression'; -import { generateAiObject } from '@server/utils/aiClient'; -import type { Cultivator } from '@shared/types/cultivator'; -import { - getCharacterGenerationPrompt, - getCharacterGenerationUserPrompt, -} from './prompts'; -import { CultivatorAIRawSchema, normalizeCultivatorAIData } from './types'; -import { generateAttributes, generateSpiritualRoots } from './utils'; - -export class CharacterGenerator { - /** - * 生成新角色 - * @param userInput 用户输入的描述/提示词 - */ - public static async generate( - userInput: string, - ): Promise<{ cultivator: Cultivator; balanceNotes: string }> { - // 1. 调用 AI 生成角色骨架 - const prompt = getCharacterGenerationPrompt(); - const userPrompt = getCharacterGenerationUserPrompt(userInput); - - const aiResponse = await generateAiObject({ - system: prompt, - prompt: userPrompt, - schema: CultivatorAIRawSchema, - name: '修仙真形骨架', - sceneId: 'character-generation', - }); - - const data = normalizeCultivatorAIData(aiResponse.output); - - // 2. 数值化生成 - const attributes = generateAttributes(); - const spiritual_roots = generateSpiritualRoots( - data.aptitude_score, - data.element_preferences, - ); - - // 4. 其他基础数值 - const age = 14 + Math.floor(Math.random() * 6); // 14-20岁 - // 寿元:炼气期基础100,分数高加成 - const lifespan = - 80 + Math.floor(Math.random() * 20) + (data.aptitude_score > 80 ? 20 : 0); - - // 构造完整的 Cultivator 对象 - const cultivator: Cultivator = { - id: '', // Placeholder - name: data.name, - gender: data.gender, - origin: data.origin, - personality: data.personality, - background: data.background, - playerRace: 'human', - raceNarrative: data.race_narrative, - - realm: '炼气', - realm_stage: '初期', - age, - lifespan, - - attributes, - unallocated_attribute_points: getRealmStageUnallocatedAttributeBudget( - '炼气', '初期', - ), - spiritual_roots, - status: 'active', - spirit_stones: 0, - pre_heaven_fates: [], // 后续流程生成 - inventory: { - artifacts: [], - consumables: [], - materials: [], - }, - equipped: { - weapon: null, - armor: null, - accessory: null, - }, - prompt: userInput, - balance_notes: data.balance_notes, - }; - - return { - cultivator, - balanceNotes: data.balance_notes, - }; - } -} diff --git a/src/shared/engine/spirit-field/SpiritSeedGenerator.ts b/src/shared/engine/spirit-field/SpiritSeedGenerator.ts deleted file mode 100644 index 01c51d18..00000000 --- a/src/shared/engine/spirit-field/SpiritSeedGenerator.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { renderPromptSystem, renderPromptUser } from '@server/lib/prompts'; -import { generateAiArray } from '@server/utils/aiClient'; -import { CREATION_MATERIAL_SEMANTIC_TAGS, CreationTags } from '@shared/engine/shared/tag-domain'; -import { ELEMENT_VALUES, QUALITY_VALUES, type ElementType, type Quality } from '@shared/types/constants'; -import type { Material } from '@shared/types/cultivator'; -import { z } from 'zod'; -import { SPIRIT_SEED_QUALITY_CHANCE_MAP, getSpiritFieldQualityBalance } from './config'; -import { buildSpiritFieldSeedMaterialFromPlant } from './seedMaterial'; -import { SPIRIT_FIELD_CULTIVATION_METHODS, SPIRIT_FIELD_OUTCOME_KINDS, SPIRIT_SEED_GROWTH_FORMS, SPIRIT_SEED_GROWTH_TRAITS, SPIRIT_SEED_HABITAT_TAGS, SPIRIT_SEED_HARVEST_PARTS, SPIRIT_SEED_USE_TAGS, type SpiritFieldPlantSnapshot, type SpiritSeedIdentity, type SpiritSeedRandomOptions, type SpiritSeedSkeleton } from './types'; - -const SpiritSeedAISchema = z.object({ - seedName: z.string().trim().min(2).max(12), - seedDescription: z.string().trim().min(12).max(100), - clueTexts: z.array(z.string().trim().min(6).max(48)).min(2).max(3), - element: z.enum(ELEMENT_VALUES), - growthForm: z.enum(SPIRIT_SEED_GROWTH_FORMS), - harvestPart: z.enum(SPIRIT_SEED_HARVEST_PARTS), - preferredMethods: z.array(z.enum(SPIRIT_FIELD_CULTIVATION_METHODS)).min(2).max(6), - avoidedMethods: z.array(z.enum(SPIRIT_FIELD_CULTIVATION_METHODS)).max(4), - preferredHabitats: z.array(z.enum(SPIRIT_SEED_HABITAT_TAGS)).min(1).max(3), - avoidedHabitats: z.array(z.enum(SPIRIT_SEED_HABITAT_TAGS)).max(2), - growthTraits: z.array(z.enum(SPIRIT_SEED_GROWTH_TRAITS)).min(1).max(4), - useTags: z.array(z.enum(SPIRIT_SEED_USE_TAGS)).min(1).max(4), - outcomeBiases: z.array(z.enum(SPIRIT_FIELD_OUTCOME_KINDS)).min(1).max(3), - creationTags: z.array(z.enum(CREATION_MATERIAL_SEMANTIC_TAGS)).min(1).max(5), -}).strict(); - -export interface SpiritSeedBatchSpec { rank: Quality; quantity: number; element?: ElementType; regionTags?: string[] } - -function pickWeightedQuality(options: SpiritSeedRandomOptions, rng: () => number): Quality { - if (options.guaranteedRank) return options.guaranteedRank; - const min = options.rankRange ? QUALITY_VALUES.indexOf(options.rankRange.min) : 0; - const max = options.rankRange ? QUALITY_VALUES.indexOf(options.rankRange.max) : QUALITY_VALUES.length - 1; - const candidates = QUALITY_VALUES.slice(Math.min(min, max), Math.max(min, max) + 1); - const weights = options.qualityChanceMap ?? SPIRIT_SEED_QUALITY_CHANCE_MAP; - const total = candidates.reduce((sum, quality) => sum + Math.max(0, weights[quality]), 0); - let cursor = rng() * total; - for (const quality of candidates) { cursor -= Math.max(0, weights[quality]); if (cursor <= 0) return quality; } - return candidates[candidates.length - 1] ?? '凡品'; -} - -function requestList(skeletons: SpiritSeedSkeleton[]): string { return skeletons.map((item, index) => `${index + 1}. 品质=${item.rank};元素=${item.forcedElement ?? '自选'};地域=${item.regionTags?.join('、') || '无'}`).join('\n'); } -function fallbackIdentity(skeleton: SpiritSeedSkeleton, index: number): SpiritSeedIdentity { - const element = skeleton.forcedElement ?? ELEMENT_VALUES[index % ELEMENT_VALUES.length] ?? '木'; - return { - seedName: `${element}纹眠籽`, - seedDescription: `灰青种壳上浮着一线${element}行微光,握在掌中时灵机时隐时现。`, - clueTexts: ['种壳遇到温和灵机时会轻轻发热', '其内生机不喜骤然催逼,似宜循序培护'], - element, growthForm: 'herb', harvestPart: 'leaf', - preferredMethods: ['seasonal_nurture', 'intrinsic_infusion', 'natural_form'], - avoidedMethods: ['monster_blood'], preferredHabitats: ['mountain'], avoidedHabitats: [], - growthTraits: ['qi-sensitive'], useTags: ['alchemy'], outcomeBiases: ['herb'], - creationTags: [CreationTags.MATERIAL.SEMANTIC_WOOD, CreationTags.MATERIAL.SEMANTIC_ALCHEMY], - }; -} - -function normalizeIdentityClues( - identity: SpiritSeedIdentity, - fallback: SpiritSeedIdentity, -): SpiritSeedIdentity { - const hiddenTokens = [ - ...identity.preferredMethods, - ...identity.avoidedMethods, - ...identity.preferredHabitats, - ...identity.avoidedHabitats, - ...identity.growthTraits, - ...identity.useTags, - ...identity.outcomeBiases, - ...identity.creationTags, - ]; - const clueTexts = identity.clueTexts.filter( - (clue) => - !hiddenTokens.some((token) => clue.includes(token)) && - !/偏好|忌讳|内部(?:规则|标签)|产物倾向|概率|分数|评分/.test(clue), - ); - const avoidedMethods = identity.avoidedMethods.filter( - (method) => !identity.preferredMethods.includes(method), - ); - const seedDescription = - hiddenTokens.some((token) => identity.seedDescription.includes(token)) || - /偏好|忌讳|内部(?:规则|标签)|产物倾向|概率|分数|评分/.test( - identity.seedDescription, - ) - ? fallback.seedDescription - : identity.seedDescription; - return { - ...identity, - seedDescription, - clueTexts: clueTexts.length >= 2 ? clueTexts.slice(0, 3) : fallback.clueTexts, - avoidedMethods, - }; -} - -export class SpiritSeedGenerator { - static generateRandomSkeletons(count: number, options: SpiritSeedRandomOptions = {}, rng: () => number = Math.random): SpiritSeedSkeleton[] { return Array.from({ length: Math.max(0, Math.floor(count)) }, () => ({ rank: pickWeightedQuality(options, rng), quantity: 1, forcedElement: options.specifiedElement, regionTags: options.regionTags?.slice(0, 8) })); } - static async generateRandom(count: number, options: SpiritSeedRandomOptions = {}): Promise>> { return this.generateFromSkeletons(this.generateRandomSkeletons(count, options)); } - static async generateBatches(batches: readonly SpiritSeedBatchSpec[]): Promise>> { return this.generateFromSkeletons(batches.map((batch) => ({ rank: batch.rank, quantity: Math.max(1, Math.floor(batch.quantity)), forcedElement: batch.element, regionTags: batch.regionTags?.slice(0, 8) }))); } - static async generateFromSkeletons(skeletons: SpiritSeedSkeleton[]): Promise>> { - if (skeletons.length === 0) return []; - let identities: SpiritSeedIdentity[]; - try { - const response = await generateAiArray({ system: renderPromptSystem('spirit-seed-generation'), prompt: renderPromptUser('spirit-seed-generation', { requestList: requestList(skeletons) }), elementSchema: SpiritSeedAISchema, name: 'SpiritSeedIdentityList', description: '灵种身份、隐性习性与玩家线索', sceneId: 'spirit-seed-generation', maxOutputTokens: Math.min(8_000, Math.max(1_600, skeletons.length * 1_000)) }); - identities = skeletons.map((skeleton, index) => { - const fallback = fallbackIdentity(skeleton, index); - const identity = { - ...(response.output[index] ?? fallback), - element: - skeleton.forcedElement ?? response.output[index]?.element ?? fallback.element, - }; - return normalizeIdentityClues(identity, fallback); - }); - } catch (error) { console.error('[spirit-seed-generation] fallback', error); identities = skeletons.map(fallbackIdentity); } - return skeletons.map((skeleton, index) => { - const identity = identities[index] ?? fallbackIdentity(skeleton, index); - const balance = getSpiritFieldQualityBalance(skeleton.rank); - const plant: SpiritFieldPlantSnapshot = { id: globalThis.crypto.randomUUID(), ...identity, quality: skeleton.rank, minRealm: balance.minRealm, stageDurationMs: balance.stageDurationMs, baseYieldMin: balance.baseYield[0], baseYieldMax: balance.baseYield[1] }; - return buildSpiritFieldSeedMaterialFromPlant(plant, skeleton.quantity); - }); - } -} diff --git a/src/tsconfig.json b/src/tsconfig.json deleted file mode 100644 index 29a073e6..00000000 --- a/src/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "../tsconfig.app.json", - "compilerOptions": { - "tsBuildInfoFile": "../node_modules/.tmp/tsconfig.src-tests.tsbuildinfo", - "types": ["vite/client", "node", "vitest/globals"] - }, - "include": [ - "../vitest.config.ts", - "./**/*.test.ts", - "./**/*.test.tsx", - "./**/tests/**/*.ts", - "./**/tests/**/*.tsx" - ], - "exclude": [] -} diff --git a/tsconfig.app.json b/tsconfig.app.json deleted file mode 100644 index eb8ab714..00000000 --- a/tsconfig.app.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", - "target": "ES2022", - "useDefineForClassFields": true, - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "isolatedModules": true, - "moduleDetection": "force", - "noEmit": true, - "jsx": "react-jsx", - "resolveJsonModule": true, - "types": ["vite/client", "node"], - - /* Linting */ - "strict": true, - "noUnusedLocals": false, - "noUnusedParameters": false, - "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true, - "paths": { - "@app/*": ["./src/react-app/*"], - "@server/*": ["./src/server/*"], - "@shared/*": ["./src/shared/*"] - } - }, - "include": ["./src/react-app", "./src/shared"], - "exclude": ["**/*.test.ts", "**/*.test.tsx"] -} diff --git a/tsconfig.json b/tsconfig.json index fb765860..e30ce0c1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,14 +1,21 @@ { "compilerOptions": { "paths": { - "@app/*": ["./src/react-app/*"], - "@server/*": ["./src/server/*"], - "@shared/*": ["./src/shared/*"] + "@app/*": ["./apps/web/src/*"], + "@server/*": ["./apps/api/src/*"], + "@daoyou/shared/*": ["./packages/shared/src/*"] } }, "files": [], "references": [ - { "path": "./tsconfig.app.json" }, - { "path": "./tsconfig.node.json" } + { + "path": "./apps/web/tsconfig.json" + }, + { + "path": "./apps/api/tsconfig.json" + }, + { + "path": "./tsconfig.node.json" + } ] } diff --git a/tsconfig.node.json b/tsconfig.node.json index 8dd25d8c..5db5712f 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -1,37 +1,20 @@ { + "extends": "./tsconfig.base.json", "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", - "target": "ES2022", - "lib": ["ES2023"], - "module": "ESNext", - "skipLibCheck": true, - "moduleResolution": "bundler", - "resolveJsonModule": true, - "allowImportingTsExtensions": true, - "isolatedModules": true, - "moduleDetection": "force", + "tsBuildInfoFile": "./node_modules/.tmp/tools.tsbuildinfo", "noEmit": true, - - /* Linting */ - "strict": true, - "noUnusedLocals": false, - "noUnusedParameters": false, - "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true, - "types": ["bun", "node", "vite/client", "vitest/globals"], + "types": ["node"], "paths": { - "@app/*": ["./src/react-app/*"], - "@server/*": ["./src/server/*"], - "@shared/*": ["./src/shared/*"] + "@server/*": ["./apps/api/src/*"], + "@daoyou/shared/*": ["./packages/shared/src/*"] } }, "include": [ - "vite*.ts", + "apps/web/vite.config.ts", "vitest.config.ts", - "drizzle.auth.config.ts", - "./src/index.ts", - "./src/server", - "./scripts" + "drizzle*.config.ts", + "scripts/**/*.ts", + "apps/api/scripts/**/*.ts" ], - "files": ["./src/server/types/markdown.d.ts"] + "files": ["./apps/api/src/types/markdown.d.ts"] } diff --git a/turbo.json b/turbo.json new file mode 100644 index 00000000..f5fc90d1 --- /dev/null +++ b/turbo.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://turborepo.com/schema.json", + "globalDependencies": ["tsconfig.base.json"], + "tasks": { + "build": { + "dependsOn": ["^build"], + "outputs": ["dist/**"], + "env": ["NODE_ENV", "VITE_*", "CF_PAGES_COMMIT_SHA", "GITHUB_SHA"] + }, + "typecheck": { + "dependsOn": ["^typecheck"], + "outputs": [] + }, + "dev": { + "cache": false, + "persistent": true + }, + "prd": { + "cache": false, + "persistent": true + } + } +} diff --git a/vite.server.config.ts b/vite.server.config.ts deleted file mode 100644 index f635fcee..00000000 --- a/vite.server.config.ts +++ /dev/null @@ -1,14 +0,0 @@ -import build from '@hono/vite-build/bun'; -import { fileURLToPath } from 'node:url'; -import { defineConfig } from 'vite'; - -export default defineConfig({ - resolve: { - alias: { - '@app': fileURLToPath(new URL('./src/react-app', import.meta.url)), - '@server': fileURLToPath(new URL('./src/server', import.meta.url)), - '@shared': fileURLToPath(new URL('./src/shared', import.meta.url)), - }, - }, - plugins: [build({ entry: './src/index.ts', emptyOutDir: true })], -}); diff --git a/vitest.config.ts b/vitest.config.ts index 32cf130c..cdf9367e 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -2,22 +2,22 @@ import { fileURLToPath } from 'node:url'; import { defineConfig } from 'vitest/config'; const alias = { - '@app': fileURLToPath(new URL('./src/react-app', import.meta.url)), - '@server': fileURLToPath(new URL('./src/server', import.meta.url)), - '@shared': fileURLToPath(new URL('./src/shared', import.meta.url)), + '@daoyou/shared': fileURLToPath( + new URL('./packages/shared/src', import.meta.url), + ), }; export default defineConfig({ resolve: { alias }, test: { environment: 'node', - exclude: ['dist/**', 'node_modules/**'], + exclude: ['**/dist/**', 'node_modules/**'], globals: true, include: [ - 'src/shared/**/*.test.ts', - 'src/shared/**/*.spec.ts', - 'src/shared/**/*.test.tsx', - 'src/shared/**/*.spec.tsx', + 'packages/shared/src/**/*.test.ts', + 'packages/shared/src/**/*.spec.ts', + 'packages/shared/src/**/*.test.tsx', + 'packages/shared/src/**/*.spec.tsx', ], restoreMocks: true, },