Serving directly from an entry store

For a durable deployment, pass an httk.store.EntryStore directly to create_asgi_app. This is the preferred path when records already live in a store:

from httk.atomistic import StructureEntry, UnitcellStructure, UnitcellStructureRecord
from httk.store.db import Database, SqlStore
from httk.serve.optimade import create_asgi_app

store = SqlStore(
    Database.duckdb("materials.duckdb"),
    entry_records={StructureEntry: UnitcellStructureRecord},
)
store.save(UnitcellStructure(...))

app = create_asgi_app(store, baseurl="https://materials.example/optimade")

No provider snapshot or in-memory table is constructed. The application discovers every family in store.entry_layout that has a registered OPTIMADE entry-type definition, translates filters and sorting to the store backend, applies offset/limit before hydrating records, and queries the store for every request. A record saved after app is created is therefore visible to later requests.

The store remains caller-owned. Closing the ASGI application does not close the store or its database; the deployment’s lifespan code should dispose them.

Discovery and mixed stores

adapter_from_store(store, **schema_options) exposes the adapter explicitly when an application needs to inspect or wrap it:

from httk.serve.optimade import adapter_from_store, create_asgi_app

adapter = adapter_from_store(store)
print(adapter.schema.all_entries)
app = create_asgi_app(adapter)

Discovery uses the store’s declared entry-family layout, not every private dataclass table reachable from stored objects. Families without an OPTIMADE definition are ignored. DSP publication declarations can consequently share a store with structures without becoming an OPTIMADE endpoint.

Every discovered OPTIMADE family must have complete, valid StoredPropertyProjection mappings for its served record classes. Adapter construction validates that contract eagerly. For a mixed store that contains defined families intended only for another service or not yet projection-ready, construct an explicit adapter_from_stores source list containing only the families this endpoint should publish.

All concrete record classes configured for one family are served together. Each record class owns its exact response/query/sort behavior through StoredPropertyProjection; properties lacking an exact query or sort mapping are not silently approximated. Public IDs are the canonical content IDs, optionally prefixed when constructing an explicit StoredEntrySource federation.

Several stores

Use adapter_from_stores only when an endpoint must federate explicitly named stores or needs public-ID prefixes:

from httk.store.db import StoredEntrySource
from httk.serve.optimade import adapter_from_stores

adapter = adapter_from_stores(
    (
        StoredEntrySource(primary, StructureEntry, "primary", "p-"),
        StoredEntrySource(archive, StructureEntry, "archive", "a-"),
    )
)

Filtering and bounds remain store-side. Sorted pages are merged from bounded candidate streams and only the final page is hydrated. Duplicate visible IDs raise rather than choosing a record implicitly.

When providers are still useful

EntryProvider and adapter_from_providers remain appropriate for small generated datasets, compatibility adapters, and entry types that do not have a durable storage representation. That path intentionally materializes provider records in an in-memory query store. It should not be used merely to copy a database into memory before serving it.

See examples/optimade/store_server/ for a runnable server and examples/optimade/query_in_process.py for a socket-free live-store example.