Passing Data — Advanced
In Quick Start, you learned the basics: tasks receive data through a context, and tags keep them decoupled. This guide goes deeper — covering the two ways to provide data to a task and when to choose each.
Via a Tag
Section titled “Via a Tag”Using a tag is the recommended default. A task declares what it needs through a tag, and the pipeline decides what fills it. The task has no knowledge of where the data comes from — it only knows the contract.
const userIdTag = createTag<number>({ name: "userId" })
const fetchUser = createTask({ name: "fetch-user", run: { context: { userId: userIdTag.value }, fn: ({ userId }) => { ... }, },})The pipeline fills the tag using bind. The source can be a task result or a static value via literal — either way, fetchUser is not affected:
// From a task result.stage({ steps: [bind(userIdTag, auth.result.userId)] })
// Or a static value — same tag, same task, different source.stage({ steps: [bind(userIdTag, literal(42))] })import { bind, compose, createTag, createTask } from "@grlt-hub/app-compose"
const userIdTag = createTag<number>({ name: "userId" })
const auth = createTask({ name: "auth", run: { fn: () => { // 👇 Simulates a login result return { userId: 1 } }, },})
const fetchUser = createTask({ name: "fetch-user", run: { // 👇 fetchUser only knows about the tag — not about auth context: { userId: userIdTag.value }, fn: async ({ userId }) => { const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`) const result = await response.json() console.log(JSON.stringify(result, null, 2)) }, },})
compose() .stage({ steps: [auth] }) .stage({ steps: [bind(userIdTag, auth.result.userId)] }) // 👈 tag is filled here .stage({ steps: [fetchUser] }) .run()Directly
Section titled “Directly”You can also provide data directly in the context — either a static value or another task’s result:
// static value baked into the taskcontext: { userId: literal(1)}
// another task's result referenced directlycontext: { userId: auth.result.userId}Both couple the task to something specific. The task can no longer be reused in a different context without modifying it. Treat this as a deliberate choice, not a default. When in doubt, prefer a tag.
import { compose, createTask, literal } from "@grlt-hub/app-compose"
const auth = createTask({ name: "auth", run: { fn: () => ({ userId: 1 }), },})
const fetchUser = createTask({ name: "fetch-user", run: { // 👇 directly coupled to auth — fetchUser can't exist without it context: { userId: auth.result.userId, url: literal("https://jsonplaceholder.typicode.com/users/"), }, fn: async (ctx) => { const response = await fetch(`${ctx.url}/${ctx.userId}`) const result = await response.json() console.log(JSON.stringify(result, null, 2)) }, },})
compose() .stage({ steps: [auth] }) .stage({ steps: [fetchUser] }) .run()