Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
0c6b8e0
refactor: overhaul architecture to v5 with code generation
brunoga Feb 28, 2026
434c0ee
refactor: cleanup v5 API, optimize generator, and remove dead code
brunoga Feb 28, 2026
1a049b6
test: rename tests to remove redundant V5 suffix and finalize migration
brunoga Feb 28, 2026
d957e92
test: rename test types and generated code to test_user*.go
brunoga Feb 28, 2026
c0a6f73
refactor: rename root package to deep, unify Text CRDT, and reorganiz…
brunoga Mar 1, 2026
8d07942
test: move internal unit tests to engine_internal_test.go and remove …
brunoga Mar 1, 2026
46c0387
refactor: unify path resolution and optimize reflection performance
brunoga Mar 1, 2026
3bae68d
refactor: optimize generator for deep Equal/Copy and package renaming
brunoga Mar 1, 2026
23196f7
fix: correct selector path resolution (items 7, 8, 9, 10)
brunoga Mar 20, 2026
bf2e63b
feat: replace fmt.Printf with slog logging
brunoga Mar 20, 2026
a1c4d48
fix: Apply/Merge/Strict bugs and expand public API
brunoga Mar 20, 2026
ef3d32e
refactor: rewrite generator with text/template
brunoga Mar 20, 2026
8086b49
test: add regression tests for all bug fixes and new API
brunoga Mar 20, 2026
ce3a65a
chore: regenerate all *_deep.go files
brunoga Mar 20, 2026
f8554f2
fix: Diff dispatch also handles value-arg Diff(T) signature
brunoga Mar 20, 2026
5c69ea0
fix: correct example shallow-copy and auditing bugs
brunoga Mar 20, 2026
31ada08
fix: v5 release readiness — all P0/P1/P2 items
brunoga Mar 21, 2026
35cc271
test: add generated vs reflection benchmarks for Diff, Equal, Copy
brunoga Mar 22, 2026
6cc1904
refactor: rename Condition.Apply to Sub; rename condition constants O…
brunoga Mar 22, 2026
ecb7c40
fix: move resolvers/crdt to internal; unexport Delta.patch; fix CRDT …
brunoga Mar 22, 2026
d061fc0
fix: use *hlc.HLC for Operation.Timestamp to suppress zero timestamps…
brunoga Mar 22, 2026
d2c3eb3
fix: move_detection example uses Edit().Move() for correct cross-fiel…
brunoga Mar 22, 2026
8db5526
docs: add package-level docs, phantom type comment, method doc comments
brunoga Mar 22, 2026
af0a8df
ci: update workflow to stable Go with vet+race; add .gitignore; fix g…
brunoga Mar 22, 2026
1dd3ba4
fix: add hlcAfter nil-safe helper for *hlc.HLC comparison
brunoga Mar 22, 2026
573d0a5
examples: overhaul all examples for correctness, clarity, and consist…
brunoga Mar 22, 2026
8e17058
refactor: remove dead API surface and clean up examples
brunoga Mar 22, 2026
be2895e
refactor: port crdt package fully to v5; fix keyed-slice path navigation
brunoga Mar 22, 2026
aec81ad
fix: generator IsText field handling and regenerate user_deep.go
brunoga Mar 22, 2026
98941d2
fix: OpMove use-after-free and move_detection example output
brunoga Mar 22, 2026
281e24a
chore: remove dead code surfaced by linter
brunoga Mar 22, 2026
bc46dd8
refactor: fix Builder API fracture via Op type and rename Copy to Clone
brunoga Mar 22, 2026
c80ae9a
feat: replace global logger with injectable logger via ApplyOption
brunoga Mar 23, 2026
d2bb1f2
docs: add observability section and update audit_logging example
brunoga Mar 23, 2026
43b295f
refactor: decouple core from CRDT — move LWW[T] to crdt, drop Operati…
brunoga Mar 23, 2026
d5fa635
chore: go fmt and remove dead code identified by deadcode
brunoga Mar 23, 2026
7419aae
refactor: principal engineer API cleanup pass
brunoga Mar 23, 2026
df86766
refactor: v5 API overhaul — clean public surface, unified Patch method
brunoga Mar 23, 2026
1a2a840
refactor: finalize public API surface for v5.0.0 release
brunoga Mar 23, 2026
04cdf03
fix: panic with helpful message on nil selector, fix stale docs
brunoga Mar 23, 2026
2f98aa8
fix: selector path resolution — pointer fields, circular types, caching
brunoga Mar 23, 2026
e85510f
docs: fix Gob/Register claims, Merge semantics, and API coverage gaps
brunoga Mar 23, 2026
eff73f4
docs: final pre-release corrections
brunoga Mar 24, 2026
69b7323
fix: rename ToPredicateInternal/FromPredicateInternal, complete CHANG…
brunoga Mar 24, 2026
c11020f
refactor: move condition package from core/ to condition/
brunoga Mar 24, 2026
6c71406
refactor: drop redundant Cond prefix and Condition suffix from public…
brunoga Mar 24, 2026
f661bc9
fix: use filepath.Join to avoid double slash in deep-gen output path
brunoga Mar 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
# This workflow will build a golang project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go

name: Go

on:
Expand All @@ -10,19 +7,22 @@ on:
branches: [ "main" ]

jobs:

build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 'stable'

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.22.1'
- name: Build
run: go build -v ./...

- name: Build
run: go build -v ./...
- name: Vet
run: go vet ./...

- name: Test
run: go test -v ./...
- name: Test
run: go test -race -v ./...
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Editor swap files
*.swp
*.swo

# macOS
.DS_Store

# Test binaries and coverage
*.test
coverage.out

# Compiled generator binary
/deep-gen
88 changes: 88 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Changelog

## v5.0.0 (in development)

Major rewrite. Breaking changes from v4.

### Architecture

- **Flat operation model**: `Patch[T]` is now a plain `[]Operation` rather than a recursive tree. Operations have `Kind`, `Path` (JSON Pointer), `Old`, `New`, `If`, and `Unless` fields.
- **Code generation**: `cmd/deep-gen` produces `*_deep.go` files with reflection-free `Patch`, `Diff`, `Equal`, and `Clone` methods — typically 10–15x faster than the reflection fallback.
- **Reflection fallback**: Types without generated code fall through to the v4-based internal engine automatically.

### New API (`github.com/brunoga/deep/v5`)

| Function | Description |
|---|---|
| `Diff[T](a, b T) (Patch[T], error)` | Compare two values; returns error for unsupported types |
| `Apply[T](*T, Patch[T], ...ApplyOption) error` | Apply a patch; returns `*ApplyError` with `Unwrap() []error` |
| `Equal[T](a, b T) bool` | Deep equality |
| `Clone[T](v T) T` | Deep copy (formerly `Copy`) |
| `Set[T,V](Path[T,V], V) Op` | Typed replace operation constructor |
| `Add[T,V](Path[T,V], V) Op` | Typed add operation constructor |
| `Remove[T,V](Path[T,V]) Op` | Typed remove operation constructor |
| `Move[T,V](from, to Path[T,V]) Op` | Typed move operation constructor |
| `Copy[T,V](from, to Path[T,V]) Op` | Typed copy operation constructor |
| `Edit[T](*T) *Builder[T]` | Returns a fluent patch builder |
| `Merge[T](base, other, resolver)` | Deduplicate ops by path; resolver called on conflicts, otherwise other wins |
| `Field[T,V](selector)` | Type-safe path from a selector function |
| `At[T,S,E](Path[T,S], int) Path[T,E]` | Extend a slice-field path to an element by index |
| `MapKey[T,M,K,V](Path[T,M], K) Path[T,V]` | Extend a map-field path to a value by key |
| `WithLogger(*slog.Logger) ApplyOption` | Pass a logger to a single Apply call |
| `ParseJSONPatch[T]([]byte) (Patch[T], error)` | Parse RFC 6902 + deep extensions back into a Patch |
| `ConflictResolver` (interface) | Implement `Resolve(path string, local, remote any) any` to customize `Merge` |

**`Patch[T]` methods:**

| Method | Description |
|---|---|
| `Patch.IsEmpty() bool` | Reports whether the patch has no operations |
| `Patch.AsStrict() Patch[T]` | Returns a copy with strict Old-value verification enabled |
| `Patch.WithGuard(*Condition) Patch[T]` | Returns a copy with a global guard condition set |
| `Patch.Reverse() Patch[T]` | Returns the inverse patch (undo) |
| `Patch.ToJSONPatch() ([]byte, error)` | Serialize to RFC 6902 JSON Patch with deep extensions |
| `Patch.String() string` | Human-readable summary of operations |

### `condition` package (`github.com/brunoga/deep/v5/condition`)

Public package used directly by generated `*_deep.go` files. Most callers will not need to import it directly.

- `Condition` — Serializable predicate struct (`Op`, `Path`, `Value`, `Sub`).
- `Evaluate(root reflect.Value, c *Condition) (bool, error)` — Evaluate a condition against a value.
- `CheckType(v any, typeName string) bool` — Runtime type name check (used in generated code).
- `ToPredicate() / FromPredicate()` — Convert `Condition` to/from the JSON Patch wire-format map.
- `Eq`, `Ne`, `Gt`, `Ge`, `Lt`, `Le`, `Exists`, `In`, `Matches`, `Type`, `And`, `Or`, `Not` — Condition operator constants.

### Condition / Guard system

- `Condition` struct with `Op`, `Path`, `Value`, `Sub` fields (serializable predicates).
- Patch-level guard set via `Patch.Guard` field or `patch.WithGuard(c)`.
- Per-operation conditions via `Operation.If` / `Operation.Unless`.
- Builder helpers: `Eq`, `Ne`, `Gt`, `Ge`, `Lt`, `Le`, `Exists`, `In`, `Matches`, `Type`, `And`, `Or`, `Not`.
- Per-op conditions attached to `Op` values via `Op.If` / `Op.Unless`; passed to the builder via `Builder.With`.

### CRDTs (`github.com/brunoga/deep/v5/crdt`)

- `CRDT[T]` — Concurrency-safe CRDT wrapper. Create with `NewCRDT(initial, nodeID)`. Key methods: `Edit(fn)`, `ApplyDelta(delta)`, `Merge(other)`, `View()`. JSON-serializable.
- `Delta[T]` — A timestamped set of changes produced by `CRDT.Edit`; send to peers and apply with `CRDT.ApplyDelta`.
- `LWW[T]` — Embeddable Last-Write-Wins register. Update with `Set(v, ts)`; accepts write only if `ts` is strictly newer.
- `Text` (`[]TextRun`) — Convergent collaborative text. Merge concurrent edits with `MergeTextRuns(a, b)`.

**`github.com/brunoga/deep/v5/crdt/hlc`**

- `Clock` — Per-node HLC state. Create with `NewClock(nodeID)`. Methods: `Now()`, `Update(remote)`, `Reserve(n)`.
- `HLC` — Timestamp struct (wall time + logical counter + node ID). Total ordering via `Compare` / `After`.

### Breaking changes from v4

- Import path: `github.com/brunoga/deep/v4` → `github.com/brunoga/deep/v5`
- `Diff` now returns `(Patch[T], error)` instead of `Patch[T]`.
- `Patch` is now generic (`Patch[T]`); patches are not cross-type compatible.
- `Patch.Condition` renamed to `Patch.Guard`; `WithCondition` → `WithGuard`.
- Global `Logger`/`SetLogger` removed; pass `WithLogger(l)` as an `Apply` option for per-call logging.
- `cond/` package removed; conditions live in `github.com/brunoga/deep/v5/condition`.
- `deep-gen` now writes output to `{type}_deep.go` by default instead of stdout.
- `OpAdd` on slices sets by index rather than inserting; true insertion is not supported for unkeyed slices.
- `Copy[T](v T) T` renamed to `Clone[T](v T) T`; `Copy` is now the patch-op constructor `Copy[T,V](from, to Path[T,V]) Op`.
- `Builder.Set/Add/Remove/Move/Copy` methods removed; use `Builder.With(deep.Set(...), ...)` instead.
- `Builder.If/Unless` methods removed; attach per-op conditions on the `Op` value before passing to `With`.
Loading
Loading