aboutsummaryrefslogtreecommitdiff
path: root/deno/base/config.ts
diff options
context:
space:
mode:
authorYuqian Yang <crupest@crupest.life>2025-06-05 22:30:51 +0800
committerYuqian Yang <crupest@crupest.life>2025-06-09 21:48:00 +0800
commit3bdca0b90cf8bf5dfd6ff1ab482d857abb4acd2d (patch)
tree42fd1bf1f0119910c09542fbf475c012404658fd /deno/base/config.ts
parent543fc733da074751e1750603df6931089efab465 (diff)
downloadcrupest-3bdca0b90cf8bf5dfd6ff1ab482d857abb4acd2d.tar.gz
crupest-3bdca0b90cf8bf5dfd6ff1ab482d857abb4acd2d.tar.bz2
crupest-3bdca0b90cf8bf5dfd6ff1ab482d857abb4acd2d.zip
feat(deno): move deno (mail-server) to top level.
Diffstat (limited to 'deno/base/config.ts')
-rw-r--r--deno/base/config.ts94
1 files changed, 94 insertions, 0 deletions
diff --git a/deno/base/config.ts b/deno/base/config.ts
new file mode 100644
index 0000000..98722f6
--- /dev/null
+++ b/deno/base/config.ts
@@ -0,0 +1,94 @@
+import { camelCaseToKebabCase } from "./text.ts";
+
+export interface ConfigDefinitionItem {
+ readonly description: string;
+ readonly default?: string;
+ readonly secret?: boolean;
+}
+
+interface ConfigMapItem extends ConfigDefinitionItem {
+ readonly env: string;
+ value?: string;
+}
+
+export type ConfigDefinition<K extends string = string> = Record<
+ K,
+ ConfigDefinitionItem
+>;
+type ConfigMap<K extends string = string> = Record<K, ConfigMapItem>;
+
+export class ConfigProvider<K extends string> {
+ readonly #prefix: string;
+ readonly #map: ConfigMap<K>;
+
+ constructor(prefix: string, ...definitions: Partial<ConfigDefinition<K>>[]) {
+ this.#prefix = prefix;
+
+ const map: ConfigMap = {};
+ for (const definition of definitions) {
+ for (const [key, def] of Object.entries(definition as ConfigDefinition)) {
+ map[key] = {
+ ...def,
+ env: `${this.#prefix}-${camelCaseToKebabCase(key as string)}`
+ .replaceAll("-", "_")
+ .toUpperCase(),
+ };
+ }
+ }
+ this.#map = map as ConfigMap<K>;
+ }
+
+ resolveFromEnv(options?: { keys?: K[] }) {
+ const keys = options?.keys ?? Object.keys(this.#map);
+ for (const key of keys) {
+ const { env, description, default: _default } = this.#map[key as K];
+ const value = Deno.env.get(env) ?? _default;
+ if (value == null) {
+ throw new Error(`Required env ${env} (${description}) is not set.`);
+ }
+ this.#map[key as K].value = value;
+ }
+ }
+
+ get(key: K): string {
+ if (!(key in this.#map)) {
+ throw new Error(`Unknown config key ${key as string}.`);
+ }
+ if (this.#map[key].value == null) {
+ this.resolveFromEnv({ keys: [key] });
+ }
+ return this.#map[key].value!;
+ }
+
+ set(key: K, value: string) {
+ if (!(key in this.#map)) {
+ throw new Error(`Unknown config key ${key as string}.`);
+ }
+ this.#map[key].value = value;
+ }
+
+ getInt(key: K): number {
+ return Number(this.get(key));
+ }
+
+ getList(key: K, separator: string = ","): string[] {
+ const value = this.get(key);
+ if (value.length === 0) return [];
+ return value.split(separator);
+ }
+
+ [Symbol.for("Deno.customInspect")]() {
+ const getValueString = (item: ConfigMapItem): string => {
+ if (item.value == null) return "(unresolved)";
+ if (item.secret === true) return "***";
+ return item.value;
+ };
+
+ return Object.entries(this.#map as ConfigMap)
+ .map(
+ ([key, item]) => `${key} [env: ${item.env}]: ${getValueString(item)}`,
+ )
+ .join("\n");
+ }
+}
+