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 keep zero values for absent fields, such as nil for an any field.
Null, empty strings, false, zero and array positions stay intact in the returned document.
Native time values
Generated native time fields use
time.Time for timestamps and time.Duration for durations.
Use stardust.InstantFromTicks, InstantToTicks, DurationFromTicks and DurationToTicks 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:
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 go.dir to a dedicated directory for the generated packages:
sdk {go {dir gen/people}
debounce {#dur PT0.25S}
max_wait {#dur PT2S}}
| Member | Value | Result |
|---|---|---|
go.dir | Directory path | A dedicated directory for the generated packages. The plugin controls all its contents. A relative path is relative to the working directory of the process at open. A missing directory is created. |
debounce | #dur duration, PT0.25S when absent | How long definition commits must stay quiet before the plugin generates again. |
max_wait | #dur duration, PT2S when absent | The longest wait after the first definition commit. |
Without the go member, the plugin publishes no Go files.
Settings apply only to the database that is open. To change them, open the database again.
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:
| Field | Contents |
|---|---|
enabled | Whether the plugin is enabled |
pending | Whether generation remains |
runs | Run count |
fingerprint | Generation model fingerprint |
outputs | Outputs, each with language, directory and file counts written and removed |
diagnostics | Diagnostics with severity, code, definition type and path, pointer, member, language and message |
failure | Last 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.
The Go packages
The plugin writes one Go package for each definition type that has definitions, in a subdirectory of go.dir named after the type.
The generated names belong to your definitions, so they live in these packages and not in the stardust binding package.
Each package also declares ModelFingerprint, which identifies the definitions that the packages were generated from.
schemas: one struct for each object schema, with a field for each property andjsontags. An optional scalar property is a pointer withomitempty. A property that refers to another stored object schema with$refis a pointer to its struct. Native time references use the time types described above. Other non-object references useany.queries: for each stored query, a row struct, a parameter struct when the query has inputs, and a function that runs the query and returnsiter.Seq2over decoded rows. When the shape of the row is not known before the query runs, for example withresultMode positional, the row type isany.mutations: for each stored mutation, a parameter struct and a function that runs the mutation and returns its commit information.expressions: for each stored expression, a parameter struct and a function that evaluates it with the stored macros of the database and decodes its result.
The inputs of a query or mutation are its parameters and its other variables:
- A declared parameter is a pointer with
omitempty. Anilpointer keeps the stored default. A non-nil pointer supplies the value, including an empty collection. A supplied null parameter reportsmissing_parameter. - A variable that appears only in a predicate, such as
?maxin[<= ?age ?max], is a required parameter and not a pointer. - Every other variable bound by the query clauses is an optional filter, a pointer with
omitempty. Anilfilter leaves the variable unbound, so it matches any value. A value binds the variable before the query runs, so the rows narrow to that value. An entity variable, such as?itemin[?item key ?key], is a*stardust.Link.
The query calculates aggregate and window result variables after the clauses run. These variables are not filter inputs.
For example, these definitions:
schemas/person {type object
properties {name {type string}
age {type integer}}
required [name]}
queries/by_age {find [?name]
parameters {min 18}
where [[?e name ?name]
[?e age ?age]
[>= ?age ?min]]}
queries/items {find [?item]
where [[?item key ?key]]}
expressions/double [* ?n 2]
give schemas.Person, queries.ByAge, queries.Items and expressions.Double with their row and parameter structs. Import each package from the module that contains go.dir:
for row, err := range queries.ByAge(db, queries.ByAgeParameters{}, stardust.RunOptions{}) {
if err != nil {
return err
}
fmt.Println(row.Name)
}
key := any("b")
for item, err := range queries.Items(db, queries.ItemsParameters{Key: &key}, stardust.RunOptions{}) {
if err != nil {
return err
}
fmt.Println(item.Item)
}
doubled, err := expressions.Double(db, expressions.DoubleParameters{N: 21})| Definition type | Go type |
|---|---|
string | string |
integer | int64 |
number | float64 |
boolean | bool |
object without a schema | map[string]any |
array | A slice of the item type |
| Native timestamp | time.Time |
| Native duration | time.Duration |
| Entity | stardust.Link |
| Not known | any |
Names
A name comes from the path of its definition: people/by_name becomes PeopleByName.
A name never depends on the other definitions, so a change to one definition never renames another.
Each definition type has its own package, so a schema and a query can have the same name.
When two definitions of one type claim one name, or one file name, or a definition claims a name that its package declares for itself, the run fails with the code Model_Errors.
A run with naming errors publishes nothing and keeps the files that the plugin published before.
status lists each error in diagnostics. Rename one of the definitions to correct it.
Go struct tags cannot represent some field names, including names with commas or backticks. Such names cause Invalid_Field. Rename the field to correct the error.
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
| Operation | Returns | Summary |
|---|---|---|
| Sdk.Status | document | sdk.status() returns the SDK generator's owned status document. |
| Sdk.Sync | none | sdk.sync(options) waits until the SDK generator published the definitions committed before the call. |
Sdk.Status
func (v Sdk) Status() (*Document, error)Errors
| Status | When |
|---|---|
StatusUnavailable | the database does not enable the sdk plugin |
StatusClosed | the database is closed |
StatusOutOfMemory | native memory is exhausted |
Notes
The document describes generation outputs, diagnostics and the last run: see runs.
Examples
Describe the SDK generator
syncOptions, err := stardust.Parse([]byte(`{maxWait {#dur PT5S}}`), stardust.DUST)
if err != nil {
return err
}
defer syncOptions.Close()
if err := db.Sdk().Sync(syncOptions); err != nil {
return err
}
result, err := db.Sdk().Status()
if err != nil {
return err
}
defer result.Close(){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
func (v Sdk) Sync(options *Document) errorParameters
| Parameter | Type | Ownership | Default | Meaning |
|---|---|---|---|---|
options | *Document | Borrowed for the call | Absent selects operation defaults | The 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
| Status | When |
|---|---|
StatusInvalidArgument | options is not an object, or an option is invalid, duplicated or unsupported |
StatusFailed | the plugin's work failed; status describes the failure |
StatusTimeout | maxWait passed before the work finished |
StatusUnavailable | the database does not enable the sdk plugin |
StatusClosed | 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
syncOptions, err := stardust.Parse([]byte(`{maxWait {#dur PT5S}}`), stardust.DUST)
if err != nil {
return err
}
defer syncOptions.Close()
if err := db.Sdk().Sync(syncOptions); err != nil {
return err
}
result, err := db.Sdk().Status()
if err != nil {
return err
}
defer result.Close(){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