Skip to content

Instantly share code, notes, and snippets.

@fsubal
Last active February 2, 2025 09:44
Show Gist options
  • Save fsubal/b484a6ab3e254ae15aad4474dd4b87d7 to your computer and use it in GitHub Desktop.
Save fsubal/b484a6ab3e254ae15aad4474dd4b87d7 to your computer and use it in GitHub Desktop.
import { compile } from 'path-to-regexp'
type UrlParams = Record<string, string | number>
function normarizeUrlParams(input: UrlParams): Record<string, string> {
return Object.fromEntries(
Object.entries(input).map(([key, value]) => [key, value.toString()])
)
}
export abstract class AppUrl<P extends UrlParams, Q extends {}> {
static get baseUrl() {
if (typeof location !== 'undefined') {
return location.protocol + '://' + location.host
} else if (typeof process !== 'undefined' && process.env.APP_HOST) {
return process.env.APP_HOST
} else {
throw new Error(
'[AppUrl] Could not resolve baseUrl. ' +
'Neither window.location nor process.env.APP_HOST is available.'
)
}
}
static dynamic<P extends UrlParams, Q extends UrlParams = {}>(
pathTemplate: `${string}:${keyof P & string}${string}`
): AppDynamicUrl<P, Q> {
return new AppDynamicUrl<P, Q>((params?: P) => compile(pathTemplate)(params ? normarizeUrlParams(params) : {}), this.baseUrl)
}
static static<Q extends UrlParams = {}>(pathTemplate: string): AppStaticUrl<Q> {
return new AppStaticUrl<Q>(() => pathTemplate, this.baseUrl)
}
protected constructor(protected pathTemplate: (params?: P) => string, protected baseUrl: string) {}
protected addQueryParams(url: URL, queryParams: Q) {
const searchParams = new URLSearchParams(Object.entries(queryParams))
url.search = searchParams.toString()
}
abstract url(...args: unknown[]): URL
abstract path(...args: unknown[]): string
}
class AppDynamicUrl<P extends UrlParams, Q extends {}> extends AppUrl<P, Q> {
url(pathParams: P, queryParams?: Q): URL {
const url = new URL(this.pathTemplate(pathParams), this.baseUrl)
if (queryParams) {
this.addQueryParams(url, queryParams)
}
return url
}
path(pathParams: P, queryParams?: Q): string {
const { pathname, search } = this.url(pathParams, queryParams)
return pathname + '?' + search
}
}
class AppStaticUrl<Q extends {}> extends AppUrl<never, Q> {
url(queryParams?: Q): URL {
const url = new URL(this.pathTemplate(), this.baseUrl)
if (queryParams) {
this.addQueryParams(url, queryParams)
}
return url
}
path(queryParams?: Q): string {
const { pathname, search } = this.url(queryParams)
return pathname + '?' + search
}
}
const templateShow = AppUrl.dynamic<{ id: 'a', name: string }>('/templates/:name/')
templateShow.path({ id: 'a' }, { utm_source: 'app' })
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment