Something went wrong. Try again.
[READ-ONLY]
a fast, modern browser for the npm registry forked from npmx.dev/npmx.dev
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178/** * Deno Integration (WASM) * * Uses @deno/doc (WASM build of deno_doc) for documentation generation. * This runs entirely in Node.js without requiring a Deno subprocess. * * @module server/utils/docs/client */
import { doc, type DocNode } from '@deno/doc'import type { DenoDocNode, DenoDocResult } from '#shared/types/deno-doc'import { isBuiltin } from 'node:module'
// =============================================================================// Configuration// =============================================================================
/** Timeout for fetching modules in milliseconds */const FETCH_TIMEOUT_MS = 30 * 1000
// =============================================================================// Main Export// =============================================================================
/** * Get documentation nodes for a package using @deno/doc WASM. */export async function getDocNodes(packageName: string, version: string): Promise<DenoDocResult> { // Get types URL from esm.sh header const typesUrl = await getTypesUrl(packageName, version)
if (!typesUrl) { return { version: 1, nodes: [] } }
// Generate docs using @deno/doc WASM let result: Record<string, DocNode[]> try { result = await doc([typesUrl], { load: createLoader(), resolve: createResolver(), }) } catch { return { version: 1, nodes: [] } }
// Collect all nodes from all specifiers const allNodes: DenoDocNode[] = [] for (const nodes of Object.values(result)) { allNodes.push(...(nodes as DenoDocNode[])) }
return { version: 1, nodes: allNodes }}
// =============================================================================// Module Loading// =============================================================================
/** Load response for the doc() function */interface LoadResponse { kind: 'module' specifier: string headers?: Record<string, string> content: string}
/** * Create a custom module loader for @deno/doc. * * Fetches modules from URLs using fetch(), with proper timeout handling. */function createLoader(): ( specifier: string, isDynamic?: boolean, cacheSetting?: string, checksum?: string,) => Promise<LoadResponse | undefined> { return async ( specifier: string, _isDynamic?: boolean, _cacheSetting?: string, _checksum?: string, ) => { const url = URL.parse(specifier)
if (url === null) { return undefined }
// Only handle http/https URLs if (url.protocol !== 'http:' && url.protocol !== 'https:') { return undefined }
try { const response = await $fetch.raw<Blob>(url.toString(), { method: 'GET', timeout: FETCH_TIMEOUT_MS, redirect: 'follow', })
if (response.status !== 200) { return undefined }
const content = (await response._data?.text()) ?? '' const headers: Record<string, string> = {} for (const [key, value] of response.headers) { headers[key.toLowerCase()] = value }
return { kind: 'module', specifier: response.url || specifier, headers, content, } } catch (e) { // eslint-disable-next-line no-console console.error(e) return undefined } }}
/** * Create a module resolver for @deno/doc. * * Handles resolving relative imports and esm.sh redirects. */function createResolver(): (specifier: string, referrer: string) => string { return (specifier: string, referrer: string) => { // Handle relative imports if (specifier.startsWith('.') || specifier.startsWith('/')) { return new URL(specifier, referrer).toString() }
// Handle bare specifiers - resolve through esm.sh if ( !specifier.startsWith('http://') && !specifier.startsWith('https://') && !isBuiltin(specifier) ) { // Try to resolve bare specifier relative to esm.sh base const baseUrl = new URL(referrer) if (baseUrl.hostname === 'esm.sh') { return `https://esm.sh/${specifier}` } }
return specifier }}
/** * Get the TypeScript types URL from esm.sh's x-typescript-types header. * * esm.sh serves types URL in the `x-typescript-types` header, not at the main URL. * Example: curl -sI 'https://esm.sh/ufo@1.5.0' returns header: * x-typescript-types: https://esm.sh/ufo@1.5.0/dist/index.d.ts */async function getTypesUrl(packageName: string, version: string): Promise<string | null> { const url = `https://esm.sh/${packageName}@${version}`
try { const response = await $fetch.raw(url, { method: 'HEAD', timeout: FETCH_TIMEOUT_MS, }) return response.headers.get('x-typescript-types') } catch (e) { // eslint-disable-next-line no-console console.error(e) return null }}