SDK generation

The SDK plugin generates a typed package from the database's stored definitions. While the database is open, it updates that package in the configured output directory.

The generated code calls the language binding and provides typed access to stored definitions.

Enable sdk in the plugin selection at open, with the output settings below. Calls without it report the unavailable status.

Query results recursively omit fields containing empty objects or arrays. Generated rows use None for absent Any fields and empty collections for absent list or dictionary fields. Null, empty strings, false, zero and array positions stay intact in the returned document.

Native time values

Generated native time fields use timezone-aware datetime.datetime for timestamps and datetime.timedelta for durations. Time data must be whole microseconds. Typed decoding rejects precision loss. Use stardust.instant_from_ticks, instant_to_ticks, duration_from_ticks and duration_to_ticks for native tick conversion.

The explicit tick conversions refuse precision loss and values outside the target range. These conversions perform no native operation.

A native time schema requires one string member, #utc or #dur, and sets additionalProperties to false. For example, this schema describes a native timestamp:

edn141 B
{:type                 "object"
 :properties           {:#utc {:type "string"}}
 :required             ["#utc"]
 :additionalProperties false}

Use #dur in both places for a duration schema. Schema fields support lists and stored-schema references. Scalar time fields also type query filters and rows. Native time expression results use the same binding types. An ordinary string schema stays text, including strings with a date-time format. Native ticks are signed 64-bit counts of 100 ns. Timestamp ticks count from the Unix epoch in UTC. Duration ticks can be negative.

Settings

Set python.dir to a dedicated package directory.

edn105 B
{:sdk {:python   {:dir "gen/people"}
       :debounce {:#dur "PT0.25S"}
       :max_wait {:#dur "PT2S"}}}

The Python package

The package contains schemas.py, queries.py, mutations.py, and expressions.py. Each module declares MODEL_FINGERPRINT from the same committed definition snapshot. Object schemas and operation inputs use standard TypedDict declarations with the original wire keys. A missing optional schema key stays missing. A present key supplies its value, including zero, false, empty collections, or null. Object schema references use forward annotations, including recursive references. Non-object references use ordinary host values.

Query inputs distinguish missing keys from null. A missing defaulted parameter keeps its stored default. A missing filter leaves its variable unbound. A supplied value narrows the rows. Entity filters use stardust.Link. A variable used only in a predicate remains required. Aggregate and window output variables are not inputs. A supplied null declared parameter reports missing_parameter.

Query functions return async context managers containing typed async row iterators. Use async with and async for, including when you stop iteration early. Query and mutation functions accept page_size, max_cost, as_of and explain as keyword arguments. Use timedelta for max_cost and subscription waits. Sync takes a native document containing its call options. The context closes the current page, rows, and query after completion, an exception, or task cancellation. Inferred object rows use dataclasses. Dynamic rows use ordinary host values. Await mutation functions to obtain stardust.Commit values. Each mutation function closes its mutation handle before it returns. Await expression functions to build stored programs with current macros and obtain decoded host values. Each expression function closes its program and result document before it returns. Schema constructors and input mappings remain plain Python values.

This example creates definitions, generates the package, and uses its typed APIs:

python1.2 kB
import asyncio
from datetime import timedelta
import stardust

async def main():
    async with await stardust.open("people.db", plugins={"sdk": {"python": {"dir": "gen/people"}}}) as database:
        await stardust.patch_definition_value(database, "schemas", "person", {
            "type": "object", "properties": {"name": {"type": "string"}}, "required": ["name"]})
        await stardust.patch_definition_value(database, "queries", "names", {
            "find": ["?name"], "where": [["schema", "person", "?e"], ["?e", "name", "?name"]]})
        await stardust.patch_definition_value(database, "expressions", "double", ["*", "?n", 2])
        async with await stardust.parse("{maxWait {#dur PT5S}}", "dust") as sync_options:
            await database.sdk().sync(sync_options)
        from gen.people import schemas, queries, expressions
        await stardust.merge_patch_value(database, {"#_ada": schemas.Person(name="Ada")})
        async with queries.names(database) as rows:
            names = [row.name async for row in rows]
        doubled = await expressions.double(database, {"n": 21})
        assert names == ["Ada"]
        assert doubled == 42

if __name__ == "__main__":
    asyncio.run(main())

Activate your license before this example, as described in installation.

Runs

The plugin generates once at open, and again after commits that change definitions. Data commits do not start a run. A run reads all definitions from one committed snapshot.

Each successful render replaces the output directory's contents, including the startup run. See file ownership before choosing an output directory.

Call status to inspect these fields:

FieldContents
enabledWhether the plugin is enabled
pendingWhether generation remains
runsRun count
fingerprintGeneration model fingerprint
outputsOutputs, each with language, directory and file counts written and removed
diagnosticsDiagnostics with severity, code, definition type and path, pointer, member, language and message
failureLast run failure, with code and message; absent after a successful run

When to call sync

Generation runs automatically at open and after definition commits, according to the debounce settings. Opening the database or committing definitions can finish before the generated files are ready. Call sync before compiling or reading files that must include those definitions. Applications that can use the files after automatic generation finishes do not need an explicit sync.

The call requests a render immediately, bypassing debounce. It also rebuilds files when definitions have not changed. Its options accept only the common maxWait option. A timeout leaves the requested run active. Repeated waits after a timeout join that run. Inspect status when generation reports failed. Without an output language, sync validates the model inline and writes no files.

File ownership

The plugin owns the entire configured output directory. Every successful render removes its previous contents, including edited files, unrelated files and subdirectories. Keep application source, database files and other data outside this directory. Do not edit generated files: the next run replaces your changes. The plugin retains only its internal .stardust-sdk.lock file between runs.

The plugin renders and validates the package before it removes any files. Rendering errors leave the previous directory contents unchanged. A filesystem error during cleanup or writing can leave the directory empty or partly written. The next successful run clears that partial output and writes a fresh package. Use sync before a build and check its result.

Each file is written through an atomic rename. Rebuilding the whole directory is not atomic. A destination lock prevents concurrent publication and reports Destination_Busy when another publisher holds it. Use a separate output directory for each database, with no directory nested inside another output directory. Cleanup removes symlinks inside the output directory without following them. The output directory itself and the lock file must not be symlinks. Directory inspection is limited to 65,536 entries, 64 levels and 16 MiB of stored paths. An inspection failure leaves the previous contents unchanged.

Operations

OperationReturnsSummary
Sdk.statusdocumentsdk.status() returns the SDK generator's owned status document.
Sdk.syncnonesdk.sync(options) waits until the SDK generator published the definitions committed before the call.

Sdk.status

python28 B
def status(self) -> Document

Parameters

ParameterTypeOwnershipDefaultMeaning
selfSdkBorrowed for the callRequiredThe sdk owner for this operation. Keep it alive during the call.

Errors

StatusWhen
"unavailable"the database does not enable the sdk plugin
"closed"the database is closed
"out_of_memory"native memory is exhausted

Notes

The document describes generation outputs, diagnostics and the last run: see runs.

Examples

Describe the SDK generator

python154 B
async with await stardust.parse("{maxWait {#dur PT5S}}", "dust") as sync_options:
    await db.sdk().sync(sync_options)
result = (await db.sdk().status())
Result / 176 B
{:value {:enabled     true
         :pending     false
         :runs        1
         :fingerprint "sha256:f0e170ac54b67b0f9a8e4794922a586c21da2910b0766dcd3feb410b416b61e8"}}

C exports: stardust_db_plugin_call, stardust_result_take_document, stardust_result_error, stardust_result_release

Sdk.sync

python55 B
def sync(self, options: Document | None = None) -> None

Parameters

ParameterTypeOwnershipDefaultMeaning
selfSdkBorrowed for the callRequiredThe sdk owner for this operation. Keep it alive during the call.
optionsDocument | NoneBorrowed for the callNoneThe borrowed call options, or none. Optional maxWait is a nonnegative native duration, such as {maxWait {#dur PT5S}}. Without it, or with zero, the call checks once.

Errors

StatusWhen
"invalid_argument"options is not an object, or an option is invalid, duplicated or unsupported
"failed"the plugin's work failed; status describes the failure
"timeout"maxWait passed before the work finished
"unavailable"the database does not enable the sdk plugin
"closed"the database is closed

Notes

Database close interrupts an active wait. See when to call sync.

Examples

Run the SDK generator and wait for it

python154 B
async with await stardust.parse("{maxWait {#dur PT5S}}", "dust") as sync_options:
    await db.sdk().sync(sync_options)
result = (await db.sdk().status())
Result / 176 B
{:value {:enabled     true
         :pending     false
         :runs        1
         :fingerprint "sha256:f0e170ac54b67b0f9a8e4794922a586c21da2910b0766dcd3feb410b416b61e8"}}

C exports: stardust_db_plugin_call, stardust_result_take_document, stardust_result_error, stardust_result_release