Skip to content

Commit

Permalink
copy as part of immutability
Browse files Browse the repository at this point in the history
  • Loading branch information
BBB committed Nov 1, 2023
1 parent 84082d2 commit 9f568f6
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 68 deletions.
95 changes: 29 additions & 66 deletions packages/http-client/src/ImmutableRequest.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,42 @@
import { ImmutableURL } from "~/src/ImmutableURL";
import { Method } from "~/src/Method";
import { ImmutableHeaders } from "~/src/ImmutableHeaders";
import { HttpAbortSignal } from "~/src/HttpAbortSignal";

export type Credentials = "omit" | "include" | "same-origin";

export class ImmutableRequest {
readonly #headers: ImmutableHeaders;
readonly #url: ImmutableURL;
readonly #method: Method;
readonly #abortSignal: AbortSignal | undefined;
readonly #abortSignal: HttpAbortSignal | undefined;
readonly #credentials: Credentials | undefined;
readonly #body: unknown;

copy(
request: Partial<
Pick<
ImmutableRequest,
"method" | "url" | "body" | "headers" | "abortSignal" | "credentials"
>
>,
) {
return new ImmutableRequest(
request.method ?? this.method,
request.url?.copy() ?? this.url.copy(),
request.body ?? this.#body,
request.headers ?? this.headers,
request.abortSignal ?? this.abortSignal,
request.credentials ?? this.credentials,
);
}

protected constructor(
method: Method,
url: ImmutableURL,
body?: unknown | undefined,
headers?: Record<string, string> | ImmutableHeaders,
abortSignal?: AbortSignal | undefined,
abortSignal?: HttpAbortSignal | undefined,
credentials?: Credentials | undefined,
) {
this.#headers = new ImmutableHeaders(headers);
Expand All @@ -31,7 +50,7 @@ export class ImmutableRequest {
static get(
url: ImmutableURL,
headers?: Record<string, string> | ImmutableHeaders,
abortSignal?: AbortSignal | undefined,
abortSignal?: HttpAbortSignal | undefined,
credentials?: Credentials | undefined,
) {
return new ImmutableRequest(
Expand All @@ -47,7 +66,7 @@ export class ImmutableRequest {
static delete(
url: ImmutableURL,
headers?: Record<string, string> | ImmutableHeaders,
abortSignal?: AbortSignal | undefined,
abortSignal?: HttpAbortSignal | undefined,
credentials?: Credentials | undefined,
) {
return new ImmutableRequest(
Expand All @@ -63,7 +82,7 @@ export class ImmutableRequest {
url: ImmutableURL,
body?: unknown | undefined,
headers?: Record<string, string> | ImmutableHeaders,
abortSignal?: AbortSignal | undefined,
abortSignal?: HttpAbortSignal | undefined,
credentials?: Credentials | undefined,
) {
return new ImmutableRequest(
Expand All @@ -80,7 +99,7 @@ export class ImmutableRequest {
url: ImmutableURL,
body?: unknown | undefined,
headers?: Record<string, string> | ImmutableHeaders,
abortSignal?: AbortSignal | undefined,
abortSignal?: HttpAbortSignal | undefined,
credentials?: Credentials | undefined,
) {
return new ImmutableRequest(
Expand All @@ -96,7 +115,7 @@ export class ImmutableRequest {
url: ImmutableURL,
body?: unknown | undefined,
headers?: Record<string, string> | ImmutableHeaders,
abortSignal?: AbortSignal | undefined,
abortSignal?: HttpAbortSignal | undefined,
credentials?: Credentials | undefined,
) {
return new ImmutableRequest(
Expand All @@ -111,75 +130,19 @@ export class ImmutableRequest {
get url() {
return this.#url;
}

get headers() {
return this.#headers;
}

setHeaders(nextHeaders: ImmutableHeaders) {
return new ImmutableRequest(
this.#method,
this.#url,
this.#body,
nextHeaders,
this.#abortSignal,
this.#credentials,
);
get body() {
return this.#body;
}

get abortSignal(): AbortSignal | undefined {
get abortSignal(): HttpAbortSignal | undefined {
return this.#abortSignal;
}

appendAbortSignal(signal: AbortSignal): ImmutableRequest {
return new ImmutableRequest(
this.#method,
this.#url,
this.#body,
this.#headers,
signal,
this.#credentials,
);
}

get credentials(): Credentials | undefined {
return this.#credentials;
}

get method(): Method {
return this.#method;
}

setCredentials(credentials: Credentials): ImmutableRequest {
return new ImmutableRequest(
this.#method,
this.#url,
this.#body,
this.#headers,
this.#abortSignal,
credentials,
);
}

setMethod(method: Method): ImmutableRequest {
return new ImmutableRequest(
method,
this.#url,
this.#body,
this.#headers,
this.#abortSignal,
this.#credentials,
);
}

setURL(url: ImmutableURL): ImmutableRequest {
return new ImmutableRequest(
this.#method,
url,
this.#body,
this.#headers,
this.#abortSignal,
this.#credentials,
);
}
}
28 changes: 28 additions & 0 deletions packages/http-client/src/ImmutableURL.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
import { URL } from "node:url";
import { ImmutableURLSearchParams } from "~/src/ImmutableURLSearchParams";

const mutableUrlField = [
"hash",
"host",
"hostname",
"href",
"password",
"pathname",
"port",
"protocol",
"search",
"username",
] as const;
type MutableURLFields = (typeof mutableUrlField)[number];
const isMutableUrlField = (it: any): it is MutableURLFields =>
mutableUrlField.includes(it);

export class ImmutableURL implements Readonly<URL> {
readonly #searchParams: ImmutableURLSearchParams;
readonly #url: URL;
Expand All @@ -12,6 +28,18 @@ export class ImmutableURL implements Readonly<URL> {
this.#searchParams = new ImmutableURLSearchParams(this.#url.searchParams);
}

copy(update?: Partial<Pick<ImmutableURL, MutableURLFields>>) {
const next = new URL(this.#url.toString());
if (update) {
Object.entries(update).forEach(([key, value]) => {
if (isMutableUrlField(key)) {
next[key] = value;
}
});
}
return new ImmutableURL(next);
}

get searchParams() {
return this.#searchParams;
}
Expand Down
6 changes: 4 additions & 2 deletions packages/http-client/test/ImmutableRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ it("constructs a request", () => {
ImmutableRequest.patch(new ImmutableURL("http://example.com/api"));
});

it("can modify the headers a header", () => {
it("can modify the headers", () => {
const underTest = ImmutableRequest.get(
new ImmutableURL("https://example.com/api"),
);
const updated = underTest.setHeaders(underTest.headers.set("bingo", "bango"));
const updated = underTest.copy({
headers: underTest.headers.set("bingo", "bango"),
});
expect(underTest.headers.get("bingo")).not.toEqual(
updated.headers.get("bingo"),
);
Expand Down
11 changes: 11 additions & 0 deletions packages/http-client/test/ImmutableURL.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { expect, it } from "vitest";
import { ImmutableRequest } from "~/src/ImmutableRequest";
import { ImmutableURL } from "~/src/ImmutableURL";

it("can copy the fields", () => {
const underTest = new ImmutableURL("https://example.com/api");
const changed = underTest.copy({ username: "user", password: "pass" });
expect(underTest.username).not.toEqual(changed.username);
expect(underTest.password).not.toEqual(changed.password);
expect(changed.toString()).toEqual("https://user:pass@example.com/api");
});

0 comments on commit 9f568f6

Please sign in to comment.