Last active
September 24, 2024 18:54
-
-
Save jamiebuilds/c6d8c8cdf7631a0e0d4b6d6f4b69924c to your computer and use it in GitHub Desktop.
Revisions
-
jamiebuilds revised this gist
Sep 24, 2024 . 2 changed files with 13 additions and 14 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,17 +1,16 @@ export type URLPathInput = boolean | string | number export type URLPathComponentInput = string | URLPath type URLPathParam = Readonly<{ key: string, value: string, }> type URLPathComponent = Readonly<{ text?: string param?: URLPathParam }> function escapeURLPathInput(value: URLPathInput): string { if (typeof value === "boolean" || typeof value === "number") { return String(value); @@ -46,7 +45,7 @@ function joinPathname(base: string, next: string) { export class URLPath { #components: URLPathComponent[] #toComponents(inputs: readonly URLPathComponentInput[]): URLPathComponent[] { return inputs.flatMap(component => { if (typeof component === "string") { return { text: component } @@ -67,27 +66,27 @@ export class URLPath { } } append(...inputs: readonly URLPathComponentInput[]): URLPath { let path = new URLPath(this) path.#components = path.#components.concat(this.#toComponents(inputs)) return path } param(key: string, value: URLPathInput): URLPath { let path = new URLPath(this) path.#components.push({ param: toURLPathParam(key, value) }) return path } params(params: Readonly<Record<string, URLPathInput>>): URLPath { let path = new URLPath(this) for (let [key, value] of Object.entries(params)) { path.#components.push({ param: toURLPathParam(key, value) }) } return path } toURL(base: string): URL { let params: URLPathParam[] = [] let url = new URL(base) This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,16 +1,16 @@ export function urlPath(strings: TemplateStringsArray, ...params: readonly (URLPathInput | URLPath)[]) { let inputs: URLPathComponentInput[] = []; for (let index = 0; index < strings.length; index += 1) { let text = strings[index] let param = params[index] inputs.push(text) if (param != null) { if (param instanceof URLPath) { inputs.push(param) } else { inputs.push(escapeURLPathInput(param)) } } } return new URLPath(...inputs) } -
jamiebuilds revised this gist
Sep 24, 2024 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -10,7 +10,7 @@ // URL { "https://example.com/one/two/three/four?v=1&one=1&two=2&three=3&four=4&five=5&six=6" } } // template string experiment { let url = urlPath`./one${urlPath`?one=1`}/two` .param("two", 2) -
jamiebuilds revised this gist
Sep 24, 2024 . 2 changed files with 9 additions and 7 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,18 +1,20 @@ // Base API { let url = new URLPath("./one") .append(new URLPath("?one=1"), "/two") .param("two", 2) .append(new URLPath().append(`./three?three=3`), "./four?four=4") .append("?five=5") .params({ six: 6 }) .toURL("https://example.com?v=1") // URL { "https://example.com/one/two/three/four?v=1&one=1&two=2&three=3&four=4&five=5&six=6" } } // template string expiriment { let url = urlPath`./one${urlPath`?one=1`}/two` .param("two", 2) .append(urlPath`./three?three=3`, "./four?four=4") .append("?five=5") .params({ six: 6 }) .toURL("https://example.com?v=1") This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -13,13 +13,13 @@ export type URLPathComponent = Readonly<{ export type URLPathComponentInput = string | URLPath function escapeURLPathInput(value: URLPathInput): string { if (typeof value === "boolean" || typeof value === "number") { return String(value); } if (typeof value === "string") { return encodeURIComponent(value); } throw new TypeError("Unexpected URLPathInput"); } function toURLPathParam(key: string, value: URLPathInput): URLPathParam { -
jamiebuilds revised this gist
Sep 24, 2024 . 1 changed file with 20 additions and 7 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,7 +1,20 @@ { let url = new URLPath() .append('./one', new URLPath("?one=1"), "/two") .param("two", 2) .append(new URLPath().append(`./three?three=3`), './four?four=4') .append("?five=5") .params({ six: 6 }) .toURL("https://example.com?v=1") // URL { "https://example.com/one/two/three/four?v=1&one=1&two=2&three=3&four=4&five=5&six=6" } } { let url = urlPath`./one${urlPath`?one=1`}/two` .param("two", 2) .append(urlPath`./three?three=3`, './four?four=4') .append("?five=5") .params({ six: 6 }) .toURL("https://example.com?v=1") // URL { "https://example.com/one/two/three/four?v=1&one=1&two=2&three=3&four=4&five=5&six=6" } } -
jamiebuilds revised this gist
Sep 24, 2024 . 2 changed files with 21 additions and 6 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,16 +1,16 @@ export type URLPathInput = boolean | string | number export type URLPathParam = Readonly<{ key: string, value: string, }> export type URLPathComponent = Readonly<{ text?: string param?: URLPathParam }> export type URLPathComponentInput = string | URLPath function escapeURLPathInput(value: URLPathInput): string { if (typeof value === 'boolean' || typeof value === 'number') { @@ -43,8 +43,7 @@ function joinPathname(base: string, next: string) { return result } export class URLPath { #components: URLPathComponent[] #toComponents(inputs: readonly URLPathComponentInput[]) { This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,16 @@ export function urlPath(strings: TemplateStringsArray, ...params: readonly (URLPathInput | URLPath)[]) { let components: URLPathComponentInput[] = []; for (let index = 0; index < strings.length; index += 1) { let text = strings[index] let param = params[index] components.push(text) if (param != null) { if (param instanceof URLPath) { components.push(param) } else { components.push(escapeURLPathInput(param)) } } } return new URLPath().append(...components) } -
jamiebuilds revised this gist
Sep 24, 2024 . 1 changed file with 36 additions and 47 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -10,7 +10,7 @@ type URLPathComponent = Readonly<{ param?: URLPathParam }> type URLPathComponentInput = string | URLPath function escapeURLPathInput(value: URLPathInput): string { if (typeof value === 'boolean' || typeof value === 'number') { @@ -22,18 +22,6 @@ function escapeURLPathInput(value: URLPathInput): string { throw new TypeError('Unexpected URLPathInput'); } function toURLPathParam(key: string, value: URLPathInput): URLPathParam { return { key: encodeURIComponent(key), value: escapeURLPathInput(value) } } @@ -55,39 +43,59 @@ function joinPathname(base: string, next: string) { return result } class URLPath { #components: URLPathComponent[] #toComponents(inputs: readonly URLPathComponentInput[]) { return inputs.flatMap(component => { if (typeof component === "string") { return { text: component } } else if (component instanceof URLPath) { return component.#components } else { throw new Error("Invalid input") } }) } constructor(...inputs: readonly URLPathComponentInput[]) { if (inputs.length === 1 && inputs[0] instanceof URLPath) { // fast path this.#components = inputs[0].#components } else { this.#components = this.#toComponents(inputs) } } append(...inputs: readonly URLPathComponentInput[]) { let path = new URLPath(this) path.#components = path.#components.concat(this.#toComponents(inputs)) return path } param(key: string, value: URLPathInput) { let path = new URLPath(this) path.#components.push({ param: toURLPathParam(key, value) }) return path } params(params: Readonly<Record<string, URLPathInput>>) { let path = new URLPath(this) for (let [key, value] of Object.entries(params)) { path.#components.push({ param: toURLPathParam(key, value) }) } return path } toURL(base: string) { let params: URLPathParam[] = [] let url = new URL(base) for (let [key, value] of url.searchParams) { params.push({ key, value }) } for (let component of this.#components) { if (component.text != null) { url = new URL(joinPathname(url.pathname, component.text), url.origin) @@ -107,23 +115,4 @@ class URLPath { return result } } -
jamiebuilds created this gist
Sep 24, 2024 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,7 @@ let url = urlPath`./one${urlPath`?one=1`}/two` .param("two", 2) .append(urlPath`./three?three=3`, './four?four=4') .append("?five=5") .params({ six: 6 }) .toURL("https://example.com") // URL { "https://example.com/one/two/three/four?one=1&two=2&three=3&four=4&five=5&six=6" } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,129 @@ type URLPathInput = boolean | string | number type URLPathParam = Readonly<{ key: string, value: string, }> type URLPathComponent = Readonly<{ text?: string param?: URLPathParam }> type URLPathComponentInput = string | URLPathComponent | URLPath function escapeURLPathInput(value: URLPathInput): string { if (typeof value === 'boolean' || typeof value === 'number') { return String(value); } if (typeof value === 'string') { return encodeURIComponent(value); } throw new TypeError('Unexpected URLPathInput'); } function toURLPathComponents(inputs: readonly URLPathComponentInput[]) { return inputs.flatMap(component => { if (typeof component === "string") { return { text: component } } else if (component instanceof URLPath) { return component[COMPONENTS] } else { return component } }) } function toURLPathParam(key: string, value: URLPathInput): URLPathParam { return { key: encodeURIComponent(key), value: escapeURLPathInput(value) } } function joinPathname(base: string, next: string) { let result = "" if (base !== "/") { result += base } if ( !result.endsWith("/") && !next.startsWith("/") && !next.startsWith("?") && !next.startsWith("#") ) { result += "/" } result += next return result } const COMPONENTS = Symbol("components") class URLPath { #components: URLPathComponent[] get [COMPONENTS]() { return this.#components } constructor(inputs: readonly URLPathComponentInput[]) { this.#components = toURLPathComponents(inputs) } append(...inputs: readonly URLPathComponentInput[]) { console.log(toURLPathComponents(inputs)) return new URLPath(this.#components.concat(toURLPathComponents(inputs))) } param(key: string, value: URLPathInput) { return new URLPath(this.#components.concat({ param: toURLPathParam(key, value) })) } params(params: Readonly<Record<string, URLPathInput>>) { let components: URLPathComponent[] = [] for (let [key, value] of Object.entries(params)) { components.push({ param: toURLPathParam(key, value) }) } return new URLPath(this.#components.concat(components)) } toURL(base: string) { let url = new URL(base) let params: URLPathParam[] = [] for (let component of this.#components) { if (component.text != null) { url = new URL(joinPathname(url.pathname, component.text), url.origin) for (let [key, value] of url.searchParams) { params.push({ key, value }) } } if (component.param != null) { params.push(component.param) } } let result = new URL(`${url.origin}${url.pathname}`) for (let param of params) { result.searchParams.set(param.key, param.value) } return result } } export type { URLPath }; export function urlPath(strings: TemplateStringsArray, ...params: readonly (URLPathInput | URLPath)[]) { let components: URLPathComponent[] = []; for (let index = 0; index < strings.length; index += 1) { let text = strings[index] let param = params[index] components.push({ text }) if (param != null) { if (param instanceof URLPath) { components = components.concat(param[COMPONENTS]) } else { components.push({ text: escapeURLPathInput(param) }) } } } return new URLPath(components); }