@databricks/appkit 0.20.3 → 0.22.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/CLAUDE.md +1 -0
- package/README.md +3 -20
- package/dist/appkit/package.js +1 -1
- package/dist/cli/commands/setup.js +2 -2
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/connectors/genie/client.js +50 -0
- package/dist/connectors/genie/client.js.map +1 -1
- package/dist/plugin/plugin.d.ts +47 -1
- package/dist/plugin/plugin.d.ts.map +1 -1
- package/dist/plugin/plugin.js +51 -2
- package/dist/plugin/plugin.js.map +1 -1
- package/dist/plugins/files/plugin.d.ts +1 -0
- package/dist/plugins/files/plugin.d.ts.map +1 -1
- package/dist/plugins/files/plugin.js +3 -0
- package/dist/plugins/files/plugin.js.map +1 -1
- package/dist/plugins/genie/genie.d.ts +1 -0
- package/dist/plugins/genie/genie.d.ts.map +1 -1
- package/dist/plugins/genie/genie.js +42 -3
- package/dist/plugins/genie/genie.js.map +1 -1
- package/dist/plugins/server/base-server.js +4 -2
- package/dist/plugins/server/base-server.js.map +1 -1
- package/dist/plugins/server/client-config-sanitizer.js +184 -0
- package/dist/plugins/server/client-config-sanitizer.js.map +1 -0
- package/dist/plugins/server/index.d.ts +2 -1
- package/dist/plugins/server/index.d.ts.map +1 -1
- package/dist/plugins/server/index.js +27 -9
- package/dist/plugins/server/index.js.map +1 -1
- package/dist/plugins/server/remote-tunnel/denied.html +68 -0
- package/dist/plugins/server/remote-tunnel/index.html +165 -0
- package/dist/plugins/server/remote-tunnel/remote-tunnel-manager.js +2 -1
- package/dist/plugins/server/remote-tunnel/remote-tunnel-manager.js.map +1 -1
- package/dist/plugins/server/remote-tunnel/wait.html +158 -0
- package/dist/plugins/server/static-server.js +2 -2
- package/dist/plugins/server/static-server.js.map +1 -1
- package/dist/plugins/server/utils.js +28 -5
- package/dist/plugins/server/utils.js.map +1 -1
- package/dist/plugins/server/vite-dev-server.js +2 -2
- package/dist/plugins/server/vite-dev-server.js.map +1 -1
- package/dist/shared/src/plugin.d.ts +1 -0
- package/dist/shared/src/plugin.d.ts.map +1 -1
- package/dist/type-generator/index.js +10 -0
- package/dist/type-generator/index.js.map +1 -1
- package/docs/api/appkit/Class.Plugin.md +75 -17
- package/docs/app-management.md +1 -1
- package/docs/architecture.md +1 -1
- package/docs/development/ai-assisted-development.md +2 -2
- package/docs/development/local-development.md +1 -1
- package/docs/development/remote-bridge.md +1 -1
- package/docs/development/templates.md +93 -0
- package/docs/development.md +1 -1
- package/docs/plugins/caching.md +3 -1
- package/docs/plugins/execution-context.md +1 -1
- package/docs/plugins/lakebase.md +1 -1
- package/docs.md +2 -2
- package/llms.txt +1 -0
- package/package.json +37 -36
- package/sbom.cdx.json +1 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<title>Waiting for Approval</title>
|
|
6
|
+
<meta http-equiv="refresh" content="2">
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root">
|
|
10
|
+
<style>
|
|
11
|
+
body {
|
|
12
|
+
background: #1b1b1d;
|
|
13
|
+
height: 100vh;
|
|
14
|
+
margin: 0;
|
|
15
|
+
display: flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
justify-content: center;
|
|
18
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
|
|
19
|
+
"Helvetica", "Arial", sans-serif;
|
|
20
|
+
}
|
|
21
|
+
.loader-container {
|
|
22
|
+
display: flex;
|
|
23
|
+
flex-direction: column;
|
|
24
|
+
align-items: center;
|
|
25
|
+
padding: 50px 70px;
|
|
26
|
+
}
|
|
27
|
+
.logo {
|
|
28
|
+
width: 64px;
|
|
29
|
+
height: 64px;
|
|
30
|
+
margin-bottom: 32px;
|
|
31
|
+
position: relative;
|
|
32
|
+
}
|
|
33
|
+
.logo svg {
|
|
34
|
+
width: 100%;
|
|
35
|
+
height: 100%;
|
|
36
|
+
}
|
|
37
|
+
.spinner {
|
|
38
|
+
width: 32px;
|
|
39
|
+
height: 32px;
|
|
40
|
+
border: 3px solid rgba(255, 255, 255, 0.1);
|
|
41
|
+
border-top: 3px solid #ff3621;
|
|
42
|
+
border-radius: 50%;
|
|
43
|
+
animation: spin 0.8s linear infinite;
|
|
44
|
+
margin-bottom: 32px;
|
|
45
|
+
}
|
|
46
|
+
@keyframes spin {
|
|
47
|
+
0% {
|
|
48
|
+
transform: rotate(0deg);
|
|
49
|
+
}
|
|
50
|
+
100% {
|
|
51
|
+
transform: rotate(360deg);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
.loading-text {
|
|
55
|
+
font-size: 1.125rem;
|
|
56
|
+
color: #ffffff;
|
|
57
|
+
letter-spacing: 0.02em;
|
|
58
|
+
font-weight: 400;
|
|
59
|
+
text-align: center;
|
|
60
|
+
margin-top: 0;
|
|
61
|
+
opacity: 0.9;
|
|
62
|
+
}
|
|
63
|
+
.sub-text {
|
|
64
|
+
font-size: 0.875rem;
|
|
65
|
+
color: rgba(255, 255, 255, 0.6);
|
|
66
|
+
margin-top: 12px;
|
|
67
|
+
text-align: center;
|
|
68
|
+
}
|
|
69
|
+
.tunnel-id {
|
|
70
|
+
font-size: 0.75rem;
|
|
71
|
+
color: rgba(255, 255, 255, 0.4);
|
|
72
|
+
margin-top: 8px;
|
|
73
|
+
letter-spacing: 0.05em;
|
|
74
|
+
font-family: monospace;
|
|
75
|
+
}
|
|
76
|
+
.databricks-brand {
|
|
77
|
+
font-size: 0.875rem;
|
|
78
|
+
color: rgba(255, 255, 255, 0.5);
|
|
79
|
+
margin-top: 24px;
|
|
80
|
+
letter-spacing: 0.05em;
|
|
81
|
+
text-transform: uppercase;
|
|
82
|
+
font-weight: 500;
|
|
83
|
+
}
|
|
84
|
+
.dots {
|
|
85
|
+
display: inline-block;
|
|
86
|
+
animation: dots 1.5s steps(4, end) infinite;
|
|
87
|
+
}
|
|
88
|
+
@keyframes dots {
|
|
89
|
+
0%,
|
|
90
|
+
20% {
|
|
91
|
+
content: ".";
|
|
92
|
+
}
|
|
93
|
+
40% {
|
|
94
|
+
content: "..";
|
|
95
|
+
}
|
|
96
|
+
60%,
|
|
97
|
+
100% {
|
|
98
|
+
content: "...";
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
</style>
|
|
102
|
+
<div class="loader-container">
|
|
103
|
+
<div class="logo">
|
|
104
|
+
<svg
|
|
105
|
+
version="1.1"
|
|
106
|
+
id="Layer_1"
|
|
107
|
+
xmlns:x="ns_extend;"
|
|
108
|
+
xmlns:i="ns_ai;"
|
|
109
|
+
xmlns:graph="ns_graphs;"
|
|
110
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
111
|
+
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
112
|
+
x="0px"
|
|
113
|
+
y="0px"
|
|
114
|
+
viewBox="0 0 40.1 42"
|
|
115
|
+
style="enable-background: new 0 0 40.1 42"
|
|
116
|
+
xml:space="preserve"
|
|
117
|
+
>
|
|
118
|
+
<style type="text/css">
|
|
119
|
+
.st0 {
|
|
120
|
+
fill: #ff3621;
|
|
121
|
+
}
|
|
122
|
+
</style>
|
|
123
|
+
<metadata>
|
|
124
|
+
<sfw xmlns="ns_sfw;">
|
|
125
|
+
<slices></slices>
|
|
126
|
+
<sliceSourceBounds
|
|
127
|
+
bottomLeftOrigin="true"
|
|
128
|
+
height="42"
|
|
129
|
+
width="40.1"
|
|
130
|
+
x="-69.1"
|
|
131
|
+
y="-10.5"
|
|
132
|
+
></sliceSourceBounds>
|
|
133
|
+
</sfw>
|
|
134
|
+
</metadata>
|
|
135
|
+
<g>
|
|
136
|
+
<path
|
|
137
|
+
class="st0"
|
|
138
|
+
d="M40.1,31.1v-7.4l-0.8-0.5L20.1,33.7l-18.2-10l0-4.3l18.2,9.9l20.1-10.9v-7.3l-0.8-0.5L20.1,21.2L2.6,11.6
|
|
139
|
+
L20.1,2l14.1,7.7l1.1-0.6V8.3L20.1,0L0,10.9V12L20.1,23l18.2-10v4.4l-18.2,10L0.8,16.8L0,17.3v7.4l20.1,10.9l18.2-9.9v4.3l-18.2,10
|
|
140
|
+
L0.8,29.5L0,30v1.1L20.1,42L40.1,31.1z"
|
|
141
|
+
></path>
|
|
142
|
+
</g>
|
|
143
|
+
</svg>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
<div class="spinner"></div>
|
|
147
|
+
<div class="loading-text">
|
|
148
|
+
Waiting for approval<span class="dots">...</span>
|
|
149
|
+
</div>
|
|
150
|
+
<div class="sub-text">
|
|
151
|
+
Requesting access to tunnel from owner
|
|
152
|
+
</div>
|
|
153
|
+
<div class="tunnel-id">Tunnel ID: {{tunnelId}}</div>
|
|
154
|
+
<div class="databricks-brand">Databricks</div>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
</body>
|
|
158
|
+
</html>
|
|
@@ -18,8 +18,8 @@ import express from "express";
|
|
|
18
18
|
*/
|
|
19
19
|
var StaticServer = class extends BaseServer {
|
|
20
20
|
staticPath;
|
|
21
|
-
constructor(app, staticPath, endpoints = {}) {
|
|
22
|
-
super(app, endpoints);
|
|
21
|
+
constructor(app, staticPath, endpoints = {}, pluginConfigs = {}) {
|
|
22
|
+
super(app, endpoints, pluginConfigs);
|
|
23
23
|
this.staticPath = staticPath;
|
|
24
24
|
}
|
|
25
25
|
/** Setup the static server. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"static-server.js","names":["expressStatic"],"sources":["../../../src/plugins/server/static-server.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type express from \"express\";\nimport expressStatic from \"express\";\nimport { BaseServer } from \"./base-server\";\nimport type { PluginEndpoints } from \"./utils\";\n\n/**\n * Static server for the AppKit.\n *\n * Serves pre-built static files in production mode. Handles SPA routing\n * by serving index.html for non-API routes and injects runtime configuration.\n *\n * @example\n * ```ts\n * const staticServer = new StaticServer(app, staticPath, endpoints);\n * staticServer.setup();\n * ```\n */\nexport class StaticServer extends BaseServer {\n private staticPath: string;\n\n constructor(\n app: express.Application,\n staticPath: string,\n endpoints: PluginEndpoints = {},\n ) {\n super(app, endpoints);\n this.staticPath = staticPath;\n }\n\n /** Setup the static server. */\n setup() {\n this.app.use(\n expressStatic.static(this.staticPath, {\n index: false,\n }),\n );\n\n this.app.get(\"*\", (req, res, next) => {\n if (req.path.startsWith(\"/api\") || req.path.startsWith(\"/query\")) {\n return next();\n }\n this.serveIndex(res);\n });\n }\n\n /** Serve the index.html file. */\n private serveIndex(res: express.Response) {\n const indexPath = path.join(this.staticPath, \"index.html\");\n\n if (!fs.existsSync(indexPath)) {\n res.status(404).send(\"index.html not found\");\n return;\n }\n\n let html = fs.readFileSync(indexPath, \"utf-8\");\n html = html.replace(\"<body>\", `<body>${this.getConfigScript()}`);\n res.send(html);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAmBA,IAAa,eAAb,cAAkC,WAAW;CAC3C,AAAQ;CAER,YACE,KACA,YACA,YAA6B,EAAE,EAC/B;AACA,QAAM,KAAK,
|
|
1
|
+
{"version":3,"file":"static-server.js","names":["expressStatic"],"sources":["../../../src/plugins/server/static-server.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type express from \"express\";\nimport expressStatic from \"express\";\nimport { BaseServer } from \"./base-server\";\nimport type { PluginClientConfigs, PluginEndpoints } from \"./utils\";\n\n/**\n * Static server for the AppKit.\n *\n * Serves pre-built static files in production mode. Handles SPA routing\n * by serving index.html for non-API routes and injects runtime configuration.\n *\n * @example\n * ```ts\n * const staticServer = new StaticServer(app, staticPath, endpoints);\n * staticServer.setup();\n * ```\n */\nexport class StaticServer extends BaseServer {\n private staticPath: string;\n\n constructor(\n app: express.Application,\n staticPath: string,\n endpoints: PluginEndpoints = {},\n pluginConfigs: PluginClientConfigs = {},\n ) {\n super(app, endpoints, pluginConfigs);\n this.staticPath = staticPath;\n }\n\n /** Setup the static server. */\n setup() {\n this.app.use(\n expressStatic.static(this.staticPath, {\n index: false,\n }),\n );\n\n this.app.get(\"*\", (req, res, next) => {\n if (req.path.startsWith(\"/api\") || req.path.startsWith(\"/query\")) {\n return next();\n }\n this.serveIndex(res);\n });\n }\n\n /** Serve the index.html file. */\n private serveIndex(res: express.Response) {\n const indexPath = path.join(this.staticPath, \"index.html\");\n\n if (!fs.existsSync(indexPath)) {\n res.status(404).send(\"index.html not found\");\n return;\n }\n\n let html = fs.readFileSync(indexPath, \"utf-8\");\n html = html.replace(\"<body>\", `<body>${this.getConfigScript()}`);\n res.send(html);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAmBA,IAAa,eAAb,cAAkC,WAAW;CAC3C,AAAQ;CAER,YACE,KACA,YACA,YAA6B,EAAE,EAC/B,gBAAqC,EAAE,EACvC;AACA,QAAM,KAAK,WAAW,cAAc;AACpC,OAAK,aAAa;;;CAIpB,QAAQ;AACN,OAAK,IAAI,IACPA,QAAc,OAAO,KAAK,YAAY,EACpC,OAAO,OACR,CAAC,CACH;AAED,OAAK,IAAI,IAAI,MAAM,KAAK,KAAK,SAAS;AACpC,OAAI,IAAI,KAAK,WAAW,OAAO,IAAI,IAAI,KAAK,WAAW,SAAS,CAC9D,QAAO,MAAM;AAEf,QAAK,WAAW,IAAI;IACpB;;;CAIJ,AAAQ,WAAW,KAAuB;EACxC,MAAM,YAAY,KAAK,KAAK,KAAK,YAAY,aAAa;AAE1D,MAAI,CAAC,GAAG,WAAW,UAAU,EAAE;AAC7B,OAAI,OAAO,IAAI,CAAC,KAAK,uBAAuB;AAC5C;;EAGF,IAAI,OAAO,GAAG,aAAa,WAAW,QAAQ;AAC9C,SAAO,KAAK,QAAQ,UAAU,SAAS,KAAK,iBAAiB,GAAG;AAChE,MAAI,KAAK,KAAK"}
|
|
@@ -77,22 +77,45 @@ function getQueries(configFolder) {
|
|
|
77
77
|
if (!fs.existsSync(queriesFolder)) return {};
|
|
78
78
|
return Object.fromEntries(fs.readdirSync(queriesFolder).filter((f) => path.extname(f) === ".sql").map((f) => [path.basename(f, ".sql"), path.basename(f, ".sql")]));
|
|
79
79
|
}
|
|
80
|
-
|
|
80
|
+
const APPKIT_CONFIG_SCRIPT_ID = "__appkit__";
|
|
81
|
+
const EMPTY_RUNTIME_CONFIG_JSON = JSON.stringify({
|
|
82
|
+
appName: "",
|
|
83
|
+
queries: {},
|
|
84
|
+
endpoints: {},
|
|
85
|
+
plugins: {}
|
|
86
|
+
});
|
|
87
|
+
const JSON_SCRIPT_ESCAPE_MAP = {
|
|
88
|
+
"<": "\\u003c",
|
|
89
|
+
">": "\\u003e",
|
|
90
|
+
"&": "\\u0026",
|
|
91
|
+
"\u2028": "\\u2028",
|
|
92
|
+
"\u2029": "\\u2029"
|
|
93
|
+
};
|
|
94
|
+
function getRuntimeConfig(endpoints = {}, pluginConfigs = {}) {
|
|
81
95
|
const configFolder = path.join(process.cwd(), "config");
|
|
82
96
|
return {
|
|
83
97
|
appName: process.env.DATABRICKS_APP_NAME || "",
|
|
84
98
|
queries: getQueries(configFolder),
|
|
85
|
-
endpoints
|
|
99
|
+
endpoints,
|
|
100
|
+
plugins: pluginConfigs
|
|
86
101
|
};
|
|
87
102
|
}
|
|
88
|
-
function getConfigScript(endpoints = {}) {
|
|
89
|
-
const config = getRuntimeConfig(endpoints);
|
|
103
|
+
function getConfigScript(endpoints = {}, pluginConfigs = {}) {
|
|
90
104
|
return `
|
|
105
|
+
<script id="${APPKIT_CONFIG_SCRIPT_ID}" type="application/json">
|
|
106
|
+
${serializeRuntimeConfig(getRuntimeConfig(endpoints, pluginConfigs))}
|
|
107
|
+
<\/script>
|
|
91
108
|
<script>
|
|
92
|
-
window.
|
|
109
|
+
window.__appkit__ = JSON.parse(
|
|
110
|
+
document.getElementById("${APPKIT_CONFIG_SCRIPT_ID}")?.textContent ||
|
|
111
|
+
'${EMPTY_RUNTIME_CONFIG_JSON}',
|
|
112
|
+
);
|
|
93
113
|
<\/script>
|
|
94
114
|
`;
|
|
95
115
|
}
|
|
116
|
+
function serializeRuntimeConfig(config) {
|
|
117
|
+
return JSON.stringify(config).replace(/[<>&\u2028\u2029]/g, (char) => JSON_SCRIPT_ESCAPE_MAP[char] ?? char);
|
|
118
|
+
}
|
|
96
119
|
|
|
97
120
|
//#endregion
|
|
98
121
|
export { generateTunnelIdFromEmail, getConfigScript, getRoutes, parseCookies, printRoutes };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","names":[],"sources":["../../../src/plugins/server/utils.ts"],"sourcesContent":["import crypto from \"node:crypto\";\nimport fs from \"node:fs\";\nimport type http from \"node:http\";\nimport path from \"node:path\";\nimport pc from \"picocolors\";\n\nexport function parseCookies(\n req: http.IncomingMessage,\n): Record<string, string> {\n const cookieHeader = req.headers.cookie;\n if (!cookieHeader) return {};\n\n // Fast path: if there's no semicolon, there's only one cookie\n const semicolonIndex = cookieHeader.indexOf(\";\");\n if (semicolonIndex === -1) {\n const eqIndex = cookieHeader.indexOf(\"=\");\n if (eqIndex === -1) return {};\n return {\n [cookieHeader.slice(0, eqIndex).trim()]: cookieHeader.slice(eqIndex + 1),\n };\n }\n\n // Multiple cookies: parse them all\n const cookies: Record<string, string> = {};\n const parts = cookieHeader.split(\";\");\n for (let i = 0; i < parts.length; i++) {\n const eqIndex = parts[i].indexOf(\"=\");\n if (eqIndex !== -1) {\n const key = parts[i].slice(0, eqIndex).trim();\n const value = parts[i].slice(eqIndex + 1);\n cookies[key] = value;\n }\n }\n return cookies;\n}\n\nexport function generateTunnelIdFromEmail(email?: string): string | undefined {\n if (!email) return undefined;\n\n const tunnelId = crypto\n .createHash(\"sha256\")\n .update(email)\n .digest(\"base64url\")\n .slice(0, 8);\n\n return tunnelId;\n}\n\nexport function getRoutes(stack: unknown[], basePath = \"\") {\n const routes: Array<{ path: string; methods: string[] }> = [];\n\n stack.forEach((layer: any) => {\n if (layer.route) {\n // normal route\n const path = basePath + layer.route.path;\n const methods = Object.keys(layer.route.methods).map((m) =>\n m.toUpperCase(),\n );\n routes.push({ path, methods });\n } else if (layer.name === \"router\" && layer.handle.stack) {\n // nested router\n const nestedBase =\n basePath +\n layer.regexp.source\n .replace(\"^\\\\\", \"\")\n .replace(\"\\\\/?(?=\\\\/|$)\", \"\")\n .replace(/\\\\\\//g, \"/\") // convert escaped slashes\n .replace(/\\$$/, \"\") || \"\";\n routes.push(...getRoutes(layer.handle.stack, nestedBase));\n }\n });\n\n return routes;\n}\n\nconst METHOD_COLORS: Record<string, (s: string) => string> = {\n GET: pc.green,\n POST: pc.blue,\n PUT: pc.yellow,\n PATCH: pc.yellow,\n DELETE: pc.red,\n HEAD: pc.magenta,\n OPTIONS: pc.magenta,\n};\n\nexport function printRoutes(\n routes: Array<{ path: string; methods: string[] }>,\n) {\n if (routes.length === 0) return;\n\n const rows = routes\n .flatMap((r) => r.methods.map((m) => ({ method: m, path: r.path })))\n .sort(\n (a, b) =>\n a.method.localeCompare(b.method) || a.path.localeCompare(b.path),\n );\n\n const maxMethodLen = Math.max(...rows.map((r) => r.method.length));\n const separator = pc.dim(\"─\".repeat(50));\n\n const colorizeParams = (p: string) =>\n p.replace(/(:[a-zA-Z_]\\w*)/g, (match) => pc.cyan(match));\n\n console.log(\"\");\n console.log(\n ` ${pc.bold(\"Registered Routes\")} ${pc.dim(`(${rows.length})`)}`,\n );\n console.log(` ${separator}`);\n\n for (const { method, path } of rows) {\n const colorize = METHOD_COLORS[method] || pc.white;\n const methodStr = colorize(pc.bold(method.padEnd(maxMethodLen)));\n console.log(` ${methodStr} ${colorizeParams(path)}`);\n }\n\n console.log(` ${separator}`);\n console.log(\"\");\n}\n\nexport function getQueries(configFolder: string) {\n const queriesFolder = path.join(configFolder, \"queries\");\n\n if (!fs.existsSync(queriesFolder)) {\n return {};\n }\n\n return Object.fromEntries(\n fs\n .readdirSync(queriesFolder)\n .filter((f) => path.extname(f) === \".sql\")\n .map((f) => [path.basename(f, \".sql\"), path.basename(f, \".sql\")]),\n );\n}\n\
|
|
1
|
+
{"version":3,"file":"utils.js","names":[],"sources":["../../../src/plugins/server/utils.ts"],"sourcesContent":["import crypto from \"node:crypto\";\nimport fs from \"node:fs\";\nimport type http from \"node:http\";\nimport path from \"node:path\";\nimport pc from \"picocolors\";\nimport type { PluginClientConfigs, PluginEndpoints } from \"shared\";\n\nexport function parseCookies(\n req: http.IncomingMessage,\n): Record<string, string> {\n const cookieHeader = req.headers.cookie;\n if (!cookieHeader) return {};\n\n // Fast path: if there's no semicolon, there's only one cookie\n const semicolonIndex = cookieHeader.indexOf(\";\");\n if (semicolonIndex === -1) {\n const eqIndex = cookieHeader.indexOf(\"=\");\n if (eqIndex === -1) return {};\n return {\n [cookieHeader.slice(0, eqIndex).trim()]: cookieHeader.slice(eqIndex + 1),\n };\n }\n\n // Multiple cookies: parse them all\n const cookies: Record<string, string> = {};\n const parts = cookieHeader.split(\";\");\n for (let i = 0; i < parts.length; i++) {\n const eqIndex = parts[i].indexOf(\"=\");\n if (eqIndex !== -1) {\n const key = parts[i].slice(0, eqIndex).trim();\n const value = parts[i].slice(eqIndex + 1);\n cookies[key] = value;\n }\n }\n return cookies;\n}\n\nexport function generateTunnelIdFromEmail(email?: string): string | undefined {\n if (!email) return undefined;\n\n const tunnelId = crypto\n .createHash(\"sha256\")\n .update(email)\n .digest(\"base64url\")\n .slice(0, 8);\n\n return tunnelId;\n}\n\nexport function getRoutes(stack: unknown[], basePath = \"\") {\n const routes: Array<{ path: string; methods: string[] }> = [];\n\n stack.forEach((layer: any) => {\n if (layer.route) {\n // normal route\n const path = basePath + layer.route.path;\n const methods = Object.keys(layer.route.methods).map((m) =>\n m.toUpperCase(),\n );\n routes.push({ path, methods });\n } else if (layer.name === \"router\" && layer.handle.stack) {\n // nested router\n const nestedBase =\n basePath +\n layer.regexp.source\n .replace(\"^\\\\\", \"\")\n .replace(\"\\\\/?(?=\\\\/|$)\", \"\")\n .replace(/\\\\\\//g, \"/\") // convert escaped slashes\n .replace(/\\$$/, \"\") || \"\";\n routes.push(...getRoutes(layer.handle.stack, nestedBase));\n }\n });\n\n return routes;\n}\n\nconst METHOD_COLORS: Record<string, (s: string) => string> = {\n GET: pc.green,\n POST: pc.blue,\n PUT: pc.yellow,\n PATCH: pc.yellow,\n DELETE: pc.red,\n HEAD: pc.magenta,\n OPTIONS: pc.magenta,\n};\n\nexport function printRoutes(\n routes: Array<{ path: string; methods: string[] }>,\n) {\n if (routes.length === 0) return;\n\n const rows = routes\n .flatMap((r) => r.methods.map((m) => ({ method: m, path: r.path })))\n .sort(\n (a, b) =>\n a.method.localeCompare(b.method) || a.path.localeCompare(b.path),\n );\n\n const maxMethodLen = Math.max(...rows.map((r) => r.method.length));\n const separator = pc.dim(\"─\".repeat(50));\n\n const colorizeParams = (p: string) =>\n p.replace(/(:[a-zA-Z_]\\w*)/g, (match) => pc.cyan(match));\n\n console.log(\"\");\n console.log(\n ` ${pc.bold(\"Registered Routes\")} ${pc.dim(`(${rows.length})`)}`,\n );\n console.log(` ${separator}`);\n\n for (const { method, path } of rows) {\n const colorize = METHOD_COLORS[method] || pc.white;\n const methodStr = colorize(pc.bold(method.padEnd(maxMethodLen)));\n console.log(` ${methodStr} ${colorizeParams(path)}`);\n }\n\n console.log(` ${separator}`);\n console.log(\"\");\n}\n\nexport function getQueries(configFolder: string) {\n const queriesFolder = path.join(configFolder, \"queries\");\n\n if (!fs.existsSync(queriesFolder)) {\n return {};\n }\n\n return Object.fromEntries(\n fs\n .readdirSync(queriesFolder)\n .filter((f) => path.extname(f) === \".sql\")\n .map((f) => [path.basename(f, \".sql\"), path.basename(f, \".sql\")]),\n );\n}\n\nexport type { PluginClientConfigs, PluginEndpoints };\n\ninterface RuntimeConfig {\n appName: string;\n queries: Record<string, string>;\n endpoints: PluginEndpoints;\n plugins: PluginClientConfigs;\n}\n\nconst APPKIT_CONFIG_SCRIPT_ID = \"__appkit__\";\nconst EMPTY_RUNTIME_CONFIG: RuntimeConfig = {\n appName: \"\",\n queries: {},\n endpoints: {},\n plugins: {},\n};\nconst EMPTY_RUNTIME_CONFIG_JSON = JSON.stringify(EMPTY_RUNTIME_CONFIG);\nconst JSON_SCRIPT_ESCAPE_MAP: Record<string, string> = {\n \"<\": \"\\\\u003c\",\n \">\": \"\\\\u003e\",\n \"&\": \"\\\\u0026\",\n \"\\u2028\": \"\\\\u2028\",\n \"\\u2029\": \"\\\\u2029\",\n};\n\nexport function getRuntimeConfig(\n endpoints: PluginEndpoints = {},\n pluginConfigs: PluginClientConfigs = {},\n): RuntimeConfig {\n const configFolder = path.join(process.cwd(), \"config\");\n\n return {\n appName: process.env.DATABRICKS_APP_NAME || \"\",\n queries: getQueries(configFolder),\n endpoints,\n plugins: pluginConfigs,\n };\n}\n\nexport function getConfigScript(\n endpoints: PluginEndpoints = {},\n pluginConfigs: PluginClientConfigs = {},\n): string {\n const config = getRuntimeConfig(endpoints, pluginConfigs);\n\n return `\n <script id=\"${APPKIT_CONFIG_SCRIPT_ID}\" type=\"application/json\">\n ${serializeRuntimeConfig(config)}\n </script>\n <script>\n window.__appkit__ = JSON.parse(\n document.getElementById(\"${APPKIT_CONFIG_SCRIPT_ID}\")?.textContent ||\n '${EMPTY_RUNTIME_CONFIG_JSON}',\n );\n </script>\n `;\n}\n\nfunction serializeRuntimeConfig(config: RuntimeConfig): string {\n return JSON.stringify(config).replace(\n /[<>&\\u2028\\u2029]/g,\n (char) => JSON_SCRIPT_ESCAPE_MAP[char] ?? char,\n );\n}\n"],"mappings":"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"}
|
|
@@ -24,8 +24,8 @@ const logger = createLogger("server:vite");
|
|
|
24
24
|
*/
|
|
25
25
|
var ViteDevServer = class extends BaseServer {
|
|
26
26
|
vite;
|
|
27
|
-
constructor(app, endpoints = {}) {
|
|
28
|
-
super(app, endpoints);
|
|
27
|
+
constructor(app, endpoints = {}, pluginConfigs = {}) {
|
|
28
|
+
super(app, endpoints, pluginConfigs);
|
|
29
29
|
this.vite = null;
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite-dev-server.js","names":[],"sources":["../../../src/plugins/server/vite-dev-server.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type express from \"express\";\nimport type { ViteDevServer as ViteDevServerType } from \"vite\";\nimport { mergeConfigDedup } from \"@/utils\";\nimport { ServerError } from \"../../errors\";\nimport { createLogger } from \"../../logging/logger\";\nimport { appKitTypesPlugin } from \"../../type-generator/vite-plugin\";\nimport { BaseServer } from \"./base-server\";\nimport type { PluginEndpoints } from \"./utils\";\n\nconst logger = createLogger(\"server:vite\");\n\n/**\n * Vite dev server for the AppKit.\n *\n * This class is responsible for serving the Vite dev server for the development server.\n * It also handles the index.html file for the development server.\n *\n * @example\n * ```ts\n * const viteDevServer = new ViteDevServer(app, endpoints);\n * await viteDevServer.setup();\n * ```\n */\nexport class ViteDevServer extends BaseServer {\n private vite: ViteDevServerType | null;\n\n constructor(app: express.Application
|
|
1
|
+
{"version":3,"file":"vite-dev-server.js","names":[],"sources":["../../../src/plugins/server/vite-dev-server.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type express from \"express\";\nimport type { ViteDevServer as ViteDevServerType } from \"vite\";\nimport { mergeConfigDedup } from \"@/utils\";\nimport { ServerError } from \"../../errors\";\nimport { createLogger } from \"../../logging/logger\";\nimport { appKitTypesPlugin } from \"../../type-generator/vite-plugin\";\nimport { BaseServer } from \"./base-server\";\nimport type { PluginClientConfigs, PluginEndpoints } from \"./utils\";\n\nconst logger = createLogger(\"server:vite\");\n\n/**\n * Vite dev server for the AppKit.\n *\n * This class is responsible for serving the Vite dev server for the development server.\n * It also handles the index.html file for the development server.\n *\n * @example\n * ```ts\n * const viteDevServer = new ViteDevServer(app, endpoints);\n * await viteDevServer.setup();\n * ```\n */\nexport class ViteDevServer extends BaseServer {\n private vite: ViteDevServerType | null;\n\n constructor(\n app: express.Application,\n endpoints: PluginEndpoints = {},\n pluginConfigs: PluginClientConfigs = {},\n ) {\n super(app, endpoints, pluginConfigs);\n this.vite = null;\n }\n\n /**\n * Setup the Vite dev server.\n *\n * This method sets up the Vite dev server and the index.html file for the development server.\n *\n * @returns\n */\n async setup() {\n const {\n createServer: createViteServer,\n loadConfigFromFile,\n mergeConfig,\n } = await import(\"vite\");\n const react = await import(\"@vitejs/plugin-react\");\n\n const clientRoot = this.findClientRoot();\n\n const loadedConfig = await loadConfigFromFile(\n {\n mode: \"development\",\n command: \"serve\",\n },\n undefined,\n clientRoot,\n );\n\n const userConfig = loadedConfig?.config ?? {};\n const viteClientPort = process.env.VITE_CLIENT_PORT;\n const serverHmr = viteClientPort\n ? { hmr: { clientPort: viteClientPort } }\n : {};\n\n const coreConfig = {\n configFile: false,\n root: clientRoot,\n server: {\n middlewareMode: true,\n ...serverHmr,\n watch: {\n useFsEvents: true,\n ignored: [\"**/node_modules/**\", \"!**/node_modules/@databricks/**\"],\n },\n },\n plugins: [react.default(), appKitTypesPlugin()],\n appType: \"custom\",\n };\n\n const mergedConfigs = mergeConfigDedup(userConfig, coreConfig, mergeConfig);\n this.vite = await createViteServer(mergedConfigs);\n\n this.app.use(this.vite.middlewares);\n\n this.app.use(\"*\", async (req, res, next) => {\n if (\n req.originalUrl.startsWith(\"/api\") ||\n req.originalUrl.startsWith(\"/query\")\n ) {\n return next();\n }\n const vite = this.vite;\n this.validateVite(vite);\n\n try {\n const indexPath = path.resolve(clientRoot, \"index.html\");\n let html = fs.readFileSync(indexPath, \"utf-8\");\n html = html.replace(\"<body>\", `<body>${this.getConfigScript()}`);\n html = await vite.transformIndexHtml(req.originalUrl, html);\n res.status(200).set({ \"Content-Type\": \"text/html\" }).end(html);\n } catch (e) {\n vite.ssrFixStacktrace(e as Error);\n next(e);\n }\n });\n }\n\n /** Close the Vite dev server. */\n async close() {\n await this.vite?.close();\n }\n\n /** Find the client root. */\n private findClientRoot(): string {\n const cwd = process.cwd();\n const candidates = [\"client\", \"src\", \"app\", \"frontend\", \".\"];\n\n for (const dir of candidates) {\n const fullPath = path.resolve(cwd, dir);\n const hasViteConfig =\n fs.existsSync(path.join(fullPath, \"vite.config.ts\")) ||\n fs.existsSync(path.join(fullPath, \"vite.config.js\"));\n const hasIndexHtml = fs.existsSync(path.join(fullPath, \"index.html\"));\n\n if (hasViteConfig && hasIndexHtml) {\n logger.debug(\"Vite dev server: using client root %s\", fullPath);\n return fullPath;\n }\n }\n\n throw ServerError.clientDirectoryNotFound(candidates);\n }\n\n // type assertion to ensure vite is not null\n private validateVite(\n vite: ViteDevServerType | null,\n ): asserts vite is ViteDevServerType {\n if (!vite) {\n throw ServerError.viteNotInitialized();\n }\n }\n}\n"],"mappings":";;;;;;;;;;aAK2C;AAM3C,MAAM,SAAS,aAAa,cAAc;;;;;;;;;;;;;AAc1C,IAAa,gBAAb,cAAmC,WAAW;CAC5C,AAAQ;CAER,YACE,KACA,YAA6B,EAAE,EAC/B,gBAAqC,EAAE,EACvC;AACA,QAAM,KAAK,WAAW,cAAc;AACpC,OAAK,OAAO;;;;;;;;;CAUd,MAAM,QAAQ;EACZ,MAAM,EACJ,cAAc,kBACd,oBACA,gBACE,MAAM,OAAO;EACjB,MAAM,QAAQ,MAAM,OAAO;EAE3B,MAAM,aAAa,KAAK,gBAAgB;EAWxC,MAAM,cATe,MAAM,mBACzB;GACE,MAAM;GACN,SAAS;GACV,EACD,QACA,WACD,GAEgC,UAAU,EAAE;EAC7C,MAAM,iBAAiB,QAAQ,IAAI;AAqBnC,OAAK,OAAO,MAAM,iBADI,iBAAiB,YAfpB;GACjB,YAAY;GACZ,MAAM;GACN,QAAQ;IACN,gBAAgB;IAChB,GATc,iBACd,EAAE,KAAK,EAAE,YAAY,gBAAgB,EAAE,GACvC,EAAE;IAQF,OAAO;KACL,aAAa;KACb,SAAS,CAAC,sBAAsB,kCAAkC;KACnE;IACF;GACD,SAAS,CAAC,MAAM,SAAS,EAAE,mBAAmB,CAAC;GAC/C,SAAS;GACV,EAE8D,YAAY,CAC1B;AAEjD,OAAK,IAAI,IAAI,KAAK,KAAK,YAAY;AAEnC,OAAK,IAAI,IAAI,KAAK,OAAO,KAAK,KAAK,SAAS;AAC1C,OACE,IAAI,YAAY,WAAW,OAAO,IAClC,IAAI,YAAY,WAAW,SAAS,CAEpC,QAAO,MAAM;GAEf,MAAM,OAAO,KAAK;AAClB,QAAK,aAAa,KAAK;AAEvB,OAAI;IACF,MAAM,YAAY,KAAK,QAAQ,YAAY,aAAa;IACxD,IAAI,OAAO,GAAG,aAAa,WAAW,QAAQ;AAC9C,WAAO,KAAK,QAAQ,UAAU,SAAS,KAAK,iBAAiB,GAAG;AAChE,WAAO,MAAM,KAAK,mBAAmB,IAAI,aAAa,KAAK;AAC3D,QAAI,OAAO,IAAI,CAAC,IAAI,EAAE,gBAAgB,aAAa,CAAC,CAAC,IAAI,KAAK;YACvD,GAAG;AACV,SAAK,iBAAiB,EAAW;AACjC,SAAK,EAAE;;IAET;;;CAIJ,MAAM,QAAQ;AACZ,QAAM,KAAK,MAAM,OAAO;;;CAI1B,AAAQ,iBAAyB;EAC/B,MAAM,MAAM,QAAQ,KAAK;EACzB,MAAM,aAAa;GAAC;GAAU;GAAO;GAAO;GAAY;GAAI;AAE5D,OAAK,MAAM,OAAO,YAAY;GAC5B,MAAM,WAAW,KAAK,QAAQ,KAAK,IAAI;GACvC,MAAM,gBACJ,GAAG,WAAW,KAAK,KAAK,UAAU,iBAAiB,CAAC,IACpD,GAAG,WAAW,KAAK,KAAK,UAAU,iBAAiB,CAAC;GACtD,MAAM,eAAe,GAAG,WAAW,KAAK,KAAK,UAAU,aAAa,CAAC;AAErE,OAAI,iBAAiB,cAAc;AACjC,WAAO,MAAM,yCAAyC,SAAS;AAC/D,WAAO;;;AAIX,QAAM,YAAY,wBAAwB,WAAW;;CAIvD,AAAQ,aACN,MACmC;AACnC,MAAI,CAAC,KACH,OAAM,YAAY,oBAAoB"}
|
|
@@ -12,6 +12,7 @@ interface BasePlugin {
|
|
|
12
12
|
getEndpoints(): PluginEndpointMap;
|
|
13
13
|
getSkipBodyParsingPaths?(): ReadonlySet<string>;
|
|
14
14
|
exports?(): unknown;
|
|
15
|
+
clientConfig?(): Record<string, unknown>;
|
|
15
16
|
}
|
|
16
17
|
/** Base configuration interface for AppKit plugins */
|
|
17
18
|
interface BasePluginConfig {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","names":[],"sources":["../../../../shared/src/plugin.ts"],"mappings":";;;;;;UAYiB,UAAA;EACf,IAAA;EAEA,qBAAA;EAEA,KAAA,IAAS,OAAA;EAET,YAAA,CAAa,MAAA,EAAQ,OAAA,CAAQ,MAAA;EAE7B,YAAA,IAAgB,iBAAA;EAEhB,uBAAA,KAA4B,WAAA;EAE5B,OAAA;AAAA;;
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","names":[],"sources":["../../../../shared/src/plugin.ts"],"mappings":";;;;;;UAYiB,UAAA;EACf,IAAA;EAEA,qBAAA;EAEA,KAAA,IAAS,OAAA;EAET,YAAA,CAAa,MAAA,EAAQ,OAAA,CAAQ,MAAA;EAE7B,YAAA,IAAgB,iBAAA;EAEhB,uBAAA,KAA4B,WAAA;EAE5B,OAAA;EAEA,YAAA,KAAiB,MAAA;AAAA;;UAIF,gBAAA;EACf,IAAA;EACA,IAAA;EAAA,CAEC,GAAA;EAMD,SAAA,GAAY,gBAAA;AAAA;AAAA,KAGF,gBAAA;EAGN,MAAA;EACA,OAAA;EACA,IAAA;AAAA;AAAA,KAQM,WAAA;AA1BZ;;;;AAAA,KAgCY,iBAAA,KACN,gBAAA,YACM,UAAA,GAAa,UAAA,UAEvB,MAAA,EAAQ,CAAA,KACL,CAAA;EACH,cAAA,GAAiB,MAAA;EACjB,KAAA,GAAQ,WAAA;EA7BR;;;;EAkCA,QAAA,EAAU,cAAA;EA/BgB;;;;EAoC1B,uBAAA,EAAyB,MAAA,EAAQ,CAAA,GAAI,mBAAA;AAAA;;;AAvBvC;;;;;AAMA;;UA6BiB,cAAA,wCACP,IAAA,CACN,gBAAA;EAGF,IAAA,EAAM,KAAA;EACN,SAAA;IACE,QAAA,EAAU,IAAA,CAAK,mBAAA;IACf,QAAA,EAAU,IAAA,CAAK,mBAAA;EAAA;EAEjB,MAAA;IACE,MAAA,EAAQ,WAAA;EAAA;AAAA;;;;;;;;;UAYK,mBAAA,SAA4B,qBAAA;EAC3C,MAAA,EAAQ,MAAA,SAAe,kBAAA;EACvB,QAAA;AAAA;;;;;;KAoCU,aAAA,WAAwB,UAAA,IAClC,CAAA,sCAAqC,CAAA,GAAI,MAAA;;;;;;;;KAS/B,UAAA,QAAkB,GAAA,cAAgB,IAAA,mBAC1C,GAAA,GACA,GAAA;EAlEU;;;;;EAwER,MAAA,GAAS,GAAA,EAAK,WAAA,KAAgB,GAAA;AAAA;;;;AAxDpC;;;;;KAmEY,SAAA,oBACS,UAAA,CAAW,iBAAA,gCAExB,CAAA,YAAa,CAAA,WAAY,UAAA,CAC7B,aAAA,CAAc,YAAA,CAAa,CAAA;;KAKnB,UAAA;EAAwB,MAAA,EAAQ,CAAA;EAAG,MAAA,EAAQ,CAAA;EAAG,IAAA,EAAM,CAAA;AAAA;;KAEpD,QAAA,4BACV,MAAA,GAAS,CAAA,KACN,UAAA,CAAW,CAAA,EAAG,CAAA,EAAG,CAAA;;KAGV,UAAA,GAAa,OAAA,CAAQ,MAAA;AAAA,KACrB,YAAA,GAAe,OAAA,CAAQ,QAAA;AAAA,KACvB,WAAA,GAAc,OAAA,CAAQ,OAAA;AAAA,KAEtB,UAAA;AAAA,KAEA,WAAA;EAlDqC,+DAoD/C,IAAA;EACA,MAAA,EAAQ,UAAA;EACR,IAAA;EACA,OAAA,GAAU,GAAA,EAAK,WAAA,EAAa,GAAA,EAAK,YAAA,KAAiB,OAAA,QAvDb;EAyDrC,eAAA;AAAA;;KAIU,iBAAA,GAAoB,MAAA"}
|
|
@@ -38,6 +38,16 @@ async function generateFromEntryPoint(options) {
|
|
|
38
38
|
logger.debug("Starting type generation...");
|
|
39
39
|
let queryRegistry = [];
|
|
40
40
|
if (queryFolder) queryRegistry = await generateQueriesFromDescribe(queryFolder, warehouseId, { noCache });
|
|
41
|
+
const failedQueries = queryRegistry.filter((q) => q.type.includes("result: unknown"));
|
|
42
|
+
if (failedQueries.length > 0) {
|
|
43
|
+
const names = failedQueries.map((q) => q.name).join(", ");
|
|
44
|
+
throw new Error([
|
|
45
|
+
`Type generation failed: ${failedQueries.length} ${failedQueries.length === 1 ? "query" : "queries"} could not be described: ${names}.`,
|
|
46
|
+
`DESCRIBE QUERY failed for these queries — see the error codes above for details.`,
|
|
47
|
+
`Common causes: SQL syntax errors, missing tables/views, or warehouse format incompatibilities.`,
|
|
48
|
+
`To debug: run the failing query directly in a SQL editor against warehouse ${warehouseId}.`
|
|
49
|
+
].join("\n"));
|
|
50
|
+
}
|
|
41
51
|
const typeDeclarations = generateTypeDeclarations(queryRegistry);
|
|
42
52
|
await fs.writeFile(outFile, typeDeclarations, "utf-8");
|
|
43
53
|
logger.debug("Type generation complete!");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/type-generator/index.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport dotenv from \"dotenv\";\nimport { createLogger } from \"../logging/logger\";\nimport { generateQueriesFromDescribe } from \"./query-registry\";\nimport type { QuerySchema } from \"./types\";\n\ndotenv.config();\n\nconst logger = createLogger(\"type-generator\");\n\n/**\n * Generate type declarations for QueryRegistry\n * Create the d.ts file from the plugin routes and query schemas\n * @param querySchemas - the list of query schemas\n * @returns - the type declarations as a string\n */\nfunction generateTypeDeclarations(querySchemas: QuerySchema[] = []): string {\n const queryEntries = querySchemas\n .map(({ name, type }) => {\n const indentedType = type\n .split(\"\\n\")\n .map((line, i) => (i === 0 ? line : ` ${line}`))\n .join(\"\\n\");\n return ` ${name}: ${indentedType}`;\n })\n .join(\";\\n\");\n\n const querySection = queryEntries ? `\\n${queryEntries};\\n ` : \"\";\n\n return `// Auto-generated by AppKit - DO NOT EDIT\n// Generated by 'npx @databricks/appkit generate-types' or Vite plugin during build\nimport \"@databricks/appkit-ui/react\";\nimport type { SQLTypeMarker, SQLStringMarker, SQLNumberMarker, SQLBooleanMarker, SQLBinaryMarker, SQLDateMarker, SQLTimestampMarker } from \"@databricks/appkit-ui/js\";\n\ndeclare module \"@databricks/appkit-ui/react\" {\n interface QueryRegistry {${querySection}}\n}\n`;\n}\n\n/**\n * Entry point for generating type declarations from all imported files\n * @param options - the options for the generation\n * @param options.entryPoint - the entry point file\n * @param options.outFile - the output file\n * @param options.querySchemaFile - optional path to query schema file (e.g. config/queries/schema.ts)\n */\nexport async function generateFromEntryPoint(options: {\n outFile: string;\n queryFolder?: string;\n warehouseId: string;\n noCache?: boolean;\n}) {\n const { outFile, queryFolder, warehouseId, noCache } = options;\n\n logger.debug(\"Starting type generation...\");\n\n let queryRegistry: QuerySchema[] = [];\n if (queryFolder)\n queryRegistry = await generateQueriesFromDescribe(\n queryFolder,\n warehouseId,\n {\n noCache,\n },\n );\n\n const typeDeclarations = generateTypeDeclarations(queryRegistry);\n\n await fs.writeFile(outFile, typeDeclarations, \"utf-8\");\n\n logger.debug(\"Type generation complete!\");\n}\n"],"mappings":";;;;;;AAMA,OAAO,QAAQ;AAEf,MAAM,SAAS,aAAa,iBAAiB;;;;;;;AAQ7C,SAAS,yBAAyB,eAA8B,EAAE,EAAU;CAC1E,MAAM,eAAe,aAClB,KAAK,EAAE,MAAM,WAAW;AAKvB,SAAO,OAAO,KAAK,IAJE,KAClB,MAAM,KAAK,CACX,KAAK,MAAM,MAAO,MAAM,IAAI,OAAO,OAAO,OAAQ,CAClD,KAAK,KAAK;GAEb,CACD,KAAK,MAAM;AAId,QAAO;;;;;;6BAFc,eAAe,KAAK,aAAa,SAAS,GAQvB;;;;;;;;;;;AAY1C,eAAsB,uBAAuB,SAK1C;CACD,MAAM,EAAE,SAAS,aAAa,aAAa,YAAY;AAEvD,QAAO,MAAM,8BAA8B;CAE3C,IAAI,gBAA+B,EAAE;AACrC,KAAI,YACF,iBAAgB,MAAM,4BACpB,aACA,aACA,EACE,SACD,CACF;CAEH,MAAM,mBAAmB,yBAAyB,cAAc;AAEhE,OAAM,GAAG,UAAU,SAAS,kBAAkB,QAAQ;AAEtD,QAAO,MAAM,4BAA4B"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/type-generator/index.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport dotenv from \"dotenv\";\nimport { createLogger } from \"../logging/logger\";\nimport { generateQueriesFromDescribe } from \"./query-registry\";\nimport type { QuerySchema } from \"./types\";\n\ndotenv.config();\n\nconst logger = createLogger(\"type-generator\");\n\n/**\n * Generate type declarations for QueryRegistry\n * Create the d.ts file from the plugin routes and query schemas\n * @param querySchemas - the list of query schemas\n * @returns - the type declarations as a string\n */\nfunction generateTypeDeclarations(querySchemas: QuerySchema[] = []): string {\n const queryEntries = querySchemas\n .map(({ name, type }) => {\n const indentedType = type\n .split(\"\\n\")\n .map((line, i) => (i === 0 ? line : ` ${line}`))\n .join(\"\\n\");\n return ` ${name}: ${indentedType}`;\n })\n .join(\";\\n\");\n\n const querySection = queryEntries ? `\\n${queryEntries};\\n ` : \"\";\n\n return `// Auto-generated by AppKit - DO NOT EDIT\n// Generated by 'npx @databricks/appkit generate-types' or Vite plugin during build\nimport \"@databricks/appkit-ui/react\";\nimport type { SQLTypeMarker, SQLStringMarker, SQLNumberMarker, SQLBooleanMarker, SQLBinaryMarker, SQLDateMarker, SQLTimestampMarker } from \"@databricks/appkit-ui/js\";\n\ndeclare module \"@databricks/appkit-ui/react\" {\n interface QueryRegistry {${querySection}}\n}\n`;\n}\n\n/**\n * Entry point for generating type declarations from all imported files\n * @param options - the options for the generation\n * @param options.entryPoint - the entry point file\n * @param options.outFile - the output file\n * @param options.querySchemaFile - optional path to query schema file (e.g. config/queries/schema.ts)\n */\nexport async function generateFromEntryPoint(options: {\n outFile: string;\n queryFolder?: string;\n warehouseId: string;\n noCache?: boolean;\n}) {\n const { outFile, queryFolder, warehouseId, noCache } = options;\n\n logger.debug(\"Starting type generation...\");\n\n let queryRegistry: QuerySchema[] = [];\n if (queryFolder)\n queryRegistry = await generateQueriesFromDescribe(\n queryFolder,\n warehouseId,\n {\n noCache,\n },\n );\n\n const failedQueries = queryRegistry.filter((q) =>\n q.type.includes(\"result: unknown\"),\n );\n if (failedQueries.length > 0) {\n const names = failedQueries.map((q) => q.name).join(\", \");\n throw new Error(\n [\n `Type generation failed: ${failedQueries.length} ${failedQueries.length === 1 ? \"query\" : \"queries\"} could not be described: ${names}.`,\n `DESCRIBE QUERY failed for these queries — see the error codes above for details.`,\n `Common causes: SQL syntax errors, missing tables/views, or warehouse format incompatibilities.`,\n `To debug: run the failing query directly in a SQL editor against warehouse ${warehouseId}.`,\n ].join(\"\\n\"),\n );\n }\n\n const typeDeclarations = generateTypeDeclarations(queryRegistry);\n\n await fs.writeFile(outFile, typeDeclarations, \"utf-8\");\n\n logger.debug(\"Type generation complete!\");\n}\n"],"mappings":";;;;;;AAMA,OAAO,QAAQ;AAEf,MAAM,SAAS,aAAa,iBAAiB;;;;;;;AAQ7C,SAAS,yBAAyB,eAA8B,EAAE,EAAU;CAC1E,MAAM,eAAe,aAClB,KAAK,EAAE,MAAM,WAAW;AAKvB,SAAO,OAAO,KAAK,IAJE,KAClB,MAAM,KAAK,CACX,KAAK,MAAM,MAAO,MAAM,IAAI,OAAO,OAAO,OAAQ,CAClD,KAAK,KAAK;GAEb,CACD,KAAK,MAAM;AAId,QAAO;;;;;;6BAFc,eAAe,KAAK,aAAa,SAAS,GAQvB;;;;;;;;;;;AAY1C,eAAsB,uBAAuB,SAK1C;CACD,MAAM,EAAE,SAAS,aAAa,aAAa,YAAY;AAEvD,QAAO,MAAM,8BAA8B;CAE3C,IAAI,gBAA+B,EAAE;AACrC,KAAI,YACF,iBAAgB,MAAM,4BACpB,aACA,aACA,EACE,SACD,CACF;CAEH,MAAM,gBAAgB,cAAc,QAAQ,MAC1C,EAAE,KAAK,SAAS,kBAAkB,CACnC;AACD,KAAI,cAAc,SAAS,GAAG;EAC5B,MAAM,QAAQ,cAAc,KAAK,MAAM,EAAE,KAAK,CAAC,KAAK,KAAK;AACzD,QAAM,IAAI,MACR;GACE,2BAA2B,cAAc,OAAO,GAAG,cAAc,WAAW,IAAI,UAAU,UAAU,2BAA2B,MAAM;GACrI;GACA;GACA,8EAA8E,YAAY;GAC3F,CAAC,KAAK,KAAK,CACb;;CAGH,MAAM,mBAAmB,yBAAyB,cAAc;AAEhE,OAAM,GAAG,UAAU,SAAS,kBAAkB,QAAQ;AAEtD,QAAO,MAAM,4BAA4B"}
|
|
@@ -251,7 +251,65 @@ A proxied plugin instance that executes as the user
|
|
|
251
251
|
|
|
252
252
|
#### Throws[](#throws "Direct link to Throws")
|
|
253
253
|
|
|
254
|
-
AuthenticationError if user token is not available in request headers (production only). In development mode (`NODE_ENV=development`),
|
|
254
|
+
AuthenticationError if user token is not available in request headers (production only). In development mode (`NODE_ENV=development`), skips user impersonation instead of throwing.
|
|
255
|
+
|
|
256
|
+
***
|
|
257
|
+
|
|
258
|
+
### clientConfig()[](#clientconfig "Direct link to clientConfig()")
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
clientConfig(): Record<string, unknown>;
|
|
262
|
+
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Returns startup config to expose to the client. Override this to surface server-side values that are safe to publish to the frontend, such as feature flags, resource IDs, or other app boot settings.
|
|
266
|
+
|
|
267
|
+
This runs once when the server starts, so it should not depend on request-scoped or user-specific state.
|
|
268
|
+
|
|
269
|
+
String values that match non-public environment variables are redacted unless you intentionally expose them via a matching `PUBLIC_APPKIT_` env var.
|
|
270
|
+
|
|
271
|
+
Values must be JSON-serializable plain data (no functions, Dates, classes, Maps, Sets, BigInts, or circular references). By default returns an empty object (plugin contributes nothing to client config).
|
|
272
|
+
|
|
273
|
+
On the client, read the config with the `usePluginClientConfig` hook (React) or the `getPluginClientConfig` function (vanilla JS), both from `@databricks/appkit-ui`.
|
|
274
|
+
|
|
275
|
+
#### Returns[](#returns-3 "Direct link to Returns")
|
|
276
|
+
|
|
277
|
+
`Record`<`string`, `unknown`>
|
|
278
|
+
|
|
279
|
+
#### Example[](#example "Direct link to Example")
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
// Server — plugin definition
|
|
283
|
+
class MyPlugin extends Plugin<MyConfig> {
|
|
284
|
+
clientConfig() {
|
|
285
|
+
return {
|
|
286
|
+
warehouseId: this.config.warehouseId,
|
|
287
|
+
features: { darkMode: true },
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Client — React component
|
|
293
|
+
import { usePluginClientConfig } from "@databricks/appkit-ui/react";
|
|
294
|
+
|
|
295
|
+
interface MyPluginConfig { warehouseId: string; features: { darkMode: boolean } }
|
|
296
|
+
|
|
297
|
+
const config = usePluginClientConfig<MyPluginConfig>("myPlugin");
|
|
298
|
+
config.warehouseId; // "abc-123"
|
|
299
|
+
|
|
300
|
+
// Client — vanilla JS
|
|
301
|
+
import { getPluginClientConfig } from "@databricks/appkit-ui/js";
|
|
302
|
+
|
|
303
|
+
const config = getPluginClientConfig<MyPluginConfig>("myPlugin");
|
|
304
|
+
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
#### Implementation of[](#implementation-of-2 "Direct link to Implementation of")
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
BasePlugin.clientConfig
|
|
311
|
+
|
|
312
|
+
```
|
|
255
313
|
|
|
256
314
|
***
|
|
257
315
|
|
|
@@ -283,7 +341,7 @@ All errors are caught and `undefined` is returned (production-safe). Route handl
|
|
|
283
341
|
| `options` | `PluginExecutionSettings` |
|
|
284
342
|
| `userKey?` | `string` |
|
|
285
343
|
|
|
286
|
-
#### Returns[](#returns-
|
|
344
|
+
#### Returns[](#returns-4 "Direct link to Returns")
|
|
287
345
|
|
|
288
346
|
`Promise`<`T` | `undefined`>
|
|
289
347
|
|
|
@@ -315,7 +373,7 @@ userKey?: string): Promise<void>;
|
|
|
315
373
|
| `options` | [`StreamExecutionSettings`](./docs/api/appkit/Interface.StreamExecutionSettings.md) |
|
|
316
374
|
| `userKey?` | `string` |
|
|
317
375
|
|
|
318
|
-
#### Returns[](#returns-
|
|
376
|
+
#### Returns[](#returns-5 "Direct link to Returns")
|
|
319
377
|
|
|
320
378
|
`Promise`<`void`>
|
|
321
379
|
|
|
@@ -332,11 +390,11 @@ Returns the public exports for this plugin. Override this to define a custom pub
|
|
|
332
390
|
|
|
333
391
|
The returned object becomes the plugin's public API on the AppKit instance (e.g. `appkit.myPlugin.method()`). AppKit automatically binds method context and adds `asUser(req)` for user-scoped execution.
|
|
334
392
|
|
|
335
|
-
#### Returns[](#returns-
|
|
393
|
+
#### Returns[](#returns-6 "Direct link to Returns")
|
|
336
394
|
|
|
337
395
|
`unknown`
|
|
338
396
|
|
|
339
|
-
#### Example[](#example "Direct link to Example")
|
|
397
|
+
#### Example[](#example-1 "Direct link to Example")
|
|
340
398
|
|
|
341
399
|
```ts
|
|
342
400
|
class MyPlugin extends Plugin {
|
|
@@ -353,7 +411,7 @@ appkit.myPlugin.getData();
|
|
|
353
411
|
|
|
354
412
|
```
|
|
355
413
|
|
|
356
|
-
#### Implementation of[](#implementation-of-
|
|
414
|
+
#### Implementation of[](#implementation-of-3 "Direct link to Implementation of")
|
|
357
415
|
|
|
358
416
|
```ts
|
|
359
417
|
BasePlugin.exports
|
|
@@ -369,11 +427,11 @@ getEndpoints(): PluginEndpointMap;
|
|
|
369
427
|
|
|
370
428
|
```
|
|
371
429
|
|
|
372
|
-
#### Returns[](#returns-
|
|
430
|
+
#### Returns[](#returns-7 "Direct link to Returns")
|
|
373
431
|
|
|
374
432
|
`PluginEndpointMap`
|
|
375
433
|
|
|
376
|
-
#### Implementation of[](#implementation-of-
|
|
434
|
+
#### Implementation of[](#implementation-of-4 "Direct link to Implementation of")
|
|
377
435
|
|
|
378
436
|
```ts
|
|
379
437
|
BasePlugin.getEndpoints
|
|
@@ -389,11 +447,11 @@ getSkipBodyParsingPaths(): ReadonlySet<string>;
|
|
|
389
447
|
|
|
390
448
|
```
|
|
391
449
|
|
|
392
|
-
#### Returns[](#returns-
|
|
450
|
+
#### Returns[](#returns-8 "Direct link to Returns")
|
|
393
451
|
|
|
394
452
|
`ReadonlySet`<`string`>
|
|
395
453
|
|
|
396
|
-
#### Implementation of[](#implementation-of-
|
|
454
|
+
#### Implementation of[](#implementation-of-5 "Direct link to Implementation of")
|
|
397
455
|
|
|
398
456
|
```ts
|
|
399
457
|
BasePlugin.getSkipBodyParsingPaths
|
|
@@ -415,11 +473,11 @@ injectRoutes(_: Router): void;
|
|
|
415
473
|
| --------- | -------- |
|
|
416
474
|
| `_` | `Router` |
|
|
417
475
|
|
|
418
|
-
#### Returns[](#returns-
|
|
476
|
+
#### Returns[](#returns-9 "Direct link to Returns")
|
|
419
477
|
|
|
420
478
|
`void`
|
|
421
479
|
|
|
422
|
-
#### Implementation of[](#implementation-of-
|
|
480
|
+
#### Implementation of[](#implementation-of-6 "Direct link to Implementation of")
|
|
423
481
|
|
|
424
482
|
```ts
|
|
425
483
|
BasePlugin.injectRoutes
|
|
@@ -442,7 +500,7 @@ protected registerEndpoint(name: string, path: string): void;
|
|
|
442
500
|
| `name` | `string` |
|
|
443
501
|
| `path` | `string` |
|
|
444
502
|
|
|
445
|
-
#### Returns[](#returns-
|
|
503
|
+
#### Returns[](#returns-10 "Direct link to Returns")
|
|
446
504
|
|
|
447
505
|
`void`
|
|
448
506
|
|
|
@@ -465,7 +523,7 @@ Returns the `x-forwarded-user` header when present. In development mode (`NODE_E
|
|
|
465
523
|
| --------- | --------- |
|
|
466
524
|
| `req` | `Request` |
|
|
467
525
|
|
|
468
|
-
#### Returns[](#returns-
|
|
526
|
+
#### Returns[](#returns-11 "Direct link to Returns")
|
|
469
527
|
|
|
470
528
|
`string`
|
|
471
529
|
|
|
@@ -495,7 +553,7 @@ protected route<_TResponse>(router: Router, config: RouteConfig): void;
|
|
|
495
553
|
| `router` | `Router` |
|
|
496
554
|
| `config` | `RouteConfig` |
|
|
497
555
|
|
|
498
|
-
#### Returns[](#returns-
|
|
556
|
+
#### Returns[](#returns-12 "Direct link to Returns")
|
|
499
557
|
|
|
500
558
|
`void`
|
|
501
559
|
|
|
@@ -508,11 +566,11 @@ setup(): Promise<void>;
|
|
|
508
566
|
|
|
509
567
|
```
|
|
510
568
|
|
|
511
|
-
#### Returns[](#returns-
|
|
569
|
+
#### Returns[](#returns-13 "Direct link to Returns")
|
|
512
570
|
|
|
513
571
|
`Promise`<`void`>
|
|
514
572
|
|
|
515
|
-
#### Implementation of[](#implementation-of-
|
|
573
|
+
#### Implementation of[](#implementation-of-7 "Direct link to Implementation of")
|
|
516
574
|
|
|
517
575
|
```ts
|
|
518
576
|
BasePlugin.setup
|
package/docs/app-management.md
CHANGED
|
@@ -5,7 +5,7 @@ Manage your AppKit application throughout its lifecycle using the Databricks CLI
|
|
|
5
5
|
## Prerequisites[](#prerequisites "Direct link to Prerequisites")
|
|
6
6
|
|
|
7
7
|
* [Node.js](https://nodejs.org) v22+ environment with `npm`
|
|
8
|
-
* Databricks CLI (v0.
|
|
8
|
+
* Databricks CLI (v0.295.0 or higher): install and configure it according to the [official tutorial](https://docs.databricks.com/aws/en/dev-tools/cli/tutorial).
|
|
9
9
|
|
|
10
10
|
## Create app[](#create-app "Direct link to Create app")
|
|
11
11
|
|
package/docs/architecture.md
CHANGED
|
@@ -59,7 +59,7 @@ The Node.js backend layer built with `@databricks/appkit`:
|
|
|
59
59
|
Integration with Databricks services:
|
|
60
60
|
|
|
61
61
|
* **SQL Warehouses**: Execute analytical queries with Arrow or JSON format
|
|
62
|
-
* **Lakebase
|
|
62
|
+
* **Lakebase Autoscaling**: OLTP database access with automatic OAuth token refresh
|
|
63
63
|
|
|
64
64
|
## See also[](#see-also "Direct link to See also")
|
|
65
65
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
## Prerequisites[](#prerequisites "Direct link to Prerequisites")
|
|
4
4
|
|
|
5
5
|
* [Node.js](https://nodejs.org) v22+ environment with `npm`
|
|
6
|
-
* Databricks CLI (v0.
|
|
6
|
+
* Databricks CLI (v0.295.0 or higher): install and configure it according to the [official tutorial](https://docs.databricks.com/aws/en/dev-tools/cli/tutorial).
|
|
7
7
|
* A new Databricks app with AppKit installed. See [Bootstrap a new Databricks app](./docs.md#quick-start-options) for more details.
|
|
8
8
|
|
|
9
9
|
AppKit integrates with AI coding assistants through the Agent Skills.
|
|
@@ -13,7 +13,7 @@ AppKit integrates with AI coding assistants through the Agent Skills.
|
|
|
13
13
|
To install the Databricks Agent Skills for your preferred AI assistant, run:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
databricks experimental aitools
|
|
16
|
+
databricks experimental aitools install
|
|
17
17
|
|
|
18
18
|
```
|
|
19
19
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
## Prerequisites[](#prerequisites "Direct link to Prerequisites")
|
|
4
4
|
|
|
5
5
|
* [Node.js](https://nodejs.org) v22+ environment with `npm`
|
|
6
|
-
* Databricks CLI (v0.
|
|
6
|
+
* Databricks CLI (v0.295.0 or higher): install and configure it according to the [official tutorial](https://docs.databricks.com/aws/en/dev-tools/cli/tutorial).
|
|
7
7
|
* A new Databricks app with AppKit installed. See [Bootstrap a new Databricks app](./docs.md#quick-start-options) for more details.
|
|
8
8
|
|
|
9
9
|
Once your app is bootstrapped according to the [Manual quick start](./docs.md#manual-quick-start) guide, you can start the development server with hot reload for both UI and backend code.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
## Prerequisites[](#prerequisites "Direct link to Prerequisites")
|
|
4
4
|
|
|
5
5
|
* [Node.js](https://nodejs.org) v22+ environment with `npm`
|
|
6
|
-
* Databricks CLI (v0.
|
|
6
|
+
* Databricks CLI (v0.295.0 or higher): install and configure it according to the [official tutorial](https://docs.databricks.com/aws/en/dev-tools/cli/tutorial).
|
|
7
7
|
* A new Databricks app with AppKit installed. See [Bootstrap a new Databricks app](./docs.md#quick-start-options) for more details.
|
|
8
8
|
|
|
9
9
|
Remote bridge allows you to develop against a deployed backend while keeping your UI and queries local. This is useful for testing against production data or debugging deployed backend code without redeploying your app.
|