Stores: the framework-free core
A page’s state is in four stores exported from the root, each a
Store<T>: getSnapshot() returns a stable, frozen snapshot and
subscribe(listener) calls the listener once synchronously and on every
change, returning an unsubscribe. Both are bound methods, so they can be
handed to useSyncExternalStore, a Svelte $store or a plain
addEventListener-style page unchanged. The stores import no framework and
no browser global; they are what the three adapters and the scaffold’s vanilla
page are built on.
Using the stores
Section titled “Using the stores”import { QgisSdk, formStore, layersQuery } from "@archont561/qgis-sdk";import schema from "./bridge.schema";
const qgis = QgisSdk.fromBridgeSchema(schema);
// A form over the declared `buffer` action: fields from the request schema.const buffer = formStore(qgis, "buffer", { initial: { distance_metres: 500 } });for (const field of buffer.fields) { // field.name, field.label ("Distance metres"), field.kind ("metres"), // field.required, field.choices, field.details ({ minimum, maximum, ... })}buffer.set("distance_metres", "750"); // strings are coerced per kindbuffer.subscribe((state) => { // state.values, state.issues, state.errors (first code per field), // state.valid, state.dirty, state.submitted, state.submission});await buffer.submit(); // validates first; an invalid form never calls the host
// A layer picker over the curated host surface, grant first.const layers = layersQuery(qgis);layers.subscribe((state) => { // idle | loading | ok (value: VectorLayerInfo[]) | error | refused});void layers.refresh();The four stores
Section titled “The four stores”Choose a store for an operation, stream, form or host query.
OperationStore(operation)
Section titled “OperationStore(operation)”One declared operation as state: idle | pending | ok (value) | error (error).
run(input) resolves with the value or undefined (failed, aborted or
superseded; the state carries the error, so the promise never rejects into an
event handler); only the latest run can change the state. abort(), reset().
StreamStore(open, handlers?)
Section titled “StreamStore(open, handlers?)”One subscription that follows the content of its input (setInput(value),
keyed on stableKey; null closes it): idle | opening | open | ended | failed. setHandlers swaps the callbacks without reopening; close() sends
one unsubscribe even before the host has acknowledged the open.
FormStore / formStore(qgis, actionId, { initial })
Section titled “FormStore / formStore(qgis, actionId, { initial })”The typed form for one declared action, built from its request schema with
schemaFields(schema): a FormField per property with the parameter kind the
Python side marked ("x-qgis-sdk",
doc-12), a labelFor label,
required, nullable, choices and details. set(name, value) coerces
strings per kind (numbers, booleans, integers) and validates with
validateAgainstSchema; setValues, reset(), submit() (validates, marks
submitted, then runs the operation), abort(), dispose(), field(name).
The declared dialect has no defaults, so starting values come from initial.
HostQueryStore(qgis, hostOperationId, loader)
Section titled “HostQueryStore(qgis, hostOperationId, loader)”Reads the curated host surface after the handshake. When qgis.host.granted
lacks the operation the query needs it settles on refused (with the operation
id and a message) without a call, so a page can say “add layers.read to
the page’s grants” instead of surfacing a not_authorized fault.
layersQuery(qgis, { pageSize? }) follows layers.list to the last page;
layerFieldsQuery(qgis, layerId) reads layers.get for a field input whose
of names a layer input. Both need the layers.read grant, which the
scaffold’s [tool.qgis-sdk.web] grants now lists.
Misuse, and the shared behaviour suite
Section titled “Misuse, and the shared behaviour suite”A QgisSdkStoreError ("store_error") is raised for misuse: a form over an
action the schema does not declare, or a field the form does not have. The
behaviour suite in tests/store-behaviour.ts runs the same ten expectations
against the core and against each adapter’s driver, so an adapter cannot
drift from the stores.