customstore

package module
v1.6.0 Latest Latest
Warning

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

Go to latest
Published: Sep 7, 2025 License: GPL-3.0 Imports: 17 Imported by: 0

README

customstore Open in Gitpod

Tests Status Go Report Card PkgGoDev

customstore is a Go package that provides a flexible way to store and manage custom records in a database table. It simplifies common database operations like creating, retrieving, updating, and deleting records.

Features

  • Easy Setup: Quickly integrate with your existing database
  • Customizable Records: Define your own record types and data structures
  • Automatic Migration: Automatically create the necessary database table
  • CRUD Operations: Supports standard Create, Read, Update, and Delete operations
  • Flexible Queries: Query records based on various criteria
  • Soft Deletes: Option to soft delete records instead of permanent deletion
  • Payload Search: Search for records based on content within the payload

What's New

  • Multi-ID queries via SetIDList([]string): efficiently fetch multiple records in a single RecordList call.

Quick Start

import (
    "database/sql"
    "github.com/dracory/customstore"
)

func example(db *sql.DB) error {
    store, err := customstore.NewStore(customstore.NewOptions().
        SetDB(db).
        SetTableName("custom_records").
        SetAutomigrateEnabled(true))
    if err != nil { return err }

    // List records by multiple IDs and type
    ids := []string{"rec_1", "rec_2", "rec_3"}
    recs, err := store.RecordList(
        customstore.NewRecordQuery().
            SetType("analysis_report").
            SetIDList(ids),
    )
    if err != nil { return err }

    for _, r := range recs {
        // use r.ID(), r.Type(), r.Payload(), ...
    }
    return nil
}

RecordQuery Cheatsheet

  • Filtering

    • SetID(id string)
    • SetIDList(ids []string)
    • SetType(recordType string)
    • Payload contains: AddPayloadSearch("needle")
    • Payload not contains: AddPayloadSearchNot("needle")
  • Pagination and order

    • SetLimit(n), SetOffset(n)
    • SetOrderBy(column) (default order is descending)
  • Soft delete

    • Excluded by default
    • Include via: SetSoftDeletedIncluded(true)

Notes

  • RecordList returns only non-soft-deleted records by default.
  • SetIDList ignores empty strings; providing an empty slice is treated as no-op (no filter).
  • Debug Mode: Enable debug mode for detailed logging
  • Schema-less Payloads: Store any JSON structure without altering DB schema
  • Document-store Feel on SQL: Document flexibility with SQL power and tooling

Document-store versatility on SQL

Customstore lets you keep your data model fluid like a document store while using a single SQL table under the hood. No migrations for shape changes—just evolve your JSON payloads.

  • Any shape: nested objects, arrays, primitives
  • No schema changes: add/remove fields freely
  • SQL-compatible: keep transactions, indexes, and familiar tooling

Example: store different shapes without migrations

// A user document
user := customstore.NewRecord(
    "user",
    customstore.WithPayloadMap(map[string]any{
        "name":  "Ada",
        "roles": []any{"admin", "editor"},
        "prefs": map[string]any{"theme": "dark"},
    }),
)
_ = store.RecordCreate(user)

// A product document with a very different shape
product := customstore.NewRecord(
    "product",
    customstore.WithPayloadMap(map[string]any{
        "sku":   "SKU-001",
        "price": 19.99,
        "tags":  []any{"new", "promo"},
    }),
)
_ = store.RecordCreate(product)

Query by payload content (driver-dependent implementation uses JSON string search):

// Find active users named Ada
q := customstore.RecordQuery().
    SetType("user").
    AddPayloadSearch(`"name": "Ada"`).
    AddPayloadSearch(`"active": true`)
list, err := store.RecordList(q)
if err != nil { panic(err) }

Installation

go get -u github.com/dracory/customstore

Setup

// Example with SQLite
db, err := sql.Open("sqlite3", "mydatabase.db")
if err != nil {
    panic(err)
}
defer db.Close()

// Initialize the store
customStore, err := customstore.NewStore(customstore.NewStoreOptions{
    DB:                 db,
    TableName:          "my_custom_records",
    AutomigrateEnabled: true,
    DebugEnabled:       false,
})

if err != nil {
    panic(err)
}

Core Concepts

Records

A Record represents a single entry in your custom data store. Each record has:

  • Type: A string that categorizes the record (e.g., "user", "product", "order")
  • ID: A unique identifier for the record
  • Payload: A JSON-encoded string containing the record's data
  • CreatedAt: A timestamp indicating when the record was created
  • UpdatedAt: A timestamp indicating when the record was last updated
  • DeletedAt: A timestamp indicating when the record was soft-deleted (if applicable)
Store

The Store is the main interface for interacting with your custom data store. It provides methods for:

  • Creating records
  • Retrieving records by ID
  • Updating records
  • Deleting records (both hard and soft deletes)
  • Listing records based on various criteria
  • Counting records
RecordQuery

The RecordQuery struct allows you to build complex queries to filter and retrieve records. You can specify:

  • Record type
  • ID
  • Limit and offset for pagination
  • Order by clause
  • Whether to include soft-deleted records
  • Payload search terms

Usage Examples

Creating a Record

You can now pass functional options to the constructor to initialize fields in one place.

record := customstore.NewRecord(
    "person",
    customstore.WithID("person-123"),
    customstore.WithMemo("seed user"),
    customstore.WithPayloadMap(map[string]any{
        "name": "John Doe",
        "age":  30,
    }),
    customstore.WithMetas(map[string]string{
        "role": "admin",
    }),
)

if err := store.RecordCreate(record); err != nil {
    panic(err)
}

Or set a raw JSON payload string at construction time:

record := customstore.NewRecord(
    "order",
    customstore.WithPayload(`{"id":1,"total":19.99}`),
)

Legacy/imperative style (still supported):

record := customstore.NewRecord("person")
record.SetID("person-123")            // optional, auto-generated if not set
record.SetMemo("seed user")            // optional memo
record.SetPayloadMap(map[string]any{   // or use SetPayload with a JSON string
    "name": "John Doe",
    "age":  30,
})
record.SetMetas(map[string]string{
    "role": "admin",
})

if err := store.RecordCreate(record); err != nil {
    panic(err)
}
Finding a Record by ID
record, err := store.RecordFindByID("1234567890")
if err != nil {
    panic(err)
}
Updating a Record
record, err := store.RecordFindByID("1234567890")
if err != nil {
    panic(err)
}

record.SetPayloadMap(map[string]interface{}{
    "name": "John Doe",
    "age":  30,
})

err = store.RecordUpdate(record)
if err != nil {
    panic(err)
}
Deleting a Record (Hard Delete)
record, err := store.RecordFindByID("1234567890")
if err != nil {
    panic(err)
}

err = store.RecordDelete(record)
if err != nil {
    panic(err)
}
Soft Deleting a Record
record, err := store.RecordFindByID("1234567890")
if err != nil {
    panic(err)
}

err = store.RecordSoftDelete(record)
if err != nil {
    panic(err)
}
Listing Records
query := customstore.RecordQuery().SetType("person").SetLimit(10)
list, err := store.RecordList(query)
if err != nil {
    panic(err)
}
Counting Records
query := customstore.RecordQuery().SetType("person")
count, err := store.RecordCount(query)
if err != nil {
    panic(err)
}
query := customstore.RecordQuery().SetType("person").
    AddPayloadSearch(`"status": "active"`).
    AddPayloadSearch(`"name": "John"`)
list, err := store.RecordList(query)
if err != nil {
    panic(err)
}
Soft Deleted Records
query := customstore.RecordQuery().SetType("person").SetSoftDeletedIncluded(true)
list, err := store.RecordList(query)
if err != nil {
    panic(err)
}

API Reference

Store Methods
  • NewStore(options NewStoreOptions) - Creates a new store instance
    • options: A NewStoreOptions struct containing the database connection, table name, and other configuration options
  • AutoMigrate() - Automigrates (creates) the session table
  • DriverName(db *sql.DB) - Finds the driver name from the database
  • EnableDebug(debug bool) - Enables/disables the debug option
  • RecordCreate(record *Record) - Creates a new record
  • RecordFindByID(id string) - Finds a record by its ID
  • RecordUpdate(record *Record) - Updates an existing record
  • RecordDelete(record *Record) - Deletes a record
  • RecordDeleteByID(id string) - Deletes a record by its ID
  • RecordSoftDelete(record *Record) - Soft deletes a record
  • RecordSoftDeleteByID(id string) - Soft deletes a record by its ID
  • RecordList(query *RecordQuery) - Lists records based on a query
  • RecordCount(query *RecordQuery) - Counts records based on a query
RecordQuery Methods
  • SetID(id string) - Sets the ID to search for
  • SetType(recordType string) - Sets the record type to search for
  • SetLimit(limit int) - Sets the maximum number of records to return
  • SetOffset(offset int) - Sets the offset for the records to return
  • SetOrderBy(orderBy string) - Sets the order by clause
  • SetSoftDeletedIncluded(softDeletedIncluded bool) - Sets whether to include soft deleted records
  • AddPayloadSearch(payloadSearch string) - Adds a payload search term

Contributing

Contributions are welcome! Please feel free to submit a pull request.

Documentation

Index

Constants

View Source
const COLUMN_CREATED_AT = "created_at"
View Source
const COLUMN_ID = "id"
View Source
const COLUMN_MEMO = "memo"
View Source
const COLUMN_METAS = "metas"
View Source
const COLUMN_PAYLOAD = "payload"
View Source
const COLUMN_RECORD_TYPE = "record_type"
View Source
const COLUMN_SOFT_DELETED_AT = "soft_deleted_at"
View Source
const COLUMN_UPDATED_AT = "updated_at"

Variables

This section is empty.

Functions

This section is empty.

Types

type NewStoreOptions

type NewStoreOptions struct {
	TableName          string
	DB                 *sql.DB
	DbDriverName       string
	TimeoutSeconds     int64
	AutomigrateEnabled bool
	DebugEnabled       bool
	Logger             *slog.Logger
}

NewStoreOptions define the options for creating a new session store

type RecordInterface

type RecordInterface interface {
	dataobject.DataObjectInterface

	IsSoftDeleted() bool

	CreatedAt() string
	CreatedAtCarbon() *carbon.Carbon
	SetCreatedAt(createdAt string)

	ID() string
	SetID(id string)

	Type() string
	SetType(t string)

	Meta(name string) string
	SetMeta(name, value string) error

	Metas() (map[string]string, error)
	SetMetas(metas map[string]string) error
	UpsertMetas(metas map[string]string) error

	Memo() string
	SetMemo(memo string)

	Payload() string
	SetPayload(payload string)

	PayloadMap() (map[string]any, error)
	SetPayloadMap(payloadMap map[string]any) error
	PayloadMapKey(key string) (any, error)
	SetPayloadMapKey(key string, value any) error

	SoftDeletedAt() string
	SoftDeletedAtCarbon() *carbon.Carbon
	SetSoftDeletedAt(softDeletedAt string)

	UpdatedAt() string
	UpdatedAtCarbon() *carbon.Carbon
	SetUpdatedAt(updatedAt string)
}

RecordInterface represents an record for accessing the API

func NewRecord

func NewRecord(recordType string, opts ...RecordOption) RecordInterface

func NewRecordFromExistingData

func NewRecordFromExistingData(data map[string]string) RecordInterface

type RecordOption

type RecordOption func(RecordInterface) error

RecordOption represents a functional option that mutates a RecordInterface instance during construction or afterwards.

func WithID

func WithID(id string) RecordOption

WithID sets the record ID.

func WithMemo

func WithMemo(memo string) RecordOption

WithMemo sets the record memo.

func WithMetas

func WithMetas(metas map[string]string) RecordOption

WithMetas sets the record metas (overwrites existing metas).

func WithPayload

func WithPayload(payload string) RecordOption

WithPayload sets the record payload (raw JSON string).

func WithPayloadMap

func WithPayloadMap(payloadMap map[string]any) RecordOption

WithPayloadMap sets the record payload from a map (will be marshaled to JSON).

type RecordQueryInterface

type RecordQueryInterface interface {
	Validate() error
	ToSelectDataset(driver string, table string) (selectDataset *goqu.SelectDataset, columns []any, err error)

	IsSoftDeletedIncluded() bool
	SetSoftDeletedIncluded(softDeletedIncluded bool) RecordQueryInterface

	SetColumns(columns []string) RecordQueryInterface
	GetColumns() []string

	IsCountOnly() bool
	SetCountOnly(countOnly bool) RecordQueryInterface

	IsIDSet() bool
	GetID() string
	SetID(id string) RecordQueryInterface

	// Multiple IDs support
	IsIDListSet() bool
	GetIDList() []string
	SetIDList(ids []string) RecordQueryInterface

	IsTypeSet() bool
	GetType() string
	SetType(recordType string) RecordQueryInterface

	IsLimitSet() bool
	GetLimit() int
	SetLimit(limit int) RecordQueryInterface

	IsOffsetSet() bool
	GetOffset() int
	SetOffset(offset int) RecordQueryInterface

	IsOrderBySet() bool
	GetOrderBy() string
	SetOrderBy(orderBy string) RecordQueryInterface

	// Payload search methods
	AddPayloadSearch(needle string) RecordQueryInterface
	GetPayloadSearch() []string
	AddPayloadSearchNot(needle string) RecordQueryInterface
	GetPayloadSearchNot() []string
}

RecordQueryInterface defines the interface for API record query operations

func NewRecordQuery

func NewRecordQuery() RecordQueryInterface

func RecordQuery

func RecordQuery() RecordQueryInterface

RecordQuery shortcut for NewRecordQuery

type StoreInterface

type StoreInterface interface {
	// AutoMigrate migrates the tables
	AutoMigrate() error

	// EnableDebug - enables the debug option
	EnableDebug(debug bool)

	// RecordCount returns the count of records based on a query
	RecordCount(query RecordQueryInterface) (int64, error)

	// RecordCreate creates a new record
	RecordCreate(record RecordInterface) error

	// RecordDelete deletes a record
	RecordDelete(record RecordInterface) error

	// RecordDeleteByID deletes a record by ID
	RecordDeleteByID(id string) error

	// RecordFindByID finds a record by ID
	RecordFindByID(id string) (RecordInterface, error)

	// RecordList returns a list of records
	RecordList(query RecordQueryInterface) ([]RecordInterface, error)

	// RecordSoftDelete soft deletes a record
	RecordSoftDelete(record RecordInterface) error

	// RecordSoftDeleteByID soft deletes a record by ID
	RecordSoftDeleteByID(id string) error

	// RecordUpdate updates a record
	RecordUpdate(record RecordInterface) error
}

func NewStore

func NewStore(opts NewStoreOptions) (StoreInterface, error)

NewStore creates a new session store

Jump to

Keyboard shortcuts

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