hook

package
v1.6.0 Latest Latest
Warning

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

Go to latest
Published: Aug 3, 2026 License: MIT Imports: 5 Imported by: 6

Documentation

Overview

Package hook provides the privacy and lifecycle hook system for Grove.

Hooks run before and after database operations, enabling:

  • Multi-tenant isolation (inject WHERE tenant_id = ?)
  • PII redaction (redact fields tagged with grove:",privacy:pii")
  • Audit logging (log mutations to Chronicle)
  • Access control (deny unauthorized operations)

The hook system does not implement authorization logic — it provides the integration point for any permissions library.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func RunModelAfterDelete

func RunModelAfterDelete(ctx context.Context, qc *QueryContext, model any) error

RunModelAfterDelete calls AfterDeleteHook on the model if implemented.

func RunModelAfterInsert

func RunModelAfterInsert(ctx context.Context, qc *QueryContext, model any) error

RunModelAfterInsert calls AfterInsertHook on the model if implemented.

func RunModelAfterScan

func RunModelAfterScan(ctx context.Context, qc *QueryContext, model any) error

RunModelAfterScan calls AfterScanHook on the model if implemented.

func RunModelAfterUpdate

func RunModelAfterUpdate(ctx context.Context, qc *QueryContext, model any) error

RunModelAfterUpdate calls AfterUpdateHook on the model if implemented.

func RunModelBeforeDelete

func RunModelBeforeDelete(ctx context.Context, qc *QueryContext, model any) error

RunModelBeforeDelete calls BeforeDeleteHook on the model if implemented.

func RunModelBeforeInsert

func RunModelBeforeInsert(ctx context.Context, qc *QueryContext, model any) error

RunModelBeforeInsert calls BeforeInsertHook on the model if implemented.

func RunModelBeforeScan

func RunModelBeforeScan(ctx context.Context, qc *QueryContext, model any) error

RunModelBeforeScan calls BeforeScanHook on the model if implemented.

func RunModelBeforeUpdate

func RunModelBeforeUpdate(ctx context.Context, qc *QueryContext, model any) error

RunModelBeforeUpdate calls BeforeUpdateHook on the model if implemented.

Types

type AfterDeleteHook

type AfterDeleteHook interface {
	AfterDelete(ctx context.Context, qc *QueryContext) error
}

AfterDeleteHook is called after a successful DELETE operation.

type AfterInsertHook

type AfterInsertHook interface {
	AfterInsert(ctx context.Context, qc *QueryContext) error
}

AfterInsertHook is called after a successful INSERT operation.

type AfterScanHook

type AfterScanHook interface {
	AfterScan(ctx context.Context, qc *QueryContext) error
}

AfterScanHook is called after scanning query results into the model. Useful for computed fields, decryption, or post-load transformations.

type AfterUpdateHook

type AfterUpdateHook interface {
	AfterUpdate(ctx context.Context, qc *QueryContext) error
}

AfterUpdateHook is called after a successful UPDATE operation.

type BeforeDeleteHook

type BeforeDeleteHook interface {
	BeforeDelete(ctx context.Context, qc *QueryContext) error
}

BeforeDeleteHook is called before a DELETE operation.

type BeforeInsertHook

type BeforeInsertHook interface {
	BeforeInsert(ctx context.Context, qc *QueryContext) error
}

BeforeInsertHook is called before an INSERT operation. The model can modify itself (e.g., set CreatedAt) or return an error to abort.

type BeforeScanHook

type BeforeScanHook interface {
	BeforeScan(ctx context.Context, qc *QueryContext) error
}

BeforeScanHook is called before scanning query results into the model.

type BeforeUpdateHook

type BeforeUpdateHook interface {
	BeforeUpdate(ctx context.Context, qc *QueryContext) error
}

BeforeUpdateHook is called before an UPDATE operation.

type Condition

type Condition struct {
	Column   string
	Operator string
	Value    any
}

Condition represents a parsed condition from a query (informational).

type Decision

type Decision int

Decision represents what a hook wants to do.

const (
	// Allow proceeds with the query.
	Allow Decision = iota
	// Deny blocks the query and returns an error.
	Deny
	// Modify indicates the hook modified the query context; re-evaluate.
	Modify
	// Skip excludes this row/document in results (post-query).
	Skip
)

type Engine

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

Engine manages hook registration and execution.

func NewEngine

func NewEngine() *Engine

NewEngine creates a new hook engine.

func (*Engine) AddHook

func (e *Engine) AddHook(h any, scope ...Scope)

AddHook registers a hook with the given scope. The hook must implement at least one of PreQueryHook, PostQueryHook, PreMutationHook, PostMutationHook.

func (*Engine) RunPostMutation

func (e *Engine) RunPostMutation(ctx context.Context, qc *QueryContext, data, result any) error

RunPostMutation executes all matching PostMutationHook hooks.

func (*Engine) RunPostQuery

func (e *Engine) RunPostQuery(ctx context.Context, qc *QueryContext, result any) error

RunPostQuery executes all matching PostQueryHook hooks.

func (*Engine) RunPreMutation

func (e *Engine) RunPreMutation(ctx context.Context, qc *QueryContext, data any) (*HookResult, error)

RunPreMutation executes all matching PreMutationHook hooks.

func (*Engine) RunPreQuery

func (e *Engine) RunPreQuery(ctx context.Context, qc *QueryContext) (*HookResult, error)

RunPreQuery executes all matching PreQueryHook hooks. Returns aggregated extra filters and any deny error.

func (*Engine) RunStreamRowHook

func (e *Engine) RunStreamRowHook(ctx context.Context, qc *QueryContext, row any) (int, error)

RunStreamRowHook executes all matching StreamRowHook hooks for a single streamed row. Returns the decision as an int (matching Decision constants) and any error. A Skip decision means the row should be skipped; a Deny decision means iteration should stop.

type ExtraFilter

type ExtraFilter struct {
	// Clause is a raw WHERE fragment with placeholders.
	// e.g., "tenant_id = $1"
	Clause string
	Args   []any

	// NativeFilter is a driver-specific filter document (e.g., bson.M for MongoDB).
	NativeFilter any
}

ExtraFilter is a condition that a hook wants to inject into the query.

type HookResult

type HookResult struct {
	Decision Decision
	Error    error         // Set when Decision == Deny.
	Filters  []ExtraFilter // Additional conditions to inject (pre-query).
}

HookResult is returned by pre-query/pre-mutation hooks.

type Operation

type Operation int

Operation represents the type of database operation.

const (
	OpSelect Operation = iota
	OpInsert
	OpUpdate
	OpDelete
	OpBulkInsert
	OpBulkUpdate
	OpBulkDelete
	OpAggregate // For NoSQL aggregation pipelines
)

func (Operation) String

func (op Operation) String() string

String returns a human-readable name for the operation.

type PostMutationHook

type PostMutationHook interface {
	AfterMutation(ctx context.Context, qc *QueryContext, data any, result any) error
}

PostMutationHook runs after mutations.

type PostQueryHook

type PostQueryHook interface {
	AfterQuery(ctx context.Context, qc *QueryContext, result any) error
}

PostQueryHook runs after query execution with the results.

type PreMutationHook

type PreMutationHook interface {
	BeforeMutation(ctx context.Context, qc *QueryContext, data any) (*HookResult, error)
}

PreMutationHook runs before mutations (INSERT/UPDATE/DELETE).

type PreQueryHook

type PreQueryHook interface {
	BeforeQuery(ctx context.Context, qc *QueryContext) (*HookResult, error)
}

PreQueryHook runs before the query is executed.

type QueryContext

type QueryContext struct {
	// Operation type.
	Operation Operation

	// Table or collection name.
	Table string

	// Model type (reflect.Type of the struct).
	ModelType reflect.Type

	// Columns being accessed (SELECT) or mutated (INSERT/UPDATE).
	Columns []string

	// PrivacyColumns maps columns to their grove:",privacy:X" classification.
	// Only populated for columns that have a privacy tag.
	PrivacyColumns map[string]string

	// Conditions extracted from WHERE/filter (informational).
	Conditions []Condition

	// RawQuery is the built query string (available after building, before execution).
	RawQuery string

	// RawArgs are the query arguments.
	RawArgs []any

	// TenantID from context, if set.
	TenantID string

	// InTransaction reports whether the query runs inside an explicit
	// transaction (built through a driver's Tx wrapper). Hooks that apply
	// different policies per path — e.g. a tenant backstop that trusts
	// RLS inside stamped transactions but denies pool-path access —
	// branch on this.
	InTransaction bool

	// TagSource indicates whether the model uses grove:"..." or bun:"..." tags.
	TagSource TagSource

	// Values holds user-supplied context values.
	Values map[string]any
}

QueryContext carries metadata about the pending operation.

type Scope

type Scope struct {
	// Tables restricts the hook to these tables. Empty means all tables.
	Tables []string

	// Operations restricts the hook to these operations. Empty means all operations.
	Operations []Operation

	// Priority determines execution order (lower = earlier). Default: 100.
	Priority int
}

Scope determines when a hook applies.

type StreamRowHook

type StreamRowHook interface {
	OnStreamRow(ctx context.Context, qc *QueryContext, row any) (Decision, error)
}

StreamRowHook runs on every row yielded by a stream. This is critical for long-lived streams where permissions can change. Pre-query hooks run once when the stream is opened (filter injection, deny). StreamRowHook runs per-row as each row is decoded from the cursor.

type TagSource

type TagSource int

TagSource indicates which tag system was used for a model field. This mirrors schema.TagSource to avoid an import cycle.

const (
	// TagSourceGrove means grove:"..." tag was present and used.
	TagSourceGrove TagSource = iota
	// TagSourceBun means bun:"..." fallback was used.
	TagSourceBun
	// TagSourceNone means no tag — field name used as column (snake_case).
	TagSourceNone
)

Jump to

Keyboard shortcuts

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