Audit the TypeScript HTTP client generator against the Go server and Go client for cross-language consistency. Fix any inconsistencies in serialization, error handling, query parameters, headers, type mapping, and unwrap handling.

Purpose: The TS client is the second reference implementation. It must produce JSON request bodies and expect JSON response shapes that are semantically identical to what the Go server produces and the Go client expects. The TS client is JSON-only (no protobuf binary support), so the JSON path must be perfect. Unwrap behavior must be consistent across all generators per the user's locked decision.

Output: A TS client generator verified consistent with the Go server and Go client, with all inconsistencies fixed, including verified unwrap handling.

<execution_context> @/Users/sebastienmelki/.claude/get-shit-done/workflows/execute-plan.md @/Users/sebastienmelki/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/03-existing-client-review/03-CONTEXT.md @.planning/phases/03-existing-client-review/03-RESEARCH.md @.planning/phases/03-existing-client-review/03-01-SUMMARY.md @.planning/phases/03-existing-client-review/03-02-SUMMARY.md @internal/tsclientgen/generator.go @internal/tsclientgen/types.go @internal/tsclientgen/helpers.go @internal/tsclientgen/golden_test.go @internal/clientgen/generator.go @internal/httpgen/generator.go Task 1: Audit and fix TS client type mapping, int64 handling, query parameter encoding, and unwrap handling internal/tsclientgen/types.go internal/tsclientgen/generator.go internal/tsclientgen/golden_test.go internal/tsclientgen/testdata/golden/unwrap_client.ts Systematically audit the TS client for consistency with the Go server and Go client:

1. int64/uint64 type mapping (CRITICAL) Read mapProtoTypeToTS in types.go. The proto3 JSON spec requires int64/uint64 to serialize as JSON strings. Verify:

  • int64 fields map to string in TS interfaces (not number)
  • uint64 fields map to string in TS interfaces (not number)
  • sint64/sfixed64 fields also map to string if present
  • int32/uint32/sint32/fixed32/sfixed32 map to number (these are safe as JSON numbers)

If int64/uint64 are currently mapped to number, change them to string. This is a LOCKED DECISION from CONTEXT.md.

2. Query parameter encoding consistency Compare TS client query param generation with Go server's convertStringToFieldValue and Go client's fmt.Sprint:

  • string: TS sends String(req.field), server expects string. OK.
  • int32: TS sends String(req.field), server parses with strconv.ParseInt. Must send numeric string like "123". Verify String(123) produces "123". OK.
  • int64 (now string in TS): If int64 is typed as string in TS, then String(req.field) is a no-op on a string. But the VALUE should be a numeric string like "12345678901234" (no quotes in the query string). Verify the TS client sends the raw numeric string, not a JSON-quoted string. The value req.offset would be "12345678901234" (a TS string), and String("12345678901234") produces "12345678901234" which is correct.
  • uint64: Same as int64.
  • bool: TS uses if (req.active) which only sends when truthy. Go client uses if req.Active != false. Both omit false. Consistent.
  • float/double: TS sends String(req.field), which for a number like 1.5 produces "1.5". Server parses with strconv.ParseFloat. Compatible.

Check zero-value omission logic:

  • For int64 as string: current code may check req.offset !== "0". If int64 is now typed as string, the zero value check needs to compare against "0" (string zero), not 0 (number zero). Verify this is correct.
  • For int32 as number: check against !== 0. Correct.
  • For bool: if (req.field) omits false. Correct.
  • For string: req.field !== "" or similar. Verify.

3. Response body handling The TS client calls resp.json() to parse responses. For int64 fields, JSON.parse will produce JavaScript numbers for values that fit, but will LOSE PRECISION for values > 2^53. Since the server sends int64 as JSON strings (protojson behavior), resp.json() will parse them as strings. Verify the TS interfaces declare int64 response fields as string, matching what JSON.parse returns.

4. Empty response handling Research flagged that resp.json() throws on empty body (204 No Content). Check if the TS client handles empty responses. If the server currently returns 200 with {} for empty messages (which it does), this is not currently broken but could be fragile. At minimum, verify the code doesn't crash on empty responses. If there's a simple defensive fix (check resp.status === 204 before calling resp.json()), add it.

5. Enum serialization Verify TS client sends enum values as string names (matching protojson). The generateEnumType in types.go already generates string union types. Verify the generated client sends the string name, not a numeric value.

6. Unwrap handling consistency (CRITICAL -- locked decision) This is required by the user's locked decision: "Unwrap thoroughly verified: All unwrap variants must be verified across both clients and the server."

Audit the TS client's handling of ALL unwrap variants by comparing with the Go client (from 03-03 SUMMARY):

  • Map-value unwrap: When a message with [(sebuf.http.unwrap) = true] on a repeated field is used as a map value, the wrapper should be collapsed. The Go server returns {"bars": {"AAPL": [...], "GOOG": [...]}} (not {"bars": {"AAPL": {"bars": [...]}}}). Verify the TS client's generated types and response deserialization match this shape.
  • Root repeated unwrap: When a response message has a single repeated field with [(sebuf.http.unwrap) = true], the JSON response is a bare array [{...}, {...}] instead of {"users": [{...}, {...}]}. Verify the TS client deserializes this correctly.
  • Root map unwrap: When a response message has a single map field with [(sebuf.http.unwrap) = true], the JSON response is a bare object {"key1": {...}, "key2": {...}} instead of {"users": {"key1": {...}}}. Verify the TS client handles this.
  • Combined unwrap: Root map unwrap with value-level unwrap producing {"AAPL": [...], "GOOG": [...]}. Verify.

Read the Go client's unwrap golden file (internal/clientgen/testdata/golden/unwrap_client.pb.go) and the TS client's unwrap golden file (if it exists) or the complex_features golden file. Compare the response type shapes. If the TS client's generated types for unwrap responses don't match the Go client's expected shapes, fix the TS client generator.

Ensure the TS client golden test includes the unwrap.proto test proto. If there's no symlink for unwrap.proto in internal/tsclientgen/testdata/proto/, create one pointing to ../../../httpgen/testdata/proto/unwrap.proto and add it to the golden test list.

After all fixes, run make lint-fix. Run go build ./... to verify compilation. Run UPDATE_GOLDEN=1 go test ./internal/tsclientgen/ -count=1 to update golden files. Run go test ./internal/tsclientgen/ -count=1 to confirm pass. Inspect golden file diffs to verify:

  1. int64 fields are string type in TS interfaces
  2. Unwrap golden file shows correct response shapes for all 4 unwrap variants
  3. Unwrap response types in TS match the Go client's expected shapes
TS client type mapping is correct: int64/uint64 are `string` in TS interfaces. Query parameter encoding matches server expectations for all scalar types. Zero-value omission logic is consistent with Go client behavior. TS client handles all unwrap variants (map-value, root repeated, root map, combined) consistently with the Go client and server.
Task 2: Audit and fix TS client error handling, header handling, and generated JSDoc internal/tsclientgen/generator.go internal/tsclientgen/types.go Continue the TS client audit focusing on error handling, headers, and documentation:

1. Error handling consistency Read the TS client's error handling code. Compare with Go server's error response format:

  • ValidationError (400): Server sends ValidationError proto with violations array. Each violation has field_path, constraint_id, message. Verify TS client's ValidationError class has matching fields using protojson field names (camelCase: fieldPath, constraintId, message).
  • ApiError (other 4xx/5xx): Server sends Error proto with message field. Verify TS client's ApiError class captures statusCode and message. Verify the TS client reads the response body and parses it.
  • Error class structure: Compare the generated ValidationError and ApiError TypeScript classes with the actual proto definitions in proto/sebuf/http/. The field names in the TS classes must match the protojson names of the proto fields.

If the TS error classes don't match the proto definitions, fix them.

2. Header handling consistency Compare TS client header handling with Go client:

  • Service-level headers: Both clients should send these on every request. Verify TS generates a constructor option for each service header (e.g., apiKey for X-API-Key).
  • Method-level headers: Both clients should send these on specific method calls. Verify TS generates a per-call option for each method header.
  • Header name casing: HTTP headers are case-insensitive per spec, but verify both clients send the exact same header names as declared in the proto (e.g., X-API-Key, not x-api-key).
  • Required vs optional semantics: Verify both clients handle required/optional headers the same way.

3. Generated JSDoc accuracy Audit JSDoc comments in generated TS code:

  • Method descriptions should match proto RPC comments
  • Parameter types should match the actual TS types (especially int64 as string)
  • Return types should be accurate
  • Error descriptions should mention ValidationError and ApiError

4. Path parameter URL encoding Verify TS uses encodeURIComponent(String(req.field)) for path parameters. Compare with Go's url.PathEscape(fmt.Sprint(req.Field)). Both should produce RFC 3986 percent-encoding. They differ in details (encodeURIComponent encodes more characters than PathEscape), but both are safe for URL paths. Document any differences found.

After all fixes, run make lint-fix. Run UPDATE_GOLDEN=1 go test ./internal/tsclientgen/ -count=1 to update golden files. Run go test ./internal/tsclientgen/ -count=1 to confirm pass. Inspect golden files to verify error handling and header handling are correct. Run make lint-fix. TS client error handling matches server error format exactly (ValidationError fields, ApiError structure). Header handling is consistent with Go client (same names, same required/optional semantics). JSDoc accurately describes types, parameters, and errors. All tsclientgen tests pass.

1. `go test ./internal/tsclientgen/ -count=1` passes 2. `make build` succeeds 3. Golden files show int64/uint64 fields as `string` type in TS interfaces 4. Error classes match proto definitions (ValidationError with violations array, ApiError with statusCode + message) 5. Header handling matches between TS and Go clients 6. Unwrap golden file verifies all 4 unwrap variants (map-value, root repeated, root map, combined) produce correct TS response types matching Go client expectations

<success_criteria>

  • int64/uint64 are typed as string in generated TS interfaces
  • Query parameter encoding matches Go server parsing for all scalar types
  • Error handling produces the same error structure as Go client
  • Header names and semantics match between Go and TS clients
  • JSDoc is accurate for all generated methods and types
  • All unwrap variants are handled consistently with Go client and server (map-value, root repeated, root map, combined)
  • All tsclientgen golden file tests pass </success_criteria>
After completion, create `.planning/phases/03-existing-client-review/03-04-SUMMARY.md`