Merge pull request #288 from bknd-io/feat/deno-and-docs

deno: add to cli starters, improve `serveStaticViaImport` and add deno to the docs
This commit is contained in:
dswbx
2025-10-25 10:37:02 +02:00
committed by GitHub
13 changed files with 246 additions and 44 deletions

View File

@@ -154,23 +154,32 @@ export async function createRuntimeApp<Args = DefaultArgs>(
* }); * });
* ``` * ```
*/ */
export function serveStaticViaImport(opts?: { manifest?: Manifest }) { export function serveStaticViaImport(opts?: {
manifest?: Manifest;
appendRaw?: boolean;
package?: string;
}) {
let files: string[] | undefined; let files: string[] | undefined;
const pkg = opts?.package ?? "bknd";
// @ts-ignore // @ts-ignore
return async (c: Context, next: Next) => { return async (c: Context, next: Next) => {
if (!files) { if (!files) {
const manifest = const manifest =
opts?.manifest || opts?.manifest ||
((await import("bknd/dist/manifest.json", { with: { type: "json" } })) ((
.default as Manifest); await import(/* @vite-ignore */ `${pkg}/dist/manifest.json`, {
with: { type: "json" },
})
).default as Manifest);
files = Object.values(manifest).flatMap((asset) => [asset.file, ...(asset.css || [])]); files = Object.values(manifest).flatMap((asset) => [asset.file, ...(asset.css || [])]);
} }
const path = c.req.path.substring(1); const path = c.req.path.substring(1);
if (files.includes(path)) { if (files.includes(path)) {
try { try {
const content = await import(/* @vite-ignore */ `bknd/static/${path}?raw`, { const url = `${pkg}/static/${path}${opts?.appendRaw ? "?raw" : ""}`;
const content = await import(/* @vite-ignore */ url, {
with: { type: "text" }, with: { type: "text" },
}).then((m) => m.default); }).then((m) => m.default);
@@ -183,7 +192,7 @@ export function serveStaticViaImport(opts?: { manifest?: Manifest }) {
}); });
} }
} catch (e) { } catch (e) {
console.error("Error serving static file:", e); console.error(`Error serving static file "${path}":`, String(e));
return c.text("File not found", 404); return c.text("File not found", 404);
} }
} }

View File

@@ -10,6 +10,7 @@ import color from "picocolors";
import { overridePackageJson, updateBkndPackages } from "./npm"; import { overridePackageJson, updateBkndPackages } from "./npm";
import { type Template, templates, type TemplateSetupCtx } from "./templates"; import { type Template, templates, type TemplateSetupCtx } from "./templates";
import { createScoped, flush } from "cli/utils/telemetry"; import { createScoped, flush } from "cli/utils/telemetry";
import path from "node:path";
const config = { const config = {
types: { types: {
@@ -20,6 +21,7 @@ const config = {
node: "Node.js", node: "Node.js",
bun: "Bun", bun: "Bun",
cloudflare: "Cloudflare", cloudflare: "Cloudflare",
deno: "Deno",
aws: "AWS Lambda", aws: "AWS Lambda",
}, },
framework: { framework: {
@@ -259,17 +261,19 @@ async function action(options: {
} }
} }
// update package name // update package name if there is a package.json
await overridePackageJson( if (fs.existsSync(path.resolve(ctx.dir, "package.json"))) {
(pkg) => ({ await overridePackageJson(
...pkg, (pkg) => ({
name: ctx.name, ...pkg,
}), name: ctx.name,
{ dir: ctx.dir }, }),
); { dir: ctx.dir },
$p.log.success(`Updated package name to ${color.cyan(ctx.name)}`); );
$p.log.success(`Updated package name to ${color.cyan(ctx.name)}`);
}
{ if (template.installDeps !== false) {
const install = const install =
options.yes ?? options.yes ??
(await $p.confirm({ (await $p.confirm({

View File

@@ -93,17 +93,19 @@ export async function replacePackageJsonVersions(
} }
export async function updateBkndPackages(dir?: string, map?: Record<string, string>) { export async function updateBkndPackages(dir?: string, map?: Record<string, string>) {
const versions = { try {
bknd: await sysGetVersion(), const versions = {
...(map ?? {}), bknd: await sysGetVersion(),
}; ...(map ?? {}),
await replacePackageJsonVersions( };
async (pkg) => { await replacePackageJsonVersions(
if (pkg in versions) { async (pkg) => {
return versions[pkg]; if (pkg in versions) {
} return versions[pkg];
return; }
}, return;
{ dir }, },
); { dir },
);
} catch (e) {}
} }

View File

@@ -0,0 +1,21 @@
import { overrideJson } from "cli/commands/create/npm";
import type { Template } from "cli/commands/create/templates";
import { getVersion } from "cli/utils/sys";
export const deno = {
key: "deno",
title: "Deno Basic",
integration: "deno",
description: "A basic bknd Deno server with static assets",
path: "gh:bknd-io/bknd/examples/deno",
installDeps: false,
ref: true,
setup: async (ctx) => {
const version = await getVersion();
await overrideJson(
"deno.json",
(json) => ({ ...json, links: undefined, imports: { bknd: `npm:bknd@${version}` } }),
{ dir: ctx.dir },
);
},
} satisfies Template;

View File

@@ -1,3 +1,4 @@
import { deno } from "cli/commands/create/templates/deno";
import { cloudflare } from "./cloudflare"; import { cloudflare } from "./cloudflare";
export type TemplateSetupCtx = { export type TemplateSetupCtx = {
@@ -15,6 +16,7 @@ export type Integration =
| "react-router" | "react-router"
| "astro" | "astro"
| "aws" | "aws"
| "deno"
| "custom"; | "custom";
type TemplateScripts = "install" | "dev" | "build" | "start"; type TemplateScripts = "install" | "dev" | "build" | "start";
@@ -34,6 +36,11 @@ export type Template = {
* adds a ref "#{ref}" to the path. If "true", adds the current version of bknd * adds a ref "#{ref}" to the path. If "true", adds the current version of bknd
*/ */
ref?: true | string; ref?: true | string;
/**
* control whether to install dependencies automatically
* e.g. on deno, this is not needed
*/
installDeps?: boolean;
scripts?: Partial<Record<TemplateScripts, string>>; scripts?: Partial<Record<TemplateScripts, string>>;
preinstall?: (ctx: TemplateSetupCtx) => Promise<void>; preinstall?: (ctx: TemplateSetupCtx) => Promise<void>;
postinstall?: (ctx: TemplateSetupCtx) => Promise<void>; postinstall?: (ctx: TemplateSetupCtx) => Promise<void>;
@@ -90,4 +97,5 @@ export const templates: Template[] = [
path: "gh:bknd-io/bknd/examples/aws-lambda", path: "gh:bknd-io/bknd/examples/aws-lambda",
ref: true, ref: true,
}, },
deno,
]; ];

View File

@@ -0,0 +1,141 @@
---
title: "Deno"
description: "Run bknd inside Deno"
tags: ["documentation"]
---
## Installation
To get started with Deno and bknd you can either install the package manually, and follow the descriptions below, or use the CLI starter:
### CLI Starter
Create a new Deno CLI starter project by running the following command:
```sh
deno run npm:bknd create -i deno
```
### Manual
Deno is fully supported as a runtime for bknd. If you plan to solely use the API, the setup is pretty straightforward.
```ts title="main.ts"
import { createAdapterApp } from "npm:bknd/adapter";
const app = await createAdapterApp({
connection: {
url: "file:data.db",
},
});
export default {
fetch: app.fetch,
};
```
## Serve the Admin UI
In order to also serve the static assets of the admin UI, you have 3 choices:
1. Use the `serveStaticViaImport` function to serve the static assets from the `bknd` package directly. Requires unstable `raw-imports`, but it's the easiest way to serve the static assets.
2. Copy the static assets to your local project and use Hono's `serveStatic` middleware.
3. Use the `adminOptions.assetsPath` property to point to a remote address with the static assets.
### `serveStaticViaImport`
The `serveStaticViaImport` function is a middleware that serves the static assets from the `bknd` package directly using dynamic raw imports. It requires the unstable `raw-imports` feature to be enabled. You can enable it by adding the following to your `deno.json`:
```json title="deno.json"
{
"unstable": ["raw-imports"]
}
```
Or by using the `--unstable-raw-imports` flag when running your script. Now create a `main.ts` file to serve the API and static assets:
```ts title="main.ts"
import { createRuntimeApp, serveStaticViaImport } from "bknd/adapter";
const app = await createRuntimeApp({
connection: {
url: "file:data.db",
},
serveStatic: serveStaticViaImport()
});
export default {
fetch: app.fetch,
};
```
In case you don't want to point your bknd dependency to the latest version, either add an `imports` section to your `deno.json` file:
```json title="deno.json"
{
"imports": {
"bknd": "npm:bknd@<VERSION>" // [!code highlight]
}
}
```
Or specify the package with the version specified to the `serveStaticViaImport` function:
```ts
const app = await createRuntimeApp({
serveStatic: serveStaticViaImport({
package: "bknd@<VERSION>", // [!code highlight]
}),
});
```
Replace `<VERSION>` with the version you want to use.
### `serveStatic` from local files
You can also serve the static assets from your local project by using Hono's `serveStatic` middleware. You can do so by copying the static assets to your local project and using the `serveStatic` middleware. First, you have to copy the static assets, by running the following command:
```bash
deno run npm:bknd copy-assets --out public
```
This will copy the static assets to the `public` directory and then serve them from there:
```ts title="main.ts"
import { createRuntimeApp, serveStatic } from "bknd/adapter";
import { serveStatic } from "npm:hono/deno";
const app = await createRuntimeApp({
connection: {
url: "file:data.db",
},
serveStatic: serveStatic({
root: "./public",
}),
});
export default {
fetch: app.fetch,
};
```
### `adminOptions.assetsPath`
You can also use the `adminOptions.assetsPath` property to point to a remote address with the static assets. This is useful in case none of the other methods work for you.
```ts title="main.ts"
import { createRuntimeApp } from "bknd/adapter";
const app = await createRuntimeApp({
connection: {
url: "file:data.db",
},
adminOptions: {
assetsPath: "https://...",
},
});
export default {
fetch: app.fetch,
};
```

View File

@@ -1,3 +1,3 @@
{ {
"pages": ["node", "bun", "cloudflare", "aws", "docker"] "pages": ["node", "bun", "cloudflare", "deno", "aws", "docker"]
} }

View File

@@ -61,6 +61,12 @@ If you prefer to use a runtime instead of a framework, you can choose from the f
href="/integration/cloudflare" href="/integration/cloudflare"
/> />
<Card
icon={<Icon icon="simple-icons:deno" className="text-fd-primary !size-6" />}
title="Deno"
href="/integration/deno"
/>
<Card <Card
icon={<Icon icon="tabler:lambda" className="text-fd-primary !size-6" />} icon={<Icon icon="tabler:lambda" className="text-fd-primary !size-6" />}
title="AWS Lambda" title="AWS Lambda"

View File

@@ -97,6 +97,12 @@ Start by using the integration guide for these popular frameworks/runtimes. Ther
href="/integration/bun" href="/integration/bun"
/> />
<Card
icon={<Icon icon="simple-icons:deno" className="text-fd-primary !size-6" />}
title="Deno"
href="/integration/deno"
/>
<Card <Card
icon={<Icon icon="tabler:lambda" className="text-fd-primary !size-6" />} icon={<Icon icon="tabler:lambda" className="text-fd-primary !size-6" />}
title="AWS Lambda" title="AWS Lambda"

5
examples/.gitignore vendored
View File

@@ -1,2 +1,5 @@
*/package-lock.json */package-lock.json
*/bun.lock */bun.lock
*/deno.lock
*/node_modules
*/*.db

11
examples/deno/deno.json Normal file
View File

@@ -0,0 +1,11 @@
{
"nodeModulesDir": "auto",
"tasks": {
"dev": "deno serve -A --watch main.ts"
},
"imports": {
"bknd": "npm:bknd@0.19.0-rc.1"
},
"links": ["../../app/"],
"unstable": ["raw-imports"]
}

View File

@@ -1,14 +1,12 @@
import { createRuntimeApp } from "bknd/adapter"; import { createRuntimeApp, serveStaticViaImport } from "bknd/adapter";
const app = await createRuntimeApp({ const app = await createRuntimeApp({
connection: { connection: {
url: "file:./data.db", url: "file:./data.db",
}, },
adminOptions: { serveStatic: serveStaticViaImport(),
// currently needs a hosted version of the static assets
assetsPath: "https://cdn.bknd.io/bknd/static/0.15.0-rc.9/",
},
}); });
// @ts-ignore export default {
Deno.serve(app.fetch); fetch: app.fetch,
};

View File

@@ -1,7 +0,0 @@
{
"name": "bknd-deno-example",
"private": true,
"dependencies": {
"bknd": "file:../../app"
}
}