This is an automated email from the ASF dual-hosted git repository.
jason810496 pushed a commit to branch v3-4-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/v3-4-test by this push:
new 0873fcdab3b [v3-4-test] TS SDK: Bump to 1.0.0-beta1 and align README
with the guide (#74693) (#75300)
0873fcdab3b is described below
commit 0873fcdab3baab2aa47e0a60ec000dbb7ff42fed
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Thu Oct 22 20:41:03 2026 +0800
[v3-4-test] TS SDK: Bump to 1.0.0-beta1 and align README with the guide
(#74693) (#75300)
* TS SDK: Bump to 1.0.0-beta1 and align README with the guide
Move the human-in-the-loop docs into the TypeScript SDK guide, describe the
TaskClient methods in a table, and trim the README and the API reference
landing page to a quick start that links to the guide.
* Keep the HITL params warning in the TypeScript SDK guide
---------
(cherry picked from commit f3cf550b94b0f3794e6d6aa7ea57d71312b38a86)
Co-authored-by: Jason(Zhe-You) Liu
<[email protected]>
---
.../language-sdks/typescript.rst | 91 +++++--
kubernetes-tests/lang_sdk/ts_example/package.json | 2 +-
task-sdk/docs/ts-bundle-spec.rst | 2 +-
ts-sdk/DEVELOPMENT.md | 14 +-
ts-sdk/README.md | 268 +++------------------
ts-sdk/docs/index.md | 43 ++--
ts-sdk/docs/package-lock.json | 4 +-
ts-sdk/docs/package.json | 2 +-
ts-sdk/docs/tests/deployment-checks.test.mjs | 4 +-
ts-sdk/example/README.md | 80 +++---
ts-sdk/example/package.json | 2 +-
ts-sdk/package.json | 2 +-
12 files changed, 186 insertions(+), 328 deletions(-)
diff --git
a/airflow-core/docs/authoring-and-scheduling/language-sdks/typescript.rst
b/airflow-core/docs/authoring-and-scheduling/language-sdks/typescript.rst
index 84b40c60ca9..5b2ca7a816f 100644
--- a/airflow-core/docs/authoring-and-scheduling/language-sdks/typescript.rst
+++ b/airflow-core/docs/authoring-and-scheduling/language-sdks/typescript.rst
@@ -527,22 +527,36 @@ has retries.
The ``TaskClient``
~~~~~~~~~~~~~~~~~~
-* ``getVariable(key)`` returns the Variable as a string, or ``null`` when it
does not exist.
- ``getVariableOrThrow(key)`` throws ``VariableNotFoundError`` instead, like
Python's ``Variable.get`` without
- a default.
-* ``setVariable(key, value, description?)`` stores a Variable, replacing any
existing value, and
- ``deleteVariable(key)`` removes one. Values are strings, so serialize
structured data, for example with
- ``JSON.stringify``, before storing it.
-* ``getConnection(connId)`` returns a ``ConnectionResult`` with ``id`` and
``type``, plus ``host``, ``schema``,
- ``login``, ``password``, ``port`` and ``extra``, each of which may be
missing or ``null``. It returns ``null``
- when the connection does not exist, and ``getConnectionOrThrow(connId)``
throws ``ConnectionNotFoundError``
- instead, like Python's ``BaseHook.get_connection``.
-* ``getXCom<T>({ key, ... })`` reads an XCom value, or ``null`` when it does
not exist. ``dagId``, ``runId``,
- ``taskId`` and ``mapIndex`` default to the current task; pass ``taskId`` to
read an upstream task's XCom.
- See :ref:`typescript-sdk/types` for how values map to JavaScript types.
-* ``setXCom({ key, value, ... })`` publishes an XCom value.
-* ``taskStateStore`` is this task instance's key/value store, with
``get<T>(key)``,
- ``set(key, value, { retentionMs? })``, ``delete(key)`` and ``clear()``.
+.. list-table::
+ :header-rows: 1
+ :widths: 40 60
+
+ * - Method
+ - Description
+ * - ``getVariable(key)``
+ - The Variable as a string, or ``null`` when it does not exist.
+ * - ``getVariableOrThrow(key)``
+ - Same, but throws ``VariableNotFoundError``, like Python's
``Variable.get`` without a default.
+ * - ``setVariable(key, value, description?)``
+ - Stores a Variable, replacing any existing value. Values are strings, so
serialize structured data, for
+ example with ``JSON.stringify``.
+ * - ``deleteVariable(key)``
+ - Removes a Variable.
+ * - ``getConnection(connId)``
+ - A ``ConnectionResult`` with ``id`` and ``type``, plus ``host``,
``schema``, ``login``, ``password``,
+ ``port`` and ``extra``, each of which may be missing or ``null``.
``null`` when the connection does not
+ exist.
+ * - ``getConnectionOrThrow(connId)``
+ - Same, but throws ``ConnectionNotFoundError``, like Python's
``BaseHook.get_connection``.
+ * - ``getXCom<T>({ key, ... })``
+ - An XCom value, or ``null`` when it does not exist. ``dagId``,
``runId``, ``taskId`` and ``mapIndex``
+ default to the current task; pass ``taskId`` to read an upstream task's
XCom. See
+ :ref:`typescript-sdk/types`.
+ * - ``setXCom({ key, value, ... })``
+ - Publishes an XCom value.
+ * - ``taskStateStore``
+ - This task instance's key/value store, with ``get<T>(key)``, ``set(key,
value, { retentionMs? })``,
+ ``delete(key)`` and ``clear()``. See
:ref:`typescript-sdk/task-state-store`.
.. note::
@@ -550,6 +564,8 @@ The ``TaskClient``
stored value when the Variable is read back. ``setVariable`` without a
description clears the existing
description, and ``deleteVariable`` succeeds even when the Variable does
not exist.
+.. _typescript-sdk/task-state-store:
+
Task state store
----------------
@@ -647,6 +663,49 @@ XComs are stored as JSON, so a value read with ``getXCom``
arrives as the matchi
JavaScript has a single ``number`` type, so integers and decimals arrive as
the same type, and an integer
larger than ``Number.MAX_SAFE_INTEGER`` (2\ :sup:`53` − 1) may lose
precision.
+Human-in-the-loop tasks
+-----------------------
+
+``approval`` and ``hitl``, from ``apache-airflow-ts-sdk/hitl``, make a task
that waits for a person, as Python's
+``ApprovalOperator`` and ``HITLOperator`` do. Pass one to ``dag.task`` in
place of a handler. Options are the
+Python keyword arguments in camelCase:
+
+.. code-block:: typescript
+
+ import { approval } from "apache-airflow-ts-sdk/hitl";
+
+ const signOff = dag.task(
+ "sign_off",
+ approval({
+ subject: ({ report }: { report: { version: string } }) => `Ship
${report.version}?`,
+ responseTimeout: 4 * 60 * 60, // seconds
+ defaults: "Reject",
+ params: { replicas: { value: 3, schema: { type: "integer", minimum: 1
} } },
+ }),
+ )({ report });
+
+ signOff.before(published);
+
+The task waits in ``awaiting_input`` without holding a worker slot, and
resumes once someone responds on the
+**Required Actions** page or ``responseTimeout`` passes.
+
+* ``subject`` and ``body`` (Markdown) are a string, or a function of the
task's inputs.
+* ``responseTimeout`` is in seconds. When it passes, ``defaults`` is applied,
or the task fails without one.
+ ``executionTimeout`` is rejected on a HITL task.
+* ``params`` adds form fields, each with a ``value`` that pre-fills it, and an
optional ``description`` and
+ JSON Schema. Answers arrive as ``paramsInput``. An answer that fills in some
params but not exactly the declared ones
+ fails the task, as ``HITLOperator`` does. The values are not yet checked
against their schema when the task resumes,
+ so an answer given through the REST API can fall outside it.
+* ``assignedUsers`` limits who can respond.
+* The task returns ``{ chosenOptions, paramsInput, respondedAt,
respondedByUser, timedout }``.
+
+On ``"Reject"``, ``approval`` succeeds and skips the tasks directly
downstream, as ``ApprovalOperator`` does.
+Set ``failOnReject: true`` to fail instead, or ``ignoreDownstreamTriggerRules:
true`` to skip everything
+downstream. ``hitl`` takes any ``options``, with ``multiple`` for several, and
never skips or fails on the answer.
+
+A retry or a cleared task writes a fresh request. Branching on the response
and notifiers are not supported yet.
+``ts-sdk/example/src/hitl.ts`` has one Dag per human-in-the-loop feature.
+
.. _typescript-sdk/build:
Building a bundle
diff --git a/kubernetes-tests/lang_sdk/ts_example/package.json
b/kubernetes-tests/lang_sdk/ts_example/package.json
index f6cfe124c40..94290747263 100644
--- a/kubernetes-tests/lang_sdk/ts_example/package.json
+++ b/kubernetes-tests/lang_sdk/ts_example/package.json
@@ -1,7 +1,7 @@
{
"name": "apache-airflow-lang-sdk-k8s-ts-example",
"private": true,
- "version": "0.1.0-beta1",
+ "version": "1.0.0-beta1",
"packageManager": "[email protected]",
"type": "module",
"license": "Apache-2.0",
diff --git a/task-sdk/docs/ts-bundle-spec.rst b/task-sdk/docs/ts-bundle-spec.rst
index 03ee63ceea0..4e4ddc7888f 100644
--- a/task-sdk/docs/ts-bundle-spec.rst
+++ b/task-sdk/docs/ts-bundle-spec.rst
@@ -127,7 +127,7 @@ The ``airflowMetadata`` payload is compact UTF-8 JSON with
this logical shape:
"airflow_bundle_metadata_version": "1.0",
"sdk": {
"language": "typescript",
- "version": "0.1.0-beta1",
+ "version": "1.0.0-beta1",
"supervisor_schema_version": "2026-06-16"
},
"source": "main.ts",
diff --git a/ts-sdk/DEVELOPMENT.md b/ts-sdk/DEVELOPMENT.md
index 1966842c31b..ac8ed73eb4e 100644
--- a/ts-sdk/DEVELOPMENT.md
+++ b/ts-sdk/DEVELOPMENT.md
@@ -148,16 +148,16 @@ names the source commit that produced the tarball. The
tag must already exist
on `apache/airflow` before the dispatch below can reference it as `--ref`:
```bash
-git tag ts-sdk/0.1.0-beta1 <commit-sha>
-git push upstream ts-sdk/0.1.0-beta1
+git tag ts-sdk/1.0.0-beta1 <commit-sha>
+git push upstream ts-sdk/1.0.0-beta1
```
To submit the package to npm's private staging area for review, run:
```bash
-gh workflow run ts-sdk-release.yml --repo apache/airflow --ref
ts-sdk/0.1.0-beta1 \
+gh workflow run ts-sdk-release.yml --repo apache/airflow --ref
ts-sdk/1.0.0-beta1 \
-f release_type=staged \
- -f tag=ts-sdk/0.1.0-beta1 \
+ -f tag=ts-sdk/1.0.0-beta1 \
-f npm_tag=beta
```
@@ -181,14 +181,14 @@ that is already staged; approve or reject that staged
version instead.
To publish directly without npm's staging review, trigger the formal path:
```bash
-gh workflow run ts-sdk-release.yml --repo apache/airflow --ref
ts-sdk/0.1.0-beta1 \
+gh workflow run ts-sdk-release.yml --repo apache/airflow --ref
ts-sdk/1.0.0-beta1 \
-f release_type=formal \
- -f tag=ts-sdk/0.1.0-beta1 \
+ -f tag=ts-sdk/1.0.0-beta1 \
-f npm_tag=beta
```
Use `latest` for stable releases. A prerelease may only use the channel derived
-from its own prerelease identifier (`0.1.0-beta1` → `beta`) or `next` —
+from its own prerelease identifier (`1.0.0-beta1` → `beta`) or `next` —
`scripts/validate-release-inputs.mjs` rejects any other tag. The workflow also
rejects a dispatch that would move the selected npm dist-tag backward as of
when it runs. For a staged release, the dist-tag only actually moves later, at
diff --git a/ts-sdk/README.md b/ts-sdk/README.md
index 10db9aaa7f5..33f8178ea22 100644
--- a/ts-sdk/README.md
+++ b/ts-sdk/README.md
@@ -19,149 +19,44 @@
# Airflow TypeScript SDK
-Write Apache Airflow Dags and tasks in TypeScript.
+Write Apache Airflow Dags in TypeScript, and run them on Node.js.
-**Status:** 0.1.0-beta1 · API may change · Node 22+ · ESM-only
-
-There are two ways to use it:
-
-- **Dag definition**: declare the whole Dag in TypeScript, with its schedule,
tasks, options and dependencies.
-- **Python Dag with stub TaskHandler**: a TypeScript `TaskHandler` implements
each stub task of a Python Dag.
-
-Both use the same task API, and one bundle can serve both.
+**Status:** 1.0.0-beta1 · API may change · Node 22+ · ESM-only
## Installation
```bash
-npm install [email protected]
+npm install [email protected]
npm install --save-dev esbuild
```
-## Declaring a Dag
+## Quick start
+
+Declare a Dag in `src/main.ts`:
```ts
-import { Bundle, Dag, getClient } from "apache-airflow-ts-sdk";
+import { Bundle, Dag } from "apache-airflow-ts-sdk";
-const dag = new Dag("sales_pipeline", { schedule: "@daily", queue:
"typescript" });
+const dag = new Dag("ts_hello", { schedule: "@daily", queue: "typescript" });
-const extract = dag.task("extract", async (): Promise<number> => {
- return Number((await getClient().getVariable("daily_row_count")) ?? "0");
+const extract = dag.task("extract", async (): Promise<number> => 42);
+const report = dag.task("report", async ({ rows }: { rows: number }) => {
+ console.log(`extracted ${rows} rows`);
});
-const transform = dag.task(
- "transform",
- async ({ rows, region }: { rows: number; region: string }) => ({ rows,
region }),
-);
-transform({ rows: extract(), region: "us" });
+report({ rows: extract() });
await new Bundle(dag).serve();
```
-A handler takes one object of named arguments, and calling a task names its
inputs. An input is either the
-reference another task's call returned, which makes this task wait for it and
receive its value, or a literal.
-
-A Dag also has order-only dependencies (`before` and `after`), task groups,
branching with `dag.if` and
-`dag.switch`, and `triggerDagRun`. See the
-[guide](https://airflow.apache.org/docs/apache-airflow/stable/authoring-and-scheduling/language-sdks/typescript.html)
-for all of them.
-
-## Implementing stub tasks of a Python Dag
-
-The Python Dag declares the tasks and their dependencies:
-
-```python
-from airflow.sdk import dag, task
-
-
-@dag
-def sales_pipeline():
- @task.stub(queue="typescript")
- def extract(): ...
-
- @task.stub(queue="typescript")
- def transform(extracted): ...
-
- transform(extract())
-
-
-sales_pipeline()
-```
-
-TypeScript implements them, with a `TaskHandler` per stub task:
-
-```ts
-import { Bundle, getClient, TaskHandler } from "apache-airflow-ts-sdk";
-
-export async function extract() {
- const rowCount = Number((await getClient().getVariable("daily_row_count"))
?? "0");
- return { rowCount };
-}
-
-export async function transform({ extracted }: { extracted: { rowCount: number
} }) {
- return { transformedRows: extracted.rowCount };
-}
-
-await new Bundle(
- new TaskHandler("sales_pipeline", "extract", extract),
- new TaskHandler("sales_pipeline", "transform", transform),
-).serve();
-```
-
-A `TaskHandler` names the `dag_id` and `task_id` it implements, so the same
`task_id` under two Dags is two
-different handlers. Nothing connects to Airflow until `bundle.serve()`, so a
unit test can build a bundle and
-call a handler through `bundle.getTaskHandler(dagId, taskId)`.
-
-### TaskFlow arguments
-
-A Python Dag that calls a stub task TaskFlow-style passes those arguments to
the handler, which destructures
-them by name:
-
-```python
[email protected](queue="typescript")
-def transform(region_code: str, threshold: float, dry_run: bool = False): ...
-
-
-transform("uk", 0.75)
-```
-
-```ts
-export async function transform({ regionCode, threshold, dryRun }:
TransformArgs) {
- // ...
-}
-```
-
-Names match ignoring case and underscores, so `region_code` reaches
`regionCode` with nothing declared on
-either side. An argument filled from another task, as in `transform(extract(),
"uk")`, arrives as that task's
-value. A dependency drawn with `>>` orders the tasks and passes nothing; read
such a value with
-`getClient().getXCom`.
-
-`withArgNames` maps a handler's name to a different Python name, when the two
cannot match on their own:
-
-```ts
-const report = withArgNames({ label: "run_label" }, async ({ summary, label }:
ReportArgs) => {
- // `label` is the call's `run_label`.
-});
-```
-
-## Writing tasks
-
-A handler is a plain, usually `async`, function. `getContext()` and
`getClient()` give it the task's context and
-Airflow access while it runs, so it takes no SDK-supplied argument. A value it
returns becomes the task's
-`return_value` XCom, and an uncaught error fails the task.
-
-## Building and deploying
-
-`airflow-ts-pack` builds the entry module and everything it imports into one
file, `dist/bundle.min.mjs`:
+Build it into a single file:
```bash
npx airflow-ts-pack src/main.ts --outdir dist
```
-- `--outdir <dir>`: output directory (default `dist`)
-- `--outfile <path>`: exact output path, whose name must end in `.min.mjs`
-
-A bundle that declares Dags goes into a Dag bundle, such as the default
`dags-folder` bundle. Route its queue to
-the Node.js coordinator:
+Put `dist/bundle.min.mjs` in a Dag bundle, such as the default `dags-folder`
bundle, and send the `typescript`
+queue to the Node.js coordinator in `airflow.cfg`:
```ini
[sdk]
@@ -169,124 +64,31 @@ coordinators = {"ts": {"classpath":
"airflow.sdk.coordinators.node.NodeCoordinat
queue_to_coordinator = {"typescript": "ts"}
```
-A bundle that only implements stub tasks goes next to the Python Dag that
declares them, or into the Dag
-bundle the coordinator's `task_handler_bundle_name` names. See the guide for
all coordinator options.
-
-See [`example/`](https://github.com/apache/airflow/tree/main/ts-sdk/example)
for a working project that serves
-both a Dag declared in TypeScript and the tasks of two Python Dags from one
bundle.
-
-## Human-in-the-loop tasks
-
-A Dag declared in TypeScript can wait for a person, as `HITLOperator` and
`ApprovalOperator` do in
-Python. `approval(...)` and `hitl(...)` come from the
`apache-airflow-ts-sdk/hitl` subpath and are
-passed to `dag.task` in place of a handler. Each is named after its Python
operator without
-"Operator", and each option is the Python keyword argument in camelCase:
-
-```ts
-import { Bundle, Dag } from "apache-airflow-ts-sdk";
-import { approval } from "apache-airflow-ts-sdk/hitl";
-
-const dag = new Dag("release", { queue: "typescript" });
-const report = dag.task("build_report", async () => ({ version: "1.4" }))();
-
-const signOff = dag.task(
- "sign_off",
- approval({
- subject: ({ report }: { report: { version: string } }) => `Ship
${report.version}?`,
- responseTimeout: 4 * 60 * 60, // seconds
- defaults: "Reject", // the answer given when the timeout passes
- }),
-)({ report });
-signOff.before(dag.task("publish", async () => {})());
-
-await new Bundle(dag).serve();
-```
-
-The task's first run writes the request and parks the task in
`awaiting_input`; its Node process
-exits, so no worker slot is held while it waits. Once someone responds on the
Required Actions page,
-or the timeout passes, the task resumes in a new process. It does not write
the request again or run
-`subject` or `body` again.
-
-- `subject` and `body` (Markdown) are a string, or a function of the task's
inputs, run once. They
- may read different inputs, and the task takes both.
-- `responseTimeout` is in whole **seconds**. With `defaults`, the defaults are
applied when it passes;
- without, the task fails, and its retries apply. A HITL task does not enforce
`executionTimeout`, so
- `dag.task` rejects it and asks for `responseTimeout` instead.
-- `assignedUsers: [{ id, name }]` limits who can respond, by the auth
manager's user id.
-- The task returns the response, which downstream tasks receive and XCom
stores under
- `return_value`: `{ chosenOptions, paramsInput, respondedAt, respondedByUser,
timedout }`.
- These are the keys of Python's `HITLOperator` result in camelCase, plus
`timedout`; `respondedAt` is an
- ISO-8601 string, and `respondedByUser` is `null` when the timeout defaults
were applied.
-
-**On "Reject", an approval succeeds and skips the tasks directly downstream by
default**, as
-`ApprovalOperator` does. The run then shows as successful, with the guarded
tasks skipped. Set
-`failOnReject: true` to fail the approval instead, or
`ignoreDownstreamTriggerRules: true` to skip
-every task downstream. `hitl(...)` offers any `options`, with `multiple` for
several, and never
-skips or fails on what is chosen, even an option named "Reject".
-
-A failed resume retries, and the retry writes a fresh request, so `approval({
failOnReject: true })`
-with `retries` asks the reviewer again on each attempt.
-
-When the task resumes, it reads its options, its `params` and its reject
options from the Dag it
-runs from, as `HITLOperator` does. A versioned Dag bundle resumes it from the
same version it
-started on; with an unversioned bundle, a bundle deployed while the request
waits is what the
-response is read against. Clearing the task starts a new attempt, which writes
a fresh request.
-
-### Forms
-
-`params` adds form fields to the request, as `HITLOperator`'s `params` does,
for `approval(...)` and
-`hitl(...)` alike. Each field is a `value` that pre-fills it, an optional
`description`, and an
-optional JSON Schema that the form renders:
-
-```ts
-import { approval } from "apache-airflow-ts-sdk/hitl";
-
-const rollout = dag.task(
- "size_rollout",
- approval({
- subject: "Approve the rollout?",
- defaults: "Reject",
- params: {
- replicas: {
- value: 3,
- description: "How many replicas to start",
- schema: { type: "integer", minimum: 1, maximum: 10 },
- },
- },
- }),
-)();
-```
-
-The answers arrive as `paramsInput`, keyed by the param names. When the
timeout applies `defaults`,
-each param's `value` is recorded as its answer. An answer that fills in some
params but not exactly
-the declared ones fails the task, as `HITLOperator` does. The values are not
yet checked against
-their schema when the task resumes, so an answer given through the REST API
can fall outside it.
-
-A `hitl(...)` whose options are exactly "Approve" and "Reject" gets the
approval buttons in the UI
-but never skips anything; use `approval(...)` when Reject should stop the
tasks after it.
-
-Branching on the response and notifiers are not supported yet.
-
-## TaskClient
+Airflow reads the bundle like any other Dag file: `ts_hello` shows up in the
UI, runs on its schedule, and
+`report` receives the value `extract` returned.
-`getClient()` returns a `TaskClient` for task-time Airflow data access, for as
long as a handler is running:
+## Learn more
-| Method |
Description |
-| --------------------------------------------------------------- |
-------------------------------------- |
-| `getVariable(key)` / `getVariableOrThrow` | Airflow
Variables |
-| `setVariable(key, value, description?)` / `deleteVariable(key)` | Variable
write / delete |
-| `getXCom(opts)` / `setXCom(opts)` | XCom
read/write |
-| `getConnection(connId)` / `getConnectionOrThrow` | Airflow
Connections |
-| `taskStateStore.get / set / delete / clear` |
Per-task-instance key/value state store |
+The [TypeScript SDK guide][guide] covers:
-Locator fields such as `dagId`, `runId`, and `taskId` default to the
-current task context when omitted.
+| Topic
| Guide section
|
+|
---------------------------------------------------------------------------------------------
| -------------------------------------------------------------- |
+| Task inputs, options, order-only dependencies, task groups, branching,
triggering another Dag | [Declaring a Dag in
TypeScript][declaring-a-dag-in-typescript] |
+| `approval` and `hitl` tasks that wait for a person
| [Human-in-the-loop tasks][human-in-the-loop-tasks]
|
+| `getContext()`, `getClient()` and the `TaskClient`
| [Writing tasks][writing-tasks]
|
+| Building a bundle with `airflow-ts-pack`
| [Building a bundle][building-a-bundle]
|
+| Where to deploy a bundle, and the `NodeCoordinator` options
| [Deploying][deploying]
|
+| What a Dag declared in TypeScript cannot do yet
| [Limitations][limitations]
|
-## Cancellation
+See [`example/`](https://github.com/apache/airflow/tree/main/ts-sdk/example)
for a working project.
-`getContext().signal` is an `AbortSignal` controlled by the active runtime.
-Pass it to `fetch()`, timers, database clients, child processes, or any other
API that accepts an abort signal,
-so tasks can clean up cooperatively when Airflow terminates the task
subprocess.
+[guide]:
https://airflow.apache.org/docs/apache-airflow/stable/authoring-and-scheduling/language-sdks/typescript.html
+[declaring-a-dag-in-typescript]:
https://airflow.apache.org/docs/apache-airflow/stable/authoring-and-scheduling/language-sdks/typescript.html#declaring-a-dag-in-typescript
+[human-in-the-loop-tasks]:
https://airflow.apache.org/docs/apache-airflow/stable/authoring-and-scheduling/language-sdks/typescript.html#human-in-the-loop-tasks
+[writing-tasks]:
https://airflow.apache.org/docs/apache-airflow/stable/authoring-and-scheduling/language-sdks/typescript.html#writing-tasks
+[building-a-bundle]:
https://airflow.apache.org/docs/apache-airflow/stable/authoring-and-scheduling/language-sdks/typescript.html#building-a-bundle
+[deploying]:
https://airflow.apache.org/docs/apache-airflow/stable/authoring-and-scheduling/language-sdks/typescript.html#deploying
+[limitations]:
https://airflow.apache.org/docs/apache-airflow/stable/authoring-and-scheduling/language-sdks/typescript.html#limitations
## Compatibility matrix
diff --git a/ts-sdk/docs/index.md b/ts-sdk/docs/index.md
index c3ba05b440d..2f46c154cf0 100644
--- a/ts-sdk/docs/index.md
+++ b/ts-sdk/docs/index.md
@@ -19,59 +19,56 @@
# Apache Airflow TypeScript SDK
-Write Apache Airflow Dags and tasks in TypeScript. Declare a whole Dag in
TypeScript, or implement the stub
-tasks of a Python Dag with `TaskHandler`.
+Write Apache Airflow Dags in TypeScript, and run them on Node.js.
> **Note**
-> This package is **0.1.0-beta1**: the API may change, it requires **Node
22+**, and
+> This package is **1.0.0-beta1**: the API may change, it requires **Node
22+**, and
> it is **ESM-only**.
-## Getting Started
+## Quick Start
Install the beta package from npm, and `esbuild` to build the bundle:
```bash
-npm install [email protected]
+npm install [email protected]
npm install --save-dev esbuild
```
-Declare a Dag with `Dag`, and its tasks with `dag.task`. Calling a task names
its inputs, which is how the
-tasks depend on each other:
+Declare a Dag in `src/main.ts`. Calling a task names its inputs, which is how
the tasks depend on each other:
```ts
import { Bundle, Dag } from "apache-airflow-ts-sdk";
-const dag = new Dag("ts_etl", { schedule: "@daily", queue: "typescript" });
+const dag = new Dag("ts_hello", { schedule: "@daily", queue: "typescript" });
const extract = dag.task("extract", async (): Promise<number> => 42);
-const load = dag.task("load", async ({ rows }: { rows: number }) => {
- console.log(`loading ${rows} rows`);
+const report = dag.task("report", async ({ rows }: { rows: number }) => {
+ console.log(`extracted ${rows} rows`);
});
-load({ rows: extract() });
+report({ rows: extract() });
await new Bundle(dag).serve();
```
-To implement a stub task of a Python Dag, bind a function to its `dag_id` and
`task_id` with `TaskHandler`:
+Build it into a single file:
-```ts
-import { Bundle, getClient, getContext, TaskHandler } from
"apache-airflow-ts-sdk";
+```bash
+npx airflow-ts-pack src/main.ts --outdir dist
+```
-export async function sayHello() {
- const greeting = await getClient().getVariable("greeting");
- return { message: `Hello from ${getContext().taskId}: ${greeting}` };
-}
+Put `dist/bundle.min.mjs` in a Dag bundle, such as the default `dags-folder`
bundle, and send the `typescript`
+queue to the Node.js coordinator in `airflow.cfg`:
-await new Bundle(new TaskHandler("example_dag", "say_hello",
sayHello)).serve();
+```ini
+[sdk]
+coordinators = {"ts": {"classpath":
"airflow.sdk.coordinators.node.NodeCoordinator"}}
+queue_to_coordinator = {"typescript": "ts"}
```
-In both cases a handler is a plain function. `getContext()` returns the
`TaskContext` and `getClient()` the
-`TaskClient` while it runs, and a value it returns becomes the task's
`return_value` XCom.
-
See the
[TypeScript SDK
guide](https://airflow.apache.org/docs/apache-airflow/stable/authoring-and-scheduling/language-sdks/typescript.html)
-for building and deploying a bundle, and for everything a Dag declared in
TypeScript can do.
+for everything a Dag declared in TypeScript can do, and for building and
deploying a bundle.
## API Reference
diff --git a/ts-sdk/docs/package-lock.json b/ts-sdk/docs/package-lock.json
index d3b69dabace..233fedc7a06 100644
--- a/ts-sdk/docs/package-lock.json
+++ b/ts-sdk/docs/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "ts-sdk-docs-toolchain",
- "version": "0.1.0-beta1",
+ "version": "1.0.0-beta1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ts-sdk-docs-toolchain",
- "version": "0.1.0-beta1",
+ "version": "1.0.0-beta1",
"devDependencies": {
"@clean-jsdoc-theme/typedoc": "^5.1.1",
"@msgpack/msgpack": "^3.1.2",
diff --git a/ts-sdk/docs/package.json b/ts-sdk/docs/package.json
index a0579cca8c0..ba4e94590e4 100644
--- a/ts-sdk/docs/package.json
+++ b/ts-sdk/docs/package.json
@@ -1,6 +1,6 @@
{
"name": "ts-sdk-docs-toolchain",
- "version": "0.1.0-beta1",
+ "version": "1.0.0-beta1",
"private": true,
"description": "Documentation toolchain for the Apache Airflow TypeScript
SDK (ts-sdk). TypeDoc renders the API reference straight to HTML with the
clean-jsdoc-theme; it is kept in its own package so the docs-only dependencies
stay out of the SDK's runtime and build toolchain.",
"type": "module",
diff --git a/ts-sdk/docs/tests/deployment-checks.test.mjs
b/ts-sdk/docs/tests/deployment-checks.test.mjs
index df4bc9acd9d..e2f08a53d76 100644
--- a/ts-sdk/docs/tests/deployment-checks.test.mjs
+++ b/ts-sdk/docs/tests/deployment-checks.test.mjs
@@ -35,8 +35,8 @@ const FONT_TAGS =
'<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />' +
'<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Source+Serif+4:wght@400;500;600;700&family=Roboto:wght@400;500;600;700&display=swap"
/>';
-const BASE = "/docs/ts-sdk/0.1.0-beta1";
-const VERSION = "0.1.0-beta1";
+const BASE = "/docs/ts-sdk/1.0.0-beta1";
+const VERSION = "1.0.0-beta1";
describe("normalizeBasePath", () => {
it("strips trailing slashes", () => {
diff --git a/ts-sdk/example/README.md b/ts-sdk/example/README.md
index 3e189a7eb21..7e5a40f0105 100644
--- a/ts-sdk/example/README.md
+++ b/ts-sdk/example/README.md
@@ -17,50 +17,31 @@
under the License.
-->
-# TypeScript Coordinator Runtime Example
+# TypeScript SDK examples
-This example shows both ways to use the TypeScript SDK, served from one bundle:
+Example Dags for the Apache Airflow TypeScript SDK. See the [TypeScript SDK
guide][guide] for the full documentation.
-- `src/native.ts` declares the `typescript_native_example` Dag entirely in
TypeScript: its schedule, task
- options and dependencies, including a task group, a condition, a multi-way
branch and a triggered Dag run.
-- `dags/typescript_example.py` and `dags/typescript_taskflow_example.py`
declare two Python Dags with stub
- tasks, and `src/main.ts` and `src/taskflow.ts` implement them with a
`TaskHandler` per stub task.
- Both Dags have a task called `build_message`; a handler names its `(dag_id,
task_id)` pair, so the two are
- different tasks with different bodies.
-- `dist/bundle.min.mjs` is the built bundle Airflow runs.
-
-The build uses the SDK's `airflow-ts-pack` tool, which builds the entry module
and everything it imports into
-that single file.
+[guide]:
https://airflow.apache.org/docs/apache-airflow/stable/authoring-and-scheduling/language-sdks/typescript.html
## Build
-Build the SDK first so the example can import the local package:
+Build the SDK, then the example bundles into `dist/`:
```bash
cd ts-sdk
pnpm install
pnpm run build
-```
-
-Build the example bundle and its metadata:
-```bash
-cd ts-sdk/example
+cd example
pnpm install
-pnpm run build
+pnpm run build # dist/bundle.min.mjs
+pnpm run build:hitl # dist/hitl.min.mjs
+pnpm run build:ai-approval # dist/ai-approval.min.mjs
```
-The coordinator expects this layout:
+## Configure Airflow
-```text
-ts-sdk/example/dist/
- bundle.min.mjs
-```
-
-## Airflow Configuration
-
-Register the example bundle directory as a Dag bundle, and configure Airflow to
-route the `typescript` queue to the Node coordinator that reads it:
+Register `dist/` as a Dag bundle, and route the `typescript` queue to the
Node.js coordinator:
```bash
export AIRFLOW__DAG_PROCESSOR__DAG_BUNDLE_CONFIG_LIST='[
@@ -80,13 +61,38 @@ export AIRFLOW__SDK__COORDINATORS='{
export AIRFLOW__SDK__QUEUE_TO_COORDINATOR='{"typescript": "ts"}'
```
-The Dag processor parses `typescript_native_example` from `bundle.min.mjs`
with `node`, so it needs
-`node` on `PATH`, or set `node_executable` in the coordinator's `kwargs`. See
-[Parsing native
Dags](../../airflow-core/docs/authoring-and-scheduling/language-sdks/typescript.rst#parsing-native-dags).
+The workers and the Dag processor need Node.js 22 or later on `PATH`.
+
+## Dag definition
+
+These Dags are declared entirely in TypeScript:
+
+| Dag | Source | Shows
|
+| --------------------------- | -------------------- |
--------------------------------------------------------------------------------------
|
+| `typescript_native_example` | `src/native.ts` | Task inputs, a task
group, `dag.if`, `dag.switch`, and `triggerDagRun` |
+| `ts_hitl_*` | `src/hitl.ts` | One Dag per
human-in-the-loop feature, such as approvals, options, timeouts, and forms |
+| `ts_hitl_ai_approval` | `src/ai-approval.ts` | An agent that asks a
person to review its tool call before it acts |
+
+`typescript_native_example` triggers `typescript_example`, so set up the
[Python Dags](#python-dag-with-stub-taskhandler) too.
+Set the `typescript_native_cadence` Variable to `weekly` to take the other
branch of `dag.switch`.
+
+```bash
+airflow dags trigger typescript_native_example
+```
+
+Answer the human-in-the-loop requests on the **Required Actions** page.
-Copy both files in `dags/` into your Airflow Dags folder.
+## Python Dag with stub TaskHandler
-The example also reads one Variable and one Connection:
+These Dags are declared in Python, and their stub tasks are implemented in
TypeScript:
+
+| Dag | Source
| Shows |
+| ----------------------------- |
-------------------------------------------------------- |
------------------------------------------------------------ |
+| `typescript_example` | `dags/typescript_example.py`, `src/main.ts`
| Variables, Connections, XComs, and the task state store |
+| `typescript_taskflow_example` | `dags/typescript_taskflow_example.py`,
`src/taskflow.ts` | TaskFlow arguments passed from Python to TypeScript
handlers |
+
+Copy both files in `dags/` into your Airflow Dags folder, and create the
Variable and Connection
+`typescript_example` reads:
```bash
airflow variables set typescript_example_greeting "hello from Airflow"
@@ -97,13 +103,7 @@ airflow connections add typescript_example_http \
--conn-password pass
```
-`write_and_delete_variable` writes the Variables it needs: it records the run
id in
-`typescript_example_last_run` and deletes the `typescript_example_scratch`
Variable it has just written.
-
-Then start Airflow and trigger the Dag:
-
```bash
airflow dags trigger typescript_example
airflow dags trigger typescript_taskflow_example
-airflow dags trigger typescript_native_example
```
diff --git a/ts-sdk/example/package.json b/ts-sdk/example/package.json
index 3a865750a6c..d5e10ab3f57 100644
--- a/ts-sdk/example/package.json
+++ b/ts-sdk/example/package.json
@@ -1,7 +1,7 @@
{
"name": "apache-airflow-ts-sdk-example",
"private": true,
- "version": "0.1.0-beta1",
+ "version": "1.0.0-beta1",
"type": "module",
"license": "Apache-2.0",
"scripts": {
diff --git a/ts-sdk/package.json b/ts-sdk/package.json
index 61aaafd6264..f6c1dbba742 100644
--- a/ts-sdk/package.json
+++ b/ts-sdk/package.json
@@ -1,6 +1,6 @@
{
"name": "apache-airflow-ts-sdk",
- "version": "0.1.0-beta1",
+ "version": "1.0.0-beta1",
"packageManager": "[email protected]",
"description": "TypeScript Task SDK for Apache Airflow task handlers",
"license": "Apache-2.0",