quarkdatasource

package module
v1.8.25 Latest Latest
Warning

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

Go to latest
Published: Sep 12, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

README

quarkdatasource

github.com/jcsvwinston/orbit/quarkdatasource

An opt-in implementation of Orbit's datasource contract (orbit ADR-001) over a Quark ORM client, so Data Studio browses and edits Quark-managed models (QADR-0006, Caso 2).

It is the second implementation of the contract — the one that proves the abstraction is not Nucleus-shaped. It lives in its own module so Quark never enters the orbit core's dependency graph.

Usage

import (
    "github.com/jcsvwinston/orbit"
    "github.com/jcsvwinston/orbit/quarkdatasource"
    "github.com/jcsvwinston/quark"
)

client, err := quark.New("pgx", dsn)
// ... client.RegisterModel / migrations as usual ...

ds := quarkdatasource.New(client)
quarkdatasource.Register[User](ds)
quarkdatasource.Register[Post](ds)

app, err := nucleus.New().
    Mount(orbit.Module(orbit.Config{
        Prefix:     "/admin",
        DataSource: ds, // Data Studio now runs on Quark models
    })).
    Build()

Why registration is generic (Register[T])

Quark's query API is typed — quark.For[T](ctx, provider) (its ADR-0002/0014 design) — so a model's CRUD path cannot be bound from a reflect.Type at runtime. Register[T] monomorphizes the typed path once, at wiring time. The model metadata (columns, PK, not-null, unique, relations) comes from the struct's Quark tags via quark.GetModelMetaByType, the same source Quark's own migrations use — not from table introspection, which would lose the Go-level facts.

Presentation metadata Quark deliberately does not carry (labels, HTML input types, list/search/filter flags) is derived from the Go type with permissive defaults: every column is listed and filterable (scalars), string columns are searchable, labels are humanized field names.

Semantics

  • IDs are strings at the boundary (ADR-001 D1), narrowed to the PK field's Go kind. Models with a composite primary key (or none) are catalogued read-only: List/Count work; Get/Create/Update/Delete return an error.
  • Records are the model's JSON object (D2), with every schema field re-keyed to its storage column: Quark models normally carry no json tags, so the raw object would use Go-case keys (CustomerID) while Data Studio reads cells by column (customer_id). Values (including quark.Nullable) and keys outside the schema — relations, extra JSON — pass through unchanged.
  • Search matches any searchable column via a single OR-group built with Quark's expression AST (WhereExpr), AND-composed with exact filters — column names go through Quark's SQLGuard like any builder input.
  • Update uses UpdateMap, so zero values are written (unlike a full-entity save). Delete follows Quark's semantics: soft delete when the model has a deleted_at column, hard otherwise.
  • Totals are real counts over the same filters (IsEstimated is always false).
  • Tenancy: pass a *quark.TenantRouter as the provider and every query runs under Quark's own scoping (WHERE-injection or native RLS). Additionally, WithTenantColumn("tenant_id") marks the tenant field so Data Studio's own tenant filter applies.
  • Store's dbAlias is ignored: a Quark client is bound to one database.

Status

Pre-1.0, alongside Orbit. The datasource contract freezes at Orbit v1.0 (QADR-0005).

Documentation

Overview

Package quarkdatasource implements Orbit's datasource contract (orbit ADR-001) over a Quark ORM client, so Data Studio browses and edits Quark-managed models (quantum QADR-0006, Caso 2).

It is the second implementation of the contract — the one that validates the abstraction did not keep Nucleus's shape. The catalogue comes from the model structs' Quark tags (db/pk/quark), not from table introspection, so Data Studio sees the same Go-level metadata Quark itself uses.

Registration is generic, per model

Quark's query API is typed (quark.For[T]; its ADR-0002/0014 design), so a model's CRUD operations cannot be bound from a reflect.Type at runtime. Each model is registered with a generic call, which monomorphizes the typed query path once at wiring time:

ds := quarkdatasource.New(client)
quarkdatasource.Register[User](ds)
quarkdatasource.Register[Post](ds)

app := nucleus.New().
    Mount(orbit.Module(orbit.Config{Prefix: "/admin", DataSource: ds})).
    Build()

New accepts any quark.ClientProvider: a *quark.Client, or a *quark.TenantRouter so every Data Studio query runs under Quark's own tenant scoping (WHERE-injection or native RLS, per the router's strategy).

Semantics

  • IDs are strings at the boundary (ADR-001 D1) and are narrowed to the PK field's Go kind. Models with a composite primary key are listed read-only: List/Count work, Get/Create/Update/Delete return an error.
  • Records are the model's JSON object (ADR-001 D2), with every schema field re-keyed to its storage column: Quark models normally carry no json tags, so the raw object would use Go-case keys ("CustomerID") while Data Studio reads cells by column ("customer_id"). Values (including quark.Nullable) and keys outside the schema — relations, extra JSON — pass through unchanged.
  • Delete follows Quark's semantics: soft delete when the model has a deleted_at column, hard delete otherwise.
  • A Quark client is bound to one database, so an adapter serves exactly one database alias (WithDatabaseAlias, default "default"): every ModelInfo carries it, and Store refuses any other alias instead of silently answering from the wrong database. Use one adapter per client if you browse several.

Index

Constants

View Source
const DefaultDatabaseAlias = "default"

DefaultDatabaseAlias is the alias an adapter answers to when WithDatabaseAlias is not given; it matches the panel's default alias.

Variables

This section is empty.

Functions

func Register

func Register[T any](a *Adapter) error

Register adds model T to the adapter's catalogue and builds its typed record store. T must be a struct with Quark tags. Registering the same name twice replaces the previous entry.

Types

type Adapter

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

Adapter implements datasource.DataSource over a Quark client. Populate it with Register[T] for each model; registration order is preserved in All.

func New

func New(provider quark.ClientProvider, opts ...Option) *Adapter

New returns an empty adapter bound to provider (a *quark.Client or a *quark.TenantRouter). Register models with Register[T].

func (*Adapter) All

func (a *Adapter) All() []datasource.ModelInfo

All returns the registered models in registration order.

func (*Adapter) DatabaseAlias

func (a *Adapter) DatabaseAlias() string

DatabaseAlias returns the alias this adapter serves.

func (*Adapter) Get

func (a *Adapter) Get(name string) (datasource.ModelInfo, bool)

Get returns one model by name.

func (*Adapter) Store

func (a *Adapter) Store(modelName, dbAlias string) (datasource.RecordStore, error)

Store returns the RecordStore for a model. An empty dbAlias means the adapter's own alias; any other alias is an error, because the Quark client behind this adapter is bound to one database and answering from it under a different name would show the operator the wrong data.

type Option

type Option func(*Adapter)

Option configures the adapter.

func WithDatabaseAlias

func WithDatabaseAlias(alias string) Option

WithDatabaseAlias names the database alias this adapter serves. The panel resolves an alias per request (?db=, or the model's declared alias) and passes it to Store; the adapter honours it by refusing every other alias.

func WithTenantColumn

func WithTenantColumn(column string) Option

WithTenantColumn names the column that scopes models to a tenant. Models carrying it get ModelInfo.TenantField set, so Data Studio's tenant filter applies. This complements — it does not replace — passing a *quark.TenantRouter as the provider, which enforces scoping in Quark itself.

Jump to

Keyboard shortcuts

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