add transformers to file helpers (#2922)

* fix undefined handling in INI

* beta.14

* Partial -> DeepPartial in action request

* boolean laziness kills

* beta.16

* misc fixes

* file transformers

* infer validator source argument

* simplify validator

* readd toml

* beta.17

* filter undefined instead of parse/stringify

* handle arrays of objects in filterUndefined
This commit is contained in:
Aiden McClelland
2025-05-06 11:04:11 -06:00
committed by GitHub
parent 97e4d036dc
commit 68955c29cb
10 changed files with 139 additions and 41 deletions

View File

@@ -69,14 +69,14 @@ export class Daemon<Manifest extends T.SDKManifest> extends Drop {
.catch((err) => console.error(err))
this.commandController = await this.startCommand()
if (
this.oneshot &&
(await this.commandController.wait({ keepSubcontainer: true }).then(
(_) => true,
(err) => {
console.error(err)
return false
},
))
)) &&
this.oneshot
) {
for (const fn of this.onExitSuccessFns) {
try {

View File

@@ -61,6 +61,28 @@ function fileMerge(...args: any[]): any {
return res
}
function filterUndefined<A>(a: A): A {
if (a && typeof a === "object") {
if (Array.isArray(a)) {
return a.map(filterUndefined) as A
}
return Object.entries(a).reduce<Record<string, any>>((acc, [k, v]) => {
if (v !== undefined) {
acc[k] = filterUndefined(v)
}
return acc
}, {}) as A
}
return a
}
export type Transformers<Raw = unknown, Transformed = unknown> = {
onRead: (value: Raw) => Transformed
onWrite: (value: Transformed) => Raw
}
type Validator<T, U> = matches.Validator<T, U> | matches.Validator<unknown, U>
/**
* @description Use this class to read/write an underlying configuration file belonging to the upstream service.
*
@@ -287,30 +309,40 @@ export class FileHelper<A> {
return new FileHelper<A>(path, toFile, fromFile, validate)
}
/**
* Create a File Helper for a .json file.
*/
static json<A>(path: string, shape: matches.Validator<unknown, A>) {
private static rawTransformed<A extends Transformed, Raw, Transformed>(
path: string,
toFile: (dataIn: Raw) => string,
fromFile: (rawData: string) => Raw,
validate: (data: Transformed) => A,
transformers: Transformers<Raw, Transformed> | undefined,
) {
return new FileHelper<A>(
path,
(inData) => JSON.stringify(inData, null, 2),
(inString) => JSON.parse(inString),
(data) => shape.unsafeCast(data),
(inData) => {
if (transformers) {
return toFile(transformers.onWrite(inData))
}
return toFile(inData as any as Raw)
},
fromFile,
validate as (a: unknown) => A,
)
}
/**
* Create a File Helper for a .toml file
* Create a File Helper for a .json file.
*/
static toml<A extends Record<string, unknown>>(
static json<A>(
path: string,
shape: matches.Validator<unknown, A>,
shape: Validator<unknown, A>,
transformers?: Transformers,
) {
return new FileHelper<A>(
return FileHelper.rawTransformed(
path,
(inData) => TOML.stringify(inData as any),
(inString) => TOML.parse(inString),
(inData) => JSON.stringify(inData, null, 2),
(inString) => JSON.parse(inString),
(data) => shape.unsafeCast(data),
transformers,
)
}
@@ -319,34 +351,94 @@ export class FileHelper<A> {
*/
static yaml<A extends Record<string, unknown>>(
path: string,
shape: matches.Validator<unknown, A>,
shape: Validator<Record<string, unknown>, A>,
): FileHelper<A>
static yaml<A extends Transformed, Transformed = Record<string, unknown>>(
path: string,
shape: Validator<Transformed, A>,
transformers: Transformers<Record<string, unknown>, Transformed>,
): FileHelper<A>
static yaml<A extends Transformed, Transformed = Record<string, unknown>>(
path: string,
shape: Validator<Transformed, A>,
transformers?: Transformers<Record<string, unknown>, Transformed>,
) {
return new FileHelper<A>(
return FileHelper.rawTransformed<A, Record<string, unknown>, Transformed>(
path,
(inData) => YAML.stringify(inData, null, 2),
(inString) => YAML.parse(inString),
(data) => shape.unsafeCast(data),
transformers,
)
}
/**
* Create a File Helper for a .toml file
*/
static toml<A extends TOML.JsonMap>(
path: string,
shape: Validator<TOML.JsonMap, A>,
): FileHelper<A>
static toml<A extends Transformed, Transformed = TOML.JsonMap>(
path: string,
shape: Validator<Transformed, A>,
transformers: Transformers<TOML.JsonMap, Transformed>,
): FileHelper<A>
static toml<A extends Transformed, Transformed = TOML.JsonMap>(
path: string,
shape: Validator<Transformed, A>,
transformers?: Transformers<TOML.JsonMap, Transformed>,
) {
return FileHelper.rawTransformed<A, TOML.JsonMap, Transformed>(
path,
(inData) => TOML.stringify(inData),
(inString) => TOML.parse(inString),
(data) => shape.unsafeCast(data),
transformers,
)
}
static ini<A extends Record<string, unknown>>(
path: string,
shape: matches.Validator<unknown, A>,
shape: Validator<Record<string, unknown>, A>,
options?: INI.EncodeOptions & INI.DecodeOptions,
) {
return new FileHelper<A>(
): FileHelper<A>
static ini<A extends Transformed, Transformed = Record<string, unknown>>(
path: string,
shape: Validator<Transformed, A>,
options: INI.EncodeOptions & INI.DecodeOptions,
transformers: Transformers<Record<string, unknown>, Transformed>,
): FileHelper<A>
static ini<A extends Transformed, Transformed = Record<string, unknown>>(
path: string,
shape: Validator<Transformed, A>,
options?: INI.EncodeOptions & INI.DecodeOptions,
transformers?: Transformers<Record<string, unknown>, Transformed>,
): FileHelper<A> {
return FileHelper.rawTransformed<A, Record<string, unknown>, Transformed>(
path,
(inData) => INI.stringify(inData, options),
(inData) => INI.stringify(filterUndefined(inData), options),
(inString) => INI.parse(inString, options),
(data) => shape.unsafeCast(data),
transformers,
)
}
static env<A extends Record<string, string>>(
path: string,
shape: matches.Validator<unknown, A>,
shape: Validator<Record<string, string>, A>,
): FileHelper<A>
static env<A extends Transformed, Transformed = Record<string, string>>(
path: string,
shape: Validator<Transformed, A>,
transformers: Transformers<Record<string, string>, Transformed>,
): FileHelper<A>
static env<A extends Transformed, Transformed = Record<string, string>>(
path: string,
shape: Validator<Transformed, A>,
transformers?: Transformers<Record<string, string>, Transformed>,
) {
return new FileHelper<A>(
return FileHelper.rawTransformed<A, Record<string, string>, Transformed>(
path,
(inData) =>
Object.entries(inData)
@@ -361,6 +453,7 @@ export class FileHelper<A> {
.map((line) => line.split("=", 2)),
),
(data) => shape.unsafeCast(data),
transformers,
)
}
}