Merge pull request #59 from bknd-io/feat/adapter-auth-env

Adapter: verified auth & env-specific construction
This commit is contained in:
dswbx
2025-01-25 13:36:59 +01:00
committed by GitHub
21 changed files with 131 additions and 110 deletions

View File

@@ -180,6 +180,14 @@ function baseConfig(adapter: string): tsup.Options {
}; };
} }
// base adapter handles
await tsup.build({
...baseConfig(""),
entry: ["src/adapter/index.ts"],
outDir: "dist/adapter"
});
// specific adatpers
await tsup.build(baseConfig("remix")); await tsup.build(baseConfig("remix"));
await tsup.build(baseConfig("bun")); await tsup.build(baseConfig("bun"));
await tsup.build(baseConfig("astro")); await tsup.build(baseConfig("astro"));

View File

@@ -148,6 +148,10 @@
"import": "./dist/adapter/cloudflare/index.js", "import": "./dist/adapter/cloudflare/index.js",
"require": "./dist/adapter/cloudflare/index.cjs" "require": "./dist/adapter/cloudflare/index.cjs"
}, },
"./adapter": {
"types": "./dist/types/adapter/index.d.ts",
"import": "./dist/adapter/index.js"
},
"./adapter/vite": { "./adapter/vite": {
"types": "./dist/types/adapter/vite/index.d.ts", "types": "./dist/types/adapter/vite/index.d.ts",
"import": "./dist/adapter/vite/index.js", "import": "./dist/adapter/vite/index.js",

View File

@@ -1,7 +1,7 @@
import { type FrameworkBkndConfig, createFrameworkApp } from "adapter"; import { type FrameworkBkndConfig, createFrameworkApp } from "adapter";
import { Api, type ApiOptions, type App } from "bknd"; import { Api, type ApiOptions, type App } from "bknd";
export type AstroBkndConfig = FrameworkBkndConfig; export type AstroBkndConfig<Args = TAstro> = FrameworkBkndConfig<Args>;
type TAstro = { type TAstro = {
request: Request; request: Request;
@@ -13,18 +13,20 @@ export type Options = {
host?: string; host?: string;
}; };
export function getApi(Astro: TAstro, options: Options = { mode: "static" }) { export async function getApi(Astro: TAstro, options: Options = { mode: "static" }) {
return new Api({ const api = new Api({
host: new URL(Astro.request.url).origin, host: new URL(Astro.request.url).origin,
headers: options.mode === "dynamic" ? Astro.request.headers : undefined headers: options.mode === "dynamic" ? Astro.request.headers : undefined
}); });
await api.verifyAuth();
return api;
} }
let app: App; let app: App;
export function serve(config: AstroBkndConfig = {}) { export function serve<Context extends TAstro = TAstro>(config: AstroBkndConfig<Context> = {}) {
return async (args: TAstro) => { return async (args: Context) => {
if (!app) { if (!app) {
app = await createFrameworkApp(config); app = await createFrameworkApp(config, args);
} }
return app.fetch(args.request); return app.fetch(args.request);
}; };

View File

@@ -1,4 +1,3 @@
import type { CreateAppConfig } from "bknd";
import { Hono } from "hono"; import { Hono } from "hono";
import { serveStatic } from "hono/cloudflare-workers"; import { serveStatic } from "hono/cloudflare-workers";
import type { FrameworkBkndConfig } from "../index"; import type { FrameworkBkndConfig } from "../index";
@@ -6,10 +5,9 @@ import { getCached } from "./modes/cached";
import { getDurable } from "./modes/durable"; import { getDurable } from "./modes/durable";
import { getFresh, getWarm } from "./modes/fresh"; import { getFresh, getWarm } from "./modes/fresh";
export type CloudflareBkndConfig<Env = any> = Omit<FrameworkBkndConfig, "app"> & { export type CloudflareBkndConfig<Env = any> = FrameworkBkndConfig<Context<Env>> & {
app: CreateAppConfig | ((env: Env) => CreateAppConfig);
mode?: "warm" | "fresh" | "cache" | "durable"; mode?: "warm" | "fresh" | "cache" | "durable";
bindings?: (env: Env) => { bindings?: (args: Context<Env>) => {
kv?: KVNamespace; kv?: KVNamespace;
dobj?: DurableObjectNamespace; dobj?: DurableObjectNamespace;
}; };
@@ -21,15 +19,15 @@ export type CloudflareBkndConfig<Env = any> = Omit<FrameworkBkndConfig, "app"> &
html?: string; html?: string;
}; };
export type Context = { export type Context<Env = any> = {
request: Request; request: Request;
env: any; env: Env;
ctx: ExecutionContext; ctx: ExecutionContext;
}; };
export function serve(config: CloudflareBkndConfig) { export function serve<Env = any>(config: CloudflareBkndConfig<Env>) {
return { return {
async fetch(request: Request, env: any, ctx: ExecutionContext) { async fetch(request: Request, env: Env, ctx: ExecutionContext) {
const url = new URL(request.url); const url = new URL(request.url);
const manifest = config.manifest; const manifest = config.manifest;

View File

@@ -2,7 +2,7 @@ import { createRuntimeApp } from "adapter";
import { App } from "bknd"; import { App } from "bknd";
import type { CloudflareBkndConfig, Context } from "../index"; import type { CloudflareBkndConfig, Context } from "../index";
export async function getCached(config: CloudflareBkndConfig, { env, ctx }: Context) { export async function getCached(config: CloudflareBkndConfig, { env, ctx, ...args }: Context) {
const { kv } = config.bindings?.(env)!; const { kv } = config.bindings?.(env)!;
if (!kv) throw new Error("kv namespace is not defined in cloudflare.bindings"); if (!kv) throw new Error("kv namespace is not defined in cloudflare.bindings");
const key = config.key ?? "app"; const key = config.key ?? "app";
@@ -37,7 +37,7 @@ export async function getCached(config: CloudflareBkndConfig, { env, ctx }: Cont
}, },
adminOptions: { html: config.html } adminOptions: { html: config.html }
}, },
env { env, ctx, ...args }
); );
if (!cachedConfig) { if (!cachedConfig) {

View File

@@ -1,5 +1,5 @@
import { DurableObject } from "cloudflare:workers"; import { DurableObject } from "cloudflare:workers";
import { createRuntimeApp } from "adapter"; import { createRuntimeApp, makeConfig } from "adapter";
import type { CloudflareBkndConfig, Context } from "adapter/cloudflare"; import type { CloudflareBkndConfig, Context } from "adapter/cloudflare";
import type { App, CreateAppConfig } from "bknd"; import type { App, CreateAppConfig } from "bknd";
@@ -17,7 +17,7 @@ export async function getDurable(config: CloudflareBkndConfig, ctx: Context) {
const id = dobj.idFromName(key); const id = dobj.idFromName(key);
const stub = dobj.get(id) as unknown as DurableBkndApp; const stub = dobj.get(id) as unknown as DurableBkndApp;
const create_config = typeof config.app === "function" ? config.app(ctx.env) : config.app; const create_config = makeConfig(config, ctx);
const res = await stub.fire(ctx.request, { const res = await stub.fire(ctx.request, {
config: create_config, config: create_config,

View File

@@ -2,13 +2,13 @@ import { createRuntimeApp } from "adapter";
import type { App } from "bknd"; import type { App } from "bknd";
import type { CloudflareBkndConfig, Context } from "../index"; import type { CloudflareBkndConfig, Context } from "../index";
export async function makeApp(config: CloudflareBkndConfig, { env }: Context) { export async function makeApp(config: CloudflareBkndConfig, ctx: Context) {
return await createRuntimeApp( return await createRuntimeApp(
{ {
...config, ...config,
adminOptions: config.html ? { html: config.html } : undefined adminOptions: config.html ? { html: config.html } : undefined
}, },
env ctx
); );
} }

View File

@@ -5,16 +5,16 @@ import type { MiddlewareHandler } from "hono";
import { StorageLocalAdapter } from "media/storage/adapters/StorageLocalAdapter"; import { StorageLocalAdapter } from "media/storage/adapters/StorageLocalAdapter";
import type { AdminControllerOptions } from "modules/server/AdminController"; import type { AdminControllerOptions } from "modules/server/AdminController";
export type BkndConfig<Env = any> = CreateAppConfig & { export type BkndConfig<Args = any> = CreateAppConfig & {
app?: CreateAppConfig | ((env: Env) => CreateAppConfig); app?: CreateAppConfig | ((args: Args) => CreateAppConfig);
onBuilt?: (app: App) => Promise<void>; onBuilt?: (app: App) => Promise<void>;
beforeBuild?: (app: App) => Promise<void>; beforeBuild?: (app: App) => Promise<void>;
buildConfig?: Parameters<App["build"]>[0]; buildConfig?: Parameters<App["build"]>[0];
}; };
export type FrameworkBkndConfig<Env = any> = BkndConfig<Env>; export type FrameworkBkndConfig<Args = any> = BkndConfig<Args>;
export type RuntimeBkndConfig<Env = any> = BkndConfig<Env> & { export type RuntimeBkndConfig<Args = any> = BkndConfig<Args> & {
distPath?: string; distPath?: string;
}; };
@@ -46,14 +46,14 @@ export function registerLocalMediaAdapter() {
registries.media.register("local", StorageLocalAdapter); registries.media.register("local", StorageLocalAdapter);
} }
export function makeConfig<Env = any>(config: BkndConfig<Env>, env?: Env): CreateAppConfig { export function makeConfig<Args = any>(config: BkndConfig<Args>, args?: Args): CreateAppConfig {
let additionalConfig: CreateAppConfig = {}; let additionalConfig: CreateAppConfig = {};
if ("app" in config && config.app) { if ("app" in config && config.app) {
if (typeof config.app === "function") { if (typeof config.app === "function") {
if (!env) { if (!args) {
throw new Error("env is required when config.app is a function"); throw new Error("args is required when config.app is a function");
} }
additionalConfig = config.app(env); additionalConfig = config.app(args);
} else { } else {
additionalConfig = config.app; additionalConfig = config.app;
} }
@@ -62,11 +62,11 @@ export function makeConfig<Env = any>(config: BkndConfig<Env>, env?: Env): Creat
return { ...config, ...additionalConfig }; return { ...config, ...additionalConfig };
} }
export async function createFrameworkApp<Env = any>( export async function createFrameworkApp<Args = any>(
config: FrameworkBkndConfig, config: FrameworkBkndConfig,
env?: Env args?: Args
): Promise<App> { ): Promise<App> {
const app = App.create(makeConfig(config, env)); const app = App.create(makeConfig(config, args));
if (config.onBuilt) { if (config.onBuilt) {
app.emgr.onEvent( app.emgr.onEvent(

View File

@@ -29,8 +29,10 @@ export function createApi({ req }: GetServerSidePropsContext) {
} }
export function withApi<T>(handler: (ctx: GetServerSidePropsContext & { api: Api }) => T) { export function withApi<T>(handler: (ctx: GetServerSidePropsContext & { api: Api }) => T) {
return (ctx: GetServerSidePropsContext & { api: Api }) => { return async (ctx: GetServerSidePropsContext & { api: Api }) => {
return handler({ ...ctx, api: createApi(ctx) }); const api = createApi(ctx);
await api.verifyAuth();
return handler({ ...ctx, api });
}; };
} }

View File

@@ -1,9 +1,11 @@
import { useAuth } from "bknd/client";
import type { BkndAdminProps } from "bknd/ui"; import type { BkndAdminProps } from "bknd/ui";
import { Suspense, lazy, useEffect, useState } from "react"; import { Suspense, lazy, useEffect, useState } from "react";
export function adminPage(props?: BkndAdminProps) { export function adminPage(props?: BkndAdminProps) {
const Admin = lazy(() => import("bknd/ui").then((mod) => ({ default: mod.Admin }))); const Admin = lazy(() => import("bknd/ui").then((mod) => ({ default: mod.Admin })));
return () => { return () => {
const auth = useAuth();
const [loaded, setLoaded] = useState(false); const [loaded, setLoaded] = useState(false);
useEffect(() => { useEffect(() => {
if (typeof window === "undefined") return; if (typeof window === "undefined") return;
@@ -13,7 +15,7 @@ export function adminPage(props?: BkndAdminProps) {
return ( return (
<Suspense> <Suspense>
<Admin {...props} /> <Admin withProvider={{ user: auth.user }} {...props} />
</Suspense> </Suspense>
); );
}; };

View File

@@ -1,14 +1,37 @@
import { type FrameworkBkndConfig, createFrameworkApp } from "adapter"; import { type FrameworkBkndConfig, createFrameworkApp } from "adapter";
import type { App } from "bknd"; import type { App } from "bknd";
import { Api } from "bknd/client";
export type RemixBkndConfig = FrameworkBkndConfig; export type RemixBkndConfig<Args = RemixContext> = FrameworkBkndConfig<Args>;
type RemixContext = {
request: Request;
};
let app: App; let app: App;
export function serve(config: RemixBkndConfig = {}) { export function serve<Args extends RemixContext = RemixContext>(
return async (args: { request: Request }) => { config: RemixBkndConfig<Args> = {}
) {
return async (args: Args) => {
if (!app) { if (!app) {
app = await createFrameworkApp(config); app = await createFrameworkApp(config, args);
} }
return app.fetch(args.request); return app.fetch(args.request);
}; };
} }
export function withApi<Args extends { request: Request; context: { api: Api } }, R>(
handler: (args: Args, api: Api) => Promise<R>
) {
return async (args: Args) => {
if (!args.context.api) {
args.context.api = new Api({
host: new URL(args.request.url).origin,
headers: args.request.headers
});
await args.context.api.verifyAuth();
}
return handler(args, args.context.api);
};
}

View File

@@ -65,7 +65,7 @@ import "bknd/dist/styles.css";
import { getApi } from "bknd/adapter/astro"; import { getApi } from "bknd/adapter/astro";
const api = getApi(Astro, { mode: "dynamic" }); const api = await getApi(Astro, { mode: "dynamic" });
const user = api.getUser(); const user = api.getUser();
export const prerender = false; export const prerender = false;
@@ -94,7 +94,7 @@ Here is an example of using the API in static context:
```jsx ```jsx
--- ---
import { getApi } from "bknd/adapter/astro"; import { getApi } from "bknd/adapter/astro";
const api = getApi(Astro); const api = await getApi(Astro);
const { data } = await api.data.readMany("todos"); const { data } = await api.data.readMany("todos");
--- ---
@@ -109,7 +109,7 @@ On SSR pages, you can also access the authenticated user:
```jsx ```jsx
--- ---
import { getApi } from "bknd/adapter/astro"; import { getApi } from "bknd/adapter/astro";
const api = getApi(Astro, { mode: "dynamic" }); const api = await getApi(Astro, { mode: "dynamic" });
const user = api.getUser(); const user = api.getUser();
const { data } = await api.data.readMany("todos"); const { data } = await api.data.readMany("todos");

View File

@@ -16,11 +16,11 @@ and then install bknd as a dependency:
If you don't choose anything specific, the following code will use the `warm` mode. See the If you don't choose anything specific, the following code will use the `warm` mode. See the
chapter [Using a different mode](#using-a-different-mode) for available modes. chapter [Using a different mode](#using-a-different-mode) for available modes.
``` ts ```ts
import { serve } from "bknd/adapter/cloudflare"; import { serve } from "bknd/adapter/cloudflare";
export default serve({ export default serve<Env>({
app: (env: Env) => ({ app: ({ env }) => ({
connection: { connection: {
type: "libsql", type: "libsql",
config: { config: {
@@ -50,12 +50,12 @@ bucket = "node_modules/bknd/dist/static"
``` ```
And then modify the worker entry as follows: And then modify the worker entry as follows:
``` ts {2, 14, 15} ```ts {2, 14, 15}
import { serve } from "bknd/adapter/cloudflare"; import { serve } from "bknd/adapter/cloudflare";
import manifest from "__STATIC_CONTENT_MANIFEST"; import manifest from "__STATIC_CONTENT_MANIFEST";
export default serve({ export default serve<Env>({
app: (env: Env) => ({ app: ({ env }) => ({
connection: { connection: {
type: "libsql", type: "libsql",
config: { config: {
@@ -75,8 +75,8 @@ You can also add custom routes by defining them after the app has been built, li
import { serve } from "bknd/adapter/cloudflare"; import { serve } from "bknd/adapter/cloudflare";
import manifest from "__STATIC_CONTENT_MANIFEST"; import manifest from "__STATIC_CONTENT_MANIFEST";
export default serve({ export default serve<Env>({
app: (env: Env) => ({ app: ({ env }) => ({
connection: { connection: {
type: "libsql", type: "libsql",
config: { config: {
@@ -111,7 +111,7 @@ mode`, like so:
import { serve } from "bknd/adapter/cloudflare"; import { serve } from "bknd/adapter/cloudflare";
export default serve({ export default serve({
/* ... */, // ...
mode: "fresh" // mode: "fresh" | "warm" | "cache" | "durable" mode: "fresh" // mode: "fresh" | "warm" | "cache" | "durable"
}); });
``` ```
@@ -119,13 +119,14 @@ export default serve({
### Mode: `cache` ### Mode: `cache`
For the cache mode to work, you also need to specify the KV to be used. For this, use the For the cache mode to work, you also need to specify the KV to be used. For this, use the
`bindings` property: `bindings` property:
```ts ```ts
import { serve } from "bknd/adapter/cloudflare"; import { serve } from "bknd/adapter/cloudflare";
export default serve({ export default serve<Env>({
/* ... */, // ...
mode: "cache", mode: "cache",
bindings: (env: Env) => ({ kv: env.KV }) bindings: ({ env }) => ({ kv: env.KV })
}); });
``` ```
@@ -136,10 +137,10 @@ environment, and additionally export the `DurableBkndApp` class:
import { serve, DurableBkndApp } from "bknd/adapter/cloudflare"; import { serve, DurableBkndApp } from "bknd/adapter/cloudflare";
export { DurableBkndApp }; export { DurableBkndApp };
export default serve({ export default serve<Env>({
/* ... */, // ...
mode: "durable", mode: "durable",
bindings: (env: Env) => ({ dobj: env.DOBJ }), bindings: ({ env }) => ({ dobj: env.DOBJ }),
keepAliveSeconds: 60 // optional keepAliveSeconds: 60 // optional
}); });
``` ```
@@ -164,9 +165,9 @@ import type { App } from "bknd";
import { serve, DurableBkndApp } from "bknd/adapter/cloudflare"; import { serve, DurableBkndApp } from "bknd/adapter/cloudflare";
export default serve({ export default serve({
/* ... */, // ...
mode: "durable", mode: "durable",
bindings: (env: Env) => ({ dobj: env.DOBJ }), bindings: ({ env }) => ({ dobj: env.DOBJ }),
keepAliveSeconds: 60 // optional keepAliveSeconds: 60 // optional
}); });

View File

@@ -10,7 +10,7 @@ Install bknd as a dependency:
## Serve the API ## Serve the API
Create a new api splat route file at `app/routes/api.$.ts`: Create a new api splat route file at `app/routes/api.$.ts`:
``` tsx ```ts
// app/routes/api.$.ts // app/routes/api.$.ts
import { serve } from "bknd/adapter/remix"; import { serve } from "bknd/adapter/remix";
@@ -32,6 +32,9 @@ Now make sure that you wrap your root layout with the `ClientProvider` so that a
share the same context: share the same context:
```tsx ```tsx
// app/root.tsx // app/root.tsx
import { withApi } from "bknd/adapter/remix"
import { type Api, ClientProvider } from "bknd/client";
export function Layout(props) { export function Layout(props) {
// nothing to change here, just for orientation // nothing to change here, just for orientation
return ( return (
@@ -48,21 +51,12 @@ declare module "@remix-run/server-runtime" {
} }
// export a loader that initiates the API // export a loader that initiates the API
// and pass it through the context // and passes it down to args.context.api
export const loader = async (args: LoaderFunctionArgs) => { export const loader = withApi(async (args: LoaderFunctionArgs, api: Api) => {
const api = new Api({ return {
host: new URL(args.request.url).origin, user: api.getUser()
headers: args.request.headers };
}); });
// get the user from the API
const user = api.getUser();
// add api to the context
args.context.api = api;
return { user };
};
export default function App() { export default function App() {
const { user } = useLoaderData<typeof loader>(); const { user } = useLoaderData<typeof loader>();
@@ -93,15 +87,9 @@ Since the API has already been constructed in the root layout, you can now use i
import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { useLoaderData } from "@remix-run/react"; import { useLoaderData } from "@remix-run/react";
export const loader = async (args: LoaderFunctionArgs) => { export const loader = async ({ context: { api } }: LoaderFunctionArgs) => {
const { api } = args.context;
// get the authenticated user
const user = api.getAuthState().user;
// get the data from the API
const { data } = await api.data.readMany("todos"); const { data } = await api.data.readMany("todos");
return { data, user }; return { data, user: api.getUser() };
}; };
export default function Index() { export default function Index() {

View File

@@ -4,8 +4,7 @@ import "bknd/dist/styles.css";
import { getApi } from "bknd/adapter/astro"; import { getApi } from "bknd/adapter/astro";
const api = getApi(Astro, { mode: "dynamic" }); const api = await getApi(Astro, { mode: "dynamic" });
await api.verifyAuth();
const user = api.getUser(); const user = api.getUser();
export const prerender = false; export const prerender = false;

View File

@@ -1,3 +1,4 @@
import type { APIContext } from "astro";
import { App } from "bknd"; import { App } from "bknd";
import { serve } from "bknd/adapter/astro"; import { serve } from "bknd/adapter/astro";
import { registerLocalMediaAdapter } from "bknd/adapter/node"; import { registerLocalMediaAdapter } from "bknd/adapter/node";
@@ -23,7 +24,7 @@ declare module "bknd/core" {
interface DB extends Database {} interface DB extends Database {}
} }
export const ALL = serve({ export const ALL = serve<APIContext>({
// we can use any libsql config, and if omitted, uses in-memory // we can use any libsql config, and if omitted, uses in-memory
connection: { connection: {
type: "libsql", type: "libsql",

View File

@@ -2,7 +2,8 @@
import { getApi } from "bknd/adapter/astro"; import { getApi } from "bknd/adapter/astro";
import Card from "../components/Card.astro"; import Card from "../components/Card.astro";
import Layout from "../layouts/Layout.astro"; import Layout from "../layouts/Layout.astro";
const api = getApi(Astro);
const api = await getApi(Astro);
const { data } = await api.data.readMany("todos"); const { data } = await api.data.readMany("todos");
--- ---

View File

@@ -2,8 +2,7 @@
import { getApi } from "bknd/adapter/astro"; import { getApi } from "bknd/adapter/astro";
import Card from "../components/Card.astro"; import Card from "../components/Card.astro";
import Layout from "../layouts/Layout.astro"; import Layout from "../layouts/Layout.astro";
const api = getApi(Astro, { mode: "dynamic" }); const api = await getApi(Astro, { mode: "dynamic" });
await api.verifyAuth();
const { data } = await api.data.readMany("todos"); const { data } = await api.data.readMany("todos");
const user = api.getUser(); const user = api.getUser();

View File

@@ -3,7 +3,7 @@ import { serve } from "bknd/adapter/cloudflare";
import manifest from "__STATIC_CONTENT_MANIFEST"; import manifest from "__STATIC_CONTENT_MANIFEST";
export default serve({ export default serve({
app: (env: Env) => ({ app: (args) => ({
connection: { connection: {
type: "libsql", type: "libsql",
config: { config: {

View File

@@ -1,12 +1,7 @@
import type { LoaderFunctionArgs } from "@remix-run/node"; import type { LoaderFunctionArgs } from "@remix-run/node";
import { Links, Meta, Outlet, Scripts, ScrollRestoration, useLoaderData } from "@remix-run/react"; import { Links, Meta, Outlet, Scripts, ScrollRestoration, useLoaderData } from "@remix-run/react";
import { Api, ClientProvider } from "bknd/client"; import { withApi } from "bknd/adapter/remix";
import { type Api, ClientProvider } from "bknd/client";
declare module "@remix-run/server-runtime" {
export interface AppLoadContext {
api: Api;
}
}
export function Layout({ children }: { children: React.ReactNode }) { export function Layout({ children }: { children: React.ReactNode }) {
return ( return (
@@ -26,20 +21,17 @@ export function Layout({ children }: { children: React.ReactNode }) {
); );
} }
export const loader = async (args: LoaderFunctionArgs) => { declare module "@remix-run/server-runtime" {
const api = new Api({ export interface AppLoadContext {
host: new URL(args.request.url).origin, api: Api;
headers: args.request.headers }
}); }
// add api to the context export const loader = withApi(async (args: LoaderFunctionArgs, api: Api) => {
args.context.api = api;
await api.verifyAuth();
return { return {
user: api.getAuthState()?.user user: api.getUser()
}; };
}; });
export default function App() { export default function App() {
const data = useLoaderData<typeof loader>(); const data = useLoaderData<typeof loader>();

View File

@@ -1,19 +1,20 @@
import { type MetaFunction, useLoaderData } from "@remix-run/react"; import { type MetaFunction, useLoaderData } from "@remix-run/react";
import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { useAuth } from "bknd/client";
export const meta: MetaFunction = () => { export const meta: MetaFunction = () => {
return [{ title: "Remix & bknd" }, { name: "description", content: "Welcome to Remix & bknd!" }]; return [{ title: "Remix & bknd" }, { name: "description", content: "Welcome to Remix & bknd!" }];
}; };
export const loader = async (args: LoaderFunctionArgs) => { export const loader = async ({ context: { api } }: LoaderFunctionArgs) => {
const api = args.context.api;
await api.verifyAuth();
const { data } = await api.data.readMany("todos"); const { data } = await api.data.readMany("todos");
return { data, user: api.getUser() }; return { data, user: api.getUser() };
}; };
export default function Index() { export default function Index() {
const { data, user } = useLoaderData<typeof loader>(); const { data, user } = useLoaderData<typeof loader>();
const auth = useAuth();
console.log("auth", auth);
return ( return (
<div> <div>