Skip to content

Declaring App Dependencies

An app rarely lives in isolation. A pricing rule reads asset.product.name. An order-enrichment reactor imports a lookupPrices function from the Products app. A returns app navigates from order to custom returnRequest nodes contributed by another app.

The requires section of h_app.yaml turns those assumptions into explicit contracts. The CLI, language server, and server use the same declaration to:

  • Compile your components against a realistic stub while you author.
  • Reject installation if your app references graph shapes or modules that are neither declared nor part of the base graph.
  • Surface Broken runtime state in the portal when the active tenant no longer satisfies your contract.

If your app only reads fields that exist in the base Hantera graph and only imports modules shipped inside your own app, you do not need a requires block.

Where requires lives

requires is an optional top-level field of h_app.yaml, alongside components, ingresses, registryEntries, and others.

yaml
id: my-pricing-app
name: My Pricing App
version: 1.0.0

requires:
  graph:
    # Nodes, fields, edges, and sets the app reads or navigates.
    ...
  modules:
    # Filtrera modules the app imports from other apps.
    ...

components:
  - id: components/apply-pricing.hrc

You can declare only requires.graph, only requires.modules, or both.

Graph contracts (requires.graph)

A graph contract says: I depend on this graph shape. Wherever my app runs, the graph must provide at least these nodes, fields, edges, and sets.

Nodes, fields, and edges

Inside requires.graph.nodes, list every node you reference. For each node, declare only the fields and edges you actually use.

yaml
requires:
  graph:
    nodes:
      asset.product:
        fields:
          name:
            type: text
            dimension: locale
          vatClass:
            type: enum
        edges:
          skus:
            cardinality: many
            relatedNode: asset.product.sku
          prices:
            cardinality: many
            relatedNode: asset.price

      asset.product.sku:
        fields:
          skuNumber:
            type: text
          quantity:
            type: number

      asset.price:
        fields:
          currentPrice:
            type: number

Sets

requires.graph.sets declares top-level query entry points your components address.

yaml
requires:
  graph:
    sets:
      products: { node: asset.product }
      prices: { node: asset.price }

If your components never query from a top-level set, you can omit sets.

Graph field types

For requires.graph.nodes.<node>.fields.<field>.type, use graph field types:

  • text
  • enum
  • number
  • instant
  • [text]
  • [enum]

enumDefinition is only valid for enum and [enum].

Dimensions and enums

If a field has a dimension, declare it explicitly.

yaml
fields:
  name:
    type: text
    dimension: locale

Enum fields use type: enum and optionally enumDefinition.

yaml
fields:
  vatClass:
    type: enum
    enumDefinition: vat-class

Module contracts (requires.modules)

When one app imports a module exported by another app, declare it under requires.modules.

yaml
requires:
  modules:
    /apps/products/price-lookup.module.hrc:
      exports:
        lookupPrices:
          type: '(params: { productNumbers: [text], currencyCode: text, priceListKeys: [text], window: duration }) => { text -> { currentPrice: number | nothing, history: [{ at: instant, price: number | nothing }], lowestPrice: number | nothing, highestPrice: number | nothing } }'

The example above is the actual contract for the Hantera Products app's Price Lookup module.

The key is the path part of the component:// URI (everything after component://).

For requires.modules.<module>.exports.<name>.type, values are Filtrera type strings. Any valid Filtrera type expression is allowed.

Full example

yaml
id: order-enrichment
name: Order Enrichment
version: 1.0.0

requires:
  graph:
    nodes:
      asset.product:
        fields:
          name:
            type: text
            dimension: locale
        edges:
          skus:
            cardinality: many
            relatedNode: asset.product.sku
      asset.product.sku:
        fields:
          skuNumber:
            type: text
    sets:
      products: { node: asset.product }

  modules:
    /apps/products/price-lookup.module.hrc:
      exports:
        lookupPrices:
          type: '(params: { productNumbers: [text], currencyCode: text, priceListKeys: [text], window: duration }) => { text -> { currentPrice: number | nothing, history: [{ at: instant, price: number | nothing }], lowestPrice: number | nothing, highestPrice: number | nothing } }'

components:
  - id: components/enrich-order.hrc

ingresses:
  - id: orders
    componentId: components/enrich-order.hrc
    type: http
    acl:
      - actors/order:create
    properties:
      route: order-enrichment/orders
      httpMethod: POST

How validation works

Validation runs in three phases.

1) h_ app pack (manifest-level validation)

The CLI rejects manifests if, for example:

  • A requires.modules key resolves to a local module in the same app.
  • A requires.graph declaration overlaps with your own registryEntries contribution.
  • A requires.graph field type is outside the supported graph field type set (text, enum, number, instant, [text], [enum]).
  • A requires.modules export type string cannot be parsed as Filtrera.
  • An edge relatedNode cannot be resolved in the composite context.

2) Install / isolated compile

Components compile against a stub composed of:

  • Base Hantera graph
  • This app's own contributions
  • requires.graph overlays
  • requires.modules stub modules

3) Activation

When activated in a tenant, components are compiled against real producer apps in the activated union. Type mismatches surface as regular Filtrera diagnostics, and runtime state becomes Broken until compatibility is restored.

Portal view

In Apps → App details, you can see:

  • Runtime state (Starting, Running, Broken)
  • Diagnostics (for Broken)
  • Declared Dependencies (requires.graph, requires.modules)
  • Derived Provides (graph fields and edges from registryEntries)

© 2024 Hantera AB. All rights reserved.