Skip to content
Draft
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
8 changes: 4 additions & 4 deletions apps/dev-playground/client/src/appkit-types/analytics.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,10 @@ declare module "@databricks/appkit-ui/react" {
result: Array<{
/** @sqlType STRING */
string_value: string;
/** @sqlType STRING */
number_value: string;
/** @sqlType STRING */
boolean_value: string;
/** @sqlType INT */
number_value: number;
/** @sqlType BOOLEAN */
boolean_value: boolean;
/** @sqlType STRING */
date_value: string;
/** @sqlType STRING */
Expand Down
21 changes: 21 additions & 0 deletions apps/dev-playground/client/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.

import { Route as rootRouteImport } from './routes/__root'
import { Route as VectorSearchRouteRouteImport } from './routes/vector-search.route'
import { Route as TypeSafetyRouteRouteImport } from './routes/type-safety.route'
import { Route as TelemetryRouteRouteImport } from './routes/telemetry.route'
import { Route as SqlHelpersRouteRouteImport } from './routes/sql-helpers.route'
Expand All @@ -23,6 +24,11 @@ import { Route as ArrowAnalyticsRouteRouteImport } from './routes/arrow-analytic
import { Route as AnalyticsRouteRouteImport } from './routes/analytics.route'
import { Route as IndexRouteImport } from './routes/index'

const VectorSearchRouteRoute = VectorSearchRouteRouteImport.update({
id: '/vector-search',
path: '/vector-search',
getParentRoute: () => rootRouteImport,
} as any)
const TypeSafetyRouteRoute = TypeSafetyRouteRouteImport.update({
id: '/type-safety',
path: '/type-safety',
Expand Down Expand Up @@ -103,6 +109,7 @@ export interface FileRoutesByFullPath {
'/sql-helpers': typeof SqlHelpersRouteRoute
'/telemetry': typeof TelemetryRouteRoute
'/type-safety': typeof TypeSafetyRouteRoute
'/vector-search': typeof VectorSearchRouteRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
Expand All @@ -118,6 +125,7 @@ export interface FileRoutesByTo {
'/sql-helpers': typeof SqlHelpersRouteRoute
'/telemetry': typeof TelemetryRouteRoute
'/type-safety': typeof TypeSafetyRouteRoute
'/vector-search': typeof VectorSearchRouteRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
Expand All @@ -134,6 +142,7 @@ export interface FileRoutesById {
'/sql-helpers': typeof SqlHelpersRouteRoute
'/telemetry': typeof TelemetryRouteRoute
'/type-safety': typeof TypeSafetyRouteRoute
'/vector-search': typeof VectorSearchRouteRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
Expand All @@ -151,6 +160,7 @@ export interface FileRouteTypes {
| '/sql-helpers'
| '/telemetry'
| '/type-safety'
| '/vector-search'
fileRoutesByTo: FileRoutesByTo
to:
| '/'
Expand All @@ -166,6 +176,7 @@ export interface FileRouteTypes {
| '/sql-helpers'
| '/telemetry'
| '/type-safety'
| '/vector-search'
id:
| '__root__'
| '/'
Expand All @@ -181,6 +192,7 @@ export interface FileRouteTypes {
| '/sql-helpers'
| '/telemetry'
| '/type-safety'
| '/vector-search'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
Expand All @@ -197,10 +209,18 @@ export interface RootRouteChildren {
SqlHelpersRouteRoute: typeof SqlHelpersRouteRoute
TelemetryRouteRoute: typeof TelemetryRouteRoute
TypeSafetyRouteRoute: typeof TypeSafetyRouteRoute
VectorSearchRouteRoute: typeof VectorSearchRouteRoute
}

declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/vector-search': {
id: '/vector-search'
path: '/vector-search'
fullPath: '/vector-search'
preLoaderRoute: typeof VectorSearchRouteRouteImport
parentRoute: typeof rootRouteImport
}
'/type-safety': {
id: '/type-safety'
path: '/type-safety'
Expand Down Expand Up @@ -309,6 +329,7 @@ const rootRouteChildren: RootRouteChildren = {
SqlHelpersRouteRoute: SqlHelpersRouteRoute,
TelemetryRouteRoute: TelemetryRouteRoute,
TypeSafetyRouteRoute: TypeSafetyRouteRoute,
VectorSearchRouteRoute: VectorSearchRouteRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
Expand Down
13 changes: 6 additions & 7 deletions apps/dev-playground/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function createMockClient() {

createApp({
plugins: [
server({ autoStart: false }),
server(),
reconnect(),
telemetryExamples(),
analytics({}),
Expand All @@ -49,9 +49,8 @@ createApp({
// }),
],
...(process.env.APPKIT_E2E_TEST && { client: createMockClient() }),
}).then((appkit) => {
appkit.server
.extend((app) => {
customize(appkit) {
appkit.server.extend((app) => {
app.get("/sp", (_req, res) => {
appkit.analytics
.query("SELECT * FROM samples.nyctaxi.trips;")
Expand Down Expand Up @@ -86,6 +85,6 @@ createApp({
});
});
});
})
.start();
});
});
},
}).catch(console.error);
21 changes: 0 additions & 21 deletions docs/docs/api/appkit/Class.ServerError.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ Use for server start/stop issues, configuration conflicts, etc.
## Example

```typescript
throw new ServerError("Cannot get server when autoStart is true");
throw new ServerError("Server not started");
```

Expand Down Expand Up @@ -151,26 +150,6 @@ Create a human-readable string representation

***

### autoStartConflict()

```ts
static autoStartConflict(operation: string): ServerError;
```

Create a server error for autoStart conflict

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `operation` | `string` |

#### Returns

`ServerError`

***

### clientDirectoryNotFound()

```ts
Expand Down
21 changes: 13 additions & 8 deletions docs/docs/api/appkit/Function.createApp.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
function createApp<T>(config: {
cache?: CacheConfig;
client?: WorkspaceClient;
customize?: (appkit: PluginMap<T>) => void | Promise<void>;
plugins?: T;
telemetry?: TelemetryConfig;
}): Promise<PluginMap<T>>;
Expand All @@ -13,6 +14,9 @@ Bootstraps AppKit with the provided configuration.

Initializes telemetry, cache, and service context, then registers plugins
in phase order (core, normal, deferred) and awaits their setup.
If a `customize` callback is provided it runs after plugin setup but
before the server starts, giving you access to the full appkit handle
for registering custom routes or performing async setup.
The returned object maps each plugin name to its `exports()` API,
with an `asUser(req)` method for user-scoped execution.

Expand All @@ -26,9 +30,10 @@ with an `asUser(req)` method for user-scoped execution.

| Parameter | Type |
| ------ | ------ |
| `config` | \{ `cache?`: [`CacheConfig`](Interface.CacheConfig.md); `client?`: `WorkspaceClient`; `plugins?`: `T`; `telemetry?`: [`TelemetryConfig`](Interface.TelemetryConfig.md); \} |
| `config` | \{ `cache?`: [`CacheConfig`](Interface.CacheConfig.md); `client?`: `WorkspaceClient`; `customize?`: (`appkit`: `PluginMap`\<`T`\>) => `void` \| `Promise`\<`void`\>; `plugins?`: `T`; `telemetry?`: [`TelemetryConfig`](Interface.TelemetryConfig.md); \} |
| `config.cache?` | [`CacheConfig`](Interface.CacheConfig.md) |
| `config.client?` | `WorkspaceClient` |
| `config.customize?` | (`appkit`: `PluginMap`\<`T`\>) => `void` \| `Promise`\<`void`\> |
| `config.plugins?` | `T` |
| `config.telemetry?` | [`TelemetryConfig`](Interface.TelemetryConfig.md) |

Expand All @@ -51,12 +56,12 @@ await createApp({
```ts
import { createApp, server, analytics } from "@databricks/appkit";

const appkit = await createApp({
plugins: [server({ autoStart: false }), analytics({})],
});

appkit.server.extend((app) => {
app.get("/custom", (_req, res) => res.json({ ok: true }));
await createApp({
plugins: [server(), analytics({})],
customize(appkit) {
appkit.server.extend((app) => {
app.get("/custom", (_req, res) => res.json({ ok: true }));
});
},
});
await appkit.server.start();
```
33 changes: 24 additions & 9 deletions docs/docs/plugins/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,38 @@ await createApp({
});
```

## Manual server start example
## Custom routes example

When you need to extend Express with custom routes:
Use the `customize` callback to extend Express with custom routes before the server starts:

```ts
import { createApp, server } from "@databricks/appkit";

const appkit = await createApp({
plugins: [server({ autoStart: false })],
await createApp({
plugins: [server()],
customize(appkit) {
appkit.server.extend((app) => {
app.get("/custom", (_req, res) => res.json({ ok: true }));
});
},
});
```

appkit.server.extend((app) => {
app.get("/custom", (_req, res) => res.json({ ok: true }));
});
The `customize` callback also supports async operations:

await appkit.server.start();
```ts
await createApp({
plugins: [server()],
async customize(appkit) {
const pool = await initializeDatabase();
appkit.server.extend((app) => {
app.get("/data", async (_req, res) => {
const result = await pool.query("SELECT 1");
res.json(result);
});
});
},
});
```

## Configuration options
Expand All @@ -64,7 +80,6 @@ await createApp({
server({
port: 8000, // default: Number(process.env.DATABRICKS_APP_PORT) || 8000
host: "0.0.0.0", // default: process.env.FLASK_RUN_HOST || "0.0.0.0"
autoStart: true, // default: true
staticPath: "dist", // optional: force a specific static directory
}),
],
Expand Down
32 changes: 23 additions & 9 deletions packages/appkit/src/core/appkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ export class AppKit<TPlugins extends InputPluginMap> {
telemetry?: TelemetryConfig;
cache?: CacheConfig;
client?: WorkspaceClient;
customize?: (appkit: PluginMap<T>) => void | Promise<void>;
} = {},
): Promise<PluginMap<T>> {
// Initialize core services
Expand Down Expand Up @@ -200,7 +201,16 @@ export class AppKit<TPlugins extends InputPluginMap> {

await Promise.all(instance.#setupPromises);

return instance as unknown as PluginMap<T>;
const handle = instance as unknown as PluginMap<T>;

await config.customize?.(handle);

const serverPlugin = instance.#pluginInstances.server;
if (serverPlugin && typeof (serverPlugin as any).start === "function") {
await (serverPlugin as any).start();
}

return handle;
}

private static preparePlugins(
Expand All @@ -222,6 +232,9 @@ export class AppKit<TPlugins extends InputPluginMap> {
*
* Initializes telemetry, cache, and service context, then registers plugins
* in phase order (core, normal, deferred) and awaits their setup.
* If a `customize` callback is provided it runs after plugin setup but
* before the server starts, giving you access to the full appkit handle
* for registering custom routes or performing async setup.
* The returned object maps each plugin name to its `exports()` API,
* with an `asUser(req)` method for user-scoped execution.
*
Expand All @@ -236,18 +249,18 @@ export class AppKit<TPlugins extends InputPluginMap> {
* });
* ```
*
* @example Extended Server with analytics and custom endpoint
* @example Server with custom routes via customize
* ```ts
* import { createApp, server, analytics } from "@databricks/appkit";
*
* const appkit = await createApp({
* plugins: [server({ autoStart: false }), analytics({})],
* });
*
* appkit.server.extend((app) => {
* app.get("/custom", (_req, res) => res.json({ ok: true }));
* await createApp({
* plugins: [server(), analytics({})],
* customize(appkit) {
* appkit.server.extend((app) => {
* app.get("/custom", (_req, res) => res.json({ ok: true }));
* });
* },
* });
* await appkit.server.start();
* ```
*/
export async function createApp<
Expand All @@ -258,6 +271,7 @@ export async function createApp<
telemetry?: TelemetryConfig;
cache?: CacheConfig;
client?: WorkspaceClient;
customize?: (appkit: PluginMap<T>) => void | Promise<void>;
} = {},
): Promise<PluginMap<T>> {
return AppKit._createApp(config);
Expand Down
10 changes: 0 additions & 10 deletions packages/appkit/src/errors/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { AppKitError } from "./base";
*
* @example
* ```typescript
* throw new ServerError("Cannot get server when autoStart is true");
* throw new ServerError("Server not started");
* ```
*/
Expand All @@ -15,15 +14,6 @@ export class ServerError extends AppKitError {
readonly statusCode = 500;
readonly isRetryable = false;

/**
* Create a server error for autoStart conflict
*/
static autoStartConflict(operation: string): ServerError {
return new ServerError(`Cannot ${operation} when autoStart is true`, {
context: { operation },
});
}

/**
* Create a server error for server not started
*/
Expand Down
6 changes: 0 additions & 6 deletions packages/appkit/src/errors/tests/errors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,12 +348,6 @@ describe("ServerError", () => {
expect(error.isRetryable).toBe(false);
});

test("autoStartConflict should create proper error", () => {
const error = ServerError.autoStartConflict("get server");
expect(error.message).toBe("Cannot get server when autoStart is true");
expect(error.context?.operation).toBe("get server");
});

test("notStarted should create proper error", () => {
const error = ServerError.notStarted();
expect(error.message).toContain("Server not started");
Expand Down
Loading
Loading