Skip to content

Commit b8ee936

Browse files
committed
feat: create new parser to return readonly map when parsing connecton strings
1 parent cb1e8be commit b8ee936

File tree

1 file changed

+101
-0
lines changed

1 file changed

+101
-0
lines changed

src/parse.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import connectionStringParser from './parser/connection-string';
2+
3+
interface CoerceTypeMap {
4+
string: string;
5+
number: number;
6+
boolean: boolean;
7+
}
8+
9+
type CoerceType = keyof CoerceTypeMap;
10+
11+
type InferSchema<T extends SchemaDefinition> = {
12+
[K in keyof T]: T[K]['type'] extends CoerceType
13+
? CoerceTypeMap[T[K]['type']]
14+
: string;
15+
};
16+
17+
interface SchemaItem<T extends CoerceType = 'string'> {
18+
type?: T;
19+
default?: CoerceTypeMap[T];
20+
aliases?: string[];
21+
}
22+
23+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
24+
type SchemaDefinition = Record<string, SchemaItem<any>>;
25+
26+
class ConnectionString implements ReadonlyMap<string, string> {
27+
readonly #connectionString: string;
28+
readonly #parsed: ReadonlyMap<string, string>;
29+
constructor(connectionString: string) {
30+
this.#connectionString = connectionString.toString();
31+
const parsed = connectionStringParser(this.#connectionString);
32+
this.#parsed = new Map<string, string>(Object.entries(parsed));
33+
}
34+
35+
get size(): number {
36+
return this.#parsed.size;
37+
}
38+
39+
// it would be really nice to be able to make this a generice (eg: get<string>) and that would then coerce the value
40+
// see typia library for an example of something similar
41+
get<T extends CoerceType = 'string'>(key: string, coerceType?: T): CoerceTypeMap[T] | undefined {
42+
const val = this.#parsed.get(key);
43+
const actualType = coerceType ?? 'string';
44+
if (typeof val === 'undefined' || actualType === 'string') {
45+
return val as CoerceTypeMap[T];
46+
}
47+
switch (actualType) {
48+
case 'boolean':
49+
// a really liberal interpretation of "false" - "" "0" and "false" are false, everything else is true
50+
return (val === '0' || val.toLowerCase() === 'false' ? false : !!val) as CoerceTypeMap[T];
51+
case 'number':
52+
return parseInt(val, 10) as CoerceTypeMap[T];
53+
default:
54+
throw new TypeError('Coerce type not supported');
55+
}
56+
}
57+
58+
keys(): MapIterator<string> {
59+
return this.#parsed.keys();
60+
}
61+
62+
values(): MapIterator<string> {
63+
return this.#parsed.values();
64+
}
65+
66+
[Symbol.iterator](): MapIterator<[string, string]> {
67+
return this.#parsed[Symbol.iterator]();
68+
}
69+
70+
entries() {
71+
return this.#parsed.entries();
72+
}
73+
74+
toString() {
75+
return this.#connectionString;
76+
}
77+
78+
has(key: string) {
79+
return this.#parsed.has(key.toLowerCase());
80+
}
81+
82+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
83+
forEach(callbackfn: (value: string, key: string, map: ReadonlyMap<string, string>) => void, thisArg?: any): void {
84+
this.#parsed.forEach((value, key) => {
85+
callbackfn.call(thisArg ?? this, value, key, this);
86+
});
87+
}
88+
89+
// a way to extract a formatted object from the connection string
90+
toSchema<T extends SchemaDefinition>(schema: T): InferSchema<T> {
91+
return Object.fromEntries(Object.entries(schema).map(([key, { type, default: defaultValue, aliases }]) => {
92+
// try to find the property
93+
const prop = [key, ...aliases ?? []].find((k) => this.has(k));
94+
return [key, prop ? this.get(prop, type) : defaultValue];
95+
})) as InferSchema<T>;
96+
}
97+
}
98+
99+
export default function parse(connectionString: string) {
100+
return Object.freeze(new ConnectionString(connectionString));
101+
}

0 commit comments

Comments
 (0)