Skip to content

feat(generate): add vendor_runtime mode for self-contained output#378

Merged
nao1215 merged 1 commit intomainfrom
feat/issue-302-self-contained-generation
Apr 17, 2026
Merged

feat(generate): add vendor_runtime mode for self-contained output#378
nao1215 merged 1 commit intomainfrom
feat/issue-302-self-contained-generation

Conversation

@nao1215
Copy link
Copy Markdown
Owner

@nao1215 nao1215 commented Apr 17, 2026

Summary

Add gen.gleam.vendor_runtime to the config schema. When enabled, sqlode generate copies sqlode/runtime.gleam into the output directory as runtime.gleam and rewrites the generated imports to point at the local copy, so the target project no longer needs sqlode as a runtime dependency. Matches what sqlc users expect from code generation tools: the emitted package stands on its own. Closes #302.

Changes

  • src/sqlode/model.gleam: add vendor_runtime: Bool to GleamOutput (default False, backwards compatible with every existing model.GleamOutput(...) literal in the tests).
  • src/sqlode/config.gleam: accept vendor_runtime in the sql.gen.gleam unknown-keys guard and parse it as an optional bool.
  • src/sqlode/codegen/common.gleam: add runtime_import_path(gleam) returning <module_path>/runtime when vendoring and sqlode/runtime otherwise. Used from every generated module that references the runtime.
  • src/sqlode/codegen/queries.gleam / codegen/adapter.gleam: replace the hard-coded import sqlode/runtime line with common.runtime_import_path(gleam).
  • src/sqlode/codegen/params.gleam: render/5 gains a runtime_import argument so the existing .{type Value} suffix can be attached to either import path. Existing call sites pass sqlode/runtime.
  • src/sqlode/generate.gleam: when vendor_runtime is True, add a runtime.gleam file to the generated set whose content is read from the sqlode source on disk. The loader tries a development checkout, then the extracted-hex layout a user project has after gleam add sqlode, and finally the dev artefact path.
  • test/generate_test.gleam: pin both modes — default keeps import sqlode/runtime and emits no vendored file; enabled emits runtime.gleam byte-for-byte identical to the tracked source and rewrites every generated-module import.
  • test/codegen_test.gleam: fill the new params.render runtime-import argument on existing call sites.
  • integration_test/cases.sh: add case_compile_vendor_runtime which writes a gleam.toml with NO sqlode dependency and then builds the generated project end-to-end, proving the vendored output stands alone. Registered in ALL_INTEGRATION_CASES.
  • README.md: document the new option, the trade-off (smaller shared dependency vs. self-contained output), and how the import rewriting works.

Design Decisions

  • Byte-for-byte copy, not a rebuild. The vendored runtime.gleam is exactly the file sqlode itself ships. That means any API that the generated code already uses (placeholder markers, PlaceholderStyle, RawQuery, prepare, encoding helpers) is guaranteed to be available, without the risk of a hand-written minimal copy drifting from the real one. The trade-off — vendored output needs sqlode generate to re-run when the runtime changes — is documented in the README.
  • Multi-candidate runtime source loader. At generation time the sqlode source could live under src/sqlode/ (dev checkout), build/packages/sqlode/src/sqlode/ (user ran gleam add sqlode), or build/dev/erlang/sqlode/src/sqlode/ (another Gleam build layout). Each is tried in order and the first readable file wins. On a genuine not-found, the vendored file is simply omitted rather than written with garbage — the flag defaults off, so users who opt in will notice the missing file.
  • Import path threading via common.runtime_import_path. Centralising the decision in one place means when the runtime module ever moves or renames, only one function needs to change. Each generated module already imported common, so no new dependency edges.
  • Integration test with an sqlode-free gleam.toml. The whole point of the feature is that the generated project does not need sqlode as a runtime dep. The new case asserts that by literally not listing sqlode in gleam.toml — and gleam build still succeeds.

Limitations

  • The escript distribution of sqlode does not include the Gleam sources, so vendor_runtime: true will not find the runtime file when invoked from an escript-only install. The next logical step is to embed the source as a Gleam string constant or ship it via priv/; kept out of scope here because it does not affect the common gleam run -m sqlode or dev-checkout flows.
  • The copy happens at sqlode generate time. Users must re-run the generator to pick up a new sqlode runtime; the trade-off is spelled out in the README.
  • Adapter-specific driver dependencies (pog / sqlight) are unaffected. Vendoring only removes the sqlode/runtime edge; native adapters still need their chosen driver package.

Summary by CodeRabbit

New Features

  • Added self-contained code generation via new vendor_runtime: true configuration option, which bundles the runtime module directly into generated code and automatically updates imports to reference the local copy
  • Eliminates sqlode as a required runtime dependency when vendor mode is enabled

Documentation

  • Added documentation describing the self-contained generation capability and runtime dependency behavior

Add `gen.gleam.vendor_runtime` to the config schema. When enabled,
`sqlode generate` copies `sqlode/runtime.gleam` verbatim into the
output directory as `runtime.gleam` and rewrites the generated
`params.gleam` / `queries.gleam` / `<driver>_adapter.gleam` imports
to point at `<module_path>/runtime` instead of the shared
`sqlode/runtime`. The resulting package no longer needs sqlode as a
runtime dependency — only as a dev dependency to run the generator
itself.

- `src/sqlode/model.gleam`: add `vendor_runtime: Bool` to
  `GleamOutput` (default `False`, backwards compatible).
- `src/sqlode/config.gleam`: parse the new field and accept it in
  the `sql.gen.gleam` unknown-keys guard.
- `src/sqlode/codegen/common.gleam`: add a `runtime_import_path`
  helper that returns `<module_path>/runtime` when vendoring is on
  and `sqlode/runtime` otherwise. Used by every generated module
  that references the runtime.
- `src/sqlode/codegen/queries.gleam` / `adapter.gleam` /
  `params.gleam`: replace the hard-coded `import sqlode/runtime`
  lines with `common.runtime_import_path(gleam)` so both modes
  produce matching imports. `params.render/5` gains a
  `runtime_import` parameter; the single caller in
  `generate.gleam` plumbs the correct path through.
- `src/sqlode/generate.gleam`: when `vendor_runtime` is `True`,
  add a `runtime.gleam` file to the generated set whose content is
  read from the sqlode source on disk. Tries a development
  checkout first, then the extracted-hex layout a user project
  has after `gleam add sqlode`, and finally the built dev
  artefact path.
- `test/generate_test.gleam`: pin both modes — default keeps the
  `sqlode/runtime` imports and emits no vendored file; enabled
  emits `runtime.gleam` identical to the tracked source and
  rewrites every `params.gleam` / `queries.gleam` import.
- `test/codegen_test.gleam`: fill the new `params.render`
  runtime-import argument on existing call sites.
- `integration_test/cases.sh`: add `case_compile_vendor_runtime`
  that writes a `gleam.toml` with NO `sqlode` dependency and then
  builds the generated project end-to-end, proving the vendored
  output stands alone.
- `README.md`: document the new option, the trade-off, and the
  build-time dependency reduction.

Verified `just all` (format-check + check + build + `gleam test` +
ShellSpec) and the full `integration_test/run.sh` (all seven cases)
pass end-to-end.

Closes #302
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 17, 2026

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Free

Run ID: 68bfb11d-4324-41f2-9325-1172a9a457f1

📥 Commits

Reviewing files that changed from the base of the PR and between 181e4b1 and 88f5f06.

📒 Files selected for processing (11)
  • README.md
  • integration_test/cases.sh
  • src/sqlode/codegen/adapter.gleam
  • src/sqlode/codegen/common.gleam
  • src/sqlode/codegen/params.gleam
  • src/sqlode/codegen/queries.gleam
  • src/sqlode/config.gleam
  • src/sqlode/generate.gleam
  • src/sqlode/model.gleam
  • test/codegen_test.gleam
  • test/generate_test.gleam

📝 Walkthrough

Walkthrough

A new vendor_runtime configuration option enables generated Gleam code to copy sqlode/runtime locally and rewrite imports to use the bundled copy, eliminating the runtime dependency on the sqlode package. Configuration parsing, data models, and code generation are updated to support this feature, with comprehensive test coverage.

Changes

Cohort / File(s) Summary
Documentation & Configuration
README.md, src/sqlode/config.gleam
Added documentation section describing vendor_runtime feature and updated runtime comparison notes. Configuration parser now recognizes and reads sql.gen.gleam.vendor_runtime boolean flag, defaulting to False.
Data Model
src/sqlode/model.gleam
Added new vendor_runtime: Bool field to GleamOutput record to track whether runtime should be vendored into generated output.
Runtime Import Path Helper
src/sqlode/codegen/common.gleam
Introduced public helper function runtime_import_path() that computes the correct import path for sqlode runtime—either the shared sqlode/runtime dependency or a local module path based on vendor_runtime setting.
Code Generation
src/sqlode/codegen/adapter.gleam, src/sqlode/codegen/params.gleam, src/sqlode/codegen/queries.gleam
Updated adapter, params, and queries code generators to use dynamically computed runtime import paths via common.runtime_import_path() instead of hardcoded sqlode/runtime strings. params.render() now accepts runtime_import parameter.
Main Generation Logic
src/sqlode/generate.gleam
Enhanced base_output_files() to conditionally emit vendored runtime.gleam file when vendor_runtime is enabled by reading source from candidate paths. Updated params.gleam generation to pass computed runtime import path. Added read_runtime_source() helper to locate and read runtime module source.
Unit Tests
test/codegen_test.gleam, test/generate_test.gleam
Updated existing test cases to pass vendor_runtime: False in GleamOutput construction and runtime_import parameters. Added two new comprehensive tests validating default behavior (shared runtime import) and vendored behavior (local copy emission with rewritten imports).
Integration Tests
integration_test/cases.sh
Added new integration test case case_compile_vendor_runtime that exercises end-to-end vendoring: creates isolated project with vendor_runtime: true, generates code, and verifies build succeeds without sqlode dependency.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A vendor arrives with runtime in tow,
Bundling code where self-contained paths go,
No more dependencies to fetch and to keep,
Just imports that rewrite—a logic so neat!
With runtime tucked in, the generated code's free,
A hop, skip, and compile for all queries to be! 🎉


Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login.

Comment @coderabbitai help to get the list of available commands and usage tips.

@nao1215 nao1215 merged commit 2620685 into main Apr 17, 2026
2 checks passed
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.

feat: offer a self-contained generation mode without a sqlode runtime dependency

1 participant