Skip to content

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.

From a checkout with Go 1.26 or later:

Terminal window
cd recipes
export GOWORK=off
go mod download
go 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.

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.

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:

BoundaryFundamentalsgoogle/uuidgofrs/uuid/v5
Parse compact, braced or URN textRejectsAcceptsAccepts
Value() on a zero UUIDDashed zero stringDashed zero stringDashed zero string
Scan(nil) on a non-nullable UUIDError, unchangedSuccess, unchangedError, unchanged
Explicit SQL nullabilitysql.Null[concepts.UUID]uuid.NullUUIDuuid.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.

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.

RecipeQualificationSupported boundary and gaps
samber/do v2.1.0RunResolver passes; level 1 failsNative 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.0RunResolver passes; level 1 failsSame borrowed-singleton subset; serialized native calls because dig is not concurrent-safe. No resource disposal, infrastructure facade or context guards.
fx v1.24.0 hostingHosted 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.

The opt-in probes call the full, unmodified ditest.RunProvider, without skipping subtests. This command is expected to fail, not a passing CI target:

Terminal window
cd recipes
GOWORK=off go test -tags=providerprobe -run '^TestProviderProbe$' ./samberdo ./dig

Both 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} and lifetimes/transient/{owned,borrowed} — unsupported lifetime or ownership.
  • failed_results_and_retry/{owned,borrowed} — requires scoped factories.
  • io_closer_disposal, reverse_cleanup and close_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} and waiter_cancellation — use owned bindings.
  • creator_cancellation_and_live_waiter_retry and close_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.

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.

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:

  1. Load all requested root patterns once with packages.LoadAllSyntax, source comments, tests disabled and the same offline environment as Inspect. Separate loads do not share a coherent go/types identity universe.
  2. Reject producer errors, package errors throughout the dependency graph, ill-typed packages, incomplete root source metadata and zero matches. These operational failures return an error and an empty plan, not partial bindings.
  3. Sort roots by import path and ID, read directives from each root’s matching syntax and type information, then merge policies.
  4. Select the exact EmitPackage import 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 before Analyze; they return a diagnostics-only plan with a nil operational error.
  5. Call Analyze with the loaded emission package and roots only. Nil Constructors discovers exact package-level NewX declarations. Imports share type identities but do not become constructor-discovery roots.
  6. Inspect diagnostic severity, not just whether diagnostics exist. Any error makes bindings nil. BT011 information preserves bindings but leaves an external dependency obligation; scalar configuration without registration is BT012, 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:

Terminal window
cd recipes
GOWORK=off GOTOOLCHAIN=local go test -run '^Example_constructorBindings$' -v ./packagesloading

It 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.