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.ts.timestamp-*
.vite/
*.local

View File

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

View File

@@ -9,11 +9,17 @@ import { deepClone } from '@/shared/helpers/deep-clone'
import type {
FLuentUrlPlainObject,
FluentUrlConfig,
ParseQueryOptions,
PlainObjectConstraint,
QueriesPlainObject,
StringifyQueryOptions,
} from '@/shared/types'
export class FluentUrl {
private readonly config: FluentUrlConfig
export class FluentUrl<
PQO extends PlainObjectConstraint = ParseQueryOptions,
SQO extends PlainObjectConstraint = StringifyQueryOptions,
> {
private readonly config: FluentUrlConfig<PQO, SQO>
private readonly protocol?: string
private readonly hostname?: string
private readonly paths: string[]
@@ -23,14 +29,15 @@ export class FluentUrl {
constructor(
urlOrOptions?: Partial<FLuentUrlPlainObject> | string,
config?: Partial<FluentUrlConfig>,
config?: Partial<FluentUrlConfig<PQO, SQO>>,
) {
this.config = normalizeConfig(config)
const { protocol, hostname, paths, port, fragment, queries } =
normalizeOptions({
urlOptions: urlOrOptions,
urlOrOptions,
parseQuery: this.config.parseQuery,
parseQueryOptions: this.config.parseQueryOptions,
})
this.protocol = protocol
@@ -76,8 +83,8 @@ export class FluentUrl {
public clone(
options?: Partial<FLuentUrlPlainObject>,
config?: Partial<FluentUrlConfig>,
): FluentUrl {
config?: Partial<FluentUrlConfig<PQO, SQO>>,
): FluentUrl<PQO, SQO> {
return new FluentUrl(
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')
}
export const stringifyQuery: QuerySerializer['stringifyQuery'] = () => {
export function stringifyQuery<T extends PlainObjectConstraint>(): ReturnType<
StringifyQuery<T>
> {
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 = {
absolute: 'absolute',
@@ -11,7 +16,11 @@ type URLTypes = (typeof URLTypesObj)[keyof typeof URLTypesObj]
const defaultProtocol = 'https'
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 urlType: URLTypes = urlString.startsWith('//')
@@ -64,11 +73,15 @@ export const parseUrl: UrlSerializer['parseUrl'] = ({ str, parseQuery }) => {
url.hash !== '' ? url.hash.replace('#', '') : undefined
urlPlainObject.queries =
url.search !== '' ? parseQuery(url.search) : Object.create(null)
url.search !== ''
? parseQuery(url.search, parseQueryOptions)
: Object.create(null)
return urlPlainObject
}
export const stringifyUrl: UrlSerializer['stringifyUrl'] = (_args) => {
export function stringifyUrl<T extends PlainObjectConstraint>(
_args: StringifyUrlArgs<T>,
): string {
throw new Error('Not implemented')
}

View File

@@ -9,23 +9,50 @@ export interface FLuentUrlPlainObject {
queries: QueriesPlainObject
}
export interface QuerySerializer {
parseQuery: (str: string) => QueriesPlainObject
stringifyQuery: (obj: QueriesPlainObject) => string
export type PlainObjectConstraint = Record<string, any>
export interface ParseQueryOptions extends PlainObjectConstraint {
[k: string]: any
}
export interface UrlSerializer {
parseUrl: (args: {
export type ParseQuery<T extends PlainObjectConstraint> = (
str: string,
options?: T,
) => QueriesPlainObject
export interface StringifyQueryOptions extends PlainObjectConstraint {
[k: string]: any
}
export type StringifyQuery<T extends PlainObjectConstraint> = (
obj: QueriesPlainObject,
options?: T,
) => string
export interface ParseUrlArgs<T extends PlainObjectConstraint> {
str: string
parseQuery: QuerySerializer['parseQuery']
}) => FLuentUrlPlainObject
stringifyUrl: (args: {
obj: FLuentUrlPlainObject
stringifyQuery: QuerySerializer['stringifyQuery']
}) => string
parseQuery: ParseQuery<T>
parseQueryOptions?: T
}
export interface FluentUrlConfig {
parseQuery: QuerySerializer['parseQuery']
stringifyQuery: QuerySerializer['stringifyQuery']
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",
"scripts": {
"build": "rimraf ./dist && tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
"type-check": "tsc --noEmit --skipLibCheck",
"prepare": "pnpm build",
"test": "vitest"
},