Skip to content

Commit 820cdb6

Browse files
authored
PR to move away from @nestjs/config and integrate our own config service. (#24)
* eject @nestjs/config add self config service * add @nestjs/common @nestjs/core dependencies * refactor the console command to support the new namespace configurations and tree like structure on console
1 parent 716aeca commit 820cdb6

31 files changed

Lines changed: 348 additions & 158 deletions

lib/cache/service.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
import { Injectable, OnModuleInit } from '@nestjs/common';
2-
import { IntentConfig } from '../config/service';
32
import { logTime } from '../utils/helpers';
43
import { InternalLogger } from '../utils/logger';
54
import { InMemoryDriver } from './drivers/inMemory';
65
import { RedisDriver } from './drivers/redis';
76
import { CacheDriver, CacheOptions } from './interfaces';
7+
import { ConfigService } from '../config';
88

99
@Injectable()
1010
export class CacheService implements OnModuleInit {
1111
static driverMap = { redis: RedisDriver, memory: InMemoryDriver };
1212
public static data: CacheOptions;
1313
static stores: Record<string, CacheDriver>;
1414

15-
constructor(config: IntentConfig) {
16-
CacheService.data = config.get('cache');
15+
constructor(config: ConfigService) {
16+
CacheService.data = config.get('cache') as CacheOptions;
1717
}
1818

1919
onModuleInit() {

lib/config/builder.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import {
2+
NamespacedConfigMapKeys,
3+
NamespacedConfigMapValues,
4+
RegisterNamespaceReturnType,
5+
} from './options';
6+
7+
export class ConfigBuilder {
8+
static async build(
9+
namespaceObjects: RegisterNamespaceReturnType<string, any>[],
10+
): Promise<Map<string, any>> {
11+
const configMap = new Map();
12+
13+
for (const namespacedConfig of namespaceObjects) {
14+
const namespacedMap = new Map<
15+
NamespacedConfigMapKeys,
16+
NamespacedConfigMapValues
17+
>();
18+
namespacedMap.set('factory', namespacedConfig._.factory);
19+
namespacedMap.set('static', await namespacedConfig._.factory());
20+
namespacedMap.set('encrypt', namespacedConfig._.options.encrypt);
21+
configMap.set(namespacedConfig._.namespace, namespacedMap);
22+
}
23+
24+
return configMap;
25+
}
26+
}

lib/config/command.ts

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,35 @@
1-
import { Injectable } from '@nestjs/common';
2-
import pc from 'picocolors';
31
import { Command, ConsoleIO } from '../console';
4-
import { Obj } from '../utils';
5-
import { Arr } from '../utils/array';
6-
import { columnify } from '../utils/columnify';
7-
import { IntentConfig } from './service';
2+
import { ConfigMap } from './options';
3+
import { CONFIG_FACTORY } from './constant';
4+
import pc from 'picocolors';
5+
import archy from 'archy';
6+
import { Inject } from '../foundation';
7+
import { jsonToArchy } from '../utils/console-helpers';
88

9-
@Injectable()
9+
@Command('config:view {--ns : Namespace of a particular config}', {
10+
desc: 'Command to view config for a given namespace',
11+
})
1012
export class ViewConfigCommand {
11-
constructor(private config: IntentConfig) {}
13+
constructor(@Inject(CONFIG_FACTORY) private config: ConfigMap) {}
14+
15+
async handle(_cli: ConsoleIO): Promise<void> {
16+
const nsFlag = _cli.option<string>('ns');
17+
const printNsToConsole = (namespace, obj) => {
18+
const values = obj.get('static') as Record<string, any>;
19+
console.log(
20+
archy(jsonToArchy(values, pc.bgGreen(pc.black(` ${namespace} `)))),
21+
);
22+
};
1223

13-
@Command('config:view {namespace}', {
14-
desc: 'Command to view config for a given namespace',
15-
})
16-
async handle(_cli: ConsoleIO): Promise<boolean> {
17-
const namespace = _cli.argument<string>('namespace');
18-
const config = this.config.get(namespace);
19-
if (!config) {
20-
_cli.error(`config with ${namespace} namespace not found`);
24+
if (nsFlag) {
25+
const ns = this.config.get(nsFlag);
26+
printNsToConsole(nsFlag, ns);
2127
return;
2228
}
23-
const columnifiedConfig = columnify(
24-
Arr.toObj(Obj.entries(config), ['key', 'value']),
25-
);
26-
const printRows = [];
27-
for (const row of columnifiedConfig) {
28-
printRows.push([pc.green(row[0]), pc.yellow(row[1])].join(' '));
29-
}
30-
31-
// eslint-disable-next-line no-console
32-
console.log(printRows.join('\n'));
3329

34-
return true;
30+
// Example usage
31+
for (const [namespace, obj] of this.config.entries()) {
32+
printNsToConsole(namespace, obj);
33+
}
3534
}
3635
}

lib/config/constant.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const CONFIG_FACTORY = '@intentjs/config_factory';

lib/config/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export * from './builder';
2+
export * from './register-namespace';
3+
export * from './options';
4+
export * from './service';
5+
export * from './command';
6+
export * from './constant';

lib/config/options.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
export type RegisterNamespaceOptions = {
2+
encrypt?: boolean;
3+
};
4+
5+
export type BuildConfigFromNS<
6+
T extends RegisterNamespaceReturnType<any, any>[],
7+
> = {
8+
[N in T[number]['_']['namespace']]: Extract<
9+
T[number],
10+
{ _: { namespace: N } }
11+
>['$inferConfig'];
12+
};
13+
14+
export type RegisterNamespaceReturnType<
15+
N extends string,
16+
T extends Record<string, any>,
17+
> = {
18+
_: {
19+
namespace: N;
20+
options: RegisterNamespaceOptions;
21+
factory: () => T | Promise<T>;
22+
};
23+
$inferConfig: T;
24+
};
25+
26+
export type NamespacedConfigMapKeys = 'factory' | 'static' | 'encrypt';
27+
28+
export type NamespacedConfigMapValues =
29+
// eslint-disable-next-line @typescript-eslint/ban-types
30+
Function | object | boolean | string | number;
31+
32+
export type NamespacedConfigMap = Map<
33+
NamespacedConfigMapKeys,
34+
NamespacedConfigMapValues
35+
>;
36+
export type ConfigMap = Map<string, NamespacedConfigMap>;

lib/config/register-namespace.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { LiteralString } from '../type-helpers';
2+
import {
3+
RegisterNamespaceOptions,
4+
RegisterNamespaceReturnType,
5+
} from './options';
6+
7+
// eslint-disable-next-line @typescript-eslint/no-var-requires
8+
require('dotenv').config();
9+
10+
export const registerNamespace = <N extends string, T>(
11+
namespace: LiteralString<N>,
12+
factory: () => T | Promise<T>,
13+
options?: RegisterNamespaceOptions,
14+
): RegisterNamespaceReturnType<LiteralString<N>, T> => {
15+
return {
16+
_: {
17+
namespace,
18+
options: {
19+
encrypt: options?.encrypt ?? false,
20+
},
21+
factory,
22+
},
23+
$inferConfig: {} as T,
24+
};
25+
};

lib/config/service.ts

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,51 @@
1-
import { Injectable } from '@nestjs/common';
2-
import { ConfigService } from '@nestjs/config';
1+
import { Inject, Injectable } from '../foundation';
2+
import { DotNotation, GetNestedPropertyType } from '../type-helpers';
3+
import { Obj } from '../utils';
4+
import { CONFIG_FACTORY } from './constant';
5+
import { ConfigMap, NamespacedConfigMapValues } from './options';
6+
7+
type ConfigPaths<T> = DotNotation<T>;
38

49
@Injectable()
5-
export class IntentConfig {
6-
private static cachedConfig: Map<string, any>;
7-
private static config: ConfigService;
10+
export class ConfigService<G = undefined> {
11+
private static cachedConfig = new Map<string, any>();
12+
private static config: ConfigMap;
813

9-
constructor(private config: ConfigService) {
10-
IntentConfig.cachedConfig = new Map<string, any>();
11-
IntentConfig.config = config;
14+
constructor(@Inject(CONFIG_FACTORY) private config: ConfigMap) {
15+
ConfigService.cachedConfig = new Map<ConfigPaths<G>, any>();
16+
ConfigService.config = this.config;
1217
}
1318

14-
get<T = any>(key: string): T {
15-
if (IntentConfig.cachedConfig.has(key))
16-
return IntentConfig.cachedConfig.get(key);
17-
const value = this.config.get<T>(key);
18-
IntentConfig.cachedConfig.set(key, value);
19-
return value;
19+
get<P extends string = ConfigPaths<G>, F = any>(
20+
key: P,
21+
): GetNestedPropertyType<G, P, F> | Promise<GetNestedPropertyType<G, P, F>> {
22+
return ConfigService.get<G, P>(key);
2023
}
2124

22-
static get<T = any>(key: string): T {
23-
if (this.cachedConfig.has(key)) return this.cachedConfig.get(key);
24-
const value = this.config.get<T>(key);
25-
this.cachedConfig.set(key, value);
26-
return value;
25+
static get<C = undefined, P extends string = ConfigPaths<C>, F = any>(
26+
key: P,
27+
): GetNestedPropertyType<C, P, F> | Promise<GetNestedPropertyType<C, P, F>> {
28+
const cachedValue = ConfigService.cachedConfig.get(key);
29+
if (cachedValue) return cachedValue;
30+
31+
const [namespace, ...paths] = (key as string).split('.');
32+
const nsConfig = this.config.get(namespace);
33+
/**
34+
* Returns a null value if the namespace doesn't exist.
35+
*/
36+
if (!nsConfig) return null;
37+
38+
if (!paths.length) return nsConfig.get('static') as any;
39+
40+
const staticValues = nsConfig.get('static') as Omit<
41+
NamespacedConfigMapValues,
42+
'function'
43+
>;
44+
45+
const valueOnPath = Obj.get<any>(staticValues, paths.join('.'));
46+
if (valueOnPath) {
47+
this.cachedConfig.set(key as string, valueOnPath);
48+
}
49+
return valueOnPath;
2750
}
2851
}

lib/database/service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import { Injectable, OnModuleInit } from '@nestjs/common';
22
import Knex, { Knex as KnexType } from 'knex';
3-
import { IntentConfig } from '../config/service';
43
import { logTime } from '../utils/helpers';
54
import { InternalLogger } from '../utils/logger';
65
import { BaseModel } from './baseModel';
76
import { ConnectionNotFound } from './exceptions';
87
import { DatabaseOptions, DbConnectionOptions } from './options';
8+
import { ConfigService } from '../config';
99

1010
@Injectable()
1111
export class ObjectionService implements OnModuleInit {
1212
static config: DatabaseOptions;
1313
static dbConnections: Record<string, KnexType>;
1414

15-
constructor(config: IntentConfig) {
15+
constructor(config: ConfigService) {
1616
const dbConfig = config.get('db') as DatabaseOptions;
1717
if (!dbConfig) return;
1818
const defaultConnection = dbConfig.connections[dbConfig.default];

lib/exceptions/intentExceptionFilter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ArgumentsHost, HttpException, Type } from '@nestjs/common';
22
import { BaseExceptionFilter } from '@nestjs/core';
3-
import { IntentConfig } from '../config/service';
3+
import { ConfigService } from '../config/service';
44
import { Log } from '../logger';
55
import { Request, Response } from '../rest/foundation';
66
import { Package } from '../utils';
@@ -29,7 +29,7 @@ export abstract class IntentExceptionFilter extends BaseExceptionFilter {
2929
}
3030

3131
reportToSentry(exception: any): void {
32-
const sentryConfig = IntentConfig.get('app.sentry');
32+
const sentryConfig = ConfigService.get('app.sentry');
3333
if (!sentryConfig?.dsn) return;
3434

3535
const exceptionConstructor = exception?.constructor;

0 commit comments

Comments
 (0)