wobsoriano/elysia-clerk

Show how to setup AuthContext in the docs + expose Clerk types

JUNIORCO opened this issue · 9 comments

// index.ts

import { clerkPlugin } from "elysia-clerk";
import someRoute from "./some.route";

const app = new Elysia()
  .group("/private", (app) => app.use(clerkPlugin()).use(someRoute))
  .listen(3000);
// some.route.ts

const someRoute = new Elysia()
  .get(
    "/",
    // ❌ TypeScript complains here that auth and clerk don't exist
    async ({ auth, clerk }) => {
      ...
    }
  );

export default someRoute;

It's known the above doesn't work, so I propose adding this code snippet to the docs:

// authContext.ts

import {
  SignedInAuthObject,
  SignedOutAuthObject,
} from "@clerk/backend/dist/tokens/authObjects";
import type { ClerkClient } from "elysia-clerk";

const authContext = new Elysia().resolve({ as: "scoped" }, (ctx) => {
  const ctxWithAuth = ctx as unknown as {
    auth: SignedInAuthObject | SignedOutAuthObject | null;
    clerk: ClerkClient;
  };

  if (ctxWithAuth.auth === undefined) {
    throw new Error("Missing auth context");
  }

  return { auth: ctxWithAuth.auth, clerk: ctxWithAuth.clerk };
});

export default authContext;

Which you can then use like

// some.route.ts

import authContext from "./authContext";

const someRoute = new Elysia()
  .use(authContext)
  .get(
    "/",
    // ✅ This is typed now!
    async ({ auth, clerk }) => {
      ...
    }
  );

export default someRoute;

Also I'm getting the SignedInAuthObject and SignedOutAuthObject types from @clerk/backend/dist/tokens/authObjects, which ideally I can get from elysia-clerk. Any way to export those types from the lib?

Thoughts?

It's known the above doesn't work

Known issue in Elysia if I'm correct?

Also I'm getting the SignedInAuthObject and SignedOutAuthObject types from @clerk/backend/dist/tokens/authObjects, which ideally I can get from elysia-clerk. Any way to export those types from the lib?

You can import AuthObject instead!

import { AuthObject } from 'elysia-clerk' 

Nice, this works

import Elysia from "elysia";
import type { AuthObject, ClerkClient } from "elysia-clerk";

type SignedInAuthObject = Extract<AuthObject, { userId: string }>;

const authContext = new Elysia().resolve({ as: "scoped" }, (ctx) => {
  const ctxWithAuth = ctx as unknown as {
    auth: AuthObject | null;
    clerk: ClerkClient;
  };

  if (!ctxWithAuth.auth?.userId) {
    return ctx.error(401, "Unauthorizedd");
  }

  return {
    auth: ctxWithAuth.auth as SignedInAuthObject,
    clerk: ctxWithAuth.clerk,
  };
});

export default authContext;

SignedInAuthObject isn't exported so I had to extract it.

And yeah it's a problem with Elysia but it'd still be helpful if you just had that code snippet in the docs :) would've saved me a lot of time reading old Discord discussions

And yeah it's a problem with Elysia but it'd still be helpful if you just had that code snippet in the docs :) would've saved me a lot of time reading old Discord discussions

Thanks for confirming, yeah I'll check what I can do about that! Appreciate you coming here

Not sure if you've seen this one, but there's a InferContext type helper from Elysia you can use:

import { Elysia, type InferContext } from 'elysia'

const app = new Elysia()
  .group('/private', (app) => app.use(clerkPlugin()).use(someRoute))
  .listen(3000);

export type AppContext = InferContext<typeof app>
import type { AppContext } from '../'
 
const someRoute = new Elysia()
  .get(
    '/',
    async ({ auth, clerk }: AppContext) => {
      ...
    }
  );

export default someRoute

Doesn't work for me :/

// index.ts

export const app = new Elysia()
  .group("/public", (app) =>
    app.use(healthRoute).group("/webhooks", (app) => app.use(clerkRoute)).use(stripeRoute),
  )
  .group("", (app) => app.use(clerkPlugin()).use(someRoute))
  .listen(config.PORT);
// appContext.ts

import { InferContext } from "elysia";
import { app } from ".";

export type AppContext = InferContext<typeof app>;
// someRoute.ts

import type { AppContext } from "./appContext";

const someRoute = new Elysia()
  .get(
    "/",
    async ({ query }: AppContext) => {
      ...
    },
  );

export default someRoute;

I get '{ query }' is referenced directly or indirectly in its own type annotation.

Has anyone found a simpler way to achieve the above? Unfortunately using InferContext doesn't work for me either.

Has anyone found a simpler way to achieve the above? Unfortunately using InferContext doesn't work for me either.

Nope, elysia limitation. Sorry