Skip to content

Commit 1e76e8f

Browse files
kookseecursoragent
andauthored
fix: align thread-safety docs and add CI test coverage (#37)
* fix: align thread-safety docs and add CI test coverage Clarify non-thread-safe container behavior, expose top-level Try* APIs, and run full test suite in task/CI with added smoke tests for public wrappers. Co-authored-by: Cursor <cursoragent@cursor.com> * test: add coverage for context and global helpers Add unit tests for dixcontext panic/nil handling and dixglobal provide/inject flows to improve wrapper-module coverage. Co-authored-by: Cursor <cursoragent@cursor.com> * docs: streamline README structure and diagnostics guide Improve bilingual READMEs with clearer onboarding, API/option quick references, production-safe examples, and condensed diagnostics guidance linked to detailed dixhttp docs. Co-authored-by: Cursor <cursoragent@cursor.com> * test: expand wrapper coverage and add README link checks Add dix wrapper edge-case tests and a README local-link checker wired into task and CI docs job. Co-authored-by: Cursor <cursoragent@cursor.com> * docs: add dixhttp auth guidance and PR template Document reverse-proxy authentication practices for dixhttp and add a reusable PR description template for review-ready changes. Co-authored-by: Cursor <cursoragent@cursor.com> * chore: remove README link checker tooling Drop the optional readme-check script and its CI/task wiring since it is not needed for day-to-day development. Co-authored-by: Cursor <cursoragent@cursor.com> * docs(example): simplify samples for easier onboarding Rewrite example programs with clearer scenarios, dix.New usage, run instructions, and Try* error-handling patterns aligned with the public API docs. Co-authored-by: Cursor <cursoragent@cursor.com> * test: isolate global InjectT test and align API docs Make dixglobal InjectT test self-contained to avoid order-dependent state and document InjectTContext in both README API tables. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 43e393e commit 1e76e8f

26 files changed

Lines changed: 886 additions & 619 deletions

File tree

.github/workflows/lint.yml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Lint
1+
name: CI
22

33
on:
44
push:
@@ -7,6 +7,18 @@ on:
77
branches: [ master, v2 ]
88

99
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- uses: actions/checkout@v4
15+
- uses: actions/setup-go@v5
16+
with:
17+
go-version-file: 'go.mod'
18+
19+
- name: Run tests
20+
run: go test ./... -count=1 -race
21+
1022
lint:
1123
runs-on: ubuntu-latest
1224

README.md

Lines changed: 112 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,28 @@ Inspired by [uber-go/dig](https://github.com/uber-go/dig), with support for adva
99

1010
[中文文档](./README_zh.md)
1111

12+
## Table of Contents
13+
14+
- [When to use dix](#when-to-use-dix)
15+
- [Features](#-features)
16+
- [Installation](#-installation)
17+
- [Quick Start](#-quick-start)
18+
- [Core API](#-core-api)
19+
- [Injection Patterns](#-injection-patterns)
20+
- [Modules](#-modules)
21+
- [Diagnostics](#-diagnostics)
22+
- [Development](#️-development)
23+
- [Examples](#-examples)
24+
- [Documentation](#-documentation)
25+
26+
## When to use dix
27+
28+
- You need **runtime** dependency registration (plugins, dynamic modules, conditional wiring).
29+
- You want **built-in diagnostics**: structured trace logs, JSONL export, and an HTTP dependency graph.
30+
- You prefer a **dig-like API** with safe `Try*` variants, map/list grouping, and method injection.
31+
32+
For compile-time wiring with minimal runtime overhead, see [google/wire](https://github.com/google/wire). For Uber's fx ecosystem, see [uber-go/dig](https://github.com/uber-go/dig).
33+
1234
## ✨ Features
1335

1436
| Feature | Description |
@@ -74,8 +96,39 @@ func main() {
7496
}
7597
```
7698

99+
For production startup, prefer `TryProvide` / `TryInject` to avoid panics and keep the process alive for diagnostics:
100+
101+
```go
102+
if err := dix.TryProvide(di, NewDatabase); err != nil {
103+
log.Fatal(err)
104+
}
105+
if err := dix.TryInject(di, Run); err != nil {
106+
log.Fatal(err)
107+
}
108+
```
109+
77110
## 📖 Core API
78111

112+
| API | Panics on error | Description |
113+
| --- | --- | --- |
114+
| `New(...Option)` || Create a container |
115+
| `Provide(di, fn)` | yes | Register a provider |
116+
| `TryProvide(di, fn)` | no | Register a provider, returns `error` |
117+
| `Inject(di, target)` | yes | Inject into a function or struct |
118+
| `TryInject(di, target)` | no | Inject, returns `error` |
119+
| `InjectT[T](di)` | yes | Allocate a struct and inject exported fields |
120+
| `InjectTContext[T](ctx, di)` | yes | Allocate a struct and inject with trace context |
121+
| `InjectContext` / `TryInjectContext` | yes / no | Inject with trace context propagation |
122+
| `Version()` || Return embedded version string |
123+
124+
Container options:
125+
126+
| Option | Default | Description |
127+
| --- | --- | --- |
128+
| `WithValuesNull()` | enabled | Allow nil provider results |
129+
| `WithProviderTimeout(d)` | `15s` | Per-provider execution timeout (`0` = disabled) |
130+
| `WithSlowProviderThreshold(d)` | `2s` | Warn when provider is slow (`0` = disabled) |
131+
79132
### Provide / TryProvide
80133

81134
Register constructor (Provider) to container:
@@ -115,98 +168,37 @@ err := dix.TryInject(di, func(svc *Service) {
115168
})
116169
```
117170

118-
### Startup Timeout / Slow Provider Warning
119-
120-
Control long-running providers during startup:
121-
122-
- Default provider timeout: `15s`
123-
- Disable provider timeout explicitly: `dix.WithProviderTimeout(0)`
124-
- Default slow provider warning threshold: `2s`
125-
- Disable slow provider warning: `dix.WithSlowProviderThreshold(0)`
171+
### Generic Helpers
126172

127173
```go
128-
di := dix.New(
129-
// Default `ProviderTimeout` is `15s`
130-
// Use `dix.WithProviderTimeout(0)` to disable provider timeout
131-
// Default `SlowProviderThreshold` is `2s`
132-
// Use `dix.WithSlowProviderThreshold(0)` to disable slow-provider warnings
133-
dix.WithProviderTimeout(2*time.Second), // override default (default: 15s, 0 = disabled)
134-
dix.WithSlowProviderThreshold(300*time.Millisecond), // override default (default: 2s, 0 = disabled)
135-
)
136-
```
137-
138-
### DI Trace Logging (Optional)
139-
140-
Enable step-by-step dependency resolution/injection/provider execution logs:
174+
// Inject into a new struct value
175+
app := dix.InjectT[App](di)
141176

142-
- Env var: `DIX_TRACE_DI`
143-
- Default: disabled
144-
- Enable values: `1`, `true`, `on`, `yes`, `enable`, `trace`, `debug`
145-
146-
```bash
147-
export DIX_TRACE_DI=true
177+
// Inject with request-scoped trace context
178+
err := dix.TryInjectContext(ctx, di, func(svc *Service) {
179+
svc.DoSomething()
180+
})
148181
```
149182

150-
When enabled, dix prints `di_trace ...` events with structured key-values (provider, input/output types, query kind, parent chain, timeout, etc.).
151-
152-
> Note: if `DIX_LLM_DIAG_MODE=machine`, human-readable text logs are suppressed by design, including `di_trace` lines.
183+
### Thread Safety
153184

154-
### Diagnostic File Collection (Optional)
185+
`Dix` containers are **not thread-safe**. Do not call `Provide` / `Inject` (or their `Try*` variants) concurrently on the same container instance.
155186

156-
You can collect detailed diagnostics into a searchable JSONL file:
187+
Recommended usage:
157188

158-
- Env var: `DIX_DIAG_FILE`
159-
- Example: `export DIX_DIAG_FILE=.local/dix-diag.jsonl`
189+
- Register all providers during application startup (single goroutine).
190+
- After startup, only read resolved dependencies, or continue injection from a single goroutine.
191+
- Use separate `Dix` instances per goroutine if you need isolated containers.
192+
- For a process-wide singleton, prefer `dixglobal` only when startup is single-threaded.
160193

161-
Behavior rules:
194+
### Startup Options
162195

163-
- If `DIX_DIAG_FILE` is **not configured**, dix keeps the original behavior (no diagnostic file output).
164-
- If `DIX_DIAG_FILE` is configured, dix appends diagnostic records to file (`trace` / `error` / `llm`).
165-
- Console verbosity still follows existing controls (`DIX_TRACE_DI`, `DIX_LLM_DIAG_MODE`).
166-
167-
Tip:
168-
169-
- Keep console output concise for users.
170-
- Keep detailed records in file for search/LLM/offline troubleshooting.
171-
172-
### In-Memory Trace Query (`dixtrace`, Optional)
173-
174-
Starting from this version, dix also emits unified trace events into an in-memory trace store (`dixtrace`), which can be queried via HTTP API (`/api/trace`).
175-
176-
- Default: enabled (in-memory ring buffer)
177-
- Optional file sink env var: `DIX_TRACE_FILE`
178-
- Example: `export DIX_TRACE_FILE=.local/dix-trace.jsonl`
179-
- Compatibility fallback: when `DIX_TRACE_FILE` is not set and `DIX_DIAG_FILE` is set, trace file sink will reuse `DIX_DIAG_FILE` in append mode.
180-
181-
`/api/trace` is optimized for online troubleshooting (filter by `operation/status/event/component/provider/output_type`).
182-
If you need separate trace-only file persistence, set `DIX_TRACE_FILE` explicitly.
183-
184-
Quick event dictionary:
185-
186-
| Event | Meaning |
187-
| ---------------------------------------------- | -------------------------------------------------------------------------- |
188-
| `di_trace inject.start` | Begin an injection request (`component`, `param_type`) |
189-
| `di_trace inject.route` | Injection route selected (`function` or `struct`) |
190-
| `di_trace provide.start` | Begin a provider registration request (`component`) |
191-
| `di_trace provide.signature` | Provider function signature analyzed (`input_count`, `output_count`) |
192-
| `di_trace provide.register.output.done` | Provider output type registered successfully |
193-
| `di_trace provide.register.failed` | Provider registration failed (`reason` or `error`) |
194-
| `di_trace resolve.value.search_provider.start` | Start searching providers for a dependency type |
195-
| `di_trace resolve.value.found` | Dependency value resolved successfully |
196-
| `di_trace resolve.value.not_found` | Dependency resolution failed (`reason` included) |
197-
| `di_trace provider.execute.dispatch` | Provider selected for execution (`provider`, `output_type`, `input_types`) |
198-
| `di_trace provider.input.resolve.start` | Resolve one provider input type |
199-
| `di_trace provider.input.resolve.found` | Provider input resolved |
200-
| `di_trace provider.input.resolve.failed` | Provider input resolution failed |
201-
| `di_trace provider.call.start` | Start executing provider (`timeout`) |
202-
| `di_trace provider.call.done` | Provider execution completed |
203-
| `di_trace provider.call.failed` | Provider execution failed (`timed_out`, `error`) |
204-
| `di_trace provider.call.return_error` | Provider returned non-nil `error` |
205-
| `di_trace inject.func.resolve_input.start` | Resolve function injection argument |
206-
| `di_trace inject.func.resolve_input.failed` | Function argument resolution failed |
207-
| `di_trace inject.struct.field.resolve.start` | Resolve one struct field injection |
208-
| `di_trace inject.struct.field.resolve.done` | Struct field injected successfully |
209-
| `di_trace inject.struct.field.resolve.failed` | Struct field injection failed |
196+
```go
197+
di := dix.New(
198+
dix.WithProviderTimeout(2*time.Second), // default: 15s; 0 disables
199+
dix.WithSlowProviderThreshold(300*time.Millisecond), // default: 2s; 0 disables
200+
)
201+
```
210202

211203
## 🎯 Injection Patterns
212204

@@ -293,11 +285,14 @@ ctx := dixcontext.Create(context.Background(), di)
293285

294286
// Retrieve and use
295287
container := dixcontext.Get(ctx)
288+
289+
// Non-panicking lookup
290+
container = dixcontext.GetOrNil(ctx)
296291
```
297292

298-
### dixhttp - Dependency Visualization 🆕
293+
### dixhttp - Dependency Visualization
299294

300-
Web interface for visualizing dependency graph, **designed for large projects**:
295+
Web interface for visualizing dependency graphs, **designed for large projects**:
301296

302297
```go
303298
import (
@@ -309,7 +304,9 @@ server := dixhttp.NewServer((*dixinternal.Dix)(di))
309304
server.ListenAndServe(":8080")
310305
```
311306

312-
Visit `http://localhost:8080` to view dependency graph.
307+
Visit `http://localhost:8080` to view the dependency graph.
308+
309+
> **Security**: exposes dependency graphs, provider source locations, runtime errors, and trace data. Use on **localhost or private networks** only. Do not expose publicly without authentication.
313310
314311
**Highlights**:
315312
- 🔍 **Fuzzy Search** - Quickly locate types or functions
@@ -318,21 +315,46 @@ Visit `http://localhost:8080` to view dependency graph.
318315
- 📏 **Depth Control** - Limit display levels (1-5 or all)
319316
- 🎨 **Modern UI** - Tailwind CSS + Alpine.js
320317

321-
See [dixhttp/README.md](./dixhttp/README.md) for details.
318+
See [dixhttp/README.md](./dixhttp/README.md) for API routes, event dictionary, and UI details.
319+
320+
## 🔍 Diagnostics
321+
322+
Optional observability for startup and injection troubleshooting. All file/console outputs are disabled unless configured.
323+
324+
| Env var | Default | Purpose |
325+
| --- | --- | --- |
326+
| `DIX_TRACE_DI` | off | Console step-by-step DI trace (`di_trace ...`) |
327+
| `DIX_DIAG_FILE` | off | Append `trace` / `error` / `llm` records to JSONL |
328+
| `DIX_TRACE_FILE` | off | Append trace-only JSONL (falls back to `DIX_DIAG_FILE`) |
329+
| `DIX_LLM_DIAG_MODE` | `human` | Log mode: `human` / `machine` / `dual` |
330+
331+
```bash
332+
export DIX_TRACE_DI=true
333+
export DIX_DIAG_FILE=.local/dix-diag.jsonl
334+
```
335+
336+
In-memory trace events (`dixtrace`) are enabled by default and queryable through `dixhttp` at `/api/trace`.
337+
338+
For the full `di_trace` event dictionary, HTTP APIs, and UI troubleshooting workflow, see [dixhttp/README.md](./dixhttp/README.md).
322339

323340
## 🛠️ Development
324341

325342
```bash
326-
# Run tests
343+
# Run all tests with coverage report
327344
task test
328345

329-
# Lint
346+
# Lint and format
330347
task lint
331348

332-
# Build
333-
task build
349+
# go vet
350+
task vet
351+
352+
# HTTP visualization demo
353+
task web-demo
334354
```
335355

356+
GitHub Actions runs `go test ./... -race` and `golangci-lint` on push/PR.
357+
336358
## 📚 Examples
337359

338360
| Example | Description |

0 commit comments

Comments
 (0)