mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
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:
2
container-runtime/package-lock.json
generated
2
container-runtime/package-lock.json
generated
@@ -37,7 +37,7 @@
|
||||
},
|
||||
"../sdk/dist": {
|
||||
"name": "@start9labs/start-sdk",
|
||||
"version": "0.4.0-beta.10",
|
||||
"version": "0.4.0-beta.17",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@iarna/toml": "^3.0.0",
|
||||
|
||||
@@ -265,6 +265,7 @@ export function makeEffects(context: EffectContext): Effects {
|
||||
return rpcRound("mount", options) as ReturnType<T.Effects["mount"]>
|
||||
},
|
||||
restart(...[]: Parameters<T.Effects["restart"]>) {
|
||||
console.log("Restarting service...")
|
||||
return rpcRound("restart", {}) as ReturnType<T.Effects["restart"]>
|
||||
},
|
||||
setDependencies(
|
||||
|
||||
@@ -289,7 +289,8 @@ export class RpcListener {
|
||||
return null
|
||||
})
|
||||
.when(startType, async ({ id }) => {
|
||||
const callbacks = this.callbacks?.child("main")
|
||||
const callbacks =
|
||||
this.callbacks?.getChild("main") || this.callbacks?.child("main")
|
||||
const effects = makeEffects({
|
||||
procedureId: null,
|
||||
callbacks,
|
||||
|
||||
@@ -35,10 +35,15 @@ export class CallbackHolder {
|
||||
}
|
||||
child(name: string): CallbackHolder {
|
||||
this.removeChild(name)
|
||||
const child = new CallbackHolder()
|
||||
const child = new CallbackHolder(this.effects)
|
||||
this.children.set(name, child)
|
||||
return child
|
||||
}
|
||||
|
||||
getChild(name: string): CallbackHolder | null {
|
||||
return this.children.get(name) || null
|
||||
}
|
||||
|
||||
removeChild(name: string) {
|
||||
const child = this.children.get(name)
|
||||
if (child) {
|
||||
@@ -60,7 +65,9 @@ export class CallbackHolder {
|
||||
callCallback(index: number, args: any[]): Promise<unknown> {
|
||||
const callback = this.getCallback(index)
|
||||
if (!callback) return Promise.resolve()
|
||||
return Promise.resolve().then(() => callback(...args))
|
||||
return Promise.resolve()
|
||||
.then(() => callback(...args))
|
||||
.catch((e) => console.error("callback failed", e))
|
||||
}
|
||||
onLeaveContext(fn: Function) {
|
||||
this.onLeaveContextCallbacks.push(fn)
|
||||
|
||||
@@ -147,7 +147,7 @@ impl Handler<RunAction> for ServiceActor {
|
||||
.into_idx(&action_id)
|
||||
.or_not_found(lazy_format!("{package_id} action {action_id}"))?
|
||||
.de()?;
|
||||
if !matches!(&action.visibility, ActionVisibility::Enabled) {
|
||||
if matches!(&action.visibility, ActionVisibility::Disabled(_)) {
|
||||
return Err(Error::new(
|
||||
eyre!("action {action_id} is disabled"),
|
||||
ErrorKind::Action,
|
||||
|
||||
@@ -54,7 +54,7 @@ type ActionRequestBase = {
|
||||
}
|
||||
type ActionRequestInput<T extends Action<T.ActionId, any, any>> = {
|
||||
kind: "partial"
|
||||
value: Partial<GetActionInputType<T>>
|
||||
value: T.DeepPartial<GetActionInputType<T>>
|
||||
}
|
||||
export type ActionRequestOptions<T extends Action<T.ActionId, any, any>> =
|
||||
ActionRequestBase &
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
15
sdk/package/package-lock.json
generated
15
sdk/package/package-lock.json
generated
@@ -1,17 +1,18 @@
|
||||
{
|
||||
"name": "@start9labs/start-sdk",
|
||||
"version": "0.4.0-beta.13",
|
||||
"version": "0.4.0-beta.17",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@start9labs/start-sdk",
|
||||
"version": "0.4.0-beta.13",
|
||||
"version": "0.4.0-beta.17",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@iarna/toml": "^3.0.0",
|
||||
"@noble/curves": "^1.8.2",
|
||||
"@noble/hashes": "^1.7.2",
|
||||
"@types/ini": "^4.1.1",
|
||||
"deep-equality-data-structures": "^2.0.0",
|
||||
"ini": "^5.0.0",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
@@ -20,8 +21,6 @@
|
||||
"yaml": "^2.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@types/ini": "^4.1.1",
|
||||
"@types/jest": "^29.4.0",
|
||||
"copyfiles": "^2.4.1",
|
||||
"jest": "^29.4.3",
|
||||
@@ -987,10 +986,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@iarna/toml": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz",
|
||||
"integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==",
|
||||
"dev": true,
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-3.0.0.tgz",
|
||||
"integrity": "sha512-td6ZUkz2oS3VeleBcN+m//Q6HlCFCPrnI0FZhrt/h4XqLEdOyYp2u21nd8MdsR+WJy5r9PTDaHTDDfhf4H4l6Q==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@istanbuljs/load-nyc-config": {
|
||||
@@ -1599,7 +1597,6 @@
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/ini/-/ini-4.1.1.tgz",
|
||||
"integrity": "sha512-MIyNUZipBTbyUNnhvuXJTY7B6qNI78meck9Jbv3wk0OgNwRyOOVEKDutAkOs1snB/tx0FafyR6/SN4Ps0hZPeg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/istanbul-lib-coverage": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@start9labs/start-sdk",
|
||||
"version": "0.4.0-beta.13",
|
||||
"version": "0.4.0-beta.17",
|
||||
"description": "Software development kit to facilitate packaging services for StartOS",
|
||||
"main": "./package/lib/index.js",
|
||||
"types": "./package/lib/index.d.ts",
|
||||
@@ -37,6 +37,7 @@
|
||||
"yaml": "^2.7.1",
|
||||
"deep-equality-data-structures": "^2.0.0",
|
||||
"ini": "^5.0.0",
|
||||
"@types/ini": "^4.1.1",
|
||||
"@iarna/toml": "^3.0.0",
|
||||
"@noble/curves": "^1.8.2",
|
||||
"@noble/hashes": "^1.7.2"
|
||||
@@ -48,9 +49,7 @@
|
||||
"singleQuote": false
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@types/jest": "^29.4.0",
|
||||
"@types/ini": "^4.1.1",
|
||||
"copyfiles": "^2.4.1",
|
||||
"jest": "^29.4.3",
|
||||
"peggy": "^3.0.2",
|
||||
|
||||
Reference in New Issue
Block a user