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>
     >();

Reply via email to