mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-15 20:17:22 +00:00
updated examples: astro, nextjs, remix, bun, node
This commit is contained in:
37
app/__test__/media/mime-types.spec.ts
Normal file
37
app/__test__/media/mime-types.spec.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import * as large from "../../src/media/storage/mime-types";
|
||||
import * as tiny from "../../src/media/storage/mime-types-tiny";
|
||||
|
||||
describe("media/mime-types", () => {
|
||||
test("tiny resolves", () => {
|
||||
const tests = [[".mp4", "video/mp4", ".jpg", "image/jpeg", ".zip", "application/zip"]];
|
||||
|
||||
for (const [ext, mime] of tests) {
|
||||
expect(tiny.guess(ext)).toBe(mime);
|
||||
}
|
||||
});
|
||||
|
||||
test("all tiny resolves to large", () => {
|
||||
for (const [ext, mime] of Object.entries(tiny.M)) {
|
||||
expect(large.guessMimeType("." + ext)).toBe(mime);
|
||||
}
|
||||
|
||||
for (const [type, exts] of Object.entries(tiny.Q)) {
|
||||
for (const ext of exts) {
|
||||
const ex = `${type}/${ext}`;
|
||||
try {
|
||||
expect(large.guessMimeType("." + ext)).toBe(ex);
|
||||
} catch (e) {
|
||||
console.log(`Failed for ${ext}`, {
|
||||
type,
|
||||
exts,
|
||||
ext,
|
||||
expected: ex,
|
||||
actual: large.guessMimeType("." + ext)
|
||||
});
|
||||
throw new Error(`Failed for ${ext}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -128,6 +128,14 @@ export class Api {
|
||||
};
|
||||
}
|
||||
|
||||
async getVerifiedAuthState(force?: boolean): Promise<AuthState> {
|
||||
if (force === true || !this.verified) {
|
||||
await this.verifyAuth();
|
||||
}
|
||||
|
||||
return this.getAuthState();
|
||||
}
|
||||
|
||||
async verifyAuth() {
|
||||
try {
|
||||
const res = await this.auth.me();
|
||||
|
||||
@@ -18,11 +18,11 @@ export function getApi(Astro: TAstro, options: Options = { mode: "static" }) {
|
||||
}
|
||||
|
||||
let app: App;
|
||||
export function serve(config: CreateAppConfig) {
|
||||
export function serve(config: CreateAppConfig & { beforeBuild?: (app: App) => Promise<void> }) {
|
||||
return async (args: TAstro) => {
|
||||
if (!app) {
|
||||
app = App.create(config);
|
||||
|
||||
await config.beforeBuild?.(app);
|
||||
await app.build();
|
||||
}
|
||||
return app.fetch(args.request);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
/// <reference types="bun-types" />
|
||||
|
||||
import path from "node:path";
|
||||
import { App, type CreateAppConfig } from "bknd";
|
||||
import { App, type CreateAppConfig, registries } from "bknd";
|
||||
import type { Serve, ServeOptions } from "bun";
|
||||
import { serveStatic } from "hono/bun";
|
||||
import { registerLocalMediaAdapter } from "../index";
|
||||
|
||||
let app: App;
|
||||
export type ExtendedAppCreateConfig = Partial<CreateAppConfig> & {
|
||||
@@ -18,6 +19,7 @@ export async function createApp({
|
||||
buildOptions,
|
||||
...config
|
||||
}: ExtendedAppCreateConfig) {
|
||||
registerLocalMediaAdapter();
|
||||
const root = path.resolve(distPath ?? "./node_modules/bknd/dist", "static");
|
||||
|
||||
if (!app) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { IncomingMessage } from "node:http";
|
||||
import type { App, CreateAppConfig } from "bknd";
|
||||
import { type App, type CreateAppConfig, registries } from "bknd";
|
||||
import { StorageLocalAdapter } from "media/storage/adapters/StorageLocalAdapter";
|
||||
|
||||
export type CloudflareBkndConfig<Env = any> = {
|
||||
mode?: "warm" | "fresh" | "cache" | "durable";
|
||||
@@ -47,3 +48,7 @@ export function nodeRequestToRequest(req: IncomingMessage): Request {
|
||||
headers
|
||||
});
|
||||
}
|
||||
|
||||
export function registerLocalMediaAdapter() {
|
||||
registries.media.register("local", StorageLocalAdapter);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ type GetServerSidePropsContext = {
|
||||
|
||||
export function createApi({ req }: GetServerSidePropsContext) {
|
||||
const request = nodeRequestToRequest(req);
|
||||
//console.log("createApi:request.headers", request.headers);
|
||||
return new Api({
|
||||
host: new URL(request.url).origin,
|
||||
headers: request.headers
|
||||
@@ -43,10 +42,11 @@ function getCleanRequest(req: Request) {
|
||||
}
|
||||
|
||||
let app: App;
|
||||
export function serve(config: CreateAppConfig) {
|
||||
export function serve(config: CreateAppConfig & { beforeBuild?: (app: App) => Promise<void> }) {
|
||||
return async (req: Request) => {
|
||||
if (!app) {
|
||||
app = App.create(config);
|
||||
await config.beforeBuild?.(app);
|
||||
await app.build();
|
||||
}
|
||||
const request = getCleanRequest(req);
|
||||
|
||||
@@ -3,3 +3,4 @@ export {
|
||||
StorageLocalAdapter,
|
||||
type LocalAdapterConfig
|
||||
} from "../../media/storage/adapters/StorageLocalAdapter";
|
||||
export { registerLocalMediaAdapter } from "../index";
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import path from "node:path";
|
||||
import { serve as honoServe } from "@hono/node-server";
|
||||
import { serveStatic } from "@hono/node-server/serve-static";
|
||||
import { App, type CreateAppConfig } from "bknd";
|
||||
import { App, type CreateAppConfig, registries } from "bknd";
|
||||
import { registerLocalMediaAdapter } from "../index";
|
||||
|
||||
export type NodeAdapterOptions = CreateAppConfig & {
|
||||
relativeDistPath?: string;
|
||||
@@ -21,6 +22,8 @@ export function serve({
|
||||
buildOptions = {},
|
||||
...config
|
||||
}: NodeAdapterOptions = {}) {
|
||||
registerLocalMediaAdapter();
|
||||
|
||||
const root = path.relative(
|
||||
process.cwd(),
|
||||
path.resolve(relativeDistPath ?? "./node_modules/bknd/dist", "static")
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { App, type CreateAppConfig } from "bknd";
|
||||
|
||||
let app: App;
|
||||
export function serve(config: CreateAppConfig) {
|
||||
export function serve(config: CreateAppConfig & { beforeBuild?: (app: App) => Promise<void> }) {
|
||||
return async (args: { request: Request }) => {
|
||||
if (!app) {
|
||||
app = App.create(config);
|
||||
await config.beforeBuild?.(app);
|
||||
await app.build();
|
||||
}
|
||||
return app.fetch(args.request);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { type AuthAction, Authenticator, type ProfileExchange, Role, type Strategy } from "auth";
|
||||
import type { PasswordStrategy } from "auth/authenticate/strategies";
|
||||
import { Exception } from "core";
|
||||
import { type Static, secureRandomString, transformObject } from "core/utils";
|
||||
import { type Entity, EntityIndex, type EntityManager } from "data";
|
||||
@@ -284,6 +285,21 @@ export class AppAuth extends Module<typeof authConfigSchema> {
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
async createUser(input: { email: string; password: string }) {
|
||||
const strategy = "password";
|
||||
const pw = this.authenticator.strategy(strategy) as PasswordStrategy;
|
||||
const strategy_value = await pw.hash(input.password);
|
||||
const mutator = this.em.mutator(this.config.entity_name as "users");
|
||||
mutator.__unstable_toggleSystemEntityCreation(false);
|
||||
const { data: created } = await mutator.insertOne({
|
||||
email: input.email,
|
||||
strategy,
|
||||
strategy_value
|
||||
});
|
||||
mutator.__unstable_toggleSystemEntityCreation(true);
|
||||
return created;
|
||||
}
|
||||
|
||||
override toJSON(secrets?: boolean): AppAuthSchema {
|
||||
if (!this.config.enabled) {
|
||||
return this.configDefault;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { readFile, readdir, stat, unlink, writeFile } from "node:fs/promises";
|
||||
import { type Static, Type, parse } from "core/utils";
|
||||
import type { FileBody, FileListObject, FileMeta, StorageAdapter } from "../../Storage";
|
||||
import { guessMimeType } from "../../mime-types";
|
||||
import { guess } from "../../mime-types-tiny";
|
||||
|
||||
export const localAdapterConfig = Type.Object(
|
||||
{
|
||||
@@ -83,7 +83,7 @@ export class StorageLocalAdapter implements StorageAdapter {
|
||||
async getObject(key: string, headers: Headers): Promise<Response> {
|
||||
try {
|
||||
const content = await readFile(`${this.config.path}/${key}`);
|
||||
const mimeType = guessMimeType(key);
|
||||
const mimeType = guess(key);
|
||||
|
||||
return new Response(content, {
|
||||
status: 200,
|
||||
@@ -105,7 +105,7 @@ export class StorageLocalAdapter implements StorageAdapter {
|
||||
async getObjectMeta(key: string): Promise<FileMeta> {
|
||||
const stats = await stat(`${this.config.path}/${key}`);
|
||||
return {
|
||||
type: guessMimeType(key) || "application/octet-stream",
|
||||
type: guess(key) || "application/octet-stream",
|
||||
size: stats.size
|
||||
};
|
||||
}
|
||||
|
||||
77
app/src/media/storage/mime-types-tiny.ts
Normal file
77
app/src/media/storage/mime-types-tiny.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
export const Q = {
|
||||
video: ["mp4", "webm"],
|
||||
audio: ["ogg"],
|
||||
image: ["jpeg", "png", "gif", "webp", "bmp", "tiff"],
|
||||
text: ["html", "css", "mdx", "yaml", "vcard", "csv", "vtt"],
|
||||
application: ["zip", "xml", "toml", "json", "json5"],
|
||||
font: ["woff", "woff2", "ttf", "otf"]
|
||||
} as const;
|
||||
|
||||
// reduced
|
||||
const c = {
|
||||
vnd: "vnd.openxmlformats-officedocument",
|
||||
z: "application/x-7z-compressed",
|
||||
t: (w = "plain") => `text/${w}`,
|
||||
a: (w = "octet-stream") => `application/${w}`,
|
||||
i: (w) => `image/${w}`,
|
||||
v: (w) => `video/${w}`
|
||||
} as const;
|
||||
export const M = new Map<string, string>([
|
||||
["7z", c.z],
|
||||
["7zip", c.z],
|
||||
["ai", c.a("pdf")],
|
||||
["apk", c.a("vnd.android.package-archive")],
|
||||
["doc", c.a("msword")],
|
||||
["docx", `${c.vnd}.wordprocessingml.document`],
|
||||
["eps", c.a("postscript")],
|
||||
["epub", c.a("epub+zip")],
|
||||
["ini", c.t()],
|
||||
["jar", c.a("java-archive")],
|
||||
["jsonld", c.a("ld+json")],
|
||||
["jpg", c.i("jpeg")],
|
||||
["log", c.t()],
|
||||
["m3u", c.t()],
|
||||
["m3u8", c.a("vnd.apple.mpegurl")],
|
||||
["manifest", c.t("cache-manifest")],
|
||||
["md", c.t("markdown")],
|
||||
["mkv", c.v("x-matroska")],
|
||||
["mp3", c.a("mpeg")],
|
||||
["mobi", c.a("x-mobipocket-ebook")],
|
||||
["ppt", c.a("powerpoint")],
|
||||
["pptx", `${c.vnd}.presentationml.presentation`],
|
||||
["qt", c.v("quicktime")],
|
||||
["svg", c.i("svg+xml")],
|
||||
["tif", c.i("tiff")],
|
||||
["tsv", c.t("tab-separated-values")],
|
||||
["tgz", c.a("x-tar")],
|
||||
["txt", c.t()],
|
||||
["text", c.t()],
|
||||
["vcd", c.a("x-cdlink")],
|
||||
["vcs", c.t("x-vcalendar")],
|
||||
["wav", c.a("x-wav")],
|
||||
["webmanifest", c.a("manifest+json")],
|
||||
["xls", c.a("vnd.ms-excel")],
|
||||
["xlsx", `${c.vnd}.spreadsheetml.sheet`],
|
||||
["yml", c.t("yaml")]
|
||||
]);
|
||||
|
||||
export function guess(f: string): string {
|
||||
try {
|
||||
const e = f.split(".").pop() as string;
|
||||
if (!e) {
|
||||
return c.a();
|
||||
}
|
||||
|
||||
// try quick first
|
||||
for (const [t, _e] of Object.entries(Q)) {
|
||||
// @ts-ignore
|
||||
if (_e.includes(e)) {
|
||||
return `${t}/${e}`;
|
||||
}
|
||||
}
|
||||
|
||||
return M.get(e!) as string;
|
||||
} catch (e) {
|
||||
return c.a();
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,7 @@ const Skeleton = ({ theme = "light" }: { theme?: string }) => {
|
||||
className="flex flex-row w-full h-16 gap-2.5 border-muted border-b justify-start bg-muted/10"
|
||||
>
|
||||
<div className="max-h-full flex hover:bg-primary/5 link p-2.5 w-[134px] outline-none">
|
||||
<Logo />
|
||||
<Logo theme={theme} />
|
||||
</div>
|
||||
<nav className="hidden md:flex flex-row gap-2.5 pl-0 p-2.5 items-center">
|
||||
{[...new Array(5)].map((item, key) => (
|
||||
|
||||
@@ -42,7 +42,11 @@ const useLocationFromRouter = (router) => {
|
||||
];
|
||||
};
|
||||
|
||||
export function Link({ className, ...props }: { className?: string } & LinkProps) {
|
||||
export function Link({
|
||||
className,
|
||||
native,
|
||||
...props
|
||||
}: { className?: string; native?: boolean } & LinkProps) {
|
||||
const router = useRouter();
|
||||
const [path, navigate] = useLocationFromRouter(router);
|
||||
|
||||
@@ -55,8 +59,6 @@ export function Link({ className, ...props }: { className?: string } & LinkProps
|
||||
return false;
|
||||
}
|
||||
|
||||
function handleClick(e) {}
|
||||
|
||||
const _href = props.href ?? props.to;
|
||||
const href = router
|
||||
.hrefs(
|
||||
@@ -72,6 +74,10 @@ export function Link({ className, ...props }: { className?: string } & LinkProps
|
||||
/*if (active) {
|
||||
console.log("link", { a, path, absPath, href, to, active, router });
|
||||
}*/
|
||||
if (native) {
|
||||
return <a className={`${active ? "active " : ""}${className}`} {...props} />;
|
||||
}
|
||||
|
||||
return (
|
||||
// @ts-expect-error className is not typed on WouterLink
|
||||
<WouterLink className={`${active ? "active " : ""}${className}`} {...props} />
|
||||
|
||||
@@ -116,7 +116,7 @@ function SidebarToggler() {
|
||||
export function Header({ hasSidebar = true }) {
|
||||
//const logoReturnPath = "";
|
||||
const { app } = useBknd();
|
||||
const logoReturnPath = app.getAdminConfig().logo_return_path ?? "/";
|
||||
const { logo_return_path = "/", color_scheme = "light" } = app.getAdminConfig();
|
||||
|
||||
return (
|
||||
<header
|
||||
@@ -124,11 +124,11 @@ export function Header({ hasSidebar = true }) {
|
||||
className="flex flex-row w-full h-16 gap-2.5 border-muted border-b justify-start bg-muted/10"
|
||||
>
|
||||
<Link
|
||||
href={logoReturnPath}
|
||||
replace
|
||||
href={logo_return_path}
|
||||
native={logo_return_path !== "/"}
|
||||
className="max-h-full flex hover:bg-primary/5 link p-2.5 w-[134px] outline-none"
|
||||
>
|
||||
<Logo />
|
||||
<Logo theme={color_scheme} />
|
||||
</Link>
|
||||
<HeaderNavigation />
|
||||
<div className="flex flex-grow" />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"_variables": {
|
||||
"lastUpdateCheck": 1732785435939
|
||||
"lastUpdateCheck": 1734966049246
|
||||
}
|
||||
}
|
||||
3
examples/astro/.gitignore
vendored
3
examples/astro/.gitignore
vendored
@@ -21,4 +21,5 @@ pnpm-debug.log*
|
||||
.DS_Store
|
||||
|
||||
# jetbrains setting folder
|
||||
.idea/
|
||||
.idea/
|
||||
*.db
|
||||
@@ -14,7 +14,7 @@ export const prerender = false;
|
||||
<body>
|
||||
<Admin
|
||||
withProvider={{ user }}
|
||||
config={{ basepath: "/admin", color_scheme: "dark" }}
|
||||
config={{ basepath: "/admin", color_scheme: "dark", logo_return_path: "/../" }}
|
||||
client:only
|
||||
/>
|
||||
</body>
|
||||
|
||||
@@ -1,12 +1,70 @@
|
||||
import { App } from "bknd";
|
||||
import { serve } from "bknd/adapter/astro";
|
||||
import { registerLocalMediaAdapter } from "bknd/adapter/node";
|
||||
import { boolean, em, entity, text } from "bknd/data";
|
||||
import { secureRandomString } from "bknd/utils";
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
// since we're running in node, we can register the local media adapter
|
||||
registerLocalMediaAdapter();
|
||||
|
||||
export const ALL = serve({
|
||||
// we can use any libsql config, and if omitted, uses in-memory
|
||||
connection: {
|
||||
type: "libsql",
|
||||
config: {
|
||||
url: "file:test.db"
|
||||
}
|
||||
},
|
||||
// an initial config is only applied if the database is empty
|
||||
initialConfig: {
|
||||
// the em() function makes it easy to create an initial schema
|
||||
data: em({
|
||||
todos: entity("todos", {
|
||||
title: text(),
|
||||
done: boolean()
|
||||
})
|
||||
}).toJSON(),
|
||||
// we're enabling auth ...
|
||||
auth: {
|
||||
enabled: true,
|
||||
jwt: {
|
||||
secret: secureRandomString(64)
|
||||
}
|
||||
},
|
||||
// ... and media
|
||||
media: {
|
||||
enabled: true,
|
||||
adapter: {
|
||||
type: "local",
|
||||
config: {
|
||||
path: "./public"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
options: {
|
||||
// the seed option is only executed if the database was empty
|
||||
seed: async (ctx) => {
|
||||
await ctx.em.mutator("todos").insertMany([
|
||||
{ title: "Learn bknd", done: true },
|
||||
{ title: "Build something cool", done: false }
|
||||
]);
|
||||
}
|
||||
},
|
||||
// here we can hook into the app lifecycle events ...
|
||||
beforeBuild: async (app) => {
|
||||
app.emgr.onEvent(
|
||||
App.Events.AppFirstBoot,
|
||||
async () => {
|
||||
// ... to create an initial user
|
||||
await app.module.auth.createUser({
|
||||
email: "ds@bknd.io",
|
||||
password: "12345678"
|
||||
});
|
||||
},
|
||||
"sync"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Binary file not shown.
1
examples/nextjs/.gitignore
vendored
1
examples/nextjs/.gitignore
vendored
@@ -38,3 +38,4 @@ yarn-error.log*
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
!test.db
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { withApi } from "bknd/adapter/nextjs";
|
||||
import type { InferGetServerSidePropsType } from "next";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
import { withApi } from "bknd/adapter/nextjs";
|
||||
import "bknd/dist/styles.css";
|
||||
|
||||
const Admin = dynamic(() => import("bknd/ui").then((mod) => mod.Admin), {
|
||||
@@ -14,7 +16,11 @@ export const getServerSideProps = withApi(async (context) => {
|
||||
};
|
||||
});
|
||||
|
||||
export default function AdminPage() {
|
||||
export default function AdminPage({
|
||||
user
|
||||
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
|
||||
if (typeof document === "undefined") return null;
|
||||
return <Admin withProvider config={{ basepath: "/admin" }} />;
|
||||
return (
|
||||
<Admin withProvider={{ user }} config={{ basepath: "/admin", logo_return_path: "/../" }} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { serve } from "bknd/adapter/nextjs";
|
||||
|
||||
export const config = {
|
||||
runtime: "experimental-edge",
|
||||
runtime: "edge",
|
||||
// add a matcher for bknd dist to allow dynamic otherwise build may fail.
|
||||
// inside this repo it's '../../app/dist/index.js', outside probably inside node_modules
|
||||
// see https://github.com/vercel/next.js/issues/51401
|
||||
|
||||
1
examples/remix/.gitignore
vendored
1
examples/remix/.gitignore
vendored
@@ -3,3 +3,4 @@ node_modules
|
||||
/.cache
|
||||
/build
|
||||
.env
|
||||
*.db
|
||||
@@ -35,8 +35,9 @@ export const loader = async (args: LoaderFunctionArgs) => {
|
||||
// add api to the context
|
||||
args.context.api = api;
|
||||
|
||||
await api.verifyAuth();
|
||||
return {
|
||||
user: api.getAuthState().user
|
||||
user: api.getAuthState()?.user
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ export const meta: MetaFunction = () => {
|
||||
|
||||
export const loader = async (args: LoaderFunctionArgs) => {
|
||||
const api = args.context.api;
|
||||
const user = api.getAuthState().user;
|
||||
const user = (await api.getVerifiedAuthState()).user;
|
||||
const { data } = await api.data.readMany("todos");
|
||||
return { data, user };
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import "bknd/dist/styles.css";
|
||||
|
||||
export default adminPage({
|
||||
config: {
|
||||
basepath: "/admin"
|
||||
basepath: "/admin",
|
||||
logo_return_path: "/../"
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,11 +1,70 @@
|
||||
import { App } from "bknd";
|
||||
import { registerLocalMediaAdapter } from "bknd/adapter/node";
|
||||
import { serve } from "bknd/adapter/remix";
|
||||
import { boolean, em, entity, text } from "bknd/data";
|
||||
import { secureRandomString } from "bknd/utils";
|
||||
|
||||
// since we're running in node, we can register the local media adapter
|
||||
registerLocalMediaAdapter();
|
||||
|
||||
const handler = serve({
|
||||
// we can use any libsql config, and if omitted, uses in-memory
|
||||
connection: {
|
||||
type: "libsql",
|
||||
config: {
|
||||
url: "file:test.db"
|
||||
}
|
||||
},
|
||||
// an initial config is only applied if the database is empty
|
||||
initialConfig: {
|
||||
// the em() function makes it easy to create an initial schema
|
||||
data: em({
|
||||
todos: entity("todos", {
|
||||
title: text(),
|
||||
done: boolean()
|
||||
})
|
||||
}).toJSON(),
|
||||
// we're enabling auth ...
|
||||
auth: {
|
||||
enabled: true,
|
||||
jwt: {
|
||||
issuer: "bknd-remix-example",
|
||||
secret: secureRandomString(64)
|
||||
}
|
||||
},
|
||||
// ... and media
|
||||
media: {
|
||||
enabled: true,
|
||||
adapter: {
|
||||
type: "local",
|
||||
config: {
|
||||
path: "./public"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
options: {
|
||||
// the seed option is only executed if the database was empty
|
||||
seed: async (ctx) => {
|
||||
await ctx.em.mutator("todos").insertMany([
|
||||
{ title: "Learn bknd", done: true },
|
||||
{ title: "Build something cool", done: false }
|
||||
]);
|
||||
}
|
||||
},
|
||||
// here we can hook into the app lifecycle events ...
|
||||
beforeBuild: async (app) => {
|
||||
app.emgr.onEvent(
|
||||
App.Events.AppFirstBoot,
|
||||
async () => {
|
||||
// ... to create an initial user
|
||||
await app.module.auth.createUser({
|
||||
email: "ds@bknd.io",
|
||||
password: "12345678"
|
||||
});
|
||||
},
|
||||
"sync"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -12,17 +12,17 @@
|
||||
"typecheck": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@remix-run/node": "^2.14.0",
|
||||
"@remix-run/react": "^2.14.0",
|
||||
"@remix-run/serve": "^2.14.0",
|
||||
"@remix-run/node": "^2.15.2",
|
||||
"@remix-run/react": "^2.15.2",
|
||||
"@remix-run/serve": "^2.15.2",
|
||||
"bknd": "workspace:*",
|
||||
"isbot": "^4.1.0",
|
||||
"isbot": "^5.1.18",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"remix-utils": "^7.7.0"
|
||||
"remix-utils": "^8.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@remix-run/dev": "^2.14.0",
|
||||
"@remix-run/dev": "^2.15.2",
|
||||
"@types/react": "^18.2.20",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"typescript": "^5.1.6",
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user