diff --git a/app/src/adapter/index.ts b/app/src/adapter/index.ts index 949aaba..79f4c97 100644 --- a/app/src/adapter/index.ts +++ b/app/src/adapter/index.ts @@ -154,23 +154,32 @@ export async function createRuntimeApp( * }); * ``` */ -export function serveStaticViaImport(opts?: { manifest?: Manifest }) { +export function serveStaticViaImport(opts?: { + manifest?: Manifest; + appendRaw?: boolean; + package?: string; +}) { let files: string[] | undefined; + const pkg = opts?.package ?? "bknd"; // @ts-ignore return async (c: Context, next: Next) => { if (!files) { const 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 || [])]); } const path = c.req.path.substring(1); if (files.includes(path)) { 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" }, }).then((m) => m.default); @@ -183,7 +192,7 @@ export function serveStaticViaImport(opts?: { manifest?: Manifest }) { }); } } 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); } } diff --git a/app/src/cli/commands/create/create.ts b/app/src/cli/commands/create/create.ts index 217b07d..3fca3b2 100644 --- a/app/src/cli/commands/create/create.ts +++ b/app/src/cli/commands/create/create.ts @@ -10,6 +10,7 @@ import color from "picocolors"; import { overridePackageJson, updateBkndPackages } from "./npm"; import { type Template, templates, type TemplateSetupCtx } from "./templates"; import { createScoped, flush } from "cli/utils/telemetry"; +import path from "node:path"; const config = { types: { @@ -20,6 +21,7 @@ const config = { node: "Node.js", bun: "Bun", cloudflare: "Cloudflare", + deno: "Deno", aws: "AWS Lambda", }, framework: { @@ -259,17 +261,19 @@ async function action(options: { } } - // update package name - await overridePackageJson( - (pkg) => ({ - ...pkg, - name: ctx.name, - }), - { dir: ctx.dir }, - ); - $p.log.success(`Updated package name to ${color.cyan(ctx.name)}`); + // update package name if there is a package.json + if (fs.existsSync(path.resolve(ctx.dir, "package.json"))) { + await overridePackageJson( + (pkg) => ({ + ...pkg, + name: ctx.name, + }), + { dir: ctx.dir }, + ); + $p.log.success(`Updated package name to ${color.cyan(ctx.name)}`); + } - { + if (template.installDeps !== false) { const install = options.yes ?? (await $p.confirm({ diff --git a/app/src/cli/commands/create/npm.ts b/app/src/cli/commands/create/npm.ts index 7722e1c..964ee47 100644 --- a/app/src/cli/commands/create/npm.ts +++ b/app/src/cli/commands/create/npm.ts @@ -93,17 +93,19 @@ export async function replacePackageJsonVersions( } export async function updateBkndPackages(dir?: string, map?: Record) { - const versions = { - bknd: await sysGetVersion(), - ...(map ?? {}), - }; - await replacePackageJsonVersions( - async (pkg) => { - if (pkg in versions) { - return versions[pkg]; - } - return; - }, - { dir }, - ); + try { + const versions = { + bknd: await sysGetVersion(), + ...(map ?? {}), + }; + await replacePackageJsonVersions( + async (pkg) => { + if (pkg in versions) { + return versions[pkg]; + } + return; + }, + { dir }, + ); + } catch (e) {} } diff --git a/app/src/cli/commands/create/templates/deno.ts b/app/src/cli/commands/create/templates/deno.ts new file mode 100644 index 0000000..eb17269 --- /dev/null +++ b/app/src/cli/commands/create/templates/deno.ts @@ -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; diff --git a/app/src/cli/commands/create/templates/index.ts b/app/src/cli/commands/create/templates/index.ts index ed0f9e1..7aab8d5 100644 --- a/app/src/cli/commands/create/templates/index.ts +++ b/app/src/cli/commands/create/templates/index.ts @@ -1,3 +1,4 @@ +import { deno } from "cli/commands/create/templates/deno"; import { cloudflare } from "./cloudflare"; export type TemplateSetupCtx = { @@ -15,6 +16,7 @@ export type Integration = | "react-router" | "astro" | "aws" + | "deno" | "custom"; 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 */ ref?: true | string; + /** + * control whether to install dependencies automatically + * e.g. on deno, this is not needed + */ + installDeps?: boolean; scripts?: Partial>; preinstall?: (ctx: TemplateSetupCtx) => Promise; postinstall?: (ctx: TemplateSetupCtx) => Promise; @@ -90,4 +97,5 @@ export const templates: Template[] = [ path: "gh:bknd-io/bknd/examples/aws-lambda", ref: true, }, + deno, ]; diff --git a/docs/content/docs/(documentation)/integration/(runtimes)/deno.mdx b/docs/content/docs/(documentation)/integration/(runtimes)/deno.mdx new file mode 100644 index 0000000..dbebcaa --- /dev/null +++ b/docs/content/docs/(documentation)/integration/(runtimes)/deno.mdx @@ -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@" // [!code highlight] + } +} +``` + +Or specify the package with the version specified to the `serveStaticViaImport` function: + +```ts +const app = await createRuntimeApp({ + serveStatic: serveStaticViaImport({ + package: "bknd@", // [!code highlight] + }), +}); +``` + +Replace `` 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, +}; +``` \ No newline at end of file diff --git a/docs/content/docs/(documentation)/integration/(runtimes)/meta.json b/docs/content/docs/(documentation)/integration/(runtimes)/meta.json index 9083adc..33c50b3 100644 --- a/docs/content/docs/(documentation)/integration/(runtimes)/meta.json +++ b/docs/content/docs/(documentation)/integration/(runtimes)/meta.json @@ -1,3 +1,3 @@ { - "pages": ["node", "bun", "cloudflare", "aws", "docker"] + "pages": ["node", "bun", "cloudflare", "deno", "aws", "docker"] } diff --git a/docs/content/docs/(documentation)/integration/introduction.mdx b/docs/content/docs/(documentation)/integration/introduction.mdx index 6d67d94..6330208 100644 --- a/docs/content/docs/(documentation)/integration/introduction.mdx +++ b/docs/content/docs/(documentation)/integration/introduction.mdx @@ -61,6 +61,12 @@ If you prefer to use a runtime instead of a framework, you can choose from the f href="/integration/cloudflare" /> +} + title="Deno" + href="/integration/deno" +/> + } title="AWS Lambda" diff --git a/docs/content/docs/(documentation)/start.mdx b/docs/content/docs/(documentation)/start.mdx index 8e664c6..91bfdd7 100644 --- a/docs/content/docs/(documentation)/start.mdx +++ b/docs/content/docs/(documentation)/start.mdx @@ -97,6 +97,12 @@ Start by using the integration guide for these popular frameworks/runtimes. Ther href="/integration/bun" /> +} + title="Deno" + href="/integration/deno" +/> + } title="AWS Lambda" diff --git a/examples/.gitignore b/examples/.gitignore index f0f60a6..d305846 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -1,2 +1,5 @@ */package-lock.json -*/bun.lock \ No newline at end of file +*/bun.lock +*/deno.lock +*/node_modules +*/*.db \ No newline at end of file diff --git a/examples/deno/deno.json b/examples/deno/deno.json new file mode 100644 index 0000000..6e8656b --- /dev/null +++ b/examples/deno/deno.json @@ -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"] +} diff --git a/examples/deno/main.ts b/examples/deno/main.ts index 58e052f..68aba30 100644 --- a/examples/deno/main.ts +++ b/examples/deno/main.ts @@ -1,14 +1,12 @@ -import { createRuntimeApp } from "bknd/adapter"; +import { createRuntimeApp, serveStaticViaImport } from "bknd/adapter"; const app = await createRuntimeApp({ connection: { url: "file:./data.db", }, - adminOptions: { - // currently needs a hosted version of the static assets - assetsPath: "https://cdn.bknd.io/bknd/static/0.15.0-rc.9/", - }, + serveStatic: serveStaticViaImport(), }); -// @ts-ignore -Deno.serve(app.fetch); +export default { + fetch: app.fetch, +}; diff --git a/examples/deno/package.json b/examples/deno/package.json deleted file mode 100644 index 97faf72..0000000 --- a/examples/deno/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "bknd-deno-example", - "private": true, - "dependencies": { - "bknd": "file:../../app" - } -}