omni

package module
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Aug 31, 2025 License: AGPL-3.0 Imports: 9 Imported by: 0

README

Omni Package

A universal Go module providing core interfaces and implementations for composable primitives with a functional options pattern for clean and flexible instantiation. Omni is designed for building structured, hierarchical data models with built-in serialization support.

Tests Status Go Report Card PkgGoDev License

Introduction

Omni is a powerful Go package that provides a flexible, composable architecture for building structured data models. It's built around the Atom type, which represents nodes in a hierarchical data structure with key-value properties and child nodes.

With Omni, you can:

  • Build complex, type-safe data structures
  • Store and retrieve properties with simple key-value pairs
  • Create hierarchical relationships between data nodes
  • Easily serialize and deserialize data to/from JSON and binary formats
  • Work with hierarchical data in a thread-safe manner
  • Use functional options for clean and flexible instantiation
  • Leverage Go's standard library without external dependencies

Features

  • Composable Data Model: Build complex, hierarchical data structures with ease
  • Thread-Safe: All operations are safe for concurrent use
  • Multiple Serialization Formats: Built-in support for JSON and Go's gob encoding
  • Flexible Property System: Store and retrieve properties with type safety
  • Functional Options: Clean API for configuration and instantiation
  • Zero Dependencies: Uses only the Go standard library

Installation

go get github.com/dracory/omni
Requirements
  • Go 1.18 or higher
  • No external dependencies

Core Concepts

Atom

An Atom is the fundamental building block that represents a node in your data structure. Each atom has:

  • A unique identifier (auto-generated if not provided)
  • A type (required)
  • String key-value properties
  • Child atoms (for hierarchical structures)
Functional Options Pattern

Atoms are created using a functional options pattern for clean and flexible instantiation:

import "github.com/dracory/omni"

// Basic creation with just type (auto-generated ID)
atom := omni.NewAtom("person")

// With custom ID and properties
person := omni.NewAtom("person",
    omni.WithID("user123"),
    omni.WithProperties(map[string]string{
        "name":  "John Doe",
        "email": "john@example.com",
    }),
)

// With children
document := omni.NewAtom("document",
    omni.WithID("doc1"),
    omni.WithChildren([]omni.AtomInterface{
        omni.NewAtom("section", omni.WithID("sec1")),
        omni.NewAtom("section", omni.WithID("sec2")),
    }),
)

Usage

Basic Operations
// Create a new atom
atom := omni.NewAtom("user")


// Set properties
atom.Set("name", "Alice")
name := atom.Get("name")  // "Alice"

// Check if a property exists
if atom.Has("email") {
    // ...
}

// Remove a property
atom.Remove("name")
Working with Children
// Create a parent atom
parent := omni.NewAtom("parent")


// Add children
child1 := omni.NewAtom("child", omni.WithID("child1"))
child2 := omni.NewAtom("child", omni.WithID("child2"))

parent.ChildAdd(child1)
parent.ChildrenAdd([]omni.AtomInterface{child2})

// Get all children
children := parent.ChildrenGet()
for _, child := range children {
    fmt.Println(child.GetID())
}

// Remove a child by ID
parent.ChildDeleteByID("child1")
Serialization
JSON
// Convert atom to JSON
jsonStr, err := atom.ToJSON()
if err != nil {
    // handle error
}

// Pretty-print JSON
jsonPretty, _ := atom.ToJSONPretty()
fmt.Println(jsonPretty)

// Parse JSON to atom
parsedAtom, err := omni.NewAtomFromJSON(jsonStr)
if err != nil {
    // handle error
}
Gob (Binary)
// Encode to binary
data, err := atom.ToGob()
if err != nil {
    // handle error
}

// Decode from binary
newAtom := &omni.Atom{}
err = newAtom.FromGob(data)
if err != nil {
    // handle error
}
Map Conversion
// Convert atom to map
atomMap := atom.ToMap()

// Create atom from map
newAtom, err := omni.NewAtomFromMap(atomMap)
if err != nil {
    // handle error
}

Thread Safety

All operations on atoms are thread-safe, using read-write mutexes to protect concurrent access. The implementation is designed for high concurrency with minimal lock contention.

Examples

Check out the examples directory for complete working examples:

  1. basic/ - Basic usage of atoms and properties
  2. book/ - A more complex example modeling a book with chapters
  3. advanced/ - Concurrent operations and advanced patterns
  4. website/ - A website structure with pages and components

Benchmarks

BenchmarkAtom_Get-8           1000000000   0.000001 ns/op
BenchmarkAtom_Set-8           500000000    0.000003 ns/op
BenchmarkAtom_ToJSON-8        2000000      750 ns/op
BenchmarkAtom_ToGob-8         1000000      1200 ns/op

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Advanced Usage

Serialization and Deserialization

Omni provides comprehensive serialization and deserialization capabilities for atoms:

JSON Serialization
// Convert atoms to JSON string
atoms := []omni.AtomInterface{atom1, atom2}
jsonStr, err := omni.MarshalAtomsToJson(atoms)
if err != nil {
    // Handle error
}

// Convert JSON string back to atoms
atomsFromJson, err := omni.UnmarshalJsonToAtoms(jsonStr)
if err != nil {
    // Handle error
}
Map Conversion
// Convert atoms to maps
atoms := []omni.AtomInterface{atom1, atom2}
maps := omni.ConvertAtomsToMap(atoms)

// Convert maps back to atoms
atomsFromMaps, err := omni.ConvertMapToAtoms(maps)
if err != nil {
    // Handle error
}

// Convert a single map to an atom
atomMap := map[string]any{"id": "my-id", "type": "my-type"}
atom, err := omni.ConvertMapToAtom(atomMap)
if err != nil {
    // Handle error
}
Individual Atom Serialization
// Convert a single atom to a map
m := atom.ToMap()

// Custom JSON serialization
customJSON, _ := json.Marshal(atom.ToMap())
Concurrency Patterns

The implementation is safe for concurrent use. Here's an example of concurrent operations:

var wg sync.WaitGroup
parent := NewAtom("parent")

// Start multiple goroutines adding children
for i := 0; i < 10; i++ {
    wg.Add(1)
    go func(i int) {
        defer wg.Done()
        child := NewAtom("child", WithID(fmt.Sprintf("child-%d", i)))
        parent.AddChild(child)
    }(i)
}

wg.Wait()
// All children will be safely added

Testing

Omni has a comprehensive test suite. To run the tests:

# Run all tests
go test ./...

# Run tests with coverage
go test -cover ./...

# Run tests with verbose output
go test -v ./...
Test Structure

Tests are organized alongside the code they test:

  • atom_test.go - Tests for atom functionality
  • property_test.go - Tests for property functionality
  • functions_test.go - Tests for serialization/deserialization functions

Versioning

Omni follows Semantic Versioning:

  • Major version changes indicate incompatible API changes
  • Minor version changes indicate added functionality in a backward-compatible manner
  • Patch version changes indicate backward-compatible bug fixes

Contributing

Contributions are welcome! Please follow these steps:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Make your changes
  4. Run tests to ensure they pass (go test ./...)
  5. Commit your changes (git commit -m 'Add some amazing feature')
  6. Push to the branch (git push origin feature/amazing-feature)
  7. Open a Pull Request

Please ensure all tests pass and add new tests for any new functionality.

License

This project is licensed under the GNU AGPLv3 - see the LICENSE file for details.

Support

If you encounter any issues or have questions, please file an issue on the GitHub repository.

Acknowledgments

  • Thanks to all contributors who have helped shape this project
  • Inspired by component-based design patterns and functional programming principles

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AtomsToGob added in v0.3.0

func AtomsToGob(atoms []AtomInterface) ([]byte, error)

AtomsToGob encodes a slice of AtomInterface to binary data using the gob package. It encodes each atom using its ToGob method and collects the results.

Business logic: - Handles nil or empty input by returning an empty byte slice - Encodes each atom using its ToGob method - Collects the encoded data for all atoms

Parameters:

  • atoms: slice of AtomInterface to encode

Returns:

  • []byte: gob-encoded binary data
  • error: if encoding fails

func AtomsToJSON added in v0.3.0

func AtomsToJSON(atoms []AtomInterface) (string, error)

AtomsToJSON converts a slice of AtomInterface to a JSON string.

Business logic: - Handles nil input by returning an empty array JSON string - Converts each non-nil atom to a JSON object using ToMap() - Returns a JSON array of atom objects

Returns: - string: JSON-encoded array of atoms - error: if marshaling to JSON fails

func AtomsToMap added in v0.3.0

func AtomsToMap(atoms []AtomInterface) []map[string]any

AtomsToMap converts a slice of AtomInterface to a slice of maps.

Business logic: - Handles nil input by returning nil - Skips nil atoms in the input - Converts each atom to a map using ToMap() - Only includes non-nil results in the output

Parameters:

  • atoms: slice of AtomInterface to convert

Returns:

  • []map[string]any: slice of atom maps (never contains nils)

Types

type Atom

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

Atom is the main implementation of AtomInterface using map[string]string for properties. All properties are stored as strings, and children are stored as a slice of AtomInterface.

There are two special properties: - "id": the unique identifier of the atom - "type": the type of the atom

func FromGob added in v0.4.0

func FromGob(data []byte) (*Atom, error)

FromGob decodes an Atom from gob-encoded data. This is a helper function that creates a new Atom and calls FromGob on it.

func (*Atom) ChildAdd added in v0.4.0

func (a *Atom) ChildAdd(child AtomInterface) AtomInterface

ChildAdd adds a child atom. If child is nil, it's a no-op.

func (*Atom) ChildDeleteByID added in v0.4.0

func (a *Atom) ChildDeleteByID(id string) AtomInterface

ChildDeleteByID removes a child atom by its ID.

func (*Atom) ChildFindByID added in v0.5.0

func (a *Atom) ChildFindByID(id string) AtomInterface

ChildFindByID returns the first immediate child with the given ID, or nil if not found.

func (*Atom) ChildrenAdd added in v0.4.0

func (a *Atom) ChildrenAdd(children []AtomInterface) AtomInterface

ChildrenAdd adds multiple child atoms.

func (*Atom) ChildrenFindByType added in v0.5.0

func (a *Atom) ChildrenFindByType(atomType string) []AtomInterface

ChildrenFindByType returns all immediate children that match the provided type.

func (*Atom) ChildrenGet added in v0.4.0

func (a *Atom) ChildrenGet() []AtomInterface

ChildrenGet returns a copy of the children slice.

func (*Atom) ChildrenLength added in v0.4.0

func (a *Atom) ChildrenLength() int

ChildrenLength returns the number of children.

func (*Atom) ChildrenSet added in v0.4.0

func (a *Atom) ChildrenSet(children []AtomInterface) AtomInterface

ChildrenSet replaces all children with the given slice. Nil children in the input slice will be filtered out.

func (*Atom) FromGob added in v0.4.0

func (a *Atom) FromGob(data []byte) error

FromGob decodes the atom from gob-encoded data. This method satisfies the AtomInterface requirement.

func (*Atom) Get added in v0.4.0

func (a *Atom) Get(key string) string

Get returns the value for the given key, or "" if not found.

func (*Atom) GetAll added in v0.4.0

func (a *Atom) GetAll() map[string]string

GetAll returns all properties of the atom.

func (*Atom) GetID

func (a *Atom) GetID() string

GetID returns the atom's ID.

func (*Atom) GetType

func (a *Atom) GetType() string

GetType returns the atom's type.

func (*Atom) GobDecode added in v0.4.0

func (a *Atom) GobDecode(data []byte) error

GobDecode implements the gob.GobDecoder interface. This is a wrapper around FromGob for compatibility with the gob package.

func (*Atom) GobEncode added in v0.4.0

func (a *Atom) GobEncode() ([]byte, error)

GobEncode implements the gob.GobEncoder interface. This is a wrapper around ToGob for compatibility with the gob package.

func (*Atom) Has added in v0.4.0

func (a *Atom) Has(key string) bool

Has checks if the atom has a property with the given key.

func (*Atom) MemoryUsage added in v0.4.0

func (a *Atom) MemoryUsage() int

MemoryUsage returns the estimated memory usage of the atom in bytes, including all its properties and recursively all its children. This is useful for memory profiling and monitoring. Note: This is an approximation and doesn't account for all memory used by the Go runtime.

func (*Atom) Remove added in v0.4.0

func (a *Atom) Remove(key string) AtomInterface

Remove removes the value for the given key.

func (*Atom) Set added in v0.4.0

func (a *Atom) Set(key, value string) AtomInterface

Set sets the value for the given key.

func (*Atom) SetAll added in v0.4.0

func (a *Atom) SetAll(properties map[string]string) AtomInterface

SetAll sets all properties of the atom.

func (*Atom) SetID

func (a *Atom) SetID(id string) AtomInterface

SetID sets the atom's ID.

func (*Atom) SetType

func (a *Atom) SetType(atomType string) AtomInterface

SetType sets the atom's type.

func (*Atom) ToGob added in v0.3.0

func (a *Atom) ToGob() ([]byte, error)

ToGob encodes the atom to a gob-encoded byte slice. This is the primary method for gob encoding that satisfies the AtomInterface.

func (*Atom) ToJSON added in v0.4.0

func (a *Atom) ToJSON() (string, error)

ToJSON converts the atom to a JSON string.

func (*Atom) ToJSONPretty added in v0.4.0

func (a *Atom) ToJSONPretty() (string, error)

ToJSONPretty converts the atom to a nicely indented JSON string.

func (*Atom) ToMap added in v0.3.0

func (a *Atom) ToMap() map[string]interface{}

ToMap converts the atom to a map representation with the following structure: - id: the atom's ID - type: the atom's type - properties: a map containing all properties (excluding id and type) - children: an array of child atoms

type AtomInterface

type AtomInterface interface {
	// ID returns the unique identifier of the atom
	GetID() string
	SetID(id string) AtomInterface

	// Type returns the type of the atom
	GetType() string
	SetType(atomType string) AtomInterface

	// Property access
	Get(key string) string
	Has(key string) bool
	Remove(key string) AtomInterface
	Set(key, value string) AtomInterface

	GetAll() map[string]string
	SetAll(properties map[string]string) AtomInterface

	// Children management
	ChildAdd(child AtomInterface) AtomInterface
	ChildDeleteByID(id string) AtomInterface
	ChildFindByID(id string) AtomInterface

	ChildrenAdd(children []AtomInterface) AtomInterface
	ChildrenFindByType(atomType string) []AtomInterface
	ChildrenGet() []AtomInterface
	ChildrenSet(children []AtomInterface) AtomInterface

	ChildrenLength() int

	// Serialization
	ToMap() map[string]any
	ToJSON() (string, error)
	ToJSONPretty() (string, error)
	ToGob() ([]byte, error)

	// MemoryUsage returns the estimated memory usage of the atom in bytes,
	// including all its properties and recursively all its children.
	// This is useful for memory profiling and monitoring.
	MemoryUsage() int
}

AtomInterface is the universal interface that all composable primitives must satisfy. It defines the methods necessary for the system to understand and process any atom, regardless of its specific type.

func FindAtomByID added in v0.5.0

func FindAtomByID(root AtomInterface, id string) AtomInterface

FindAtomByID recursively finds an atom by ID in a tree. It performs a pre-order traversal: checks the current node first, then descends into its children in order.

func FindAtomsByType added in v0.5.0

func FindAtomsByType(root AtomInterface, atomType string) []AtomInterface

FindAtomsByType recursively finds all atoms of a specific type in a tree. It performs a pre-order traversal and returns matches in that order.

func FindFirstAtomByType added in v0.5.0

func FindFirstAtomByType(root AtomInterface, atomType string) AtomInterface

FindAtomByType recursively finds the first atom with the given type in a tree. It performs a pre-order traversal: checks the current node first, then its children in order.

func GobToAtom added in v0.3.0

func GobToAtom(data []byte) (AtomInterface, error)

GobToAtom decodes an atom from gob-encoded data.

Business logic: - Decodes the binary data into a temporary struct - Creates a new atom with the decoded type, id, and properties - Recursively decodes and adds child atoms - Returns an error if the data is invalid

Parameters:

  • data: binary data containing the gob-encoded atom

Returns:

  • AtomInterface: the decoded atom
  • error: if decoding fails

func GobToAtoms added in v0.3.0

func GobToAtoms(data []byte) ([]AtomInterface, error)

GobToAtoms decodes multiple atoms from binary data encoded with the gob package. It decodes the data in the format written by AtomsToGob.

Business logic: - Handles empty or nil input by returning an empty slice - Validates the input data structure - Decodes the count of atoms first - Then decodes each atom's data and validates it before conversion - Preserves the order of atoms from the encoded data

Parameters:

  • data: binary data containing gob-encoded atoms

Returns:

  • []AtomInterface: slice of decoded atoms
  • error: if the data cannot be decoded or is invalid

func JSONToAtom added in v0.4.0

func JSONToAtom(jsonStr string) (AtomInterface, error)

JSONToAtom converts a JSON string to a single Atom.

Business logic: - Handles empty input by returning an error - Validates JSON structure before processing - Converts JSON object to an Atom using MapToAtom

Parameters:

  • jsonStr: JSON string containing a single atom's data

Returns:

  • AtomInterface: the parsed atom
  • error: if JSON is invalid or missing required fields

func JSONToAtoms added in v0.3.0

func JSONToAtoms(atomsJson string) ([]AtomInterface, error)

JSONToAtoms converts a JSON string to a slice of AtomInterface.

Business logic: - Handles empty input by returning an empty slice - Supports both array of atoms and single atom object - Validates JSON structure before processing - Converts each JSON object to an Atom using MapToAtom

Parameters:

  • atomsJson: JSON string containing atom data

Returns:

  • []AtomInterface: slice of parsed atoms
  • error: if JSON is invalid or missing required fields

func MapToAtom added in v0.3.0

func MapToAtom(atomMap map[string]any) (AtomInterface, error)

MapToAtom converts a map to an AtomInterface.

The map must represent a valid atom with at least "id" and "type" fields. Properties should be in a nested "properties" map, and children in a "children" slice. For backward compatibility, top-level properties are also supported but not recommended.

Parameters:

  • atomMap: map containing the atom data

Returns:

  • AtomInterface: the converted atom
  • error: if the map is not a valid atom

func MapToAtoms added in v0.3.0

func MapToAtoms(atoms []map[string]any) []AtomInterface

MapToAtoms converts a slice of atom maps to a slice of AtomInterface.

Business logic: - Handles nil input by returning nil - Preserves nil elements in the output for nil inputs - Silently ignores errors from NewAtomFromMap (for backward compatibility) - Maintains the order of atoms from input to output

Note: This function is provided for convenience and backward compatibility. For better error handling, consider using NewAtomFromMap directly.

Parameters:

  • atoms: slice of maps containing atom data

Returns:

  • []AtomInterface: slice of converted atoms (may contain nils)

func NewAtom

func NewAtom(atomType string, opts ...AtomOption) AtomInterface

NewAtom creates a new Atom with the given type and applies the provided options. If no ID is provided via options, a human-readable UID will be generated. Returns an AtomInterface to maintain consistency with other constructors.

func NewAtomFromGob added in v0.3.0

func NewAtomFromGob(data []byte) (AtomInterface, error)

NewAtomFromGob creates a new Atom from binary data encoded with the gob package. This is a convenience function that delegates to GobToAtom.

Example:

data, _ := atom.ToGob()
newAtom, err := NewAtomFromGob(data)

Parameters:

  • data: binary data containing the gob-encoded atom

Returns:

  • AtomInterface: the decoded atom
  • error: if the data is invalid or cannot be decoded

func NewAtomFromJSON added in v0.3.0

func NewAtomFromJSON(jsonStr string) (AtomInterface, error)

NewAtomFromJSON creates a new Atom from a JSON string. This is a convenience function that delegates to JSONToAtom.

The JSON should be an object with at least "id" and "type" fields. Properties should be in a "properties" map, and children in a "children" array.

Parameters:

  • jsonStr: JSON string containing the atom data

Returns:

  • AtomInterface: the parsed atom
  • error: if the JSON is invalid or missing required fields

Example:

jsonStr := `{"id":"atom1","type":"test","properties":{"key":"value"}}`
atom, err := NewAtomFromJSON(jsonStr)

Note: For parsing multiple atoms from a JSON array, use JSONToAtoms instead.

func NewAtomFromMap added in v0.3.0

func NewAtomFromMap(atomMap map[string]any) (AtomInterface, error)

NewAtomFromMap creates a new Atom from a map. This is a convenience function that delegates to MapToAtom.

The map should contain at least "id" and "type" fields. Properties should be in a "properties" map, and children in a "children" slice.

Parameters:

  • atomMap: map containing the atom data

Returns:

  • AtomInterface: the created atom
  • error: if the map is missing required fields or is invalid

Example:

atomMap := map[string]any{
  "id":   "atom1",
  "type": "test",
  "properties": map[string]string{"key": "value"},
}
atom, err := NewAtomFromMap(atomMap)

type AtomOption added in v0.3.0

type AtomOption func(*Atom)

AtomOption configures an Atom.

func WithChildren added in v0.3.0

func WithChildren(children ...AtomInterface) AtomOption

WithChildren adds child atoms to the Atom.

func WithData added in v0.4.0

func WithData(data map[string]string) AtomOption

WithData adds initial data to the Atom. This is a convenience function for setting multiple key-value pairs at once.

func WithID added in v0.3.0

func WithID(id string) AtomOption

WithID sets the ID of the Atom.

func WithProperties added in v0.3.0

func WithProperties(properties map[string]string) AtomOption

WithProperties adds properties to the Atom. Note: This will not set 'id' or 'type' as they are now direct fields.

func WithType added in v0.4.0

func WithType(atomType string) AtomOption

WithType sets the type of the Atom.

Directories

Path Synopsis
examples
advanced command
Advanced example demonstrating concurrent access and property management
Advanced example demonstrating concurrent access and property management
basic command
Basic example demonstrating the core functionality of the omni package.
Basic example demonstrating the core functionality of the omni package.
book command
Book example demonstrating a hierarchical book structure with pages and content.
Book example demonstrating a hierarchical book structure with pages and content.
website command
Website example demonstrating a simple website structure with pages and content.
Website example demonstrating a simple website structure with pages and content.

Jump to

Keyboard shortcuts

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