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
@get loads data and stores the result in an optional ApiResult<T> property.
@get
@get loads data and stores the result in an optional ApiResult property.
The property is undefined until a result is available.
- payload.result: typed data;
- payload.response: the HTTP response;
- payload.request: the request that was sent.
Minimal usage
const usersEndpoint = new Endpoint ("users");
@get(usersEndpoint)
@state()
payload?: ApiResult ;
The same decorator adapts to a method. The method receives the complete
ApiResult payload after each GET:
@get(usersEndpoint)
handleUsers(payload?: ApiResult ) {
if (!payload) return;
console.log(payload.response?.status);
console.log(payload.result);
}
The first argument is always an
Endpoint . API configuration is read
from the HTML scope (serviceURL, token, etc.), so the component is usually
placed below an element that provides this configuration.
Refetching a GET
From the component
Keep the decorator in a constant to access refetch(component). Refetch only
targets the instance passed as the argument, even before it has received a
result.
const usersGet = get(new Endpoint ("users"));
@usersGet
@state()
payload?: ApiResult ;
async refresh() {
const payload = await usersGet.refetch(this);
console.log(
@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
@patch applies a partial update with the body from a publisher. It uses the
@patch
@patch applies a partial update with the body from a publisher. It uses the
same model as @post:
Endpoint , DataProviderKey , ApiResult , options, and the
send(component) handle.
Minimal usage
const bodyKey = new DataProviderKey >("users.patch");
const patchUser = patch(new Endpoint ("users/42"), bodyKey);
@patchUser
@state()
payload?: ApiResult ;
The same decorator adapts to a method:
@patch(new Endpoint ("users/42"), bodyKey)
handlePatch(payload?: ApiResult ) {
if (payload?.response?.ok) console.log(payload.result);
}
To run the operation again from the component:
async savePatch() {
await patchUser.send(this);
}
send(component) returns a Promise resolved after the PATCH completes. See
@post for the method-decorator form and the
when: "before" option.
The PATCH is also sent automatically when the body mutates, unless
autoPostOnBodyMutation: false is set. The available options are described in
@post:
- autoPostOnBodyMutation
- skipIfBodyMissing
- refetchEveryMs
- skipEmptyPlaceholder
- triggerKey
Pass explicit API configuration as the third argument:
@patch(endpoint, bodyKey, apiConfigurationKey)
Import
import { patch, type ApiResult } from "@supersoniks/
@post
Doc ID: docs/_decorators/post · full page
@post sends the contents of a publisher and stores the response in an
@post
@post sends the contents of a publisher and stores the response in an
optional ApiResult property. The property is undefined until a result
is available.
Minimal usage
Pass an Endpoint and a DataProviderKey for the request body.
const userBodyKey = new DataProviderKey ("users.form");
const createUser = post(new Endpoint ("users"), userBodyKey);
@createUser
@state()
payload?: ApiResult ;
The same decorator adapts to a method and receives the complete payload:
@post(new Endpoint ("users"), userBodyKey)
handleCreate(payload?: ApiResult ) {
if (payload?.response?.ok) console.log(payload.result);
}
The current body is read from userBodyKey. By default, the POST is sent when
the component connects and after each body mutation. Mutations in the same
frame are coalesced into one request.
API configuration is read from the HTML scope, as with
@get.
Sending the POST again
From the component
Keep the decorator to access send(component). The body is read again when the
operation runs.
async saveAgain() {
const payload = await createUser.send(this);
console.log(payload?.result);
}
send() returns a Promise | undefined> that resolves after the
operation completes. It
@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
@put replaces a resource with the body from a publisher. It uses exactly the
@put
@put replaces a resource with the body from a publisher. It uses exactly the
same model as @post:
Endpoint , DataProviderKey , ApiResult , options, and the
send(component) handle.
Minimal usage
const bodyKey = new DataProviderKey ("users.form");
const updateUser = put(new Endpoint ("users/42"), bodyKey);
@updateUser
@state()
payload?: ApiResult ;
The same decorator adapts to a method:
@put(new Endpoint ("users/42"), bodyKey)
handleUpdate(payload?: ApiResult ) {
if (payload?.response?.ok) console.log(payload.result);
}
To run the operation again from the component:
async update() {
await updateUser.send(this);
}
send(component) returns a Promise resolved after the PUT completes. See
@post for the method-decorator form and the
when: "before" option.
The PUT is also sent automatically when the body mutates, unless
autoPostOnBodyMutation: false is set. See
@post for the option details:
- autoPostOnBodyMutation
- skipIfBodyMissing
- refetchEveryMs
- skipEmptyPlaceholder
- triggerKey
Pass explicit API configuration as the third argument:
@put(endpoint, bodyKey, apiConfigurationKey)
Import
import { put, type ApiResult } from "@supersoniks/concorde/decorators";
impo
@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