{"version":3,"file":"index.cjs","sources":["../../src/index.ts"],"sourcesContent":["import { eventHandler, toWebRequest } from '@tanstack/start-server-core'\nimport vinxiFileRoutes from 'vinxi/routes'\nimport type { ResolveParams } from '@tanstack/router-core'\n\nexport type StartAPIHandlerCallback = (ctx: {\n  request: Request\n}) => Response | Promise<Response>\n\nexport type StartAPIMethodCallback<TPath extends string> = (ctx: {\n  request: Request\n  params: ResolveParams<TPath>\n}) => Response | Promise<Response>\n\nconst HTTP_API_METHODS = [\n  'GET',\n  'POST',\n  'PUT',\n  'PATCH',\n  'DELETE',\n  'OPTIONS',\n  'HEAD',\n] as const\nexport type HTTP_API_METHOD = (typeof HTTP_API_METHODS)[number]\n\n/**\n *\n * @param cb The callback function that will be called when the API handler is invoked\n * @returns The response from the callback function\n */\nexport function createStartAPIHandler(cb: StartAPIHandlerCallback) {\n  return eventHandler(async (event) => {\n    const request = toWebRequest(event)!\n    const res = await cb({ request })\n    return res\n  })\n}\n\ntype APIRoute<TPath extends string> = {\n  path: TPath\n  methods: Partial<Record<HTTP_API_METHOD, StartAPIMethodCallback<TPath>>>\n}\n\ntype CreateAPIRouteFn<TPath extends string> = (\n  methods: Partial<Record<HTTP_API_METHOD, StartAPIMethodCallback<TPath>>>,\n) => APIRoute<TPath>\n\ntype CreateAPIRoute = <TPath extends string>(\n  path: TPath,\n) => CreateAPIRouteFn<TPath>\n\ntype APIRouteReturnType = ReturnType<ReturnType<CreateAPIRoute>>\n\n/**\n * This function is used to create an API route that will be listening on a specific path when you are not using the file-based routes.\n *\n * @param path The path that the API route will be listening on. You need to make sure that this is a valid TanStack Router path in order for the route to be matched. This means that you can use the following syntax:\n * /api/foo/$bar/name/$\n * - The `$bar` is a parameter that will be extracted from the URL and passed to the handler\n * - The `$` is a wildcard that will match any number of segments in the URL\n * @returns A function that takes the methods that the route will be listening on and returns the API route object\n */\nexport const createAPIRoute: CreateAPIRoute = (path) => (methods) => ({\n  path,\n  methods,\n})\n\n/**\n * This function is used to create an API route that will be listening on a specific path when you are using the file-based routes.\n *\n * @param filePath The path that the API file route will be listening on. This filePath should automatically be generated by the TSR plugin and should be a valid TanStack Router path\n * @returns A function that takes the methods that the route will be listening on and returns the API route object\n */\nexport const createAPIFileRoute: CreateAPIRoute = (filePath) => (methods) => ({\n  path: filePath,\n  methods,\n})\n\n/**\n * This function takes a URL object and a list of routes and finds the route that matches the URL.\n *\n * @param url URL object\n * @param entryRoutes List of routes entries in the TSR format to find the current match by the URL\n * @returns Returns the route that matches the URL or undefined if no route matches\n */\nfunction findRoute<TPayload = unknown>(\n  url: URL,\n  entryRoutes: Array<{ routePath: string; payload: TPayload }>,\n):\n  | {\n      routePath: string\n      params: Record<string, string>\n      payload: TPayload\n    }\n  | undefined {\n  const urlSegments = url.pathname.split('/').filter(Boolean)\n\n  const routes = entryRoutes\n    .sort((a, b) => {\n      const aParts = a.routePath.split('/').filter(Boolean)\n      const bParts = b.routePath.split('/').filter(Boolean)\n\n      return bParts.length - aParts.length\n    })\n    .filter((r) => {\n      const routeSegments = r.routePath.split('/').filter(Boolean)\n      return urlSegments.length >= routeSegments.length\n    })\n\n  for (const route of routes) {\n    const routeSegments = route.routePath.split('/').filter(Boolean)\n    const params: Record<string, string> = {}\n    let matches = true\n    for (let i = 0; i < routeSegments.length; i++) {\n      const routeSegment = routeSegments[i] as string\n      const urlSegment = urlSegments[i] as string\n      if (routeSegment.startsWith('$')) {\n        if (routeSegment === '$') {\n          const wildcardValue = urlSegments.slice(i).join('/')\n          if (wildcardValue !== '') {\n            params['*'] = wildcardValue\n            params['_splat'] = wildcardValue\n          } else {\n            matches = false\n            break\n          }\n        } else {\n          const paramName = routeSegment.slice(1)\n          params[paramName] = urlSegment\n        }\n      } else if (routeSegment !== urlSegment) {\n        matches = false\n        break\n      }\n    }\n    if (matches) {\n      return { routePath: route.routePath, params, payload: route.payload }\n    }\n  }\n\n  return undefined\n}\n\n/**\n * You should only be using this function if you are not using the file-based routes.\n *\n *\n * @param opts - A map of TSR routes with the values being the route handlers\n * @returns The handler for the incoming request\n *\n * @example\n * ```ts\n * // app/foo.ts\n * import { createAPIRoute } from '@tanstack/start-api-routes'\n * const fooBarRoute = createAPIRoute('/api/foo/$bar')({\n *  GET: ({ params }) => {\n *   return new Response(JSON.stringify({ params }))\n *  }\n * })\n *\n * // app/api.ts\n * import {\n *    createStartAPIHandler,\n *    defaultAPIRoutesHandler\n * } from '@tanstack/start-api-routes'\n *\n * export default createStartAPIHandler(\n *  defaultAPIRoutesHandler({\n *   '/api/foo/$bar': fooBarRoute\n *  })\n * )\n * ```\n */\nexport const defaultAPIRoutesHandler: (opts: {\n  routes: { [TPath in string]: APIRoute<TPath> }\n}) => StartAPIHandlerCallback = (opts) => {\n  return async ({ request }) => {\n    if (!HTTP_API_METHODS.includes(request.method as HTTP_API_METHOD)) {\n      return new Response('Method not allowed', { status: 405 })\n    }\n\n    const url = new URL(request.url, 'http://localhost:3000')\n\n    const routes = Object.entries(opts.routes).map(([routePath, route]) => ({\n      routePath,\n      payload: route,\n    }))\n\n    // Find the route that matches the request by the request URL\n    const match = findRoute(url, routes)\n\n    // If we don't have a route that could possibly handle the request, return a 404\n    if (!match) {\n      return new Response('Not found', { status: 404 })\n    }\n\n    // If the route path doesn't match the payload path, return a 404\n    if (match.routePath !== match.payload.path) {\n      console.error(\n        `Route path mismatch: ${match.routePath} !== ${match.payload.path}. Please make sure that the route path in \\`createAPIRoute\\` matches the path in the handler map in \\`defaultAPIRoutesHandler\\``,\n      )\n      return new Response('Not found', { status: 404 })\n    }\n\n    const method = request.method as HTTP_API_METHOD\n\n    // Get the handler for the request method based on the Request Method\n    const handler = match.payload.methods[method]\n\n    // If the handler is not defined, return a 405\n    if (!handler) {\n      return new Response('Method not allowed', { status: 405 })\n    }\n\n    return await handler({ request, params: match.params })\n  }\n}\n\ninterface CustomizedVinxiFileRoute {\n  path: string // this path adheres to the h3 router path format\n  filePath: string // this is the file path on the system\n  $APIRoute?: {\n    src: string // this is the path to the source file\n    import: () => Promise<{\n      APIRoute: APIRouteReturnType\n    }>\n  }\n}\n\n/**\n * This is populated by the work done in the config file using the tsrFileRouter\n */\nconst vinxiRoutes = (\n  vinxiFileRoutes as unknown as Array<CustomizedVinxiFileRoute>\n).filter((route) => route['$APIRoute'])\n\n/**\n * This function takes the vinxi routes and interpolates them into a format that can be worked with in the API handler\n *\n * @param routes The vinxi routes that have been filtered to only include those with a $APIRoute property\n * @returns An array of objects where the path `key` is interpolated to a valid TanStack Router path, with the `payload` being the original route object\n *\n * @example\n * ```\n * const input = [\n *  {\n *    path: '/api/boo/:$id?/name/*splat',\n *    filePath: '..../code/tanstack/router/examples/react/start-basic/app/routes/api.boo.$id.name.$.tsx',\n *   '$APIRoute': [Object]\n *  }\n * ]\n *\n * toTSRFileBasedRoutes(input)\n * [\n *  {\n *     path: '/api/boo/$id/name/$',\n *     route: {\n *       path: '/api/boo/:$id?/name/*splat',\n *       filePath: '..../code/tanstack/router/examples/react/start-basic/app/routes/api.boo.$id.name.$.tsx',\n *      '$APIRoute': [Object]\n *     }\n *  }\n * ]\n * ```\n */\nfunction toTSRFileBasedRoutes(\n  routes: Array<CustomizedVinxiFileRoute>,\n): Array<{ routePath: string; payload: CustomizedVinxiFileRoute }> {\n  const pairs: Array<{\n    routePath: string\n    payload: CustomizedVinxiFileRoute\n  }> = []\n\n  routes.forEach((route) => {\n    const parts = route.path.split('/').filter(Boolean)\n\n    const path = parts\n      .map((part) => {\n        if (part === '*splat') {\n          return '$'\n        }\n\n        if (part.startsWith(':$') && part.endsWith('?')) {\n          return part.slice(1, -1)\n        }\n\n        return part\n      })\n      .join('/')\n\n    pairs.push({ routePath: `/${path}`, payload: route })\n  })\n\n  return pairs\n}\n\n/**\n * This function is the default handler for the API routes when using file-based routes.\n *\n * @param StartAPIHandlerCallbackContext\n * @returns The handler for the incoming request\n *\n * @example\n * ```ts\n * // app/api.ts\n * import {\n *    createStartAPIHandler,\n *    defaultAPIFileRouteHandler\n * } from '@tanstack/start-api-routes'\n *\n * export default createStartAPIHandler(defaultAPIFileRouteHandler)\n * ```\n */\nexport const defaultAPIFileRouteHandler: StartAPIHandlerCallback = async ({\n  request,\n}) => {\n  // Simple early abort if there are no routes\n  if (!vinxiRoutes.length) {\n    return new Response('No routes found', { status: 404 })\n  }\n\n  if (!HTTP_API_METHODS.includes(request.method as HTTP_API_METHOD)) {\n    return new Response('Method not allowed', { status: 405 })\n  }\n\n  const routes = toTSRFileBasedRoutes(vinxiRoutes)\n\n  const url = new URL(request.url, 'http://localhost:3000')\n\n  // Find the route that file that matches the request by the request URL\n  const match = findRoute(url, routes)\n\n  // If we don't have a route that could possibly handle the request, return a 404\n  if (!match) {\n    return new Response('Not found', { status: 404 })\n  }\n\n  // The action is the route file that we need to import\n  // which contains the possible handlers for the incoming request\n  let action: APIRouteReturnType | undefined = undefined\n\n  try {\n    // We can guarantee that action is defined since we filtered for it earlier\n    action = await match.payload.$APIRoute!.import().then((m) => m.APIRoute)\n  } catch (err) {\n    // If we can't import the route file, return a 500\n    console.error('Error importing route file:', err)\n    return new Response('Internal server error', { status: 500 })\n  }\n\n  // If we don't have an action, return a 500\n  if (!action) {\n    return new Response('Internal server error', { status: 500 })\n  }\n\n  const method = request.method as HTTP_API_METHOD\n\n  // Get the handler for the request method based on the Request Method\n  const handler = action.methods[method]\n\n  // If the handler is not defined, return a 405\n  // What this means is that we have a route that matches the request\n  // but we don't have a handler for the request method\n  // i.e we have a route that matches /api/foo/$ but we don't have a POST handler\n  if (!handler) {\n    return new Response('Method not allowed', { status: 405 })\n  }\n\n  return await handler({ request, params: match.params })\n}\n"],"names":["eventHandler","toWebRequest"],"mappings":"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"}