@databricks/appkit 0.25.1 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/appkit/package.js +1 -1
- package/dist/cli/commands/codemod/index.js +16 -0
- package/dist/cli/commands/codemod/index.js.map +1 -0
- package/dist/cli/commands/codemod/on-plugins-ready.js +364 -0
- package/dist/cli/commands/codemod/on-plugins-ready.js.map +1 -0
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/core/appkit.d.ts +12 -8
- package/dist/core/appkit.d.ts.map +1 -1
- package/dist/core/appkit.js +22 -9
- package/dist/core/appkit.js.map +1 -1
- package/dist/errors/server.d.ts +0 -5
- package/dist/errors/server.d.ts.map +1 -1
- package/dist/errors/server.js +0 -6
- package/dist/errors/server.js.map +1 -1
- package/dist/plugins/server/index.d.ts +16 -13
- package/dist/plugins/server/index.d.ts.map +1 -1
- package/dist/plugins/server/index.js +19 -17
- package/dist/plugins/server/index.js.map +1 -1
- package/dist/plugins/server/manifest.js +0 -5
- package/dist/plugins/server/types.d.ts +0 -1
- package/dist/plugins/server/types.d.ts.map +1 -1
- package/dist/registry/manifest-loader.d.ts +2 -2
- package/dist/registry/manifest-loader.d.ts.map +1 -1
- package/docs/api/appkit/Class.ServerError.md +4 -26
- package/docs/api/appkit/Function.createApp.md +17 -15
- package/docs/plugins/server.md +25 -9
- package/package.json +1 -1
- package/sbom.cdx.json +1 -1
package/dist/errors/server.d.ts
CHANGED
|
@@ -7,7 +7,6 @@ import { AppKitError } from "./base.js";
|
|
|
7
7
|
*
|
|
8
8
|
* @example
|
|
9
9
|
* ```typescript
|
|
10
|
-
* throw new ServerError("Cannot get server when autoStart is true");
|
|
11
10
|
* throw new ServerError("Server not started");
|
|
12
11
|
* ```
|
|
13
12
|
*/
|
|
@@ -15,10 +14,6 @@ declare class ServerError extends AppKitError {
|
|
|
15
14
|
readonly code = "SERVER_ERROR";
|
|
16
15
|
readonly statusCode = 500;
|
|
17
16
|
readonly isRetryable = false;
|
|
18
|
-
/**
|
|
19
|
-
* Create a server error for autoStart conflict
|
|
20
|
-
*/
|
|
21
|
-
static autoStartConflict(operation: string): ServerError;
|
|
22
17
|
/**
|
|
23
18
|
* Create a server error for server not started
|
|
24
19
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","names":[],"sources":["../../src/errors/server.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"server.d.ts","names":[],"sources":["../../src/errors/server.ts"],"mappings":";;;;;AAWA;;;;;;;cAAa,WAAA,SAAoB,WAAA;EAAA,SACtB,IAAA;EAAA,SACA,UAAA;EAAA,SACA,WAAA;EADA;;;EAAA,OAMF,UAAA,CAAA,GAAc,WAAA;EASd;;;EAAA,OAAA,kBAAA,CAAA,GAAsB,WAAA;EAO4B;;;EAAA,OAAlD,uBAAA,CAAwB,aAAA,aAA0B,WAAA;AAAA"}
|
package/dist/errors/server.js
CHANGED
|
@@ -10,12 +10,6 @@ var init_server = __esmMin((() => {
|
|
|
10
10
|
statusCode = 500;
|
|
11
11
|
isRetryable = false;
|
|
12
12
|
/**
|
|
13
|
-
* Create a server error for autoStart conflict
|
|
14
|
-
*/
|
|
15
|
-
static autoStartConflict(operation) {
|
|
16
|
-
return new ServerError(`Cannot ${operation} when autoStart is true`, { context: { operation } });
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
13
|
* Create a server error for server not started
|
|
20
14
|
*/
|
|
21
15
|
static notStarted() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","names":[],"sources":["../../src/errors/server.ts"],"sourcesContent":["import { AppKitError } from \"./base\";\n\n/**\n * Error thrown when server lifecycle operations fail.\n * Use for server start/stop issues, configuration conflicts, etc.\n *\n * @example\n * ```typescript\n * throw new ServerError(\"
|
|
1
|
+
{"version":3,"file":"server.js","names":[],"sources":["../../src/errors/server.ts"],"sourcesContent":["import { AppKitError } from \"./base\";\n\n/**\n * Error thrown when server lifecycle operations fail.\n * Use for server start/stop issues, configuration conflicts, etc.\n *\n * @example\n * ```typescript\n * throw new ServerError(\"Server not started\");\n * ```\n */\nexport class ServerError extends AppKitError {\n readonly code = \"SERVER_ERROR\";\n readonly statusCode = 500;\n readonly isRetryable = false;\n\n /**\n * Create a server error for server not started\n */\n static notStarted(): ServerError {\n return new ServerError(\n \"Server not started. Please start the server first by calling the start() method\",\n );\n }\n\n /**\n * Create a server error for Vite dev server not initialized\n */\n static viteNotInitialized(): ServerError {\n return new ServerError(\"Vite dev server not initialized\");\n }\n\n /**\n * Create a server error for missing client directory\n */\n static clientDirectoryNotFound(searchedPaths: string[]): ServerError {\n return new ServerError(\n `Could not find client directory. Searched for vite.config.ts/js + index.html in: ${searchedPaths.join(\", \")}`,\n { context: { searchedPaths } },\n );\n }\n}\n"],"mappings":";;;;;;YAAqC;CAWxB,cAAb,MAAa,oBAAoB,YAAY;EAC3C,AAAS,OAAO;EAChB,AAAS,aAAa;EACtB,AAAS,cAAc;;;;EAKvB,OAAO,aAA0B;AAC/B,UAAO,IAAI,YACT,kFACD;;;;;EAMH,OAAO,qBAAkC;AACvC,UAAO,IAAI,YAAY,kCAAkC;;;;;EAM3D,OAAO,wBAAwB,eAAsC;AACnE,UAAO,IAAI,YACT,oFAAoF,cAAc,KAAK,KAAK,IAC5G,EAAE,SAAS,EAAE,eAAe,EAAE,CAC/B"}
|
|
@@ -15,17 +15,23 @@ import express from "express";
|
|
|
15
15
|
* This plugin is responsible for starting the server and serving the static files.
|
|
16
16
|
* It also handles the remote tunneling for development purposes.
|
|
17
17
|
*
|
|
18
|
+
* The server is started automatically by `createApp` after all plugins are set up
|
|
19
|
+
* and the optional `onPluginsReady` callback has run.
|
|
20
|
+
*
|
|
18
21
|
* @example
|
|
19
22
|
* ```ts
|
|
20
23
|
* createApp({
|
|
21
|
-
* plugins: [server(),
|
|
24
|
+
* plugins: [server(), analytics({})],
|
|
25
|
+
* onPluginsReady(appkit) {
|
|
26
|
+
* appkit.server.extend((app) => {
|
|
27
|
+
* app.get("/custom", (_req, res) => res.json({ ok: true }));
|
|
28
|
+
* });
|
|
29
|
+
* },
|
|
22
30
|
* });
|
|
23
31
|
* ```
|
|
24
|
-
*
|
|
25
32
|
*/
|
|
26
33
|
declare class ServerPlugin extends Plugin {
|
|
27
34
|
static DEFAULT_CONFIG: {
|
|
28
|
-
autoStart: boolean;
|
|
29
35
|
host: string;
|
|
30
36
|
port: number;
|
|
31
37
|
};
|
|
@@ -40,20 +46,16 @@ declare class ServerPlugin extends Plugin {
|
|
|
40
46
|
private rawBodyPaths;
|
|
41
47
|
static phase: PluginPhase;
|
|
42
48
|
constructor(config: ServerConfig);
|
|
43
|
-
/** Setup the server plugin. */
|
|
44
49
|
setup(): Promise<void>;
|
|
45
50
|
/** Get the server configuration. */
|
|
46
51
|
getConfig(): {
|
|
47
52
|
[key: string]: unknown;
|
|
48
53
|
port?: number;
|
|
49
54
|
staticPath?: string;
|
|
50
|
-
autoStart?: boolean;
|
|
51
55
|
host?: string;
|
|
52
56
|
name?: string;
|
|
53
57
|
telemetry?: TelemetryOptions;
|
|
54
58
|
};
|
|
55
|
-
/** Check if the server should auto start. */
|
|
56
|
-
shouldAutoStart(): boolean | undefined;
|
|
57
59
|
/**
|
|
58
60
|
* Start the server.
|
|
59
61
|
*
|
|
@@ -68,16 +70,18 @@ declare class ServerPlugin extends Plugin {
|
|
|
68
70
|
*
|
|
69
71
|
* Only use this method if you need to access the server instance for advanced usage like a custom websocket server, etc.
|
|
70
72
|
*
|
|
71
|
-
* @throws {Error} If the server
|
|
73
|
+
* @throws {Error} If the server has not started yet.
|
|
72
74
|
* @returns {HTTPServer} The server instance.
|
|
73
75
|
*/
|
|
74
76
|
getServer(): Server;
|
|
75
77
|
/**
|
|
76
78
|
* Extend the server with custom routes or middleware.
|
|
77
79
|
*
|
|
80
|
+
* Call this inside the `onPluginsReady` callback of `createApp` to register
|
|
81
|
+
* custom Express routes or middleware before the server starts listening.
|
|
82
|
+
*
|
|
78
83
|
* @param fn - A function that receives the express application.
|
|
79
84
|
* @returns The server plugin instance for chaining.
|
|
80
|
-
* @throws {Error} If autoStart is true.
|
|
81
85
|
*/
|
|
82
86
|
extend(fn: (app: express.Application) => void): this;
|
|
83
87
|
/**
|
|
@@ -103,18 +107,17 @@ declare class ServerPlugin extends Plugin {
|
|
|
103
107
|
* Exposes server management methods.
|
|
104
108
|
*/
|
|
105
109
|
exports(): {
|
|
106
|
-
/**
|
|
107
|
-
extend(fn: (app: express.Application) => void): /*elided*/any; /** Get the underlying HTTP server instance */
|
|
110
|
+
/** Extend the server with custom routes or middleware */extend(fn: (app: express.Application) => void): /*elided*/any; /** Get the underlying HTTP server instance */
|
|
108
111
|
getServer: () => Server; /** Get the server configuration */
|
|
109
112
|
getConfig: () => {
|
|
110
113
|
[key: string]: unknown;
|
|
111
114
|
port?: number;
|
|
112
115
|
staticPath?: string;
|
|
113
|
-
autoStart?: boolean;
|
|
114
116
|
host?: string;
|
|
115
117
|
name?: string;
|
|
116
118
|
telemetry?: TelemetryOptions;
|
|
117
|
-
};
|
|
119
|
+
}; /** @deprecated Server is now started automatically by createApp. */
|
|
120
|
+
start(): never;
|
|
118
121
|
};
|
|
119
122
|
}
|
|
120
123
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/plugins/server/index.ts"],"mappings":";;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/plugins/server/index.ts"],"mappings":";;;;;;;;;;;;;;;;AA4CA;;;;;;;;;;;;;;;;cAAa,YAAA,SAAqB,MAAA;EAAA,OAClB,cAAA;;;;;SAMP,QAAA,EAAuB,cAAA;EAAA,QACtB,iBAAA;EAAA,QACA,MAAA;EAAA,QACA,aAAA;EAAA,QACA,sBAAA;EAAA,UACU,MAAA,EAAQ,YAAA;EAAA,QAClB,gBAAA;EAAA,QACA,YAAA;EAAA,OACD,KAAA,EAAO,WAAA;cAEF,MAAA,EAAQ,YAAA;EAmBd,KAAA,CAAA,GAAK,OAAA;EArBJ;EAwBP,SAAA,CAAA;IAAA;;;;;gBAHW,gBAAA;EAAA;;;;;;;;;EAiBL,KAAA,CAAA,GAAS,OAAA,CAAQ,OAAA,CAAQ,WAAA;EAAA;;;;;;;;EA2D/B,SAAA,CAAA,GAAa,MAAA;EA+FC;;;;;;;;;EA9Ed,MAAA,CAAO,EAAA,GAAK,GAAA,EAAK,OAAA,CAAQ,WAAA;;;;;;;;UAYX,YAAA;;;;;;AA0OhB;UAxKgB,aAAA;EAAA,eA4CC,cAAA;EAAA,QAaP,cAAA;EAAA,QA4BM,iBAAA;EAmFG;;;;EAlCjB,OAAA,CAAA;IAkCiB,qEA9BD,GAAA,EAAK,OAAA,CAAQ,WAAA,YA8BZ,eAAA;qBAvQJ,MAAA;;;;;;;kBAAU,gBAAA;IAAA;;;;;;;cAuQZ,MAAA,EAAM,QAAA,QAAA,YAAA,EAAA,YAAA"}
|
|
@@ -27,17 +27,23 @@ const logger = createLogger("server");
|
|
|
27
27
|
* This plugin is responsible for starting the server and serving the static files.
|
|
28
28
|
* It also handles the remote tunneling for development purposes.
|
|
29
29
|
*
|
|
30
|
+
* The server is started automatically by `createApp` after all plugins are set up
|
|
31
|
+
* and the optional `onPluginsReady` callback has run.
|
|
32
|
+
*
|
|
30
33
|
* @example
|
|
31
34
|
* ```ts
|
|
32
35
|
* createApp({
|
|
33
|
-
* plugins: [server(),
|
|
36
|
+
* plugins: [server(), analytics({})],
|
|
37
|
+
* onPluginsReady(appkit) {
|
|
38
|
+
* appkit.server.extend((app) => {
|
|
39
|
+
* app.get("/custom", (_req, res) => res.json({ ok: true }));
|
|
40
|
+
* });
|
|
41
|
+
* },
|
|
34
42
|
* });
|
|
35
43
|
* ```
|
|
36
|
-
*
|
|
37
44
|
*/
|
|
38
45
|
var ServerPlugin = class ServerPlugin extends Plugin {
|
|
39
46
|
static DEFAULT_CONFIG = {
|
|
40
|
-
autoStart: true,
|
|
41
47
|
host: process.env.FLASK_RUN_HOST || "0.0.0.0",
|
|
42
48
|
port: Number(process.env.DATABRICKS_APP_PORT) || 8e3
|
|
43
49
|
};
|
|
@@ -51,6 +57,7 @@ var ServerPlugin = class ServerPlugin extends Plugin {
|
|
|
51
57
|
rawBodyPaths = /* @__PURE__ */ new Set();
|
|
52
58
|
static phase = "deferred";
|
|
53
59
|
constructor(config) {
|
|
60
|
+
if ("autoStart" in config) throw new ServerError("server({ autoStart }) has been removed. The server is now started automatically by createApp.\n\nRun `npx appkit codemod on-plugins-ready --write` to auto-migrate.");
|
|
54
61
|
super(config);
|
|
55
62
|
this.config = config;
|
|
56
63
|
this.serverApplication = express();
|
|
@@ -58,19 +65,12 @@ var ServerPlugin = class ServerPlugin extends Plugin {
|
|
|
58
65
|
this.serverExtensions = [];
|
|
59
66
|
this.telemetry.registerInstrumentations([instrumentations.http, instrumentations.express]);
|
|
60
67
|
}
|
|
61
|
-
|
|
62
|
-
async setup() {
|
|
63
|
-
if (this.shouldAutoStart()) await this.start();
|
|
64
|
-
}
|
|
68
|
+
async setup() {}
|
|
65
69
|
/** Get the server configuration. */
|
|
66
70
|
getConfig() {
|
|
67
71
|
const { plugins: _plugins, ...config } = this.config;
|
|
68
72
|
return config;
|
|
69
73
|
}
|
|
70
|
-
/** Check if the server should auto start. */
|
|
71
|
-
shouldAutoStart() {
|
|
72
|
-
return this.config.autoStart;
|
|
73
|
-
}
|
|
74
74
|
/**
|
|
75
75
|
* Start the server.
|
|
76
76
|
*
|
|
@@ -103,23 +103,23 @@ var ServerPlugin = class ServerPlugin extends Plugin {
|
|
|
103
103
|
*
|
|
104
104
|
* Only use this method if you need to access the server instance for advanced usage like a custom websocket server, etc.
|
|
105
105
|
*
|
|
106
|
-
* @throws {Error} If the server
|
|
106
|
+
* @throws {Error} If the server has not started yet.
|
|
107
107
|
* @returns {HTTPServer} The server instance.
|
|
108
108
|
*/
|
|
109
109
|
getServer() {
|
|
110
|
-
if (this.shouldAutoStart()) throw ServerError.autoStartConflict("get server");
|
|
111
110
|
if (!this.server) throw ServerError.notStarted();
|
|
112
111
|
return this.server;
|
|
113
112
|
}
|
|
114
113
|
/**
|
|
115
114
|
* Extend the server with custom routes or middleware.
|
|
116
115
|
*
|
|
116
|
+
* Call this inside the `onPluginsReady` callback of `createApp` to register
|
|
117
|
+
* custom Express routes or middleware before the server starts listening.
|
|
118
|
+
*
|
|
117
119
|
* @param fn - A function that receives the express application.
|
|
118
120
|
* @returns The server plugin instance for chaining.
|
|
119
|
-
* @throws {Error} If autoStart is true.
|
|
120
121
|
*/
|
|
121
122
|
extend(fn) {
|
|
122
|
-
if (this.shouldAutoStart()) throw ServerError.autoStartConflict("extend server");
|
|
123
123
|
this.serverExtensions.push(fn);
|
|
124
124
|
return this;
|
|
125
125
|
}
|
|
@@ -245,13 +245,15 @@ var ServerPlugin = class ServerPlugin extends Plugin {
|
|
|
245
245
|
exports() {
|
|
246
246
|
const self = this;
|
|
247
247
|
return {
|
|
248
|
-
start: this.start,
|
|
249
248
|
extend(fn) {
|
|
250
249
|
self.extend(fn);
|
|
251
250
|
return this;
|
|
252
251
|
},
|
|
253
252
|
getServer: this.getServer,
|
|
254
|
-
getConfig: this.getConfig
|
|
253
|
+
getConfig: this.getConfig,
|
|
254
|
+
start() {
|
|
255
|
+
throw new ServerError("server.start() has been removed. Use the onPluginsReady callback instead:\n\n createApp({\n plugins: [server(), ...],\n onPluginsReady(appkit) {\n appkit.server.extend(...);\n },\n });\n\nRun `npx appkit codemod on-plugins-ready --write` to auto-migrate.");
|
|
256
|
+
}
|
|
255
257
|
};
|
|
256
258
|
}
|
|
257
259
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["manifest"],"sources":["../../../src/plugins/server/index.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport type { Server as HTTPServer } from \"node:http\";\nimport path from \"node:path\";\nimport dotenv from \"dotenv\";\nimport express from \"express\";\nimport type { PluginClientConfigs, PluginPhase } from \"shared\";\nimport { ServerError } from \"../../errors\";\nimport { createLogger } from \"../../logging/logger\";\nimport { Plugin, toPlugin } from \"../../plugin\";\nimport type { PluginManifest } from \"../../registry\";\nimport { instrumentations } from \"../../telemetry\";\nimport { sanitizeClientConfig } from \"./client-config-sanitizer\";\nimport manifest from \"./manifest.json\";\nimport { RemoteTunnelController } from \"./remote-tunnel/remote-tunnel-controller\";\nimport { StaticServer } from \"./static-server\";\nimport type { ServerConfig } from \"./types\";\nimport { getRoutes, type PluginEndpoints, printRoutes } from \"./utils\";\nimport { ViteDevServer } from \"./vite-dev-server\";\n\ndotenv.config({ path: path.resolve(process.cwd(), \"./.env\") });\n\nconst logger = createLogger(\"server\");\n\n/**\n * Server plugin for the AppKit.\n *\n * This plugin is responsible for starting the server and serving the static files.\n * It also handles the remote tunneling for development purposes.\n *\n * @example\n * ```ts\n * createApp({\n * plugins: [server(), telemetryExamples(), analytics({})],\n * });\n * ```\n *\n */\nexport class ServerPlugin extends Plugin {\n public static DEFAULT_CONFIG = {\n autoStart: true,\n host: process.env.FLASK_RUN_HOST || \"0.0.0.0\",\n port: Number(process.env.DATABRICKS_APP_PORT) || 8000,\n };\n\n /** Plugin manifest declaring metadata and resource requirements */\n static manifest = manifest as PluginManifest<\"server\">;\n private serverApplication: express.Application;\n private server: HTTPServer | null;\n private viteDevServer?: ViteDevServer;\n private remoteTunnelController?: RemoteTunnelController;\n protected declare config: ServerConfig;\n private serverExtensions: ((app: express.Application) => void)[] = [];\n private rawBodyPaths: Set<string> = new Set();\n static phase: PluginPhase = \"deferred\";\n\n constructor(config: ServerConfig) {\n super(config);\n this.config = config;\n this.serverApplication = express();\n this.server = null;\n this.serverExtensions = [];\n this.telemetry.registerInstrumentations([\n instrumentations.http,\n instrumentations.express,\n ]);\n }\n\n /** Setup the server plugin. */\n async setup() {\n if (this.shouldAutoStart()) {\n await this.start();\n }\n }\n\n /** Get the server configuration. */\n getConfig() {\n const { plugins: _plugins, ...config } = this.config;\n\n return config;\n }\n\n /** Check if the server should auto start. */\n shouldAutoStart() {\n return this.config.autoStart;\n }\n\n /**\n * Start the server.\n *\n * This method starts the server and sets up the frontend.\n * It also sets up the remote tunneling if enabled.\n *\n * @returns The express application.\n */\n async start(): Promise<express.Application> {\n this.serverApplication.use(\n express.json({\n type: (req) => {\n // Skip JSON parsing for routes that declared skipBodyParsing\n // (e.g. file uploads where the raw body must flow through).\n // rawBodyPaths is populated by extendRoutes() below; the type\n // callback runs per-request so the set is already filled.\n const urlPath = req.url?.split(\"?\")[0];\n if (urlPath && this.rawBodyPaths.has(urlPath)) return false;\n const ct = req.headers[\"content-type\"] ?? \"\";\n return ct.includes(\"json\");\n },\n }),\n );\n\n const { endpoints, pluginConfigs } = await this.extendRoutes();\n\n for (const extension of this.serverExtensions) {\n extension(this.serverApplication);\n }\n\n // register remote tunnel controller (before static/vite)\n this.remoteTunnelController = new RemoteTunnelController(\n this.devFileReader,\n );\n this.serverApplication.use(this.remoteTunnelController.middleware);\n\n await this.setupFrontend(endpoints, pluginConfigs);\n\n const server = this.serverApplication.listen(\n this.config.port ?? ServerPlugin.DEFAULT_CONFIG.port,\n this.config.host ?? ServerPlugin.DEFAULT_CONFIG.host,\n () => this.logStartupInfo(),\n );\n\n this.server = server;\n\n // attach server to remote tunnel controller\n this.remoteTunnelController.setServer(server);\n\n process.on(\"SIGTERM\", () => this._gracefulShutdown());\n process.on(\"SIGINT\", () => this._gracefulShutdown());\n\n if (process.env.NODE_ENV === \"development\") {\n const allRoutes = getRoutes(this.serverApplication._router.stack);\n printRoutes(allRoutes);\n }\n return this.serverApplication;\n }\n\n /**\n * Get the low level node.js http server instance.\n *\n * Only use this method if you need to access the server instance for advanced usage like a custom websocket server, etc.\n *\n * @throws {Error} If the server is not started or autoStart is true.\n * @returns {HTTPServer} The server instance.\n */\n getServer(): HTTPServer {\n if (this.shouldAutoStart()) {\n throw ServerError.autoStartConflict(\"get server\");\n }\n\n if (!this.server) {\n throw ServerError.notStarted();\n }\n\n return this.server;\n }\n\n /**\n * Extend the server with custom routes or middleware.\n *\n * @param fn - A function that receives the express application.\n * @returns The server plugin instance for chaining.\n * @throws {Error} If autoStart is true.\n */\n extend(fn: (app: express.Application) => void) {\n if (this.shouldAutoStart()) {\n throw ServerError.autoStartConflict(\"extend server\");\n }\n\n this.serverExtensions.push(fn);\n return this;\n }\n\n /**\n * Setup the routes with the plugins.\n *\n * This method goes through all the plugins and injects the routes into the server application.\n * Returns a map of plugin names to their registered named endpoints,\n * and a map of plugin names to their client-exposed configs.\n */\n private async extendRoutes(): Promise<{\n endpoints: PluginEndpoints;\n pluginConfigs: PluginClientConfigs;\n }> {\n const endpoints: PluginEndpoints = {};\n const pluginConfigs: PluginClientConfigs = {};\n\n if (!this.config.plugins) return { endpoints, pluginConfigs };\n\n this.serverApplication.get(\"/health\", (_, res) => {\n res.status(200).json({ status: \"ok\" });\n });\n this.registerEndpoint(\"health\", \"/health\");\n\n for (const plugin of Object.values(this.config.plugins)) {\n if (EXCLUDED_PLUGINS.includes(plugin.name)) continue;\n\n if (plugin?.injectRoutes && typeof plugin.injectRoutes === \"function\") {\n const router = express.Router();\n\n plugin.injectRoutes(router);\n\n const basePath = `/api/${plugin.name}`;\n this.serverApplication.use(basePath, router);\n\n endpoints[plugin.name] = plugin.getEndpoints();\n\n // Collect paths that should skip body parsing\n if (\n plugin.getSkipBodyParsingPaths &&\n typeof plugin.getSkipBodyParsingPaths === \"function\"\n ) {\n for (const p of plugin.getSkipBodyParsingPaths()) {\n this.rawBodyPaths.add(p);\n }\n }\n }\n\n if (typeof plugin.clientConfig === \"function\") {\n try {\n const raw = plugin.clientConfig();\n if (raw != null) {\n const sanitized = sanitizeClientConfig(plugin.name, raw);\n if (Object.keys(sanitized).length > 0) {\n pluginConfigs[plugin.name] = sanitized;\n }\n }\n } catch (error) {\n logger.error(\n \"Plugin '%s' clientConfig() failed, skipping its config: %O\",\n plugin.name,\n error,\n );\n }\n }\n }\n\n return { endpoints, pluginConfigs };\n }\n\n /**\n * Setup frontend serving based on environment:\n * - If staticPath is explicitly provided: use static server\n * - Dev mode (no staticPath): Vite for HMR\n * - Production (no staticPath): Static files auto-detected\n */\n private async setupFrontend(\n endpoints: PluginEndpoints,\n pluginConfigs: PluginClientConfigs,\n ) {\n const isDev = process.env.NODE_ENV === \"development\";\n const hasExplicitStaticPath = this.config.staticPath !== undefined;\n\n // explict static path provided\n if (hasExplicitStaticPath) {\n const staticServer = new StaticServer(\n this.serverApplication,\n this.config.staticPath as string,\n endpoints,\n pluginConfigs,\n );\n staticServer.setup();\n return;\n }\n\n // auto-detection based on environment\n if (isDev) {\n this.viteDevServer = new ViteDevServer(\n this.serverApplication,\n endpoints,\n pluginConfigs,\n );\n await this.viteDevServer.setup();\n return;\n }\n\n // auto-detection based on static path\n const staticPath = ServerPlugin.findStaticPath();\n if (staticPath) {\n const staticServer = new StaticServer(\n this.serverApplication,\n staticPath,\n endpoints,\n pluginConfigs,\n );\n\n staticServer.setup();\n }\n }\n\n private static findStaticPath() {\n const staticPaths = [\"dist\", \"client/dist\", \"build\", \"public\", \"out\"];\n const cwd = process.cwd();\n for (const p of staticPaths) {\n const fullPath = path.resolve(cwd, p);\n if (fs.existsSync(path.resolve(fullPath, \"index.html\"))) {\n logger.debug(\"Static files: serving from %s\", fullPath);\n return fullPath;\n }\n }\n return undefined;\n }\n\n private logStartupInfo() {\n const isDev = process.env.NODE_ENV === \"development\";\n const hasExplicitStaticPath = this.config.staticPath !== undefined;\n const port = this.config.port ?? ServerPlugin.DEFAULT_CONFIG.port;\n const host = this.config.host ?? ServerPlugin.DEFAULT_CONFIG.host;\n\n logger.info(\"Server running on http://%s:%d\", host, port);\n\n if (hasExplicitStaticPath) {\n logger.info(\"Mode: static (%s)\", this.config.staticPath);\n } else if (isDev) {\n logger.info(\"Mode: development (Vite HMR)\");\n } else {\n logger.info(\"Mode: production (static)\");\n }\n\n const remoteServerController = this.remoteTunnelController;\n if (!remoteServerController) {\n logger.debug(\"Remote tunnel: disabled (controller not initialized)\");\n } else {\n logger.debug(\n \"Remote tunnel: %s; %s\",\n remoteServerController.isAllowedByEnv() ? \"allowed\" : \"blocked\",\n remoteServerController.isActive() ? \"active\" : \"inactive\",\n );\n }\n }\n\n private async _gracefulShutdown() {\n logger.info(\"Starting graceful shutdown...\");\n\n if (this.viteDevServer) {\n await this.viteDevServer.close();\n }\n\n if (this.remoteTunnelController) {\n this.remoteTunnelController.cleanup();\n }\n\n // 1. abort active operations from plugins\n if (this.config.plugins) {\n for (const plugin of Object.values(this.config.plugins)) {\n if (plugin.abortActiveOperations) {\n try {\n plugin.abortActiveOperations();\n } catch (err) {\n logger.error(\n \"Error aborting operations for plugin %s: %O\",\n plugin.name,\n err,\n );\n }\n }\n }\n }\n\n // 2. close the server\n if (this.server) {\n this.server.close(() => {\n logger.debug(\"Server closed gracefully\");\n process.exit(0);\n });\n\n // 3. timeout to force shutdown after 15 seconds\n setTimeout(() => {\n logger.debug(\"Force shutdown after timeout\");\n process.exit(1);\n }, 15000);\n } else {\n process.exit(0);\n }\n }\n\n /**\n * Returns the public exports for the server plugin.\n * Exposes server management methods.\n */\n exports() {\n const self = this;\n return {\n /** Start the server */\n start: this.start,\n /** Extend the server with custom routes or middleware */\n extend(fn: (app: express.Application) => void) {\n self.extend(fn);\n return this;\n },\n /** Get the underlying HTTP server instance */\n getServer: this.getServer,\n /** Get the server configuration */\n getConfig: this.getConfig,\n };\n }\n}\n\nconst EXCLUDED_PLUGINS: string[] = [ServerPlugin.manifest.name];\n\n/**\n * @internal\n */\nexport const server = toPlugin(ServerPlugin);\n// Export manifest and types\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["manifest"],"sources":["../../../src/plugins/server/index.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport type { Server as HTTPServer } from \"node:http\";\nimport path from \"node:path\";\nimport dotenv from \"dotenv\";\nimport express from \"express\";\nimport type { PluginClientConfigs, PluginPhase } from \"shared\";\nimport { ServerError } from \"../../errors\";\nimport { createLogger } from \"../../logging/logger\";\nimport { Plugin, toPlugin } from \"../../plugin\";\nimport type { PluginManifest } from \"../../registry\";\nimport { instrumentations } from \"../../telemetry\";\nimport { sanitizeClientConfig } from \"./client-config-sanitizer\";\nimport manifest from \"./manifest.json\";\nimport { RemoteTunnelController } from \"./remote-tunnel/remote-tunnel-controller\";\nimport { StaticServer } from \"./static-server\";\nimport type { ServerConfig } from \"./types\";\nimport { getRoutes, type PluginEndpoints, printRoutes } from \"./utils\";\nimport { ViteDevServer } from \"./vite-dev-server\";\n\ndotenv.config({ path: path.resolve(process.cwd(), \"./.env\") });\n\nconst logger = createLogger(\"server\");\n\n/**\n * Server plugin for the AppKit.\n *\n * This plugin is responsible for starting the server and serving the static files.\n * It also handles the remote tunneling for development purposes.\n *\n * The server is started automatically by `createApp` after all plugins are set up\n * and the optional `onPluginsReady` callback has run.\n *\n * @example\n * ```ts\n * createApp({\n * plugins: [server(), analytics({})],\n * onPluginsReady(appkit) {\n * appkit.server.extend((app) => {\n * app.get(\"/custom\", (_req, res) => res.json({ ok: true }));\n * });\n * },\n * });\n * ```\n */\nexport class ServerPlugin extends Plugin {\n public static DEFAULT_CONFIG = {\n host: process.env.FLASK_RUN_HOST || \"0.0.0.0\",\n port: Number(process.env.DATABRICKS_APP_PORT) || 8000,\n };\n\n /** Plugin manifest declaring metadata and resource requirements */\n static manifest = manifest as PluginManifest<\"server\">;\n private serverApplication: express.Application;\n private server: HTTPServer | null;\n private viteDevServer?: ViteDevServer;\n private remoteTunnelController?: RemoteTunnelController;\n protected declare config: ServerConfig;\n private serverExtensions: ((app: express.Application) => void)[] = [];\n private rawBodyPaths: Set<string> = new Set();\n static phase: PluginPhase = \"deferred\";\n\n constructor(config: ServerConfig) {\n if (\"autoStart\" in config) {\n throw new ServerError(\n \"server({ autoStart }) has been removed. \" +\n \"The server is now started automatically by createApp.\\n\\n\" +\n \"Run `npx appkit codemod on-plugins-ready --write` to auto-migrate.\",\n );\n }\n super(config);\n this.config = config;\n this.serverApplication = express();\n this.server = null;\n this.serverExtensions = [];\n this.telemetry.registerInstrumentations([\n instrumentations.http,\n instrumentations.express,\n ]);\n }\n\n async setup() {}\n\n /** Get the server configuration. */\n getConfig() {\n const { plugins: _plugins, ...config } = this.config;\n\n return config;\n }\n\n /**\n * Start the server.\n *\n * This method starts the server and sets up the frontend.\n * It also sets up the remote tunneling if enabled.\n *\n * @returns The express application.\n */\n async start(): Promise<express.Application> {\n this.serverApplication.use(\n express.json({\n type: (req) => {\n // Skip JSON parsing for routes that declared skipBodyParsing\n // (e.g. file uploads where the raw body must flow through).\n // rawBodyPaths is populated by extendRoutes() below; the type\n // callback runs per-request so the set is already filled.\n const urlPath = req.url?.split(\"?\")[0];\n if (urlPath && this.rawBodyPaths.has(urlPath)) return false;\n const ct = req.headers[\"content-type\"] ?? \"\";\n return ct.includes(\"json\");\n },\n }),\n );\n\n const { endpoints, pluginConfigs } = await this.extendRoutes();\n\n for (const extension of this.serverExtensions) {\n extension(this.serverApplication);\n }\n\n // register remote tunnel controller (before static/vite)\n this.remoteTunnelController = new RemoteTunnelController(\n this.devFileReader,\n );\n this.serverApplication.use(this.remoteTunnelController.middleware);\n\n await this.setupFrontend(endpoints, pluginConfigs);\n\n const server = this.serverApplication.listen(\n this.config.port ?? ServerPlugin.DEFAULT_CONFIG.port,\n this.config.host ?? ServerPlugin.DEFAULT_CONFIG.host,\n () => this.logStartupInfo(),\n );\n\n this.server = server;\n\n // attach server to remote tunnel controller\n this.remoteTunnelController.setServer(server);\n\n process.on(\"SIGTERM\", () => this._gracefulShutdown());\n process.on(\"SIGINT\", () => this._gracefulShutdown());\n\n if (process.env.NODE_ENV === \"development\") {\n const allRoutes = getRoutes(this.serverApplication._router.stack);\n printRoutes(allRoutes);\n }\n return this.serverApplication;\n }\n\n /**\n * Get the low level node.js http server instance.\n *\n * Only use this method if you need to access the server instance for advanced usage like a custom websocket server, etc.\n *\n * @throws {Error} If the server has not started yet.\n * @returns {HTTPServer} The server instance.\n */\n getServer(): HTTPServer {\n if (!this.server) {\n throw ServerError.notStarted();\n }\n\n return this.server;\n }\n\n /**\n * Extend the server with custom routes or middleware.\n *\n * Call this inside the `onPluginsReady` callback of `createApp` to register\n * custom Express routes or middleware before the server starts listening.\n *\n * @param fn - A function that receives the express application.\n * @returns The server plugin instance for chaining.\n */\n extend(fn: (app: express.Application) => void) {\n this.serverExtensions.push(fn);\n return this;\n }\n\n /**\n * Setup the routes with the plugins.\n *\n * This method goes through all the plugins and injects the routes into the server application.\n * Returns a map of plugin names to their registered named endpoints,\n * and a map of plugin names to their client-exposed configs.\n */\n private async extendRoutes(): Promise<{\n endpoints: PluginEndpoints;\n pluginConfigs: PluginClientConfigs;\n }> {\n const endpoints: PluginEndpoints = {};\n const pluginConfigs: PluginClientConfigs = {};\n\n if (!this.config.plugins) return { endpoints, pluginConfigs };\n\n this.serverApplication.get(\"/health\", (_, res) => {\n res.status(200).json({ status: \"ok\" });\n });\n this.registerEndpoint(\"health\", \"/health\");\n\n for (const plugin of Object.values(this.config.plugins)) {\n if (EXCLUDED_PLUGINS.includes(plugin.name)) continue;\n\n if (plugin?.injectRoutes && typeof plugin.injectRoutes === \"function\") {\n const router = express.Router();\n\n plugin.injectRoutes(router);\n\n const basePath = `/api/${plugin.name}`;\n this.serverApplication.use(basePath, router);\n\n endpoints[plugin.name] = plugin.getEndpoints();\n\n // Collect paths that should skip body parsing\n if (\n plugin.getSkipBodyParsingPaths &&\n typeof plugin.getSkipBodyParsingPaths === \"function\"\n ) {\n for (const p of plugin.getSkipBodyParsingPaths()) {\n this.rawBodyPaths.add(p);\n }\n }\n }\n\n if (typeof plugin.clientConfig === \"function\") {\n try {\n const raw = plugin.clientConfig();\n if (raw != null) {\n const sanitized = sanitizeClientConfig(plugin.name, raw);\n if (Object.keys(sanitized).length > 0) {\n pluginConfigs[plugin.name] = sanitized;\n }\n }\n } catch (error) {\n logger.error(\n \"Plugin '%s' clientConfig() failed, skipping its config: %O\",\n plugin.name,\n error,\n );\n }\n }\n }\n\n return { endpoints, pluginConfigs };\n }\n\n /**\n * Setup frontend serving based on environment:\n * - If staticPath is explicitly provided: use static server\n * - Dev mode (no staticPath): Vite for HMR\n * - Production (no staticPath): Static files auto-detected\n */\n private async setupFrontend(\n endpoints: PluginEndpoints,\n pluginConfigs: PluginClientConfigs,\n ) {\n const isDev = process.env.NODE_ENV === \"development\";\n const hasExplicitStaticPath = this.config.staticPath !== undefined;\n\n // explict static path provided\n if (hasExplicitStaticPath) {\n const staticServer = new StaticServer(\n this.serverApplication,\n this.config.staticPath as string,\n endpoints,\n pluginConfigs,\n );\n staticServer.setup();\n return;\n }\n\n // auto-detection based on environment\n if (isDev) {\n this.viteDevServer = new ViteDevServer(\n this.serverApplication,\n endpoints,\n pluginConfigs,\n );\n await this.viteDevServer.setup();\n return;\n }\n\n // auto-detection based on static path\n const staticPath = ServerPlugin.findStaticPath();\n if (staticPath) {\n const staticServer = new StaticServer(\n this.serverApplication,\n staticPath,\n endpoints,\n pluginConfigs,\n );\n\n staticServer.setup();\n }\n }\n\n private static findStaticPath() {\n const staticPaths = [\"dist\", \"client/dist\", \"build\", \"public\", \"out\"];\n const cwd = process.cwd();\n for (const p of staticPaths) {\n const fullPath = path.resolve(cwd, p);\n if (fs.existsSync(path.resolve(fullPath, \"index.html\"))) {\n logger.debug(\"Static files: serving from %s\", fullPath);\n return fullPath;\n }\n }\n return undefined;\n }\n\n private logStartupInfo() {\n const isDev = process.env.NODE_ENV === \"development\";\n const hasExplicitStaticPath = this.config.staticPath !== undefined;\n const port = this.config.port ?? ServerPlugin.DEFAULT_CONFIG.port;\n const host = this.config.host ?? ServerPlugin.DEFAULT_CONFIG.host;\n\n logger.info(\"Server running on http://%s:%d\", host, port);\n\n if (hasExplicitStaticPath) {\n logger.info(\"Mode: static (%s)\", this.config.staticPath);\n } else if (isDev) {\n logger.info(\"Mode: development (Vite HMR)\");\n } else {\n logger.info(\"Mode: production (static)\");\n }\n\n const remoteServerController = this.remoteTunnelController;\n if (!remoteServerController) {\n logger.debug(\"Remote tunnel: disabled (controller not initialized)\");\n } else {\n logger.debug(\n \"Remote tunnel: %s; %s\",\n remoteServerController.isAllowedByEnv() ? \"allowed\" : \"blocked\",\n remoteServerController.isActive() ? \"active\" : \"inactive\",\n );\n }\n }\n\n private async _gracefulShutdown() {\n logger.info(\"Starting graceful shutdown...\");\n\n if (this.viteDevServer) {\n await this.viteDevServer.close();\n }\n\n if (this.remoteTunnelController) {\n this.remoteTunnelController.cleanup();\n }\n\n // 1. abort active operations from plugins\n if (this.config.plugins) {\n for (const plugin of Object.values(this.config.plugins)) {\n if (plugin.abortActiveOperations) {\n try {\n plugin.abortActiveOperations();\n } catch (err) {\n logger.error(\n \"Error aborting operations for plugin %s: %O\",\n plugin.name,\n err,\n );\n }\n }\n }\n }\n\n // 2. close the server\n if (this.server) {\n this.server.close(() => {\n logger.debug(\"Server closed gracefully\");\n process.exit(0);\n });\n\n // 3. timeout to force shutdown after 15 seconds\n setTimeout(() => {\n logger.debug(\"Force shutdown after timeout\");\n process.exit(1);\n }, 15000);\n } else {\n process.exit(0);\n }\n }\n\n /**\n * Returns the public exports for the server plugin.\n * Exposes server management methods.\n */\n exports() {\n const self = this;\n return {\n /** Extend the server with custom routes or middleware */\n extend(fn: (app: express.Application) => void) {\n self.extend(fn);\n return this;\n },\n /** Get the underlying HTTP server instance */\n getServer: this.getServer,\n /** Get the server configuration */\n getConfig: this.getConfig,\n /** @deprecated Server is now started automatically by createApp. */\n start() {\n throw new ServerError(\n \"server.start() has been removed. Use the onPluginsReady callback instead:\\n\\n\" +\n \" createApp({\\n\" +\n \" plugins: [server(), ...],\\n\" +\n \" onPluginsReady(appkit) {\\n\" +\n \" appkit.server.extend(...);\\n\" +\n \" },\\n\" +\n \" });\\n\\n\" +\n \"Run `npx appkit codemod on-plugins-ready --write` to auto-migrate.\",\n );\n },\n };\n }\n}\n\nconst EXCLUDED_PLUGINS: string[] = [ServerPlugin.manifest.name];\n\n/**\n * @internal\n */\nexport const server = toPlugin(ServerPlugin);\n// Export manifest and types\n"],"mappings":"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"}
|
|
@@ -11,11 +11,6 @@ var manifest_default = {
|
|
|
11
11
|
config: { "schema": {
|
|
12
12
|
"type": "object",
|
|
13
13
|
"properties": {
|
|
14
|
-
"autoStart": {
|
|
15
|
-
"type": "boolean",
|
|
16
|
-
"default": true,
|
|
17
|
-
"description": "Automatically start the server on plugin setup"
|
|
18
|
-
},
|
|
19
14
|
"host": {
|
|
20
15
|
"type": "string",
|
|
21
16
|
"default": "0.0.0.0",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/plugins/server/types.ts"],"mappings":";;;;;;UAGiB,YAAA,SAAqB,gBAAA;EACpC,IAAA;EACA,OAAA,GAAU,MAAA,SAAe,MAAA;EACzB,UAAA;EACA,
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/plugins/server/types.ts"],"mappings":";;;;;;UAGiB,YAAA,SAAqB,gBAAA;EACpC,IAAA;EACA,OAAA,GAAU,MAAA,SAAe,MAAA;EACzB,UAAA;EACA,IAAA;AAAA"}
|
|
@@ -35,11 +35,11 @@ declare function getPluginManifest(plugin: PluginConstructor): PluginManifest;
|
|
|
35
35
|
declare function getResourceRequirements(plugin: PluginConstructor): {
|
|
36
36
|
required: boolean;
|
|
37
37
|
description: string;
|
|
38
|
-
type: ResourceType;
|
|
39
|
-
permission: ResourcePermission;
|
|
40
38
|
fields: Record<string, ResourceFieldEntry>;
|
|
39
|
+
type: ResourceType;
|
|
41
40
|
alias: string;
|
|
42
41
|
resourceKey: string;
|
|
42
|
+
permission: ResourcePermission;
|
|
43
43
|
}[];
|
|
44
44
|
//#endregion
|
|
45
45
|
export { getPluginManifest, getResourceRequirements };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest-loader.d.ts","names":[],"sources":["../../src/registry/manifest-loader.ts"],"mappings":";;;;;;;;;;;AA4DA;;;;iBAAgB,iBAAA,CAAkB,MAAA,EAAQ,iBAAA,GAAoB,cAAA;;;;;AA4F9D;;;;;;;;;;;;;;iBAAgB,uBAAA,CAAwB,MAAA,EAAQ,iBAAA"}
|
|
1
|
+
{"version":3,"file":"manifest-loader.d.ts","names":[],"sources":["../../src/registry/manifest-loader.ts"],"mappings":";;;;;;;;;;;AA4DA;;;;iBAAgB,iBAAA,CAAkB,MAAA,EAAQ,iBAAA,GAAoB,cAAA;;;;;AA4F9D;;;;;;;;;;;;;;iBAAgB,uBAAA,CAAwB,MAAA,EAAQ,iBAAA;;;yBAAiB,kBAAA"}
|
|
@@ -5,7 +5,6 @@ Error thrown when server lifecycle operations fail. Use for server start/stop is
|
|
|
5
5
|
## Example[](#example "Direct link to Example")
|
|
6
6
|
|
|
7
7
|
```typescript
|
|
8
|
-
throw new ServerError("Cannot get server when autoStart is true");
|
|
9
8
|
throw new ServerError("Server not started");
|
|
10
9
|
|
|
11
10
|
```
|
|
@@ -158,27 +157,6 @@ Create a human-readable string representation
|
|
|
158
157
|
|
|
159
158
|
***
|
|
160
159
|
|
|
161
|
-
### autoStartConflict()[](#autostartconflict "Direct link to autoStartConflict()")
|
|
162
|
-
|
|
163
|
-
```ts
|
|
164
|
-
static autoStartConflict(operation: string): ServerError;
|
|
165
|
-
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
Create a server error for autoStart conflict
|
|
169
|
-
|
|
170
|
-
#### Parameters[](#parameters-1 "Direct link to Parameters")
|
|
171
|
-
|
|
172
|
-
| Parameter | Type |
|
|
173
|
-
| ----------- | -------- |
|
|
174
|
-
| `operation` | `string` |
|
|
175
|
-
|
|
176
|
-
#### Returns[](#returns-3 "Direct link to Returns")
|
|
177
|
-
|
|
178
|
-
`ServerError`
|
|
179
|
-
|
|
180
|
-
***
|
|
181
|
-
|
|
182
160
|
### clientDirectoryNotFound()[](#clientdirectorynotfound "Direct link to clientDirectoryNotFound()")
|
|
183
161
|
|
|
184
162
|
```ts
|
|
@@ -188,13 +166,13 @@ static clientDirectoryNotFound(searchedPaths: string[]): ServerError;
|
|
|
188
166
|
|
|
189
167
|
Create a server error for missing client directory
|
|
190
168
|
|
|
191
|
-
#### Parameters[](#parameters-
|
|
169
|
+
#### Parameters[](#parameters-1 "Direct link to Parameters")
|
|
192
170
|
|
|
193
171
|
| Parameter | Type |
|
|
194
172
|
| --------------- | ----------- |
|
|
195
173
|
| `searchedPaths` | `string`\[] |
|
|
196
174
|
|
|
197
|
-
#### Returns[](#returns-
|
|
175
|
+
#### Returns[](#returns-3 "Direct link to Returns")
|
|
198
176
|
|
|
199
177
|
`ServerError`
|
|
200
178
|
|
|
@@ -209,7 +187,7 @@ static notStarted(): ServerError;
|
|
|
209
187
|
|
|
210
188
|
Create a server error for server not started
|
|
211
189
|
|
|
212
|
-
#### Returns[](#returns-
|
|
190
|
+
#### Returns[](#returns-4 "Direct link to Returns")
|
|
213
191
|
|
|
214
192
|
`ServerError`
|
|
215
193
|
|
|
@@ -224,6 +202,6 @@ static viteNotInitialized(): ServerError;
|
|
|
224
202
|
|
|
225
203
|
Create a server error for Vite dev server not initialized
|
|
226
204
|
|
|
227
|
-
#### Returns[](#returns-
|
|
205
|
+
#### Returns[](#returns-5 "Direct link to Returns")
|
|
228
206
|
|
|
229
207
|
`ServerError`
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
function createApp<T>(config: {
|
|
5
5
|
cache?: CacheConfig;
|
|
6
6
|
client?: WorkspaceClient;
|
|
7
|
+
onPluginsReady?: (appkit: PluginMap<T>) => void | Promise<void>;
|
|
7
8
|
plugins?: T;
|
|
8
9
|
telemetry?: TelemetryConfig;
|
|
9
10
|
}): Promise<PluginMap<T>>;
|
|
@@ -12,7 +13,7 @@ function createApp<T>(config: {
|
|
|
12
13
|
|
|
13
14
|
Bootstraps AppKit with the provided configuration.
|
|
14
15
|
|
|
15
|
-
Initializes telemetry, cache, and service context, then registers plugins in phase order (core, normal, deferred) and awaits their setup. The returned object maps each plugin name to its `exports()` API, with an `asUser(req)` method for user-scoped execution.
|
|
16
|
+
Initializes telemetry, cache, and service context, then registers plugins in phase order (core, normal, deferred) and awaits their setup. If a `onPluginsReady` 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.
|
|
16
17
|
|
|
17
18
|
## Type Parameters[](#type-parameters "Direct link to Type Parameters")
|
|
18
19
|
|
|
@@ -22,13 +23,14 @@ Initializes telemetry, cache, and service context, then registers plugins in pha
|
|
|
22
23
|
|
|
23
24
|
## Parameters[](#parameters "Direct link to Parameters")
|
|
24
25
|
|
|
25
|
-
| Parameter
|
|
26
|
-
|
|
|
27
|
-
| `config`
|
|
28
|
-
| `config.cache?`
|
|
29
|
-
| `config.client?`
|
|
30
|
-
| `config.
|
|
31
|
-
| `config.
|
|
26
|
+
| Parameter | Type |
|
|
27
|
+
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
28
|
+
| `config` | { `cache?`: [`CacheConfig`](./docs/api/appkit/Interface.CacheConfig.md); `client?`: `WorkspaceClient`; `onPluginsReady?`: (`appkit`: `PluginMap`<`T`>) => `void` \| `Promise`<`void`>; `plugins?`: `T`; `telemetry?`: [`TelemetryConfig`](./docs/api/appkit/Interface.TelemetryConfig.md); } |
|
|
29
|
+
| `config.cache?` | [`CacheConfig`](./docs/api/appkit/Interface.CacheConfig.md) |
|
|
30
|
+
| `config.client?` | `WorkspaceClient` |
|
|
31
|
+
| `config.onPluginsReady?` | (`appkit`: `PluginMap`<`T`>) => `void` \| `Promise`<`void`> |
|
|
32
|
+
| `config.plugins?` | `T` |
|
|
33
|
+
| `config.telemetry?` | [`TelemetryConfig`](./docs/api/appkit/Interface.TelemetryConfig.md) |
|
|
32
34
|
|
|
33
35
|
## Returns[](#returns "Direct link to Returns")
|
|
34
36
|
|
|
@@ -50,13 +52,13 @@ await createApp({
|
|
|
50
52
|
```ts
|
|
51
53
|
import { createApp, server, analytics } from "@databricks/appkit";
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
plugins: [server(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
await createApp({
|
|
56
|
+
plugins: [server(), analytics({})],
|
|
57
|
+
onPluginsReady(appkit) {
|
|
58
|
+
appkit.server.extend((app) => {
|
|
59
|
+
app.get("/custom", (_req, res) => res.json({ ok: true }));
|
|
60
|
+
});
|
|
61
|
+
},
|
|
59
62
|
});
|
|
60
|
-
await appkit.server.start();
|
|
61
63
|
|
|
62
64
|
```
|
package/docs/plugins/server.md
CHANGED
|
@@ -40,22 +40,39 @@ await createApp({
|
|
|
40
40
|
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
##
|
|
43
|
+
## Custom routes example[](#custom-routes-example "Direct link to Custom routes example")
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
Use the `onPluginsReady` callback to extend Express with custom routes before the server starts:
|
|
46
46
|
|
|
47
47
|
```ts
|
|
48
48
|
import { createApp, server } from "@databricks/appkit";
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
plugins: [server(
|
|
50
|
+
await createApp({
|
|
51
|
+
plugins: [server()],
|
|
52
|
+
onPluginsReady(appkit) {
|
|
53
|
+
appkit.server.extend((app) => {
|
|
54
|
+
app.get("/custom", (_req, res) => res.json({ ok: true }));
|
|
55
|
+
});
|
|
56
|
+
},
|
|
52
57
|
});
|
|
53
58
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The `onPluginsReady` callback also supports async operations:
|
|
57
62
|
|
|
58
|
-
|
|
63
|
+
```ts
|
|
64
|
+
await createApp({
|
|
65
|
+
plugins: [server()],
|
|
66
|
+
async onPluginsReady(appkit) {
|
|
67
|
+
const pool = await initializeDatabase();
|
|
68
|
+
appkit.server.extend((app) => {
|
|
69
|
+
app.get("/data", async (_req, res) => {
|
|
70
|
+
const result = await pool.query("SELECT 1");
|
|
71
|
+
res.json(result);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
});
|
|
59
76
|
|
|
60
77
|
```
|
|
61
78
|
|
|
@@ -69,7 +86,6 @@ await createApp({
|
|
|
69
86
|
server({
|
|
70
87
|
port: 8000, // default: Number(process.env.DATABRICKS_APP_PORT) || 8000
|
|
71
88
|
host: "0.0.0.0", // default: process.env.FLASK_RUN_HOST || "0.0.0.0"
|
|
72
|
-
autoStart: true, // default: true
|
|
73
89
|
staticPath: "dist", // optional: force a specific static directory
|
|
74
90
|
}),
|
|
75
91
|
],
|