Ecosystem recipes
You can keep your UUID library, DI container or generator package without making it a Fundamentals dependency. These recipes show the boundary code and exercise it with executable examples. They are unpublished examples, not supported integration modules: copy the relevant pattern into your application and keep its limitations in view.
Run the recipes
Section titled “Run the recipes”From a checkout with Go 1.26 or later:
cd recipesexport GOWORK=offgo mod downloadgo test -count=1 ./...go test -run Example -v ./...The nested module replaces Fundamentals with the repository root, so examples
always use the current source. Root go build ./... and go test ./... exclude
this module; CI runs the same build, vet, test, race, lint, tidy and vulnerability
gates inside it separately. See unpublished modules.
Documentation tooling
Section titled “Documentation tooling”documentationcheck contains test-only authored-documentation checks, run by
go test ./... inside recipes/, not by root-only tests. Goldmark v1.8.6 (MIT)
parses CommonMark/GFM; go.yaml.in/yaml/v3 v3.0.5 (MIT and Apache-2.0) parses
frontmatter and flat or nested name/href/items navigation. Neither adds a
root dependency or runtime API.
The check discovers Documentation/**/*.md, toc.yml, README and CONTRIBUTING.
It verifies local Markdown destinations and explicit full/collapsed references;
undefined shortcuts remain prose. External schemes and site-root routes are
reported as unverified without network requests. MDX, raw HTML other than
comments, and symlinked authored-tree entries are outside its profile. Anchors
use rendered heading text, Unicode simple lowercase, letters/numbers/marks,
underscores and hyphens; each whitespace becomes a hyphen, other punctuation
and symbols are omitted, and duplicates take the first free -N suffix. This
source-anchor profile does not claim complete GitHub or Starlight rendering parity.
Exchange UUID values
Section titled “Exchange UUID values”UUID interop source and examples
use google/uuid v1.6.0 and gofrs/uuid/v5 v5.5.1. All three UUID types store 16 bytes
in RFC/network order. Convert values directly, not through .NET’s mixed-endian
Guid.ToByteArray representation:
// Excerpt: id is a concepts.UUID; uuid is github.com/google/uuid.googleID := uuid.UUID(id)roundTrip := concepts.UUID(googleID)The asymmetric fixture 00112233-4455-6677-8899-aabbccddeeff checks every byte.
Text and JSON emit the same lowercase dashed string and round-trip unchanged.
Conversion does not make the libraries’ parsing or null policies equivalent:
| Boundary | Fundamentals | google/uuid | gofrs/uuid/v5 |
|---|---|---|---|
| Parse compact, braced or URN text | Rejects | Accepts | Accepts |
Value() on a zero UUID | Dashed zero string | Dashed zero string | Dashed zero string |
Scan(nil) on a non-nullable UUID | Error, unchanged | Success, unchanged | Error, unchanged |
| Explicit SQL nullability | sql.Null[concepts.UUID] | uuid.NullUUID | uuid.NullUUID |
All three scan canonical text and 16-byte RFC-order values. In particular,
google’s successful Scan(nil) can leave a previous nonzero value in place;
it is not a nullable-column representation. Use the nullable wrapper when NULL
means absence. Validate external input with concepts.ParseUUID if the strict
Fundamentals text contract matters, even when another parser accepts it.
Choose a DI boundary
Section titled “Choose a DI boundary”The native bridges deliberately stop at borrowed singletons. Rebuilding ownership, scoped/transient lifetimes, cancellable construction and cleanup coordination over a second container would turn a recipe into another container implementation. Use plain constructor wiring first; when full provider semantics are needed, host the Fundamentals provider rather than claiming a native container is one.
| Recipe | Qualification | Supported boundary and gaps |
|---|---|---|
| samber/do v2.1.0 | RunResolver passes; level 1 fails | Native lazy singleton caching, exact keys, borrowed scope handles and catalog. Rejects owned, scoped and transient bindings. No infrastructure facade or context guards. |
| dig v1.19.0 | RunResolver passes; level 1 fails | Same borrowed-singleton subset; serialized native calls because dig is not concurrent-safe. No resource disposal, infrastructure facade or context guards. |
| fx v1.24.0 hosting | Hosted Fundamentals provider passes RunProvider (level 1) | The provider remains Fundamentals, not a native fx adapter. Actual fx start, stop and startup rollback are exercised separately. Level 2 is not claimed for this hosting recipe. |
For either native bridge, construct borrowed singleton descriptors with
di.NewBinding(di.Singleton, di.Borrowed, factory, dependencies...), pass them to
New, open a scope, and call di.Resolve[T]. The compiling ExampleNew in each
package shows the complete workflow. Native containers cache successful results;
failed factories can be retried. Exact di.Key identities map to private native
names, not potentially ambiguous diagnostic type strings.
Scope handles are concurrent-safe and share those native singleton values.
Close invalidates handles and joins admitted lookups; it never closes borrowed
resources. Stop application work and close those resources yourself. Factories
receive a metadata-free context and may resolve only declared acyclic dependencies
through their supplied resolver. Never retain that resolver, use it concurrently,
or capture and re-enter the provider. Cancellation can interrupt waiting for
admission, not an already running native singleton constructor. These restrictions
are why a Provider method set alone is not provider qualification.
Reproduce the native provider gaps
Section titled “Reproduce the native provider gaps”The opt-in probes call the full, unmodified ditest.RunProvider, without skipping
subtests. This command is expected to fail, not a passing CI target:
cd recipesGOWORK=off go test -tags=providerprobe -run '^TestProviderProbe$' ./samberdo ./digBoth adapters reject the following level-1 configurations at build time. Braces
below enumerate the exact failing leaf subtests under TestProviderProbe/:
nil_results/{nil,typed_nil,nil_pointer}— requires owned transient bindings.lifetimes/singleton/owned,lifetimes/scoped/{owned,borrowed}andlifetimes/transient/{owned,borrowed}— unsupported lifetime or ownership.failed_results_and_retry/{owned,borrowed}— requires scoped factories.io_closer_disposal,reverse_cleanupandclose_retains_errors_and_attempts_all/{scope,provider}— require disposal.provider_drains_scopes— requires owned scoped resources.scope_factory_facade— requests the unsupported infrastructure facade.borrowed_interface_forwarding/{singleton,scoped}— owns the concrete resource.pre_canceled_resolution— uses an owned scoped binding.concurrent_cached_construction/{singleton,scoped}andwaiter_cancellation— use owned bindings.creator_cancellation_and_live_waiter_retryandclose_joins_and_resumes/{scope,provider}— require owned scoped construction.
The passing provider subtests are exact_keys_and_catalog,
lifetimes/singleton/borrowed, borrowed_value_never_disposed and
scope_ownership. Those partial results do not add up to level 1. In particular,
the rejected cancellation fixtures say nothing about cancellation conformance.
Normal CI runs the complete resolver suite and focused tests for the supported
subset and fail-closed policies. No level-2 claim is made for either native bridge.
Host the full provider with fx
Section titled “Host the full provider with fx”The dig.Provide recipe is an fx constructor: use fx.Provide(dig.Provide) and
supply a configured *container.Registry. It builds a Fundamentals provider and
appends provider.Close as an fx.Lifecycle OnStop hook. fx still manages the
application graph; Fundamentals manages its own scopes and resources.
Resolve resources from a later OnStart hook, not during fx graph construction.
fx cannot stop hooks that never started. Append consumer shutdown hooks after the
provider hook, so reverse-order stopping joins consumers before provider disposal.
ExampleProvide prints running, closed: 0 followed by stopped, closed: 1.
The startup-failure test also proves rollback closes the provider once.
Load types for a generator
Section titled “Load types for a generator”Package-loading source
uses golang.org/x/tools/go/packages v0.51.0 to load a fixture under
packagesloading/testdata, then passes every exported type to
conceptstypes.Underlying. The fixture stays inside the recipes module, so it
needs neither a second module nor a generated manifest.
Inspect(ctx, dir, pattern) requires exactly one type-checked package, reports
load diagnostics and malformed concepts as errors, and returns recognized names
in sorted order. Ordinary unmarked types and non-type exports are omitted.
ExampleInspect finds ID uuid and Name string; a separate invalid fixture
proves a marker without scalar codecs is rejected.
Loading never executes the fixture. Workspace lookup, toolchain downloads,
module fetching, external package drivers and manifest writes are disabled for
this offline inspection; run go mod download first to prepare dependencies.
conceptstypes itself still owns no package loader and adds no dependency to the
standard-library-only root.
To plan constructors across packages, use the
compiled constructor-planning example.
Its private planConstructorBindings helper connects go/packages to the released
bindingtypes.ReadDirectives and bindingtypes.Analyze APIs. The helper is a pattern
to copy, not a new exported API or an installable, supported integration module.
The unpublished recipes module uses its local-root replacement; your product owns
its loader and composition decisions.
The helper follows this order:
- Load all requested root patterns once with
packages.LoadAllSyntax, source comments, tests disabled and the same offline environment asInspect. Separate loads do not share a coherentgo/typesidentity universe. - Reject producer errors, package errors throughout the dependency graph,
ill-typed packages, incomplete root source metadata and zero matches. These
operational failures return an
errorand an empty plan, not partial bindings. - Sort roots by import path and ID, read directives from each root’s matching syntax and type information, then merge policies.
- Select the exact
EmitPackageimport path uniquely among those roots; a missing or ambiguous destination is an operational error. An imported dependency is not an emission root. Error-severity directive diagnostics stop planning beforeAnalyze; they return a diagnostics-only plan with a nil operational error. - Call
Analyzewith the loaded emission package and roots only. NilConstructorsdiscovers exact package-levelNewXdeclarations. Imports share type identities but do not become constructor-discovery roots. - Inspect diagnostic severity, not just whether diagnostics exist. Any error
makes bindings nil.
BT011information preserves bindings but leaves an external dependency obligation; scalar configuration without registration isBT012, an error. Products must satisfy obligations before emitting or activating code.
This excerpt from Example_constructorBindings assumes the private helper and
fixture-path constant in that source file, with context, fmt and
bindingtypes imported. Both operational and structural failures stop the example
visibly before it prints success output:
plan, err := planConstructorBindings(context.Background(), ".", bindingsFixturePath+"/service", "./testdata/bindings/repository", "./testdata/bindings/service")if err != nil { panic(err)}for _, diagnostic := range plan.Diagnostics { if diagnostic.Severity == bindingtypes.Error { panic(fmt.Sprintf("%s: %s", diagnostic.Code, diagnostic.Message)) }}From the checkout, run the output-checked example:
cd recipesGOWORK=off GOTOOLCHAIN=local go test -run '^Example_constructorBindings$' -v ./packagesloadingIt reports two owned constructor registrations: singleton NewStore with no
arguments and scoped NewService with two ordered store arguments, one unique
exact dependency and an error return. Constructor bodies panic if called; loading
and planning never activate them. Plan ordering is deterministic service-identity
ordering, not a topological activation schedule, and this recipe emits no source.
The helper propagates context into loading and checks cancellation before planning
and returning. Analyze is synchronous; these checks do not preempt it.
EmitPackage controls declaration accessibility, not proof of importability,
main/internal restrictions, freedom from import cycles or publishability.
Products must preflight and compile their actual generated outputs.