diff --git a/template/.env.example.tmpl b/template/.env.example.tmpl index 0a7c80ec..7ab495cb 100644 --- a/template/.env.example.tmpl +++ b/template/.env.example.tmpl @@ -1,7 +1,15 @@ DATABRICKS_HOST=https://... -{{- if .dotenv_example}} -{{.dotenv_example}} +{{- if .dotEnv.example}} +{{.dotEnv.example}} +{{- end}} +{{- if .plugins.lakebase}} +PGHOST=your-lakebase-host.databricks.com +PGDATABASE=databricks_postgres +# Run: databricks postgres list-endpoints projects/{project-id}/branches/{branch-id} +LAKEBASE_ENDPOINT=projects//branches//endpoints/ +# PGUSER=your_user # optional, defaults to DATABRICKS_CLIENT_ID +PGSSLMODE=require {{- end}} DATABRICKS_APP_PORT=8000 -DATABRICKS_APP_NAME={{.project_name}} +DATABRICKS_APP_NAME={{.projectName}} FLASK_RUN_HOST=0.0.0.0 diff --git a/template/.env.tmpl b/template/.env.tmpl index 31d0d997..693834f8 100644 --- a/template/.env.tmpl +++ b/template/.env.tmpl @@ -1,7 +1,18 @@ -{{if ne .profile ""}}DATABRICKS_CONFIG_PROFILE={{.profile}}{{else}}DATABRICKS_HOST={{.workspace_host}}{{end}} -{{- if .dotenv}} -{{.dotenv}} +{{- if ne .profile ""}} +DATABRICKS_CONFIG_PROFILE={{.profile}} +{{- else}} +DATABRICKS_HOST={{.workspaceHost}} +{{- end}} +{{- if .dotEnv.content}} +{{.dotEnv.content}} +{{- end}} +{{- if .plugins.lakebase}} +PGHOST='' # Copy from the Lakebase Postgres UI +PGDATABASE='databricks_postgres' # Copy from the Lakebase Postgres UI +LAKEBASE_ENDPOINT='' # Run: databricks postgres list-endpoints projects/{project-id}/branches/{branch-id} +# PGUSER='' # optional, defaults to DATABRICKS_CLIENT_ID +PGSSLMODE=require {{- end}} DATABRICKS_APP_PORT=8000 -DATABRICKS_APP_NAME={{.project_name}} +DATABRICKS_APP_NAME={{.projectName}} FLASK_RUN_HOST=0.0.0.0 diff --git a/template/app.yaml.tmpl b/template/app.yaml.tmpl index a4e7b27d..f4ac5ed4 100644 --- a/template/app.yaml.tmpl +++ b/template/app.yaml.tmpl @@ -1,5 +1,17 @@ command: ['npm', 'run', 'start'] -{{- if .app_env}} env: -{{.app_env}} +{{- if .appEnv}} +{{.appEnv}} {{- end}} +{{- if .plugins.lakebase}} + - name: PGHOST + value: "" # Copy from the Lakebase Postgres UI + - name: PGDATABASE + value: "databricks_postgres" # Copy from the Lakebase Postgres UI + - name: LAKEBASE_ENDPOINT + value: "" # Run: databricks postgres list-endpoints projects/{project-id}/branches/{branch-id} + - name: PGSSLMODE + value: "require" +# - name: PGUSER +# value: "" # optional, defaults to DATABRICKS_CLIENT_ID +{{- end}} \ No newline at end of file diff --git a/template/client/index.html b/template/client/index.html index 3c0f326f..a3da7c2b 100644 --- a/template/client/index.html +++ b/template/client/index.html @@ -9,7 +9,7 @@ - {{.project_name}} + {{.projectName}}
diff --git a/template/client/public/site.webmanifest b/template/client/public/site.webmanifest index 03106ced..64190b64 100644 --- a/template/client/public/site.webmanifest +++ b/template/client/public/site.webmanifest @@ -1,6 +1,6 @@ { - "name": "{{.project_name}}", - "short_name": "{{.project_name}}", + "name": "{{.projectName}}", + "short_name": "{{.projectName}}", "icons": [ { "src": "/favicon-192x192.png", diff --git a/template/client/src/App.tsx b/template/client/src/App.tsx index 3075436b..d6497de7 100644 --- a/template/client/src/App.tsx +++ b/template/client/src/App.tsx @@ -1,167 +1,114 @@ -/** - * ⚠️ BEFORE MODIFYING THIS FILE: - * - * 1. Create SQL files in config/queries/ - * 2. Run `npm run typegen` to generate query types - * 3. Check appKitTypes.d.ts for available types - * - * Common Mistakes: - * - DataTable does NOT accept `data` or `columns` props - * - Charts use `xKey` and `yKey`, NOT `seriesKey`/`nameKey`/`valueKey` - * - useAnalyticsQuery has no `enabled` option - use conditional rendering - */ +import { createBrowserRouter, RouterProvider, NavLink, Outlet } from 'react-router'; import { - useAnalyticsQuery, - AreaChart, - LineChart, - RadarChart, Card, CardContent, CardHeader, CardTitle, - Skeleton, - Label, - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, } from '@databricks/appkit-ui/react'; -import { sql } from "@databricks/appkit-ui/js"; -import { useState, useEffect } from 'react'; +{{- if .plugins.analytics}} +import { AnalyticsPage } from './pages/analytics/AnalyticsPage'; +{{- end}} +{{- if .plugins.lakebase}} +import { LakebasePage } from './pages/lakebase/LakebasePage'; +{{- end}} -function App() { - const { data, loading, error } = useAnalyticsQuery('hello_world', { - message: sql.string('hello world'), - }); +const navLinkClass = ({ isActive }: { isActive: boolean }) => + `px-3 py-1.5 rounded-md text-sm font-medium transition-colors ${ + isActive + ? 'bg-primary text-primary-foreground' + : 'text-muted-foreground hover:bg-muted hover:text-foreground' + }`; - const [health, setHealth] = useState<{ - status: string; - timestamp: string; - } | null>(null); - const [healthError, setHealthError] = useState(null); +function Layout() { + return ( +
+
+

{{.projectName}}

+ +
- useEffect(() => { - fetch('/health') - .then((response) => response.json()) - .then((data: { status: string }) => setHealth({ ...data, timestamp: new Date().toISOString() })) - .catch((error: { message: string }) => setHealthError(error.message)); - }, []); +
+ +
+
+ ); +} - const [maxMonthNum, setMaxMonthNum] = useState(12); +const router = createBrowserRouter([ + { + element: , + children: [ + { path: '/', element: }, +{{- if .plugins.analytics}} + { path: '/analytics', element: }, +{{- end}} +{{- if .plugins.lakebase}} + { path: '/lakebase', element: }, +{{- end}} + ], + }, +]); - const salesParameters = { max_month_num: sql.number(maxMonthNum) }; +export default function App() { + return ; +} +function HomePage() { return ( -
-
-

Minimal Databricks App

-

A minimal Databricks App powered by Databricks AppKit

+
+
+

+ Welcome to your Databricks App +

+

+ Powered by Databricks AppKit +

-
- - - SQL Query Result - - - {loading && ( -
- - -
- )} - {error &&
Error: {error}
} - {data && data.length > 0 && ( -
-
Query: SELECT :message AS value
-
{data[0].value}
-
- )} - {data && data.length === 0 &&
No results
} -
-
- - - - Health Check - - - {!health && !healthError && ( -
- - -
- )} - {healthError && ( -
Error: {healthError}
- )} - {health && ( -
-
-
-
{health.status.toUpperCase()}
-
-
- Last checked: {new Date(health.timestamp).toLocaleString()} -
-
- )} -
-
- - - - Sales Data Filter - - -
-
- - -
-
-
-
- - - - Sales Trend Area Chart - - - - - - - - Sales Trend Custom Line Chart - - - - - - - - Sales Trend Radar Chart - - - - - -
+ + + Getting Started + + +

Your app is ready. Explore the resources below to continue building.

+ +
+
); } - -export default App; diff --git a/template/client/src/pages/analytics/AnalyticsPage.tsx b/template/client/src/pages/analytics/AnalyticsPage.tsx new file mode 100644 index 00000000..e1ca7a9d --- /dev/null +++ b/template/client/src/pages/analytics/AnalyticsPage.tsx @@ -0,0 +1,108 @@ +{{if .plugins.analytics -}} +import { + useAnalyticsQuery, + AreaChart, + LineChart, + RadarChart, + Card, + CardContent, + CardHeader, + CardTitle, + Skeleton, + Label, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@databricks/appkit-ui/react'; +import { sql } from "@databricks/appkit-ui/js"; +import { useState } from 'react'; + +export function AnalyticsPage() { + const { data, loading, error } = useAnalyticsQuery('hello_world', { + message: sql.string('hello world'), + }); + + const [maxMonthNum, setMaxMonthNum] = useState(12); + const salesParameters = { max_month_num: sql.number(maxMonthNum) }; + + return ( +
+
+ + + SQL Query Result + + + {loading && ( +
+ + +
+ )} + {error &&
Error: {error}
} + {data && data.length > 0 && ( +
+
Query: SELECT :message AS value
+
{data[0].value}
+
+ )} + {data && data.length === 0 &&
No results
} +
+
+ + + + Sales Data Filter + + +
+
+ + +
+
+
+
+ + + + Sales Trend Area Chart + + + + + + + + Sales Trend Line Chart + + + + + + + + Sales Trend Radar Chart + + + + + +
+
+ ); +} +{{- end}} diff --git a/template/client/src/pages/lakebase/LakebasePage.tsx b/template/client/src/pages/lakebase/LakebasePage.tsx new file mode 100644 index 00000000..06c02922 --- /dev/null +++ b/template/client/src/pages/lakebase/LakebasePage.tsx @@ -0,0 +1,178 @@ +{{if .plugins.lakebase -}} +import { + Card, + CardContent, + CardHeader, + CardTitle, + Button, + Input, + Skeleton, +} from '@databricks/appkit-ui/react'; +import { useState, useEffect } from 'react'; +import { Check, X } from 'lucide-react'; + +interface Todo { + id: number; + title: string; + completed: boolean; + created_at: string; +} + +export function LakebasePage() { + const [todos, setTodos] = useState([]); + const [newTitle, setNewTitle] = useState(''); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [submitting, setSubmitting] = useState(false); + + useEffect(() => { + fetch('/api/lakebase/todos') + .then((res) => { + if (!res.ok) throw new Error(`Failed to fetch todos: ${res.statusText}`); + return res.json() as Promise; + }) + .then(setTodos) + .catch((err) => setError(err instanceof Error ? err.message : 'Failed to load todos')) + .finally(() => setLoading(false)); + }, []); + + const addTodo = async (e: React.FormEvent) => { + e.preventDefault(); + const title = newTitle.trim(); + if (!title) return; + + setSubmitting(true); + try { + const res = await fetch('/api/lakebase/todos', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ title }), + }); + if (!res.ok) throw new Error(`Failed to create todo: ${res.statusText}`); + const created = (await res.json()) as Todo; + setTodos((prev) => [created, ...prev]); + setNewTitle(''); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to add todo'); + } finally { + setSubmitting(false); + } + }; + + const toggleTodo = async (id: number) => { + try { + const res = await fetch(`/api/lakebase/todos/${id}`, { method: 'PATCH' }); + if (!res.ok) throw new Error(`Failed to update todo: ${res.statusText}`); + const updated = (await res.json()) as Todo; + setTodos((prev) => prev.map((t) => (t.id === id ? updated : t))); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to update todo'); + } + }; + + const deleteTodo = async (id: number) => { + try { + const res = await fetch(`/api/lakebase/todos/${id}`, { method: 'DELETE' }); + if (!res.ok) throw new Error(`Failed to delete todo: ${res.statusText}`); + setTodos((prev) => prev.filter((t) => t.id !== id)); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to delete todo'); + } + }; + + const completedCount = todos.filter((t) => t.completed).length; + + return ( +
+ + + Todo List + + +

+ A simple CRUD example powered by Databricks Lakebase (PostgreSQL). +

+ +
+ setNewTitle(e.target.value)} + disabled={submitting} + className="flex-1" + /> + +
+ + {error && ( +
+ {error} +
+ )} + + {loading && ( +
+ {Array.from({ length: 3 }, (_, i) => ( +
+ + +
+ ))} +
+ )} + + {!loading && todos.length === 0 && ( +

+ No todos yet. Add one above to get started. +

+ )} + + {!loading && todos.length > 0 && ( +
+ {todos.map((todo) => ( +
+ + + + {todo.title} + + + +
+ ))} + +

+ {completedCount} of {todos.length} completed +

+
+ )} +
+
+
+ ); +} +{{- end}} diff --git a/template/config/queries/hello_world.sql b/template/config/queries/hello_world.sql index 31e480f7..2ee26aa5 100644 --- a/template/config/queries/hello_world.sql +++ b/template/config/queries/hello_world.sql @@ -1 +1,3 @@ +{{if .plugins.analytics -}} SELECT :message AS value; +{{- end}} diff --git a/template/config/queries/mocked_sales.sql b/template/config/queries/mocked_sales.sql index 868e9472..526b5fd8 100644 --- a/template/config/queries/mocked_sales.sql +++ b/template/config/queries/mocked_sales.sql @@ -1,3 +1,4 @@ +{{if .plugins.analytics -}} WITH mock_data AS ( SELECT 'January' AS month, 1 AS month_num, 65000 AS revenue, 45000 AS expenses, 850 AS customers UNION ALL SELECT 'February', 2, 72000, 48000, 920 @@ -16,3 +17,4 @@ SELECT * FROM mock_data WHERE month_num <= :max_month_num ORDER BY month_num; +{{- end}} diff --git a/template/databricks.yml.tmpl b/template/databricks.yml.tmpl index a4cace29..4e610b44 100644 --- a/template/databricks.yml.tmpl +++ b/template/databricks.yml.tmpl @@ -1,35 +1,35 @@ bundle: - name: {{.project_name}} -{{if .variables}} + name: {{.projectName}} +{{- if .bundle.variables}} variables: -{{.variables}} +{{.bundle.variables}} {{- end}} resources: apps: app: - name: "{{.project_name}}" - description: "{{.app_description}}" + name: "{{.projectName}}" + description: "{{.appDescription}}" source_code_path: ./ # Uncomment to enable on behalf of user API scopes. Available scopes: sql, dashboards.genie, files.files # user_api_scopes: # - sql -{{if .resources}} +{{- if .bundle.resources}} # The resources which this app has access to. resources: -{{.resources}} +{{.bundle.resources}} {{- end}} targets: default: default: true workspace: - host: {{.workspace_host}} -{{if .target_variables}} + host: {{.workspaceHost}} +{{- if .bundle.targetVariables}} variables: -{{.target_variables}} +{{.bundle.targetVariables}} {{- end}} diff --git a/template/package-lock.json b/template/package-lock.json index 83ab2b14..37e60232 100644 --- a/template/package-lock.json +++ b/template/package-lock.json @@ -1,11 +1,11 @@ { - "name": "{{.project_name}}", + "name": "{{.projectName}}", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "{{.project_name}}", + "name": "{{.projectName}}", "version": "1.0.0", "hasInstallScript": true, "license": "Unlicensed", @@ -20,7 +20,7 @@ "react": "^19.1.1", "react-dom": "^19.1.1", "react-resizable-panels": "^3.0.6", - "superjson": "^2.2.5", + "react-router": "^7.13.0", "tailwind-merge": "^3.3.1", "tailwindcss-animate": "^1.0.7", "tw-animate-css": "^1.4.0", @@ -33,7 +33,7 @@ "@playwright/test": "^1.57.0", "@tailwindcss/postcss": "^4.1.17", "@tailwindcss/vite": "^4.1.17", - "@types/express": "^5.0.5", + "@types/express": "^5.0.5"t, "@types/node": "^24.6.0", "@types/react": "^19.1.16", "@types/react-dom": "^19.1.9", @@ -264,7 +264,6 @@ "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", @@ -1850,7 +1849,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=8.0.0" } @@ -1948,7 +1946,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, @@ -5526,6 +5523,7 @@ "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz", "integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==", "license": "MIT", + "peer": true, "dependencies": { "@standard-schema/spec": "^1.0.0", "@standard-schema/utils": "^0.3.0", @@ -5552,6 +5550,7 @@ "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.4.tgz", "integrity": "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==", "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/immer" @@ -6272,25 +6271,29 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/d3-color": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/d3-ease": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/d3-interpolate": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", "license": "MIT", + "peer": true, "dependencies": { "@types/d3-color": "*" } @@ -6299,13 +6302,15 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/d3-scale": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", "license": "MIT", + "peer": true, "dependencies": { "@types/d3-time": "*" } @@ -6315,6 +6320,7 @@ "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", "license": "MIT", + "peer": true, "dependencies": { "@types/d3-path": "*" } @@ -6323,13 +6329,15 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/d3-timer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/deep-eql": { "version": "4.0.2", @@ -6460,7 +6468,6 @@ "integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -6471,7 +6478,6 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -6516,7 +6522,8 @@ "version": "0.0.6", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.54.0", @@ -6553,7 +6560,6 @@ "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", @@ -6902,7 +6908,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -7405,7 +7410,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -7727,21 +7731,6 @@ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", "license": "MIT" }, - "node_modules/copy-anything": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", - "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", - "license": "MIT", - "dependencies": { - "is-what": "^5.2.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -7768,6 +7757,7 @@ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", "license": "ISC", + "peer": true, "dependencies": { "internmap": "1 - 2" }, @@ -7780,6 +7770,7 @@ "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -7789,6 +7780,7 @@ "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", "license": "BSD-3-Clause", + "peer": true, "engines": { "node": ">=12" } @@ -7798,6 +7790,7 @@ "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -7807,6 +7800,7 @@ "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", "license": "ISC", + "peer": true, "dependencies": { "d3-color": "1 - 3" }, @@ -7819,6 +7813,7 @@ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -7828,6 +7823,7 @@ "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", "license": "ISC", + "peer": true, "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", @@ -7844,6 +7840,7 @@ "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", "license": "ISC", + "peer": true, "dependencies": { "d3-path": "^3.1.0" }, @@ -7856,6 +7853,7 @@ "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", "license": "ISC", + "peer": true, "dependencies": { "d3-array": "2 - 3" }, @@ -7868,6 +7866,7 @@ "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", "license": "ISC", + "peer": true, "dependencies": { "d3-time": "1 - 3" }, @@ -7880,6 +7879,7 @@ "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -7984,7 +7984,8 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/deep-is": { "version": "0.1.4", @@ -8122,6 +8123,7 @@ "resolved": "https://registry.npmjs.org/echarts/-/echarts-6.0.0.tgz", "integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "tslib": "2.3.0", "zrender": "6.0.0" @@ -8145,7 +8147,8 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/ee-first": { "version": "1.1.1", @@ -8164,8 +8167,7 @@ "version": "8.6.0", "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz", "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/embla-carousel-react": { "version": "8.6.0", @@ -8404,6 +8406,7 @@ "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.44.0.tgz", "integrity": "sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==", "license": "MIT", + "peer": true, "workspaces": [ "docs", "benchmarks" @@ -8443,7 +8446,6 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -8801,7 +8803,8 @@ "version": "5.0.4", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/expect-type": { "version": "1.3.0", @@ -9715,6 +9718,7 @@ "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/immer" @@ -9804,6 +9808,7 @@ "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -10220,18 +10225,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-what": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", - "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -11246,7 +11239,6 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz", "integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==", "license": "MIT", - "peer": true, "dependencies": { "pg-connection-string": "^2.11.0", "pg-pool": "^3.11.0", @@ -11342,7 +11334,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -11411,7 +11402,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -11603,7 +11593,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -11634,7 +11623,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -11647,7 +11635,6 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.1.tgz", "integrity": "sha512-9SUJKCGKo8HUSsCO+y0CtqkqI5nNuaDqTxyqPsZPqIwudpj4rCrAz/jZV+jn57bx5gtZKOh3neQu94DXMc+w5w==", "license": "MIT", - "peer": true, "engines": { "node": ">=18.0.0" }, @@ -11757,6 +11744,41 @@ "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, + "node_modules/react-router": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.1.tgz", + "integrity": "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router/node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/react-style-singleton": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", @@ -11822,6 +11844,7 @@ "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", "license": "MIT", + "peer": true, "peerDependencies": { "redux": "^5.0.0" } @@ -11911,7 +11934,8 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/resolve": { "version": "2.0.0-next.5", @@ -12158,6 +12182,12 @@ "node": ">= 0.8.0" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -12612,18 +12642,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/superjson": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", - "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", - "license": "MIT", - "dependencies": { - "copy-anything": "^4" - }, - "engines": { - "node": ">=16" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -12676,8 +12694,7 @@ "version": "4.1.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tailwindcss-animate": { "version": "1.0.7", @@ -12706,7 +12723,8 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tinybench": { "version": "2.9.0", @@ -13395,7 +13413,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -13613,6 +13630,7 @@ "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", "license": "MIT AND ISC", + "peer": true, "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", @@ -13636,7 +13654,6 @@ "resolved": "https://registry.npmjs.org/rolldown-vite/-/rolldown-vite-7.1.14.tgz", "integrity": "sha512-eSiiRJmovt8qDJkGyZuLnbxAOAdie6NCmmd0NkTC0RJI9duiSBTfr8X2mBYJOUFzxQa2USaHmL99J9uMxkjCyw==", "license": "MIT", - "peer": true, "dependencies": { "@oxc-project/runtime": "0.92.0", "fdir": "^6.5.0", @@ -14090,7 +14107,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -14123,6 +14139,7 @@ "resolved": "https://registry.npmjs.org/zrender/-/zrender-6.0.0.tgz", "integrity": "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==", "license": "BSD-3-Clause", + "peer": true, "dependencies": { "tslib": "2.3.0" } @@ -14131,7 +14148,8 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", - "license": "0BSD" + "license": "0BSD", + "peer": true } } } diff --git a/template/package.json b/template/package.json index 070cd178..8ff6f486 100644 --- a/template/package.json +++ b/template/package.json @@ -1,8 +1,6 @@ { - "name": "{{.project_name}}", + "name": "{{.projectName}}", "version": "1.0.0", - "main": "build/index.js", - "type": "module", "scripts": { "start": "NODE_ENV=production node --env-file-if-exists=./.env ./dist/server/server.js", "dev": "NODE_ENV=development tsx watch --tsconfig ./tsconfig.server.json --env-file-if-exists=./.env ./server/server.ts", @@ -30,7 +28,7 @@ "keywords": [], "author": "", "license": "Unlicensed", - "description": "{{.app_description}}", + "description": "{{.appDescription}}", "dependencies": { "@databricks/appkit": "0.10.1", "@databricks/appkit-ui": "0.10.1", @@ -42,7 +40,7 @@ "react": "^19.1.1", "react-dom": "^19.1.1", "react-resizable-panels": "^3.0.6", - "superjson": "^2.2.5", + "react-router": "^7.13.0", "tailwind-merge": "^3.3.1", "tw-animate-css": "^1.4.0", "tailwindcss-animate": "^1.0.7", diff --git a/template/server/routes/lakebase/todo-routes.ts b/template/server/routes/lakebase/todo-routes.ts new file mode 100644 index 00000000..16007a17 --- /dev/null +++ b/template/server/routes/lakebase/todo-routes.ts @@ -0,0 +1,100 @@ +{{if .plugins.lakebase -}} +import { z } from 'zod'; +import type { LakebasePlugin, ServerPlugin } from '@databricks/appkit'; + +interface AppKitWithLakebase { + lakebase: ReturnType; + server: ReturnType; +} + +const CREATE_TABLE_SQL = ` + CREATE TABLE IF NOT EXISTS todos ( + id SERIAL PRIMARY KEY, + title TEXT NOT NULL, + completed BOOLEAN NOT NULL DEFAULT false, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + ) +`; + +const CreateTodoBody = z.object({ title: z.string().min(1) }); + +export async function setupSampleLakebaseRoutes(appkit: AppKitWithLakebase) { + await appkit.lakebase.query(CREATE_TABLE_SQL); + + appkit.server.extend((app) => { + app.get('/api/lakebase/todos', async (_req, res) => { + try { + const result = await appkit.lakebase.query( + 'SELECT id, title, completed, created_at FROM todos ORDER BY created_at DESC', + ); + res.json(result.rows); + } catch (err) { + console.error('Failed to list todos:', err); + res.status(500).json({ error: 'Failed to list todos' }); + } + }); + + app.post('/api/lakebase/todos', async (req, res) => { + try { + const parsed = CreateTodoBody.safeParse(req.body); + if (!parsed.success) { + res.status(400).json({ error: 'title is required' }); + return; + } + const result = await appkit.lakebase.query( + 'INSERT INTO todos (title) VALUES ($1) RETURNING id, title, completed, created_at', + [parsed.data.title.trim()], + ); + res.status(201).json(result.rows[0]); + } catch (err) { + console.error('Failed to create todo:', err); + res.status(500).json({ error: 'Failed to create todo' }); + } + }); + + app.patch('/api/lakebase/todos/:id', async (req, res) => { + try { + const id = parseInt(req.params.id, 10); + if (isNaN(id)) { + res.status(400).json({ error: 'Invalid id' }); + return; + } + const result = await appkit.lakebase.query( + 'UPDATE todos SET completed = NOT completed WHERE id = $1 RETURNING id, title, completed, created_at', + [id], + ); + if (result.rows.length === 0) { + res.status(404).json({ error: 'Todo not found' }); + return; + } + res.json(result.rows[0]); + } catch (err) { + console.error('Failed to update todo:', err); + res.status(500).json({ error: 'Failed to update todo' }); + } + }); + + app.delete('/api/lakebase/todos/:id', async (req, res) => { + try { + const id = parseInt(req.params.id, 10); + if (isNaN(id)) { + res.status(400).json({ error: 'Invalid id' }); + return; + } + const result = await appkit.lakebase.query( + 'DELETE FROM todos WHERE id = $1 RETURNING id', + [id], + ); + if (result.rows.length === 0) { + res.status(404).json({ error: 'Todo not found' }); + return; + } + res.status(204).send(); + } catch (err) { + console.error('Failed to delete todo:', err); + res.status(500).json({ error: 'Failed to delete todo' }); + } + }); + }); +} +{{- end}} diff --git a/template/server/server.ts b/template/server/server.ts index e5f3b323..a44a4626 100644 --- a/template/server/server.ts +++ b/template/server/server.ts @@ -1,7 +1,26 @@ -import { createApp, {{.plugin_imports}} } from '@databricks/appkit'; +import { createApp{{range $name, $_ := .plugins}}, {{$name}}{{end}} } from '@databricks/appkit'; +{{- if .plugins.lakebase}} +import { setupSampleLakebaseRoutes } from './routes/lakebase/todo-routes'; +{{- end}} createApp({ plugins: [ - {{.plugin_usages}} +{{- if .plugins.lakebase}} + server({ autoStart: false }), +{{- range $name, $_ := .plugins}} +{{- if ne $name "server"}} + {{$name}}(), +{{- end}} +{{- end}} +{{- else}} +{{- range $name, $_ := .plugins}} + {{$name}}(), +{{- end}} +{{- end}} ], +}).then(async (appkit) => { +{{- if .plugins.lakebase}} + await setupSampleLakebaseRoutes(appkit); + await appkit.server.start(); +{{- end}} }).catch(console.error); diff --git a/template/tsconfig.server.json b/template/tsconfig.server.json index 8cdada22..19ce069e 100644 --- a/template/tsconfig.server.json +++ b/template/tsconfig.server.json @@ -5,6 +5,10 @@ "target": "ES2020", "lib": ["ES2020"], + /* Modules */ + "module": "CommonJS", + "moduleResolution": "node", + /* Emit */ "outDir": "./dist", "rootDir": "./",