docs: resolve protect-ffi's real types so the Stack reference stops saying any - #77
Merged
Conversation
…aying `any`
The generated Stack API reference documents 13 types as `any` and gives
AuthStrategy the wrong description entirely. Both come from one missing
compiler option.
@cipherstash/protect-ffi's `exports` map routes the `node` condition to
lib/index.d.cts (the real type surface) and everything else to
`default: ./dist/wasm/protect_ffi.js`, which ships no `types`. TypeScript
therefore falls through to the sibling dist/wasm/protect_ffi.d.ts — raw
wasm-bindgen output — where none of the hand-written types exist.
`moduleResolution: "bundler"` does not imply the `node` condition;
packages/stack/tsconfig.json adds `customConditions: ["node"]`, which is why
the stack team never sees this. But typedoc.tsconfig.json extends stack's ROOT
tsconfig, which does not set it. Set it on the generated config.
Confirmed with a minimal repro against protect-ffi 0.30.0 (the version stack
1.0.0-rc.4 pins exactly): `moduleResolution: bundler` alone yields
"has no exported member 'ProtectError'. Did you mean 'encryptQuery'?"; adding
customConditions typechecks clean.
Also turns error checking back on. It was disabled to tolerate this, on the
theory that the references were "unresolved even though the source is correct"
and TypeDoc would still emit accurate signatures. It does not — an unresolved
import becomes `any` in the output, so the workaround converted a loud failure
into a silently wrong reference. With the condition fixed the surface
typechecks with 0 errors, so the next resolution break fails the build instead.
Generating with and without the fix, back to back against the same stack tag,
changes 23 pages. Representative:
EncryptionError.code any -> ProtectErrorCode
EncryptQueryOperation.execute() Promise<Result<any, ...>>
-> Promise<Result<EncryptedQueryResult, ...>>
encryptQuery plaintext param any -> Plaintext | null | undefined
AuthStrategy description the module's blurb -> the type's own docs
Claude-Session: https://claude.ai/code/session_01NkuQNMvw9BpB4BWWeKtfV8
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This was referenced Jul 28, 2026
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.
Found while debugging why #76 fails CI on
main. The build failure turned out to be the visible half of a bug that is also silently degrading the published Stack reference onv2.The bug
@cipherstash/protect-ffi'sexports["."]is conditional, and the fallback branch has notypes:{ "node": { "import": { "types": "./lib/index.d.mts", "default": "./lib/index.mjs" }, "require": { "types": "./lib/index.d.cts", "default": "./lib/index.cjs" } }, "default": "./dist/wasm/protect_ffi.js" }moduleResolution: "bundler"does not imply thenodecondition — you needcustomConditions: ["node"].packages/stack/tsconfig.jsonsets exactly that, which is why the stack team never hits this. Buttypedoc.tsconfig.jsonextends stack's root tsconfig, which doesn't. So TypeScript misses thenodebranch, falls todefault, finds notypes, and resolves the siblingdist/wasm/protect_ffi.d.ts— raw wasm-bindgen output, where none of the hand-written types exist.Hence the giveaway in the error text: "has no exported member
EncryptedV3Query. Did you meanencryptQuery?"Minimal repro against protect-ffi 0.30.0 — the version stack
1.0.0-rc.4pins exactly, so this is not resolution drift:moduleResolution: bundlerProtectError,EncryptedV3QuerycustomConditions: ["node"]Why this matters beyond the build
On
v2the errors were hidden byskipErrorChecking: true, whose comment said the references were "unresolved even though the source is correct" and that "TypeDoc still emits accurate signatures from the source AST".That last part isn't true. An unresolved import doesn't just warn — TypeDoc emits
any. So the workaround converted a loud build failure into a silently wrong reference. Generating with and without the fix back-to-back against the same stack tag changes 23 pages, including 13anys:EncryptionError.codeanyProtectErrorCodeEncryptQueryOperation.execute()Promise<Result<any, …>>Promise<Result<EncryptedQueryResult, …>>encryptQueryplaintext paramanyPlaintext | null | undefinedAuthStrategydescriptionSo this is a docs-accuracy fix first and a build fix second.
Changes
customConditions: ["node"]on the generated typedoc tsconfig — the actual fix.skipErrorCheckingback tofalse. With the condition fixed the whole surface typechecks with 0 errors (down from 36), so the next resolution break fails the build instead of quietly publishingany.Verified
bun run generate-docs→ exit 0,Found 0 errors and 7 warnings, noerror TSlines.biome checkclean. The 23-page diff was produced by two back-to-back runs against the same stack tag, so it is attributable to this change and not to upstream movement.Follow-ups, not in this PR
main. ci: open a PR when a new EQL patch release lands #76's red CI is pre-existing: pristineorigin/mainfails the same way (I checked it out clean and reproduced), because it never gotskipErrorChecking: trueand has no fix for the underlying cause. PR Docs V2: new information architecture (CIP-3307) #37 (v2 → main) will hit it the moment it leaves draft.main's lockfile is stale too —bun install --frozen-lockfilefails there.@cipherstash/stack@tag, unpinned (tagFilterinscripts/generate-docs.ts). That is precisely the hazard we pin EQL against: a stack release can redden the docs build with no commit here. Worth pinning the same way, now that docs: add an EQL pin resolver so patch bumps stop relying on memory #75 gives us the machinery.exportsmap is byte-identical, so upgrading does not fix the above — and it dropsProtectErrorandQueryOpNamefromlib/index.d.cts.packages/stack/src/dynamodb/helpers.tsimportsProtectErroras a value, andencryption/helpers/infer-index-type.tsimportsQueryOpName. Looks like an unintended gap in the re-export barrel rather than a deliberate removal.defaultcondition atypesentry, so no consumer needscustomConditionsto see the package's own types.https://claude.ai/code/session_01NkuQNMvw9BpB4BWWeKtfV8