fix(wire): round-trip the core error taxonomy over the wire#82
Merged
Conversation
APIAdapter collapsed StackPermissionError/StackNotFoundError/
StackConflictError/StackValidationError to a generic APIAdapterError with
just a status code, so app-level `instanceof StackPermissionError` handling
worked against adapter-local but silently broke against adapter-api.
- wire-types: define the WireError body ({ error: { code, message,
details? } }), a code<->status table, and serializeError/deserializeError/
errorForStatus helpers shared by servers and APIAdapter.
- core: give each Stack*Error class a static `code` so serialization is
mechanical; add StackQueryError (code bad_request) for structurally
malformed input like an undecodable pagination cursor, distinct from
StackValidationError's content-schema failures.
- adapter-api: reconstruct the typed error from the wire body, falling back
to status-based reconstruction for foreign/legacy servers, then to a
generic APIAdapterError. 500 is deliberately excluded from status
fallback so an unrelated server crash isn't misclassified as
StackMigrationError.
- record-adapter-sqljs: cursor decode now throws StackQueryError (was a
generic Error) for an unknown sort field, non-numeric value, or malformed
blob, so a server can map it to 400 instead of a bare 500.
- conformance-fixtures: pin the wire error body for 403/404/409/422/400 so
APIAdapter and any server implementation can't drift on the mapping.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5YpGYNzdRhuvgQo1g5ZHx
request()'s error-reconstruction now imports isWireError/deserializeError/ errorForStatus as values (not just types) from @haverstack/wire-types. CI's test job runs before any build step, so wire-types' dist/ doesn't exist yet and vite fails to resolve the package entry. @haverstack/core and @haverstack/conformance-fixtures already had this alias for the same reason; wire-types just needed the same treatment. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S5YpGYNzdRhuvgQo1g5ZHx
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.
Summary
Closes #53.
APIAdaptercollapsedStackPermissionError/StackNotFoundError/StackConflictError/StackValidationErrordown to a genericAPIAdapterErrorwith just an HTTP status code. App code that doescatch (e) { if (e instanceof StackPermissionError) ... }worked againstadapter-localand silently fell through tothrowagainstadapter-api— breaking the "swap backends without changing your app" promise, and specifically undermining #48's opt-in conflict handling and #51's 403-vs-404 anti-oracle distinction.wire-types: newWireErrorbody ({ error: { code, message, details? } }),WireErrorCodeunion, a code↔status table, andserializeError/deserializeError/errorForStatushelpers so servers andAPIAdaptershare one mapping instead of hand-maintained switches.core: eachStack*Errorclass now exposes a staticcode. AddedStackQueryError(codebad_request) for structurally malformed input — distinct fromStackValidationError's content-schema failures.adapter-api:request()/requestBinary()/uploadBinary()reconstruct the typed core error from the response body (codeis authoritative), falling back to status-based reconstruction for foreign/legacy servers, then to a genericAPIAdapterError. Status 500 is deliberately excluded from the status-only fallback so an unrelated server crash isn't misclassified asStackMigrationError.record-adapter-sqljs: cursor decode now throwsStackQueryError(was a bareError) for an unknown sort field, non-numeric value, or malformed/undecodable blob, so a server can map it to 400 instead of a bare 500. Also fixes a latent bug where a cursor with no|separator leftcursorIdsilentlyundefinedinstead of erroring.conformance-fixtures: new fixtures pinning the wire error body for 403/404/409/422/400, driven end-to-end againstAPIAdapterin tests — this is the enforcement mechanism APIAdapter collapses the error taxonomy: Stack* errors never survive the wire round trip #53 asked for so the mapping can't drift silently again.docs/spec.md: documents the wire error body contract, why 500/StackMigrationErroris reserved-but-never-inferred, and the cursor 400 behavior.Test plan
pnpm -r build— all packages compilepnpm -r typecheck— cleanpnpm -r lint— cleanpnpm format:check— cleanpnpm -r test— all suites pass (core 264, record-adapter-sqljs 79, adapter-api 83, adapter-local 17, blob-adapter-disk 12)https://claude.ai/code/session_01S5YpGYNzdRhuvgQo1g5ZHx
Generated by Claude Code