@mustafahmed1997/learnify-backend 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +167 -0
- package/dist/constants/validRoles.d.ts +2 -0
- package/dist/constants/validRoles.js +2 -0
- package/dist/constants/validRoles.js.map +1 -0
- package/dist/errors/AppError.d.ts +4 -0
- package/dist/errors/AppError.js +8 -0
- package/dist/errors/AppError.js.map +1 -0
- package/dist/errors/globalErrorHandler.d.ts +2 -0
- package/dist/errors/globalErrorHandler.js +13 -0
- package/dist/errors/globalErrorHandler.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/middlewares/checkAuth.d.ts +2 -0
- package/dist/middlewares/checkAuth.js +32 -0
- package/dist/middlewares/checkAuth.js.map +1 -0
- package/dist/middlewares/checkRole.d.ts +3 -0
- package/dist/middlewares/checkRole.js +12 -0
- package/dist/middlewares/checkRole.js.map +1 -0
- package/dist/types/ClerkUser.d.ts +12 -0
- package/dist/types/ClerkUser.js +2 -0
- package/dist/types/ClerkUser.js.map +1 -0
- package/dist/types/express.d.ts +9 -0
- package/dist/types/express.js +8 -0
- package/dist/types/express.js.map +1 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# @mustafahmed1997/learnify-backend
|
|
2
|
+
|
|
3
|
+
Shared backend utilities for Learnify microservices: Clerk-based auth/role
|
|
4
|
+
middlewares, error handling utilities, and the Express `Request` type
|
|
5
|
+
augmentation for `req.user` — all importable from the package root.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @mustafahmed1997/learnify-backend
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Peer dependencies
|
|
14
|
+
|
|
15
|
+
This package expects the consuming service to provide its own `express` and
|
|
16
|
+
`@clerk/express` installs (so there is a single shared instance of each at
|
|
17
|
+
runtime, and Clerk's request-scoped auth context works correctly):
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install express @clerk/express
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Setup: `clerkMiddleware()` before `checkAuth`
|
|
24
|
+
|
|
25
|
+
`checkAuth` reads Clerk auth state via `getAuth(req)`, which requires
|
|
26
|
+
`clerkMiddleware()` from `@clerk/express` to have already run on the request:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import express from "express";
|
|
30
|
+
import { clerkMiddleware } from "@clerk/express";
|
|
31
|
+
import { checkAuth, checkRole, globalErrorHandler } from "@mustafahmed1997/learnify-backend";
|
|
32
|
+
|
|
33
|
+
const app = express();
|
|
34
|
+
|
|
35
|
+
app.use(clerkMiddleware());
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## `checkAuth`
|
|
39
|
+
|
|
40
|
+
Verifies the request is authenticated via Clerk and attaches a typed
|
|
41
|
+
`req.user` (a `ClerkUser`) for downstream handlers.
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { checkAuth } from "@mustafahmed1997/learnify-backend";
|
|
45
|
+
|
|
46
|
+
app.get("/me", checkAuth, (req, res) => {
|
|
47
|
+
res.json(req.user);
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
If the request is not authenticated, `checkAuth` responds `401` with
|
|
52
|
+
`{ error: "Unauthenticated. Login first please." }` and does not call `next()`.
|
|
53
|
+
|
|
54
|
+
## `checkRole`
|
|
55
|
+
|
|
56
|
+
Authorizes a request based on the role in the Clerk session claims
|
|
57
|
+
(`sessionClaims.userMetadata.role`). Takes an array of allowed roles.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { checkRole } from "@mustafahmed1997/learnify-backend";
|
|
61
|
+
|
|
62
|
+
app.delete(
|
|
63
|
+
"/courses/:id",
|
|
64
|
+
checkRole(["ADMIN", "INSTRUCTOR"]),
|
|
65
|
+
deleteCourseController,
|
|
66
|
+
);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
If the caller's role is not in the allowed list, it responds `403` with
|
|
70
|
+
`{ message: "Unauthorized !!" }` and does not call `next()`.
|
|
71
|
+
|
|
72
|
+
### Combined route example
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
app.patch(
|
|
76
|
+
"/users/:id/role",
|
|
77
|
+
checkAuth,
|
|
78
|
+
checkRole(["ADMIN"]),
|
|
79
|
+
updateRoleController,
|
|
80
|
+
);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## `validRoles` and `Role`
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { validRoles } from "@mustafahmed1997/learnify-backend";
|
|
87
|
+
import type { Role } from "@mustafahmed1997/learnify-backend";
|
|
88
|
+
|
|
89
|
+
console.log(validRoles); // ["STUDENT", "INSTRUCTOR", "ADMIN"]
|
|
90
|
+
|
|
91
|
+
function isValidRole(value: string): value is Role {
|
|
92
|
+
return (validRoles as readonly string[]).includes(value);
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## `AppError`
|
|
97
|
+
|
|
98
|
+
A typed operational error carrying an HTTP status code.
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import { AppError } from "@mustafahmed1997/learnify-backend";
|
|
102
|
+
|
|
103
|
+
if (!course) {
|
|
104
|
+
throw new AppError("Course not found", 404);
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## `globalErrorHandler`
|
|
109
|
+
|
|
110
|
+
Express error-handling middleware. Register it **last**, after all routes:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import { globalErrorHandler } from "@mustafahmed1997/learnify-backend";
|
|
114
|
+
|
|
115
|
+
app.use(globalErrorHandler);
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
- An `AppError` produces `{ error: err.message }` with `err.statusCode`.
|
|
119
|
+
- Any other thrown value produces `{ error: "Internal server error" }` with
|
|
120
|
+
status `500`.
|
|
121
|
+
|
|
122
|
+
## `ClerkUser` and `req.user` typing
|
|
123
|
+
|
|
124
|
+
Importing anything from this package's root automatically loads the Express
|
|
125
|
+
`Request` augmentation, so `req.user` is typed as `ClerkUser | undefined`
|
|
126
|
+
wherever `express`'s `Request` type is used — no extra `express.d.ts` needed
|
|
127
|
+
in the consuming service.
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
import type { Request, Response } from "express";
|
|
131
|
+
import type { ClerkUser } from "@mustafahmed1997/learnify-backend";
|
|
132
|
+
|
|
133
|
+
function exampleController(req: Request, res: Response) {
|
|
134
|
+
const clerkId = req.user?.clerkId;
|
|
135
|
+
const role = req.user?.userMetadata.role;
|
|
136
|
+
|
|
137
|
+
res.json({ clerkId, role });
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`ClerkUser` shape:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
type ClerkUser = {
|
|
145
|
+
clerkId: string;
|
|
146
|
+
userMetadata: {
|
|
147
|
+
role: "STUDENT" | "INSTRUCTOR" | "ADMIN";
|
|
148
|
+
dbUserId: string;
|
|
149
|
+
};
|
|
150
|
+
email?: string;
|
|
151
|
+
firstName?: string;
|
|
152
|
+
lastName?: string;
|
|
153
|
+
imageURL?: string;
|
|
154
|
+
};
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## TypeScript usage
|
|
158
|
+
|
|
159
|
+
The package ships its own `.d.ts` declarations — no `@types` package needed.
|
|
160
|
+
It targets ESM (`"type": "module"`) and expects a `moduleResolution` setting
|
|
161
|
+
that supports package `exports` (e.g. `node16`, `nodenext`, or `bundler`).
|
|
162
|
+
|
|
163
|
+
## What this package does not do
|
|
164
|
+
|
|
165
|
+
- Does not depend on Prisma or any database.
|
|
166
|
+
- Does not call the Auth Service (or any other service) over HTTP.
|
|
167
|
+
- Does not manage Clerk session creation — that's handled by `clerkMiddleware()`.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validRoles.js","sourceRoot":"","sources":["../../src/constants/validRoles.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,SAAS,EAAE,YAAY,EAAE,OAAO,CAAU,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AppError.js","sourceRoot":"","sources":["../../src/errors/AppError.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,QAAS,SAAQ,KAAK;IAGjC,YAAY,OAAe,EAAE,UAAkB;QAC7C,KAAK,CAAC,OAAO,CAAC,CAAC;QAEf,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAE7B,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;IAClD,CAAC;CACF"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { AppError } from "./AppError.js";
|
|
2
|
+
export function globalErrorHandler(err, req, res, next) {
|
|
3
|
+
console.error(err);
|
|
4
|
+
if (err instanceof AppError) {
|
|
5
|
+
return res.status(err.statusCode).json({
|
|
6
|
+
error: err.message,
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
return res.status(500).json({
|
|
10
|
+
error: "Internal server error",
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=globalErrorHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"globalErrorHandler.js","sourceRoot":"","sources":["../../src/errors/globalErrorHandler.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,MAAM,UAAU,kBAAkB,CAChC,GAAY,EACZ,GAAY,EACZ,GAAa,EACb,IAAkB;IAElB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAEnB,IAAI,GAAG,YAAY,QAAQ,EAAE,CAAC;QAC5B,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC;YACrC,KAAK,EAAE,GAAG,CAAC,OAAO;SACnB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;QAC1B,KAAK,EAAE,uBAAuB;KAC/B,CAAC,CAAC;AACL,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import "./types/express.js";
|
|
2
|
+
export { checkAuth } from "./middlewares/checkAuth.js";
|
|
3
|
+
export { checkRole } from "./middlewares/checkRole.js";
|
|
4
|
+
export { validRoles } from "./constants/validRoles.js";
|
|
5
|
+
export type { Role } from "./constants/validRoles.js";
|
|
6
|
+
export { AppError } from "./errors/AppError.js";
|
|
7
|
+
export { globalErrorHandler } from "./errors/globalErrorHandler.js";
|
|
8
|
+
export type { ClerkUser } from "./types/ClerkUser.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import "./types/express.js";
|
|
2
|
+
export { checkAuth } from "./middlewares/checkAuth.js";
|
|
3
|
+
export { checkRole } from "./middlewares/checkRole.js";
|
|
4
|
+
export { validRoles } from "./constants/validRoles.js";
|
|
5
|
+
export { AppError } from "./errors/AppError.js";
|
|
6
|
+
export { globalErrorHandler } from "./errors/globalErrorHandler.js";
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,oBAAoB,CAAC;AAE5B,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAEvD,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAGvD,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { getAuth } from "@clerk/express";
|
|
2
|
+
export async function checkAuth(req, res, next) {
|
|
3
|
+
try {
|
|
4
|
+
const { isAuthenticated, userId, sessionClaims } = getAuth(req);
|
|
5
|
+
if (!isAuthenticated || !userId) {
|
|
6
|
+
return res.status(401).json({
|
|
7
|
+
error: "Unauthenticated. Login first please.",
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
const clerkId = userId;
|
|
11
|
+
const userMetadata = sessionClaims?.userMetadata;
|
|
12
|
+
const email = sessionClaims?.email;
|
|
13
|
+
const firstName = sessionClaims?.firstName;
|
|
14
|
+
const lastName = sessionClaims?.lastName;
|
|
15
|
+
const imageURL = sessionClaims?.imageURL;
|
|
16
|
+
const user = {
|
|
17
|
+
clerkId,
|
|
18
|
+
email,
|
|
19
|
+
firstName,
|
|
20
|
+
lastName,
|
|
21
|
+
imageURL,
|
|
22
|
+
userMetadata,
|
|
23
|
+
};
|
|
24
|
+
// Attach user to request
|
|
25
|
+
req.user = user;
|
|
26
|
+
next();
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
next(error);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=checkAuth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkAuth.js","sourceRoot":"","sources":["../../src/middlewares/checkAuth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAIzC,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,GAAY,EACZ,GAAa,EACb,IAAkB;IAElB,IAAI,CAAC;QACH,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QAEhE,IAAI,CAAC,eAAe,IAAI,CAAC,MAAM,EAAE,CAAC;YAChC,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBAC1B,KAAK,EAAE,sCAAsC;aAC9C,CAAC,CAAC;QACL,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,CAAC;QACvB,MAAM,YAAY,GAAG,aAAa,EAAE,YAAyC,CAAC;QAC9E,MAAM,KAAK,GAAG,aAAa,EAAE,KAA2B,CAAC;QACzD,MAAM,SAAS,GAAG,aAAa,EAAE,SAA+B,CAAC;QACjE,MAAM,QAAQ,GAAG,aAAa,EAAE,QAA8B,CAAC;QAC/D,MAAM,QAAQ,GAAG,aAAa,EAAE,QAA8B,CAAC;QAE/D,MAAM,IAAI,GAAc;YACtB,OAAO;YACP,KAAK;YACL,SAAS;YACT,QAAQ;YACR,QAAQ;YACR,YAAY;SACb,CAAC;QAEF,yBAAyB;QACzB,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;QAEhB,IAAI,EAAE,CAAC;IACT,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,KAAK,CAAC,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { NextFunction, Request, Response } from "express";
|
|
2
|
+
import { validRoles } from "../constants/validRoles.js";
|
|
3
|
+
export declare function checkRole(roles: (typeof validRoles)[number][]): (req: Request, res: Response, next: NextFunction) => Promise<Response<any, Record<string, any>> | undefined>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { getAuth } from "@clerk/express";
|
|
2
|
+
export function checkRole(roles) {
|
|
3
|
+
return async (req, res, next) => {
|
|
4
|
+
const { sessionClaims } = getAuth(req);
|
|
5
|
+
const userMetaData = sessionClaims?.userMetadata;
|
|
6
|
+
const role = userMetaData?.role;
|
|
7
|
+
if (!roles.includes(role))
|
|
8
|
+
return res.status(403).json({ message: "Unauthorized !!" });
|
|
9
|
+
next();
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=checkRole.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkRole.js","sourceRoot":"","sources":["../../src/middlewares/checkRole.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAIzC,MAAM,UAAU,SAAS,CAAC,KAAoC;IAC5D,OAAO,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QAC/D,MAAM,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QACvC,MAAM,YAAY,GAAG,aAAa,EAAE,YAGnC,CAAC;QACF,MAAM,IAAI,GAAG,YAAY,EAAE,IAAmC,CAAC;QAE/D,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;YACvB,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,iBAAiB,EAAE,CAAC,CAAC;QAE9D,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { validRoles } from "../constants/validRoles.js";
|
|
2
|
+
export type ClerkUser = {
|
|
3
|
+
clerkId: string;
|
|
4
|
+
userMetadata: {
|
|
5
|
+
role: (typeof validRoles)[number];
|
|
6
|
+
dbUserId: string;
|
|
7
|
+
};
|
|
8
|
+
email?: string;
|
|
9
|
+
firstName?: string;
|
|
10
|
+
lastName?: string;
|
|
11
|
+
imageURL?: string;
|
|
12
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClerkUser.js","sourceRoot":"","sources":["../../src/types/ClerkUser.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// types/express.ts
|
|
2
|
+
//
|
|
3
|
+
// Augments the global Express namespace so `req.user` is typed everywhere.
|
|
4
|
+
// This must be a real module (imported for its side effect from index.ts,
|
|
5
|
+
// not merely referenced) so the augmentation survives declaration emission
|
|
6
|
+
// and is automatically loaded by any consumer importing from the package root.
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=express.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"express.js","sourceRoot":"","sources":["../../src/types/express.ts"],"names":[],"mappings":"AAAA,mBAAmB;AACnB,EAAE;AACF,2EAA2E;AAC3E,0EAA0E;AAC1E,2EAA2E;AAC3E,+EAA+E"}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mustafahmed1997/learnify-backend",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Shared Clerk-based auth middlewares, role checking, error utilities, and Express types for Learnify backend microservices.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"express",
|
|
7
|
+
"clerk",
|
|
8
|
+
"middleware",
|
|
9
|
+
"learnify"
|
|
10
|
+
],
|
|
11
|
+
"license": "ISC",
|
|
12
|
+
"author": "Mostafa Ahmed",
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/mustafaahmed-f/learnify-backend-package.git"
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc -p tsconfig.build.json",
|
|
34
|
+
"typecheck": "tsc -p tsconfig.json",
|
|
35
|
+
"test": "vitest run",
|
|
36
|
+
"test:watch": "vitest"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"@clerk/express": "^1.7.74",
|
|
40
|
+
"express": "^5.1.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@clerk/express": "^1.7.74",
|
|
44
|
+
"@types/express": "^5.0.3",
|
|
45
|
+
"@types/node": "^20.19.39",
|
|
46
|
+
"express": "^5.1.0",
|
|
47
|
+
"typescript": "^5.1.6",
|
|
48
|
+
"vitest": "^3.0.0"
|
|
49
|
+
}
|
|
50
|
+
}
|