implement config

This commit is contained in:
2025-09-11 17:13:10 +02:00
parent ec81ff7b01
commit 7bed7fe9a7
5 changed files with 86 additions and 12 deletions
+2
View File
@@ -0,0 +1,2 @@
[test]
preload = "./test/init.ts"
+4 -4
View File
@@ -6,9 +6,9 @@
"test": "bun test",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"log": "git+https://git.pband.ch/typescript/log.git"
"log": "git+https://git.pband.ch/typescript/log.git",
"yup": "^1.7.0"
},
"devDependencies": {
"@eslint/js": "^9.35.0",
@@ -17,15 +17,15 @@
"eslint": "^9.35.0",
"globals": "^16.4.0",
"jiti": "^2.5.1",
"logger-console": "git+https://git.pband.ch/typescript/logger-console.git",
"prettier": "^3.6.2",
"tmp": "^0.2.5",
"typescript": "^5.9.2",
"typescript-eslint": "^8.43.0"
},
"name": "config",
"description": "Parse configuration from env or files",
"version": "1.0.0",
"author": "Pascal Perrenoud <pascal@pband.ch>",
"type": "module",
"main": "./src/index.ts",
+1 -1
View File
@@ -1 +1 @@
export type Ok<T> = {ok: true, data: T} | {ok: false}
export type Ok<T> = {ok: true, data: T} | {ok?: false}
+17 -7
View File
@@ -1,17 +1,27 @@
import logger from 'log'
import * as yup from 'yup'
import {Ok} from './helpers'
import * as parsing from './parsing'
import type {Ok} from './helpers'
// TODO : re-export types used to describe schema
export * as yup from 'yup'
const log = logger('config')
export async function parse<S>(schema: S): Promise<Ok<unknown>> {
log.info("Parse configuration from env")
export async function parse<S extends yup.Maybe<yup.AnyObject>>(schema: yup.ObjectSchema<S>): Promise<Ok<S>> {
log.info("Parse from env")
// TODO : Read config from env
log.trace("Start parsing")
const config = await parsing.object(schema)
if (!config.ok) return config
// TODO : maybe double check config
log.trace("double-check")
const res = await schema.isValid(config.data, {strict: true})
if (!res) {
log.error("Double-check failed")
log.debug('Config', config.data)
return {ok: false}
}
return {ok: false}
return config
}
+62
View File
@@ -0,0 +1,62 @@
import * as yup from 'yup'
import logger from 'log'
import {read_env} from './env'
import type {Ok} from './helpers'
const log = logger('config:parsing')
export async function object<S extends yup.Maybe<yup.AnyObject>>(schema: yup.ObjectSchema<S>, base_name: string = ""): Promise<Ok<S>> {
log.debug("Object")
if (base_name.length !== 0) base_name = base_name + "_"
// @ts-expect-error Ugly hack with type S
const data: S = {}
for (const key in schema.fields) {
const sub_key = base_name + key.toUpperCase()
const sub_scheme = schema.fields[key]
let value: Ok<unknown>
// TODO : If array, add a transform
if (sub_scheme.describe().type === 'object') {
value = await object(sub_scheme, sub_key)
} else {
value = await generic(sub_scheme, sub_key)
}
if (!value.ok) return value
if (value.data === undefined) continue
data[key] = value.data
}
return {ok: true, data}
}
export async function generic<S>(scheme: yup.AnySchema<S>, key: string): Promise<Ok<S | undefined>> {
log.debug('Generic', scheme.describe().type)
const value = await read_env(key)
if (!value.ok) return value
if (value.data === undefined) {
const def = scheme.getDefault()
if (def !== undefined) return {ok: true, data: def}
else if (scheme.spec.optional) return {ok: true, data: undefined}
log.warn('Missing value for', key)
return {}
}
if (!(await scheme.isValid(value.data))) {
log.warn('Invaid value for', key)
log.debug('Value:', value.data)
return {}
}
const res = scheme.cast(value.data)
return {ok: true, data: res}
}