Documentation
¶
Overview ¶
Package db provides multi-tenant PostgreSQL access with app-scoped credential isolation and row-level security.
Production: credentials injected per invocation via Lambda payload. Dev: DATABASE_URL env var with localhost fallback.
Index ¶
- func AcquireScopedConn(ctx context.Context, pool *pgxpool.Pool) (*pgxpool.Conn, func(), error)
- func PrefixFrom(ctx context.Context) string
- func SchemaFrom(ctx context.Context) string
- func Tx(ctx context.Context, pool *pgxpool.Pool, fn func(q Querier) error) error
- func TxReadOnly(ctx context.Context, pool *pgxpool.Pool, fn func(tx pgx.Tx) error) error
- func WithCredential(ctx context.Context, cred Credential) context.Context
- func WithCredentialProvider(ctx context.Context, provider CredentialProvider) context.Context
- func WithDependencies(ctx context.Context, manifest []DependencyGrant) context.Context
- func WithModuleCredential(ctx context.Context, cred Credential) context.Context
- func WithModuleCredentialProvider(ctx context.Context, provider CredentialProvider) context.Context
- func WithPrefix(ctx context.Context, prefix string) context.Context
- func WithSchema(ctx context.Context, schema string) context.Context
- type Credential
- type CredentialProvider
- type CredentialProviderKey
- type DB
- type DependencyGrant
- type PoolCache
- type Querier
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AcquireScopedConn ¶
AcquireScopedConn is AcquireScoped but returns the concrete *pgxpool.Conn instead of the narrower Querier interface. Callers that need the raw driver connection underneath — e.g. COPY FROM STDIN via conn.Conn().PgConn(), which db.Querier does not expose — use this; everything else should keep using AcquireScoped.
func PrefixFrom ¶
PrefixFrom reads the module's resolved table prefix from the context. Returns the empty string when unset.
func SchemaFrom ¶
SchemaFrom reads the app schema from the context.
func Tx ¶
Tx runs fn inside a transaction. Commits on success, rolls back on error or panic.
Inside the transaction, search_path and ms.app_id are set transaction-local (SET LOCAL / set_config is_local=true) so they are automatically cleared on COMMIT or ROLLBACK. The pool's pre-borrow sanitizer is the defense-in-depth backstop before any later caller receives the connection.
err := db.Tx(ctx, pool, func(q db.Querier) error {
queries := generated.New(q)
item, err := queries.GetItem(ctx, id)
if err != nil { return err }
return queries.DeductBalance(ctx, params)
})
func TxReadOnly ¶
TxReadOnly runs fn inside a READ ONLY transaction (pgx.TxOptions with AccessMode: pgx.ReadOnly). Postgres rejects any write attempted inside fn with SQLSTATE 25006 regardless of what the connecting role is granted — the doubled enforcement the deployed cross-module read runs under (decision 18 §2 invariant 2: consumer-role connection + READ ONLY tx). The read executes AS whatever role owns pool, so the install-time GRANT is the ceiling.
Unlike Tx, fn receives the raw pgx.Tx: the dynamic-SELECT executor needs tx.Query + pgx.CollectRows, which the Querier interface does not expose. The app schema (search_path + ms.app_id) is pinned transaction-local from ctx (WithSchema) via the shared applyScope so SET LOCAL auto-clears on COMMIT/ROLLBACK and RLS on the producer's exposed relation resolves to this tenant — SET LOCAL is legal in a read-only tx. The pool's pre-borrow sanitizer is the defense-in-depth backstop.
Acquire-a-conn (like Tx) rather than pool.BeginTx so the tenant-scoping SQL routes through the single applyScope seam (one batched round trip, one place the ms.app_id RLS GUC is composed) instead of a divergent second copy.
func WithCredential ¶
func WithCredential(ctx context.Context, cred Credential) context.Context
WithCredential returns a context with the per-app DB credential set. Used by the platform's Lambda invoke shim before calling the module handler, so Module.DB / Module.Tx can scope queries to the app schema.
func WithCredentialProvider ¶
func WithCredentialProvider(ctx context.Context, provider CredentialProvider) context.Context
WithCredentialProvider installs a renewable per-app database credential source.
func WithDependencies ¶
func WithDependencies(ctx context.Context, manifest []DependencyGrant) context.Context
WithDependencies returns a context carrying the platform-resolved dependency manifest, parallel to WithSchema / WithCredential / WithPrefix. Set once by the Lambda invoke shim (runtime.InjectResources) from the trusted envelope; read by the deployed-plane DependencyDB branch. Never set from module input.
func WithModuleCredential ¶
func WithModuleCredential(ctx context.Context, cred Credential) context.Context
WithModuleCredential returns a context with the per-module DB credential set. Used by the platform's Lambda invoke shim for handlers that touch the module's cross-app shared schema (mod_<id>). The use cases live on Module.ModuleDB; this function only handles credential injection.
func WithModuleCredentialProvider ¶
func WithModuleCredentialProvider(ctx context.Context, provider CredentialProvider) context.Context
WithModuleCredentialProvider installs a renewable module-database source.
func WithPrefix ¶
WithPrefix returns a context with the module's resolved table prefix set. The platform's Lambda invoke shim populates this from app_<app_id>.module_install.prefix per request, so the SDK can resolve a module's per-app table names without a fresh catalog round-trip on each call. Empty prefix means "no resolution available" — callers fall back to the dev/legacy mod_<id> form.
Distinct from WithSchema: schema is the Postgres search_path (app_<app_id>); prefix is the leading segment baked into per-app table names inside that schema (<username>_<slug>_<table>).
Types ¶
type Credential ¶
type Credential struct {
Host string `json:"host"`
Port int `json:"port"`
Database string `json:"database"`
Username string `json:"username"`
Token string `json:"token"`
ExpiresAt time.Time `json:"expiresAt,omitzero"`
}
Credential holds per-invocation database credentials injected by the platform. The same struct serves both per-app and per-module credentials, distinguished by the context key they live under (credentialKey vs moduleCredentialKey). Different DB usernames separate pool cache keys naturally — no flag on the struct is needed.
func CredentialFrom ¶
func CredentialFrom(ctx context.Context) *Credential
CredentialFrom reads the per-app DB credential from the context.
func EnvBaseCredential ¶
func EnvBaseCredential() (Credential, error)
EnvBaseCredential returns the Host/Port/Database derived from the SDK's standard dev env vars (resolved via devEnvURL — same ladder db.Open consults). Username and Token are left empty for the caller to fill from per-invocation credentials — e.g. the install request body carries a per-(app, module) username + token but the DB location it should connect to comes from this helper.
Production (deployed runtime) will replace this with a managed-secret fetch when that infra lands; the contract (returns a partial Credential the caller completes with per-invocation fields) stays identical so the install handler doesn't change.
func ModuleCredentialFrom ¶
func ModuleCredentialFrom(ctx context.Context) *Credential
ModuleCredentialFrom reads the per-module DB credential from the context.
type CredentialProvider ¶
type CredentialProvider interface {
Credential(context.Context) (Credential, error)
}
CredentialProvider supplies renewable database credentials. Implementations must keep Host, Port, Database and Username stable for their lifetime.
func CredentialProviderFrom ¶
func CredentialProviderFrom(ctx context.Context) CredentialProvider
CredentialProviderFrom reads the renewable per-app credential source.
func ModuleCredentialProviderFrom ¶
func ModuleCredentialProviderFrom(ctx context.Context) CredentialProvider
ModuleCredentialProviderFrom reads the renewable module-database source.
type CredentialProviderKey ¶
type CredentialProviderKey interface {
CredentialProviderKey() string
}
CredentialProviderKey may be implemented by a job-scoped provider to allow safe pool reuse within that provider's lifetime. Providers without a key are never shared: endpoint/user equality alone is not enough because it could attach a later task attempt to an earlier attempt's renewal capability.
type DB ¶
type DB struct {
// contains filtered or unexported fields
}
DB is the dev-mode database client (single pool, no credential injection).
func New ¶
New creates a DB with the given connection string. The pool gets the same pre-borrow scope sanitizer as production pools so dev mode has identical session-state guarantees.
func Open ¶
Open creates a DB for local development. Precedence:
MS_LOCAL_DB_URL — set by `mirrorstack dev` to point at the bundled compose Postgres DATABASE_URL — generic 12-factor name, honored for non-CLI dev flows defaultDevURL — falls back to the compose-shape default
Use this for dev mode only. Production uses PoolCache with injected credentials. Returns an error if called inside AWS Lambda (credentials should be injected per-invocation).
func (*DB) Conn ¶
Conn acquires a scoped connection. It sanitizes prior session scope before applying search_path and ms.app_id from context.
type DependencyGrant ¶
DependencyGrant is one authorized cross-module read target the platform resolves at INVOKE time and ships down the trusted Lambda envelope (decision 18 §3). It is advisory ROUTING only — never authority: the install-time GRANT SELECT on the consumer's r_<app8>_<consumer> role is the single DB-level authorizer, and Postgres enforces it on the vended pool no matter what this manifest says.
That last sentence is a DEPLOYED-plane statement. A co-located `mirrorstack dev` session runs one superuser role and has no r_<app8>_<mod> roles at all, so there is no DB-level authorizer beneath it and the SDK's Go-side walk is the only gate — see the KNOWN LIMITS block in internal/core/dependency_local.go for exactly what that does and does not buy.
- Ref is the producer keyed by the SAME normalized form the SDK's parseProducerRef yields (the bare "slug" the platform reconstructs from the producer's owner/slug — see decision 18 §3 step 6).
- Tables maps each exposed LOGICAL table name ("users") to the physical relation the platform computed via ids.PhysicalTableName ("m<hex>_users"). The SDK never derives the physical name; it reads it here — except in a co-located dev session, where there is no platform to compute it; see localPhysicalName. Only tables exposed + consented on the producer's running version appear.
The JSON tags are the cross-repo wire contract with api-platform's local mirror (moduleinvoke.DependencyGrant — api-platform never imports the SDK). A rename on either side surfaces as a decode mismatch, not a silent drop.
func DependenciesFrom ¶
func DependenciesFrom(ctx context.Context) []DependencyGrant
DependenciesFrom reads the dependency manifest from the context. Returns nil when unset — which the deployed DependencyDB branch treats as "the platform does not inject a manifest yet" and fails closed to today's dev-plane-only error (the rollout gate: decision 18 §3 read step 1).
type PoolCache ¶
type PoolCache struct {
// contains filtered or unexported fields
}
PoolCache manages per-(host,port,db,user) connection pools. It is a thin wrapper around refcache.Cache that adds credential validation, key derivation, and pool construction. The refcount + LRU + double-checked-locking lifecycle is implemented in refcache.
func NewPoolCache ¶
func NewPoolCache() *PoolCache
NewPoolCache creates a PoolCache with default settings.
func (*PoolCache) Get ¶
Get returns a pool for the given credential and a release closure. The pool is refcount-pinned until release runs, so concurrent eviction cannot close it. Pair every Get with a deferred release call.
func (*PoolCache) GetProvider ¶
func (c *PoolCache) GetProvider(ctx context.Context, provider CredentialProvider) (*pgxpool.Pool, func(), error)
GetProvider returns a pool whose BeforeConnect hook asks provider for a credential on every new physical connection. The connection scope is pinned by the first credential; a provider may rotate only the token/expiry.
type Querier ¶
type Querier interface {
Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error)
Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error)
QueryRow(ctx context.Context, sql string, args ...any) pgx.Row
}
Querier is the interface for database operations. Compatible with pgxpool.Conn and sqlc's DBTX interface.
func AcquireScoped ¶
AcquireScoped acquires a connection from the pool, sets search_path and ms.app_id from context via a single batch round trip. The pool's PrepareConn hook cleared the previous borrower's scope before Acquire returned.
This entry point is for non-transactional access (no surrounding BEGIN), so it uses session-scoped SET / set_config(_, _, false). The Tx() function uses transaction-local SET LOCAL inside its BEGIN block.