deep

package module
v5.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 25, 2026 License: Apache-2.0 Imports: 9 Imported by: 0

README

Deep v5: The High-Performance Type-Safe Synchronization Toolkit

deep is a comprehensive Go library for comparing, cloning, and synchronizing complex data structures. Deep introduces a revolutionary architecture centered on Code Generation and Type-Safe Selectors, delivering up to 15x performance improvements over traditional reflection-based libraries.

Key Features

  • Extreme Performance: Reflection-free operations via deep-gen (10x-20x faster than v4).
  • Compile-Time Safety: Type-safe field selectors replace brittle string paths.
  • Data-Oriented: Patches are pure, flat data structures, natively serializable to JSON.
  • Integrated Causality: Native support for HLC (Hybrid Logical Clocks) and LWW (Last-Write-Wins).
  • First-Class CRDTs: Built-in support for Text and LWW[T] convergent registers.
  • Standard Compliant: Export to RFC 6902 JSON Patch with advanced predicate extensions.
  • Hybrid Architecture: Optimized generated paths with a robust reflection safety net.

Performance Comparison (Deep Generated vs v4 Reflection)

Benchmarks performed on typical struct models (User with IDs, Names, Slices):

Operation v4 (Reflection) Deep (Generated) Speedup
Apply Patch 726 ns/op 50 ns/op 14.5x
Diff + Apply 2,391 ns/op 270 ns/op 8.8x
Clone 1,872 ns/op 290 ns/op 6.4x
Equality 202 ns/op 84 ns/op 2.4x

Run go test -bench=. ./... to reproduce. BenchmarkApplyGenerated uses generated code; BenchmarkApplyReflection uses the fallback path on a type with no generated code.

Quick Start

1. Define your models
type User struct {
    ID    int            `json:"id"`
    Name  string         `json:"name"`
    Roles []string       `json:"roles"`
    Score map[string]int `json:"score"`
}
2. Generate optimized code

Add a go:generate directive to your source file:

//go:generate go run github.com/brunoga/deep/v5/cmd/deep-gen -type=User .

Then run:

go generate ./...

This writes user_deep.go in the same directory. Commit it alongside your source.

3. Use the Type-Safe API
import deep "github.com/brunoga/deep/v5"

u1 := User{ID: 1, Name: "Alice", Roles: []string{"user"}}
u2 := User{ID: 1, Name: "Bob", Roles: []string{"user", "admin"}}

// State-based Diffing
patch, err := deep.Diff(u1, u2)
if err != nil {
    log.Fatal(err)
}

// Operation-based Building (Fluent, Type-Safe API)
namePath  := deep.Field(func(u *User) *string        { return &u.Name  })
scorePath := deep.Field(func(u *User) *map[string]int { return &u.Score })

patch2 := deep.Edit(&u1).
    With(
        deep.Set(namePath, "Alice Smith"),
        deep.Add(deep.MapKey(scorePath, "power"), 100),
    ).
    Build()

// Application
if err := deep.Apply(&u1, patch); err != nil {
    log.Fatal(err)
}

Advanced Features

Integrated CRDTs

Convert any field into a convergent register:

type Document struct {
    Title   crdt.LWW[string] // Native Last-Write-Wins
    Content crdt.Text        // Collaborative Text CRDT
}
Conditional Patching

Apply changes only if specific business rules are met:

namePath := deep.Field(func(u *User) *string { return &u.Name })
idPath   := deep.Field(func(u *User) *int    { return &u.ID   })

patch := deep.Edit(&u).
    With(deep.Set(namePath, "New Name").If(deep.Eq(idPath, 1))).
    Build()

Apply a patch only if a global guard condition holds:

patch = patch.WithGuard(deep.Gt(deep.Field(func(u *User) *int { return &u.ID }), 0))
Observability

Embed OpLog operations in a patch to emit structured trace messages during Apply. Route them to any *slog.Logger — useful for request-scoped loggers, test capture, or tracing without touching your model types:

namePath := deep.Field(func(u *User) *string { return &u.Name })

patch := deep.Edit(&u).
    Log("starting update").
    With(deep.Set(namePath, "Alice Smith")).
    Log("update complete").
    Build()

logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
deep.Apply(&u, patch, deep.WithLogger(logger))
// {"level":"INFO","msg":"deep log","message":"starting update","path":"/"}
// {"level":"INFO","msg":"deep log","message":"update complete","path":"/"}

When no logger is provided, slog.Default() is used — so existing slog.SetDefault configuration is respected without any extra wiring.

Patch Utilities
// Reverse a patch to produce an undo patch.
undo := patch.Reverse()

// Enable strict mode — Apply verifies Old values match before each operation.
strictPatch := patch.AsStrict()
Standard Interop

Export your Deep patches to standard RFC 6902 JSON Patch format, and parse them back:

jsonData, err := patch.ToJSONPatch()
// Output: [{"op":"replace","path":"/name","value":"Bob"}]

restored, err := deep.ParseJSONPatch[User](jsonData)

JSON deserialization note: When a patch is JSON-encoded and then decoded, numeric values in Operation.Old and Operation.New are unmarshaled as float64 (standard Go JSON behavior). Generated Patch methods handle this automatically with numeric coercion. If you use the reflection fallback, be aware of this when inspecting Old/New directly.

Architecture: Why v5?

v4 used a Recursive Tree Patch model. Every field was a nested patch object. While flexible, this caused high memory allocations and made serialization difficult.

Deep uses a Flat Operation Model. A patch is a simple slice of Operations. This makes patches:

  1. Portable: Trivially serializable to any format.
  2. Fast: Iterating a slice is much faster than traversing a tree.
  3. Composable: Merging two patches is a stateless operation.

License

Apache 2.0

Documentation

Overview

Package deep provides high-performance, type-safe deep diff, copy, equality, and patch-apply operations for Go values.

Architecture

Deep operates on Patch values — flat, serializable lists of Operation records describing changes between two values of the same type. The four core operations are:

  • Diff computes the patch from a to b.
  • Apply applies a patch to a target pointer.
  • Equal reports whether two values are deeply equal.
  • Clone returns a deep copy of a value.

Code Generation

For production use, run deep-gen to generate reflection-free implementations of all four operations for your types:

//go:generate go run github.com/brunoga/deep/v5/cmd/deep-gen -type=MyType .

Generated code is 4–14x faster than the reflection fallback and is used automatically — no API changes required. The reflection engine remains as a transparent fallback for types without generated code.

Patch Construction

Patches can be computed via Diff or built manually with Edit. Typed operation constructors (Set, Add, Remove, Move, Copy) return an Op value that can be passed to Builder.With for a fluent, type-safe chain:

patch := deep.Edit(&user).
    With(
        deep.Set(nameField, "Alice"),
        deep.Set(ageField, 30).If(deep.Gt(ageField, 0)),
    ).
    Guard(deep.Gt(ageField, 18)).
    Build()

Field creates type-safe path selectors from struct field accessors. At and MapKey extend paths into slices and maps with full type safety.

Conditions

Per-operation guards are attached to Op values via Op.If and Op.Unless. A global patch guard is set via Builder.Guard or Patch.WithGuard. Conditions are serializable and survive JSON round-trips.

Causality and CRDTs

The [crdt] sub-package provides [crdt.LWW], a generic Last-Write-Wins register; [crdt.CRDT], a concurrency-safe wrapper for any type; and [crdt.Text], a convergent collaborative text type.

Serialization

Patch marshals to/from JSON natively. Patch.ToJSONPatch and ParseJSONPatch interoperate with RFC 6902 JSON Patch (with deep extensions for conditions and log operations).

Index

Constants

View Source
const (
	OpAdd     = engine.OpAdd
	OpRemove  = engine.OpRemove
	OpReplace = engine.OpReplace
	OpMove    = engine.OpMove
	OpCopy    = engine.OpCopy
	OpLog     = engine.OpLog
)

Variables

This section is empty.

Functions

func And

func And(conds ...*condition.Condition) *condition.Condition

And combines multiple conditions with logical AND.

func Apply

func Apply[T any](target *T, p Patch[T], opts ...ApplyOption) error

Apply applies a Patch to a target pointer. v5 prioritizes the generated Patch method but falls back to reflection if needed.

Note: when a Patch has been serialized to JSON and decoded, numeric values in Operation.Old and Operation.New will be float64 regardless of the original type. This affects strict-mode Old-value checks.

func Clone

func Clone[T any](v T) T

Clone returns a deep copy of v.

func Eq

func Eq[T, V any](p Path[T, V], val V) *condition.Condition

Eq creates an equality condition.

func Equal

func Equal[T any](a, b T) bool

Equal returns true if a and b are deeply equal.

func Exists

func Exists[T, V any](p Path[T, V]) *condition.Condition

Exists creates a condition that checks if a path exists.

func Ge

func Ge[T, V any](p Path[T, V], val V) *condition.Condition

Ge creates a greater-than-or-equal condition.

func Gt

func Gt[T, V any](p Path[T, V], val V) *condition.Condition

Gt creates a greater-than condition.

func In

func In[T, V any](p Path[T, V], vals []V) *condition.Condition

In creates a condition that checks if a value is in a list.

func Le

func Le[T, V any](p Path[T, V], val V) *condition.Condition

Le creates a less-than-or-equal condition.

func Lt

func Lt[T, V any](p Path[T, V], val V) *condition.Condition

Lt creates a less-than condition.

func Matches

func Matches[T, V any](p Path[T, V], regex string) *condition.Condition

Matches creates a regex condition.

func Ne

func Ne[T, V any](p Path[T, V], val V) *condition.Condition

Ne creates a non-equality condition.

func Not

Not inverts a condition.

func Or

func Or(conds ...*condition.Condition) *condition.Condition

Or combines multiple conditions with logical OR.

func Type

func Type[T, V any](p Path[T, V], typeName string) *condition.Condition

Type creates a type-check condition.

Types

type ApplyError

type ApplyError struct {
	Errors []error
}

ApplyError represents one or more errors that occurred during patch application.

func (*ApplyError) Error

func (e *ApplyError) Error() string

func (*ApplyError) Unwrap

func (e *ApplyError) Unwrap() []error

Unwrap implements the errors.Join interface, allowing errors.Is and errors.As to inspect individual errors within the ApplyError.

type ApplyOption

type ApplyOption func(*applyConfig)

ApplyOption configures the behaviour of Apply.

func WithLogger

func WithLogger(l *slog.Logger) ApplyOption

WithLogger sets the slog.Logger used for OpLog operations within a single Apply call. If not provided, slog.Default is used.

type Builder

type Builder[T any] struct {
	// contains filtered or unexported fields
}

Builder constructs a Patch via a fluent chain.

func Edit

func Edit[T any](_ *T) *Builder[T]

Edit returns a Builder for constructing a Patch[T]. The target argument is used only for type inference and is not stored; the builder produces a standalone Patch, not a live view of the target.

func (*Builder[T]) Build

func (b *Builder[T]) Build() Patch[T]

Build assembles and returns the completed Patch.

func (*Builder[T]) Guard

func (b *Builder[T]) Guard(c *condition.Condition) *Builder[T]

Guard sets the global guard condition on the patch. If Guard has already been called, the new condition is ANDed with the existing one rather than replacing it — calling Guard twice is equivalent to Guard(And(c1, c2)).

func (*Builder[T]) Log

func (b *Builder[T]) Log(msg string) *Builder[T]

Log appends a log operation.

func (*Builder[T]) With

func (b *Builder[T]) With(ops ...Op) *Builder[T]

With appends one or more operations to the patch being built. Obtain operations from the typed constructors Set, Add, Remove, Move, and Copy; per-operation conditions can be attached with Op.If and Op.Unless before passing here.

type ConflictResolver

type ConflictResolver interface {
	Resolve(path string, local, remote any) any
}

ConflictResolver defines how to resolve merge conflicts.

type Op

type Op struct {
	// contains filtered or unexported fields
}

Op is a pending patch operation. Obtain one from Set, Add, Remove, Move, or Copy; attach per-operation conditions with Op.If or Op.Unless before passing to Builder.With.

func Add

func Add[T, V any](p Path[T, V], val V) Op

Add returns a type-safe add (insert) operation.

func Copy

func Copy[T, V any](from, to Path[T, V]) Op

Copy returns a type-safe copy operation that duplicates the value at from to to. Both paths must share the same value type V.

func Move

func Move[T, V any](from, to Path[T, V]) Op

Move returns a type-safe move operation that relocates the value at from to to. Both paths must share the same value type V.

func Remove

func Remove[T, V any](p Path[T, V]) Op

Remove returns a type-safe remove operation.

func Set

func Set[T, V any](p Path[T, V], val V) Op

Set returns a type-safe replace operation.

func (Op) If

func (o Op) If(c *condition.Condition) Op

If attaches a condition that must hold for this operation to be applied.

func (Op) Unless

func (o Op) Unless(c *condition.Condition) Op

Unless attaches a condition that must NOT hold for this operation to be applied.

type OpKind

type OpKind = engine.OpKind

OpKind represents the type of operation in a patch.

type Operation

type Operation = engine.Operation

Operation is an alias for the internal engine operation type.

Note: after JSON round-trip, numeric Old/New values become float64.

type Patch

type Patch[T any] struct {

	// Guard is a global Condition that must be satisfied before any operation
	// in this patch is applied. Set via WithGuard or Builder.Guard.
	Guard *condition.Condition `json:"cond,omitempty"`

	// Operations is a flat list of changes.
	Operations []Operation `json:"ops"`

	// Strict mode enables Old value verification.
	Strict bool `json:"strict,omitempty"`
	// contains filtered or unexported fields
}

Patch is a pure data structure representing a set of changes to type T. It is designed to be easily serializable and manipulatable.

func Diff

func Diff[T any](a, b T) (Patch[T], error)

Diff compares two values and returns a Patch describing the changes from a to b. Generated types (produced by deep-gen) dispatch to a reflection-free implementation. For other types, Diff falls back to the reflection engine which may return an error for unsupported kinds (chan, func, etc.).

func Merge

func Merge[T any](base, other Patch[T], r ConflictResolver) Patch[T]

Merge combines two patches into a single patch, resolving conflicts. Operations are deduplicated by path. When both patches modify the same path, r.Resolve is called if r is non-nil; otherwise other's operation wins over base. The output operations are sorted by path for deterministic ordering.

func ParseJSONPatch

func ParseJSONPatch[T any](data []byte) (Patch[T], error)

ParseJSONPatch parses a JSON Patch document (RFC 6902 plus deep extensions) back into a Patch[T]. This is the inverse of Patch.ToJSONPatch().

func (Patch[T]) AsStrict

func (p Patch[T]) AsStrict() Patch[T]

AsStrict returns a new patch with strict mode enabled. When strict mode is on, every Replace and Remove operation verifies the current value matches Op.Old before applying; mismatches return an error.

func (Patch[T]) IsEmpty

func (p Patch[T]) IsEmpty() bool

IsEmpty reports whether the patch contains no operations.

func (Patch[T]) Reverse

func (p Patch[T]) Reverse() Patch[T]

Reverse returns a new patch that undoes the changes in this patch.

func (Patch[T]) String

func (p Patch[T]) String() string

String returns a human-readable summary of the patch operations.

func (Patch[T]) ToJSONPatch

func (p Patch[T]) ToJSONPatch() ([]byte, error)

ToJSONPatch returns a JSON Patch representation compatible with RFC 6902 and the github.com/brunoga/jsonpatch extensions.

func (Patch[T]) WithGuard

func (p Patch[T]) WithGuard(c *condition.Condition) Patch[T]

WithGuard returns a new patch with the global guard condition set.

type Path

type Path[T, V any] struct {
	// contains filtered or unexported fields
}

Path represents a type-safe path to a field of type V within type T.

func At

func At[T any, S ~[]E, E any](p Path[T, S], i int) Path[T, E]

At returns a type-safe path to the element at index i within a slice field.

func Field

func Field[T, V any](s func(*T) *V) Path[T, V]

Field creates a new type-safe path from a selector function.

func MapKey

func MapKey[T any, M ~map[K]V, K comparable, V any](p Path[T, M], k K) Path[T, V]

MapKey returns a type-safe path to the value at key k within a map field.

func (Path[T, V]) String

func (p Path[T, V]) String() string

String returns the string representation of the path. Paths built from a selector resolve lazily; the result is cached per selector function so repeated calls are O(1) after the first.

Directories

Path Synopsis
cmd
deep-gen command
Package crdt provides Conflict-free Replicated Data Types (CRDTs) built on top of the deep patch engine.
Package crdt provides Conflict-free Replicated Data Types (CRDTs) built on top of the deep patch engine.
hlc
Package hlc implements a Hybrid Logical Clock (HLC) for distributed causality tracking.
Package hlc implements a Hybrid Logical Clock (HLC) for distributed causality tracking.
examples
atomic_config command
Code generated by deep-gen.
Code generated by deep-gen.
audit_logging command
Code generated by deep-gen.
Code generated by deep-gen.
concurrent_updates command
Code generated by deep-gen.
Code generated by deep-gen.
config_manager command
Code generated by deep-gen.
Code generated by deep-gen.
crdt_sync command
http_patch_api command
Code generated by deep-gen.
Code generated by deep-gen.
json_interop command
Code generated by deep-gen.
Code generated by deep-gen.
keyed_inventory command
Code generated by deep-gen.
Code generated by deep-gen.
lww_fields command
move_detection command
multi_error command
Code generated by deep-gen.
Code generated by deep-gen.
policy_engine command
Code generated by deep-gen.
Code generated by deep-gen.
state_management command
Code generated by deep-gen.
Code generated by deep-gen.
struct_map_keys command
Code generated by deep-gen.
Code generated by deep-gen.
text_sync command
three_way_merge command
Code generated by deep-gen.
Code generated by deep-gen.
websocket_sync command
Code generated by deep-gen.
Code generated by deep-gen.
internal
testmodels
Code generated by deep-gen.
Code generated by deep-gen.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL