Improve JSDoc types and add eslint-plugin-jsdoc

Standardized JSDoc type annotations across the codebase, replacing 'Object' with 'object' and refining function signatures for better type safety. Added and configured eslint-plugin-jsdoc for enhanced documentation linting, and updated ESLint settings to include adapters and new JSDoc rules. Updated dependencies to include eslint-plugin-jsdoc and related packages.
This commit is contained in:
xixu-me committed 2025-12-11 13:27:26 +08:00
1 parent bfec1d2d9e
commit 07f5f35972
18 files changed
+295 -102

No files matched your search

+2 -1
View File
@@ -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
+6 -9
View File
@@ -16,25 +16,22 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/* 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<unknown>) => void} [context.waitUntil] - Background task extension (Netlify)
* @returns {Promise<Response>} 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');
-2
View File
@@ -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<Response>} Standard Web API Response
*
* @example
* // Deno Deploy invokes automatically:
* // Deno.serve((request) => handler(request))
+8 -11
View File
@@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/* 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<unknown>) => void} context.waitUntil - Extend function execution for background tasks
* @param {() => Promise<Response>} context.next - Call next middleware in chain (not used here)
* @param {object} context.data - Shared data between functions
* @returns {Promise<Response>} 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');
+39 -1
View File
@@ -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',
+213 -12
View File
@@ -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"
},
+1
View File
@@ -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",
+13 -25
View File
@@ -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.<string, string>} 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<string, any>} env - Environment variables from Cloudflare Workers env object
* @param {Record<string, unknown>} 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) {
+1 -13
View File
@@ -115,17 +115,13 @@
* - `cr-suse` - SUSE Registry
* - `cr-opensuse` - openSUSE Registry
* - `cr-gitpod` - Gitpod Registry
*
* @type {Object.<string, string>}
*
* @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')
+5 -5
View File
@@ -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<Response>} 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) {
-2
View File
@@ -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
*/
-4
View File
@@ -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
-3
View File
@@ -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
+1 -4
View File
@@ -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.<string, number>} 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
-2
View File
@@ -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
-2
View File
@@ -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
+2 -2
View File
@@ -18,9 +18,9 @@ export class PerformanceTestHelper {
/**
* Measure execution time of an async function
* @param {Function} fn - Async function to measure
* @param {() => Promise<unknown>} fn - Async function to measure
* @param {string} name - Measurement name
* @returns {Promise<any>} Function result
* @returns {Promise<unknown>} Function result
*/
async measure(fn, name = 'operation') {
const start = performance.now();
+4 -4
View File
@@ -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<Response>} 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 {