@zimic/http
Version:
Next-gen TypeScript-first HTTP utilities
525 lines (518 loc) • 18.5 kB
JavaScript
'use strict';
// ../zimic-utils/dist/data.mjs
async function blobEquals(blob, otherBlob) {
if (blob.type !== otherBlob.type || blob.size !== otherBlob.size) {
return false;
}
const reader = blob.stream().getReader();
const otherReader = otherBlob.stream().getReader();
let buffer = new Uint8Array(0);
let otherBuffer = new Uint8Array(0);
try {
while (true) {
const bufferReadPromises = [];
if (buffer.length === 0) {
bufferReadPromises.push(
reader.read().then((result) => {
if (!result.done) {
buffer = result.value;
}
})
);
}
if (otherBuffer.length === 0) {
bufferReadPromises.push(
otherReader.read().then((result) => {
if (!result.done) {
otherBuffer = result.value;
}
})
);
}
await Promise.all(bufferReadPromises);
const haveStreamsEndedTogether = buffer.length === 0 && otherBuffer.length === 0;
if (haveStreamsEndedTogether) {
return true;
}
const hasOneStreamEndedBeforeTheOther = buffer.length === 0 && otherBuffer.length > 0 || buffer.length > 0 && otherBuffer.length === 0;
if (hasOneStreamEndedBeforeTheOther) {
return false;
}
const minimumByteLength = Math.min(buffer.length, otherBuffer.length);
for (let byteIndex = 0; byteIndex < minimumByteLength; byteIndex++) {
if (buffer[byteIndex] !== otherBuffer[byteIndex]) {
return false;
}
}
buffer = buffer.slice(minimumByteLength);
otherBuffer = otherBuffer.slice(minimumByteLength);
}
} finally {
reader.releaseLock();
otherReader.releaseLock();
}
}
var blobEquals_default = blobEquals;
async function fileEquals(file, otherFile) {
return file.name === otherFile.name && await blobEquals_default(file, otherFile);
}
var fileEquals_default = fileEquals;
// src/formData/HttpFormData.ts
var HttpFormData = class extends FormData {
_schema;
set(name, blobOrValue, fileName) {
if (fileName === void 0) {
super.set(name, blobOrValue);
} else {
super.set(name, blobOrValue, fileName);
}
}
append(name, blobOrValue, fileName) {
if (fileName === void 0) {
super.append(name, blobOrValue);
} else {
super.append(name, blobOrValue, fileName);
}
}
/** @see {@link https://zimic.dev/docs/http/api/http-form-data#formdataget `formData.get()` API reference} */
get(name) {
return super.get(name);
}
/** @see {@link https://zimic.dev/docs/http/api/http-form-data#formdatagetall `formData.getAll()` API reference} */
getAll(name) {
return super.getAll(name);
}
/** @see {@link https://zimic.dev/docs/http/api/http-form-data#formdatahas `formData.has()` API reference} */
has(name) {
return super.has(name);
}
/** @see {@link https://zimic.dev/docs/http/api/http-form-data#formdatadelete `formData.delete()` API reference} */
delete(name) {
super.delete(name);
}
/** @see {@link https://zimic.dev/docs/http/api/http-form-data#formdataforeach `formData.forEach()` API reference} */
forEach(callback, thisArg) {
super.forEach(callback, thisArg);
}
/** @see {@link https://zimic.dev/docs/http/api/http-form-data#formdatakeys `formData.keys()` API reference} */
keys() {
return super.keys();
}
/** @see {@link https://zimic.dev/docs/http/api/http-form-data#formdatavalues `formData.values()` API reference} */
values() {
return super.values();
}
/** @see {@link https://zimic.dev/docs/http/api/http-form-data#formdataentries `formData.entries()` API reference} */
entries() {
return super.entries();
}
[Symbol.iterator]() {
return super[Symbol.iterator]();
}
/** @see {@link https://zimic.dev/docs/http/api/http-form-data#formdataequals `formData.equals()` API reference} */
async equals(otherData) {
if (!await this.contains(otherData)) {
return false;
}
for (const fieldName of this.keys()) {
if (!super.has.call(otherData, fieldName)) {
return false;
}
}
return true;
}
/** @see {@link https://zimic.dev/docs/http/api/http-form-data#formdatacontains `formData.contains()` API reference} */
async contains(otherData) {
for (const [otherFieldName, otherFieldValue] of otherData.entries()) {
const fieldValues = super.getAll.call(this, otherFieldName);
const haveSameNumberOfFieldValues = fieldValues.length === super.getAll.call(otherData, otherFieldName).length;
if (!haveSameNumberOfFieldValues) {
return false;
}
let fieldValueExists = false;
for (const fieldValue of fieldValues) {
if (fieldValue === otherFieldValue || fieldValue instanceof Blob && otherFieldValue instanceof Blob && await fileEquals_default(fieldValue, otherFieldValue)) {
fieldValueExists = true;
break;
}
}
if (!fieldValueExists) {
return false;
}
}
return true;
}
/** @see {@link https://zimic.dev/docs/http/api/http-form-data#formdataassign `formData.assign()` API reference} */
assign(...otherDataArray) {
for (const otherData of otherDataArray) {
if (this === otherData) {
continue;
}
for (const fieldName of otherData.keys()) {
super.delete(fieldName);
}
for (const [fieldName, fieldValue] of otherData.entries()) {
super.append(fieldName, fieldValue);
}
}
}
/** @see {@link https://zimic.dev/docs/http/api/http-form-data#formdatatoobject `formData.toObject()` API reference} */
toObject() {
const object = {};
for (const [fieldName, fieldValue] of this.entries()) {
if (fieldName in object) {
const existingFieldValue = object[fieldName];
if (Array.isArray(existingFieldValue)) {
existingFieldValue.push(fieldValue);
} else {
object[fieldName] = [existingFieldValue, fieldValue];
}
} else {
object[fieldName] = fieldValue;
}
}
return object;
}
};
var HttpFormData_default = HttpFormData;
// src/headers/HttpHeaders.ts
function pickPrimitiveProperties(schema) {
return Object.entries(schema).reduce((accumulated, [key, value]) => {
if (value !== void 0) {
accumulated[key] = String(value);
}
return accumulated;
}, {});
}
var HttpHeaders = class extends Headers {
_schema;
constructor(init) {
if (init instanceof Headers || Array.isArray(init) || !init) {
super(init);
} else {
super(pickPrimitiveProperties(init));
}
}
/** @see {@link https://zimic.dev/docs/http/api/http-headers#headersset `headers.set()` API reference} */
set(name, value) {
super.set(name, value);
}
/** @see {@link https://zimic.dev/docs/http/api/http-headers#headersappend `headers.append()` API reference} */
append(name, value) {
super.append(name, value);
}
/** @see {@link https://zimic.dev/docs/http/api/http-headers#headersget `headers.get()` API reference} */
get(name) {
return super.get(name);
}
/** @see {@link https://zimic.dev/docs/http/api/http-headers#headersgetsetcookie `headers.getSetCookie()` API reference} */
getSetCookie() {
return super.getSetCookie();
}
/** @see {@link https://zimic.dev/docs/http/api/http-headers#headershas `headers.has()` API reference} */
has(name) {
return super.has(name);
}
/** @see {@link https://zimic.dev/docs/http/api/http-headers#headersdelete `headers.delete()` API reference} */
delete(name) {
super.delete(name);
}
/** @see {@link https://zimic.dev/docs/http/api/http-headers#headersforeach `headers.forEach()` API reference} */
forEach(callback, thisArg) {
super.forEach(callback, thisArg);
}
/** @see {@link https://zimic.dev/docs/http/api/http-headers#headerskeys `headers.keys()` API reference} */
keys() {
return super.keys();
}
/** @see {@link https://zimic.dev/docs/http/api/http-headers#headersvalues `headers.values()` API reference} */
values() {
return super.values();
}
/** @see {@link https://zimic.dev/docs/http/api/http-headers#headersentries `headers.entries()` API reference} */
entries() {
return super.entries();
}
[Symbol.iterator]() {
return super[Symbol.iterator]();
}
/** @see {@link https://zimic.dev/docs/http/api/http-headers#headersequals `headers.equals()` API reference} */
equals(otherHeaders) {
if (!this.contains(otherHeaders)) {
return false;
}
for (const headerName of this.keys()) {
if (!super.has.call(otherHeaders, headerName)) {
return false;
}
}
return true;
}
/** @see {@link https://zimic.dev/docs/http/api/http-headers#headerscontains `headers.contains()` API reference} */
contains(otherHeaders) {
for (const [headerName, otherHeaderValue] of otherHeaders.entries()) {
const headerValue = super.get.call(this, headerName);
if (headerValue === null) {
return false;
}
const headerValueItems = this.splitHeaderValues(headerValue);
const otherHeaderValueItems = this.splitHeaderValues(otherHeaderValue);
const haveSameNumberOfHeaderValues = headerValueItems.length === otherHeaderValueItems.length;
if (!haveSameNumberOfHeaderValues) {
return false;
}
for (const otherValueItem of otherHeaderValueItems) {
if (!headerValueItems.includes(otherValueItem)) {
return false;
}
}
}
return true;
}
/** @see {@link https://zimic.dev/docs/http/api/http-headers#headersassign `headers.assign()` API reference} */
assign(...otherHeadersArray) {
for (const otherHeaders of otherHeadersArray) {
if (this === otherHeaders) {
continue;
}
for (const headerName of otherHeaders.keys()) {
super.delete(headerName);
}
for (const [headerName, headerValue] of otherHeaders.entries()) {
super.append(headerName, headerValue);
}
}
}
/** @see {@link https://zimic.dev/docs/http/api/http-headers#headerstoobject `headers.toObject()` API reference} */
toObject() {
const object = {};
for (const [headerName, headerValue] of this.entries()) {
object[headerName] = headerValue;
}
return object;
}
splitHeaderValues(value) {
return value.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
}
};
var HttpHeaders_default = HttpHeaders;
// src/searchParams/HttpSearchParams.ts
function pickPrimitiveProperties2(schema) {
const schemaWithPrimitiveProperties = Object.entries(schema).reduce(
(accumulated, [key, value]) => {
if (value !== void 0 && !Array.isArray(value)) {
accumulated[key] = String(value);
}
return accumulated;
},
{}
);
return schemaWithPrimitiveProperties;
}
var HttpSearchParams = class extends URLSearchParams {
_schema;
constructor(init) {
if (init instanceof URLSearchParams || Array.isArray(init) || typeof init === "string" || !init) {
super(init);
} else {
super(pickPrimitiveProperties2(init));
this.populateInitArrayProperties(init);
}
}
populateInitArrayProperties(init) {
for (const [paramName, paramValues] of Object.entries(init)) {
if (Array.isArray(paramValues)) {
for (const paramValue of paramValues) {
super.append(paramName, String(paramValue));
}
}
}
}
/** @see {@link https://zimic.dev/docs/http/api/http-search-params#searchparamsset `searchParams.set()` API reference} */
set(name, value) {
super.set(name, value);
}
/** @see {@link https://zimic.dev/docs/http/api/http-search-params#searchparamsappend `searchParams.append()` API reference} */
append(name, value) {
super.append(name, value);
}
/** @see {@link https://zimic.dev/docs/http/api/http-search-params#searchparamsget `searchParams.get()` API reference} */
get(name) {
return super.get(name);
}
/** @see {@link https://zimic.dev/docs/http/api/http-search-params#searchparamsgetall `searchParams.getAll()` API reference} */
getAll(name) {
return super.getAll(name);
}
/** @see {@link https://zimic.dev/docs/http/api/http-search-params#searchparamshas `searchParams.has()` API reference} */
has(name, value) {
return super.has(name, value);
}
/** @see {@link https://zimic.dev/docs/http/api/http-search-params#searchparamsdelete `searchParams.delete()` API reference} */
delete(name, value) {
super.delete(name, value);
}
/** @see {@link https://zimic.dev/docs/http/api/http-search-params#searchparamsforeach `searchParams.forEach()` API reference} */
forEach(callback, thisArg) {
super.forEach(callback, thisArg);
}
/** @see {@link https://zimic.dev/docs/http/api/http-search-params#searchparamskeys `searchParams.keys()` API reference} */
keys() {
return super.keys();
}
/** @see {@link https://zimic.dev/docs/http/api/http-search-params#searchparamsvalues `searchParams.values()` API reference} */
values() {
return super.values();
}
/** @see {@link https://zimic.dev/docs/http/api/http-search-params#searchparamsentries `searchParams.entries()` API reference} */
entries() {
return super.entries();
}
[Symbol.iterator]() {
return super[Symbol.iterator]();
}
/** @see {@link https://zimic.dev/docs/http/api/http-search-params#searchparamsequals `searchParams.equals()` API reference} */
equals(otherParams) {
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(otherParams) {
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(...otherParamsArray) {
for (const otherParams of otherParamsArray) {
if (this === otherParams) {
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 = {};
for (const [paramName, paramValue] of this.entries()) {
if (paramName in object) {
const existingParamValue = object[paramName];
if (Array.isArray(existingParamValue)) {
existingParamValue.push(paramValue);
} else {
object[paramName] = [existingParamValue, paramValue];
}
} else {
object[paramName] = paramValue;
}
}
return object;
}
};
var HttpSearchParams_default = HttpSearchParams;
// src/types/schema.ts
var HTTP_METHODS = Object.freeze(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
// src/utils/bodies.ts
var InvalidFormDataError = class extends SyntaxError {
constructor(value) {
super(`Failed to parse value as form data: ${value}`);
this.name = "InvalidFormDataError";
}
};
var InvalidJSONError = class extends SyntaxError {
constructor(value) {
super(`Failed to parse value as JSON: ${value}`);
this.name = "InvalidJSONError";
}
};
async function parseHttpBodyAsText(resource) {
const bodyAsText = await resource.text();
return bodyAsText || null;
}
async function parseHttpBodyAsBlob(resource) {
const bodyAsBlob = await resource.blob();
return bodyAsBlob;
}
async function parseHttpBodyAsFormData(resource) {
const resourceClone = resource.clone();
try {
const bodyAsRawFormData = await resource.formData();
const bodyAsFormData = new HttpFormData_default();
for (const [key, value] of bodyAsRawFormData) {
bodyAsFormData.append(key, value);
}
return bodyAsFormData;
} catch {
const bodyAsText = await resourceClone.text();
if (!bodyAsText.trim()) {
return null;
}
throw new InvalidFormDataError(bodyAsText);
}
}
async function parseHttpBodyAsSearchParams(resource) {
const bodyAsText = await resource.text();
if (!bodyAsText.trim()) {
return null;
}
const bodyAsSearchParams = new HttpSearchParams_default(bodyAsText);
return bodyAsSearchParams;
}
async function parseHttpBodyAsJSON(resource) {
const bodyAsText = await resource.text();
if (!bodyAsText.trim()) {
return null;
}
try {
const bodyAsJSON = JSON.parse(bodyAsText);
return bodyAsJSON;
} catch {
throw new InvalidJSONError(bodyAsText);
}
}
async function parseHttpBody(resource) {
const contentType = resource.headers.get("content-type");
if (contentType?.startsWith("application/json")) {
return parseHttpBodyAsJSON(resource);
}
if (contentType?.startsWith("multipart/form-data")) {
return parseHttpBodyAsFormData(resource);
}
if (contentType?.startsWith("application/x-www-form-urlencoded")) {
return parseHttpBodyAsSearchParams(resource);
}
if (contentType?.startsWith("text/") || contentType?.startsWith("application/xml")) {
return parseHttpBodyAsText(resource);
}
if (contentType?.startsWith("application/") || contentType?.startsWith("image/") || contentType?.startsWith("audio/") || contentType?.startsWith("font/") || contentType?.startsWith("video/") || contentType?.startsWith("multipart/")) {
return parseHttpBodyAsBlob(resource);
}
const resourceClone = resource.clone();
try {
return await parseHttpBodyAsJSON(resource);
} catch {
return parseHttpBodyAsBlob(resourceClone);
}
}
exports.HTTP_METHODS = HTTP_METHODS;
exports.HttpFormData = HttpFormData_default;
exports.HttpHeaders = HttpHeaders_default;
exports.HttpSearchParams = HttpSearchParams_default;
exports.InvalidFormDataError = InvalidFormDataError;
exports.InvalidJSONError = InvalidJSONError;
exports.parseHttpBody = parseHttpBody;
//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map