Skip to content

Commit a17ec82

Browse files
committed
feat(lakebase): add ability to lookup DB username with API
1 parent 64930bc commit a17ec82

12 files changed

Lines changed: 114 additions & 17 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ const result = await pool.query('SELECT * FROM users');
257257
```
258258

259259
**ORM Integration:**
260-
Works with Drizzle, Prisma, TypeORM - see [Lakebase Integration Docs](docs/docs/integrations/lakebase.md) for examples.
260+
Works with Drizzle, Prisma, TypeORM - see the [`@databricks/lakebase` README](https://github.com/databricks/appkit/blob/main/packages/lakebase/README.md) for examples.
261261

262262
**Architecture:**
263263
- Connector files: `packages/appkit/src/connectors/lakebase/`
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Function: getUsernameWithApiLookup()
2+
3+
```ts
4+
function getUsernameWithApiLookup(config?: Partial<LakebasePoolConfig>): Promise<string | undefined>;
5+
```
6+
7+
Resolves the PostgreSQL username for a Lakebase connection.
8+
9+
Extends getUsernameSync with an async fallback that fetches the
10+
current user's identity from the Databricks workspace API. Use this when
11+
you don't have an explicit username configured and want automatic resolution
12+
(e.g. human users authenticating via PAT or browser OAuth in ~/.databrickscfg).
13+
14+
Resolution priority:
15+
1. `config.user` — explicit config value
16+
2. `PGUSER` env var
17+
3. `DATABRICKS_CLIENT_ID` env var (service principals)
18+
4. `currentUser.me()` — fetched from Databricks workspace API
19+
20+
Returns `undefined` if all attempts fail rather than throwing, so the
21+
caller can decide whether to proceed or surface an error.
22+
23+
## Parameters
24+
25+
| Parameter | Type |
26+
| ------ | ------ |
27+
| `config?` | `Partial`\<[`LakebasePoolConfig`](Interface.LakebasePoolConfig.md)\> |
28+
29+
## Returns
30+
31+
`Promise`\<`string` \| `undefined`\>

docs/docs/api/appkit/Function.getWorkspaceClient.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Function: getWorkspaceClient()
22

33
```ts
4-
function getWorkspaceClient(config: Partial<LakebasePoolConfig>): Promise<WorkspaceClient>;
4+
function getWorkspaceClient(config: Partial<LakebasePoolConfig>): WorkspaceClient;
55
```
66

77
Get workspace client from config or SDK default auth chain
@@ -14,4 +14,4 @@ Get workspace client from config or SDK default auth chain
1414

1515
## Returns
1616

17-
`Promise`\<`WorkspaceClient`\>
17+
`WorkspaceClient`

docs/docs/api/appkit/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,5 +73,6 @@ plugin architecture, and React integration.
7373
| [getLakebasePgConfig](Function.getLakebasePgConfig.md) | Get Lakebase connection configuration for PostgreSQL clients. |
7474
| [getPluginManifest](Function.getPluginManifest.md) | Loads and validates the manifest from a plugin constructor. Normalizes string type/permission to strict ResourceType/ResourcePermission. |
7575
| [getResourceRequirements](Function.getResourceRequirements.md) | Gets the resource requirements from a plugin's manifest. |
76+
| [getUsernameWithApiLookup](Function.getUsernameWithApiLookup.md) | Resolves the PostgreSQL username for a Lakebase connection. |
7677
| [getWorkspaceClient](Function.getWorkspaceClient.md) | Get workspace client from config or SDK default auth chain |
7778
| [isSQLTypeMarker](Function.isSQLTypeMarker.md) | Type guard to check if a value is a SQL type marker |

docs/docs/api/appkit/typedoc-sidebar.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,11 @@ const typedocSidebar: SidebarsConfig = {
240240
id: "api/appkit/Function.getResourceRequirements",
241241
label: "getResourceRequirements"
242242
},
243+
{
244+
type: "doc",
245+
id: "api/appkit/Function.getUsernameWithApiLookup",
246+
label: "getUsernameWithApiLookup"
247+
},
243248
{
244249
type: "doc",
245250
id: "api/appkit/Function.getWorkspaceClient",

packages/appkit/src/connectors/lakebase/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export {
3232
generateDatabaseCredential,
3333
getLakebaseOrmConfig,
3434
getLakebasePgConfig,
35+
getUsernameWithApiLookup,
3536
getWorkspaceClient,
3637
type LakebasePoolConfig,
3738
type Logger,

packages/appkit/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export {
2727
generateDatabaseCredential,
2828
getLakebaseOrmConfig,
2929
getLakebasePgConfig,
30+
getUsernameWithApiLookup,
3031
getWorkspaceClient,
3132
RequestedClaimsPermissionSet,
3233
} from "./connectors/lakebase";

packages/lakebase/README.md

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,7 @@ To find your `LAKEBASE_ENDPOINT`, run the Databricks CLI and use the `name` fiel
4242
databricks postgres list-endpoints projects/{project-id}/branches/{branch-id}
4343
```
4444

45-
You can obtain the Project ID and Branch ID from the Lakebase Autoscaling UI, like the "Branch Overview" page. (Project list -> Project dashboard -> Branch overview). When using the driver as a part of Databricks Apps, the `LAKEBASE_ENDPOINT` is automatically injected using `fromValue`:
46-
47-
```yaml
48-
env:
49-
- name: LAKEBASE_ENDPOINT
50-
valueFrom: database # Lakebase Autoscaling database resource name
51-
```
45+
You can obtain the Project ID and Branch ID from the Lakebase Autoscaling UI, like the "Branch Overview" page. (Project list -> Project dashboard -> Branch overview).
5246

5347
Then use the driver:
5448

@@ -85,14 +79,43 @@ The driver supports Databricks authentication via:
8579

8680
See [Databricks authentication docs](https://docs.databricks.com/en/dev-tools/auth/index.html) or [Lakebase Autoscaling authentication docs](https://docs.databricks.com/aws/en/oltp/projects/authentication#overview) for more information.
8781

82+
## PostgreSQL Username Resolution
83+
84+
The driver resolves the PostgreSQL username (`user` configuration option) using the following priority order:
85+
86+
1. `config.user` — explicit value passed to `createLakebasePool`
87+
2. `PGUSER` environment variable
88+
3. `DATABRICKS_CLIENT_ID` environment variable (service principals using OAuth M2M)
89+
90+
If none of these are set, the driver throws a `ConfigurationError`.
91+
92+
### Automatic resolution via Workspace API
93+
94+
For human users authenticating with a PAT token or browser OAuth via `~/.databrickscfg`, none of the above are typically set. Use `getUsernameWithApiLookup` to automatically fetch the username from the Databricks workspace before creating the pool:
95+
96+
```typescript
97+
import { createLakebasePool, getUsernameWithApiLookup } from "@databricks/lakebase";
98+
99+
// Tries config/env vars first, then falls back to currentUser.me() API call
100+
const user = await getUsernameWithApiLookup();
101+
102+
const pool = createLakebasePool({ user });
103+
```
104+
105+
`getUsernameWithApiLookup` extends the sync resolution above with a fourth step:
106+
107+
4. `currentUser.me()` — fetches the current user's identity from the Databricks workspace API (works with PAT tokens and browser OAuth in `~/.databrickscfg`)
108+
109+
> **Note:** `getUsernameWithApiLookup` makes a network call to the Databricks workspace API when the sync resolution steps (config, `PGUSER`, `DATABRICKS_CLIENT_ID`) all fail. Call it once during initialization, not on every request.
110+
88111
## Configuration
89112

90113
| Option | Environment Variable | Description | Default |
91114
| ------------------------- | ---------------------------------- | --------------------------------------- | ----------------------- |
92115
| `host` | `PGHOST` | Lakebase host | _Required_ |
93116
| `database` | `PGDATABASE` | Database name | _Required_ |
94117
| `endpoint` | `LAKEBASE_ENDPOINT` | Endpoint resource path | _Required_ |
95-
| `user` | `PGUSER` or `DATABRICKS_CLIENT_ID` | Username or service principal ID | Auto-detected |
118+
| `user` | `PGUSER` or `DATABRICKS_CLIENT_ID` | Username or service principal ID | See [Username Resolution](#username-resolution)|
96119
| `port` | `PGPORT` | Port number | `5432` |
97120
| `sslMode` | `PGSSLMODE` | SSL mode | `require` |
98121
| `max` | - | Max pool connections | `10` |

packages/lakebase/src/__tests__/pool.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ describe("createLakebasePool", () => {
209209
createLakebasePool({
210210
workspaceClient: {} as any,
211211
}),
212-
).toThrow("PGUSER, DATABRICKS_CLIENT_ID, or config.user");
212+
).toThrow("config.user, PGUSER or DATABRICKS_CLIENT_ID");
213213
});
214214

215215
test("should use DATABRICKS_CLIENT_ID as fallback for user", () => {

packages/lakebase/src/config.ts

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,9 @@ function validateSslMode(value: string | undefined): SslMode | undefined {
106106
}
107107

108108
/** Get workspace client from config or SDK default auth chain */
109-
export async function getWorkspaceClient(
109+
export function getWorkspaceClient(
110110
config: Partial<LakebasePoolConfig>,
111-
): Promise<WorkspaceClient> {
111+
): WorkspaceClient {
112112
// Priority 1: Explicit workspaceClient in config
113113
if (config.workspaceClient) {
114114
return config.workspaceClient;
@@ -140,6 +140,41 @@ export function getUsernameSync(config: Partial<LakebasePoolConfig>): string {
140140
}
141141

142142
throw ConfigurationError.missingEnvVar(
143-
"PGUSER, DATABRICKS_CLIENT_ID, or config.user",
143+
"config.user, PGUSER or DATABRICKS_CLIENT_ID",
144144
);
145145
}
146+
147+
/**
148+
* Resolves the PostgreSQL username for a Lakebase connection.
149+
*
150+
* Extends {@link getUsernameSync} with an async fallback that fetches the
151+
* current user's identity from the Databricks workspace API. Use this when
152+
* you don't have an explicit username configured and want automatic resolution
153+
* (e.g. human users authenticating via PAT or browser OAuth in ~/.databrickscfg).
154+
*
155+
* Resolution priority:
156+
* 1. `config.user` — explicit config value
157+
* 2. `PGUSER` env var
158+
* 3. `DATABRICKS_CLIENT_ID` env var (service principals)
159+
* 4. `currentUser.me()` — fetched from Databricks workspace API
160+
*
161+
* Returns `undefined` if all attempts fail rather than throwing, so the
162+
* caller can decide whether to proceed or surface an error.
163+
*/
164+
export async function getUsernameWithApiLookup(
165+
config?: Partial<LakebasePoolConfig>,
166+
): Promise<string | undefined> {
167+
try {
168+
return getUsernameSync(config ?? {});
169+
} catch {
170+
// sync resolution failed, try workspace API
171+
}
172+
173+
try {
174+
const workspaceClient = getWorkspaceClient(config ?? {});
175+
const me = await workspaceClient.currentUser.me();
176+
return me.userName ?? undefined;
177+
} catch {
178+
return undefined;
179+
}
180+
}

0 commit comments

Comments
 (0)