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/crawl/hubs/decorators.html.
@ancestorAttribute
Doc ID: docs/_decorators/ancestor-attribute · full page
The @ancestorAttribute decorator automatically injects the value of an ancestor's attribute into a class property at the time of connectedCallback.
@ancestorAttribute
The @ancestorAttribute decorator automatically injects the value of an ancestor's attribute into a class property at the time of connectedCallback.
Principle
This decorator uses HTML.getAncestorAttributeValue to traverse up the DOM tree from the current element and find the first ancestor that has the specified attribute. The value of this attribute is then assigned to the decorated property.
Usage
Import
import { ancestorAttribute } from "@supersoniks/concorde/decorators";
Basic example
The component reads dataProvider and testAttribute from its ancestor wrapper. By default (dynamic: false), values are read once at connect.
import { html, LitElement } from "lit";
import { customElement } from "lit/decorators.js";
import { ancestorAttribute } from "@supersoniks/concorde/decorators";
@customElement("demo-ancestor-attribute")
export class DemoAncestorAttribute extends LitElement {
@ancestorAttribute("dataProvider")
dataProvider: string | null = null;
@ancestorAttribute("testAttribute")
testAttribute: string | null = null;
render() {
return html
dataProvider: ${this.dataProvider ?? "null"}
testAttribute: ${this.testAttribute
@autoSubscribe
Doc ID: docs/_decorators/auto-subscribe · full page
> Legacy: prefer @subscribe + DataProviderKey. Examples below may still show PublisherManager for existing codebases.
@autoSubscribe
> Legacy: prefer @subscribe + DataProviderKey. Examples below may still show PublisherManager for existing codebases.
The @autoSubscribe decorator automatically detects which publishers are accessed within a method and subscribes to them. When any of these publishers change, the method is automatically re-executed.
Principle
This decorator wraps a method to track which publishers are accessed during its execution. It then subscribes to all accessed publishers, and when any of them change, the method is re-executed. This provides automatic reactivity without manually managing subscriptions.
Usage
Import
import { autoSubscribe } from "@supersoniks/concorde/decorators";
Basic example
@customElement("demo-auto-subscribe")
export class DemoAutoSubscribe extends LitElement {
static styles = [tailwind];
@state() displayText: string = "";
@state() computedValue: number = 0;
@autoSubscribe()
updateDisplay() {
const value1 = PublisherManager.get("autoValue1").get() || 0;
const value2 = PublisherManager.get("autoValue2").get() || 0;
this.computedValue = value1 + value2;
this.displayText = ${value1} + ${value2} = ${this.computedValue};
}
@bind
Doc ID: docs/_decorators/bind · full page
Binds a class property to a path in a publisher. The property updates when publisher data changes.
@bind
Binds a class property to a path in a publisher. The property updates when publisher data changes.
For Lit re-renders, also add @state() on the same property.
See also: @subscribe, @handle, @publish, @get, @post, @put, @patch.
Principle
The decorator subscribes to the DataProvider store using dot notation or a DataProviderKey. Updates flow into the decorated property (Data flow).
Import
import { bind } from "@supersoniks/concorde/decorators";
Example
@customElement("demo-bind")
export class DemoBind extends LitElement {
static styles = [tailwind];
@bind("demoData.firstName")
@state()
firstName = "";
@bind("demoData.lastName")
@state()
lastName: string = "";
@bind("demoData.count")
@state()
count: number = 0;
render() {
return //......
}
updateData() {
set(demoDataKey, { ...get(demoDataKey), count: get(demoDataKey).count + 1 });
// see demo-bind in src/docs/example/decorators-demo-bind-demos.ts
const randomIndex = Math.floor(Math.random() demoUsers.get().length);
const randomUser = demoUsers.get()[randomIndex];
demoData.set({
firstName: randomUser.firstName,
lastName: randomUser.lastName,
count:
@get
Doc ID: docs/_decorators/get · full page
Loads data through API.getDetailed. The decorated property is ApiResult<T> | null: request, response (or null for dataProvider(...) resolution without HTTP), and typed result.
@get
Loads data through API.getDetailed. The decorated property is ApiResult | null: request, response (or null for dataProvider(...) resolution without HTTP), and typed result.
Pass an Endpoint as the first argument. Import get and ApiResult from @supersoniks/concorde/decorators, and Endpoint from @supersoniks/concorde/utils/endpoint.
Configuration
- Default: HTML.getApiConfiguration(host) (ancestor serviceURL, etc.).
- Second argument: DataProviderKey — config is read from the publisher at the resolved path; internal mutations trigger another GET. See API configuration for mock demos.
Dynamic path
${prop} on the endpoint or config key is resolved on the host. While a placeholder is null or undefined, no GET runs. Optional skipEmptyPlaceholder: true also blocks "" (empty string only). Details: Dynamic path placeholders.
Options (GetOptions)
Pass as the second argument (scoped config) or third (with a configuration key). Same shape as send decorators where applicable:
| Option | Description |
|--------|-------------|
| skipEmptyPlaceholder | Treat "" as not ready (see Dynamic path). |
| refetchEveryMs | Automatic polling interval (ms). 0 or omitted = no polling. |
| tri
@handle
Doc ID: docs/_decorators/handle · full page
Typed callback on one or more DataProviderKey<T> paths: invokes the decorated method when a publisher assigns a value (calculations, side effects, updating other @state properties, etc.).
@handle
Typed callback on one or more DataProviderKey paths: invokes the decorated method when a publisher assigns a value (calculations, side effects, updating other @state properties, etc.).
Unlike @subscribe, nothing is bound to the decorated member — only your method runs. @handle is typed and accepts up to 3 keys plus an optional trailing HandleOptions object. It supersedes the string-based @onAssign.
By default the method is called on every assignment, even when the value is null / undefined. Use the options below to restrict that.
Import
import { handle, Skip } from "@supersoniks/concorde/decorators";
import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
import { get, set } from "@supersoniks/concorde/utils";
Basic example
type DemoCounterData = { count: number };
const demoDataKey = new DataProviderKey<DemoCounterData>("demoData");
@customElement("demo-handle")
export class DemoHandle extends LitElement {
@state() doubled = 0;
@state() lastUpdate = "";
@handle(demoDataKey.count)
onCountChange(count: number) {
this.doubled = count 2;
this.lastUpdate = new Date().toLocaleTimeString();
}
incrementCount() {
const data
@onAssign
Doc ID: docs/_decorators/on-assign · full page
> New apps: use @handle with DataProviderKey (Data flow). @onAssign uses untyped string paths; it remains documented for existing codebases — see Migrating to @handle below.
@onAssign
> New apps: use @handle with DataProviderKey (Data flow). @onAssign uses untyped string paths; it remains documented for existing codebases — see Migrating to @handle below.
The @onAssign decorator allows you to execute a method when one or more publishers are updated. The method is called only when all specified publishers have been assigned values.
For a typed equivalent (recommended), use @handle.
Principle
This decorator subscribes to one or more publishers by string path (legacy). When all specified publishers have been assigned values (via set), the decorated method is called with all the values as arguments. Prefer @handle + DataProviderKey and get / set from Data flow.
This is particularly useful when you need to wait for multiple data sources to be ready before executing logic.
Usage
Import
import { onAssign } from "@supersoniks/concorde/decorators";
Basic example
//...
@customElement("demo-on-assign")
export class DemoOnAssign extends LitElement {
static styles = [tailwind];
@state() userWithSettings: any = null;
@state() isReady: boolean = false;
@state() lastUpdate: string = "";
@onAssign("demoUser", "demoUserSettings")
handleDataReady
@patch
Doc ID: docs/_decorators/patch · full page
Sends data through API.patchDetailed. Same model as @post: decorated property is ApiResult<T> | null (request, response, result).
@patch
Sends data through API.patchDetailed. Same model as @post: decorated property is ApiResult | null (request, response, result).
Pass an Endpoint and a DataProviderKey for the request body. Import patch and ApiResult from @supersoniks/concorde/decorators.
Configuration
Same as @post / @get: scoped HTML.getApiConfiguration(host) or DataProviderKey as third argument.
Options (PatchOptions)
Same shape as PostOptions on @post (ApiSendOptions): refetchEveryMs, skipIfBodyMissing, autoPostOnBodyMutation, triggerKey, skipEmptyPlaceholder. See Dynamic path placeholders for ${sessionId} and empty-string behaviour.
Import
import { patch, type ApiResult } from "@supersoniks/concorde/decorators";
import { Endpoint } from "@supersoniks/concorde/utils/endpoint";
import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
Example
const patchBodyKey = new DataProviderKey<Partial<Resource>>("resourcePatch");
@patch(
new Endpoint<Resource, { resourceId: string }>("resources/${resourceId}"),
patchBodyKey,
{ skipEmptyPlaceholder: true },
)
@state()
payload?: ApiResult<Resource> | null;
See also
- @post — POST (live demos)
- @put — full rep
@post
Doc ID: docs/_decorators/post · full page
Sends data through API.postDetailed. The decorated property is ApiResult<T> | null: request, response, and typed result.
@post Sends data through API.postDetailed. The decorated property is ApiResult | null: request, response, and typed result. Pass an Endpoint as the first argument and a DataProviderKey for the request body as the second. Import post and ApiResult from @supersoniks/concorde/decorators, and Endpoint from @supersoniks/concorde/utils/endpoint. Configuration Same as @get: scoped HTML.getApiConfiguration(host) by default, or DataProviderKey as third argument. See API configuration for mock demos. Optional PostOptions (third or fourth argument) | Option | Description | |--------|-------------| | refetchEveryMs | Re-post on an interval (ms). | | skipIfBodyMissing | Skip when body publisher is null/undefined (default: true). | | autoPostOnBodyMutation | Re-post when the body publisher mutates (default: true). false = manual via triggerKey.invalidate(). | | skipEmptyPlaceholder | If true, a placeholder resolved to '' blocks the request (empty string only — not 0 or false). See Dynamic paths. | | triggerKey | DataProviderKey — invalidate() re-runs the POST with the current body. | When the POST runs again - Body publisher onInternalMutation when autoPostOnBodyMutation is true (defa
@publish
Doc ID: docs/_decorators/publish · full page
Write-only binding: assigning to the property publishes to the DataProviderKey path. No read subscription (inverse of @subscribe).
@publish
Write-only binding: assigning to the property publishes to the DataProviderKey path. No read subscription (inverse of @subscribe).
Similar to the “reflect” half of @bind without listening to the publisher.
Import
import { publish } from "@supersoniks/concorde/decorators";
import { sub } from "@supersoniks/concorde/directives";
import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
Example
type PublishDemoData = { email: string; message: string };
const publishDemoKey = new DataProviderKey ("publishDemo");
//
@customElement("demo-publish")
export class DemoPublish extends LitElement {
@publish(publishDemoKey.email)
@state()
email = "";
//
@publish(publishDemoKey.message)
@state()
message = "";
//
render() {
return html
(this.email = (e.target as HTMLInputElement).value)}
label="Email"
>
${sub(publishDemoKey.email)}
;
}
}
Dynamic paths use the same placeholder rules as @bind / @subscribe. Resolution and skipEmptyPlaceholder: Dynamic path placeholders.
@put
Doc ID: docs/_decorators/put · full page
Sends data through API.putDetailed. Same model as @post: decorated property is ApiResult<T> | null (request, response, result).
@put
Sends data through API.putDetailed. Same model as @post: decorated property is ApiResult | null (request, response, result).
Pass an Endpoint and a DataProviderKey for the request body. Import put and ApiResult from @supersoniks/concorde/decorators.
Configuration
Same as @post / @get: scoped HTML.getApiConfiguration(host) or DataProviderKey as third argument.
Options (PutOptions)
Same shape as PostOptions on @post (ApiSendOptions): refetchEveryMs, skipIfBodyMissing, autoPostOnBodyMutation, triggerKey, skipEmptyPlaceholder. See Dynamic path placeholders for ${sessionId} and empty-string behaviour.
Import
import { put, type ApiResult } from "@supersoniks/concorde/decorators";
import { Endpoint } from "@supersoniks/concorde/utils/endpoint";
import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
Example
const bodyKey = new DataProviderKey<UpdatePayload>("resourceBody");
@put(new Endpoint<Resource, { resourceId: string }>("resources/${resourceId}"), bodyKey)
@state()
payload?: ApiResult<Resource> | null;
Changing resourceId on the host re-runs the PUT when the path becomes ready — see Dynamic path placeholders.
See also
- @post —
@subscribe
Doc ID: docs/_decorators/subscribe · full page
Keeps a Lit property in sync with a read-only slice of the DataProvider store. You pass a DataProviderKey; when that path changes, the property updates and the component re-renders.
@subscribe
Keeps a Lit property in sync with a read-only slice of the DataProvider store. You pass a DataProviderKey; when that path changes, the property updates and the component re-renders.
Typical setup (same idea as My first component):
| Piece | Role |
|-------|------|
| Type T | Shape of the object at that path (DocsUserData, { count: number }, …) |
| Key | DataProviderKey — static path ("cart") or dynamic ("users.${userIndex}", "${dataProvider}") |
| Scope on the host | Properties listed in U (e.g. dataProvider, userIndex) — often filled via @ancestorAttribute |
| @subscribe(key) | Mirrors the store into @state() (or another property); read-only from the component side |
For writing back to the store from component state, use @publish. In templates, the same paths work with sub().
Import
import { subscribe } from "@supersoniks/concorde/decorators";
import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
type Data = { count: number };
const dataKey = new DataProviderKey<Data>("data");
@subscribe(dataKey.count)
@state()
count = 0;
Static path
The key path is fixed. The property type must match T at that segment.
const cartKey = new DataProvi
@awaitConnectedAncestors and @dispatchConnectedEvent
Doc ID: docs/_decorators/wait-for-ancestors · full page
The @awaitConnectedAncestors and @dispatchConnectedEvent decorators delay a web component's initialization until its matching ancestors have executed their connectedCallback. This is when contextual e
@awaitConnectedAncestors and @dispatchConnectedEvent
The @awaitConnectedAncestors and @dispatchConnectedEvent decorators delay a web component's initialization until its matching ancestors have executed their connectedCallback. This is when contextual elements (publisher, dataProvider, etc.) are configured.
Principle
When a child component attaches to the DOM, its ancestors may not yet be initialized (especially if custom element definitions are loaded asynchronously). The @awaitConnectedAncestors decorator delays the component's connectedCallback until all ancestors matching the provided CSS selectors have executed their connectedCallback.
The @dispatchConnectedEvent decorator allows ancestors to signal they are ready by dispatching the sonic-connected event at the end of their connectedCallback. The event bubbles, so it can be listened to from anywhere (e.g. document.addEventListener(CONNECTED, handler)).
Ancestors that are not web components (no hyphen in tag name) are considered connected by default and do not need to emit the event.
Usage
Import
import { awaitConnectedAncestors, dispatchConnectedEvent, ancestorAttribute } from "@supersoniks/concorde/decorators";
Basic