@tsed/config 8.9.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/lib/esm/constants/constants.js +2 -0
  2. package/lib/esm/constants/constants.js.map +1 -0
  3. package/lib/esm/hooks/afterResolveConfiguration.js +55 -0
  4. package/lib/esm/hooks/afterResolveConfiguration.js.map +1 -0
  5. package/lib/esm/index.js +20 -0
  6. package/lib/esm/index.js.map +1 -0
  7. package/lib/esm/interfaces/ConfigSource.js +2 -0
  8. package/lib/esm/interfaces/ConfigSource.js.map +1 -0
  9. package/lib/esm/interfaces/index.js +2 -0
  10. package/lib/esm/interfaces/index.js.map +1 -0
  11. package/lib/esm/providers/dotenv/DotEnvsConfigSource.js +30 -0
  12. package/lib/esm/providers/dotenv/DotEnvsConfigSource.js.map +1 -0
  13. package/lib/esm/providers/dotenv/index.js +3 -0
  14. package/lib/esm/providers/dotenv/index.js.map +1 -0
  15. package/lib/esm/providers/envs/EnvsConfigSource.js +14 -0
  16. package/lib/esm/providers/envs/EnvsConfigSource.js.map +1 -0
  17. package/lib/esm/providers/envs/index.js +3 -0
  18. package/lib/esm/providers/envs/index.js.map +1 -0
  19. package/lib/esm/providers/json/JsonConfigSource.js +23 -0
  20. package/lib/esm/providers/json/JsonConfigSource.js.map +1 -0
  21. package/lib/esm/providers/json/index.js +3 -0
  22. package/lib/esm/providers/json/index.js.map +1 -0
  23. package/lib/esm/providers/yaml/YamlConfigSource.js +23 -0
  24. package/lib/esm/providers/yaml/YamlConfigSource.js.map +1 -0
  25. package/lib/esm/providers/yaml/index.js +3 -0
  26. package/lib/esm/providers/yaml/index.js.map +1 -0
  27. package/lib/esm/utils/getConfigSources.js +43 -0
  28. package/lib/esm/utils/getConfigSources.js.map +1 -0
  29. package/lib/esm/utils/jsonParse.js +9 -0
  30. package/lib/esm/utils/jsonParse.js.map +1 -0
  31. package/lib/esm/utils/validate.js +16 -0
  32. package/lib/esm/utils/validate.js.map +1 -0
  33. package/lib/esm/utils/withOptions.js +13 -0
  34. package/lib/esm/utils/withOptions.js.map +1 -0
  35. package/lib/tsconfig.esm.tsbuildinfo +1 -0
  36. package/lib/types/constants/constants.d.ts +3 -0
  37. package/lib/types/hooks/afterResolveConfiguration.d.ts +2 -0
  38. package/lib/types/index.d.ts +19 -0
  39. package/lib/types/interfaces/ConfigSource.d.ts +57 -0
  40. package/lib/types/interfaces/index.d.ts +11 -0
  41. package/lib/types/providers/dotenv/DotEnvsConfigSource.d.ts +8 -0
  42. package/lib/types/providers/dotenv/index.d.ts +2 -0
  43. package/lib/types/providers/envs/EnvsConfigSource.d.ts +8 -0
  44. package/lib/types/providers/envs/index.d.ts +2 -0
  45. package/lib/types/providers/json/JsonConfigSource.d.ts +18 -0
  46. package/lib/types/providers/json/index.d.ts +2 -0
  47. package/lib/types/providers/yaml/YamlConfigSource.d.ts +13 -0
  48. package/lib/types/providers/yaml/index.d.ts +2 -0
  49. package/lib/types/utils/getConfigSources.d.ts +6 -0
  50. package/lib/types/utils/jsonParse.d.ts +1 -0
  51. package/lib/types/utils/validate.d.ts +2 -0
  52. package/lib/types/utils/withOptions.d.ts +12 -0
  53. package/package.json +105 -0
  54. package/readme.md +354 -0
  55. package/src/constants/constants.ts +4 -0
  56. package/src/hooks/afterResolveConfiguration.ts +77 -0
  57. package/src/index.ts +19 -0
  58. package/src/interfaces/ConfigSource.ts +65 -0
  59. package/src/interfaces/index.ts +12 -0
  60. package/src/providers/dotenv/DotEnvsConfigSource.ts +40 -0
  61. package/src/providers/dotenv/index.ts +3 -0
  62. package/src/providers/envs/EnvsConfigSource.spec.ts +51 -0
  63. package/src/providers/envs/EnvsConfigSource.ts +23 -0
  64. package/src/providers/envs/index.ts +3 -0
  65. package/src/providers/json/JsonConfigSource.spec.ts +60 -0
  66. package/src/providers/json/JsonConfigSource.ts +45 -0
  67. package/src/providers/json/index.ts +3 -0
  68. package/src/providers/yaml/YamlConfigSource.spec.ts +64 -0
  69. package/src/providers/yaml/YamlConfigSource.ts +39 -0
  70. package/src/providers/yaml/index.ts +3 -0
  71. package/src/utils/getConfigSources.ts +52 -0
  72. package/src/utils/jsonParse.ts +7 -0
  73. package/src/utils/validate.ts +18 -0
  74. package/src/utils/withOptions.ts +19 -0
  75. package/tsconfig.esm.json +26 -0
  76. package/vitest.config.mts +21 -0
@@ -0,0 +1,77 @@
1
+ import "@tsed/ajv";
2
+
3
+ import {configuration, constant, injectable, logger} from "@tsed/di";
4
+ import {$on} from "@tsed/hooks";
5
+
6
+ import {CONFIG_SOURCES} from "../constants/constants.js";
7
+ import type {ConfigurationExtends} from "../interfaces/ConfigSource.js";
8
+ import {getConfigSources} from "../utils/getConfigSources.js";
9
+ import {validate} from "../utils/validate.js";
10
+
11
+ export async function afterResolveConfiguration() {
12
+ const sources = getConfigSources(constant<ConfigurationExtends>("extends", []));
13
+
14
+ for (const source of sources.values()) {
15
+ const {instance, options, watch, refreshOn, name, validationSchema} = source;
16
+
17
+ const refresh = async () => {
18
+ let data = await instance.getAll();
19
+
20
+ if (validationSchema) {
21
+ data = await validate(name!, data, validationSchema);
22
+ }
23
+
24
+ source.data = data;
25
+ configuration().set(data);
26
+ };
27
+
28
+ await refresh();
29
+
30
+ // manage watcher
31
+ if (watch && instance?.watch) {
32
+ const closer = await instance.watch(refresh);
33
+
34
+ $on("$onDestroy", closer);
35
+ }
36
+
37
+ // manager refresh on request
38
+ if (refreshOn === "request") {
39
+ $on("$onRequest", () => {
40
+ return refresh();
41
+ });
42
+ }
43
+
44
+ // manager refresh on response
45
+ if (refreshOn === "response") {
46
+ $on("$onResponse", () => {
47
+ refresh().catch((error) => {
48
+ logger().error({
49
+ event: "CONFIG_SOURCE_REFRESH_ERROR",
50
+ hook: "$onResponse",
51
+ error_name: error.name,
52
+ error_message: error.message,
53
+ error_stack: error.stack
54
+ });
55
+ });
56
+ });
57
+ }
58
+ }
59
+
60
+ configuration().set(
61
+ "configs",
62
+ Object.fromEntries(
63
+ [...sources.entries()].map(([key, source]) => {
64
+ return [key, source.data];
65
+ })
66
+ )
67
+ );
68
+
69
+ const configs = Object.fromEntries(
70
+ [...sources.entries()].map(([key, source]) => {
71
+ return [key, source.instance];
72
+ })
73
+ );
74
+ injectable(CONFIG_SOURCES).value(configs);
75
+ }
76
+
77
+ $on("$afterResolveConfiguration", afterResolveConfiguration);
package/src/index.ts ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @file Automatically generated by @tsed/barrels.
3
+ */
4
+ export * from "./constants/constants.js";
5
+ export * from "./hooks/afterResolveConfiguration.js";
6
+ export * from "./interfaces/ConfigSource.js";
7
+ export * from "./interfaces/index.js";
8
+ export * from "./providers/dotenv/DotEnvsConfigSource.js";
9
+ export * from "./providers/dotenv/index.js";
10
+ export * from "./providers/envs/EnvsConfigSource.js";
11
+ export * from "./providers/envs/index.js";
12
+ export * from "./providers/json/index.js";
13
+ export * from "./providers/json/JsonConfigSource.js";
14
+ export * from "./providers/yaml/index.js";
15
+ export * from "./providers/yaml/YamlConfigSource.js";
16
+ export * from "./utils/getConfigSources.js";
17
+ export * from "./utils/jsonParse.js";
18
+ export * from "./utils/validate.js";
19
+ export * from "./utils/withOptions.js";
@@ -0,0 +1,65 @@
1
+ import type {Type} from "@tsed/core";
2
+ import {JsonSchema} from "@tsed/schema";
3
+
4
+ export interface ConfigSourceOptions<Opts = any> {
5
+ /**
6
+ * The name of the configuration provider.
7
+ */
8
+ name?: string;
9
+
10
+ /**
11
+ * The priority of the configuration provider. Higher priority providers will override values from lower priority providers.
12
+ * @default 0
13
+ */
14
+ priority?: number;
15
+
16
+ /**
17
+ * Whether to enable the configuration provider.
18
+ * @default true
19
+ */
20
+ enabled?: boolean;
21
+ /**
22
+ * The ConfigSource to use for loading configuration values.
23
+ * @param args
24
+ */
25
+ use: Type<ConfigSource<Opts>>;
26
+ /**
27
+ * Options for the configuration provider.
28
+ */
29
+ options: Opts;
30
+ /**
31
+ * The schema to validate the configuration values against.
32
+ */
33
+ validationSchema?: JsonSchema;
34
+ /**
35
+ * Enable FileSystem Watch mode. ConfigSource must implement the `watch()` method.
36
+ */
37
+ watch?: boolean;
38
+ /**
39
+ * Refresh strategy. Ts.ED can refresh ConfigSource during the "$onRequest" or "$onResponse" hook.
40
+ *
41
+ * Note: refresh data during `$onRequest` can degrade performance depending on the ConfigSource type.
42
+ * We recommend to refresh data during the $onResponse hook (action are asynchronous).
43
+ */
44
+ refreshOn?: "request" | "response";
45
+ }
46
+
47
+ export type InitialConfigSourceOptions = Omit<ConfigSourceOptions, "options" | "use">;
48
+ export type ConfigSourceWatchCloseCB = () => Promise<void> | void;
49
+ export type ConfigSourceOnChangeCB = () => void;
50
+
51
+ /**
52
+ * Interface for a configuration source that can provide configuration values.
53
+ */
54
+ export interface ConfigSource<Opts = any> {
55
+ options: Opts;
56
+
57
+ /**
58
+ * Load configuration from the source.
59
+ */
60
+ getAll(): Promise<Record<string, unknown>> | Record<string, unknown>;
61
+
62
+ watch?(onChange?: ConfigSourceOnChangeCB): Promise<ConfigSourceWatchCloseCB> | ConfigSourceWatchCloseCB;
63
+ }
64
+
65
+ export type ConfigurationExtends = (Type<ConfigSource> | ConfigSourceOptions)[];
@@ -0,0 +1,12 @@
1
+ import type {ConfigurationExtends} from "./ConfigSource.js";
2
+
3
+ /**
4
+ * Extend the global TsED namespace to include configuration for this package.
5
+ */
6
+ declare global {
7
+ namespace TsED {
8
+ interface Configuration {
9
+ extends?: ConfigurationExtends;
10
+ }
11
+ }
12
+ }
@@ -0,0 +1,40 @@
1
+ import {watch} from "node:fs";
2
+ import {join} from "node:path";
3
+
4
+ import dotenvExpand from "dotenv-expand";
5
+ import dotenv, {type DotenvFlowConfigOptions} from "dotenv-flow";
6
+ import {globby} from "globby";
7
+
8
+ import type {ConfigSourceOnChangeCB} from "../../interfaces/ConfigSource.js";
9
+ import {EnvsConfigSource, type EnvsConfigSourceOptions} from "../envs/EnvsConfigSource.js";
10
+
11
+ export type DotEnvsConfigSourceOptions = EnvsConfigSourceOptions & DotenvFlowConfigOptions;
12
+
13
+ export class DotEnvsConfigSource extends EnvsConfigSource<DotEnvsConfigSourceOptions> {
14
+ getAll(): Record<string, unknown> {
15
+ dotenvExpand.expand(dotenv.config(this.options));
16
+
17
+ return super.getAll();
18
+ }
19
+
20
+ async watch(onChange?: ConfigSourceOnChangeCB) {
21
+ const files = await globby(join(this.options.path!, ".env"), {
22
+ dot: true,
23
+ absolute: true
24
+ });
25
+
26
+ const watchers = files.map((file) => {
27
+ const watcher = watch(file, onChange);
28
+
29
+ return () => {
30
+ watcher.close();
31
+ };
32
+ });
33
+
34
+ return () => {
35
+ watchers.map((close) => {
36
+ return close();
37
+ });
38
+ };
39
+ }
40
+ }
@@ -0,0 +1,3 @@
1
+ import "../../hooks/afterResolveConfiguration.js";
2
+
3
+ export * from "./DotEnvsConfigSource.js";
@@ -0,0 +1,51 @@
1
+ import {EnvsConfigSource} from "./EnvsConfigSource.js";
2
+
3
+ describe("EnvsConfigSource", () => {
4
+ describe("getAll()", () => {
5
+ it("should return all environment variables", () => {
6
+ process.env.TEST_VAR = "test";
7
+ process.env.TEST_VAR_2 = "test2";
8
+
9
+ const source = new EnvsConfigSource();
10
+ source.options = {};
11
+
12
+ const result = source.getAll();
13
+
14
+ expect(result.TEST_VAR).toEqual("test");
15
+ expect(result.TEST_VAR_2).toEqual("test2");
16
+ });
17
+
18
+ it("should parse JSON values if parseJson is true", () => {
19
+ process.env.TEST_JSON = '{"key":"value"}';
20
+
21
+ const source = new EnvsConfigSource();
22
+ source.options = {parseJson: true};
23
+
24
+ const result = source.getAll();
25
+
26
+ expect(result.TEST_JSON).toEqual({key: "value"});
27
+ });
28
+
29
+ it("should not parse JSON values if parseJson is false", () => {
30
+ process.env.TEST_JSON = '{"key":"value"}';
31
+
32
+ const source = new EnvsConfigSource();
33
+ source.options = {parseJson: false};
34
+
35
+ const result = source.getAll();
36
+
37
+ expect(result.TEST_JSON).toEqual('{"key":"value"}');
38
+ });
39
+ it("should handle invalid JSON gracefully", () => {
40
+ process.env.INVALID_JSON = "{invalid json}";
41
+
42
+ const source = new EnvsConfigSource();
43
+ source.options = {parseJson: true};
44
+
45
+ const result = source.getAll();
46
+
47
+ // Check that it falls back to the string value when parsing fails
48
+ expect(result.INVALID_JSON).toEqual("{invalid json}");
49
+ });
50
+ });
51
+ });
@@ -0,0 +1,23 @@
1
+ import type {ConfigSource} from "../../interfaces/ConfigSource.js";
2
+ import {jsonParse} from "../../utils/jsonParse.js";
3
+
4
+ export interface EnvsConfigSourceOptions {
5
+ parseJson?: boolean;
6
+ }
7
+
8
+ export class EnvsConfigSource<Config extends EnvsConfigSourceOptions = EnvsConfigSourceOptions> implements ConfigSource<Config> {
9
+ options: Config;
10
+
11
+ getAll(): Record<string, unknown> {
12
+ const {parseJson = true} = this.options;
13
+
14
+ return Object.entries(process.env).reduce((obj, [key, value]) => {
15
+ const formattedValue = parseJson ? jsonParse(value!) : value;
16
+
17
+ return {
18
+ ...obj,
19
+ [key]: formattedValue
20
+ };
21
+ }, {});
22
+ }
23
+ }
@@ -0,0 +1,3 @@
1
+ import "../../hooks/afterResolveConfiguration.js";
2
+
3
+ export * from "./EnvsConfigSource.js";
@@ -0,0 +1,60 @@
1
+ import {existsSync, readFileSync, watch} from "node:fs";
2
+
3
+ import {JsonConfigSource} from "./JsonConfigSource.js";
4
+
5
+ vi.mock("node:fs");
6
+ describe("JsonConfigSource", () => {
7
+ describe("getAll()", () => {
8
+ it("should read JSON file and return parsed object", () => {
9
+ const source = new JsonConfigSource();
10
+ source.options = {
11
+ path: "./test.json"
12
+ };
13
+
14
+ vi.mocked(existsSync).mockReturnValue(true);
15
+ vi.mocked(readFileSync).mockReturnValue(JSON.stringify({key: "value"}));
16
+
17
+ const result = source.getAll();
18
+
19
+ expect(result).toEqual({key: "value"});
20
+ });
21
+ it("should return empty object if file does not exist", () => {
22
+ const source = new JsonConfigSource();
23
+ source.options = {
24
+ path: "./test.json"
25
+ };
26
+
27
+ vi.mocked(existsSync).mockReturnValue(false);
28
+
29
+ const result = source.getAll();
30
+
31
+ expect(result).toEqual({});
32
+ });
33
+ });
34
+
35
+ describe("watch()", () => {
36
+ it("should call watch with the correct path", () => {
37
+ const source = new JsonConfigSource();
38
+ source.options = {
39
+ path: "./test.json"
40
+ };
41
+
42
+ const mockOnChange = vi.fn();
43
+
44
+ const mockWatcher = {
45
+ close: vi.fn()
46
+ };
47
+
48
+ vi.mocked(watch).mockReturnValue(mockWatcher as any);
49
+
50
+ const result = source.watch(mockOnChange);
51
+
52
+ expect(watch).toHaveBeenCalledWith("./test.json", mockOnChange);
53
+ expect(result).toBeInstanceOf(Function);
54
+
55
+ result();
56
+
57
+ expect(mockWatcher.close).toHaveBeenCalled();
58
+ });
59
+ });
60
+ });
@@ -0,0 +1,45 @@
1
+ import {existsSync, readFileSync, watch} from "node:fs";
2
+
3
+ import {logger} from "@tsed/di";
4
+
5
+ import type {ConfigSource} from "../../interfaces/ConfigSource.js";
6
+
7
+ export interface JsonConfigSourceOptions {
8
+ /**
9
+ * The path to the JSON file.
10
+ */
11
+ path: string;
12
+
13
+ /**
14
+ * The encoding to use when reading the file.
15
+ * @default "utf8"
16
+ */
17
+ encoding?: BufferEncoding;
18
+ }
19
+
20
+ export class JsonConfigSource implements ConfigSource<JsonConfigSourceOptions> {
21
+ options: JsonConfigSourceOptions;
22
+
23
+ getAll(): Record<string, unknown> {
24
+ const {path, encoding = "utf8"} = this.options;
25
+ // Check if the file exists
26
+ if (!existsSync(path)) {
27
+ logger().warn(`Configuration file not found: ${path}`);
28
+ return {};
29
+ }
30
+
31
+ // Read the file
32
+ const fileContent = readFileSync(path, encoding);
33
+
34
+ return JSON.parse(fileContent);
35
+ }
36
+
37
+ watch(onChange: () => void) {
38
+ const {path} = this.options;
39
+ const watcher = watch(path, onChange);
40
+
41
+ return () => {
42
+ watcher.close();
43
+ };
44
+ }
45
+ }
@@ -0,0 +1,3 @@
1
+ import "../../hooks/afterResolveConfiguration.js";
2
+
3
+ export * from "./JsonConfigSource.js";
@@ -0,0 +1,64 @@
1
+ import {existsSync, readFileSync, watch} from "node:fs";
2
+
3
+ import JsYaml from "js-yaml";
4
+
5
+ import {YamlConfigSource} from "./YamlConfigSource.js";
6
+
7
+ vi.mock("node:fs");
8
+ vi.mock("js-yaml");
9
+
10
+ describe("YamlConfigSource", () => {
11
+ describe("getAll()", () => {
12
+ it("should read JSON file and return parsed object", async () => {
13
+ const source = new YamlConfigSource();
14
+ source.options = {
15
+ path: "./test.yaml"
16
+ };
17
+
18
+ vi.mocked(existsSync).mockReturnValue(true);
19
+ vi.mocked(JsYaml.load).mockReturnValue({key: "value"});
20
+
21
+ const result = await source.getAll();
22
+
23
+ expect(result).toEqual({key: "value"});
24
+ });
25
+ it("should return empty object if file does not exist", async () => {
26
+ const source = new YamlConfigSource();
27
+ source.options = {
28
+ path: "./test.yaml"
29
+ };
30
+
31
+ vi.mocked(existsSync).mockReturnValue(false);
32
+
33
+ const result = await source.getAll();
34
+
35
+ expect(result).toEqual({});
36
+ });
37
+ });
38
+
39
+ describe("watch()", () => {
40
+ it("should call watch with the correct path", () => {
41
+ const source = new YamlConfigSource();
42
+ source.options = {
43
+ path: "./test.yaml"
44
+ };
45
+
46
+ const mockOnChange = vi.fn();
47
+
48
+ const mockWatcher = {
49
+ close: vi.fn()
50
+ };
51
+
52
+ vi.mocked(watch).mockReturnValue(mockWatcher as any);
53
+
54
+ const result = source.watch(mockOnChange);
55
+
56
+ expect(watch).toHaveBeenCalledWith("./test.yaml", mockOnChange);
57
+ expect(result).toBeInstanceOf(Function);
58
+
59
+ result();
60
+
61
+ expect(mockWatcher.close).toHaveBeenCalled();
62
+ });
63
+ });
64
+ });
@@ -0,0 +1,39 @@
1
+ import {existsSync, watch} from "node:fs";
2
+
3
+ import {logger} from "@tsed/di";
4
+ import JsYaml, {type LoadOptions} from "js-yaml";
5
+
6
+ import type {ConfigSource} from "../../interfaces/ConfigSource.js";
7
+
8
+ export interface YamlConfigSourceOptions extends LoadOptions {
9
+ /**
10
+ * The path to the JSON file.
11
+ */
12
+ path: string;
13
+ }
14
+
15
+ export class YamlConfigSource implements ConfigSource<YamlConfigSourceOptions> {
16
+ options: YamlConfigSourceOptions;
17
+
18
+ async getAll() {
19
+ const {path, ...opts} = this.options;
20
+
21
+ // Check if the file exists
22
+ if (!existsSync(path)) {
23
+ logger().warn(`Configuration file not found: ${path}`);
24
+ return {};
25
+ }
26
+
27
+ // Read the file
28
+ return (await JsYaml.load(path, opts)) as Record<string, unknown>;
29
+ }
30
+
31
+ watch(onChange: () => void) {
32
+ const {path} = this.options;
33
+ const watcher = watch(path, onChange);
34
+
35
+ return () => {
36
+ watcher.close();
37
+ };
38
+ }
39
+ }
@@ -0,0 +1,3 @@
1
+ import "../../hooks/afterResolveConfiguration.js";
2
+
3
+ export * from "./YamlConfigSource.js";
@@ -0,0 +1,52 @@
1
+ import {nameOf, type Type} from "@tsed/core";
2
+ import {inject, injectable, ProviderScope} from "@tsed/di";
3
+ import {camelCase} from "change-case";
4
+
5
+ import type {ConfigSource, ConfigSourceOptions} from "../interfaces/ConfigSource.js";
6
+
7
+ export function getConfigSources(configSources: (Type<ConfigSource<any>> | ConfigSourceOptions)[]) {
8
+ return configSources
9
+ .map((configSource, index) => {
10
+ if (!("use" in configSource)) {
11
+ return {
12
+ name: camelCase(nameOf(configSource).replace("ConfigSource", "")),
13
+ use: configSource,
14
+ options: {},
15
+ priority: index,
16
+ enabled: true
17
+ } satisfies ConfigSourceOptions;
18
+ }
19
+
20
+ return {
21
+ enabled: true,
22
+ ...configSource,
23
+ priority: configSource.priority || 0,
24
+ name: configSource.name || camelCase(nameOf(configSource.use).replace("ConfigSource", ""))
25
+ } satisfies ConfigSourceOptions;
26
+ })
27
+ .sort((a, b) => a.priority! - b.priority!)
28
+ .filter(({enabled}) => enabled)
29
+ .reduce((map, options, index) => {
30
+ let {use, name, options: useOpts} = options;
31
+
32
+ injectable(use).scope(ProviderScope.INSTANCE);
33
+
34
+ const instance = inject(use, {
35
+ useOpts
36
+ });
37
+
38
+ instance.options = useOpts;
39
+
40
+ if (map.has(name)) {
41
+ name = `${name}_${index}`;
42
+ }
43
+
44
+ map.set(name, {
45
+ ...options,
46
+ instance,
47
+ data: {}
48
+ });
49
+
50
+ return map;
51
+ }, new Map<string, ConfigSourceOptions & {instance: ConfigSource<any>; data: Record<string, any>}>());
52
+ }
@@ -0,0 +1,7 @@
1
+ export function jsonParse(value: string): any {
2
+ try {
3
+ return JSON.parse(value);
4
+ } catch (e) {
5
+ return value;
6
+ }
7
+ }
@@ -0,0 +1,18 @@
1
+ import {AjvService} from "@tsed/ajv";
2
+ import {inject} from "@tsed/di";
3
+ import {JsonSchema} from "@tsed/schema";
4
+
5
+ export async function validate(configName: string, config: Record<string, unknown>, validationSchema: JsonSchema) {
6
+ const ajv = inject(AjvService);
7
+
8
+ try {
9
+ return await ajv.validate(config, {
10
+ returnsCoercedValues: true,
11
+ schema: validationSchema.toJSON()
12
+ });
13
+ } catch (er) {
14
+ er.message = er.message.replace("Value.", `extends[${configName}].`);
15
+
16
+ throw er;
17
+ }
18
+ }
@@ -0,0 +1,19 @@
1
+ import type {Type} from "@tsed/core";
2
+
3
+ import type {ConfigSource, InitialConfigSourceOptions} from "../interfaces/ConfigSource.js";
4
+
5
+ export function withOptions<Opts>(
6
+ use: Type<ConfigSource<Opts>>,
7
+ {name, priority, validationSchema, enabled, refreshOn, watch, ...opts}: InitialConfigSourceOptions & Opts
8
+ ) {
9
+ return {
10
+ name,
11
+ priority,
12
+ validationSchema,
13
+ use,
14
+ watch,
15
+ refreshOn,
16
+ enabled: enabled !== false,
17
+ options: opts
18
+ };
19
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "extends": "@tsed/typescript/tsconfig.node.json",
3
+ "compilerOptions": {
4
+ "baseUrl": ".",
5
+ "rootDir": "src",
6
+ "outDir": "./lib/esm",
7
+ "declarationDir": "./lib/types",
8
+ "declaration": true,
9
+ "composite": true,
10
+ "noEmit": false
11
+ },
12
+ "include": ["src/**/*.ts", "src/**/*.json"],
13
+ "exclude": [
14
+ "node_modules",
15
+ "test",
16
+ "lib",
17
+ "benchmark",
18
+ "coverage",
19
+ "spec",
20
+ "**/*.benchmark.ts",
21
+ "**/*.spec.ts",
22
+ "keys",
23
+ "**/__mock__/**",
24
+ "webpack.config.js"
25
+ ]
26
+ }
@@ -0,0 +1,21 @@
1
+ // @ts-ignore
2
+ import {presets} from "@tsed/vitest/presets";
3
+ import {defineConfig} from "vitest/config";
4
+
5
+ export default defineConfig(
6
+ {
7
+ ...presets,
8
+ test: {
9
+ ...presets.test,
10
+ coverage: {
11
+ ...presets.test.coverage,
12
+ thresholds: {
13
+ statements: 0,
14
+ branches: 0,
15
+ functions: 0,
16
+ lines: 0
17
+ }
18
+ }
19
+ }
20
+ }
21
+ );