Hypermedia Schemas in One Page
The condensed reference to Hypermedia Schemas, covering the model, the library by link, the three keys that type a document, and the commands, SDK calls and agent verbs that read, write and check them.

Every item on this page links into the reference. Read it once top to bottom, then use it as an index. The long versions are why, how it works, the schema language, typed documents, user stories.

The model in ten lines

    Every value is one of nine kinds: null, boolean, integer, float, string, bytes, list, map, link. struct is a map with named fields. The bytes are DAG-CBOR, and the readable form is dag-json.

    A schema is a map that constrains a value: type, properties (one property per field: {value, required?, description?}), items, values, anyOf, target, generics, and leaf constraints. A bare literal ("draft", 1) is also a schema, and accepts exactly that value. See the schema language.

    The meta-schema describes what a schema is: a union of struct-schema, map-schema, list-schema, scalar-schema, link-schema, include-schema, anyof, var-schema, literal-schema. It validates itself.

    A schema is itself a DAG-CBOR blob with a CID. Schemas reference each other by hm:// name (references), so types can recurse and form cycles (the fixpoint problem). Library schemas are named by the domain hyper.media: example/person is hm://hyper.media/example/person. The SDK resolves these names from its bundle. The network does not resolve domains in hm:// URLs yet, so the published pages carry the docs space's key.

    {type: X} alone includes X. {type: X, properties: …} extends it: the parent's fields plus new ones, closedness kept.

    Validation is advisory in the editors (warn, never block) and strict in the reference validator, the CLI's checks, and the agent's refusals for blobs.

The library, by link

Typed documents: three keys

key

on which document

says

value

schemaDefinition

the type's home page

"I define a schema."

ipfs://<cid> of the schema blob

attributesSchema

an instance page

"My attributes follow that schema."

the type page's hm:// URL (or ipfs://<cid>)

childAttributesSchema

a folder

"My children's attributes follow that schema."

the type page's hm:// URL (or ipfs://<cid>)


    An attributes schema is a plain struct of the fields a document's metadata carries. person-doc is {surname required, givenName}. It never extends document and never mentions metadata or content. Build one type on another by extending the struct: employee = person + employeeId.

    A document's effective schema is its own attributesSchema, else its parent's childAttributesSchema, else none. This goes one level deep: a folder types its direct children.

    When a document is checked, the base metadata fields (name, summary, icon, the three keys, …) are folded in beneath the type's fields. The result stays open to extra keys, so a typed page is still a full document with a body.

    A type has a URL because its home page does: hm://acme/person resolves through that page's schemaDefinition to the blob. Reference it by URL to follow the type as it changes, or by CID to pin it (pinning versus following).

The worked demo is the World Builder: a world page typed by world-doc, four type pages, four folders bound with childAttributesSchema, and starter pages with dates, title pills and linked objects.

Working with them

In the app

    A type's home page (any document with schemaDefinition) shows New Document and New Collection. New Document starts a draft whose attributesSchema is the page. New Collection starts a draft whose childAttributesSchema is the page. The options menu adds Extend Schema, New Raw Value (a bare IPFS blob of the type, for developers), and Inspect Schema. New Schema (Developer Mode) opens the editor on the meta-schema.

    Any document's Attributes tab opens with its two schema bindings, Attributes schema and Children attributes schema. Each shows the full schema editor in place when the document owns the schema. The options menu's Attributes Schema / Children Attributes Schema entries jump there, and draft an empty struct when nothing is bound. Publishing freezes edits into IPFS objects that the binding keys point at.

    A typed document's Attributes tab shows the type's required fields as fixed rows, the optional ones as chips, the right control per field (date picker, title pill, file picker, dropdown), and violations in red. It never blocks a save.

With the CLI

The Seed CLI reads the same library and resolves references the same way the app does. Every command is in the CLI reference.

# type a document, a folder, and publish a type seed-cli document create -f bob.md --attributes-schema hm://acme/person seed-cli document update hm://acme/people --child-attributes-schema hm://acme/person seed-cli document create -f person.md --schema-definition person.schema.json # publishes the blob, binds schemaDefinition # frontmatter works too: attributesSchema: hm://acme/person / childAttributesSchema: … / schemaDefinition: ipfs://… # check seed-cli document validate hm://acme/people/bob # effective schema, each violation on a line, exit 1 on any seed-cli document validate hm://acme/people/bob --content --json seed-cli space import self -d ./site --check # every file against its schema; publishes nothing on a violation seed-cli schema get hm://acme/person --resolve # the schema, references followed, extensions merged # find seed-cli query acme --where 'attributesSchema=hm://acme/person' # every page typed by person (any attribute condition works) seed-cli query '*' --where 'has:childAttributesSchema' # every typed folder, anywhere seed-cli attributes acme # which attribute keys documents carry, with kinds seed-cli attributes acme --values status # the distinct values of one key seed-cli schema validate ipfs://<cid> # is this blob a valid Hypermedia schema? # raw objects seed-cli blob create -f value.json --schema hm://acme/person # validate, publish, link the blob to its type seed-cli blob validate -f value.json --schema hm://acme/person seed-cli blob verify ipfs://<cid> # signature + schema of a published blob

From code (@seed-hypermedia/client)

The SDK holds the one implementation every surface shares. The CLI and the agents service call these same functions.

import {HM_SCHEMAS, validate, resolveSchema, structFields} from '@seed-hypermedia/client/schema-engine' import { classifyRef, resolveSchemaRef, loadSchemaRef, hydrateSchemaRegistry, effectiveSchemaRef, checkDocumentSchema, checkSchemaDefinition, metadataSchemaOf, documentMetadataSchema, blobSchemaRef, withoutSchemaLink, } from '@seed-hypermedia/client/schema-resolve' validate(HM_SCHEMAS['example/person'], {name: 'Bob', age: 41}) // [] — errors as "$.path: message" strings const person = await loadSchemaRef(client, 'hm://acme/person') // {schema, cid, registry} — refs fetched validate(person.schema, value, '$', {}, person.registry) await checkDocumentSchema(client, docId, doc.metadata) // {schema, via: 'own'|'inherited'|'none', required, missing, violations} await checkSchemaDefinition(client, 'ipfs://<cid>') // violations of the meta-schema, or why it failed to load await client.request('QueryDocuments', {filter: {comparison: {key: 'attributesSchema', operator: 'EQUAL', value: {stringValue: 'hm://acme/person'}}}}) await client.request('ListDocumentAttributeNames', {account: 'acme'}) // keys documents carry, with kinds await client.request('ListDocumentAttributeValues', {path: ['status'], kind: 'DOCUMENT_ATTRIBUTE_KIND_STRING'}) documentMetadataSchema(metadataSchemaOf(person.schema)) // the open, base-folded metadata schema the editor uses


    classifyRef sorts a reference into a bundled library name, a CID, or a document URL without fetching. resolveSchemaRef follows it: from a document URL to its schemaDefinition, then to the blob.

    effectiveSchemaRef applies the own-else-parent rule. hydrateSchemaRegistry fetches every type a schema references so nested type references validate.

    QueryDocuments takes a recursive DocumentFilter (and / or / not, comparison, exists / missing, stringMatch, spaceMatch, pathMatch, urlMatch) in protobuf JSON. It sorts by attribute or built-in field, and pages. The two attribute listings answer "which keys exist" and "which values does this key take". The Explore grammar compiles to the same filter: compileExploreQuery(parseExploreQuery(q), {type: 'node'}).filter?.toJson() from the shared package.

    Generated TypeScript for the whole library ships as schema-types.generated.ts (HMDocument, HMMetadata, HMChange<B>, …), from scripts/hypermedia/typegen.mjs.

Through an agent

The Seed Agents verbs (read, write, call) expose the same checks. A document that does not conform gets a warning. A raw object that does not conform is refused.

    read hm://acme/people/bob: a typed document returns a schema block: {schema, via, required, missing, violations}. /:attributes reads only the metadata.

    read ipfs://<cid>: a DAG-CBOR object decodes to value, signature (who signed, whether it verifies), and schema (violations against the schema it links to, or options: {schema}).

    write hm://acme/people/bob with options.metadata: {surname: "Smith", attributesSchema: "hm://acme/person"}. Any key is allowed. The result reports schema and warnings beside the published id. options.metadata.childAttributesSchema types a folder, and options.metadata.schemaDefinition makes a page a type. dryRun: true returns the same report without publishing.

    call tool query takes q in the Explore grammar, or a raw filter. Examples of q: attributesSchema=hm://acme/person (every page typed by person), in:hm://acme/places kind=fortress, has:childAttributesSchema, status="In Progress" AND priority>=3. It returns each document with its full attributes, sortable and paged. call tool attributes lists the attribute keys documents carry, with kinds. With key, it lists the distinct values of one key.


    write ipfs:// with JSON content and options: {schema: "hm://acme/person"} validates the value and refuses it on a violation. force: true publishes anyway, with warnings. The published object carries a schema link. options.schema: "hypermedia-schema" publishes a schema blob after checking it against the meta-schema. options.sign: true signs a blob whose type extends blob.

Who can check what

check

app

CLI

SDK

agent

a document against its effective schema

Attributes tab, live

document validate, space import --check

checkDocumentSchema

read → schema; write → warnings

a schemaDefinition is a valid schema

schema editor, live

schema validate

checkSchemaDefinition

write → warnings

a raw object against a type

value editor, live

blob validate, blob create

validate + loadSchemaRef

read ipfs://, write ipfs:// (refuses)

a signed blob's signature

inspector

blob verify

verifySignedBlob

read ipfs:// → signature

resolve a type by URL, CID or name

schema browser

schema get [--resolve]

resolveSchemaRef, loadSchemaRef

read hm:// (type page), read ipfs:// (blob)

find documents by attribute, or typed by a schema

Explore (advanced search)

query --where, query --filter

QueryDocuments

call → query

which attribute keys exist, and their values

Explore's attribute pickers

attributes, attributes --values

ListDocumentAttributeNames, ListDocumentAttributeValues

call → attributes

See also

Do you like what you are reading? Subscribe to receive updates.

Unsubscribe anytime