Compare commits

1 Commits

7 changed files with 109 additions and 43 deletions

1
.gitignore vendored
View File

@@ -142,3 +142,4 @@ dist
vite.config.js.timestamp-* vite.config.js.timestamp-*
vite.config.ts.timestamp-* vite.config.ts.timestamp-*
.vite/ .vite/
*.local

View File

@@ -4,28 +4,32 @@ import { deepClone } from '@/shared/helpers/deep-clone'
import type { import type {
FLuentUrlPlainObject, FLuentUrlPlainObject,
FluentUrlConfig, FluentUrlConfig,
QuerySerializer, NormalizeOptionsArgs,
PlainObjectConstraint,
} from '@/shared/types' } from '@/shared/types'
export function normalizeConfig( export function normalizeConfig<
config?: Partial<FluentUrlConfig>, PQO extends PlainObjectConstraint,
): FluentUrlConfig { SQO extends PlainObjectConstraint,
>(config?: Partial<FluentUrlConfig<PQO, SQO>>): FluentUrlConfig<PQO, SQO> {
return { return {
parseQuery: config?.parseQuery ?? parseQuery, parseQuery: config?.parseQuery ?? parseQuery,
parseQueryOptions: config?.parseQueryOptions,
stringifyQuery: config?.stringifyQuery ?? stringifyQuery, stringifyQuery: config?.stringifyQuery ?? stringifyQuery,
stringifyQueryOptions: config?.stringifyQueryOptions,
} }
} }
export function normalizeOptions(args: { export function normalizeOptions<T extends PlainObjectConstraint>({
urlOptions?: Partial<FLuentUrlPlainObject> | string urlOrOptions,
parseQuery: QuerySerializer['parseQuery'] parseQuery,
}): FLuentUrlPlainObject { parseQueryOptions,
const { urlOptions: urlOrOptions, parseQuery } = args }: NormalizeOptionsArgs<T>): FLuentUrlPlainObject {
if (typeof urlOrOptions === 'string') { if (typeof urlOrOptions === 'string') {
return parseUrl({ return parseUrl({
str: urlOrOptions, str: urlOrOptions,
parseQuery: parseQuery, parseQuery,
parseQueryOptions,
}) })
} }
@@ -39,13 +43,18 @@ export function normalizeOptions(args: {
} }
} }
export function mergeConfig( export function mergeConfig<
current: FluentUrlConfig, PQO extends PlainObjectConstraint,
merge?: Partial<FluentUrlConfig>, SQO extends PlainObjectConstraint,
): FluentUrlConfig { >(
current: FluentUrlConfig<PQO, SQO>,
merge?: Partial<FluentUrlConfig<PQO, SQO>>,
): FluentUrlConfig<PQO, SQO> {
return { return {
parseQuery: merge?.parseQuery ?? current.parseQuery, parseQuery: merge?.parseQuery ?? current.parseQuery,
parseQueryOptions: merge?.parseQueryOptions,
stringifyQuery: merge?.stringifyQuery ?? current.stringifyQuery, stringifyQuery: merge?.stringifyQuery ?? current.stringifyQuery,
stringifyQueryOptions: merge?.stringifyQueryOptions,
} }
} }

View File

@@ -9,11 +9,17 @@ import { deepClone } from '@/shared/helpers/deep-clone'
import type { import type {
FLuentUrlPlainObject, FLuentUrlPlainObject,
FluentUrlConfig, FluentUrlConfig,
ParseQueryOptions,
PlainObjectConstraint,
QueriesPlainObject, QueriesPlainObject,
StringifyQueryOptions,
} from '@/shared/types' } from '@/shared/types'
export class FluentUrl { export class FluentUrl<
private readonly config: FluentUrlConfig PQO extends PlainObjectConstraint = ParseQueryOptions,
SQO extends PlainObjectConstraint = StringifyQueryOptions,
> {
private readonly config: FluentUrlConfig<PQO, SQO>
private readonly protocol?: string private readonly protocol?: string
private readonly hostname?: string private readonly hostname?: string
private readonly paths: string[] private readonly paths: string[]
@@ -23,14 +29,15 @@ export class FluentUrl {
constructor( constructor(
urlOrOptions?: Partial<FLuentUrlPlainObject> | string, urlOrOptions?: Partial<FLuentUrlPlainObject> | string,
config?: Partial<FluentUrlConfig>, config?: Partial<FluentUrlConfig<PQO, SQO>>,
) { ) {
this.config = normalizeConfig(config) this.config = normalizeConfig(config)
const { protocol, hostname, paths, port, fragment, queries } = const { protocol, hostname, paths, port, fragment, queries } =
normalizeOptions({ normalizeOptions({
urlOptions: urlOrOptions, urlOrOptions,
parseQuery: this.config.parseQuery, parseQuery: this.config.parseQuery,
parseQueryOptions: this.config.parseQueryOptions,
}) })
this.protocol = protocol this.protocol = protocol
@@ -76,8 +83,8 @@ export class FluentUrl {
public clone( public clone(
options?: Partial<FLuentUrlPlainObject>, options?: Partial<FLuentUrlPlainObject>,
config?: Partial<FluentUrlConfig>, config?: Partial<FluentUrlConfig<PQO, SQO>>,
): FluentUrl { ): FluentUrl<PQO, SQO> {
return new FluentUrl( return new FluentUrl(
mergeOptions( mergeOptions(
{ {

View File

@@ -1,9 +1,17 @@
import type { QuerySerializer } from '@/shared/types' import type {
ParseQuery,
PlainObjectConstraint,
StringifyQuery,
} from '@/shared/types'
export const parseQuery: QuerySerializer['parseQuery'] = () => { export function parseQuery<T extends PlainObjectConstraint>(): ReturnType<
ParseQuery<T>
> {
throw new Error('Not implemented') throw new Error('Not implemented')
} }
export const stringifyQuery: QuerySerializer['stringifyQuery'] = () => { export function stringifyQuery<T extends PlainObjectConstraint>(): ReturnType<
StringifyQuery<T>
> {
throw new Error('Not implemented') throw new Error('Not implemented')
} }

View File

@@ -1,4 +1,9 @@
import type { FLuentUrlPlainObject, UrlSerializer } from '@/shared/types' import type {
FLuentUrlPlainObject,
ParseUrlArgs,
PlainObjectConstraint,
StringifyUrlArgs,
} from '@/shared/types'
const URLTypesObj = { const URLTypesObj = {
absolute: 'absolute', absolute: 'absolute',
@@ -11,7 +16,11 @@ type URLTypes = (typeof URLTypesObj)[keyof typeof URLTypesObj]
const defaultProtocol = 'https' const defaultProtocol = 'https'
const defaultHost = 'example.com' const defaultHost = 'example.com'
export const parseUrl: UrlSerializer['parseUrl'] = ({ str, parseQuery }) => { export function parseUrl<T extends PlainObjectConstraint>({
str,
parseQuery,
parseQueryOptions,
}: ParseUrlArgs<T>): FLuentUrlPlainObject {
const urlString = str.trim() const urlString = str.trim()
const urlType: URLTypes = urlString.startsWith('//') const urlType: URLTypes = urlString.startsWith('//')
@@ -64,11 +73,15 @@ export const parseUrl: UrlSerializer['parseUrl'] = ({ str, parseQuery }) => {
url.hash !== '' ? url.hash.replace('#', '') : undefined url.hash !== '' ? url.hash.replace('#', '') : undefined
urlPlainObject.queries = urlPlainObject.queries =
url.search !== '' ? parseQuery(url.search) : Object.create(null) url.search !== ''
? parseQuery(url.search, parseQueryOptions)
: Object.create(null)
return urlPlainObject return urlPlainObject
} }
export const stringifyUrl: UrlSerializer['stringifyUrl'] = (_args) => { export function stringifyUrl<T extends PlainObjectConstraint>(
_args: StringifyUrlArgs<T>,
): string {
throw new Error('Not implemented') throw new Error('Not implemented')
} }

View File

@@ -9,23 +9,50 @@ export interface FLuentUrlPlainObject {
queries: QueriesPlainObject queries: QueriesPlainObject
} }
export interface QuerySerializer { export type PlainObjectConstraint = Record<string, any>
parseQuery: (str: string) => QueriesPlainObject
stringifyQuery: (obj: QueriesPlainObject) => string export interface ParseQueryOptions extends PlainObjectConstraint {
[k: string]: any
} }
export interface UrlSerializer { export type ParseQuery<T extends PlainObjectConstraint> = (
parseUrl: (args: { str: string,
str: string options?: T,
parseQuery: QuerySerializer['parseQuery'] ) => QueriesPlainObject
}) => FLuentUrlPlainObject
stringifyUrl: (args: { export interface StringifyQueryOptions extends PlainObjectConstraint {
obj: FLuentUrlPlainObject [k: string]: any
stringifyQuery: QuerySerializer['stringifyQuery']
}) => string
} }
export interface FluentUrlConfig { export type StringifyQuery<T extends PlainObjectConstraint> = (
parseQuery: QuerySerializer['parseQuery'] obj: QueriesPlainObject,
stringifyQuery: QuerySerializer['stringifyQuery'] options?: T,
) => string
export interface ParseUrlArgs<T extends PlainObjectConstraint> {
str: string
parseQuery: ParseQuery<T>
parseQueryOptions?: T
}
export interface StringifyUrlArgs<T extends PlainObjectConstraint> {
obj: FLuentUrlPlainObject
stringifyQuery: StringifyQuery<T>
stringifyQueryOptions?: T
}
export interface FluentUrlConfig<
PQO extends PlainObjectConstraint,
SQO extends PlainObjectConstraint,
> {
parseQuery: ParseQuery<PQO>
parseQueryOptions?: PQO
stringifyQuery: StringifyQuery<SQO>
stringifyQueryOptions?: SQO
}
export interface NormalizeOptionsArgs<T extends PlainObjectConstraint> {
urlOrOptions?: Partial<FLuentUrlPlainObject> | string
parseQuery: ParseQuery<T>
parseQueryOptions?: T
} }

View File

@@ -5,6 +5,7 @@
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"build": "rimraf ./dist && tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json", "build": "rimraf ./dist && tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
"type-check": "tsc --noEmit --skipLibCheck",
"prepare": "pnpm build", "prepare": "pnpm build",
"test": "vitest" "test": "vitest"
}, },