Skip to content

Split modules into model (core), codec, contract (client) and implementations (client-*); byte[] SPIs - #35

Merged
dfa1 merged 8 commits into
mainfrom
module-split
Oct 4, 2026
Merged

dfa1 merged 8 commits into
mainfrom
module-split

Conversation

@dfa1

@dfa1 dfa1 commented Oct 4, 2026 •

Copy link
Copy Markdown
Owner

Implements the layout agreed on #12, refined in review; recorded in ADR 0003.

Layout

Rule: a module's directory, artifact (typesafe-java-<module>) and single package (io.github.dfa1.typesafe.<module>, dashes as dots) share one name; no package spans two modules; a module named X-Y builds on module X.

Module (typesafe-java-*) Package (io.github.dfa1.typesafe.*) Contains Depends on
core core the model, as plain records —
codec (new) codec Codec SPI (was json.JsonCodec) —
codec-jackson2 / codec-jackson3 (were jackson2/jackson3) codec.jackson2 / codec.jackson3 Jackson codecs codec, core
client (new) client TypeSafeClient, TypeSafeException, Retrying/DeadlineTypeSafeClient, TokenCounter (from core) core
client-http (new) client.http DefaultTypeSafeClient, ApiKey, HttpTransport SPI (from core) client, codec
client-http-jdk / client-http-okhttp (were client-jdk/client-okhttp) client.http.jdk / client.http.okhttp transports client-http
client-local / client-mapping / client-testkit (were local/mapping/testkit) client.local / client.mapping / client.testkit as before client (+ codec for client-local)

Breaking

  • TypeSafeClient.builder() is gone (client can't see client-http): use DefaultTypeSafeClient.builder().
  • JsonCodec is now Codec, and it and HttpTransport speak byte[] (UTF-8 JSON for the shipped codecs), with no String copy on the success path; Builder.jsonCodec(...) is now codec(...): writeValueAsBytes/readValue(byte[], ...) (no pretty-printing variant; the CLI's --verbose prints compact JSON), and a byte[] body on post and HttpTransportResponse.
  • Every artifact except core/cli/bom is renamed, and every package except core/cli moves. There are no users yet, so no shims.

Found along the way

Jackson 2's UTF-8 writer escapes emoji as surrogate pairs by default, unlike its String writer and Jackson 3. That changed what Clef tokenizes (ClefEngineTest caught it). Jackson2Codec now enables COMBINE_UNICODE_SURROGATES_IN_UTF8, and both codec tests pin it.

Verified

  • ./mvnw clean verify: all modules, checkstyle and the javadoc check.
  • Acceptance tests against the live API: 44/44, every transport/codec pair.
  • client-local model tests on real model files: 43/43, including Clef's token-for-token check against Python.
  • The CLI uber-jar answers a live request.

Closes #12.

🤖 Generated with Claude Code

…ign names

core keeps the TypeSafeClient interface, its decorators, TypeSafeException, the
model and the JsonCodec SPI. DefaultTypeSafeClient, ApiKey and the HttpTransport
SPI move to the new client-http module, so the compiler now enforces that core
never sees the HTTP client; TypeSafeClient.builder() becomes
DefaultTypeSafeClient.builder().

Every module's directory, artifact and package share one name:
client-http-jdk, client-http-okhttp, codec-jackson2, codec-jackson3,
client-local, client-mapping, client-testkit (io.github.dfa1.typesafe.<module>,
dashes as dots). No client-* module depends on another in production.

ADR 0003 records the layout (closes #12).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
dfa1 and others added 3 commits October 4, 2026 08:36
…ckage

The core artifact held two packages (core, codec); every other module holds
exactly the package its name gives. JsonCodec now sits in core directly, as
HttpTransport sits in client.http.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
core is now the model and JsonCodec only; the client contract (TypeSafeClient,
TypeSafeException, Retrying/DeadlineTypeSafeClient, TokenCounter) is the new
client module, which every client-* module builds on. Serializing the model
(core + a codec) no longer pulls in any client code, and a module named X-Y now
always builds on module X. ADR 0003 rewritten to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
JSON goes to and from the wire as UTF-8 bytes: writeValueAsBytes /
writeValueAsPrettyBytes / readValue(byte[], ...), and a byte[] body on
HttpTransport.post and HttpTransportResponse. No String copy on the success
path; bodies become text only for error messages. Jackson2Codec enables
COMBINE_UNICODE_SURROGATES_IN_UTF8: its UTF-8 writer otherwise escapes emoji
as surrogate pairs, which changed what a local model tokenizes (caught by
ClefEngineTest; now pinned by a codec test in both codecs).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@dfa1 dfa1 changed the title Split modules into contract (core) and implementations (client-*), align names Split modules into model (core), contract (client) and implementations (client-*); byte[] SPIs Oct 4, 2026
dfa1 and others added 4 commits October 4, 2026 13:31
The SPI is byte[] in and out, so it isn't JSON-specific: a binary codec (#37)
can implement it. Builder.jsonCodec(...) becomes codec(...); client-http still
needs a JSON codec, since the API speaks JSON.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Its one caller was the CLI's --verbose, and indentation is a JSON notion that
doesn't belong on a format-neutral Codec. --verbose now prints compact JSON;
pipe it into jq to read it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
core held one piece of behaviour, the Codec SPI, and the codec-* modules broke
the X-Y rule (no codec module to build on). Codec now lives in
typesafe-java-codec (package io.github.dfa1.typesafe.codec), with no
dependencies; codec-*, client-http and client-local depend on it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@dfa1 dfa1 changed the title Split modules into model (core), contract (client) and implementations (client-*); byte[] SPIs Split modules into model (core), codec, contract (client) and implementations (client-*); byte[] SPIs Oct 4, 2026
@dfa1
dfa1 merged commit af4527d into main Oct 4, 2026
3 checks passed
@dfa1
dfa1 deleted the module-split branch October 4, 2026 11:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Split client into its own module and align artifact names with packages

1 participant