From 74c382ff096e242af837eaf3cc77cd12cae4c355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20Sj=C3=B6green?= Date: Wed, 18 Dec 2024 11:04:41 +0100 Subject: [PATCH] feat: Implement typed `URLSearchParams` --- .github/workflows/checks.yml | 39 +--- .github/workflows/publish.yml | 8 +- main.ts | 2 +- scripts/npm.ts | 4 +- types/headers.ts | 20 +- types/json.ts | 17 ++ types/url_search_params.ts | 202 ++++++++++++++++++ ...hparams.ts => url_search_params_string.ts} | 0 8 files changed, 254 insertions(+), 38 deletions(-) create mode 100644 types/url_search_params.ts rename types/{urlsearchparams.ts => url_search_params_string.ts} (100%) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 58fafa8..90499c0 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -10,30 +10,16 @@ jobs: fmt: runs-on: ubuntu-latest steps: - - name: Checkout sources - uses: actions/checkout@v2 - - - name: Setup latest deno version - uses: denoland/setup-deno@v1 - with: - deno-version: v1.x - - - name: Run deno fmt - run: deno fmt --check + - uses: actions/checkout@v4 + - uses: denoland/setup-deno@v2 + - run: deno fmt --check lint: runs-on: ubuntu-latest steps: - - name: Checkout sources - uses: actions/checkout@v2 - - - name: Setup latest deno version - uses: denoland/setup-deno@v1 - with: - deno-version: v1.x - - - name: Run deno lint - run: deno lint + - uses: actions/checkout@v4 + - uses: denoland/setup-deno@v2 + - run: deno lint check: runs-on: ubuntu-latest @@ -52,13 +38,6 @@ jobs: test: runs-on: ubuntu-latest steps: - - name: Checkout sources - uses: actions/checkout@v2 - - - name: Setup latest deno version - uses: denoland/setup-deno@v1 - with: - deno-version: v1.x - - - name: Run deno test - run: deno test -A + - uses: actions/checkout@v4 + - uses: denoland/setup-deno@v2 + - run: deno test -A diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 797f1ff..a8cfee9 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -20,11 +20,11 @@ jobs: with: node-version: "20.x" registry-url: "https://registry.npmjs.org" - - uses: denoland/setup-deno@v1 + - uses: denoland/setup-deno@v2 - run: | - echo "DENO_VERSION=$(cat deno.json | jq \".version\")" >> $GITHUB_ENV - echo "NPM_VERSION=$(npm info @denosaurs/typefetch --json | jq \".['dist-tags'].latest\")" >> $GITHUB_ENV - echo "JSR_VERSION=$(curl -s https://jsr.io/@denosaurs/typefetch/meta.json | jq \".latest\")" >> $GITHUB_ENV + echo "DENO_VERSION=$(cat deno.json | jq \".version\")" >> $GITHUB_ENV + echo "NPM_VERSION=$(npm info @denosaurs/typefetch --json | jq \".['dist-tags'].latest\")" >> $GITHUB_ENV + echo "JSR_VERSION=$(curl -s https://jsr.io/@denosaurs/typefetch/meta.json | jq \".latest\")" >> $GITHUB_ENV - run: deno publish if: ${{ env.DENO_VERSION != env.JSR_VERSION }} - run: deno run -A scripts/npm.ts diff --git a/main.ts b/main.ts index 9236587..d9b4e27 100644 --- a/main.ts +++ b/main.ts @@ -136,7 +136,7 @@ source.addImportDeclaration({ if (options.experimentalURLSearchParams) { source.addImportDeclaration({ isTypeOnly: true, - moduleSpecifier: `${args["import"]}/types/urlsearchparams${ + moduleSpecifier: `${args["import"]}/types/url_search_params_string${ URL.canParse(args["import"]) ? ".ts" : "" }`, namedImports: ["URLSearchParamsString"], diff --git a/scripts/npm.ts b/scripts/npm.ts index 6264a90..6504837 100644 --- a/scripts/npm.ts +++ b/scripts/npm.ts @@ -24,8 +24,8 @@ await build({ }, { kind: "export", - name: "./types/urlsearchparams", - path: "./types/urlsearchparams.ts", + name: "./types/url_search_params_string", + path: "./types/url_search_params_string.ts", }, ], filterDiagnostic: (diagnostic) => { diff --git a/types/headers.ts b/types/headers.ts index d03b9cb..2b139ee 100644 --- a/types/headers.ts +++ b/types/headers.ts @@ -1,3 +1,20 @@ +/* + * Certain comments and type definitions are based off work from the official + * TypeScript project, for those the following attribution is applicable: + * + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at http://www.apache.org/licenses/LICENSE-2.0 + * + * THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED + * WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, + * MERCHANTABLITY OR NON-INFRINGEMENT. + * + * See the Apache Version 2.0 License for specific language governing permissions + * and limitations under the License. + */ // deno-lint-ignore-file no-var import type { OptionalKeys, RequiredKeys } from "./utils.ts"; @@ -46,7 +63,8 @@ declare interface Headers { */ set(name: K, value: NonNullable): void; - /** Returns an array containing the values of all `Set-Cookie` headers + /** + * Returns an array containing the values of all `Set-Cookie` headers * associated with a response. */ getSetCookie(): string[]; diff --git a/types/json.ts b/types/json.ts index 8ad3aa7..c4b7229 100644 --- a/types/json.ts +++ b/types/json.ts @@ -1,3 +1,20 @@ +/* + * Certain comments and type definitions are based off work from the official + * TypeScript project, for those the following attribution is applicable: + * + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at http://www.apache.org/licenses/LICENSE-2.0 + * + * THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED + * WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, + * MERCHANTABLITY OR NON-INFRINGEMENT. + * + * See the Apache Version 2.0 License for specific language governing permissions + * and limitations under the License. + */ // deno-lint-ignore-file no-explicit-any import type { Brand } from "./brand.ts"; diff --git a/types/url_search_params.ts b/types/url_search_params.ts new file mode 100644 index 0000000..afd446d --- /dev/null +++ b/types/url_search_params.ts @@ -0,0 +1,202 @@ +/* + * Certain comments and type definitions are based off work from the official + * TypeScript project, for those the following attribution is applicable: + * + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at http://www.apache.org/licenses/LICENSE-2.0 + * + * THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED + * WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, + * MERCHANTABLITY OR NON-INFRINGEMENT. + * + * See the Apache Version 2.0 License for specific language governing permissions + * and limitations under the License. + */ +// deno-lint-ignore-file no-var + +import type { Brand } from "./brand.ts"; +import type { OptionalKeys, RequiredKeys } from "./utils.ts"; + +type URLSearchParamsRecord = Record; + +/** + * A {@link Brand branded} string containing the URLSearchParams stringified representation of {@link T}. + */ +export type URLSearchParamsString = Brand< + string, + T +>; + +// TODO: Add support for the iterable variant of the init parameter +export type URLSearchParamsInit = + | T + | URLSearchParamsString; + +declare interface URLSearchParams< + T extends URLSearchParamsRecord = URLSearchParamsRecord, +> { + /** + * Appends a specified key/value pair as a new search parameter. + * + * ```ts + * let searchParams = new URLSearchParams(); + * searchParams.append('name', 'first'); + * searchParams.append('name', 'second'); + * ``` + */ + append(name: K, value: T[K]): void; + + /** + * Deletes search parameters that match a name, and optional value, + * from the list of all search parameters. + * + * ```ts + * let searchParams = new URLSearchParams([['name', 'value']]); + * searchParams.delete('name'); + * searchParams.delete('name', 'value'); + * ``` + */ + delete>(name: K, value?: T[K]): void; + + /** + * Returns all the values associated with a given search parameter + * as an array. + * + * ```ts + * searchParams.getAll('name'); + * ``` + */ + get>(name: K): [T[K]]; + get>(name: K): [] | [T[K]]; + + /** + * Returns the first value associated to the given search parameter. + * + * ```ts + * searchParams.get('name'); + * ``` + */ + get(name: K): T[K]; + + /** + * Returns a boolean value indicating if a given parameter, + * or parameter and value pair, exists. + * + * ```ts + * searchParams.has('name'); + * searchParams.has('name', 'value'); + * ``` + */ + has>(name: K): true; + has>(name: K): boolean; + + /** + * Sets the value associated with a given search parameter to the + * given value. If there were several matching values, this method + * deletes the others. If the search parameter doesn't exist, this + * method creates it. + * + * ```ts + * searchParams.set('name', 'value'); + * ``` + */ + set(name: K, value: NonNullable): void; + + /** + * Calls a function for each element contained in this object in + * place and return undefined. Optionally accepts an object to use + * as this when executing callback as second argument. + * + * ```ts + * const params = new URLSearchParams([["a", "b"], ["c", "d"]]); + * params.forEach((value, key, parent) => { + * console.log(value, key, parent); + * }); + * ``` + */ + forEach( + callbackfn: (value: T[keyof T], key: keyof T, parent: this) => void, + // deno-lint-ignore no-explicit-any + thisArg?: any, + ): void; + + /** + * Returns an iterator allowing to go through all keys contained + * in this object. + * + * ```ts + * const params = new URLSearchParams([["a", "b"], ["c", "d"]]); + * for (const key of params.keys()) { + * console.log(key); + * } + * ``` + */ + keys(): IterableIterator; + + /** + * Returns an iterator allowing to go through all values contained + * in this object. + * + * ```ts + * const params = new URLSearchParams([["a", "b"], ["c", "d"]]); + * for (const value of params.values()) { + * console.log(value); + * } + * ``` + */ + values(): IterableIterator; + + /** + * Returns an iterator allowing to go through all key/value + * pairs contained in this object. + * + * ```ts + * const params = new URLSearchParams([["a", "b"], ["c", "d"]]); + * for (const [key, value] of params.entries()) { + * console.log(key, value); + * } + * ``` + */ + entries(): IterableIterator<[keyof T, T[keyof T]]>; + + /** + * Returns an iterator allowing to go through all key/value + * pairs contained in this object. + * + * ```ts + * const params = new URLSearchParams([["a", "b"], ["c", "d"]]); + * for (const [key, value] of params) { + * console.log(key, value); + * } + * ``` + */ + [Symbol.iterator](): IterableIterator<[keyof T, T[keyof T]]>; + + /** + * Returns a query string suitable for use in a URL. + * + * ```ts + * searchParams.toString(); + * ``` + */ + toString(): URLSearchParamsString; + + /** + * Contains the number of search parameters + * + * ```ts + * searchParams.size + * ``` + */ + size: number; +} + +declare var URLSearchParams: { + readonly prototype: URLSearchParams; + new ( + init?: URLSearchParamsInit, + ): URLSearchParams; +}; diff --git a/types/urlsearchparams.ts b/types/url_search_params_string.ts similarity index 100% rename from types/urlsearchparams.ts rename to types/url_search_params_string.ts