wobsoriano / elysia-clerk

Unofficial Clerk plugin for Elysia.js.
MIT License
67 stars 3 forks source link

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

Open JUNIORCO opened 3 weeks ago

JUNIORCO commented 3 weeks ago
// 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?

wobsoriano commented 3 weeks ago

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' 
JUNIORCO commented 3 weeks ago

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

wobsoriano commented 3 weeks ago

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

wobsoriano commented 3 weeks ago

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
JUNIORCO commented 3 weeks ago

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.

arvandhassan commented 1 day ago

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

wobsoriano commented 23 hours ago

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

Nope, elysia limitation. Sorry