# 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: ```python from httk.atomistic import StructureEntry, UnitcellStructure, UnitcellStructureRecord from httk.store import Backend, EntryIdScheme, SqlStore from httk.serve.optimade import create_asgi_app store = SqlStore( Backend.duckdb("materials.duckdb"), entry_records={StructureEntry: UnitcellStructureRecord}, entry_ids=EntryIdScheme("materials.example", "1"), ) 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: ```python 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 minted lineage IDs; configured `StoredEntrySource` federations may additionally prepend a public prefix. ## Entry ids and revisions Defined entry families need an `EntryIdScheme` (or explicit record IDs). The normal entry endpoint serves the latest revision for each lineage: | URL | Result | | --- | --- | | `/` | Latest revision of every lineage. | | `//` | Latest revision of one lineage. | | `///_httk_revs` | Every revision of that lineage. | | `///_httk_revs/` | One positive, canonical revision number. | | `/_httk_~revs` | Every revision of every lineage. | | `/_httk_~revs/` | One revision by its complete immutable ID. | | `/info/_httk_~revs` | Metadata for the revision endpoint. | | `///_httk_alts` | Every named alternative of that lineage (latest of each kind). | | `///_httk_alts/` | One named alternative by its kind token. | | `/_httk_~alts` | Every named alternative of every lineage. | | `/_httk_~alts/~` | One named alternative by its composite ID. | | `/info/_httk_~alts` | Metadata for the alternative endpoint. | Revision collections support the usual `filter`, `sort`, and paging query parameters. Their resource `id` is the immutable ID (for example, `httk.mydb-1-42~3`) while `_httk_id` is the shared lineage ID (`httk.mydb-1-42`). The ordinary endpoint continues to render `id` as the lineage ID and any declared `immutable_id` property as the current immutable revision. These revision URLs are available only for store-backed adapters. Named alternatives are sibling representations of an entry (kind tokens matching `[a-z][a-z0-9_]*`, for example `conventional` or `primitive`). Their resource `id` is the composite `~` addressing the latest revision of that alternative, `_httk_id` is the shared lineage ID, and `_httk_kind` is the kind token; both `_httk_id` and `_httk_kind` are filterable and sortable, and the collections support the usual `filter`, `sort`, and paging query parameters. The ordinary `/` endpoints serve mains only, and the revision endpoints are likewise mains-only (an alternative's revision ID such as `/_httk_~revs/~~` is never served). Like the revision URLs, these alternative URLs are available only for store-backed adapters. ## Several stores Use `adapter_from_stores` only when an endpoint must federate explicitly named stores or needs public-ID prefixes: ```python from httk.store.backend.sql 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. ## Relationships from stored weak links An exposed weak link (a `StorageInfo` `WeakLink(..., exposed_relationship=True)` on a served record class) whose target family is also served is rendered as an OPTIMADE relationship on the declaring resource. The relationship is keyed by the target's wire entry type, its resource identifiers carry the linked lineage IDs, and the link's `role`, `description`, and edge label render in each identifier's `meta` (the label as the provider-prefixed `_httk_label`). A resource with no such links carries no relationships, and `include=` inlines the related resources when their family is mounted. ## Relationships from stored strong links (run provenance) A run's provenance edges are served as **semantic** OPTIMADE relationships in both directions. Forward, a run resource (`_httk_runs`) carries `_httk_has_input` / `_httk_has_artifact` / `_httk_has_output` keys pointing at the entries it consumed and produced. Reverse, each targeted entry carries the matching `_httk_is_input` / `_httk_is_artifact` / `_httk_is_output` key back to the run — DERIVED at serving time, never stored, with an identical `_httk_label` and `role` payload in both directions. Where weak links are mutable curation *outside* record identity (lineage-live), strong links are record content *inside* identity (revision-pinned): a run's edges are the ones its own revision declares. Reverse serving has store-scoped semantics: a reverse edge derives only from runs in the *same* source store (cross-store references produce no reverse block), from the latest-main revision per lineage. Reverse blocks are suppressed on a target's `~alts` alternative cells and carried lineage-level on `~revs`. A backend with a custom `id_of` mapping gets empty reverse blocks. StrongLink identifiers are raw stored ids in *both* directions, so the F9 non-empty `public_id_prefix` case is untested by design for forward and reverse alike. Mongo serves these on the provider path only — Mongo federation serves no relationships. The forward-only `_httk_has_product` edge has no reverse. These served relationships are filterable through the `_httk_relationships..id HAS ...` extension on this surface, keyed exactly as served (the semantic keys plus typed aliases such as `_httk_relationships.references.id`); see [how it works](how_it_works.md#the-httk-relationships-filter-extension). ## Wire naming `EntryTypeDefinition.served_form()` is the single wire-naming transform applied at the serving edge: a provider entry type and its provider properties are served under their registered prefix (the runs family serves as `_httk_runs` with `_httk_source_id` / `_httk_workflow_declaration_uri`), while standard families and properties are served unchanged. Derived revision and alternative endpoints keep a single prefix for an already-prefixed base — `_httk_runs~revs` and `_httk_runs~alts`, never a doubled `_httk__httk_runs~revs`. The same transform names the run family's served provenance relationship keys (`_httk_has_input`/`_httk_is_input`/...), so runs serve relationships under their registered prefix like any other provider property. ## 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.