feat: implement #421 debounce search, #422 /v1 prefix, #423 contract …#464
Open
Joyyyb wants to merge 1 commit into
Open
feat: implement #421 debounce search, #422 /v1 prefix, #423 contract …#464Joyyyb wants to merge 1 commit into
Joyyyb wants to merge 1 commit into
Conversation
…/v1 prefix, Vatix-Protocol#423 contract test, Vatix-Protocol#424 load test ## Summary Implements four GitHub issues in a single coordinated change across the NestJS API and Next.js web app. --- ### Vatix-Protocol#421 — Debounce search API calls from the web app **Problem:** Every keystroke in the pool search box fired a new API request immediately, causing unnecessary load and race-condition risk when responses arrive out of order. **Implementation:** - Created `apps/web/hooks/useSearchDebounce.ts` — a reusable React hook (`useSearchDebounce(value, delay = 300)`) that wraps `useState` + `useEffect` with a `setTimeout` / `clearTimeout` pattern. The debounced value only updates after the user has paused typing for the configured delay (default 300 ms). - Replaced the ad-hoc inline debounce in `apps/web/app/pools/page.tsx` (which used a `useRef` + manual `setTimeout`) with a single call to `useSearchDebounce(search)`. This removes ~8 lines of repeated logic and makes the page's intent clearer. - The `debouncedSearch` value is still the one forwarded to `usePools()`, so no API call is made until the user stops typing. --- ### Vatix-Protocol#422 — Version all public REST routes under /v1 **Problem:** All routes were served at the root (e.g. `GET /pools`). Without a version prefix there is no non-breaking path to evolve the API contract in the future. **Implementation:** - Added `app.setGlobalPrefix('v1', { exclude: ['health', 'docs', 'docs-json', '/'] })` in `apps/api/src/main.ts`, immediately after `enableShutdownHooks()`. NestJS applies the prefix to every registered controller route automatically. The exclusion list keeps `GET /` (health check), `/health`, and Swagger docs accessible at their existing paths without a prefix. - Updated `apps/web/lib/constants.ts`: `API_BASE` now evaluates to `${NEXT_PUBLIC_API_URL ?? 'http://localhost:3001'}/v1`. Because every hook in `apps/web/hooks/` and every page component derives its fetch URL from `API_BASE`, this single-line change routes all 15+ fetch calls to the versioned prefix without touching each file individually. - Updated `apps/api/src/app.smoke.spec.ts` to register the same global prefix in its test application bootstrap and changed all route assertions from `/pools`, `/swaps`, etc. to `/v1/pools`, `/v1/swaps`, etc. so the smoke suite continues to pass. --- ### Vatix-Protocol#423 — Contract test for GET /v1/pools list schema **Problem:** There was no test that asserted the exact JSON shape returned by the pools list endpoint. A future refactor could silently drop a field (e.g. `feeApr`) and nothing would catch it. **Implementation:** - Created `apps/api/src/pools/pools.contract.spec.ts` — a Jest suite that boots the full NestJS `AppModule` with the same stub strategy used by `app.smoke.spec.ts` (Prisma, Redis, and BullMQ replaced with lightweight jest mocks so no live database is required). - The spec registers the `/v1` global prefix in the test app and then makes four assertions via supertest: 1. `GET /v1/pools` returns 200 with the envelope shape `{ items, page, limit, total, totalPages, orderBy }`. 2. Every object in `items` contains all required `PoolListItem` fields: `id`, `token0`, `token1`, `feeTier`, `tvl`, `volume24h`, `feeApr`, `currentPrice`. 3. The `?search=` query param is accepted without breaking the schema. 4. When no pools match, `items` is an empty array (not `null`) and `total` is 0. --- ### Vatix-Protocol#424 — Load test script for GET /v1/pools at scale **Problem:** There was no automated way to measure throughput or tail latency of the pools list endpoint under concurrent load. **Implementation:** - Created `apps/api/scripts/load-test-pools.js` — a self-contained Node.js 18+ script (zero external dependencies; uses native `fetch` and `performance.now()`). - Accepts three optional positional arguments: concurrency (default 10), total requests (default 100), and base URL (default `http://localhost:3001`). - Implements a bounded-concurrency dispatcher: up to N requests are in-flight at once; as each completes a new one is dispatched until the total is reached. - Prints p50 / p95 / p99 latencies, min/max, total elapsed time, throughput (req/s), and a per-status-code error breakdown. - Exits with code 1 if any request fails so it can be wired into CI or a performance gate. --- Closes Vatix-Protocol#421 Closes Vatix-Protocol#422 Closes Vatix-Protocol#423 Closes Vatix-Protocol#424
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
…test, #424 load test
Summary
Implements four GitHub issues in a single coordinated change across the NestJS API and Next.js web app.
closes #421 — Debounce search API calls from the web app
Problem: Every keystroke in the pool search box fired a new API request immediately, causing unnecessary load and race-condition risk when responses arrive out of order.
Implementation:
apps/web/hooks/useSearchDebounce.ts— a reusable React hook (useSearchDebounce(value, delay = 300)) that wrapsuseState+useEffectwith asetTimeout/clearTimeoutpattern. The debounced value only updates after the user has paused typing for the configured delay (default 300 ms).apps/web/app/pools/page.tsx(which used auseRef+ manualsetTimeout) with a single call touseSearchDebounce(search). This removes ~8 lines of repeated logic and makes the page's intent clearer.debouncedSearchvalue is still the one forwarded tousePools(), so no API call is made until the user stops typing.closes #422 — Version all public REST routes under /v1
Problem: All routes were served at the root (e.g.
GET /pools). Without a version prefix there is no non-breaking path to evolve the API contract in the future.Implementation:
app.setGlobalPrefix('v1', { exclude: ['health', 'docs', 'docs-json', '/'] })inapps/api/src/main.ts, immediately afterenableShutdownHooks(). NestJS applies the prefix to every registered controller route automatically. The exclusion list keepsGET /(health check),/health, and Swagger docs accessible at their existing paths without a prefix.apps/web/lib/constants.ts:API_BASEnow evaluates to${NEXT_PUBLIC_API_URL ?? 'http://localhost:3001'}/v1. Because every hook inapps/web/hooks/and every page component derives its fetch URL fromAPI_BASE, this single-line change routes all 15+ fetch calls to the versioned prefix without touching each file individually.apps/api/src/app.smoke.spec.tsto register the same global prefix in its test application bootstrap and changed all route assertions from/pools,/swaps, etc. to/v1/pools,/v1/swaps, etc. so the smoke suite continues to pass.closes #423 — Contract test for GET /v1/pools list schema
Problem: There was no test that asserted the exact JSON shape returned by the pools list endpoint. A future refactor could silently drop a field (e.g.
feeApr) and nothing would catch it.Implementation:
apps/api/src/pools/pools.contract.spec.ts— a Jest suite that boots the full NestJSAppModulewith the same stub strategy used byapp.smoke.spec.ts(Prisma, Redis, and BullMQ replaced with lightweight jest mocks so no live database is required)./v1global prefix in the test app and then makes four assertions via supertest:GET /v1/poolsreturns 200 with the envelope shape{ items, page, limit, total, totalPages, orderBy }.itemscontains all requiredPoolListItemfields:id,token0,token1,feeTier,tvl,volume24h,feeApr,currentPrice.?search=query param is accepted without breaking the schema.itemsis an empty array (notnull) andtotalis 0.closes #424 — Load test script for GET /v1/pools at scale
Problem: There was no automated way to measure throughput or tail latency of the pools list endpoint under concurrent load.
Implementation:
apps/api/scripts/load-test-pools.js— a self-contained Node.js 18+ script (zero external dependencies; uses nativefetchandperformance.now()).http://localhost:3001).Closes #421
Closes #422
Closes #423
Closes #424
Summary
Related issue
Type of change
Checklist