Skip to content

feat: move TypeScript client codegen into the SDK module#9

Draft
TomChv wants to merge 1 commit into
mainfrom
feat/extract-client-codegen-in-typescript-sdk
Draft

feat: move TypeScript client codegen into the SDK module#9
TomChv wants to merge 1 commit into
mainfrom
feat/extract-client-codegen-in-typescript-sdk

Conversation

@TomChv

@TomChv TomChv commented Jul 6, 2026

Copy link
Copy Markdown
Member

Move TypeScript client codegen into the SDK module

Moves TypeScript client generation out of dagger/dagger's engine runtime and into this .dang SDK module. The module now generates a typed client for a bound module itself — receiving the schema + module metadata from the engine as plain data and running codegen in an ordinary container, with no nested engine session.

Important

Depends on two engine PRs, both gated v1.0.0-0:

This PR requires an engine that ships both.

What's in it

helpers/codegen — the TypeScript client generator ported from dagger/dagger's cmd/codegen as a standalone, engine-free Go module (no dagger.io/dagger dependency). Reads a pre-computed introspection schema + a meta.json (module name, engine version, bound module) and emits the client bindings. The upstream live-engine query is replaced by the meta.json input. Upstream golden tests ported to guard fidelity.

helpers/config-updator — new client config writers producing a scoped npm package: package.json (@dagger.io/<module>-client name, @dagger.io/dagger pinned to the engine version, type:module, typescript), plus tsconfig.json / deno.json.

typescript-sdk.dang — the dang wiring:

  • generateClient(ws, module, path) — the workspace analogue of mod(ws, path).generate(ws).
  • generateAllClient(ws) @generate — regenerates every registered client (currentModule.asSDK.clients), resolving each bound module via client.moduleSource and generating from clientSchemaIntrospectionJSON.
  • Renamed generateAllgenerateAllModule for symmetry.
  • codegenBuilder / configUpdatorBuilder container builders (compiled like the existing helpers).

e2egenerateClientCheck, generateAllClientCheck, generateClientRespectsExistingCheck, generateClientCompilesCheck, and helperTestsCheck (runs the Go helper tests inside dagger check), plus dependency-bearing fixtures (client/appclient/dep).

Client output layout

A generated client is a scoped package:

  • dagger.gen.ts — core Dagger types (named to match the Go SDK's dagger.gen.go)
  • <module>.gen.ts — one per module: the bound module (e.g. hello.gen.ts) and each dependency; each carries its own types plus the prototype augmentations it contributes to Query/Client/Binding/Env.

Key design decisions

  • Always Remote / scoped package — the client depends on the npm @dagger.io/dagger; no TypeScript library bundle is vendored here.
  • Serve the one bound module — the client schema is core + the bound module only, so the generated serveBoundModule bootstrap serves exactly that module (no dependency loop, no includeDependencies). A LOCAL_SOURCE/DIR_SOURCE module is resolved against the workspace by its workspace-root-relative path (dag.currentWorkspace().moduleSource(path)) — cwd-independent, so it works anywhere in the project tree; a GIT_SOURCE module serves from its canonical ref + pin (dag.moduleSource(ref, { refPin })), which resolves from anywhere. A local-bound client shipped away from the project tree is unservable by design (remote-only) — see hack/designs/generated-client-module-loading.md.

Full rationale in design/client-gen.md.

Testing

  • Go unit + golden tests: go test ./... in each helper (also run inside dagger check via helperTestsCheck), including a golden test for the serveBoundModule bootstrap (local path vs. git ref + pin).
  • e2e checks green on the #13646 dev engine, except generateClientCompilesCheck, which additionally requires #13663: it type-checks the generated dag.currentWorkspace().moduleSource(...) against the engine's Workspace.moduleSource, so it only passes once the engine ships that field.

Known limitations / follow-ups

  • @dagger.io/dagger version on a dev/unreleased engine isn't on npm. generateClientCompilesCheck builds it from a pinned dagger/dagger commit; the client's package.json respects an existing local ./sdk ref (see design/client-bundle.md).
  • Deno client config exists in config-updator but isn't wired into clientDirectory yet.

@TomChv
TomChv force-pushed the feat/extract-client-codegen-in-typescript-sdk branch 2 times, most recently from 71b3bbb to 8d31ef1 Compare July 15, 2026 14:44
@TomChv

TomChv commented Jul 15, 2026

Copy link
Copy Markdown
Member Author

PR needs dagger/dagger#13646 merged and released in order to load

@TomChv

TomChv commented Jul 17, 2026

Copy link
Copy Markdown
Member Author
Screenshot 2026-07-17 at 16 47 55

Once dagger/dagger#13663 is merge, the typescript client codegen will work :D
I did a manual test and that works hehehe

Client generation used to run inside dagger/dagger's engine runtime, which
opened a nested engine session and coupled client bindings to engine
internals. Moving it into this SDK module makes generation ordinary codegen in
a plain container: the engine hands over the client-facing schema and the
bound module's metadata as data, with no nested session.

A generated client now binds exactly one module and ships as a self-contained,
scoped npm package. At runtime it serves that single module through
Workspace.moduleSource, so the bindings resolve from any plain client session
rather than only from a module runtime.

Requires the engine client-codegen primitives released in v1.0.0-beta.7
(dagger/dagger#13646 and #13663).

Signed-off-by: Tom Chauveau <tom@dagger.io>
@TomChv
TomChv force-pushed the feat/extract-client-codegen-in-typescript-sdk branch from 5f38891 to d9835de Compare July 20, 2026 10:34
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.

1 participant