SQLStyle

Documentation

Application Stored Procedures

Define typed application operations that SQLStyle runs with the current database primary.

An Application Stored Procedure (ASP) is a named, typed database operation whose handler is application code and whose execution follows the database primary.

The logic remains ordinary Go or Rust. Nothing is installed into SQLite or deployed as a separate service.

Why use an ASP?

A request can arrive at any application instance, while a database write must run against the current primary. Without an ASP, the application needs its own primary discovery and RPC path, or a separate service that all writes pass through.

An ASP makes that boundary part of the database SDK. The application defines the operation by giving the SDK its name, payload and result types, and handler. The Go SDK returns a typed function; the Rust SDK returns a typed Procedure. Application code invokes the Go function or calls Procedure.call in Rust.

How a call runs

When the application invokes a generated ASP call, the SDK reads its current view of primary authority and chooses one of two paths.

Local primary

The SDK passes the typed payload directly to the handler. No payload or result serialization is involved.

Replica

The SDK serializes the payload and sends a named invocation over SQLStyle's primary stream. At the primary, the receiving SDK resolves the name to the handler, runs it, and serializes the result back.

Serialization

The payload and result are application-defined native types. Go and Rust infer them from the handler and use named-map MessagePack by default; domain types do not implement SQLStyle-specific serialization methods.

Named-map MessagePack is the recommended general-purpose format in the managed Go and Rust SDKs. Go uses msgpack field tags when present and otherwise reuses json field tags; Rust uses ordinary Serde traits. Stable field names plus optional or defaulted new fields support additive compatibility across mixed application versions. Rust also offers Postcard for fixed schemas. Both SDKs retain JSON for readable payloads. Go's JSON selection follows encoding/json tags and custom marshaling interfaces. Its options-bearing definition chooses one encoding for both the payload and result.

Go's Binary selection calls the standard encoding.BinaryAppender and encoding.BinaryUnmarshaler interfaces directly. CustomEncoding retains the typed serializer contract for generated and handwritten formats. Remote append encoders receive a reusable SDK-owned destination.

Select JSON for a procedure

addTodo, err := sqlstyle.DefineIASPWithOptions(
    config,
    "todos.add-json",
    sqlstyle.ProcedureOptions{Encoding: sqlstyle.JSON},
    handler,
)

When no primary is available

If no writable primary can accept the invocation, the SDK waits for authority to change instead of failing immediately. One wait budget covers primary discovery, write admission, and retries after a transient busy or unavailable response. The default budget is eight seconds.

If a primary becomes ready within the budget, the invocation continues. Otherwise the SDK returns its primary-unavailable error. A zero budget fails immediately; in Go, context cancellation or an earlier deadline ends the wait sooner.

Set the budget in the SDK open configuration. It is not a shared JSON5 configuration field.

Primary wait timeout configuration

wait := 3 * time.Second
// wait := time.Duration(0) // Fail immediately.
config.Options.PrimaryWaitTimeout = &wait

Example

Define and call todos.add

type AddTodo struct {
    Title string `json:"title"`
}

type AddTodoResult struct {
    ID int64 `json:"id"`
}

addTodo, err := sqlstyle.DefineIASP(
    config,
    "todos.add",
    func(ctx context.Context, tx *sql.Tx, payload AddTodo) (AddTodoResult, error) {
        result, err := tx.ExecContext(ctx,
            "INSERT INTO todos(title) VALUES (?)", payload.Title)
        if err != nil { return AddTodoResult{}, err }
        id, err := result.LastInsertId()
        return AddTodoResult{ID: id}, err
    },
)
if err != nil { return err }

result, err := addTodo(ctx, AddTodo{Title: "ship it"})
id := result.ID

ASP or IASP

Choose based on who owns the transaction and duplicate handling.

IASP

SQLStyle owns the transaction. It makes remote execution idempotent; the handler must not commit or roll back.

ASP

Your application owns the transaction and duplicate handling. Use it when the operation already has an idempotency key or duplicate policy.