This is an automated email from the ASF dual-hosted git repository.
guan404ming pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new fe9ca480fb8 Add ConnectionNotFoundError to the TypeScript SDK (#71375)
fe9ca480fb8 is described below
commit fe9ca480fb8aad5f836624302677f39281a83a55
Author: Jason(Zhe-You) Liu <[email protected]>
AuthorDate: Tue Aug 11 21:58:05 2026 +0800
Add ConnectionNotFoundError to the TypeScript SDK (#71375)
Variable lookups already offer both shapes: getVariable returns null and
getVariableOrThrow raises a typed error. Connections only had the
null-returning half, so a task that genuinely requires a Connection had to
hand-write the absence check and had no error type to catch. The Go SDK
already exposes a ConnectionNotFound sentinel next to VariableNotFound.
---
.../language-sdks/typescript.rst | 4 +++-
ts-sdk/README.md | 10 ++++-----
ts-sdk/src/coordinator/client.ts | 8 ++++++-
ts-sdk/src/index.ts | 2 +-
ts-sdk/src/sdk/client.ts | 20 +++++++++++++++++
ts-sdk/tests/coordinator/client.test.ts | 26 ++++++++++++++++++++++
ts-sdk/tests/public-api.test.ts | 9 ++++++++
7 files changed, 71 insertions(+), 8 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 87e12cea474..51fc45c87eb 100644
--- a/airflow-core/docs/authoring-and-scheduling/language-sdks/typescript.rst
+++ b/airflow-core/docs/authoring-and-scheduling/language-sdks/typescript.rst
@@ -174,7 +174,9 @@ The ``TaskClient`` surface
with no default.
* ``getConnection(connId)`` — returns a ``ConnectionResult`` with fields
``id`` and ``type``, plus the
optional fields ``host``, ``schema``, ``login``, ``password``, ``port``, and
``extra`` (each may be
- missing or ``null``), or ``null`` when the connection does not exist.
+ missing or ``null``), or ``null`` when the connection does not exist;
+ ``getConnectionOrThrow(connId)`` throws ``ConnectionNotFoundError`` instead,
matching Python
+ ``BaseHook.get_connection``.
* ``getXCom<T>({key, ...})`` — reads an XCom value, or ``null`` when it is
missing. The locator fields
(``dagId``, ``runId``, ``taskId``, ``mapIndex``) default to the current
task; pass ``taskId`` to read an
upstream task's XCom. See :ref:`typescript-sdk/types` for how the stored
JSON maps to JavaScript types.
diff --git a/ts-sdk/README.md b/ts-sdk/README.md
index dfc84b10f91..9eb8205aaf7 100644
--- a/ts-sdk/README.md
+++ b/ts-sdk/README.md
@@ -177,11 +177,11 @@ Options:
Every task handler receives a `TaskClient` for task-time Airflow data access:
-| Method | Description |
-| ----------------------------------------- | ------------------- |
-| `getVariable(key)` / `getVariableOrThrow` | Airflow Variables |
-| `getXCom(opts)` / `setXCom(opts)` | XCom read/write |
-| `getConnection(connId)` | Airflow Connections |
+| Method | Description |
+| ------------------------------------------------ | ------------------- |
+| `getVariable(key)` / `getVariableOrThrow` | Airflow Variables |
+| `getXCom(opts)` / `setXCom(opts)` | XCom read/write |
+| `getConnection(connId)` / `getConnectionOrThrow` | Airflow Connections |
Locator fields such as `dagId`, `runId`, and `taskId` default to the
current task context when omitted.
diff --git a/ts-sdk/src/coordinator/client.ts b/ts-sdk/src/coordinator/client.ts
index 2d719761416..84942e349c3 100644
--- a/ts-sdk/src/coordinator/client.ts
+++ b/ts-sdk/src/coordinator/client.ts
@@ -22,7 +22,7 @@ import type { LogChannel } from "./log-channel.js";
import type { TaskContext } from "../sdk/task.js";
import type { TaskClient } from "../sdk/client.js";
import type { ConnectionResult, GetXComOpts, SetXComOpts } from
"../sdk/client-types.js";
-import { VariableNotFoundError } from "../sdk/client.js";
+import { ConnectionNotFoundError, VariableNotFoundError } from
"../sdk/client.js";
import type {
GetVariable,
GetXCom,
@@ -138,6 +138,12 @@ export function createCoordinatorClient(
fromWireConnection(body as unknown as WireConnectionResult),
);
},
+
+ async getConnectionOrThrow(connId: string): Promise<ConnectionResult> {
+ const connection = await client.getConnection(connId);
+ if (connection == null) throw new ConnectionNotFoundError(connId);
+ return connection;
+ },
};
return client;
}
diff --git a/ts-sdk/src/index.ts b/ts-sdk/src/index.ts
index 7abeb1af02c..0a63cb263f4 100644
--- a/ts-sdk/src/index.ts
+++ b/ts-sdk/src/index.ts
@@ -18,7 +18,7 @@
*/
export { registerTask, listRegisteredTasks } from "./sdk/registry.js";
-export { VariableNotFoundError } from "./sdk/client.js";
+export { ConnectionNotFoundError, VariableNotFoundError } from
"./sdk/client.js";
export { startCoordinator, SUPERVISOR_API_VERSION } from
"./coordinator/index.js";
export type { TaskClient } from "./sdk/client.js";
export type { ConnectionResult, GetXComOpts, JsonValue, SetXComOpts } from
"./sdk/client-types.js";
diff --git a/ts-sdk/src/sdk/client.ts b/ts-sdk/src/sdk/client.ts
index 461dc01f637..a0a9a48bd8d 100644
--- a/ts-sdk/src/sdk/client.ts
+++ b/ts-sdk/src/sdk/client.ts
@@ -75,8 +75,20 @@ export interface TaskClient {
*
* Returns `null` when the connection does not exist. Throws on any other
* error.
+ *
+ * This is intentionally JS-friendly behavior. Use
+ * {@link getConnectionOrThrow} when missing connections should raise.
*/
getConnection(connId: string): Promise<ConnectionResult | null>;
+
+ /**
+ * Look up an Airflow Connection by ID and raise when it is missing.
+ *
+ * This matches Python `BaseHook.get_connection` behavior.
+ *
+ * @throws {@link ConnectionNotFoundError} when the connection does not
exist.
+ */
+ getConnectionOrThrow(connId: string): Promise<ConnectionResult>;
}
/** Error thrown by {@link TaskClient.getVariableOrThrow}. */
@@ -86,3 +98,11 @@ export class VariableNotFoundError extends Error {
this.name = "VariableNotFoundError";
}
}
+
+/** Error thrown by {@link TaskClient.getConnectionOrThrow}. */
+export class ConnectionNotFoundError extends Error {
+ constructor(public readonly connId: string) {
+ super(`Connection not found: ${connId}`);
+ this.name = "ConnectionNotFoundError";
+ }
+}
diff --git a/ts-sdk/tests/coordinator/client.test.ts
b/ts-sdk/tests/coordinator/client.test.ts
index b2583e80e0a..7b92e8c3b5a 100644
--- a/ts-sdk/tests/coordinator/client.test.ts
+++ b/ts-sdk/tests/coordinator/client.test.ts
@@ -18,6 +18,7 @@
*/
import { describe, it, expect } from "vitest";
+import { ConnectionNotFoundError } from "../../src/sdk/client.js";
import { createCoordinatorClient } from "../../src/coordinator/client.js";
import type { CommChannel } from "../../src/coordinator/comm-channel.js";
import type { TaskContext } from "../../src/sdk/task.js";
@@ -242,3 +243,28 @@ describe("getConnection", () => {
expect(await c.getConnection("missing")).toBeNull();
});
});
+
+describe("getConnectionOrThrow", () => {
+ it("returns the connection when present", async () => {
+ const c = client([
+ { body: { type: "ConnectionResult", conn_id: "warehouse", conn_type:
"postgres" } },
+ ]);
+
+ await expect(c.getConnectionOrThrow("warehouse")).resolves.toMatchObject({
+ id: "warehouse",
+ type: "postgres",
+ });
+ });
+
+ it("throws ConnectionNotFoundError on a missing connection", async () => {
+ const c = client([{ body: { type: "ErrorResponse", error:
"CONNECTION_NOT_FOUND" } }]);
+ const result = c.getConnectionOrThrow("missing");
+ await expect(result).rejects.toThrow(ConnectionNotFoundError);
+ await expect(result).rejects.toThrow(/Connection not found: missing/);
+ });
+
+ it("propagates non-not-found errors instead of ConnectionNotFoundError",
async () => {
+ const c = client([{ body: { type: "ErrorResponse", error:
"API_SERVER_ERROR" } }]);
+ await
expect(c.getConnectionOrThrow("warehouse")).rejects.toThrow(/API_SERVER_ERROR/);
+ });
+});
diff --git a/ts-sdk/tests/public-api.test.ts b/ts-sdk/tests/public-api.test.ts
index 751b7495397..4e7a49ddb4d 100644
--- a/ts-sdk/tests/public-api.test.ts
+++ b/ts-sdk/tests/public-api.test.ts
@@ -28,6 +28,7 @@ import type {
TaskRegistration,
} from "../src/index.js";
import {
+ ConnectionNotFoundError,
listRegisteredTasks,
registerTask,
startCoordinator,
@@ -47,6 +48,11 @@ describe("public API", () => {
expect(err).toBeInstanceOf(Error);
expect(err.name).toBe("VariableNotFoundError");
expect(err.key).toBe("missing");
+
+ const connErr = new ConnectionNotFoundError("missing_conn");
+ expect(connErr).toBeInstanceOf(Error);
+ expect(connErr.name).toBe("ConnectionNotFoundError");
+ expect(connErr.connId).toBe("missing_conn");
});
it("exports the coordinator runtime entrypoint", () => {
@@ -103,6 +109,9 @@ describe("public API", () => {
expectTypeOf<TaskClient["getConnection"]>().toEqualTypeOf<
(connId: string) => Promise<ConnectionResult | null>
>();
+ expectTypeOf<TaskClient["getConnectionOrThrow"]>().toEqualTypeOf<
+ (connId: string) => Promise<ConnectionResult>
+ >();
expectTypeOf<TaskClient["getXCom"]>().toEqualTypeOf<
<T = unknown>(opts: GetXComOpts) => Promise<T | null>
>();