Concorde documentation

Concorde documentation — Concorde decorators reference. Hub for all Concorde Lit decorators: @get @post @put @patch @subscribe @publish @bind @handle and related. Use this page when asking about any decorator. Doc ID: hubs/decorators. Keywords: decorator, @get, @post, @put, @patch, @subscribe, @publish, @bind, @handle, ApiResult, Endpoint, DataProviderKey. URL: https://concorde.supersoniks.org/hubs/decorators.html.

@ancestorAttribute

Doc ID: docs/decorators/ancestor-attribute · full page

Copies the value of an attribute from the nearest element that carries it (the host itself, then its ancestors, across shadow roots) into a class property. Use it to pick up a scope such as dataProvid

@ancestorAttribute

Copies the value of an attribute from the nearest element that carries it (the host itself, then its ancestors, across shadow roots) into a class property. Use it to pick up a scope such as dataProvider without passing it down by hand, typically to feed a dynamic key of @subscribe.

Import

import { ancestorAttribute } from "@supersoniks/concorde/decorators";

@ancestorAttribute("dataProvider")
dataProvider: string | null = null;

Read once at connect

By default the value is read once, just before the component's connectedCallback, and never updated. The closest element with the attribute wins. If no ancestor has it, the property keeps its initial value.

An attribute present without a value ( ) resolves to "". A camelCase name also matches its lowercase form (dataProvider and dataprovider).

Follow DOM changes

With { dynamic: true }, the property stays in sync while the component is connected: when an ancestor changes the attribute, when a closer ancestor gains or loses it, and when the component is moved. When no ancestor has the attribute, the property becomes null. Add @state() so Lit re-renders on each assignment.

@ancestorAttribute("dataProvider", { dyn

@autoSubscribe

Doc ID: docs/decorators/auto-subscribe · full page

Legacy decorator that re-runs a method whenever one of the store values it reads is assigned. For new code, prefer @subscribe with a DataProviderKey: dependencies are explicit and typed.

@autoSubscribe

Legacy decorator that re-runs a method whenever one of the store values it reads is assigned. For new code, prefer @subscribe with a DataProviderKey: dependencies are explicit and typed.

Import

import { autoSubscribe } from "@supersoniks/concorde/decorators";
import { get } from "@supersoniks/concorde/utils";

@autoSubscribe()
updateDisplay() {
  this.total = get(passengersKey) + get(crewKey);
}

Tracked reads

The method runs once after connectedCallback. Every store value read during that run becomes a dependency; assigning any of them runs the method again. Write the result to a @state() property (or call requestUpdate()) so the component re-renders.

Notes

- Several assignments in the same task trigger a single re-run (batched with queueMicrotask).
- Keep reads unconditional: a value read only behind a condition becomes a dependency the first time that branch runs.
- Subscriptions are removed in disconnectedCallback.

API reference

| Argument | Type | Description |
|----------|------|-------------|
| none | | @autoSubscribe() takes no argument; apply it to a method of a custom element |

@bind

Doc ID: docs/decorators/bind · full page

@bind(key) keeps a class property and one path of the DataProvider store in sync. With { reflect: true } the sync goes both ways: the property follows the store, and each assignment is written back to

@bind

@bind(key) keeps a class property and one path of the DataProvider store in sync. With { reflect: true } the sync goes both ways: the property follows the store, and each assignment is written back to it. For one direction only, use the complementary decorators: @subscribe to read, @publish to write. See Data flow for the overall model.

Import

import { bind } from "@supersoniks/concorde/decorators";
import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";

type Flight = { flight: string; gate: string; boarded: number };
const flightKey = new DataProviderKey<Flight>("flight");

@bind(flightKey.boarded, { reflect: true })
@state()
boarded = 0;

Basic usage

The key is a DataProviderKey: TypeScript checks that the property type matches T at that path. The property updates each time the store value changes. Add @state() so Lit re-renders.

Reflect

With reflect: true, each assignment to the property calls set() at the key path, so every other subscriber sees the new value. An internal guard prevents update loops. Without reflect (the default), assigning the property does not touch the store, and @bind behaves like @subscribe.

Dynamic path

Placeholders s

@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(

@handle

Doc ID: docs/decorators/handle · full page

@handle(...keys) calls the decorated method each time a publisher assigns one of up to three store paths, with one typed argument per key. Nothing is stored on the component: use it for derived values

@handle

@handle(...keys) calls the decorated method each time a publisher assigns one of up to three store paths, with one typed argument per key. Nothing is stored on the component: use it for derived values and side effects, and @subscribe when you only need the value. See Data flow for the overall model.

Import

import { handle, Skip } from "@supersoniks/concorde/decorators";
import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";

const cockpitKey = new DataProviderKey&lt;{ altitude: number }&gt;("cockpit");

@handle(cockpitKey.altitude)
onAltitude(feet: number) {
  this.meters = Math.round(feet  0.3048);
}

Basic usage

The method runs once with the current value when the component connects, then on every assignment, including null and undefined. Below, each climb assigns a new altitude and the method derives meters and flight level.

Dynamic path

${prop} placeholders resolve from properties of the same component; when one changes, the method is called with the value at the new path. Rules: Dynamic paths.

const personKey = new DataProviderKey&lt;OnBoard, { seatIndex: number }&gt;(
  "demoOnBoard.${seatIndex}",
);

@property({ type: Number }) seatIndex = 0;

@onAssign

Doc ID: docs/decorators/on-assign · full page

Legacy decorator that calls a method with the values at one or more string paths of the store, once all of them are defined. For new code, use @handle with { waitForAllDefined: true }: same behaviour,

@onAssign

Legacy decorator that calls a method with the values at one or more string paths of the store, once all of them are defined. For new code, use @handle with { waitForAllDefined: true }: same behaviour, with typed DataProviderKey paths.

Import

import { onAssign } from "@supersoniks/concorde/decorators";

@onAssign("passenger", "passengerSettings")
onReady(passenger: Passenger, settings: Settings) {}

Wait for every path

The method is called only when every path holds a value other than null or undefined, with the values in the order of the paths. After that, each assignment calls it again with the latest values. Subscriptions start after connectedCallback and stop in disconnectedCallback.

Dynamic path

Paths accept ${prop} placeholders resolved on the host. While a placeholder is null or undefined, that path is unsubscribed. Write the paths as plain strings, not template literals. Rules: Dynamic path placeholders.

@property({ type: Number }) seat = 0;

@onAssign("passengers.${seat}", "passengerSettings.${seat}")
onSeat(passenger: Passenger, settings: Settings) {}

Migrate to @handle

// Before
@onAssign("passenger", "passengerSettings")
onReady(passenger: any, setting

@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

@publish

Doc ID: docs/decorators/publish · full page

@publish(key) writes a component property to one path of the store: every assignment to the property is published there. It never reads back from the store; it is the inverse of @subscribe. See Data f

@publish

@publish(key) writes a component property to one path of the store: every assignment to the property is published there. It never reads back from the store; it is the inverse of @subscribe. See Data flow for the overall model.

Import

import { publish } from "@supersoniks/concorde/decorators";
import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";

type CheckIn = { passenger: string; seat: string };
const checkInKey = new DataProviderKey&lt;CheckIn&gt;("checkIn");

@publish(checkInKey.passenger)
@state()
passenger = "Ada Lindqvist";

Basic usage

Each assignment calls set() on the publisher at the DataProviderKey path. On connect, a defined property value is published immediately, so the initializer overwrites what the store held. Leave the property undefined to keep the store value. To read and write the same path, use @bind with reflect: true.

The check-in form below publishes passenger and seat; the boarding pass reads them back with sub().

Dynamic path

${prop} placeholders resolve from properties of the same component. When a placeholder changes, the property is published to the new path. While it is null or undefined, nothing is published. Rules

@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

@subscribe

Doc ID: docs/decorators/subscribe · full page

@subscribe(key) keeps a component property in sync with one path of the store: when a publisher assigns that path, the property updates and the component re-renders. It is read-only; use @publish to w

@subscribe

@subscribe(key) keeps a component property in sync with one path of the store: when a publisher assigns that path, the property updates and the component re-renders. It is read-only; use @publish to write and sub() to read inside a template. See Data flow for the overall model.

Import

import { subscribe } from "@supersoniks/concorde/decorators";
import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";

type Cockpit = { altitude: number };
const cockpitKey = new DataProviderKey&lt;Cockpit&gt;("cockpit");

@subscribe(cockpitKey.altitude)
@state()
altitude = 0;

Static path

The key is a DataProviderKey: the property type must match T at that path. Add @state() so Lit re-renders. Until the publisher holds a value, the property keeps its initializer.

type Flight = { code: string; gate: string; status: string };
const flightKey = new DataProviderKey&lt;Flight&gt;("flight");

@subscribe(flightKey)
@state()
flight: Flight | null = null;

Dynamic path

${prop} placeholders resolve from properties of the same component. Declare them in the key's second generic so TypeScript requires them on the host. When seatIndex changes, the path is resolved again and perso

@awaitConnectedAncestors

Doc ID: docs/decorators/wait-for-ancestors · full page

Delays a component's connectedCallback until its matching ancestors are defined and connected, so context they set up (a dataProvider, a publisher, a fetched payload) is ready when the child starts. P

@awaitConnectedAncestors

Delays a component's connectedCallback until its matching ancestors are defined and connected, so context they set up (a dataProvider, a publisher, a fetched payload) is ready when the child starts. Pair it with @dispatchConnectedEvent on the ancestors, which signals readiness.

Import

import {
  awaitConnectedAncestors,
  dispatchConnectedEvent,
} from "@supersoniks/concorde/decorators";

@customElement("flight-scope")
@dispatchConnectedEvent()
export class FlightScope extends LitElement {}

@customElement("gate-panel")
@awaitConnectedAncestors("flight-scope")
export class GatePanel extends LitElement {}

Wait for an ancestor

Arguments are CSS selectors tested with element.matches() on each ancestor, across shadow roots. Here the parent element is not defined yet: the child stays uninitialized until you register it.

Several ancestors

With several selectors, the child waits for every matching ancestor. Register both to initialize it, in any order.

Ancestor already connected

When the ancestor is already connected, or no ancestor matches, the child initializes immediately.

Signal readiness

@dispatchConnectedEvent() dispatches a bubbling sonic-connect