Concorde documentation

Concorde documentation — Fetching remote data in Concorde. Hub for loading online data: @get decorator, Endpoint, API configuration, dynamic paths, sonic-list fetch, sonic-queue, legacy sonic-fetch. Doc ID: hubs/remote-data. Keywords: @get, fetch, remote data, API, Endpoint, ApiResult, serviceURL, sonic-list, sonic-queue, HTTP GET. URL: https://concorde.supersoniks.org/hubs/remote-data.html.

Fetch

Doc ID: core/components/functional/fetch/fetch · full page

sonic-fetch sends a GET request to serviceURL + endPoint and publishes the response under dataProvider, so its children and any subscriber of that provider can render it. In a Lit component, prefer th

Fetch

sonic-fetch sends a GET request to serviceURL + endPoint and publishes the response under dataProvider, so its children and any subscriber of that provider can render it. In a Lit component, prefer the @get decorator; for collections, use List or Queue.

Basic example

dataProvider is required; serviceURL and dataProvider can be set on an ancestor. Children are hidden while the request is pending. With no serviceURL, requests go to the page origin.

       $code  to $city, gate $gate">

Data provider as endpoint

With no endPoint, the dataProvider value is used as the endpoint.

       $code  from $city at $time">

Key

key keeps only part of the response, using a dot path: with { "flights": [ … ] } and key="flights.1", the provider holds the second flight. Add preserveOtherKeys to merge the target into the parent object instead of dropping its siblings. With no key, the whole response is published.

Loader and skeleton

With no loader, nothing is shown during the request. Set loader (fixed overlay) or loader="inline" to show a Loader, or add a   child rendered in place of the content while loading.

         Loading arrivals…

Text responses

When the response type starts w

List

Doc ID: core/components/functional/list/list · full page

sonic-list renders one row per entry of its props array, either set directly or loaded with fetch. In TypeScript, prefer the .items row renderer (with .separator · .noItems · .skeleton); <template> ch

List

sonic-list renders one row per entry of its props array, either set directly or loaded with fetch. In TypeScript, prefer the .items row renderer (with .separator · .noItems · .skeleton);   children are for plain HTML hosts without Lit. Each row gets its own data provider at  /list-item/ .

Basic example

Add fetch to load props from serviceURL + endPoint (or dataProvider when endPoint is empty). key picks the array inside the response. Without fetch, the list only renders the props it receives.

         $time  $code from $city ($airport)">

This example uses HTML templates; from Lit, use the row renderer instead. Fetch attributes (lazyload · refetchEveryMs · headers · authentication) are shared with Fetch.

Row renderer

From a Lit component, set .items to a function (item, metadata) => TemplateResult. Functions only pass as properties, so always use the . binding: the same applies to .separator · .noItems · .skeleton. metadata holds key · even · odd · firstChild · lastChild · onlyChild.

Row variants

In HTML, templateKey (default template) names the row field that selects a  . Rows without a match cycle through the   children that have no data-value. From Lit, branch insid

Queue

Doc ID: core/components/functional/queue/queue · full page

sonic-queue loads a paginated API in batches and renders each batch as an internal List. Choose it over sonic-list for long or endless lists; the key attributes are dataProviderExpression · limit · da

Queue

sonic-queue loads a paginated API in batches and renders each batch as an internal List. Choose it over sonic-list for long or endless lists; the key attributes are dataProviderExpression · limit · dataFilterProvider.

Lazy load

dataProviderExpression is the request path: $offset and $limit are replaced for each batch. The first batch starts at offset (default 0) with limit rows (default 5, at most 15). The next batch loads when the end of the list scrolls into view, and the queue stops at the first empty batch. Put the queue in a box with a fixed height and overflow-y-auto, then scroll inside it.

Rows come from the .items callback, as on List; .noItems and .skeleton work the same way. Without $offset in the expression, the queue sends one request and behaves like a list. Set noLazyload to load every batch in a row without waiting for the scroll.

Filters

dataFilterProvider names a form store (formDataProvider). Each non-empty field is added to the query string; when a field changes, the queue drops the loaded batches and reloads from the first offset, after a filterTimeoutMs debounce (default 400). Search a city, an airport or a flight number.

To keep some form fields o

@get

Doc ID: docs/decorators/get · full page

@get(endpoint) runs an HTTP GET on an Endpoint and stores the full ApiResult<T> in a property, or passes it to a method. It runs again when the endpoint's placeholders or the API configuration change,

@get

@get(endpoint) runs an HTTP GET on an Endpoint and stores the full ApiResult  in a property, or passes it to a method. It runs again when the endpoint's placeholders or the API configuration change, and on demand through refetch. For writes, see @post, @put and @patch.

Import

import { get, Endpoint, type ApiResult } from "@supersoniks/concorde/decorators";

type FlightBoard = { flights: Flight[] };
const departuresEndpoint = new Endpoint&lt;FlightBoard&gt;("flights?direction=departures");

@get(departuresEndpoint)
@state()
departures?: ApiResult&lt;FlightBoard&gt;;

Property

The property is undefined until the first response, then holds result (typed T), response (the HTTP response) and request. It is reset to undefined when the component disconnects.

Without a configuration key, serviceURL and the token are read from the HTML scope: place the component under an element that sets them (see API configuration). The GET waits until serviceURL is available.

Method

Decorate a method to receive the ApiResult  after each GET instead of storing it.

@get(departuresEndpoint)
onDepartures(payload?: ApiResult&lt;FlightBoard&gt;) {
  this.boarding = payload?.result?.flights.filter(

@patch

Doc ID: docs/decorators/patch · full page

Sends the value of a publisher as the body of a PATCH request, to update part of a resource, and stores the response in an ApiResult<T> property. It takes the same arguments, options and send(componen

@patch

Sends the value of a publisher as the body of a PATCH request, to update part of a resource, and stores the response in an ApiResult  property. It takes the same arguments, options and send(component) handle as @post; only the HTTP method differs.

Import

import { patch, type ApiResult } from "@supersoniks/concorde/decorators";
import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
import { Endpoint } from "@supersoniks/concorde/utils/endpoint";

Basic usage

The body holds only the fields to change, here a gate change for flight CN 001.

type Flight = { flight: string; route: string; gate: string };

const gateChangeKey = new DataProviderKey&lt;Partial&lt;Flight&gt;&gt;("gateChange");

@patch(new Endpoint&lt;Flight&gt;("flights/CN001"), gateChangeKey)
@state()
payload?: ApiResult&lt;Flight&gt;;

moveToB12() {
  set(gateChangeKey, { gate: "B12" }); // sends the PATCH
}

Triggers, the send method decorator and every option are described on @post.

API reference

| Argument | Type | Description |
|----------|------|-------------|
| endpoint | Endpoint  | Resource path and response type |
| bodyKey | DataProviderKey  | Publisher read as the partial body |
| 

@post

Doc ID: docs/decorators/post · full page

Sends the value of a publisher as the body of a POST request and stores the response in an ApiResult<T> property. Pass an Endpoint and a DataProviderKey for the body; the request runs on connect and a

@post

Sends the value of a publisher as the body of a POST request and stores the response in an ApiResult  property. Pass an Endpoint and a DataProviderKey for the body; the request runs on connect and again whenever the body changes. @put and @patch work the same way.

Import

import { post, type ApiResult } from "@supersoniks/concorde/decorators";
import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
import { Endpoint } from "@supersoniks/concorde/utils/endpoint";

type SignUp = { email: string; password: string };
type CrewMember = { id: number; email: string };

const signUpKey = new DataProviderKey&lt;SignUp&gt;("crewSignUp");

@post(new Endpoint&lt;CrewMember&gt;("api/register"), signUpKey)
@state()
payload?: ApiResult&lt;CrewMember&gt;;

Basic usage

The property is undefined until a response arrives, then holds request · response · the typed result. Each body mutation sends a new request; mutations in the same frame are coalesced into one. The demo posts to the local mock POST /docs-mock-api/api/register.

Method form

On a method, the decorator calls it with the full ApiResult  after each request instead of assigning a property.

Sending again

Keep th

@put

Doc ID: docs/decorators/put · full page

Sends the value of a publisher as the body of a PUT request, to replace a resource, and stores the response in an ApiResult<T> property. It takes the same arguments, options and send(component) handle

@put

Sends the value of a publisher as the body of a PUT request, to replace a resource, and stores the response in an ApiResult  property. It takes the same arguments, options and send(component) handle as @post; only the HTTP method differs.

Import

import { put, type ApiResult } from "@supersoniks/concorde/decorators";
import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
import { Endpoint } from "@supersoniks/concorde/utils/endpoint";

Basic usage

The full passenger record is sent on connect and after each body mutation. Placeholders such as ${passengerId} are resolved on the host component.

type Passenger = { name: string; flight: string; seat: string };

const passengerKey = new DataProviderKey&lt;Passenger&gt;("passengerForm");
const savePassenger = put(
  new Endpoint&lt;Passenger, { passengerId: string }&gt;("passengers/${passengerId}"),
  passengerKey,
  { autoPostOnBodyMutation: false },
);

@property() passengerId = "ada-lindqvist";

@savePassenger
@state()
payload?: ApiResult&lt;Passenger&gt;;

async save() {
  await savePassenger.send(this);
}

Triggers, the send method decorator and every option are described on @post.

API reference

| Argumen

Data flow

Doc ID: docs/getting-started/dataFlow · full page

Recommended patterns for new Concorde apps (Lit + TypeScript). Under the hood, data lives in a DataProvider store.

Data flow

Recommended patterns for new Concorde apps (Lit + TypeScript). Under the hood, data lives in a DataProvider store.

Quick map

| Need | Use |
|------|-----|
| Read/write in code | get / set / dp + DataProviderKey (static paths only) |
| Reactive Lit template | sub(key) or @subscribe |
| Read component state from store | @subscribe + DataProviderKey  + @state |
| Inherit ancestor attributes | @ancestorAttribute |
| Write from component state | @publish |
| React to assignments | @handle |
| HTTP GET | @get + Endpoint, or sonic-list / sonic-queue with fetch |
| HTTP POST (body from store) | @post + Endpoint + body DataProviderKey |
| HTTP PUT / PATCH (body from store) | @put / @patch — same model as @post |
| Forms | formDataProvider + name on fields (no Subscriber on form elements) |
| Derived / aggregate (JSONata) | derive() or sonic-jsonata — Jsonata |
| Offline doc demos | serviceURL="/docs-mock-api" — Local API demos |

Agent skill: concorde (rules/data.md), see AI agents.

DataProviderKey

import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
import { dp, get, set } from "@supersoniks/concorde/utils";

const cartKey = new DataProviderKey&lt;{ items

API configuration

Doc ID: docs/misc/api-configuration · full page

APIConfiguration is the object built by HTML.getApiConfiguration from ancestor attributes on the DOM (or from a typed publisher — see @get / @post configuration key). It is passed to API by fetchers,

API configuration

APIConfiguration is the object built by HTML.getApiConfiguration from ancestor attributes on the DOM (or from a typed publisher — see @get / @post configuration key). It is passed to API by fetchers, sonic-submit, the wording() directive, @get, @post, @put, and @patch.

> Mock service: same Local API demos Service Worker / Vite middleware. Routes used on this page are listed in the API config routes section below.

Attribute map

| Attribute (ancestor) | APIConfiguration field | Role |
|---------------------|---------------------------|------|
| serviceURL | serviceURL | Base URL (e.g. /docs-mock-api) |
| token | token | Static Bearer sent on REST calls |
| userName / password | userName / password | Basic auth for tokenProvider fetch only |
| eventsApiToken | authToken | Bearer for tokenProvider when no Basic |
| tokenProvider | tokenProvider | Path to GET a new token ({ token } JSON) |
| wordingProvider | — (read by wording()) | Base path + query for label batch GET |
| wordingVersionProvider | — | Publisher id; bump version → reload wordings |
| credentials | credentials | fetch credentials mode |
| addHTTPResponse | addHTTPResponse | Attach sonichttpresponse 

Dynamic path placeholders

Doc ID: docs/misc/dynamic-path · full page

Decorators and DataProviderKey paths can include placeholders resolved on the host component at runtime:

Dynamic path placeholders

Decorators and DataProviderKey paths can include placeholders resolved on the host component at runtime:

- ${prop} or {$prop} — e.g. "users/${userId}", "api/sessions/${sessionId}/sync"
- Nested expressions — e.g. "teams.${teamId}.members"

Resolution is done by resolveDynamicPath. The root property names (userId, sessionId, …) are watched via requestAnimationFrame (see dynamicPropertyWatch.ts).

Default behaviour (ready / not ready)

| Placeholder value | Path ready? | Inserted segment | Notes |
|-------------------|---------------|------------------|-------|
| undefined | no | — | Wait until defined |
| null | no | — | Same as undefined |
| "" | yes | empty string | e.g. sessions//sync — request may still run |
| 0 | yes | "0" | Not treated as “missing” |
| false | yes | "false" | |
| 42, "alpha" | yes | "42", "alpha" | |

When ready: false, decorators do not call the network (for @get / @post / @put / @patch), unsubscribe (@bind / @subscribe), or skip publisher binding (@publish / @handle). The decorated property is often left unchanged or set to undefined (HTTP decorators).

When the placeholder later becomes valid, observers run again and behaviour r

Endpoint

Doc ID: docs/misc/endpoint · full page

Endpoint<T, U> describes a single HTTP path (or a path accepted by API.get) and carries the expected response type T. Unlike DataProviderKey, there is no dot-navigation: the path is one string.

Endpoint

Endpoint  describes a single HTTP path (or a path accepted by API.get) and carries the expected response type T. Unlike DataProviderKey, there is no dot-navigation: the path is one string.

The optional second generic U (default any) describes host properties used to resolve dynamic segments in the path (${…} / {$…}), for example with @get or @post. See Dynamic path placeholders for null / undefined / "" / 0 and skipEmptyPlaceholder.

Import

import { Endpoint } from "@supersoniks/concorde/utils/endpoint";

Construction

const users = new Endpoint&lt;User[]&gt;("users?limit=10");
users.path; // "users?limit=10"

const one = new Endpoint&lt;User, { userId: string }&gt;("users/${userId}");
// userId on the host class is observed when used with @get

Normalization

Endpoint.normalizePath trims the string, rejects an empty path, strips leading slashes for paths relative to serviceURL, collapses duplicate slashes, and validates absolute http(s):// URLs.

Publisher key for payloads

getDataProviderKey() returns a typed publisher key whose path matches the endpoint path (payload typing follows ApiResult for this endpoint). Useful when pairing @get with @publish / @subscribe (see