Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,7 @@
"release": "npm version patch && npm publish && git push --follow-tags"
},
"dependencies": {
"@convex-dev/rate-limiter": "^0.3.0",
"@convex-dev/sharded-counter": "^0.2.0",
"@convex-dev/aggregate": "^0.2.0",
"@convex-dev/crons": "^0.2.0"
"@convex-dev/sharded-counter": "^0.2.0"
},
"peerDependencies": {
"convex": "^1.33.0"
Expand Down Expand Up @@ -89,7 +86,6 @@
"convex",
"api-keys",
"authentication",
"rate-limiting",
"multi-tenant",
"convex-component"
]
Expand Down
55 changes: 0 additions & 55 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions specs/history.log
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
2026-03-25 | shipped | v0.2.0-production-hardening | 14h→4h | ~4h | Auth boundary, server-side secret gen, remove events+rate-limiter, input validation, pagination. 82 tests, PR #37.
2026-03-27 | shipped | ts2589-type-depth | 1h→0.5h | ~20m | Remove 3 stale sub-component refs from _generated/api.ts + phantom deps from package.json. Fixes #38.
127 changes: 127 additions & 0 deletions specs/shipped/2026-03-27-ts2589-type-depth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
---
title: "Fix TS2589: component types too deep"
status: shipped
created: 2026-03-27
estimate: 1h
tier: mini
---

# Fix TS2589: component types too deep

## Context

Registering `@vllnt/convex-api-keys` as a Convex component triggers TS2589 (type instantiation excessively deep) in the host app's generated `api.d.ts`. Root cause: `src/component/_generated/api.ts` was not regenerated after v0.2.0 removed 3 sub-components. It still declares `ComponentApi` imports for `@convex-dev/rate-limiter`, `@convex-dev/aggregate`, and `@convex-dev/crons` — none of which are used. `package.json` still lists all 3 as `dependencies`, pulling their type definitions into consumers' `node_modules`.

## Codebase Impact

| Area | Impact | Detail |
|------|--------|--------|
| `src/component/_generated/api.ts:52-57` | MODIFY | Remove 3 stale `ComponentApi` imports — keep only `shardedCounter` |
| `package.json:53-56` | MODIFY | Remove `@convex-dev/rate-limiter`, `@convex-dev/aggregate`, `@convex-dev/crons` from `dependencies` |
| `package.json:4` | MODIFY | Bump version (patch or prerelease) |
| `pnpm-lock.yaml` | AFFECTED | Shrinks after removing 3 deps |
| `src/component/convex.config.ts` | AFFECTED | Already correct — only `shardedCounter` — no change needed |
| `src/component/_generated/component.ts` | AFFECTED | Already correct — no change needed |
| `src/component/mutations.ts` | AFFECTED | Only imports `shardedCounter` — no change needed |
| `src/component/queries.ts` | AFFECTED | Only imports `shardedCounter` — no change needed |
| `CLAUDE.md` | AFFECTED | Already says "Single child component: @convex-dev/sharded-counter" — correct |
| `README.md` | MODIFY | Remove mentions of rate-limiter/aggregate/crons if present |
| `package.json:88` (keywords) | MODIFY | Remove `"rate-limiting"` keyword — no longer a feature |

**Files:** 0 create | 3 modify | 5 affected
**Reuse:** `pnpm build:codegen` regenerates `_generated/api.ts` correctly from current `convex.config.ts`
**Breaking changes:** None — removing phantom types that were never accessible at runtime
**New dependencies:** None — removing 3

## User Journey

1. Developer installs `@vllnt/convex-api-keys` and adds `app.use(apiKeys)` in `convex.config.ts`
-> Convex codegen generates host app `api.d.ts` with component types
-> Host app typechecks cleanly (no TS2589)

2. Developer uses `action()` / `query()` / `mutation()` from `_generated/server`
-> TypeScript resolves through `FilterApi<...>` recursion without exceeding depth limit
-> IntelliSense works normally

Error: Developer has cached `node_modules` from older version
-> `pnpm install` pulls only `@convex-dev/sharded-counter` (not 3 phantom deps)
-> Clean install, no stale types

## Acceptance Criteria

### Must Have (BLOCKING)

- [ ] AC-1: GIVEN a host app with `app.use(apiKeys)` WHEN running `npx convex dev` and `tsc --noEmit` THEN typecheck passes without TS2589
- [ ] AC-2: GIVEN `src/component/_generated/api.ts` WHEN inspecting `components` export THEN only `shardedCounter` is declared
- [ ] AC-3: GIVEN `package.json` WHEN inspecting `dependencies` THEN only `@convex-dev/sharded-counter` is listed

### Error Criteria (BLOCKING)

- [ ] AC-E1: GIVEN existing tests WHEN running `pnpm test` THEN all tests pass (no regressions from dep removal)

### Should Have

- [ ] AC-4: GIVEN `package.json` keywords WHEN inspecting THEN `"rate-limiting"` is removed (no longer a feature)

## Scope

- [ ] 1. Regenerate `_generated/api.ts` via `pnpm build:codegen` -> AC-1, AC-2
- [ ] 2. Remove 3 phantom deps from `package.json` dependencies -> AC-3
- [ ] 3. Remove stale keyword from `package.json` -> AC-4
- [ ] 4. Run `pnpm install` to update lockfile -> AC-3
- [ ] 5. Verify existing tests pass -> AC-E1
- [ ] 6. Check README.md for stale sub-component references -> AC-1

### Out of Scope

- Version bump (separate release step)
- Host app integration test (manual verification by maintainer)
- Example app `_generated/` cleanup (separate concern, uses `AnyApi` escape hatch)

## Quality Checklist

### Blocking

- [ ] All Must Have ACs passing
- [ ] All Error Criteria ACs passing
- [ ] All scope items implemented
- [ ] No regressions in existing tests
- [ ] `pnpm build:codegen` succeeds
- [ ] `pnpm typecheck` passes
- [ ] `pnpm lint` passes

### Advisory

- [ ] `pnpm-lock.yaml` shrinks (fewer transitive deps)
- [ ] Code follows existing project patterns

## Test Strategy

Runner: vitest 3.0.0 (edge-runtime) | E2E: none (backend component library) | TDD: verify existing tests pass
AC-1 -> manual: `pnpm build:codegen && pnpm typecheck` | AC-2 -> inspect file | AC-3 -> inspect package.json | AC-E1 -> `pnpm test`
Mocks: none (convex-test provides in-memory DB)

## Analysis

**Assumptions:** Stale `_generated/api.ts` is sole cause -> VALID (grep confirms only file referencing removed packages; `convex.config.ts` and `component.ts` already correct) | `pnpm build:codegen` regenerates correctly -> VALID (script runs `convex codegen --component-dir ./src/component` which reads current `convex.config.ts`) | No source code uses removed packages -> VALID (grep shows zero imports outside `_generated/api.ts`)
**Blind Spots:** [ops] Published npm tarball includes `src/` — consumers may resolve stale `_generated/api.ts` from cache until new version published. Impact: fix only takes effect after publish.
**Failure Hypothesis:** IF `pnpm build:codegen` also requires the removed packages installed to generate THEN codegen fails BECAUSE convex CLI reads `node_modules` for sub-component resolution -> LOW severity, mitigate by running codegen before removing deps.
**The Real Question:** Confirmed — spec solves the right problem. The TS2589 is caused by stale generated types declaring 4 sub-components when only 1 exists. Removing the phantom declarations directly addresses the recursive `FilterApi` depth explosion.
**Open Items:** [question] Should we run codegen before or after removing deps from package.json? -> explore (test both orders)

## Notes

### Ship Retro (2026-03-27)
**Estimate vs Actual:** 1h -> 0.5h (200% accuracy)
**What worked:** Root cause was immediately clear from diffing `convex.config.ts` (1 sub-component) vs `_generated/api.ts` (4 sub-components). Parallel discovery agents accelerated context gathering.
**What didn't:** `pnpm build:codegen` requires a Convex deployment, so had to manually edit the generated file instead. Not a problem — the edit was trivial.
**Next time:** For generated file fixes, check if codegen can run locally before planning it as the primary approach.

## Timeline

| Action | Timestamp | Duration | Notes |
|--------|-----------|----------|-------|
| plan | 2026-03-27T00:00:00Z | - | Created |
| ship | 2026-03-27T00:10:00Z | 10m | Implemented |
| review | 2026-03-27T00:15:00Z | 5m | CLEAN |
| done | 2026-03-27T00:20:00Z | 20m | Shipped |
3 changes: 0 additions & 3 deletions src/component/_generated/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,5 @@ export const internal: FilterApi<
> = anyApi as any;

export const components = componentsGeneric() as unknown as {
rateLimiter: import("@convex-dev/rate-limiter/_generated/component.js").ComponentApi<"rateLimiter">;
shardedCounter: import("@convex-dev/sharded-counter/_generated/component.js").ComponentApi<"shardedCounter">;
usageAggregate: import("@convex-dev/aggregate/_generated/component.js").ComponentApi<"usageAggregate">;
crons: import("@convex-dev/crons/_generated/component.js").ComponentApi<"crons">;
};
Loading