example

package
v0.0.21 Latest Latest
Warning

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

Go to latest
Published: Aug 21, 2026 License: MIT Imports: 26 Imported by: 0

Documentation

Overview

Package example is the ADR-0100 slice-S1 worked example: a small "device" fact table with three ECS-style components (Identity, Battery, Tagged), the reference store over it, and the clickhouse-local round-trip test. Its generated files (*.out.go, *.out.sql) come from the existing leeway generators, driven by gen_test.go the same way leeway/anchor/ecsdemo/stage2 drives them; the hand-written store here is the reference the recordstore/gen emitter must reproduce.

Index

Constants

View Source
const (
	DeviceColKey       = `"id:id:u64:47::0:"`
	DeviceColOrder     = `"ts:ts:z64:47::0:"`
	DeviceColLifecycle = `"lc:lifecycle:u8:4::0:"`
)

Physical (encoded, quoted) names of the envelope role columns, derived from the IR at generation time — exported so consumers can address them in ScanOpts.ExtraPredicate and their own SQL.

View Source
const (
	WidgetColKey       = `"id:id:u64:47::0:"`
	WidgetColOrder     = `"ts:ts:z64:47::0:"`
	WidgetColLifecycle = `"lc:lifecycle:u8:4::0:"`
)

Physical (encoded, quoted) names of the envelope role columns, derived from the IR at generation time — exported so consumers can address them in ScanOpts.ExtraPredicate and their own SQL.

View Source
const DeviceTableName = "device"

DeviceTableName is the ClickHouse table this store binds — database- qualified ("<db>.<table>") when a Database was set at generation.

TableRowConfig matches anchor's: multiple attributes per row.

View Source
const WidgetTableName = "widget"

WidgetTableName is the ClickHouse table this store binds — database- qualified ("<db>.<table>") when a Database was set at generation.

Variables

View Source
var DeviceComponentSQL = componentsql.Set{
	Store: "Device",
	Table: DeviceTableName,
	Kinds: map[string]componentsql.Artefacts{
		"Identity": {
			Presence:   "has(\"tv:symbol:lr:lr:u64:1247:::0::data\", 1)",
			Validator:  "countEqual(\"tv:symbol:lr:lr:u64:1247:::0::data\", 1) = 1 AND countEqual(\"tv:symbol:lr:lr:u64:1247:::0::data\", 2) <= 1",
			Filter:     deviceScanIdentityFilter,
			Projection: "CAST(tuple(\"id:id:u64:47::0:\", LW_VALUE_BY_TAG_EQUAL(\"tv:symbol:value:val:s:124::I:0::data\", \"tv:symbol:lr:lr:u64:1247:::0::data\", 1, LW_RAGGED_PARENT_IDS(\"tv:symbol:lrcard:lrcard:u64:4E:::0::data\")), if(has(\"tv:symbol:lr:lr:u64:1247:::0::data\", 2), LW_VALUE_BY_TAG_EQUAL(\"tv:symbol:value:val:s:124::I:0::data\", \"tv:symbol:lr:lr:u64:1247:::0::data\", 2, LW_RAGGED_PARENT_IDS(\"tv:symbol:lrcard:lrcard:u64:4E:::0::data\")), NULL)), 'Tuple(ID UInt64, Status String, Nick Nullable(String))')",
		},
		"Battery": {
			Presence:   "has(\"tv:u64Array:lr:lr:u64:1247:::0::data\", 1)",
			Validator:  "countEqual(\"tv:u64Array:lr:lr:u64:1247:::0::data\", 1) = 1 AND if(has(\"tv:u64Array:lr:lr:u64:1247:::0::data\", 1), \"tv:u64Array:len:len:u64:4D:::0::data\"[arrayFirstIndex(cum -> cum >= indexOf(\"tv:u64Array:lr:lr:u64:1247:::0::data\", 1), arrayCumSum(\"tv:u64Array:lrcard:lrcard:u64:4E:::0::data\"))], 0) = 1",
			Filter:     deviceScanBatteryFilter,
			Projection: "CAST(tuple(\"id:id:u64:47::0:\", LW_LIST_BY_TAG_EQUAL(\"tv:u64Array:value:val:u64h:4:::0::data\", \"tv:u64Array:len:len:u64:4D:::0::data\", \"tv:u64Array:lr:lr:u64:1247:::0::data\", 1, LW_RAGGED_PARENT_IDS(\"tv:u64Array:lrcard:lrcard:u64:4E:::0::data\"))[1]), 'Tuple(ID UInt64, Charge UInt64)')",
		},
		"Tagged": {
			Presence:   "has(\"tv:symbolArray:lr:lr:u64:1247:::0::data\", 1)",
			Validator:  "countEqual(\"tv:symbolArray:lr:lr:u64:1247:::0::data\", 1) <= 1",
			Filter:     deviceScanTaggedFilter,
			Projection: "CAST(tuple(\"id:id:u64:47::0:\", LW_LIST_BY_TAG_EQUAL(\"tv:symbolArray:value:val:sh:4::I:0::data\", \"tv:symbolArray:len:len:u64:4D:::0::data\", \"tv:symbolArray:lr:lr:u64:1247:::0::data\", 1, LW_RAGGED_PARENT_IDS(\"tv:symbolArray:lrcard:lrcard:u64:4E:::0::data\"))), 'Tuple(ID UInt64, Tags Array(String))')",
		},
		"Located": {
			Presence:   "has(\"tv:geoPoint:lr:lr:u64:1247:::0::data\", 1)",
			Validator:  "countEqual(\"tv:geoPoint:lr:lr:u64:1247:::0::data\", 1) = 1",
			Filter:     deviceScanLocatedFilter,
			Projection: "CAST(tuple(\"id:id:u64:47::0:\", LW_VALUE_BY_TAG_EQUAL(\"tv:geoPoint:pointLat:val:f32:4:::0::data\", \"tv:geoPoint:lr:lr:u64:1247:::0::data\", 1, LW_RAGGED_PARENT_IDS(\"tv:geoPoint:lrcard:lrcard:u64:4E:::0::data\")), LW_VALUE_BY_TAG_EQUAL(\"tv:geoPoint:pointLng:val:f32:4:::0::data\", \"tv:geoPoint:lr:lr:u64:1247:::0::data\", 1, LW_RAGGED_PARENT_IDS(\"tv:geoPoint:lrcard:lrcard:u64:4E:::0::data\")), LW_VALUE_BY_TAG_EQUAL(\"tv:geoPoint:h3:val:u64:4:::0::data\", \"tv:geoPoint:lr:lr:u64:1247:::0::data\", 1, LW_RAGGED_PARENT_IDS(\"tv:geoPoint:lrcard:lrcard:u64:4E:::0::data\"))), 'Tuple(ID UInt64, Lat Float32, Lng Float32, Cell UInt64)')",
		},
	},
}

DeviceComponentSQL publishes this store's ADR-0066 read-back artefacts — the SQL its component definitions generate — for an authoring surface to expand (ADR-0189). A host registers it into a componentsql.Registry; nothing here self-registers.

Filter is the same constant the Scan verbs use, so the store's own read path and the authoring surface cannot disagree about what a conforming row is. Projection must not be embedded without Filter — it locates an attribute by indexOf and returns the first match, so on a row carrying a membership twice it answers plausibly and wrongly (ADR-0066).

The column references are UNQUALIFIED, so a consumer embedding them in a join must bind them to DeviceTableName itself (ADR-0189 SD6).

View Source
var DeviceMembershipIds = map[string]map[string]uint64{
	"Identity": {
		"deviceStatus": 1,
		"deviceNick":   2,
	},
	"Battery": {
		"deviceCharge": 1,
	},
	"Tagged": {
		"deviceTags": 1,
	},
	"Located": {
		"deviceLoc": 1,
	},
}

DeviceMembershipIds is the membership-id assignment this store was generated under: component kind -> membership name -> the uint64 id carried in the membership columns. Verbatim-channel memberships embed their literal name instead and are absent here.

The ids are declaration-order (1..N per component) and are baked into both the component codecs and this store's Scan filters. Nothing on the wire records which assignment wrote a row, so rows written under a different one decode as ABSENT rather than failing — VerifySchema cannot see it. Compare this map against the writer's before pointing a regenerated store at existing rows.

View Source
var PackageProps = packageprops.Props{
	WASMWASI:         packageprops.WASMBlocked,
	WASMJS:           packageprops.WASMBlocked,
	WASMFreestanding: packageprops.WASMBlocked,
}

PackageProps records this package's curated properties (ADR-0080). Blocked like recordstore: the arrow dependency does not compile under TinyGo.

Functions

func GetDeviceSchemaInManipulator

func GetDeviceSchemaInManipulator() (manip *common.TableManipulator, err error)

GetDeviceSchemaInManipulator builds the device fact table. The three envelope plain columns carry the ADR-0100 roles — id (Key), ts (Order), lifecycle (Lifecycle; 0 = live, 1 = tombstone) — and each component owns one tagged section: symbol (Identity.Status + the optional Identity.Nick), u64Array (Battery.Charge, single-valued via the unit modifier), symbolArray (Tagged.Tags) and the multi-sub-column geoPoint (Located.Lat/Lng/Cell).

func GetWidgetSchemaInManipulator added in v0.0.13

func GetWidgetSchemaInManipulator() (manip *common.TableManipulator, err error)

GetWidgetSchemaInManipulator builds the pass-through reference schema: the device schema's sections and roles (id = Key, ts = Order, lifecycle = the u8 state-view marker) plus a backbone richer than that role model, so the extra plain columns exercise the generated envelope (ADR-0100 Update 2026-07-09). The second id (alt), a routing scalar (region) and a routing set (tags) are not roles — they ride through WidgetEnvelope, written on Begin and read back onto WidgetEntity. It reuses loadDeviceSchema so the driven DML / read-access generators see the same section shapes device does (a plain-only table leaves several of their imports unused); no component binds those sections here.

Types

type Battery

type Battery struct {
	ID     uint64 `lw:",id"`
	Charge uint64 `lw:"deviceCharge,u64Array,unit"`
	// contains filtered or unexported fields
}

Battery is the charge-level device component; see identity_dto.go for the component-file layout rationale.

type DeviceCache

type DeviceCache[W comparable] struct {
	// contains filtered or unexported fields
}

DeviceCache is the batched read-through, write-through KV view over a DeviceStore (ADR-0100 SD5): misses queue under work items and flush as one IN (…) lookup, and local writes populate the view at Commit — pinned until the store's Flush makes them durable — so reads after writes hit immediately. Admission is version-gated on the entity's Order timestamp: a raced refetch of an older row bounces off. Only EXTERNAL writers can leave the view stale; they need a caller- provided signal: MarkStale / Invalidate / InvalidateAll (a freshness TTL option exists on the underlying cache). Raw() commits and discarded writes invalidate instead of populating. Like the store it wraps, a view is single-goroutine. W is the work-item type (use struct{} when the suspend/replay machinery is not needed).

func NewDeviceCache

func NewDeviceCache[W comparable](st *DeviceStore, cfg DeviceCacheConfig) (inst *DeviceCache[W])

NewDeviceCache attaches a read-through, write-through cache view to st, registering its write-through and flush hooks with the store. Views attach for the store's lifetime — there is no detach.

func (*DeviceCache[W]) AdvanceEpoch

func (inst *DeviceCache[W]) AdvanceEpoch()

AdvanceEpoch advances the cache's pinning epoch — call once per frame / batch so untouched L1 entries become evictable.

func (*DeviceCache[W]) Get

func (inst *DeviceCache[W]) Get(key uint64) (ent *DeviceEntity, found bool)

Get retrieves an entity by Key through the cache; local writes are visible immediately (write-through). A miss queues the key for the next batch fetch (the caching suspend/replay contract). A miss can also mean the batched fetch errored (misses swallow fetch errors; the circuit breaker backs off) — GetFetch surfaces the error instead, and the store's Latest stays the authoritative check. The returned entity is shared with the cache: treat it as immutable.

func (*DeviceCache[W]) GetFetch

func (inst *DeviceCache[W]) GetFetch(ctx context.Context, key uint64) (ent *DeviceEntity, found bool, err error)

GetFetch is the single-lookup read: the cached entity when present, otherwise one immediate batched point fetch — fetch errors surface instead of reading as misses, so found=false with err=nil is the authoritative absent. A key in the dirty write window that the cache could not answer is an error rather than a stale row (see below). Prefer Get plus the work-item protocol when batching lookups across a frame; the initial miss here also queues the key, so a later batch fetch may include it redundantly (harmless).

func (*DeviceCache[W]) GetLive

func (inst *DeviceCache[W]) GetLive(key uint64) (ent *DeviceEntity, found bool)

GetLive is the cached state-view read: the cache's newest row for the key with the tombstone read as absent — exact under this process's single writer (local writes invalidate); external writers need MarkStale / Invalidate. The store's uncached GetLive stays the authoritative read. A miss queues the batch fetch like Get.

func (*DeviceCache[W]) GetLiveAcceptStale

func (inst *DeviceCache[W]) GetLiveAcceptStale(key uint64) (ent *DeviceEntity, found bool, stale bool)

GetLiveAcceptStale is the stale-while-revalidate state-view read: a stale entry is served immediately (stale=true) while the refetch queues in the background — pair with the work-item replay loop. Tombstones read as absent; stale then reports whether that verdict came from a stale entry.

func (*DeviceCache[W]) Invalidate

func (inst *DeviceCache[W]) Invalidate(key uint64)

Invalidate drops the key's cached entry (L1 and stash).

func (*DeviceCache[W]) InvalidateAll

func (inst *DeviceCache[W]) InvalidateAll()

InvalidateAll drops every cached entry — the bulk external-writer signal (e.g. after an import). In-flight miss bookkeeping (queued keys, pending work items) and the dirty-window pins are dropped with it: call between frames, with no suspended work and no unflushed local writes (the fetcher's dirty-guard keeps pre-write rows out of the cleared cache until the next Flush, at the cost of misses on those keys).

func (*DeviceCache[W]) IterateReadyWorkItems

func (inst *DeviceCache[W]) IterateReadyWorkItems(ctx context.Context) iter.Seq[W]

IterateReadyWorkItems flushes the queued keys when the fetch criteria are met and replays the work items that had misses.

func (*DeviceCache[W]) IterateRestWorkItems

func (inst *DeviceCache[W]) IterateRestWorkItems(ctx context.Context) iter.Seq[W]

IterateRestWorkItems forces a fetch of all queued keys and replays the pending work items.

func (*DeviceCache[W]) MarkStale

func (inst *DeviceCache[W]) MarkStale(key uint64)

MarkStale flags the key's cached entry as stale — the external-writer signal: the next strict read misses and queues a refetch, while accept-stale reads keep serving the old value until it lands.

func (*DeviceCache[W]) MarkStaleIfOlder

func (inst *DeviceCache[W]) MarkStaleIfOlder(key uint64, order time.Time)

MarkStaleIfOlder is the version-carrying external-writer signal: it stales the cached entry only if its Order is below order, so a redundant signal for a version the view already holds is free — the natural sink for an invalidation stream carrying (key, Order).

func (*DeviceCache[W]) WorkItem

func (inst *DeviceCache[W]) WorkItem(w W) iter.Seq[functional.NilIteratorValueType]

WorkItem marks the current work item for the cache's miss bookkeeping.

type DeviceCacheConfig

type DeviceCacheConfig struct {
	// Capacity is the L1 capacity in entries, not bytes — budget
	// memory as Capacity × the largest expected entity payload. Zero
	// or negative selects the default (1024).
	Capacity int
	// FetchCriteria are the cache's batch-flush thresholds.
	FetchCriteria caching.FetchCriteria
	// FreshnessTTL enables age-based staleness onset (ADR-0100's
	// external-writer staleness story): entries older than this read
	// as stale — strict reads miss and queue a refetch, accept-stale
	// reads keep serving. Zero disables (staleness stays signal-only).
	FreshnessTTL time.Duration
	// NegativeTTL enables absent-key marking: keys a clean fetch did
	// not return are treated as absent for this long — misses on them
	// neither queue nor suspend work items, so replay loops over keys
	// that do not exist terminate. Zero disables.
	NegativeTTL time.Duration
}

DeviceCacheConfig parameterizes an attached read-through cache view.

type DeviceEntity

type DeviceEntity struct {
	ID        uint64
	Ts        time.Time
	Lifecycle uint8
	Identity  option.Option[Identity]
	Battery   option.Option[Battery]
	Tagged    option.Option[Tagged]
	Located   option.Option[Located]
}

DeviceEntity is the entity bag (ADR-0100 SD5): the envelope plus one option per bound component. Arrow-free — safe to hold in the cache. Entities returned by cached reads are shared with the cache (and every later reader): treat them as immutable.

func (*DeviceEntity) Archetype

func (inst *DeviceEntity) Archetype() (a []string)

Archetype reports which components the entity carries, in schema order.

func (*DeviceEntity) IsTombstone

func (inst *DeviceEntity) IsTombstone() bool

IsTombstone reports whether this row is a state-view deletion marker — what the tombstone-blind verbs (Latest, Replay, the cache's Get) hand back for a deleted key.

type DeviceEntityBuilder

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

DeviceEntityBuilder assembles one entity: envelope from Begin, components via Add*, direct attribute manipulation via Raw, then Commit.

func (*DeviceEntityBuilder) AddBattery

func (inst *DeviceEntityBuilder) AddBattery(row Battery) *DeviceEntityBuilder

AddBattery contributes the Battery component to the open entity.

The attributes are buffered, not written: a section frame closes for good, so a component that closed its own sections would shut out the next component sharing one. Commit writes them, one frame per section in first-seen order (ADR-0183 D4).

A second Add of this component, or an Add on an entity already using Raw(), is refused: both used to mark the row un-mirrorable and carry on, which made its read-back shape depend on a call the writer had probably made by accident.

func (*DeviceEntityBuilder) AddIdentity

func (inst *DeviceEntityBuilder) AddIdentity(row Identity) *DeviceEntityBuilder

AddIdentity contributes the Identity component to the open entity.

The attributes are buffered, not written: a section frame closes for good, so a component that closed its own sections would shut out the next component sharing one. Commit writes them, one frame per section in first-seen order (ADR-0183 D4).

A second Add of this component, or an Add on an entity already using Raw(), is refused: both used to mark the row un-mirrorable and carry on, which made its read-back shape depend on a call the writer had probably made by accident.

func (*DeviceEntityBuilder) AddLocated

func (inst *DeviceEntityBuilder) AddLocated(row Located) *DeviceEntityBuilder

AddLocated contributes the Located component to the open entity.

The attributes are buffered, not written: a section frame closes for good, so a component that closed its own sections would shut out the next component sharing one. Commit writes them, one frame per section in first-seen order (ADR-0183 D4).

A second Add of this component, or an Add on an entity already using Raw(), is refused: both used to mark the row un-mirrorable and carry on, which made its read-back shape depend on a call the writer had probably made by accident.

func (*DeviceEntityBuilder) AddTagged

func (inst *DeviceEntityBuilder) AddTagged(row Tagged) *DeviceEntityBuilder

AddTagged contributes the Tagged component to the open entity.

The attributes are buffered, not written: a section frame closes for good, so a component that closed its own sections would shut out the next component sharing one. Commit writes them, one frame per section in first-seen order (ADR-0183 D4).

A second Add of this component, or an Add on an entity already using Raw(), is refused: both used to mark the row un-mirrorable and carry on, which made its read-back shape depend on a call the writer had probably made by accident.

func (*DeviceEntityBuilder) Commit

func (inst *DeviceEntityBuilder) Commit() (err error)

Commit finishes the open entity, buffers the row, and writes it through to attached cache views: the entity is populated and pinned until the store's Flush makes it durable — reads after writes hit immediately, and the caching version gate plus the pin make a raced refetch of the pre-write row bounce off. A commit that touched Raw() cannot be materialized faithfully and invalidates the key instead. A failed Commit rolls the frame back — the entity is discarded and the store stays usable.

func (*DeviceEntityBuilder) Raw

Raw exposes the underlying DML entity for direct attribute manipulation within the same entity frame. The type lives in internal/lowlevel: callers outside the generated package hold the returned value by inference (raw := b.Raw()) and chain its methods, but cannot name the type in their own signatures.

func (*DeviceEntityBuilder) Rollback

func (inst *DeviceEntityBuilder) Rollback() (err error)

Rollback abandons the open entity frame without committing it; already-buffered rows and the store remain usable.

type DeviceStore

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

DeviceStore is single-goroutine, like every part it composes. Batched cached retrieval is not built in — attach a DeviceCache view.

func NewDeviceStore

func NewDeviceStore(exec recordstore.ExecutorI, alloc memory.Allocator, cfg DeviceStoreConfig) (inst *DeviceStore)

NewDeviceStore wires the store. A nil alloc selects the Go allocator.

func (*DeviceStore) Begin

func (inst *DeviceStore) Begin(id uint64, ts time.Time) *DeviceEntityBuilder

Begin opens one entity with the envelope roles as typed arguments (Key, Order) and a live lifecycle.

func (*DeviceStore) Buffered

func (inst *DeviceStore) Buffered() int

Buffered reports the number of committed-but-unflushed rows.

func (*DeviceStore) Close

func (inst *DeviceStore) Close()

Close discards everything unflushed and releases the store's Arrow builder; the store must not be used afterwards. Required for a clean shutdown under tracking/checked allocators — the default Go allocator needs no Close.

func (*DeviceStore) Delete

func (inst *DeviceStore) Delete(id uint64, ts time.Time) (err error)

Delete appends a tombstone row for id (no components; lifecycle marks the deletion). The tombstone writes through to attached cache views like any commit — a versioned deletion, so GetLive reads the key as absent immediately.

func (*DeviceStore) DiscardPending

func (inst *DeviceStore) DiscardPending()

DiscardPending drops every committed-but-unflushed row: records retained by a failed Flush, rows still in the DML builder, and an open (uncommitted) entity frame. It gives a failed Flush "never happened" semantics — ClickHouse state is the truth afterwards. Ambient stamps are cleared with the frame they were pushed for — including any pushed through Raw() — so an abandoned builder cannot leak its stamps onto later entities.

func (*DeviceStore) EnsureTable

func (inst *DeviceStore) EnsureTable(ctx context.Context) (err error)

EnsureTable applies the composed CREATE TABLE (plus the DDLTail suffix, when configured). Idempotent (CREATE TABLE IF NOT EXISTS). The embedded script is issued one statement per Exec — the optional CREATE DATABASE, then the CREATE TABLE — because the ClickHouse HTTP interface rejects a multi-statement body; under a Table override the statements are re-pointed at the override (recordstore.ProvisioningStatements: header and database only, the column block stays byte-identical).

func (*DeviceStore) Flush

func (inst *DeviceStore) Flush(ctx context.Context) (n int, err error)

Flush drains the buffered rows to ClickHouse (Arrow IPC, ADR-0089 pivot). Rows are durable when Flush returns, engine permitting. On insert failure the transferred records are retained and the next Flush ships them — Flush is retryable; DiscardPending drops them instead. An open (uncommitted) entity frame makes Flush error.

func (*DeviceStore) GetLive

func (inst *DeviceStore) GetLive(ctx context.Context, key uint64) (ent *DeviceEntity, found bool, err error)

GetLive is Latest plus tombstone interpretation: newest row wins, a tombstone reads as absent — the state-view read (the cache view carries the cached twin).

func (*DeviceStore) IngestBattery

func (inst *DeviceStore) IngestBattery(ts time.Time, rows []Battery) (err error)

IngestBattery buffers one whole entity per row carrying only the Battery component, all stamped with ts — rows ship on the next Flush, like every write. Keys must be distinct within one call (rows share ts, so duplicates would tie on Order): a duplicate returns recordstore.ErrDuplicateIngestKey. On any error the rows buffered so far remain buffered — Flush ships them, DiscardPending drops them.

func (*DeviceStore) IngestIdentity

func (inst *DeviceStore) IngestIdentity(ts time.Time, rows []Identity) (err error)

IngestIdentity buffers one whole entity per row carrying only the Identity component, all stamped with ts — rows ship on the next Flush, like every write. Keys must be distinct within one call (rows share ts, so duplicates would tie on Order): a duplicate returns recordstore.ErrDuplicateIngestKey. On any error the rows buffered so far remain buffered — Flush ships them, DiscardPending drops them.

func (*DeviceStore) IngestLocated

func (inst *DeviceStore) IngestLocated(ts time.Time, rows []Located) (err error)

IngestLocated buffers one whole entity per row carrying only the Located component, all stamped with ts — rows ship on the next Flush, like every write. Keys must be distinct within one call (rows share ts, so duplicates would tie on Order): a duplicate returns recordstore.ErrDuplicateIngestKey. On any error the rows buffered so far remain buffered — Flush ships them, DiscardPending drops them.

func (*DeviceStore) IngestTagged

func (inst *DeviceStore) IngestTagged(ts time.Time, rows []Tagged) (err error)

IngestTagged buffers one whole entity per row carrying only the Tagged component, all stamped with ts — rows ship on the next Flush, like every write. Keys must be distinct within one call (rows share ts, so duplicates would tie on Order): a duplicate returns recordstore.ErrDuplicateIngestKey. On any error the rows buffered so far remain buffered — Flush ships them, DiscardPending drops them.

func (*DeviceStore) Latest

func (inst *DeviceStore) Latest(ctx context.Context, key uint64) (ent *DeviceEntity, found bool, err error)

Latest returns the newest row for key, tombstone-blind (the raw row-level primitive — a deleted key still returns its tombstone row; GetLive is the interpreted state-view read). Reads see only flushed rows.

func (*DeviceStore) Replay

func (inst *DeviceStore) Replay(ctx context.Context, key uint64, fromOrder time.Time, opts recordstore.ReplayOpts) iter.Seq2[*DeviceEntity, error]

Replay iterates the rows for key with the order column >= fromOrder in ascending order — the event-replay primitive. A zero fromOrder replays everything (zero time.Time has no defined UnixNano; recordstore.SeqTs(0) is the equivalent explicit bound); opts.To bounds the replay exclusively ("state as of To") and opts.Limit caps the row count. The sequence is single-use; ctx must stay valid until iteration completes; the query may execute at call time or lazily during iteration (buffered in v1 — a streaming executor changes nothing visible); an error ends the sequence as a final (nil, err) pair. Reads see only flushed rows.

func (*DeviceStore) ScanBattery

func (inst *DeviceStore) ScanBattery(ctx context.Context, opts recordstore.ScanOpts) iter.Seq2[*DeviceEntity, error]

ScanBattery iterates the entities whose rows carry a conforming Battery component, ordered by (Order, Key) — so entities sharing an Order still come out in a fixed sequence. Rows that tie on BOTH (the same key written twice at the same Order) are not ordered against each other by this clause; the table keeps newest-per-key, so which of them survives is the engine's choice, not the scan's. opts.ExtraPredicate (trusted raw SQL over the physical columns — never untrusted input) further restricts the scan; opts.Limit caps the row count. The Filter artefact uses ClickHouse built-ins only, so this is a single SELECT — no helper UDFs, no multi-statement script (the ExecutorI contract). The sequence is single-use; ctx must stay valid until iteration completes; an error ends it as a final (nil, err) pair. Scans see only flushed rows.

func (*DeviceStore) ScanIdentity

func (inst *DeviceStore) ScanIdentity(ctx context.Context, opts recordstore.ScanOpts) iter.Seq2[*DeviceEntity, error]

ScanIdentity iterates the entities whose rows carry a conforming Identity component, ordered by (Order, Key) — so entities sharing an Order still come out in a fixed sequence. Rows that tie on BOTH (the same key written twice at the same Order) are not ordered against each other by this clause; the table keeps newest-per-key, so which of them survives is the engine's choice, not the scan's. opts.ExtraPredicate (trusted raw SQL over the physical columns — never untrusted input) further restricts the scan; opts.Limit caps the row count. The Filter artefact uses ClickHouse built-ins only, so this is a single SELECT — no helper UDFs, no multi-statement script (the ExecutorI contract). The sequence is single-use; ctx must stay valid until iteration completes; an error ends it as a final (nil, err) pair. Scans see only flushed rows.

func (*DeviceStore) ScanLocated

func (inst *DeviceStore) ScanLocated(ctx context.Context, opts recordstore.ScanOpts) iter.Seq2[*DeviceEntity, error]

ScanLocated iterates the entities whose rows carry a conforming Located component, ordered by (Order, Key) — so entities sharing an Order still come out in a fixed sequence. Rows that tie on BOTH (the same key written twice at the same Order) are not ordered against each other by this clause; the table keeps newest-per-key, so which of them survives is the engine's choice, not the scan's. opts.ExtraPredicate (trusted raw SQL over the physical columns — never untrusted input) further restricts the scan; opts.Limit caps the row count. The Filter artefact uses ClickHouse built-ins only, so this is a single SELECT — no helper UDFs, no multi-statement script (the ExecutorI contract). The sequence is single-use; ctx must stay valid until iteration completes; an error ends it as a final (nil, err) pair. Scans see only flushed rows.

func (*DeviceStore) ScanTagged

func (inst *DeviceStore) ScanTagged(ctx context.Context, opts recordstore.ScanOpts) iter.Seq2[*DeviceEntity, error]

ScanTagged iterates the entities whose rows carry a conforming Tagged component, ordered by (Order, Key) — so entities sharing an Order still come out in a fixed sequence. Rows that tie on BOTH (the same key written twice at the same Order) are not ordered against each other by this clause; the table keeps newest-per-key, so which of them survives is the engine's choice, not the scan's. opts.ExtraPredicate (trusted raw SQL over the physical columns — never untrusted input) further restricts the scan; opts.Limit caps the row count. The Filter artefact uses ClickHouse built-ins only, so this is a single SELECT — no helper UDFs, no multi-statement script (the ExecutorI contract). The sequence is single-use; ctx must stay valid until iteration completes; an error ends it as a final (nil, err) pair. Scans see only flushed rows.

func (*DeviceStore) VerifySchema

func (inst *DeviceStore) VerifySchema(ctx context.Context) (err error)

VerifySchema compares the live table's columns — names and order — against the generated schema. EnsureTable alone cannot detect drift on an existing table (IF NOT EXISTS succeeds against any old shape), and the decode is positional, so drift fails late or, for same-typed column swaps, silently: run VerifySchema at startup after EnsureTable.

It checks the COLUMN contract only. The membership-id contract is not checked and cannot be from the schema alone: the ids live in the membership columns as ordinary values, and a FAT table legitimately carries other kinds' ids beside this store's. Rows written under a different id assignment therefore pass VerifySchema and then match nothing — every component decodes absent, with no error. Compare DeviceMembershipIds against the writer's assignment when pointing this store at rows it did not write.

What it describes is the reader's own projection — DESCRIBE over `SELECT *`, not over the table — because that is what the positional decode consumes. DESCRIBE TABLE lists columns SELECT * does not return (MATERIALIZED, ALIAS, EPHEMERAL), so a table legitimately carrying one beside the generated shape — a derived column added by ALTER after EnsureTable, which is how a store gets a skip index over a value its leeway attributes only encode — would fail a check whose contract still held. Describing the projection also follows the asterisk_include_* settings, which decide what SELECT * returns and which nothing here pins: under those, a derived column IS in the decode, and this notices where a column-kind filter would have blessed the mis-decode.

type DeviceStoreConfig

type DeviceStoreConfig struct {
	// Table overrides the ClickHouse table this store binds — the baked
	// DeviceTableName — for every statement it issues (DDL, DESCRIBE, INSERT,
	// SELECT). Optionally database-qualified ("<db>.<table>"), unquoted-
	// identifier shape only ([A-Za-z_][A-Za-z0-9_]* per part; the
	// constructor panics otherwise). Empty (the default) binds the baked
	// name. The schema is unchanged — this moves WHERE the rows land, not
	// what they look like — so a scratch table for a test or a per-
	// deployment table needs no regeneration.
	Table string
	// DDLTail is a raw suffix appended verbatim after the composed
	// CREATE TABLE at EnsureTable time — the escape hatch for clauses
	// the generation-time table options (ADR-0102) do not carry.
	DDLTail string
	// Stampers are consulted on every Begin (ADR-0112 M1): each yields
	// surrogate ids stamped as additive HighCardRef memberships onto the
	// entity's attributes. Empty (the default) leaves the store unstamped
	// and behaviour-identical. A stamper must not write to this store.
	// The schema must carry the HighCardRef membership lane, and no
	// component may read that lane back as data — the constructor
	// panics otherwise (ADR-0112 SD2 lane hygiene).
	Stampers []recordstore.ReferenceStamper
	// BestEffortStampFlush relaxes the ADR-0112 SD5 ordered flush: when
	// true, Flush does NOT flush the stampers' dimension stores before its
	// own insert, so a referencing row may become durable ahead of its
	// descriptor fact (resolution self-heals on the dimension's own flush).
	// The default keeps the descriptor durable no later than the row.
	BestEffortStampFlush bool
}

type Identity

type Identity struct {
	ID     uint64                `lw:",id"`
	Status string                `lw:"deviceStatus,symbol"`
	Nick   option.Option[string] `lw:"deviceNick,symbol"`
	// contains filtered or unexported fields
}

Identity, Battery (battery_dto.go), Tagged (tagged_dto.go) and Located (located_dto.go) are the device components: flat lw:-tagged DTOs, each carrying the entity id plus its own tagged section, mirroring the ecsdemo component split. One kind lives per source file because marshallgen.ParsePlan reads a single kind per input. Membership ids are per-section and each component owns a distinct section, so the generated per-kind ids cannot collide in storage.

Nick is an Option scalar (ZeroToOne): absent Nick and present Identity coexist on one row — the shape the presence-gated ReadRow decode covers.

type Located

type Located struct {
	ID   uint64  `lw:",id"`
	Lat  float32 `lw:"deviceLoc,geoPoint:pointLat"`
	Lng  float32 `lw:"deviceLoc,geoPoint:pointLng"`
	Cell uint64  `lw:"deviceLoc,geoPoint:h3"`
	// contains filtered or unexported fields
}

Located is the position device component, bound to the multi-sub-column geoPoint section (one membership carrying three aligned value columns); see identity_dto.go for the component-file layout rationale.

type Tagged

type Tagged struct {
	ID   uint64   `lw:",id"`
	Tags []string `lw:"deviceTags,symbolArray"`
	// contains filtered or unexported fields
}

Tagged is the free-form label device component; see identity_dto.go for the component-file layout rationale.

type WidgetCache added in v0.0.13

type WidgetCache[W comparable] struct {
	// contains filtered or unexported fields
}

WidgetCache is the batched read-through, write-through KV view over a WidgetStore (ADR-0100 SD5): misses queue under work items and flush as one IN (…) lookup, and local writes populate the view at Commit — pinned until the store's Flush makes them durable — so reads after writes hit immediately. Admission is version-gated on the entity's Order timestamp: a raced refetch of an older row bounces off. Only EXTERNAL writers can leave the view stale; they need a caller- provided signal: MarkStale / Invalidate / InvalidateAll (a freshness TTL option exists on the underlying cache). Raw() commits and discarded writes invalidate instead of populating. Like the store it wraps, a view is single-goroutine. W is the work-item type (use struct{} when the suspend/replay machinery is not needed).

func NewWidgetCache added in v0.0.13

func NewWidgetCache[W comparable](st *WidgetStore, cfg WidgetCacheConfig) (inst *WidgetCache[W])

NewWidgetCache attaches a read-through, write-through cache view to st, registering its write-through and flush hooks with the store. Views attach for the store's lifetime — there is no detach.

func (*WidgetCache[W]) AdvanceEpoch added in v0.0.13

func (inst *WidgetCache[W]) AdvanceEpoch()

AdvanceEpoch advances the cache's pinning epoch — call once per frame / batch so untouched L1 entries become evictable.

func (*WidgetCache[W]) Get added in v0.0.13

func (inst *WidgetCache[W]) Get(key uint64) (ent *WidgetEntity, found bool)

Get retrieves an entity by Key through the cache; local writes are visible immediately (write-through). A miss queues the key for the next batch fetch (the caching suspend/replay contract). A miss can also mean the batched fetch errored (misses swallow fetch errors; the circuit breaker backs off) — GetFetch surfaces the error instead, and the store's Latest stays the authoritative check. The returned entity is shared with the cache: treat it as immutable.

func (*WidgetCache[W]) GetFetch added in v0.0.13

func (inst *WidgetCache[W]) GetFetch(ctx context.Context, key uint64) (ent *WidgetEntity, found bool, err error)

GetFetch is the single-lookup read: the cached entity when present, otherwise one immediate batched point fetch — fetch errors surface instead of reading as misses, so found=false with err=nil is the authoritative absent. A key in the dirty write window that the cache could not answer is an error rather than a stale row (see below). Prefer Get plus the work-item protocol when batching lookups across a frame; the initial miss here also queues the key, so a later batch fetch may include it redundantly (harmless).

func (*WidgetCache[W]) GetLive added in v0.0.13

func (inst *WidgetCache[W]) GetLive(key uint64) (ent *WidgetEntity, found bool)

GetLive is the cached state-view read: the cache's newest row for the key with the tombstone read as absent — exact under this process's single writer (local writes invalidate); external writers need MarkStale / Invalidate. The store's uncached GetLive stays the authoritative read. A miss queues the batch fetch like Get.

func (*WidgetCache[W]) GetLiveAcceptStale added in v0.0.13

func (inst *WidgetCache[W]) GetLiveAcceptStale(key uint64) (ent *WidgetEntity, found bool, stale bool)

GetLiveAcceptStale is the stale-while-revalidate state-view read: a stale entry is served immediately (stale=true) while the refetch queues in the background — pair with the work-item replay loop. Tombstones read as absent; stale then reports whether that verdict came from a stale entry.

func (*WidgetCache[W]) Invalidate added in v0.0.13

func (inst *WidgetCache[W]) Invalidate(key uint64)

Invalidate drops the key's cached entry (L1 and stash).

func (*WidgetCache[W]) InvalidateAll added in v0.0.13

func (inst *WidgetCache[W]) InvalidateAll()

InvalidateAll drops every cached entry — the bulk external-writer signal (e.g. after an import). In-flight miss bookkeeping (queued keys, pending work items) and the dirty-window pins are dropped with it: call between frames, with no suspended work and no unflushed local writes (the fetcher's dirty-guard keeps pre-write rows out of the cleared cache until the next Flush, at the cost of misses on those keys).

func (*WidgetCache[W]) IterateReadyWorkItems added in v0.0.13

func (inst *WidgetCache[W]) IterateReadyWorkItems(ctx context.Context) iter.Seq[W]

IterateReadyWorkItems flushes the queued keys when the fetch criteria are met and replays the work items that had misses.

func (*WidgetCache[W]) IterateRestWorkItems added in v0.0.13

func (inst *WidgetCache[W]) IterateRestWorkItems(ctx context.Context) iter.Seq[W]

IterateRestWorkItems forces a fetch of all queued keys and replays the pending work items.

func (*WidgetCache[W]) MarkStale added in v0.0.13

func (inst *WidgetCache[W]) MarkStale(key uint64)

MarkStale flags the key's cached entry as stale — the external-writer signal: the next strict read misses and queues a refetch, while accept-stale reads keep serving the old value until it lands.

func (*WidgetCache[W]) MarkStaleIfOlder added in v0.0.13

func (inst *WidgetCache[W]) MarkStaleIfOlder(key uint64, order time.Time)

MarkStaleIfOlder is the version-carrying external-writer signal: it stales the cached entry only if its Order is below order, so a redundant signal for a version the view already holds is free — the natural sink for an invalidation stream carrying (key, Order).

func (*WidgetCache[W]) WorkItem added in v0.0.13

func (inst *WidgetCache[W]) WorkItem(w W) iter.Seq[functional.NilIteratorValueType]

WorkItem marks the current work item for the cache's miss bookkeeping.

type WidgetCacheConfig added in v0.0.13

type WidgetCacheConfig struct {
	// Capacity is the L1 capacity in entries, not bytes — budget
	// memory as Capacity × the largest expected entity payload. Zero
	// or negative selects the default (1024).
	Capacity int
	// FetchCriteria are the cache's batch-flush thresholds.
	FetchCriteria caching.FetchCriteria
	// FreshnessTTL enables age-based staleness onset (ADR-0100's
	// external-writer staleness story): entries older than this read
	// as stale — strict reads miss and queue a refetch, accept-stale
	// reads keep serving. Zero disables (staleness stays signal-only).
	FreshnessTTL time.Duration
	// NegativeTTL enables absent-key marking: keys a clean fetch did
	// not return are treated as absent for this long — misses on them
	// neither queue nor suspend work items, so replay loops over keys
	// that do not exist terminate. Zero disables.
	NegativeTTL time.Duration
}

WidgetCacheConfig parameterizes an attached read-through cache view.

type WidgetEntity added in v0.0.13

type WidgetEntity struct {
	ID        uint64
	Ts        time.Time
	Lifecycle uint8
	// WidgetEnvelope is embedded: its pass-through columns read as
	// promoted entity fields.
	WidgetEnvelope
}

WidgetEntity is the entity bag (ADR-0100 SD5): the envelope plus one option per bound component. Arrow-free — safe to hold in the cache. Entities returned by cached reads are shared with the cache (and every later reader): treat them as immutable.

func (*WidgetEntity) Archetype added in v0.0.13

func (inst *WidgetEntity) Archetype() (a []string)

Archetype reports which components the entity carries, in schema order.

func (*WidgetEntity) IsTombstone added in v0.0.13

func (inst *WidgetEntity) IsTombstone() bool

IsTombstone reports whether this row is a state-view deletion marker — what the tombstone-blind verbs (Latest, Replay, the cache's Get) hand back for a deleted key.

type WidgetEntityBuilder added in v0.0.13

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

WidgetEntityBuilder assembles one entity: envelope from Begin, components via Add*, direct attribute manipulation via Raw, then Commit.

func (*WidgetEntityBuilder) Commit added in v0.0.13

func (inst *WidgetEntityBuilder) Commit() (err error)

Commit finishes the open entity, buffers the row, and writes it through to attached cache views: the entity is populated and pinned until the store's Flush makes it durable — reads after writes hit immediately, and the caching version gate plus the pin make a raced refetch of the pre-write row bounce off. A commit that touched Raw() cannot be materialized faithfully and invalidates the key instead. A failed Commit rolls the frame back — the entity is discarded and the store stays usable.

func (*WidgetEntityBuilder) Raw added in v0.0.13

Raw exposes the underlying DML entity for direct attribute manipulation within the same entity frame. The type lives in internal/lowlevel: callers outside the generated package hold the returned value by inference (raw := b.Raw()) and chain its methods, but cannot name the type in their own signatures.

func (*WidgetEntityBuilder) Rollback added in v0.0.13

func (inst *WidgetEntityBuilder) Rollback() (err error)

Rollback abandons the open entity frame without committing it; already-buffered rows and the store remain usable.

type WidgetEnvelope added in v0.0.13

type WidgetEnvelope struct {
	Alt    uint64
	Region uint64
	Tags   []string
}

WidgetEnvelope carries the pass-through backbone columns — every plain column that is not the Key, Order or state-view Lifecycle role. Pass one to Begin; it is written verbatim onto the row and read back onto the entity.

type WidgetStore added in v0.0.13

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

WidgetStore is single-goroutine, like every part it composes. Batched cached retrieval is not built in — attach a WidgetCache view.

func NewWidgetStore added in v0.0.13

func NewWidgetStore(exec recordstore.ExecutorI, alloc memory.Allocator, cfg WidgetStoreConfig) (inst *WidgetStore)

NewWidgetStore wires the store. A nil alloc selects the Go allocator.

func (*WidgetStore) Begin added in v0.0.13

func (inst *WidgetStore) Begin(id uint64, ts time.Time, env WidgetEnvelope) *WidgetEntityBuilder

Begin opens one entity with the envelope roles as typed arguments (Key, Order), a live lifecycle and the pass-through envelope.

func (*WidgetStore) Buffered added in v0.0.13

func (inst *WidgetStore) Buffered() int

Buffered reports the number of committed-but-unflushed rows.

func (*WidgetStore) Close added in v0.0.13

func (inst *WidgetStore) Close()

Close discards everything unflushed and releases the store's Arrow builder; the store must not be used afterwards. Required for a clean shutdown under tracking/checked allocators — the default Go allocator needs no Close.

func (*WidgetStore) Delete added in v0.0.13

func (inst *WidgetStore) Delete(id uint64, ts time.Time) (err error)

Delete appends a tombstone row for id (no components; lifecycle marks the deletion). The tombstone writes through to attached cache views like any commit — a versioned deletion, so GetLive reads the key as absent immediately.

func (*WidgetStore) DiscardPending added in v0.0.13

func (inst *WidgetStore) DiscardPending()

DiscardPending drops every committed-but-unflushed row: records retained by a failed Flush, rows still in the DML builder, and an open (uncommitted) entity frame. It gives a failed Flush "never happened" semantics — ClickHouse state is the truth afterwards. Ambient stamps are cleared with the frame they were pushed for — including any pushed through Raw() — so an abandoned builder cannot leak its stamps onto later entities.

func (*WidgetStore) EnsureTable added in v0.0.13

func (inst *WidgetStore) EnsureTable(ctx context.Context) (err error)

EnsureTable applies the composed CREATE TABLE (plus the DDLTail suffix, when configured). Idempotent (CREATE TABLE IF NOT EXISTS). The embedded script is issued one statement per Exec — the optional CREATE DATABASE, then the CREATE TABLE — because the ClickHouse HTTP interface rejects a multi-statement body; under a Table override the statements are re-pointed at the override (recordstore.ProvisioningStatements: header and database only, the column block stays byte-identical).

func (*WidgetStore) Flush added in v0.0.13

func (inst *WidgetStore) Flush(ctx context.Context) (n int, err error)

Flush drains the buffered rows to ClickHouse (Arrow IPC, ADR-0089 pivot). Rows are durable when Flush returns, engine permitting. On insert failure the transferred records are retained and the next Flush ships them — Flush is retryable; DiscardPending drops them instead. An open (uncommitted) entity frame makes Flush error.

func (*WidgetStore) GetLive added in v0.0.13

func (inst *WidgetStore) GetLive(ctx context.Context, key uint64) (ent *WidgetEntity, found bool, err error)

GetLive is Latest plus tombstone interpretation: newest row wins, a tombstone reads as absent — the state-view read (the cache view carries the cached twin).

func (*WidgetStore) Latest added in v0.0.13

func (inst *WidgetStore) Latest(ctx context.Context, key uint64) (ent *WidgetEntity, found bool, err error)

Latest returns the newest row for key, tombstone-blind (the raw row-level primitive — a deleted key still returns its tombstone row; GetLive is the interpreted state-view read). Reads see only flushed rows.

func (*WidgetStore) Replay added in v0.0.13

func (inst *WidgetStore) Replay(ctx context.Context, key uint64, fromOrder time.Time, opts recordstore.ReplayOpts) iter.Seq2[*WidgetEntity, error]

Replay iterates the rows for key with the order column >= fromOrder in ascending order — the event-replay primitive. A zero fromOrder replays everything (zero time.Time has no defined UnixNano; recordstore.SeqTs(0) is the equivalent explicit bound); opts.To bounds the replay exclusively ("state as of To") and opts.Limit caps the row count. The sequence is single-use; ctx must stay valid until iteration completes; the query may execute at call time or lazily during iteration (buffered in v1 — a streaming executor changes nothing visible); an error ends the sequence as a final (nil, err) pair. Reads see only flushed rows.

func (*WidgetStore) VerifySchema added in v0.0.13

func (inst *WidgetStore) VerifySchema(ctx context.Context) (err error)

VerifySchema compares the live table's columns — names and order — against the generated schema. EnsureTable alone cannot detect drift on an existing table (IF NOT EXISTS succeeds against any old shape), and the decode is positional, so drift fails late or, for same-typed column swaps, silently: run VerifySchema at startup after EnsureTable.

It checks the COLUMN contract only. The membership-id contract is not checked and cannot be from the schema alone: the ids live in the membership columns as ordinary values, and a FAT table legitimately carries other kinds' ids beside this store's. Rows written under a different id assignment therefore pass VerifySchema and then match nothing — every component decodes absent, with no error. Compare WidgetMembershipIds against the writer's assignment when pointing this store at rows it did not write.

What it describes is the reader's own projection — DESCRIBE over `SELECT *`, not over the table — because that is what the positional decode consumes. DESCRIBE TABLE lists columns SELECT * does not return (MATERIALIZED, ALIAS, EPHEMERAL), so a table legitimately carrying one beside the generated shape — a derived column added by ALTER after EnsureTable, which is how a store gets a skip index over a value its leeway attributes only encode — would fail a check whose contract still held. Describing the projection also follows the asterisk_include_* settings, which decide what SELECT * returns and which nothing here pins: under those, a derived column IS in the decode, and this notices where a column-kind filter would have blessed the mis-decode.

type WidgetStoreConfig added in v0.0.13

type WidgetStoreConfig struct {
	// Table overrides the ClickHouse table this store binds — the baked
	// WidgetTableName — for every statement it issues (DDL, DESCRIBE, INSERT,
	// SELECT). Optionally database-qualified ("<db>.<table>"), unquoted-
	// identifier shape only ([A-Za-z_][A-Za-z0-9_]* per part; the
	// constructor panics otherwise). Empty (the default) binds the baked
	// name. The schema is unchanged — this moves WHERE the rows land, not
	// what they look like — so a scratch table for a test or a per-
	// deployment table needs no regeneration.
	Table string
	// DDLTail is a raw suffix appended verbatim after the composed
	// CREATE TABLE at EnsureTable time — the escape hatch for clauses
	// the generation-time table options (ADR-0102) do not carry.
	DDLTail string
	// Stampers are consulted on every Begin (ADR-0112 M1): each yields
	// surrogate ids stamped as additive HighCardRef memberships onto the
	// entity's attributes. Empty (the default) leaves the store unstamped
	// and behaviour-identical. A stamper must not write to this store.
	// The schema must carry the HighCardRef membership lane, and no
	// component may read that lane back as data — the constructor
	// panics otherwise (ADR-0112 SD2 lane hygiene).
	Stampers []recordstore.ReferenceStamper
	// BestEffortStampFlush relaxes the ADR-0112 SD5 ordered flush: when
	// true, Flush does NOT flush the stampers' dimension stores before its
	// own insert, so a referencing row may become durable ahead of its
	// descriptor fact (resolution self-heals on the dimension's own flush).
	// The default keeps the descriptor durable no later than the row.
	BestEffortStampFlush bool
}

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

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