Skip to content

Commit d2792ec

Browse files
author
oldskytree
committed
feat: rewrite to typescript
1 parent 37508fd commit d2792ec

28 files changed

Lines changed: 466 additions & 348 deletions

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
build

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
node_modules
2+
build

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ node_js:
33
- '6'
44
- '8'
55
script:
6+
- npm build
67
- npm test

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Config is described with a combination of a functions:
88
var parser = root(section({
99
system: section({
1010
parallelLimit: option({
11+
defaultValue: 0,
1112
parseEnv: Number,
1213
parseCli: Number,
1314
validate: function() {...}

lib/core.js renamed to lib/core.ts

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,35 @@
1-
const _ = require('lodash');
2-
const {buildLazyObject, forceParsing} = require('./lazy');
3-
const {MissingOptionError, UnknownKeysError} = require('./errors');
4-
const initLocator = require('./locator');
1+
import _ from 'lodash';
2+
3+
import { MissingOptionError, UnknownKeysError } from './errors';
4+
import { buildLazyObject, forceParsing } from './lazy';
5+
import initLocator from './locator';
6+
7+
import type { LazyObject } from '../types/lazy';
8+
import type { RootParsedConfig } from '../types/common';
9+
import type { MapParser } from '../types/map';
10+
import type { OptionParser, OptionParserConfig } from '../types/option';
11+
import type { RootParser, RootPrefixes, ConfigParser } from '../types/root';
12+
import type { SectionParser, SectionProperties } from '../types/section';
13+
14+
type Parser<T, R = any> = OptionParser<T, R> | SectionParser<T, R> | MapParser<T, R>;
515

616
/**
717
* Single option
818
*/
9-
function option({
19+
export function option<T, S = T, R = any>({
1020
defaultValue,
1121
parseCli = _.identity,
1222
parseEnv = _.identity,
1323
validate = _.noop,
1424
map: mapFunc = _.identity
15-
}) {
25+
}: OptionParserConfig<T, S, R>): OptionParser<S, R> {
26+
const validateFunc: typeof validate = validate;
27+
1628
return (locator, parsed) => {
1729
const config = parsed.root;
18-
const currNode = locator.parent ? _.get(config, locator.parent) : config;
30+
const currNode = locator.parent ? _.get(parsed, locator.parent) : config;
1931

20-
let value;
32+
let value: unknown;
2133
if (locator.cliOption !== undefined) {
2234
value = parseCli(locator.cliOption);
2335
} else if (locator.envVar !== undefined) {
@@ -31,7 +43,8 @@ function option({
3143
} else {
3244
throw new MissingOptionError(locator.name);
3345
}
34-
validate(value, config, currNode);
46+
47+
validateFunc(value, config, currNode);
3548

3649
return mapFunc(value, config, currNode);
3750
};
@@ -41,13 +54,15 @@ function option({
4154
* Object with fixed properties.
4255
* Any unknown property will be reported as error.
4356
*/
44-
function section(properties) {
45-
const expectedKeys = _.keys(properties);
57+
export function section<T, R = any>(properties: SectionProperties<T, R>): SectionParser<T, R> {
58+
const expectedKeys = _.keys(properties) as Array<keyof T>;
59+
4660
return (locator, config) => {
4761
const unknownKeys = _.difference(
4862
_.keys(locator.option),
49-
expectedKeys
63+
expectedKeys as Array<string>
5064
);
65+
5166
if (unknownKeys.length > 0) {
5267
throw new UnknownKeysError(
5368
unknownKeys.map((key) => `${locator.name}.${key}`)
@@ -56,6 +71,7 @@ function section(properties) {
5671

5772
const lazyResult = buildLazyObject(expectedKeys, (key) => {
5873
const parser = properties[key];
74+
5975
return () => parser(locator.nested(key), config);
6076
});
6177

@@ -69,17 +85,20 @@ function section(properties) {
6985
* Object with user-specified keys and values,
7086
* parsed by valueParser.
7187
*/
72-
function map(valueParser, defaultValue) {
88+
export function map<T extends Record<string, any>, V extends T[string] = T[string], R = any>(
89+
valueParser: Parser<V, R>,
90+
defaultValue: Record<string, V>
91+
): MapParser<Record<string, V>, R> {
7392
return (locator, config) => {
7493
if (locator.option === undefined) {
7594
if (!defaultValue) {
76-
return {};
95+
return {} as LazyObject<T>;
7796
}
7897
locator = locator.resetOption(defaultValue);
7998
}
8099

81-
const optionsToParse = Object.keys(locator.option);
82-
const lazyResult = buildLazyObject(optionsToParse, (key) => {
100+
const optionsToParse = Object.keys(locator.option as Record<string, V>);
101+
const lazyResult = buildLazyObject<Record<string, V>>(optionsToParse, (key) => {
83102
return () => valueParser(locator.nested(key), config);
84103
});
85104
_.set(config, locator.name, lazyResult);
@@ -88,13 +107,11 @@ function map(valueParser, defaultValue) {
88107
};
89108
}
90109

91-
function root(rootParser, {envPrefix, cliPrefix}) {
110+
export function root<T>(rootParser: RootParser<T>, {envPrefix, cliPrefix}: RootPrefixes): ConfigParser<T> {
92111
return ({options, env, argv}) => {
93112
const rootLocator = initLocator({options, env, argv, envPrefix, cliPrefix});
94-
const parsed = {};
95-
rootParser(rootLocator, parsed);
96-
return forceParsing(parsed.root);
113+
const parsed = rootParser(rootLocator, {} as RootParsedConfig<T>);
114+
115+
return forceParsing(parsed);
97116
};
98117
}
99-
100-
module.exports = {option, section, map, root};

lib/errors.js renamed to lib/errors.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
class MissingOptionError extends Error {
2-
constructor(optionName) {
1+
export class MissingOptionError extends Error {
2+
public optionName: string;
3+
4+
constructor(optionName: string) {
35
const message = `${optionName} is required`;
46
super(message);
57
this.name = 'MissingOptionError';
@@ -10,8 +12,10 @@ class MissingOptionError extends Error {
1012
}
1113
}
1214

13-
class UnknownKeysError extends Error {
14-
constructor(keys) {
15+
export class UnknownKeysError extends Error {
16+
public keys: Array<string>;
17+
18+
constructor(keys: Array<string>) {
1519
const message = `Unknown options: ${keys.join(', ')}`;
1620
super(message);
1721
this.name = 'UnknownKeysError';
@@ -21,5 +25,3 @@ class UnknownKeysError extends Error {
2125
Error.captureStackTrace(this, UnknownKeysError);
2226
}
2327
}
24-
25-
module.exports = {MissingOptionError, UnknownKeysError};

lib/index.js

Lines changed: 0 additions & 7 deletions
This file was deleted.

lib/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { root, section, map, option } from './core';
2+
export { MissingOptionError, UnknownKeysError } from './errors';

lib/lazy.js

Lines changed: 0 additions & 38 deletions
This file was deleted.

lib/lazy.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import _ from 'lodash';
2+
3+
import type { LazyObject } from '../types/lazy';
4+
5+
export const isLazy = Symbol('isLazy');
6+
7+
export function buildLazyObject<T>(keys: Array<keyof T>, getKeyGetter: (key: keyof T) => () => (T[keyof T] | LazyObject<T[keyof T]>)): LazyObject<T> {
8+
const target = {
9+
[isLazy]: true
10+
} as LazyObject<T>;
11+
12+
for (const key of keys) {
13+
defineLazy(target, key, getKeyGetter(key));
14+
}
15+
16+
return target;
17+
}
18+
19+
export function forceParsing<T>(lazyObject: LazyObject<T>): T {
20+
return _.cloneDeep(lazyObject);
21+
}
22+
23+
function defineLazy<T>(object: LazyObject<T>, key: keyof T, getter: () => T[keyof T] | LazyObject<T[keyof T]>): void {
24+
let defined = false;
25+
let value: T[keyof T];
26+
27+
Object.defineProperty(object, key, {
28+
get(): T[keyof T] {
29+
if (!defined) {
30+
defined = true;
31+
const val = getter();
32+
33+
if (isLazyObject(val)) {
34+
value = forceParsing(val);
35+
} else {
36+
value = val;
37+
}
38+
}
39+
40+
return value;
41+
},
42+
enumerable: true
43+
});
44+
}
45+
46+
function isLazyObject<T>(value: T): value is LazyObject<T> {
47+
return _.isObject(value) && hasOwnProperty(value, isLazy) && value[isLazy] === true;
48+
}
49+
50+
function hasOwnProperty<T extends {}>(obj: T, prop: PropertyKey): obj is T & Record<typeof prop, unknown> {
51+
return obj.hasOwnProperty(prop);
52+
}

0 commit comments

Comments
 (0)