diff --git a/.editorconfig b/.editorconfig index a727df3..6ae0591 100644 --- a/.editorconfig +++ b/.editorconfig @@ -2,7 +2,8 @@ root = true [*] -indent_style = tab +indent_style = space +indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true diff --git a/adapters/functions/api/index.js b/adapters/functions/api/index.js index 1618d7a..49c43eb 100644 --- a/adapters/functions/api/index.js +++ b/adapters/functions/api/index.js @@ -16,25 +16,22 @@ * along with this program. If not, see . */ -/* eslint-disable no-undef, no-unused-vars */ + import { handleRequest } from '../src/index.js'; /** * Edge Function handler. - * * @param {Request} request - Standard Web API Request object - * @param {Object} [context] - Platform-specific context (Netlify only) - * @param {Object} [context.geo] - Geolocation data (Netlify) + * @param {object} [context] - Platform-specific context (Netlify only) + * @param {object} [context.geo] - Geolocation data (Netlify) * @param {string} [context.ip] - Client IP address (Netlify) - * @param {Object} [context.env] - Environment variables (Netlify) - * @param {Function} [context.waitUntil] - Background task extension (Netlify) + * @param {object} [context.env] - Environment variables (Netlify) + * @param {(promise: Promise) => void} [context.waitUntil] - Background task extension (Netlify) * @returns {Promise} Standard Web API Response - * * @example * // Netlify invokes with context * handler(request, { geo: {...}, ip: '1.2.3.4', env: {...}, waitUntil: fn }) - * * @example * // Vercel invokes without context * handler(request) @@ -70,7 +67,7 @@ export default async function handler(request, context) { const ctx = { waitUntil: isNetlify && context.waitUntil ? (promise) => context.waitUntil(promise) - : (promise) => { + : (_promise) => { // No-op on Vercel: background tasks not supported // Cache writes will run synchronously instead console.warn('waitUntil is not supported in Vercel Edge Runtime'); diff --git a/adapters/functions/deno.js b/adapters/functions/deno.js index 1605205..5dc960c 100644 --- a/adapters/functions/deno.js +++ b/adapters/functions/deno.js @@ -25,10 +25,8 @@ import { handleRequest } from './src/index.js'; * * This is the entry point for Deno Deploy deployments. It uses the * standard Deno.serve() API to handle incoming HTTP requests. - * * @param {Request} request - Standard Web API Request object * @returns {Promise} Standard Web API Response - * * @example * // Deno Deploy invokes automatically: * // Deno.serve((request) => handler(request)) diff --git a/adapters/pages/functions/[[path]].js b/adapters/pages/functions/[[path]].js index fdefbc5..a4ce63d 100644 --- a/adapters/pages/functions/[[path]].js +++ b/adapters/pages/functions/[[path]].js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -/* eslint-disable no-undef */ + import { handleRequest } from '../src/index.js'; @@ -31,21 +31,18 @@ import { handleRequest } from '../src/index.js'; * The [[path]] syntax in the filename creates a catch-all route that matches * any path, allowing this single function to handle all requests to the Pages * application. - * - * @param {Object} context - Pages Function context + * @param {object} context - Pages Function context * @param {Request} context.request - The incoming HTTP request - * @param {Object} context.env - Environment variables and bindings (KV, secrets, etc.) - * @param {Object} context.params - Route parameters (path segments from [[path]]) - * @param {Function} context.waitUntil - Extend function execution for background tasks - * @param {Function} context.next - Call next middleware in chain (not used here) - * @param {Object} context.data - Shared data between functions + * @param {object} context.env - Environment variables and bindings (KV, secrets, etc.) + * @param {object} context.params - Route parameters (path segments from [[path]]) + * @param {(promise: Promise) => void} context.waitUntil - Extend function execution for background tasks + * @param {() => Promise} context.next - Call next middleware in chain (not used here) + * @param {object} context.data - Shared data between functions * @returns {Promise} The HTTP response to return to the client - * * @example * // This is called automatically by Pages * // Runtime invokes: onRequest(context) * // Returns: Response with package data - * * @example * // Environment variables usage * // wrangler.toml: [vars] TIMEOUT_SECONDS = "60" @@ -58,7 +55,7 @@ export async function onRequest(context) { // Create a minimal ExecutionContext-like object for compatibility const ctx = { - waitUntil: waitUntil, + waitUntil, passThroughOnException: () => { // Pages doesn't support passThroughOnException, so this is a no-op console.warn('passThroughOnException is not supported in Pages Functions'); diff --git a/eslint.config.js b/eslint.config.js index ddbc45c..fed8fc3 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,10 +1,12 @@ import js from '@eslint/js'; import prettierConfig from 'eslint-config-prettier'; +import jsdoc from 'eslint-plugin-jsdoc'; export default [ js.configs.recommended, + jsdoc.configs['flat/recommended'], { - files: ['src/**/*.js', 'test/**/*.js'], + files: ['src/**/*.js', 'test/**/*.js', 'adapters/**/*.js'], languageOptions: { ecmaVersion: 2022, sourceType: 'module', @@ -33,6 +35,7 @@ export default [ TextDecoder: 'readonly', performance: 'readonly', globalThis: 'readonly', + process: 'readonly', // Vitest globals describe: 'readonly', @@ -45,7 +48,42 @@ export default [ vi: 'readonly' } }, + settings: { + jsdoc: { + mode: 'typescript', + tagNamePreference: { + returns: 'returns' + } + } + }, rules: { + // JSDoc rules overrides + 'jsdoc/require-description': 'warn', + 'jsdoc/require-returns': 'off', // Often redundant if return type is void or obvious + 'jsdoc/require-param-description': 'off', // Names are often self-explanatory + 'jsdoc/no-undefined-types': [ + 'warn', + { + definedTypes: [ + 'ExecutionContext', + 'Cache', + 'RequestInit', + 'Request', + 'Response', + 'Headers', + 'URL', + 'URLSearchParams', + 'AbortController', + 'AbortSignal', + 'ReadableStream', + 'WritableStream', + 'TransformStream', + 'TextEncoder', + 'TextDecoder' + ] + } + ], + // Code quality rules 'no-unused-vars': [ 'error', diff --git a/package-lock.json b/package-lock.json index d927e71..fcb85a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@cloudflare/workers-types": "^4.20251205.0", "eslint": "^9.39.1", "eslint-config-prettier": "^10.1.8", + "eslint-plugin-jsdoc": "^61.5.0", "prettier": "^3.7.4", "typescript": "^5.9.3", "vitest": "^3.1.4", @@ -188,6 +189,33 @@ "tslib": "^2.4.0" } }, + "node_modules/@es-joy/jsdoccomment": { + "version": "0.76.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.76.0.tgz", + "integrity": "sha512-g+RihtzFgGTx2WYCuTHbdOXJeAlGnROws0TeALx9ow/ZmOROOZkVg5wp/B44n0WJgI4SQFP1eWM2iRPlU2Y14w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.8", + "@typescript-eslint/types": "^8.46.0", + "comment-parser": "1.4.1", + "esquery": "^1.6.0", + "jsdoc-type-pratt-parser": "~6.10.0" + }, + "engines": { + "node": ">=20.11.0" + } + }, + "node_modules/@es-joy/resolve.exports": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@es-joy/resolve.exports/-/resolve.exports-1.2.0.tgz", + "integrity": "sha512-Q9hjxWI5xBM+qW2enxfe8wDKdFWMfd0Z29k5ZJnuBqD/CasY5Zryj09aCA6owbGATWz+39p5uIdaHXpopOcG8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.9", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", @@ -1584,6 +1612,19 @@ "win32" ] }, + "node_modules/@sindresorhus/base62": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/base62/-/base62-1.0.0.tgz", + "integrity": "sha512-TeheYy0ILzBEI/CO55CP6zJCSdSWeRtGnHy8U8dWSUH4I68iqTsy7HkMktR4xakThc9jotkPQUXT4ITdbV7cHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@sindresorhus/is": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.0.2.tgz", @@ -1635,6 +1676,20 @@ "dev": true, "license": "MIT" }, + "node_modules/@typescript-eslint/types": { + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.49.0.tgz", + "integrity": "sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@vitest/expect": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", @@ -1698,7 +1753,6 @@ "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/utils": "3.2.4", "pathe": "^2.0.3", @@ -1714,7 +1768,6 @@ "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/pretty-format": "3.2.4", "magic-string": "^0.30.17", @@ -1771,7 +1824,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1832,6 +1884,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/are-docs-informative": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", + "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2062,6 +2124,16 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/comment-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", + "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2327,7 +2399,6 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -2398,6 +2469,35 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-plugin-jsdoc": { + "version": "61.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-61.5.0.tgz", + "integrity": "sha512-PR81eOGq4S7diVnV9xzFSBE4CDENRQGP0Lckkek8AdHtbj+6Bm0cItwlFnxsLFriJHspiE3mpu8U20eODyToIg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@es-joy/jsdoccomment": "~0.76.0", + "@es-joy/resolve.exports": "1.2.0", + "are-docs-informative": "^0.0.2", + "comment-parser": "1.4.1", + "debug": "^4.4.3", + "escape-string-regexp": "^4.0.0", + "espree": "^10.4.0", + "esquery": "^1.6.0", + "html-entities": "^2.6.0", + "object-deep-merge": "^2.0.0", + "parse-imports-exports": "^0.2.4", + "semver": "^7.7.3", + "spdx-expression-parse": "^4.0.0", + "to-valid-identifier": "^1.0.0" + }, + "engines": { + "node": ">=20.11.0" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, "node_modules/eslint-scope": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", @@ -2846,6 +2946,23 @@ "node": ">= 0.4" } }, + "node_modules/html-entities": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -2997,6 +3114,16 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdoc-type-pratt-parser": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-6.10.0.tgz", + "integrity": "sha512-+LexoTRyYui5iOhJGn13N9ZazL23nAHGkXsa1p/C8yeq79WRfLBag6ZZ0FQG2aRoc9yfo59JT9EYCQonOkHKkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -3264,6 +3391,13 @@ "node": ">= 0.6" } }, + "node_modules/object-deep-merge": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/object-deep-merge/-/object-deep-merge-2.0.0.tgz", + "integrity": "sha512-3DC3UMpeffLTHiuXSy/UG4NOIYTLlY9u3V82+djSCLYClWobZiS4ivYzpIUWrRY/nfsJ8cWsKyG3QfyLePmhvg==", + "dev": true, + "license": "MIT" + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -3360,6 +3494,23 @@ "node": ">=6" } }, + "node_modules/parse-imports-exports": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/parse-imports-exports/-/parse-imports-exports-0.2.4.tgz", + "integrity": "sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-statements": "1.0.11" + } + }, + "node_modules/parse-statements": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/parse-statements/-/parse-statements-1.0.11.tgz", + "integrity": "sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==", + "dev": true, + "license": "MIT" + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -3429,7 +3580,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -3554,6 +3704,19 @@ "node": ">= 0.10" } }, + "node_modules/reserved-identifiers": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/reserved-identifiers/-/reserved-identifiers-1.2.0.tgz", + "integrity": "sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -3628,9 +3791,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { @@ -3845,6 +4008,31 @@ "node": ">=0.10.0" } }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -3979,6 +4167,23 @@ "node": ">=14.0.0" } }, + "node_modules/to-valid-identifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-valid-identifier/-/to-valid-identifier-1.0.0.tgz", + "integrity": "sha512-41wJyvKep3yT2tyPqX/4blcfybknGB4D+oETKLs7Q76UiPqRpUJK3hr1nxelyYO0PHKVzJwlu0aCeEAsGI6rpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/base62": "^1.0.0", + "reserved-identifiers": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -4053,7 +4258,6 @@ "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "pathe": "^2.0.3" } @@ -4092,7 +4296,6 @@ "integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -4191,7 +4394,6 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -4309,7 +4511,6 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "bin": { "workerd": "bin/workerd" }, diff --git a/package.json b/package.json index f89b791..6ee04ad 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "@cloudflare/workers-types": "^4.20251205.0", "eslint": "^9.39.1", "eslint-config-prettier": "^10.1.8", + "eslint-plugin-jsdoc": "^61.5.0", "prettier": "^3.7.4", "typescript": "^5.9.3", "vitest": "^3.1.4", diff --git a/src/config/index.js b/src/config/index.js index eb7382c..cc62407 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -20,12 +20,10 @@ import { PLATFORMS } from './platforms.js'; /** * Security-related configuration options for request validation and CORS. - * - * @typedef {Object} SecurityConfig + * @typedef {object} SecurityConfig * @property {string[]} ALLOWED_METHODS - List of allowed HTTP methods for incoming requests * @property {string[]} ALLOWED_ORIGINS - List of allowed CORS origins (use ['*'] for all origins) * @property {number} MAX_PATH_LENGTH - Maximum allowed URL path length in characters - * * @example * // Default security config * const security = { @@ -33,7 +31,6 @@ import { PLATFORMS } from './platforms.js'; * ALLOWED_ORIGINS: ['*'], * MAX_PATH_LENGTH: 2048 * }; - * * @example * // Custom security config with restricted origins * const security = { @@ -49,15 +46,13 @@ import { PLATFORMS } from './platforms.js'; * This configuration controls timeout behavior, retry logic, caching, security policies, * and platform URL mappings. All values can be overridden via environment variables * in Cloudflare Workers. - * - * @typedef {Object} ApplicationConfig + * @typedef {object} ApplicationConfig * @property {number} TIMEOUT_SECONDS - Request timeout in seconds (default: 30) * @property {number} MAX_RETRIES - Maximum number of retry attempts for failed requests (default: 3) * @property {number} RETRY_DELAY_MS - Delay between retry attempts in milliseconds (default: 1000) * @property {number} CACHE_DURATION - Cache duration in seconds for successful responses (default: 1800) * @property {SecurityConfig} SECURITY - Security-related configurations - * @property {Object.} PLATFORMS - Platform-specific base URL mappings - * + * @property {{ [key: string]: string }} PLATFORMS - Platform-specific base URL mappings * @example * // Default configuration * const config = { @@ -72,7 +67,6 @@ import { PLATFORMS } from './platforms.js'; * }, * PLATFORMS: { gh: 'https://github.com', ... } * }; - * * @example * // Configuration with environment overrides * const env = { @@ -99,16 +93,13 @@ import { PLATFORMS } from './platforms.js'; * - `ALLOWED_METHODS` - Comma-separated HTTP methods (default: 'GET,HEAD') * - `ALLOWED_ORIGINS` - Comma-separated CORS origins (default: '*') * - `MAX_PATH_LENGTH` - Override max path length (default: 2048) - * - * @param {Record} env - Environment variables from Cloudflare Workers env object + * @param {Record} env - Environment variables from Cloudflare Workers env object * @returns {ApplicationConfig} Complete application configuration with applied overrides - * * @example * // Create config with defaults (no environment variables) * const config = createConfig(); * console.log(config.TIMEOUT_SECONDS); // 30 * console.log(config.CACHE_DURATION); // 1800 - * * @example * // Create config with environment overrides * const env = { @@ -122,7 +113,6 @@ import { PLATFORMS } from './platforms.js'; * console.log(config.MAX_RETRIES); // 5 * console.log(config.CACHE_DURATION); // 3600 (1 hour) * console.log(config.SECURITY.ALLOWED_METHODS); // ['GET', 'HEAD', 'POST', 'PUT'] - * * @example * // Invalid environment values fallback to defaults * const env = { @@ -132,7 +122,6 @@ import { PLATFORMS } from './platforms.js'; * const config = createConfig(env); * console.log(config.TIMEOUT_SECONDS); // 30 (default) * console.log(config.MAX_RETRIES); // 3 (default) - * * @example * // Custom CORS origins * const env = { @@ -144,14 +133,16 @@ import { PLATFORMS } from './platforms.js'; */ export function createConfig(env = {}) { return { - TIMEOUT_SECONDS: parseInt(env.TIMEOUT_SECONDS, 10) || 30, - MAX_RETRIES: parseInt(env.MAX_RETRIES, 10) || 3, - RETRY_DELAY_MS: parseInt(env.RETRY_DELAY_MS, 10) || 1000, - CACHE_DURATION: parseInt(env.CACHE_DURATION, 10) || 1800, // 30 minutes + TIMEOUT_SECONDS: parseInt(String(env.TIMEOUT_SECONDS), 10) || 30, + MAX_RETRIES: parseInt(String(env.MAX_RETRIES), 10) || 3, + RETRY_DELAY_MS: parseInt(String(env.RETRY_DELAY_MS), 10) || 1000, + CACHE_DURATION: parseInt(String(env.CACHE_DURATION), 10) || 1800, // 30 minutes SECURITY: { - ALLOWED_METHODS: env.ALLOWED_METHODS ? env.ALLOWED_METHODS.split(',') : ['GET', 'HEAD'], - ALLOWED_ORIGINS: env.ALLOWED_ORIGINS ? env.ALLOWED_ORIGINS.split(',') : ['*'], - MAX_PATH_LENGTH: parseInt(env.MAX_PATH_LENGTH, 10) || 2048 + ALLOWED_METHODS: + typeof env.ALLOWED_METHODS === 'string' ? env.ALLOWED_METHODS.split(',') : ['GET', 'HEAD'], + ALLOWED_ORIGINS: + typeof env.ALLOWED_ORIGINS === 'string' ? env.ALLOWED_ORIGINS.split(',') : ['*'], + MAX_PATH_LENGTH: parseInt(String(env.MAX_PATH_LENGTH), 10) || 2048 }, PLATFORMS }; @@ -163,14 +154,11 @@ export function createConfig(env = {}) { * This is a pre-instantiated configuration object using default values with no * environment overrides. In production (Cloudflare Workers), you should use * `createConfig(env)` instead to allow runtime configuration. - * * @type {ApplicationConfig} - * * @example * // Import default config * import { CONFIG } from './config/index.js'; * console.log(CONFIG.TIMEOUT_SECONDS); // 30 - * * @example * // Check platform availability * if (CONFIG.PLATFORMS.npm) { diff --git a/src/config/platforms.js b/src/config/platforms.js index acafc57..dab6cd4 100644 --- a/src/config/platforms.js +++ b/src/config/platforms.js @@ -115,17 +115,13 @@ * - `cr-suse` - SUSE Registry * - `cr-opensuse` - openSUSE Registry * - `cr-gitpod` - Gitpod Registry - * - * @type {Object.} - * + * @type {{ [key: string]: string }} * @example * // Access GitHub base URL * const githubUrl = PLATFORMS.gh; // 'https://github.com' - * * @example * // Access OpenAI API base URL * const openaiUrl = PLATFORMS['ip-openai']; // 'https://api.openai.com' - * * @example * // Check if platform exists * if (PLATFORMS.npm) { @@ -248,41 +244,33 @@ export const SORTED_PLATFORMS = Object.keys(PLATFORMS).sort((a, b) => { * * The function handles special cases for platforms that require API path prefixes or * URL structure modifications to match their upstream API conventions. - * * @param {string} path - The original request path including platform prefix (e.g., '/gh/user/repo') * @param {string} platformKey - The platform key from PLATFORMS object (e.g., 'gh', 'crates', 'npm') * @returns {string} The transformed path ready for upstream request - * * @example * // Basic transformation - strips platform prefix * transformPath('/gh/torvalds/linux', 'gh') * // Returns: '/torvalds/linux' - * * @example * // crates.io API transformation - adds API prefix * transformPath('/crates/serde/1.0.0/download', 'crates') * // Returns: '/api/v1/crates/serde/1.0.0/download' - * * @example * // crates.io search endpoint * transformPath('/crates/?q=tokio', 'crates') * // Returns: '/api/v1/crates?q=tokio' - * * @example * // Jenkins update center transformation * transformPath('/jenkins/update-center.json', 'jenkins') * // Returns: '/current/update-center.json' - * * @example * // Homebrew API paths (pass-through) * transformPath('/homebrew/api/formula/git.json', 'homebrew-api') * // Returns: '/formula/git.json' - * * @example * // Unknown platform (no transformation) * transformPath('/unknown/path', 'nonexistent') * // Returns: '/unknown/path' - * * @example * // Multi-part platform key (hyphens converted to slashes) * transformPath('/ip/openai/v1/chat/completions', 'ip-openai') diff --git a/src/index.js b/src/index.js index 0008e73..bdbe9a1 100644 --- a/src/index.js +++ b/src/index.js @@ -24,9 +24,8 @@ import { isDockerRequest, validateRequest } from './utils/validation.js'; /** * Main request handler with comprehensive caching, retry logic, and security measures. - * * @param {Request} request - The incoming HTTP request - * @param {Object} env - Cloudflare Workers environment variables for runtime config overrides + * @param {object} env - Cloudflare Workers environment variables for runtime config overrides * @param {ExecutionContext} ctx - Cloudflare Workers execution context for background tasks * @returns {Promise} The HTTP response with appropriate headers and body */ @@ -130,8 +129,8 @@ async function handleRequest(request, env, ctx) { /** @type {Cache | null} */ // @ts-ignore - Cloudflare Workers cache API const cache = - typeof caches !== 'undefined' && /** @type {any} */ (caches).default - ? /** @type {any} */ (caches).default + typeof caches !== 'undefined' && /** @type {any} */ (caches).default // eslint-disable-line jsdoc/reject-any-type + ? /** @type {any} */ (caches).default // eslint-disable-line jsdoc/reject-any-type : null; if (cache && !isGit && !isGitLFS && !isDocker && !isAI) { @@ -602,8 +601,9 @@ async function handleRequest(request, env, ctx) { export default { /** + * Main Worker entry point. * @param {Request} request - * @param {Object} env + * @param {object} env * @param {ExecutionContext} ctx */ fetch(request, env, ctx) { diff --git a/src/protocols/ai.js b/src/protocols/ai.js index bc68053..6b91431 100644 --- a/src/protocols/ai.js +++ b/src/protocols/ai.js @@ -27,7 +27,6 @@ * - AI provider path prefix (/ip/{provider}/...) * - Common AI API endpoints (chat, completions, embeddings, etc.) * - AI-specific URL patterns with JSON POST requests - * * @param {Request} request - The incoming request object * @param {URL} url - Parsed URL object * @returns {boolean} True if this is an AI inference request @@ -74,7 +73,6 @@ export function isAIInferenceRequest(request, url) { * Configures headers for AI protocol requests. * * Sets Content-Type and User-Agent headers for AI inference requests. - * * @param {Headers} headers - The headers object to modify * @param {Request} request - The original request */ diff --git a/src/protocols/docker.js b/src/protocols/docker.js index 9beb0f3..e114bca 100644 --- a/src/protocols/docker.js +++ b/src/protocols/docker.js @@ -28,7 +28,6 @@ import { createErrorResponse } from '../utils/security.js'; * * Extracts authentication realm and service information from the Bearer * authentication challenge header returned by container registries. - * * @param {string} authenticateStr - The WWW-Authenticate header value * @returns {{realm: string, service: string}} Parsed authentication info with realm URL and service name * @throws {Error} If the header format is invalid or missing required fields @@ -53,7 +52,6 @@ export function parseAuthenticate(authenticateStr) { * * Requests a Bearer token from the registry's authentication service, * optionally including scope (repository permissions) and authorization credentials. - * * @param {{realm: string, service: string}} wwwAuthenticate - Authentication info from WWW-Authenticate header * @param {string} scope - The scope for the token (e.g., "repository:library/nginx:pull") * @param {string} authorization - Authorization header value (optional, for authenticated access) @@ -79,7 +77,6 @@ export async function fetchToken(wwwAuthenticate, scope, authorization) { * * Generates a Docker/OCI registry-compliant 401 response with a WWW-Authenticate * header that directs clients to the token authentication endpoint. - * * @param {URL} url - Request URL used to construct authentication realm * @returns {Response} Unauthorized response with WWW-Authenticate header */ @@ -96,7 +93,6 @@ export function responseUnauthorized(url) { * Handles the special /v2/auth endpoint for Docker authentication. * * Proxies generation of auth tokens by negotiating with the upstream registry. - * * @param {Request} request - The incoming request * @param {URL} url - The parsed URL * @param {import('../config/index.js').ApplicationConfig} config - App configuration diff --git a/src/protocols/git.js b/src/protocols/git.js index cb8e973..9b639d1 100644 --- a/src/protocols/git.js +++ b/src/protocols/git.js @@ -28,7 +28,6 @@ * - Git User-Agent headers * - Git service query parameters * - Git-specific Content-Type headers - * * @param {Request} request - The incoming request object * @param {URL} url - Parsed URL object * @returns {boolean} True if this is a Git operation @@ -72,7 +71,6 @@ export function isGitRequest(request, url) { * - LFS object storage paths (SHA-256 hash patterns) * - Git LFS Accept/Content-Type headers * - Git LFS User-Agent - * * @param {Request} request - The incoming request object * @param {URL} url - Parsed URL object * @returns {boolean} True if this is a Git LFS operation @@ -116,7 +114,6 @@ export function isGitLFSRequest(request, url) { * Configures headers for Git protocol requests. * * Sets User-Agent and Content-Type headers required by Git and Git LFS protocols. - * * @param {Headers} headers - The headers object to modify * @param {Request} request - The original request * @param {URL} url - The parsed URL diff --git a/src/utils/performance.js b/src/utils/performance.js index a505ef3..d5877ae 100644 --- a/src/utils/performance.js +++ b/src/utils/performance.js @@ -45,7 +45,6 @@ export class PerformanceMonitor { * * Records the elapsed time (in milliseconds) since the monitor was created. * If a mark with the same name already exists, logs a warning and overwrites it. - * * @param {string} name - The name of the timing mark (e.g., 'cache_hit', 'attempt_0', 'success') */ mark(name) { @@ -60,8 +59,7 @@ export class PerformanceMonitor { * * Converts the internal Map of timing marks to a JavaScript object suitable for * JSON serialization and inclusion in response headers. - * - * @returns {Object.} Object containing name-timestamp pairs in milliseconds + * @returns {{ [key: string]: number }} Object containing name-timestamp pairs in milliseconds */ getMetrics() { return Object.fromEntries(this.marks.entries()); @@ -76,7 +74,6 @@ export class PerformanceMonitor { * headers are included. * * **Note:** This header is only added to non-protocol responses (not Git/Docker/AI). - * * @param {Response} response - The original response object * @param {PerformanceMonitor} monitor - Performance monitor instance with collected metrics * @returns {Response} New response with added performance and security headers diff --git a/src/utils/security.js b/src/utils/security.js index e37535c..12493bc 100644 --- a/src/utils/security.js +++ b/src/utils/security.js @@ -30,7 +30,6 @@ * - Referrer-Policy (referrer information control) * - Content-Security-Policy (resource loading restrictions) * - Permissions-Policy (privacy-invasive feature restrictions) - * * @param {Headers} headers - Headers object to modify (mutates in place) * @returns {Headers} Modified headers object (same reference) */ @@ -49,7 +48,6 @@ export function addSecurityHeaders(headers) { * * Generates an HTTP error response with appropriate content type and security headers. * Can return either plain text or detailed JSON error format. - * * @param {string} message - Error message to display * @param {number} status - HTTP status code (e.g., 400, 404, 500) * @param {boolean} includeDetails - Whether to include detailed JSON error information diff --git a/src/utils/validation.js b/src/utils/validation.js index a2f623c..13745ab 100644 --- a/src/utils/validation.js +++ b/src/utils/validation.js @@ -33,7 +33,6 @@ import { isGitLFSRequest, isGitRequest } from '../protocols/git.js'; * - Registry API endpoints (/v2/...) * - Docker-specific User-Agent headers * - Docker/OCI manifest Accept headers - * * @param {Request} request - The incoming request object * @param {URL} url - Parsed URL object * @returns {boolean} True if this is a container registry operation @@ -85,7 +84,6 @@ export { isAIInferenceRequest, isGitLFSRequest, isGitRequest }; * Different protocols have different allowed methods: * - Regular requests: GET, HEAD (configurable via SECURITY.ALLOWED_METHODS) * - Git/LFS/Docker/AI: GET, HEAD, POST, PUT, PATCH - * * @param {Request} request - The incoming request object * @param {URL} url - Parsed URL object * @param {import('../config/index.js').ApplicationConfig} config - Configuration object diff --git a/test/helpers/index.js b/test/helpers/index.js index ab25a7f..a345427 100644 --- a/test/helpers/index.js +++ b/test/helpers/index.js @@ -18,9 +18,9 @@ export class PerformanceTestHelper { /** * Measure execution time of an async function - * @param {Function} fn - Async function to measure + * @param {() => Promise} fn - Async function to measure * @param {string} name - Measurement name - * @returns {Promise} Function result + * @returns {Promise} Function result */ async measure(fn, name = 'operation') { const start = performance.now(); diff --git a/test/helpers/mocks.js b/test/helpers/mocks.js index d07b4cf..21badfa 100644 --- a/test/helpers/mocks.js +++ b/test/helpers/mocks.js @@ -5,7 +5,7 @@ /** * Create a mock request with default options * @param {string} url - Request URL - * @param {Object} options - Request options + * @param {object} options - Request options * @returns {Request} Mock request object */ export function createMockRequest(url, options = {}) { @@ -23,7 +23,7 @@ export function createMockRequest(url, options = {}) { /** * Create a mock response with default options * @param {string} body - Response body - * @param {Object} options - Response options + * @param {object} options - Response options * @returns {Response} Mock response object */ export function createMockResponse(body = 'OK', options = {}) { @@ -80,7 +80,7 @@ export function createDockerRequest(url, options = {}) { /** * Mock fetch function for testing * @param {string} url - Request URL - * @param {Object} _options - Fetch options + * @param {object} _options - Fetch options * @returns {Promise} Mock response */ export function mockFetch(url, _options = {}) { @@ -101,7 +101,7 @@ export function mockFetch(url, _options = {}) { * Create a mock npm registry response * @param {string} packageName - Package name * @param {string} version - Package version - * @returns {Object} Mock npm registry response + * @returns {object} Mock npm registry response */ export function createMockNpmRegistryResponse(packageName, version = '1.0.0') { return {