diff --git a/lib/core/config-defaults.ts b/lib/core/config-defaults.ts new file mode 100644 index 0000000..15a8934 --- /dev/null +++ b/lib/core/config-defaults.ts @@ -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 | 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), + } +} diff --git a/lib/core/fluent-url.ts b/lib/core/fluent-url.ts new file mode 100644 index 0000000..8244500 --- /dev/null +++ b/lib/core/fluent-url.ts @@ -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 | 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, + 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, + }, + ) + } +} diff --git a/lib/core/serializer/query-serializer.ts b/lib/core/serializer/query-serializer.ts new file mode 100644 index 0000000..8386f03 --- /dev/null +++ b/lib/core/serializer/query-serializer.ts @@ -0,0 +1,7 @@ +export const parseQuery: QuerySerializer['parseQuery'] = () => { + throw new Error('Not implemented') +} + +export const stringifyQuery: QuerySerializer['stringifyQuery'] = () => { + throw new Error('Not implemented') +} diff --git a/lib/core/serializer/url-serializer.ts b/lib/core/serializer/url-serializer.ts new file mode 100644 index 0000000..ea0976c --- /dev/null +++ b/lib/core/serializer/url-serializer.ts @@ -0,0 +1,7 @@ +export const parseUrl: UrlSerializer['parseUrl'] = () => { + throw new Error('Not implemented') +} + +export const stringifyUrl: UrlSerializer['stringifyUrl'] = () => { + throw new Error('Not implemented') +} diff --git a/lib/index.ts b/lib/index.ts new file mode 100644 index 0000000..96bd09a --- /dev/null +++ b/lib/index.ts @@ -0,0 +1,3 @@ +import { FluentUrl } from '@/core/fluent-url' + +export { FluentUrl } diff --git a/lib/shared/types/index.d.ts b/lib/shared/types/index.d.ts new file mode 100644 index 0000000..874dc5f --- /dev/null +++ b/lib/shared/types/index.d.ts @@ -0,0 +1,33 @@ +/** biome-ignore-all lint/correctness/noUnusedVariables: <> */ + +type QueriesPlainObject = Record + +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'] +}