feat: implement FluentUrl class with configuration and options normalization

This commit is contained in:
2026-02-22 16:42:34 -05:00
parent 03f30ecbcd
commit f01755fda9
6 changed files with 169 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
import { parseQuery, stringifyQuery } from '@/core/serializer/query-serializer'
import { deepClone } from '@/shared/helpers/deep-clone'
import { parseUrl } from './serializer/url-serializer'
export function normalizeConfig(config?: FluentUrlConfig): FluentUrlConfig {
return {
parseQuery: config?.parseQuery ?? parseQuery,
stringifyQuery: config?.stringifyQuery ?? stringifyQuery,
}
}
export function normalizeOptions(args: {
urlOptions?: Partial<FLuentUrlPlainObject> | string
parseQuery: QuerySerializer['parseQuery']
}): FLuentUrlPlainObject {
const { urlOptions: urlOrOptions, parseQuery } = args
if (typeof urlOrOptions === 'string') {
return parseUrl({
str: urlOrOptions,
parseQuery: parseQuery,
})
}
return {
protocol: urlOrOptions?.protocol,
hostname: urlOrOptions?.hostname,
paths: deepClone(urlOrOptions?.paths) ?? [],
port: urlOrOptions?.port,
fragment: urlOrOptions?.fragment,
queries: deepClone(urlOrOptions?.queries) ?? Object.create(null),
}
}

86
lib/core/fluent-url.ts Normal file
View File

@@ -0,0 +1,86 @@
import { normalizeConfig, normalizeOptions } from '@/core/config-defaults'
import { deepClone } from '@/shared/helpers/deep-clone'
import { stringifyUrl } from './serializer/url-serializer'
export class FluentUrl {
private config: FluentUrlConfig
private readonly protocol?: string
private readonly hostname?: string
private readonly paths: string[]
private readonly port?: number
private readonly fragment?: string
private readonly queries: QueriesPlainObject
constructor(
urlOrOptions?: Partial<FLuentUrlPlainObject> | string,
config?: FluentUrlConfig,
) {
this.config = normalizeConfig(config)
const { protocol, hostname, paths, port, fragment, queries } =
normalizeOptions({
urlOptions: urlOrOptions,
parseQuery: this.config.parseQuery,
})
this.protocol = protocol
this.hostname = hostname
this.paths = paths
this.port = port
this.fragment = fragment
this.queries = queries
}
public toString(): string {
return stringifyUrl({
obj: {
protocol: this.protocol,
hostname: this.hostname,
paths: this.paths,
port: this.port,
fragment: this.fragment,
queries: this.queries,
},
stringifyQuery: this.config.stringifyQuery,
})
}
public toJSON(): string {
return this.toString()
}
public valueOf(): string {
return this.toString()
}
public toPlainObject(): FLuentUrlPlainObject {
return deepClone({
protocol: this.protocol,
hostname: this.hostname,
paths: this.paths,
port: this.port,
fragment: this.fragment,
queries: this.queries,
})
}
public clone(
options?: Partial<FLuentUrlPlainObject>,
config?: FluentUrlConfig,
): FluentUrl {
return new FluentUrl(
{
protocol: options?.protocol ?? this.protocol,
hostname: options?.hostname ?? this.hostname,
paths: options?.paths ?? this.paths,
port: options?.port ?? this.port,
fragment: options?.fragment ?? this.fragment,
queries: options?.queries ?? this.queries,
},
{
parseQuery: config?.parseQuery ?? this.config.parseQuery,
stringifyQuery: config?.stringifyQuery ?? this.config.stringifyQuery,
},
)
}
}

View File

@@ -0,0 +1,7 @@
export const parseQuery: QuerySerializer['parseQuery'] = () => {
throw new Error('Not implemented')
}
export const stringifyQuery: QuerySerializer['stringifyQuery'] = () => {
throw new Error('Not implemented')
}

View File

@@ -0,0 +1,7 @@
export const parseUrl: UrlSerializer['parseUrl'] = () => {
throw new Error('Not implemented')
}
export const stringifyUrl: UrlSerializer['stringifyUrl'] = () => {
throw new Error('Not implemented')
}

3
lib/index.ts Normal file
View File

@@ -0,0 +1,3 @@
import { FluentUrl } from '@/core/fluent-url'
export { FluentUrl }

33
lib/shared/types/index.d.ts vendored Normal file
View File

@@ -0,0 +1,33 @@
/** biome-ignore-all lint/correctness/noUnusedVariables: <> */
type QueriesPlainObject = Record<string, any>
interface FLuentUrlPlainObject {
protocol?: string
hostname?: string
paths: string[]
port?: number
fragment?: string
queries: QueriesPlainObject
}
interface QuerySerializer {
parseQuery: (str: string) => QueriesPlainObject
stringifyQuery: (obj: QueriesPlainObject) => string
}
interface UrlSerializer {
parseUrl: (args: {
str: string
parseQuery?: QuerySerializer['parseQuery']
}) => FLuentUrlPlainObject
stringifyUrl: (args: {
obj: FLuentUrlPlainObject
stringifyQuery?: QuerySerializer['stringifyQuery']
}) => string
}
interface FluentUrlConfig {
parseQuery: Serializer['parseQuery']
stringifyQuery: Serializer['stringifyQuery']
}