@zimic/http
Version:
Next-gen TypeScript-first HTTP utilities
205 lines (172 loc) • 7.65 kB
text/typescript
import { Replace, ArrayItemIfArray } from '@zimic/utils/types';
import {
HttpSearchParamsSchema,
HttpSearchParamsInit,
HttpSearchParamsSchemaName,
HttpSearchParamsSerialized,
} from './types';
function pickPrimitiveProperties<Schema extends HttpSearchParamsSchema.Loose>(schema: Schema) {
const schemaWithPrimitiveProperties = Object.entries(schema).reduce<Record<string, string>>(
(accumulated, [key, value]) => {
if (value !== undefined && !Array.isArray(value)) {
accumulated[key] = String(value);
}
return accumulated;
},
{},
);
return schemaWithPrimitiveProperties;
}
/** @see {@link https://zimic.dev/docs/http/api/http-search-params `HttpSearchParams` API reference} */
class HttpSearchParams<
LooseSchema extends HttpSearchParamsSchema.Loose = HttpSearchParamsSchema.Loose,
> extends URLSearchParams {
readonly _schema!: HttpSearchParamsSerialized<LooseSchema>;
constructor(init?: HttpSearchParamsInit<LooseSchema>) {
if (init instanceof URLSearchParams || Array.isArray(init) || typeof init === 'string' || !init) {
super(init);
} else {
super(pickPrimitiveProperties(init));
this.populateInitArrayProperties(init);
}
}
private populateInitArrayProperties(init: LooseSchema) {
for (const [paramName, paramValues] of Object.entries(init)) {
if (Array.isArray(paramValues)) {
for (const paramValue of paramValues as unknown[]) {
super.append(paramName, String(paramValue));
}
}
}
}
/** @see {@link https://zimic.dev/docs/http/api/http-search-params#searchparamsset `searchParams.set()` API reference} */
set<Name extends HttpSearchParamsSchemaName<this['_schema']>>(
name: Name,
value: ArrayItemIfArray<NonNullable<LooseSchema[Name]>>,
): void {
super.set(name, value);
}
/** @see {@link https://zimic.dev/docs/http/api/http-search-params#searchparamsappend `searchParams.append()` API reference} */
append<Name extends HttpSearchParamsSchemaName<this['_schema']>>(
name: Name,
value: ArrayItemIfArray<NonNullable<LooseSchema[Name]>>,
): void {
super.append(name, value);
}
/** @see {@link https://zimic.dev/docs/http/api/http-search-params#searchparamsget `searchParams.get()` API reference} */
get<Name extends HttpSearchParamsSchemaName.NonArray<this['_schema']>>(
name: Name,
): Replace<ArrayItemIfArray<this['_schema'][Name]>, undefined, null> {
return super.get(name) as never;
}
/** @see {@link https://zimic.dev/docs/http/api/http-search-params#searchparamsgetall `searchParams.getAll()` API reference} */
getAll<Name extends HttpSearchParamsSchemaName.Array<this['_schema']>>(
name: Name,
): ArrayItemIfArray<NonNullable<this['_schema'][Name]>>[] {
return super.getAll(name) as never;
}
/** @see {@link https://zimic.dev/docs/http/api/http-search-params#searchparamshas `searchParams.has()` API reference} */
has<Name extends HttpSearchParamsSchemaName<this['_schema']>>(
name: Name,
value?: ArrayItemIfArray<NonNullable<LooseSchema[Name]>>,
): boolean {
return super.has(name, value);
}
/** @see {@link https://zimic.dev/docs/http/api/http-search-params#searchparamsdelete `searchParams.delete()` API reference} */
delete<Name extends HttpSearchParamsSchemaName<this['_schema']>>(
name: Name,
value?: ArrayItemIfArray<NonNullable<LooseSchema[Name]>>,
): void {
super.delete(name, value);
}
/** @see {@link https://zimic.dev/docs/http/api/http-search-params#searchparamsforeach `searchParams.forEach()` API reference} */
forEach<This extends HttpSearchParams<this['_schema']>>(
callback: <Key extends HttpSearchParamsSchemaName<this['_schema']>>(
value: ArrayItemIfArray<NonNullable<this['_schema'][Key]>>,
key: Key,
searchParams: HttpSearchParams<this['_schema']>,
) => void,
thisArg?: This,
): void {
super.forEach(callback as (value: string, key: string, parent: URLSearchParams) => void, thisArg);
}
/** @see {@link https://zimic.dev/docs/http/api/http-search-params#searchparamskeys `searchParams.keys()` API reference} */
keys(): URLSearchParamsIterator<HttpSearchParamsSchemaName<this['_schema']>> {
return super.keys() as never;
}
/** @see {@link https://zimic.dev/docs/http/api/http-search-params#searchparamsvalues `searchParams.values()` API reference} */
values(): URLSearchParamsIterator<
ArrayItemIfArray<NonNullable<this['_schema'][HttpSearchParamsSchemaName<this['_schema']>]>>
> {
return super.values() as never;
}
/** @see {@link https://zimic.dev/docs/http/api/http-search-params#searchparamsentries `searchParams.entries()` API reference} */
entries(): URLSearchParamsIterator<
[
HttpSearchParamsSchemaName<this['_schema']>,
ArrayItemIfArray<NonNullable<this['_schema'][HttpSearchParamsSchemaName<this['_schema']>]>>,
]
> {
return super.entries() as never;
}
[Symbol.iterator](): URLSearchParamsIterator<
[
HttpSearchParamsSchemaName<this['_schema']>,
ArrayItemIfArray<NonNullable<this['_schema'][HttpSearchParamsSchemaName<this['_schema']>]>>,
]
> {
return super[Symbol.iterator]() as never;
}
/** @see {@link https://zimic.dev/docs/http/api/http-search-params#searchparamsequals `searchParams.equals()` API reference} */
equals<OtherSchema extends LooseSchema>(otherParams: HttpSearchParams<OtherSchema>): boolean {
return this.contains(otherParams) && this.size === otherParams.size;
}
/** @see {@link https://zimic.dev/docs/http/api/http-search-params#searchparamscontains `searchParams.contains()` API reference} */
contains<OtherSchema extends LooseSchema>(otherParams: HttpSearchParams<OtherSchema>): boolean {
for (const [paramName, otherParamValue] of otherParams.entries()) {
const paramValues = super.getAll.call(this, paramName);
const haveSameNumberOfParamValues = paramValues.length === super.getAll.call(otherParams, paramName).length;
if (!haveSameNumberOfParamValues) {
return false;
}
const paramValueExists = paramValues.includes(otherParamValue);
if (!paramValueExists) {
return false;
}
}
return true;
}
/** @see {@link https://zimic.dev/docs/http/api/http-search-params#searchparamsassign `searchParams.assign()` API reference} */
assign<OtherSchema extends LooseSchema>(...otherParamsArray: HttpSearchParams<OtherSchema>[]) {
for (const otherParams of otherParamsArray) {
if (this === (otherParams as unknown)) {
continue;
}
for (const paramName of otherParams.keys()) {
super.delete(paramName);
}
for (const [paramName, paramValue] of otherParams.entries()) {
super.append(paramName, paramValue);
}
}
}
/** @see {@link https://zimic.dev/docs/http/api/http-search-params#searchparamstoobject `searchParams.toObject()` API reference} */
toObject() {
const object = {} as this['_schema'];
type SchemaValue = this['_schema'][HttpSearchParamsSchemaName<this['_schema']>];
for (const [paramName, paramValue] of this.entries()) {
if (paramName in object) {
const existingParamValue = object[paramName] as SchemaValue[];
if (Array.isArray<SchemaValue>(existingParamValue)) {
existingParamValue.push(paramValue as SchemaValue);
} else {
object[paramName] = [existingParamValue, paramValue] as SchemaValue;
}
} else {
object[paramName] = paramValue as SchemaValue;
}
}
return object;
}
}
export default HttpSearchParams;