alien-resources
v0.1.3
Published
Async signal bridge for alien-signals — reactive resources with loading/error states and automatic cancellation
Maintainers
Readme
alien-resources
Async signal bridge for alien-signals. Wraps async fetchers into reactive signals with loading/error states and automatic cancellation.
Install
npm install alien-resources alien-signalsalien-signals is a peer dependency.
Usage
import { signal, effect } from "alien-signals"
import { createResource } from "alien-resources"
const userId = signal("user-1")
// Resource tracks a fetcher — refetches when dependencies change
const profile = createResource(async (abort) => {
const res = await fetch(`/api/users/${userId()}`, { signal: abort })
return res.json()
})
// Read data, loading, and error reactively
effect(() => {
if (profile.loading()) console.log("Loading...")
else if (profile.error()) console.log("Error:", profile.error())
else console.log("Data:", profile())
})
// Change dependency — automatically refetches (cancels in-flight request)
userId("user-2")API
createResource(fetcher, options?)
Creates a reactive resource that tracks an async fetcher.
Parameters:
fetcher: (abort: AbortSignal) => Promise<T>— Async function that produces data. Receives anAbortSignalfor cancellation. Any alien-signals signals read in the synchronous preamble (before the firstawait) become reactive dependencies.options.initialValue?: T— Initial data value (default:undefined).
Returns: Resource<T>
| Member | Type | Description |
|--------|------|-------------|
| resource() | T \| undefined | Read current data. undefined while loading (unless initialValue set). |
| resource.loading() | boolean | Whether a fetch is in progress. |
| resource.error() | Error \| null | Error from last fetch, or null. |
| resource.refetch() | void | Manually trigger a re-fetch. |
| resource.mutate(value) | void | Optimistically set data (overwritten on next fetch). |
| resource.dispose() | void | Stop tracking, cancel in-flight fetches. |
How It Works
- The fetcher runs inside an
effect()tracking context. - Any signals read during the fetcher's synchronous portion (before the first
await) become dependencies. - When dependencies change, the effect re-runs — aborting any in-flight request via
AbortControllerand starting a new fetch. - A monotonic fetch ID ensures only the latest fetch writes to the data/loading/error signals (stale responses are discarded).
Examples
Initial value
const data = createResource(
async () => fetchExpensiveData(),
{ initialValue: [] }
)
// data() returns [] immediately, then the fetched resultManual refetch
const feed = createResource(async () => fetchFeed())
// Poll every 30 seconds
setInterval(() => feed.refetch(), 30_000)Optimistic update
const todos = createResource(async () => fetchTodos())
function addTodo(todo: Todo) {
// Show immediately
todos.mutate([...todos()!, todo])
// Persist (refetch will overwrite with server state)
postTodo(todo).then(() => todos.refetch())
}Cleanup
const resource = createResource(async (abort) => {
const res = await fetch(url, { signal: abort })
return res.json()
})
// When done — stops tracking dependencies, cancels in-flight fetch
resource.dispose()Credits & Inspiration
- alien-signals by Johnson Chu — the reactive engine this package builds on. Fastest signals implementation, proven by Vue 3.6 adoption.
- SolidJS by Ryan Carniato — pioneered
createResource(Solid 1.x) andcreateAsync(Solid 2.0) for bridging async data into reactive graphs. The loading/error/refetch/mutate pattern originates here. - Angular —
resource()(Angular 17+) provides a similar async-to-signal bridge with.value(),.isLoading(),.error(). - TanStack Query — popularized the loading/error/refetch pattern for async data in React. Not signal-based, but the UX conventions (stale-while-revalidate, optimistic updates) influenced this API.
Compatibility
This package is not API-compatible with SolidJS createResource or Angular resource(). It follows alien-signals conventions (callable accessors: resource() not resource.value, .loading() not .loading). SolidJS returns [data, { loading, error, refetch, mutate }] (tuple); this returns a single callable with signal sub-properties. The concepts are the same; the ergonomics match the alien-signals ecosystem.
See Also
- alien-projections — Incremental reactive collection transforms for alien-signals.
createProjection(source, { key, map, filter, sort })only re-maps changed entries. The companion package for derived collections. - @silvery/signals — The Silvery TUI framework includes
alien-resourcesandalien-projectionsas part of its@silvery/signalspackage, adding React integration (useSignal), deep stores (createStore), and model factories on top.
License
MIT
