Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ coverage
*.tsbuildinfo

.turbo

# AppKit type generator caches
.databricks
24 changes: 24 additions & 0 deletions docs/docs/api/appkit/Function.appKitServingTypesPlugin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Function: appKitServingTypesPlugin()

```ts
function appKitServingTypesPlugin(options?: AppKitServingTypesPluginOptions): Plugin$1;
```

Vite plugin to generate TypeScript types for AppKit serving endpoints.
Fetches OpenAPI schemas from Databricks and generates a .d.ts with
ServingEndpointRegistry module augmentation.

Endpoint discovery order:
1. Explicit `endpoints` option (override)
2. AST extraction from server file (server/index.ts or server/server.ts)
3. DATABRICKS_SERVING_ENDPOINT env var (single default endpoint)

## Parameters

| Parameter | Type |
| ------ | ------ |
| `options?` | `AppKitServingTypesPluginOptions` |

## Returns

`Plugin$1`
24 changes: 24 additions & 0 deletions docs/docs/api/appkit/Function.extractServingEndpoints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Function: extractServingEndpoints()

```ts
function extractServingEndpoints(serverFilePath: string):
| Record<string, EndpointConfig>
| null;
```

Extract serving endpoint config from a server file by AST-parsing it.
Looks for `serving({ endpoints: { alias: { env: "..." }, ... } })` calls
and extracts the endpoint alias names and their environment variable mappings.

## Parameters

| Parameter | Type | Description |
| ------ | ------ | ------ |
| `serverFilePath` | `string` | Absolute path to the server entry file |

## Returns

\| `Record`\<`string`, [`EndpointConfig`](Interface.EndpointConfig.md)\>
\| `null`

Extracted endpoint config, or null if not found or not extractable
19 changes: 19 additions & 0 deletions docs/docs/api/appkit/Function.findServerFile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Function: findServerFile()

```ts
function findServerFile(basePath: string): string | null;
```

Find the server entry file by checking candidate paths in order.

## Parameters

| Parameter | Type | Description |
| ------ | ------ | ------ |
| `basePath` | `string` | Project root directory to search from |

## Returns

`string` \| `null`

Absolute path to the server file, or null if none found
3 changes: 3 additions & 0 deletions docs/docs/api/appkit/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,12 @@ plugin architecture, and React integration.

| Function | Description |
| ------ | ------ |
| [appKitServingTypesPlugin](Function.appKitServingTypesPlugin.md) | Vite plugin to generate TypeScript types for AppKit serving endpoints. Fetches OpenAPI schemas from Databricks and generates a .d.ts with ServingEndpointRegistry module augmentation. |
| [appKitTypesPlugin](Function.appKitTypesPlugin.md) | Vite plugin to generate types for AppKit queries. Calls generateFromEntryPoint under the hood. |
| [createApp](Function.createApp.md) | Bootstraps AppKit with the provided configuration. |
| [createLakebasePool](Function.createLakebasePool.md) | Create a Lakebase pool with appkit's logger integration. Telemetry automatically uses appkit's OpenTelemetry configuration via global registry. |
| [extractServingEndpoints](Function.extractServingEndpoints.md) | Extract serving endpoint config from a server file by AST-parsing it. Looks for `serving({ endpoints: { alias: { env: "..." }, ... } })` calls and extracts the endpoint alias names and their environment variable mappings. |
| [findServerFile](Function.findServerFile.md) | Find the server entry file by checking candidate paths in order. |
| [generateDatabaseCredential](Function.generateDatabaseCredential.md) | Generate OAuth credentials for Postgres database connection using the proper Postgres API. |
| [getExecutionContext](Function.getExecutionContext.md) | Get the current execution context. |
| [getLakebaseOrmConfig](Function.getLakebaseOrmConfig.md) | Get Lakebase connection configuration for ORMs that don't accept pg.Pool directly. |
Expand Down
15 changes: 15 additions & 0 deletions docs/docs/api/appkit/typedoc-sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,11 @@ const typedocSidebar: SidebarsConfig = {
type: "category",
label: "Functions",
items: [
{
type: "doc",
id: "api/appkit/Function.appKitServingTypesPlugin",
label: "appKitServingTypesPlugin"
},
{
type: "doc",
id: "api/appkit/Function.appKitTypesPlugin",
Expand All @@ -240,6 +245,16 @@ const typedocSidebar: SidebarsConfig = {
id: "api/appkit/Function.createLakebasePool",
label: "createLakebasePool"
},
{
type: "doc",
id: "api/appkit/Function.extractServingEndpoints",
label: "extractServingEndpoints"
},
{
type: "doc",
id: "api/appkit/Function.findServerFile",
label: "findServerFile"
},
{
type: "doc",
id: "api/appkit/Function.generateDatabaseCredential",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { act, renderHook, waitFor } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { useServingInvoke } from "../use-serving-invoke";

describe("useServingInvoke", () => {
beforeEach(() => {
vi.spyOn(globalThis, "fetch").mockResolvedValue(
new Response(JSON.stringify({ choices: [] }), { status: 200 }),
);
});

afterEach(() => {
vi.restoreAllMocks();
});

test("initial state is idle", () => {
const { result } = renderHook(() => useServingInvoke({ messages: [] }));

expect(result.current.data).toBeNull();
expect(result.current.loading).toBe(false);
expect(result.current.error).toBeNull();
expect(typeof result.current.invoke).toBe("function");
});

test("calls fetch to correct URL on invoke", async () => {
const fetchSpy = vi.spyOn(globalThis, "fetch");

const { result } = renderHook(() =>
useServingInvoke({ messages: [{ role: "user", content: "Hello" }] }),
);

act(() => {
result.current.invoke();
});

await waitFor(() => {
expect(fetchSpy).toHaveBeenCalledWith(
"/api/serving/invoke",
expect.objectContaining({
method: "POST",
body: JSON.stringify({
messages: [{ role: "user", content: "Hello" }],
}),
}),
);
});
});

test("uses alias in URL when provided", async () => {
const fetchSpy = vi.spyOn(globalThis, "fetch");

const { result } = renderHook(() =>
useServingInvoke({ messages: [] }, { alias: "llm" }),
);

act(() => {
result.current.invoke();
});

await waitFor(() => {
expect(fetchSpy).toHaveBeenCalledWith(
"/api/serving/llm/invoke",
expect.any(Object),
);
});
});

test("sets data on successful response", async () => {
const responseData = {
choices: [{ message: { content: "Hi" } }],
};

vi.spyOn(globalThis, "fetch").mockResolvedValue(
new Response(JSON.stringify(responseData), { status: 200 }),
);

const { result } = renderHook(() => useServingInvoke({ messages: [] }));

act(() => {
result.current.invoke();
});

await waitFor(() => {
expect(result.current.data).toEqual(responseData);
expect(result.current.loading).toBe(false);
});
});

test("sets error on failed response", async () => {
vi.spyOn(globalThis, "fetch").mockResolvedValue(
new Response(JSON.stringify({ error: "Not found" }), { status: 404 }),
);

const { result } = renderHook(() => useServingInvoke({ messages: [] }));

await act(async () => {
result.current.invoke();
// Wait for the fetch promise chain to resolve
await new Promise((r) => setTimeout(r, 10));
});

await waitFor(() => {
expect(result.current.error).toBe("Not found");
expect(result.current.loading).toBe(false);
});
});

test("auto starts when autoStart is true", async () => {
const fetchSpy = vi.spyOn(globalThis, "fetch");

renderHook(() => useServingInvoke({ messages: [] }, { autoStart: true }));

await waitFor(() => {
expect(fetchSpy).toHaveBeenCalled();
});
});
});
Loading