diff --git a/apps/backend/index.ts b/apps/backend/index.ts index 5174bce..ffba6ff 100755 --- a/apps/backend/index.ts +++ b/apps/backend/index.ts @@ -1,13 +1,12 @@ import 'dotenv/config'; -import express, { NextFunction, Request, Response } from "express"; -import cors from "cors"; -// import bodyParser from "body-parser"; -import path from "path"; -import fs from "fs"; -import { getStaffUserById, getUserDetailsByUserId, isUserSuspended } from '@/src/dbService'; +import { Hono } from 'hono'; +import { cors } from 'hono/cors'; +import { logger } from 'hono/logger'; +import { serve } from '@hono/node-server'; +import { trpcServer } from '@hono/trpc-server'; +import { getStaffUserById, isUserSuspended } from '@/src/dbService'; import mainRouter from '@/src/main-router'; import initFunc from '@/src/lib/init'; -import { createExpressMiddleware } from '@trpc/server/adapters/express'; import { appRouter } from '@/src/trpc/router'; import { TRPCError } from '@trpc/server'; import { jwtVerify } from 'jose' @@ -21,57 +20,28 @@ seed() initFunc() startAutomatedJobs() -const app = express(); +const app = new Hono(); +// CORS middleware app.use(cors({ - origin: 'http://localhost:5174' + origin: 'http://localhost:5174', + allowMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], + allowHeaders: ['Origin', 'X-Requested-With', 'Content-Type', 'Accept', 'Authorization'], + credentials: true, })); - signedUrlCache.loadFromDisk(); -app.use(express.json()); -app.use(express.urlencoded({ extended: true })); +// Logger middleware +app.use(logger()); -// Middleware to log all request URLs -app.use((req, res, next) => { - const timestamp = new Date().toISOString(); - console.log(`[${timestamp}] ${req.method} ${req.url}`); - next(); -}); - -//cors middleware -export function corsMiddleware(req: Request, res: Response, next: NextFunction) { - // Allow requests from any origin (for production, replace * with your domain) - res.header('Access-Control-Allow-Origin', '*'); - - // Allow specific headers clients can send - res.header( - 'Access-Control-Allow-Headers', - 'Origin, X-Requested-With, Content-Type, Accept, Authorization' - ); - - // Allow specific HTTP methods - res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS'); - - // Allow credentials if needed (optional) - // res.header('Access-Control-Allow-Credentials', 'true'); - - // Handle preflight (OPTIONS) requests quickly - if (req.method === 'OPTIONS') { - return res.sendStatus(204); - } - - next(); -} - - -app.use('/api/trpc', createExpressMiddleware({ +// tRPC middleware +app.use('/api/trpc', trpcServer({ router: appRouter, - createContext: async ({ req, res }) => { + createContext: async ({ req }) => { let user = null; let staffUser = null; - const authHeader = req.headers.authorization; + const authHeader = req.headers.get('authorization'); if (authHeader?.startsWith('Bearer ')) { const token = authHeader.substring(7); @@ -85,7 +55,7 @@ app.use('/api/trpc', createExpressMiddleware({ const staff = await getStaffUserById(decoded.staffId); if (staff) { - user=staffUser + user = staffUser staffUser = { id: staff.id, name: staff.name, @@ -110,71 +80,64 @@ app.use('/api/trpc', createExpressMiddleware({ // Invalid token, both user and staffUser remain null } } - return { req, res, user, staffUser }; + return { req, user, staffUser }; }, onError({ error, path, type, ctx }) { - console.error('🚨 tRPC Error :', { - path, - type, - code: error.code, - message: error.message, - userId: ctx?.user?.userId, - stack: error.stack, - }); -}, + console.error('🚨 tRPC Error :', { + path, + type, + code: error.code, + message: error.message, + userId: ctx?.user?.userId, + stack: error.stack, + }); + }, })); -app.use('/api', mainRouter) - -const fallbackUiDirCandidates = [ - path.resolve(__dirname, '../fallback-ui/dist'), - path.resolve(__dirname, '../../fallback-ui/dist'), - path.resolve(process.cwd(), '../fallback-ui/dist'), - path.resolve(process.cwd(), './apps/fallback-ui/dist') -] - -const fallbackUiDir = - fallbackUiDirCandidates.find((candidate) => fs.existsSync(candidate)) ?? - fallbackUiDirCandidates[0] - - -const fallbackUiIndex = path.join(fallbackUiDir, 'index.html') -// const fallbackUiMountPath = '/admin-web' -const fallbackUiMountPath = '/'; - -if (fs.existsSync(fallbackUiIndex)) { - app.use(fallbackUiMountPath, express.static(fallbackUiDir)) - app.use('/mf'+fallbackUiMountPath, express.static(fallbackUiDir)) - const fallbackUiRegex = new RegExp( - `^${fallbackUiMountPath.replace(/\//g, '\\/')}(?:\\/.*)?$` - ) - app.get([fallbackUiMountPath, fallbackUiRegex], (req, res) => { - res.sendFile(fallbackUiIndex) - }) - app.get(/.*/, (req,res) => { - res.sendFile(fallbackUiIndex) - }) -} else { - console.warn(`Fallback UI build not found at ${fallbackUiIndex}`) -} - -// Serve /assets/public folder at /assets route -const assetsPublicDir = path.resolve(__dirname, './assets/public'); -if (fs.existsSync(assetsPublicDir)) { - app.use('/assets', express.static(assetsPublicDir)); - console.log('Serving /assets from', assetsPublicDir); -} else { - console.warn('Assets public folder not found at', assetsPublicDir); -} +// Mount main router +app.route('/api', mainRouter); // Global error handler -app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => { +app.onError((err, c) => { console.error(err); - const status = err.statusCode || err.status || 500; - const message = err.message || 'Internal Server Error'; - res.status(status).json({ message }); + // Handle different error types + let status = 500; + let message = 'Internal Server Error'; + + if (err instanceof TRPCError) { + // Map TRPC error codes to HTTP status codes + const trpcStatusMap: Record = { + 'BAD_REQUEST': 400, + 'UNAUTHORIZED': 401, + 'FORBIDDEN': 403, + 'NOT_FOUND': 404, + 'TIMEOUT': 408, + 'CONFLICT': 409, + 'PRECONDITION_FAILED': 412, + 'PAYLOAD_TOO_LARGE': 413, + 'METHOD_NOT_SUPPORTED': 405, + 'UNPROCESSABLE_CONTENT': 422, + 'TOO_MANY_REQUESTS': 429, + 'INTERNAL_SERVER_ERROR': 500, + }; + status = trpcStatusMap[err.code] || 500; + message = err.message; + } else if ((err as any).statusCode) { + status = (err as any).statusCode; + message = err.message; + } else if ((err as any).status) { + status = (err as any).status; + message = err.message; + } else if (err.message) { + message = err.message; + } + + return c.json({ message }, status as any); }); -app.listen(4000, () => { - console.log("Server is running on http://localhost:4000/api/mobile/"); +serve({ + fetch: app.fetch, + port: 4000, +}, (info) => { + console.log(`Server is running on http://localhost:${info.port}/api/mobile/`); }); diff --git a/apps/backend/package.json b/apps/backend/package.json index d319667..2ee2228 100755 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -20,23 +20,22 @@ "dependencies": { "@aws-sdk/client-s3": "^3.888.0", "@aws-sdk/s3-request-presigner": "^3.888.0", + "@hono/node-server": "^1.19.11", + "@hono/trpc-server": "^0.4.2", "@trpc/server": "^11.6.0", "@turf/turf": "^7.2.0", "@types/bcryptjs": "^2.4.6", - "@types/cors": "^2.8.19", "axios": "^1.11.0", "bcryptjs": "^3.0.2", - "cors": "^2.8.5", "dayjs": "^1.11.18", "dotenv": "^17.2.1", "expo-server-sdk": "^4.0.0", - "express": "^5.1.0", "fuse.js": "^7.1.0", + "hono": "^4.12.9", "jose": "^6.2.2", "zod": "^4.1.12" }, "devDependencies": { - "@types/express": "^5.0.3", "@types/node": "^24.5.2", "rimraf": "^6.1.2", "ts-node-dev": "^2.0.0", diff --git a/apps/backend/src/apis/admin-apis/apis/av-router.ts b/apps/backend/src/apis/admin-apis/apis/av-router.ts index cf30b8d..cc2c6ef 100755 --- a/apps/backend/src/apis/admin-apis/apis/av-router.ts +++ b/apps/backend/src/apis/admin-apis/apis/av-router.ts @@ -1,10 +1,10 @@ -import { Router } from "express"; +import { Hono } from 'hono'; import { authenticateStaff } from "@/src/middleware/staff-auth"; -const router = Router(); +const router = new Hono(); // Apply staff authentication to all admin routes -router.use(authenticateStaff); +router.use('*', authenticateStaff); const avRouter = router; diff --git a/apps/backend/src/apis/admin-apis/apis/product.controller.ts b/apps/backend/src/apis/admin-apis/apis/product.controller.ts index 45a1ced..2d91554 100644 --- a/apps/backend/src/apis/admin-apis/apis/product.controller.ts +++ b/apps/backend/src/apis/admin-apis/apis/product.controller.ts @@ -1,4 +1,4 @@ -import { Request, Response } from "express"; +import { Context } from 'hono'; import { checkProductExistsByName, checkUnitExists, @@ -25,8 +25,9 @@ type CreateDeal = { /** * Create a new product */ -export const createProduct = async (req: Request, res: Response) => { - const { name, shortDescription, longDescription, unitId, storeId, price, marketPrice, incrementStep, productQuantity, isSuspended, isFlashAvailable, flashPrice, deals, tagIds } = req.body; +export const createProduct = async (c: Context) => { + const body = await c.req.parseBody({ all: true }); + const { name, shortDescription, longDescription, unitId, storeId, price, marketPrice, incrementStep, productQuantity, isSuspended, isFlashAvailable, flashPrice, deals, tagIds } = body; // Validate required fields if (!name || !unitId || !storeId || !price) { @@ -34,100 +35,111 @@ export const createProduct = async (req: Request, res: Response) => { } // Check for duplicate name - const existingProduct = await checkProductExistsByName(name.trim()) + const existingProduct = await checkProductExistsByName((name as string).trim()) if (existingProduct) { throw new ApiError("A product with this name already exists", 400); } // Check if unit exists - const unitExists = await checkUnitExists(unitId) + const unitExists = await checkUnitExists(parseInt(unitId as string)) if (!unitExists) { throw new ApiError("Invalid unit ID", 400); } - // Extract images from req.files - const images = (req.files as Express.Multer.File[])?.filter(item => item.fieldname === 'images'); + // Extract images from body + const images = body.images; let uploadedImageUrls: string[] = []; - if (images && Array.isArray(images)) { - const imageUploadPromises = images.map((file, index) => { - const key = `product-images/${Date.now()}-${index}`; - return imageUploadS3(file.buffer, file.mimetype, key); - }); + if (images) { + const imageFiles = Array.isArray(images) ? images : [images]; + const imageUploadPromises = imageFiles.map((file, index) => { + if (file instanceof File) { + const key = `product-images/${Date.now()}-${index}`; + return imageUploadS3(Buffer.from(file.stream() as any), file.type, key); + } + return null; + }).filter(Boolean); - uploadedImageUrls = await Promise.all(imageUploadPromises); + uploadedImageUrls = await Promise.all(imageUploadPromises as Promise[]); } // Create product const productData: any = { - name, - shortDescription, - longDescription, - unitId, - storeId, - price, - marketPrice, - incrementStep: incrementStep || 1, - productQuantity: productQuantity || 1, - isSuspended: isSuspended || false, - isFlashAvailable: isFlashAvailable || false, + name: name as string, + shortDescription: shortDescription as string | undefined, + longDescription: longDescription as string | undefined, + unitId: parseInt(unitId as string), + storeId: parseInt(storeId as string), + price: parseFloat(price as string), + marketPrice: marketPrice ? parseFloat(marketPrice as string) : null, + incrementStep: incrementStep ? parseInt(incrementStep as string) : 1, + productQuantity: productQuantity ? parseInt(productQuantity as string) : 1, + isSuspended: isSuspended === 'true', + isFlashAvailable: isFlashAvailable === 'true', images: uploadedImageUrls, }; if (flashPrice) { - productData.flashPrice = parseFloat(flashPrice); + productData.flashPrice = parseFloat(flashPrice as string); } const newProduct = await createProductRecord(productData) // Handle deals if provided let createdDeals: AdminSpecialDeal[] = [] - if (deals && Array.isArray(deals)) { - createdDeals = await createSpecialDealsForProduct(newProduct.id, deals) + if (deals) { + const parsedDeals = typeof deals === 'string' ? JSON.parse(deals) : deals; + if (Array.isArray(parsedDeals)) { + createdDeals = await createSpecialDealsForProduct(newProduct.id, parsedDeals) + } } // Handle tag assignments if provided - if (tagIds && Array.isArray(tagIds)) { - await replaceProductTags(newProduct.id, tagIds) + if (tagIds) { + const parsedTagIds = typeof tagIds === 'string' ? JSON.parse(tagIds) : tagIds; + if (Array.isArray(parsedTagIds)) { + await replaceProductTags(newProduct.id, parsedTagIds) + } } // Reinitialize stores to reflect changes scheduleStoreInitialization() // Send response first - res.status(201).json({ + return c.json({ product: newProduct, deals: createdDeals, message: "Product created successfully", - }); + }, 201); }; /** * Update a product */ -export const updateProduct = async (req: Request, res: Response) => { - const id = req.params.id as string - const { name, shortDescription, longDescription, unitId, storeId, price, marketPrice, incrementStep, productQuantity, isSuspended, isFlashAvailable, flashPrice, deals:dealsRaw, imagesToDelete:imagesToDeleteRaw, tagIds } = req.body; +export const updateProduct = async (c: Context) => { + const id = c.req.param('id') + const body = await c.req.parseBody({ all: true }); + const { name, shortDescription, longDescription, unitId, storeId, price, marketPrice, incrementStep, productQuantity, isSuspended, isFlashAvailable, flashPrice, deals:dealsRaw, imagesToDelete:imagesToDeleteRaw, tagIds } = body; - const deals = dealsRaw ? JSON.parse(dealsRaw) : null; - const imagesToDelete = imagesToDeleteRaw ? JSON.parse(imagesToDeleteRaw) : []; + const deals = dealsRaw ? (typeof dealsRaw === 'string' ? JSON.parse(dealsRaw) : dealsRaw) : null; + const imagesToDelete = imagesToDeleteRaw ? (typeof imagesToDeleteRaw === 'string' ? JSON.parse(imagesToDeleteRaw) : imagesToDeleteRaw) : []; if (!name || !unitId || !storeId || !price) { throw new ApiError("Name, unitId, storeId, and price are required", 400); } // Check if unit exists - const unitExists = await checkUnitExists(unitId) + const unitExists = await checkUnitExists(parseInt(unitId as string)) if (!unitExists) { throw new ApiError("Invalid unit ID", 400); } // Get current product to handle image updates - const currentImages = await getProductImagesById(parseInt(id)) + const currentImages = await getProductImagesById(parseInt(id as string)) if (!currentImages) { throw new ApiError("Product not found", 404); @@ -154,45 +166,49 @@ export const updateProduct = async (req: Request, res: Response) => { updatedImages = updatedImages.filter(img => !imagesToRemoveFromDb.includes(img)); } - // Extract new images from req.files - const images = (req.files as Express.Multer.File[])?.filter(item => item.fieldname === 'images'); + // Extract new images from body + const images = body.images; let uploadedImageUrls: string[] = []; - if (images && Array.isArray(images)) { - const imageUploadPromises = images.map((file, index) => { - const key = `product-images/${Date.now()}-${index}`; - return imageUploadS3(file.buffer, file.mimetype, key); - }); + if (images) { + const imageFiles = Array.isArray(images) ? images : [images]; + const imageUploadPromises = imageFiles.map((file, index) => { + if (file instanceof File) { + const key = `product-images/${Date.now()}-${index}`; + return imageUploadS3(Buffer.from(file.stream() as any), file.type, key); + } + return null; + }).filter(Boolean); - uploadedImageUrls = await Promise.all(imageUploadPromises); + uploadedImageUrls = await Promise.all(imageUploadPromises as Promise[]); } // Combine remaining current images with new uploaded images const finalImages = [...updatedImages, ...uploadedImageUrls]; const updateData: any = { - name, - shortDescription, - longDescription, - unitId, - storeId, - price, - marketPrice, - incrementStep: incrementStep || 1, - productQuantity: productQuantity || 1, - isSuspended: isSuspended || false, + name: name as string, + shortDescription: shortDescription as string | undefined, + longDescription: longDescription as string | undefined, + unitId: parseInt(unitId as string), + storeId: parseInt(storeId as string), + price: parseFloat(price as string), + marketPrice: marketPrice ? parseFloat(marketPrice as string) : null, + incrementStep: incrementStep ? parseInt(incrementStep as string) : 1, + productQuantity: productQuantity ? parseInt(productQuantity as string) : 1, + isSuspended: isSuspended === 'true', images: finalImages.length > 0 ? finalImages : undefined, }; if (isFlashAvailable !== undefined) { - updateData.isFlashAvailable = isFlashAvailable; + updateData.isFlashAvailable = isFlashAvailable === 'true'; } if (flashPrice !== undefined) { - updateData.flashPrice = flashPrice ? parseFloat(flashPrice) : null; + updateData.flashPrice = flashPrice ? parseFloat(flashPrice as string) : null; } - const updatedProduct = await updateProductRecord(parseInt(id), updateData) + const updatedProduct = await updateProductRecord(parseInt(id as string), updateData) if (!updatedProduct) { throw new ApiError("Product not found", 404); @@ -200,22 +216,21 @@ export const updateProduct = async (req: Request, res: Response) => { // Handle deals if provided if (deals && Array.isArray(deals)) { - await updateProductDeals(parseInt(id), deals) + await updateProductDeals(parseInt(id as string), deals) } // Handle tag assignments if provided - // if (tagIds && Array.isArray(tagIds)) { - if (tagIds && Boolean(tagIds)) { - const tagIdsArray = Array.isArray(tagIds) ? tagIds : [+tagIds] - await replaceProductTags(parseInt(id), tagIdsArray) + if (tagIds) { + const parsedTagIds = typeof tagIds === 'string' ? [parseInt(tagIds)] : (Array.isArray(tagIds) ? tagIds.map((t: any) => parseInt(t)) : [parseInt(tagIds as any)]) + await replaceProductTags(parseInt(id as string), parsedTagIds) } // Reinitialize stores to reflect changes scheduleStoreInitialization() // Send response first - res.status(200).json({ + return c.json({ product: updatedProduct, message: "Product updated successfully", - }); + }, 200); }; diff --git a/apps/backend/src/apis/common-apis/apis/common-product.controller.ts b/apps/backend/src/apis/common-apis/apis/common-product.controller.ts index 3594a8f..4499874 100644 --- a/apps/backend/src/apis/common-apis/apis/common-product.controller.ts +++ b/apps/backend/src/apis/common-apis/apis/common-product.controller.ts @@ -1,4 +1,4 @@ -import { Request, Response } from "express"; +import { Context } from 'hono'; import { scaffoldAssetUrl } from "@/src/lib/s3-client" import { getNextDeliveryDate } from "@/src/trpc/apis/common-apis/common" import { @@ -9,19 +9,19 @@ import { /** * Get all products summary for dropdown */ -export const getAllProductsSummary = async (req: Request, res: Response) => { +export const getAllProductsSummary = async (c: Context) => { try { - const { tagId } = req.query; - const tagIdNum = tagId ? parseInt(tagId as string) : undefined; + const tagId = c.req.query('tagId'); + const tagIdNum = tagId ? parseInt(tagId) : undefined; // If tagId is provided but no products found, return empty array if (tagIdNum) { const products = await getAllProductsWithUnits(tagIdNum); if (products.length === 0) { - return res.status(200).json({ + return c.json({ products: [], count: 0, - }); + }, 200); } } @@ -46,13 +46,13 @@ export const getAllProductsSummary = async (req: Request, res: Response) => { }) ); - return res.status(200).json({ + return c.json({ products: formattedProducts, count: formattedProducts.length, - }); + }, 200); } catch (error) { console.error("Get products summary error:", error); - return res.status(500).json({ error: "Failed to fetch products summary" }); + return c.json({ error: "Failed to fetch products summary" }, 500); } }; diff --git a/apps/backend/src/apis/common-apis/apis/common-product.router.ts b/apps/backend/src/apis/common-apis/apis/common-product.router.ts index 90b7655..f46bd20 100644 --- a/apps/backend/src/apis/common-apis/apis/common-product.router.ts +++ b/apps/backend/src/apis/common-apis/apis/common-product.router.ts @@ -1,10 +1,10 @@ -import { Router } from "express"; +import { Hono } from 'hono'; import { getAllProductsSummary } from "@/src/apis/common-apis/apis/common-product.controller" -const router = Router(); +const router = new Hono(); router.get("/summary", getAllProductsSummary); const commonProductsRouter= router; -export default commonProductsRouter; \ No newline at end of file +export default commonProductsRouter; diff --git a/apps/backend/src/apis/common-apis/apis/common.router.ts b/apps/backend/src/apis/common-apis/apis/common.router.ts index 7277d1f..17a1324 100644 --- a/apps/backend/src/apis/common-apis/apis/common.router.ts +++ b/apps/backend/src/apis/common-apis/apis/common.router.ts @@ -1,10 +1,10 @@ -import { Router } from "express"; +import { Hono } from 'hono'; import commonProductsRouter from "@/src/apis/common-apis/apis/common-product.router" -const router = Router(); +const router = new Hono(); -router.use('/products', commonProductsRouter) +router.route('/products', commonProductsRouter) const commonRouter = router; -export default commonRouter; \ No newline at end of file +export default commonRouter; diff --git a/apps/backend/src/lib/catch-async.ts b/apps/backend/src/lib/catch-async.ts index d78004b..878afc9 100755 --- a/apps/backend/src/lib/catch-async.ts +++ b/apps/backend/src/lib/catch-async.ts @@ -1,6 +1,11 @@ -import express from 'express'; -const catchAsync = - (fn: express.RequestHandler) => - (req: express.Request, res: express.Response, next: express.NextFunction) => - Promise.resolve(fn(req, res, next)).catch(next); -export default catchAsync; \ No newline at end of file +// catchAsync is no longer needed with Hono +// Hono handles async errors automatically +// This file is kept for backward compatibility but should be removed in the future + +import { Context } from 'hono'; + +const catchAsync = (fn: (c: Context) => Promise) => { + return fn; +}; + +export default catchAsync; diff --git a/apps/backend/src/main-router.ts b/apps/backend/src/main-router.ts index f8dd753..60e11e6 100755 --- a/apps/backend/src/main-router.ts +++ b/apps/backend/src/main-router.ts @@ -1,56 +1,36 @@ -import { Router, Request, Response, NextFunction } from "express"; +import { Hono } from 'hono'; import avRouter from "@/src/apis/admin-apis/apis/av-router" import { ApiError } from "@/src/lib/api-error" import v1Router from "@/src/v1-router" import testController from "@/src/test-controller" import { authenticateUser } from "@/src/middleware/auth.middleware" - -const router = Router(); +const router = new Hono(); // Health check endpoints (no auth required) -router.get('/health', (req: Request, res: Response) => { - res.status(200).json({ +router.get('/health', (c) => { + return c.json({ status: 'OK', timestamp: new Date().toISOString(), uptime: process.uptime(), message: 'Hello world' }); }); -router.get('/seed', (req:Request, res: Response) => { - res.status(200).json({ + +router.get('/seed', (c) => { + return c.json({ status: 'OK', timestamp: new Date().toISOString(), uptime: process.uptime() }); -}) +}); // Apply authentication middleware to all subsequent routes -router.use(authenticateUser); +router.use('*', authenticateUser); -router.use('/v1', v1Router); -// router.use('/av', avRouter); -router.use('/test', testController); - -// Global error handling middleware -router.use((err: Error, req: Request, res: Response, next: NextFunction) => { - console.error('Error:', err); - - if (err instanceof ApiError) { - return res.status(err.statusCode).json({ - error: err.message, - details: err.details, - statusCode: err.statusCode - }); - } - - // Handle unknown errors - return res.status(500).json({ - error: 'Internal Server Error', - message: process.env.NODE_ENV === 'development' ? err.message : undefined, - statusCode: 500 - }); -}); +router.route('/v1', v1Router); +// router.route('/av', avRouter); +router.route('/test', testController); const mainRouter = router; diff --git a/apps/backend/src/middleware/auth.middleware.ts b/apps/backend/src/middleware/auth.middleware.ts index 0def21a..f01a988 100644 --- a/apps/backend/src/middleware/auth.middleware.ts +++ b/apps/backend/src/middleware/auth.middleware.ts @@ -1,32 +1,31 @@ -import { Request, Response, NextFunction } from 'express'; +import { Context, Next } from 'hono'; import { jwtVerify } from 'jose'; import { getStaffUserById, isUserSuspended } from '@/src/dbService'; import { ApiError } from '@/src/lib/api-error'; import { encodedJwtSecret } from '@/src/lib/env-exporter'; -interface AuthenticatedRequest extends Request { - user?: { - userId: number; - name?: string; - email?: string; - mobile?: string; - }; - staffUser?: { - id: number; - name: string; - }; +interface UserContext { + userId: number; + name?: string; + email?: string; + mobile?: string; } -export const authenticateUser = async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { +interface StaffContext { + id: number; + name: string; +} + +export const authenticateUser = async (c: Context, next: Next) => { try { - const authHeader = req.headers.authorization; + const authHeader = c.req.header('authorization'); if (!authHeader?.startsWith('Bearer ')) { throw new ApiError('Authorization token required', 401); } const token = authHeader.substring(7); - console.log(req.headers) + console.log(c.req.header) const { payload } = await jwtVerify(token, encodedJwtSecret); const decoded = payload as any; @@ -51,13 +50,13 @@ export const authenticateUser = async (req: AuthenticatedRequest, res: Response, throw new ApiError('Invalid staff token', 401); } - req.staffUser = { + c.set('staffUser', { id: staff.id, name: staff.name, - }; + }); } else { // This is a regular user token - req.user = decoded; + c.set('user', decoded); /* // Old implementation - direct DB queries: @@ -82,8 +81,8 @@ export const authenticateUser = async (req: AuthenticatedRequest, res: Response, } } - next(); + await next(); } catch (error) { - next(error); + throw error; } }; diff --git a/apps/backend/src/middleware/auth.ts b/apps/backend/src/middleware/auth.ts index 7ac6042..d059b06 100755 --- a/apps/backend/src/middleware/auth.ts +++ b/apps/backend/src/middleware/auth.ts @@ -1,21 +1,12 @@ -import { Request, Response, NextFunction } from 'express'; +import { Context, Next } from 'hono'; import { jwtVerify, errors } from 'jose'; import { ApiError } from '@/src/lib/api-error' import { encodedJwtSecret } from '@/src/lib/env-exporter'; -// Extend the Request interface to include user property -declare global { - namespace Express { - interface Request { - user?: any; - } - } -} - -export const verifyToken = async (req: Request, res: Response, next: NextFunction) => { +export const verifyToken = async (c: Context, next: Next) => { try { // Get token from Authorization header - const authHeader = req.headers.authorization; + const authHeader = c.req.header('authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { @@ -32,37 +23,38 @@ export const verifyToken = async (req: Request, res: Response, next: NextFunctio const { payload } = await jwtVerify(token, encodedJwtSecret); - // Add user info to request - req.user = payload; + // Add user info to context + c.set('user', payload); - next(); + await next(); } catch (error) { if (error instanceof errors.JOSEError) { - next(new ApiError('Invalid Auth Credentials', 401)); + throw new ApiError('Invalid Auth Credentials', 401); } else { - next(error); + throw error; } } }; export const requireRole = (roles: string[]) => { - return (req: Request, res: Response, next: NextFunction) => { + return async (c: Context, next: Next) => { try { - if (!req.user) { + const user = c.get('user'); + if (!user) { throw new ApiError('Authentication required', 401); } // Check if user has any of the required roles - const userRoles = req.user.roles || []; + const userRoles = user.roles || []; const hasPermission = roles.some(role => userRoles.includes(role)); if (!hasPermission) { throw new ApiError('Access denied. Insufficient permissions', 403); } - next(); + await next(); } catch (error) { - next(error); + throw error; } }; }; diff --git a/apps/backend/src/middleware/staff-auth.ts b/apps/backend/src/middleware/staff-auth.ts index bb2aeda..17ccb27 100644 --- a/apps/backend/src/middleware/staff-auth.ts +++ b/apps/backend/src/middleware/staff-auth.ts @@ -1,21 +1,9 @@ -import { Request, Response, NextFunction } from 'express'; -import { jwtVerify, errors } from 'jose'; +import { Context, Next } from 'hono'; +import { jwtVerify } from 'jose'; import { getStaffUserById } from '@/src/dbService'; import { ApiError } from '@/src/lib/api-error'; import { encodedJwtSecret } from '@/src/lib/env-exporter'; -// Extend Request interface to include staffUser -declare global { - namespace Express { - interface Request { - staffUser?: { - id: number; - name: string; - }; - } - } -} - /** * Verify JWT token and extract payload */ @@ -29,12 +17,12 @@ const verifyStaffToken = async (token: string) => { }; /** - * Middleware to authenticate staff users and attach staffUser to request + * Middleware to authenticate staff users and attach staffUser to context */ -export const authenticateStaff = async (req: Request, res: Response, next: NextFunction) => { +export const authenticateStaff = async (c: Context, next: Next) => { try { // Extract token from Authorization header - const authHeader = req.headers.authorization; + const authHeader = c.req.header('authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { throw new ApiError('Staff authentication required', 401); @@ -72,14 +60,14 @@ export const authenticateStaff = async (req: Request, res: Response, next: NextF throw new ApiError('Staff user not found', 401); } - // Attach staff user to request - req.staffUser = { + // Attach staff user to context + c.set('staffUser', { id: staff.id, name: staff.name, - }; + }); - next(); + await next(); } catch (error) { - next(error); + throw error; } }; diff --git a/apps/backend/src/test-controller.ts b/apps/backend/src/test-controller.ts index ae98af6..ed6d6b2 100644 --- a/apps/backend/src/test-controller.ts +++ b/apps/backend/src/test-controller.ts @@ -1,13 +1,13 @@ -import { Router, Request, Response } from 'express'; +import { Hono } from 'hono'; -const router = Router(); +const router = new Hono(); -router.get('/', (req: Request, res: Response) => { - res.json({ +router.get('/', (c) => { + return c.json({ status: 'ok', message: 'Health check passed', timestamp: new Date().toISOString(), }); }); -export default router; \ No newline at end of file +export default router; diff --git a/apps/backend/src/trpc/apis/user-apis/apis/auth.ts b/apps/backend/src/trpc/apis/user-apis/apis/auth.ts index a0cd2aa..93fb98b 100644 --- a/apps/backend/src/trpc/apis/user-apis/apis/auth.ts +++ b/apps/backend/src/trpc/apis/user-apis/apis/auth.ts @@ -357,7 +357,8 @@ export const authRouter = router({ ? await generateSignedUrlFromS3Url(userDetail.profileImage) : null - const token = ctx.req.headers.authorization?.replace('Bearer ', '') || '' + const authHeader = ctx.req.header('authorization'); + const token = authHeader?.replace('Bearer ', '') || '' const response: UserAuthResponse = { token, diff --git a/apps/backend/src/trpc/trpc-index.ts b/apps/backend/src/trpc/trpc-index.ts index 85c5457..9c30803 100644 --- a/apps/backend/src/trpc/trpc-index.ts +++ b/apps/backend/src/trpc/trpc-index.ts @@ -1,9 +1,8 @@ import { initTRPC, TRPCError } from '@trpc/server'; -import { type CreateExpressContextOptions } from '@trpc/server/adapters/express'; +import type { Context as HonoContext } from 'hono'; export interface Context { - req: CreateExpressContextOptions['req']; - res: CreateExpressContextOptions['res']; + req: HonoContext['req']; user?: any; staffUser?: { id: number; @@ -70,4 +69,4 @@ export const protectedProcedure = t.procedure.use(errorLoggerMiddleware).use( ); export const createCallerFactory = t.createCallerFactory; -export const createTRPCRouter = t.router; \ No newline at end of file +export const createTRPCRouter = t.router; diff --git a/apps/backend/src/types/hono.d.ts b/apps/backend/src/types/hono.d.ts new file mode 100644 index 0000000..07e8bba --- /dev/null +++ b/apps/backend/src/types/hono.d.ts @@ -0,0 +1,13 @@ +import { Context as HonoContext } from 'hono'; + +declare module 'hono' { + interface ContextVariableMap { + user?: any; + staffUser?: { + id: number; + name: string; + }; + } +} + +export {}; diff --git a/apps/backend/src/v1-router.ts b/apps/backend/src/v1-router.ts index 3808c3a..4c11fca 100644 --- a/apps/backend/src/v1-router.ts +++ b/apps/backend/src/v1-router.ts @@ -1,12 +1,11 @@ - import { Router } from "express"; +import { Hono } from 'hono'; import avRouter from "@/src/apis/admin-apis/apis/av-router" import commonRouter from "@/src/apis/common-apis/apis/common.router" -const router = Router(); - -router.use('/av', avRouter); -router.use('/cm', commonRouter); +const router = new Hono(); +router.route('/av', avRouter); +router.route('/cm', commonRouter); const v1Router = router; diff --git a/package-lock.json b/package-lock.json index 6ed71bd..457be0d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -127,23 +127,22 @@ "dependencies": { "@aws-sdk/client-s3": "^3.888.0", "@aws-sdk/s3-request-presigner": "^3.888.0", + "@hono/node-server": "^1.19.11", + "@hono/trpc-server": "^0.4.2", "@trpc/server": "^11.6.0", "@turf/turf": "^7.2.0", "@types/bcryptjs": "^2.4.6", - "@types/cors": "^2.8.19", "axios": "^1.11.0", "bcryptjs": "^3.0.2", - "cors": "^2.8.5", "dayjs": "^1.11.18", "dotenv": "^17.2.1", "expo-server-sdk": "^4.0.0", - "express": "^5.1.0", "fuse.js": "^7.1.0", + "hono": "^4.12.9", "jose": "^6.2.2", "zod": "^4.1.12" }, "devDependencies": { - "@types/express": "^5.0.3", "@types/node": "^24.5.2", "rimraf": "^6.1.2", "ts-node-dev": "^2.0.0", @@ -3720,6 +3719,31 @@ "excpretty": "build/cli.js" } }, + "node_modules/@hono/node-server": { + "version": "1.19.11", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", + "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@hono/trpc-server": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@hono/trpc-server/-/trpc-server-0.4.2.tgz", + "integrity": "sha512-3TDrc42CZLgcTFkXQba+y7JlRWRiyw1AqhLqztWyNS2IFT+3bHld0lxKdGBttCtGKHYx0505dM67RMazjhdZqw==", + "license": "MIT", + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@trpc/server": "^10.10.0 || >11.0.0-rc", + "hono": ">=4.0.0" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "dev": true, @@ -7600,35 +7624,11 @@ "version": "2.4.6", "license": "MIT" }, - "node_modules/@types/body-parser": { - "version": "1.19.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, "node_modules/@types/canvas-confetti": { "version": "1.9.0", "dev": true, "license": "MIT" }, - "node_modules/@types/connect": { - "version": "3.4.38", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/cors": { - "version": "2.8.19", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/d3-voronoi": { "version": "1.1.12", "license": "MIT" @@ -7642,27 +7642,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/express": { - "version": "5.0.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^5.0.0", - "@types/serve-static": "^2" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "5.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, "node_modules/@types/geojson": { "version": "7946.0.16", "license": "MIT" @@ -7706,11 +7685,6 @@ "@types/react": "*" } }, - "node_modules/@types/http-errors": { - "version": "2.0.5", - "dev": true, - "license": "MIT" - }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "license": "MIT" @@ -7781,16 +7755,6 @@ "pg-types": "^2.2.0" } }, - "node_modules/@types/qs": { - "version": "6.15.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "dev": true, - "license": "MIT" - }, "node_modules/@types/react": { "version": "19.0.14", "license": "MIT", @@ -7821,23 +7785,6 @@ "@types/react": "*" } }, - "node_modules/@types/send": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*" - } - }, "node_modules/@types/stack-utils": { "version": "2.0.3", "license": "MIT" @@ -8232,17 +8179,6 @@ "node": ">=6.5" } }, - "node_modules/accepts": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/acorn": { "version": "8.16.0", "license": "MIT", @@ -8915,28 +8851,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/body-parser": { - "version": "2.2.2", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", - "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/boolbase": { "version": "1.0.0", "license": "ISC" @@ -9573,17 +9487,6 @@ "@babel/types": "^7.6.1" } }, - "node_modules/content-disposition": { - "version": "1.0.1", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/content-type": { "version": "1.0.5", "license": "MIT", @@ -9606,13 +9509,6 @@ "version": "2.0.0", "license": "MIT" }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, "node_modules/copy-anything": { "version": "4.0.5", "license": "MIT", @@ -9637,21 +9533,6 @@ "url": "https://opencollective.com/core-js" } }, - "node_modules/cors": { - "version": "2.8.6", - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/cosmiconfig": { "version": "5.2.1", "license": "MIT", @@ -11587,47 +11468,6 @@ "version": "3.1.3", "license": "Apache-2.0" }, - "node_modules/express": { - "version": "5.2.1", - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/fallback-ui": { "resolved": "apps/fallback-ui", "link": true @@ -11809,25 +11649,6 @@ "node": ">=0.10.0" } }, - "node_modules/finalhandler": { - "version": "2.1.1", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/find-up": { "version": "5.0.0", "license": "MIT", @@ -11996,13 +11817,6 @@ "node": ">=8" } }, - "node_modules/fresh": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "license": "ISC" @@ -12439,6 +12253,15 @@ "version": "16.13.1", "license": "MIT" }, + "node_modules/hono": { + "version": "4.12.9", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.9.tgz", + "integrity": "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/hosted-git-info": { "version": "7.0.2", "license": "ISC", @@ -12486,20 +12309,6 @@ "version": "1.1.0", "license": "BSD-3-Clause" }, - "node_modules/iconv-lite": { - "version": "0.7.2", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/ieee754": { "version": "1.2.1", "funding": [ @@ -12940,10 +12749,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-promise": { - "version": "4.0.0", - "license": "MIT" - }, "node_modules/is-regex": { "version": "1.2.1", "license": "MIT", @@ -13773,27 +13578,10 @@ "version": "2.0.14", "license": "CC0-1.0" }, - "node_modules/media-typer": { - "version": "1.1.0", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/memoize-one": { "version": "5.2.1", "license": "MIT" }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/merge-stream": { "version": "2.0.0", "license": "MIT" @@ -14175,27 +13963,6 @@ "node": ">=4" } }, - "node_modules/mime-db": { - "version": "1.54.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.2", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/mimic-fn": { "version": "1.2.0", "license": "MIT", @@ -14311,13 +14078,6 @@ "dev": true, "license": "MIT" }, - "node_modules/negotiator": { - "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/nested-error-stacks": { "version": "2.0.1", "license": "MIT" @@ -15443,19 +15203,6 @@ "qrcode-terminal": "bin/qrcode-terminal.js" } }, - "node_modules/qs": { - "version": "6.15.0", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/query-string": { "version": "7.1.3", "license": "MIT", @@ -15516,19 +15263,6 @@ "node": ">= 0.6" } }, - "node_modules/raw-body": { - "version": "3.0.2", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/rbush": { "version": "3.0.1", "license": "MIT", @@ -16394,28 +16128,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/router": { - "version": "2.2.0", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/router/node_modules/path-to-regexp": { - "version": "8.3.0", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "funding": [ @@ -16529,30 +16241,6 @@ "semver": "bin/semver.js" } }, - "node_modules/send": { - "version": "1.2.1", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/serialize-error": { "version": "2.1.0", "license": "MIT", @@ -16577,23 +16265,6 @@ "seroval": "^1.0" } }, - "node_modules/serve-static": { - "version": "2.2.1", - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/server-only": { "version": "0.0.1", "license": "MIT" @@ -17944,18 +17615,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/type-is": { - "version": "2.0.1", - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/typed-array-buffer": { "version": "1.0.3", "dev": true,