
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
A library of lit-html directives and decorators for handling async operations.
✨ Key Features:
@sync decorator for reactive properties - Automatically sync async state to propertiesnpm install lit-async
The following functions and properties are used in the examples below:
const myPromise = new Promise((resolve) =>
setTimeout(() => resolve('Hello from a promise!'), 1000)
);
async function fetchData() {
await new Promise(resolve => setTimeout(resolve, 3000));
return 'Data loaded!';
}
async function *count() {
for (let i = 1; ; i++) {
yield i;
await new Promise(r => setTimeout(r, 1000));
}
}
async function *colors() {
const colors = ['lightyellow', 'lightpink', 'lightgreen', 'lightcyan'];
let i = 0;
for (;;) {
yield colors[i++ % colors.length];
await new Promise((r) => setTimeout(r, 1000));
}
}
trackA directive that renders the resolved value of a promise or an async generator.
track<T>(state: Promise<T> | AsyncIterable<T> | T, transform?: (value: T) => unknown): unknown
Ownership Policy: track does not own the async sources it receives. It will not call return() on generators or abort() on promises. When disconnected from the DOM, it simply unsubscribes and ignores future values. You are responsible for managing the lifecycle of your async sources.
Error Handling: If a promise rejects or an async generator throws, track logs the error to the console and renders undefined.
Re-render Behavior: track caches the last value received. When the component re-renders but the generator hasn't yielded new values, track displays the last cached value instead of showing nothing.
Render the resolved value of a promise directly into the DOM.
import { html } from 'lit';
import { track } from 'lit-async';
html`${track(myPromise)}`
track also works with async generators, re-rendering whenever the generator yields a new value.
Important: When using async generators with track, store the generator instance in a property to avoid creating new generators on each render. Creating a new generator on every render will cause resource leaks as old generators continue running.
// ✅ Good: Store generator instance
class MyElement extends LitElement {
_count = count();
render() {
return html`Count: ${track(this._count)}`;
}
}
// ❌ Bad: Creates new generator each render
render() {
return html`Count: ${track(count())}`;
}
Provide a second argument to transform the resolved/yielded value before rendering.
class MyElement extends LitElement {
_count = count();
render() {
return html`Count * 2: ${track(this._count, (value) => value * 2)}`;
}
}
You can bind an async generator to an element's attribute. Lit handles this efficiently.
class MyElement extends LitElement {
_colors = colors();
render() {
return html`
<div style=${track(this._colors, (color) => `background-color: ${color}`)}>
This div's background color is set by an async generator.
</div>
`;
}
}
track can be used as a property directive to set an element's property to the resolved/yielded value.
class MyElement extends LitElement {
_count = count();
render() {
return html`<input type="number" .value=${track(this._count)} readonly>`;
}
}
Multiple track directives can share the same generator instance. All instances will receive the same values simultaneously.
class MyElement extends LitElement {
_count = count();
render() {
return html`
<p>First instance: ${track(this._count)}</p>
<p>Second instance: ${track(this._count)}</p>
<p>With transform (×10): ${track(this._count, (v) => v * 10)}</p>
`;
}
}
All three track() directives will display the same count value at the same time.
How it works: The generator runs once, and each yielded value is cached and broadcast to all track() directives using that generator. When a new track() subscribes to an already-running generator, it immediately receives the last yielded value (if any), ensuring all subscribers stay synchronized.
loadingA helper that shows a fallback value while waiting for async operations to complete.
loading<T>(state: Promise<T> | AsyncIterable<T> | T, loadingValue: unknown, transform?: (value: T) => unknown): AsyncIterable<unknown>
import { html } from 'lit';
import { track, loading } from 'lit-async';
html`${track(loading(fetchData(), 'Fetching data...'))}`
You can also provide a custom template for the loading state:
const loadingTemplate = html`<span>Please wait...</span>`;
html`${track(loading(fetchData(), loadingTemplate))}`
@syncA decorator that automatically syncs a property with values from a Promise or AsyncIterable.
sync<T>(stateFactory: (this: any) => Promise<T> | AsyncIterable<T> | T): PropertyDecorator
Requirements:
accessor keyword with the propertyexperimentalDecorators: true (uses standard decorators)Basic Example:
import { LitElement, html } from 'lit';
import { customElement } from 'lit/decorators.js';
import { sync } from 'lit-async';
@customElement('my-element')
class MyElement extends LitElement {
// Sync an async generator
@sync(() => (async function*() {
for (let i = 0; ; i++) {
yield i;
await new Promise(r => setTimeout(r, 1000));
}
})())
accessor count: number | undefined;
render() {
return html`<p>Count: ${this.count ?? 'Loading...'}</p>`;
}
}
Using this context:
@customElement('user-profile')
class UserProfile extends LitElement {
@property() userId!: string;
// Factory function can access 'this'
@sync(function() {
return fetch(`/api/users/${this.userId}`).then(r => r.json());
})
accessor userData: User | undefined;
render() {
return html`<p>User: ${this.userData?.name ?? 'Loading...'}</p>`;
}
}
FAQs
Async directives and helpers for Lit.
We found that lit-async demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.