mirror of
https://github.com/Start9Labs/start-sdk.git
synced 2026-03-26 02:11:56 +00:00
chore: Update the lazy config
This commit is contained in:
@@ -1,37 +1,38 @@
|
|||||||
import { Parser } from "ts-matches"
|
|
||||||
import { Config } from "../config/builder"
|
import { Config } from "../config/builder"
|
||||||
import { ActionMetaData, ActionResult, Effects, ExportedAction } from "../types"
|
import { ActionMetaData, ActionResult, Effects, ExportedAction } from "../types"
|
||||||
import { Utils, once, utils } from "../util"
|
import { Utils, utils } from "../util"
|
||||||
import { TypeFromProps } from "../util/propertiesMatcher"
|
|
||||||
import { InputSpec } from "../config/configTypes"
|
|
||||||
|
|
||||||
export class CreatedAction<WrapperData, Input extends Config<InputSpec>> {
|
export class CreatedAction<WrapperData, Type extends Record<string, any>> {
|
||||||
private constructor(
|
private constructor(
|
||||||
private myMetaData: Omit<ActionMetaData, "input"> & { input: Input },
|
private myMetaData: Omit<ActionMetaData, "input"> & {
|
||||||
|
input: Config<Type, WrapperData, never>
|
||||||
|
},
|
||||||
readonly fn: (options: {
|
readonly fn: (options: {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
utils: Utils<WrapperData>
|
utils: Utils<WrapperData>
|
||||||
input: TypeFromProps<Input>
|
input: Type
|
||||||
}) => Promise<ActionResult>,
|
}) => Promise<ActionResult>,
|
||||||
) {}
|
) {}
|
||||||
private validator = this.myMetaData.input.validator() as Parser<
|
private validator = this.myMetaData.input.validator
|
||||||
unknown,
|
|
||||||
TypeFromProps<Input>
|
|
||||||
>
|
|
||||||
metaData = {
|
|
||||||
...this.myMetaData,
|
|
||||||
input: this.myMetaData.input.build(),
|
|
||||||
}
|
|
||||||
|
|
||||||
static of<WrapperData, Input extends Config<InputSpec>>(
|
static of<
|
||||||
metaData: Omit<ActionMetaData, "input"> & { input: Input },
|
WrapperData,
|
||||||
|
Input extends Config<Type, WrapperData, never>,
|
||||||
|
Type extends Record<string, any> = (Input extends Config<any, infer B, any>
|
||||||
|
? B
|
||||||
|
: never) &
|
||||||
|
Record<string, any>,
|
||||||
|
>(
|
||||||
|
metaData: Omit<ActionMetaData, "input"> & {
|
||||||
|
input: Config<Type, WrapperData, never>
|
||||||
|
},
|
||||||
fn: (options: {
|
fn: (options: {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
utils: Utils<WrapperData>
|
utils: Utils<WrapperData>
|
||||||
input: TypeFromProps<Input>
|
input: Type
|
||||||
}) => Promise<ActionResult>,
|
}) => Promise<ActionResult>,
|
||||||
) {
|
) {
|
||||||
return new CreatedAction<WrapperData, Input>(metaData, fn)
|
return new CreatedAction<WrapperData, Type>(metaData, fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
exportedAction: ExportedAction = ({ effects, input }) => {
|
exportedAction: ExportedAction = ({ effects, input }) => {
|
||||||
@@ -43,7 +44,16 @@ export class CreatedAction<WrapperData, Input extends Config<InputSpec>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async exportAction(effects: Effects) {
|
async exportAction(effects: Effects) {
|
||||||
await effects.exportAction(this.metaData)
|
const myUtils = utils<WrapperData>(effects)
|
||||||
|
const metaData = {
|
||||||
|
...this.myMetaData,
|
||||||
|
input: await this.myMetaData.input.build({
|
||||||
|
effects,
|
||||||
|
utils: myUtils,
|
||||||
|
config: null,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
await effects.exportAction(metaData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
import { _ } from "../../util"
|
|
||||||
export class IBuilder<A> {
|
|
||||||
protected constructor(readonly a: A) {}
|
|
||||||
|
|
||||||
public build(): A {
|
|
||||||
return this.a
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type BuilderExtract<A> = A extends IBuilder<infer B> ? B : never
|
|
||||||
@@ -1,9 +1,20 @@
|
|||||||
import { InputSpec, ValueSpec } from "../configTypes"
|
import { ValueSpec } from "../configTypes"
|
||||||
import { typeFromProps } from "../../util"
|
import { Utils } from "../../util"
|
||||||
import { BuilderExtract, IBuilder } from "./builder"
|
|
||||||
import { Value } from "./value"
|
import { Value } from "./value"
|
||||||
import { _ } from "../../util"
|
import { _ } from "../../util"
|
||||||
|
import { Effects } from "../../types"
|
||||||
|
import { Parser, object } from "ts-matches"
|
||||||
|
|
||||||
|
export type LazyBuildOptions<Manifest, ConfigType> = {
|
||||||
|
effects: Effects
|
||||||
|
utils: Utils<Manifest>
|
||||||
|
config: ConfigType | null
|
||||||
|
}
|
||||||
|
export type LazyBuild<Manifest, ConfigType, ExpectedOut> = (
|
||||||
|
options: LazyBuildOptions<Manifest, ConfigType>,
|
||||||
|
) => Promise<ExpectedOut> | ExpectedOut
|
||||||
|
|
||||||
|
export type MaybeLazyValues<A> = LazyBuild<any, any, A> | A
|
||||||
/**
|
/**
|
||||||
* Configs are the specs that are used by the os configuration form for this service.
|
* Configs are the specs that are used by the os configuration form for this service.
|
||||||
* Here is an example of a simple configuration
|
* Here is an example of a simple configuration
|
||||||
@@ -60,44 +71,33 @@ export const addNodesSpec = Config.of({ hostname: hostname, port: port });
|
|||||||
|
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
export class Config<A extends InputSpec> extends IBuilder<A> {
|
export class Config<Type extends Record<string, any>, WD, ConfigType> {
|
||||||
static empty() {
|
private constructor(
|
||||||
return new Config({})
|
private readonly spec: {
|
||||||
}
|
[K in keyof Type]: Value<Type[K], WD, ConfigType>
|
||||||
static withValue<K extends string, B extends ValueSpec>(
|
},
|
||||||
key: K,
|
public validator: Parser<unknown, Type>,
|
||||||
value: Value<B>,
|
) {}
|
||||||
) {
|
async build(options: LazyBuildOptions<WD, ConfigType>) {
|
||||||
return Config.empty().withValue(key, value)
|
const answer = {} as {
|
||||||
}
|
[K in keyof Type]: ValueSpec
|
||||||
static addValue<K extends string, B extends ValueSpec>(
|
|
||||||
key: K,
|
|
||||||
value: Value<B>,
|
|
||||||
) {
|
|
||||||
return Config.empty().withValue(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
static of<B extends { [key: string]: Value<ValueSpec> }>(spec: B) {
|
|
||||||
const answer: { [K in keyof B]: BuilderExtract<B[K]> } = {} as any
|
|
||||||
for (const key in spec) {
|
|
||||||
answer[key] = spec[key].build() as any
|
|
||||||
}
|
}
|
||||||
return new Config(answer)
|
for (const k in this.spec) {
|
||||||
}
|
answer[k] = await this.spec[k].build(options)
|
||||||
withValue<K extends string, B extends ValueSpec>(key: K, value: Value<B>) {
|
}
|
||||||
return new Config({
|
return answer
|
||||||
...this.a,
|
|
||||||
[key]: value.build(),
|
|
||||||
} as A & { [key in K]: B })
|
|
||||||
}
|
|
||||||
addValue<K extends string, B extends ValueSpec>(key: K, value: Value<B>) {
|
|
||||||
return new Config({
|
|
||||||
...this.a,
|
|
||||||
[key]: value.build(),
|
|
||||||
} as A & { [key in K]: B })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public validator() {
|
static of<Type extends Record<string, any>, Manifest, ConfigType>(spec: {
|
||||||
return typeFromProps(this.a)
|
[K in keyof Type]: Value<Type[K], Manifest, ConfigType>
|
||||||
|
}) {
|
||||||
|
const validatorObj = {} as {
|
||||||
|
[K in keyof Type]: Parser<unknown, Type[K]>
|
||||||
|
}
|
||||||
|
for (const key in spec) {
|
||||||
|
validatorObj[key] = spec[key].validator
|
||||||
|
}
|
||||||
|
const validator = object(validatorObj)
|
||||||
|
return new Config<Type, Manifest, ConfigType>(spec, validator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import { BuilderExtract, IBuilder } from "./builder"
|
import { Config, LazyBuild } from "./config"
|
||||||
import { Config } from "./config"
|
|
||||||
import {
|
import {
|
||||||
InputSpec,
|
|
||||||
ListValueSpecText,
|
ListValueSpecText,
|
||||||
Pattern,
|
Pattern,
|
||||||
UniqueBy,
|
UniqueBy,
|
||||||
ValueSpecList,
|
ValueSpecList,
|
||||||
} from "../configTypes"
|
} from "../configTypes"
|
||||||
import { guardAll } from "../../util"
|
import { Parser, arrayOf, number, string } from "ts-matches"
|
||||||
/**
|
/**
|
||||||
* Used as a subtype of Value.list
|
* Used as a subtype of Value.list
|
||||||
```ts
|
```ts
|
||||||
@@ -21,8 +19,12 @@ export const authorizationList = List.string({
|
|||||||
export const auth = Value.list(authorizationList);
|
export const auth = Value.list(authorizationList);
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
export class List<A extends ValueSpecList> extends IBuilder<A> {
|
export class List<Type, WD, ConfigType> {
|
||||||
static text(
|
private constructor(
|
||||||
|
public build: LazyBuild<WD, ConfigType, ValueSpecList>,
|
||||||
|
public validator: Parser<unknown, Type>,
|
||||||
|
) {}
|
||||||
|
static text<WD, CT>(
|
||||||
a: {
|
a: {
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
@@ -43,27 +45,29 @@ export class List<A extends ValueSpecList> extends IBuilder<A> {
|
|||||||
inputmode?: ListValueSpecText["inputmode"]
|
inputmode?: ListValueSpecText["inputmode"]
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const spec = {
|
return new List<string[], WD, CT>(() => {
|
||||||
type: "text" as const,
|
const spec = {
|
||||||
placeholder: null,
|
type: "text" as const,
|
||||||
minLength: null,
|
placeholder: null,
|
||||||
maxLength: null,
|
minLength: null,
|
||||||
masked: false,
|
maxLength: null,
|
||||||
inputmode: "text" as const,
|
masked: false,
|
||||||
...aSpec,
|
inputmode: "text" as const,
|
||||||
}
|
...aSpec,
|
||||||
return new List({
|
}
|
||||||
description: null,
|
return {
|
||||||
warning: null,
|
description: null,
|
||||||
default: [],
|
warning: null,
|
||||||
type: "list" as const,
|
default: [],
|
||||||
minLength: null,
|
type: "list" as const,
|
||||||
maxLength: null,
|
minLength: null,
|
||||||
...a,
|
maxLength: null,
|
||||||
spec,
|
...a,
|
||||||
})
|
spec,
|
||||||
|
}
|
||||||
|
}, arrayOf(string))
|
||||||
}
|
}
|
||||||
static number(
|
static number<WD, CT>(
|
||||||
a: {
|
a: {
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
@@ -82,27 +86,29 @@ export class List<A extends ValueSpecList> extends IBuilder<A> {
|
|||||||
placeholder?: string | null
|
placeholder?: string | null
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const spec = {
|
return new List<number[], WD, CT>(() => {
|
||||||
type: "number" as const,
|
const spec = {
|
||||||
placeholder: null,
|
type: "number" as const,
|
||||||
min: null,
|
placeholder: null,
|
||||||
max: null,
|
min: null,
|
||||||
step: null,
|
max: null,
|
||||||
units: null,
|
step: null,
|
||||||
...aSpec,
|
units: null,
|
||||||
}
|
...aSpec,
|
||||||
return new List({
|
}
|
||||||
description: null,
|
return {
|
||||||
warning: null,
|
description: null,
|
||||||
minLength: null,
|
warning: null,
|
||||||
maxLength: null,
|
minLength: null,
|
||||||
default: [],
|
maxLength: null,
|
||||||
type: "list" as const,
|
default: [],
|
||||||
...a,
|
type: "list" as const,
|
||||||
spec,
|
...a,
|
||||||
})
|
spec,
|
||||||
|
}
|
||||||
|
}, arrayOf(number))
|
||||||
}
|
}
|
||||||
static obj<Spec extends Config<InputSpec>>(
|
static obj<Type extends Record<string, any>, WrapperData, ConfigType>(
|
||||||
a: {
|
a: {
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
@@ -113,36 +119,34 @@ export class List<A extends ValueSpecList> extends IBuilder<A> {
|
|||||||
maxLength?: number | null
|
maxLength?: number | null
|
||||||
},
|
},
|
||||||
aSpec: {
|
aSpec: {
|
||||||
spec: Spec
|
spec: Config<Type, WrapperData, ConfigType>
|
||||||
displayAs?: null | string
|
displayAs?: null | string
|
||||||
uniqueBy?: null | UniqueBy
|
uniqueBy?: null | UniqueBy
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const { spec: previousSpecSpec, ...restSpec } = aSpec
|
return new List<Type[], WrapperData, ConfigType>(async (options) => {
|
||||||
const specSpec = previousSpecSpec.build() as BuilderExtract<Spec>
|
const { spec: previousSpecSpec, ...restSpec } = aSpec
|
||||||
const spec = {
|
const specSpec = await previousSpecSpec.build(options)
|
||||||
type: "object" as const,
|
const spec = {
|
||||||
displayAs: null,
|
type: "object" as const,
|
||||||
uniqueBy: null,
|
displayAs: null,
|
||||||
...restSpec,
|
uniqueBy: null,
|
||||||
spec: specSpec,
|
...restSpec,
|
||||||
}
|
spec: specSpec,
|
||||||
const value = {
|
}
|
||||||
spec,
|
const value = {
|
||||||
default: [],
|
spec,
|
||||||
...a,
|
default: [],
|
||||||
}
|
...a,
|
||||||
return new List({
|
}
|
||||||
description: null,
|
return {
|
||||||
warning: null,
|
description: null,
|
||||||
minLength: null,
|
warning: null,
|
||||||
maxLength: null,
|
minLength: null,
|
||||||
type: "list" as const,
|
maxLength: null,
|
||||||
...value,
|
type: "list" as const,
|
||||||
})
|
...value,
|
||||||
}
|
}
|
||||||
|
}, arrayOf(aSpec.spec.validator))
|
||||||
public validator() {
|
|
||||||
return guardAll(this.a)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,28 @@
|
|||||||
import { BuilderExtract, IBuilder } from "./builder"
|
import { Config, LazyBuild } from "./config"
|
||||||
import { Config } from "./config"
|
|
||||||
import { List } from "./list"
|
import { List } from "./list"
|
||||||
import { Variants } from "./variants"
|
import { Variants } from "./variants"
|
||||||
import {
|
import {
|
||||||
InputSpec,
|
|
||||||
Pattern,
|
Pattern,
|
||||||
ValueSpec,
|
ValueSpec,
|
||||||
ValueSpecColor,
|
|
||||||
ValueSpecDatetime,
|
ValueSpecDatetime,
|
||||||
ValueSpecList,
|
|
||||||
ValueSpecNumber,
|
|
||||||
ValueSpecSelect,
|
|
||||||
ValueSpecText,
|
ValueSpecText,
|
||||||
ValueSpecTextarea,
|
ValueSpecTextarea,
|
||||||
} from "../configTypes"
|
} from "../configTypes"
|
||||||
import { guardAll } from "../../util"
|
import { once } from "../../util"
|
||||||
import { DefaultString } from "../configTypes"
|
import { DefaultString } from "../configTypes"
|
||||||
import { _ } from "../../util"
|
import { _ } from "../../util"
|
||||||
|
import {
|
||||||
|
Parser,
|
||||||
|
anyOf,
|
||||||
|
arrayOf,
|
||||||
|
boolean,
|
||||||
|
literal,
|
||||||
|
literals,
|
||||||
|
number,
|
||||||
|
object,
|
||||||
|
string,
|
||||||
|
unknown,
|
||||||
|
} from "ts-matches"
|
||||||
|
|
||||||
type RequiredDefault<A> =
|
type RequiredDefault<A> =
|
||||||
| false
|
| false
|
||||||
@@ -40,6 +46,30 @@ function requiredLikeToAbove<Input extends RequiredDefault<A>, A>(
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
type AsRequired<Type, MaybeRequiredType> = MaybeRequiredType extends
|
||||||
|
| { default: unknown }
|
||||||
|
| never
|
||||||
|
? Type
|
||||||
|
: Type | null | undefined
|
||||||
|
|
||||||
|
type InputAsRequired<A, Type> = A extends
|
||||||
|
| { required: { default: any } | never }
|
||||||
|
| never
|
||||||
|
? Type
|
||||||
|
: Type | null | undefined
|
||||||
|
const testForAsRequiredParser = once(
|
||||||
|
() => object({ required: object({ default: unknown }) }).test,
|
||||||
|
)
|
||||||
|
function asRequiredParser<
|
||||||
|
Type,
|
||||||
|
Input,
|
||||||
|
Return extends
|
||||||
|
| Parser<unknown, Type>
|
||||||
|
| Parser<unknown, Type | null | undefined>,
|
||||||
|
>(parser: Parser<unknown, Type>, input: Input): Return {
|
||||||
|
if (testForAsRequiredParser()(input)) return parser as any
|
||||||
|
return parser.optional() as any
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* A value is going to be part of the form in the FE of the OS.
|
* A value is going to be part of the form in the FE of the OS.
|
||||||
* Something like a boolean, a string, a number, etc.
|
* Something like a boolean, a string, a number, etc.
|
||||||
@@ -62,22 +92,29 @@ const username = Value.string({
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
export class Value<A extends ValueSpec> extends IBuilder<A> {
|
export class Value<Type, WD, ConfigType> {
|
||||||
static toggle(a: {
|
private constructor(
|
||||||
|
public build: LazyBuild<WD, ConfigType, ValueSpec>,
|
||||||
|
public validator: Parser<unknown, Type>,
|
||||||
|
) {}
|
||||||
|
static toggle<WD, CT>(a: {
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
warning?: string | null
|
warning?: string | null
|
||||||
default?: boolean | null
|
default?: boolean | null
|
||||||
}) {
|
}) {
|
||||||
return new Value({
|
return new Value<boolean, WD, CT>(
|
||||||
description: null,
|
async () => ({
|
||||||
warning: null,
|
description: null,
|
||||||
default: null,
|
warning: null,
|
||||||
type: "toggle" as const,
|
default: null,
|
||||||
...a,
|
type: "toggle" as const,
|
||||||
})
|
...a,
|
||||||
|
}),
|
||||||
|
boolean,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
static text<Required extends RequiredDefault<DefaultString>>(a: {
|
static text<Required extends RequiredDefault<DefaultString>, WD, CT>(a: {
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
warning?: string | null
|
warning?: string | null
|
||||||
@@ -92,21 +129,24 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
|
|||||||
/** Default = 'text' */
|
/** Default = 'text' */
|
||||||
inputmode?: ValueSpecText["inputmode"]
|
inputmode?: ValueSpecText["inputmode"]
|
||||||
}) {
|
}) {
|
||||||
return new Value({
|
return new Value<AsRequired<string, Required>, WD, CT>(
|
||||||
type: "text" as const,
|
async () => ({
|
||||||
description: null,
|
type: "text" as const,
|
||||||
warning: null,
|
description: null,
|
||||||
masked: false,
|
warning: null,
|
||||||
placeholder: null,
|
masked: false,
|
||||||
minLength: null,
|
placeholder: null,
|
||||||
maxLength: null,
|
minLength: null,
|
||||||
patterns: [],
|
maxLength: null,
|
||||||
inputmode: "text",
|
patterns: [],
|
||||||
...a,
|
inputmode: "text",
|
||||||
...requiredLikeToAbove(a.required),
|
...a,
|
||||||
})
|
...requiredLikeToAbove(a.required),
|
||||||
|
}),
|
||||||
|
asRequiredParser(string, a),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
static textarea(a: {
|
static textarea<WD, CT>(a: {
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
warning?: string | null
|
warning?: string | null
|
||||||
@@ -115,17 +155,21 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
|
|||||||
maxLength?: number | null
|
maxLength?: number | null
|
||||||
placeholder?: string | null
|
placeholder?: string | null
|
||||||
}) {
|
}) {
|
||||||
return new Value({
|
return new Value<string, WD, CT>(
|
||||||
description: null,
|
async () =>
|
||||||
warning: null,
|
({
|
||||||
minLength: null,
|
description: null,
|
||||||
maxLength: null,
|
warning: null,
|
||||||
placeholder: null,
|
minLength: null,
|
||||||
type: "textarea" as const,
|
maxLength: null,
|
||||||
...a,
|
placeholder: null,
|
||||||
} as ValueSpecTextarea)
|
type: "textarea" as const,
|
||||||
|
...a,
|
||||||
|
} satisfies ValueSpecTextarea),
|
||||||
|
string,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
static number<Required extends RequiredDefault<number>>(a: {
|
static number<Required extends RequiredDefault<number>, WD, CT>(a: {
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
warning?: string | null
|
warning?: string | null
|
||||||
@@ -138,34 +182,41 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
|
|||||||
units?: string | null
|
units?: string | null
|
||||||
placeholder?: string | null
|
placeholder?: string | null
|
||||||
}) {
|
}) {
|
||||||
return new Value({
|
return new Value<AsRequired<number, Required>, WD, CT>(
|
||||||
type: "number" as const,
|
() => ({
|
||||||
description: null,
|
type: "number" as const,
|
||||||
warning: null,
|
description: null,
|
||||||
min: null,
|
warning: null,
|
||||||
max: null,
|
min: null,
|
||||||
step: null,
|
max: null,
|
||||||
units: null,
|
step: null,
|
||||||
placeholder: null,
|
units: null,
|
||||||
...a,
|
placeholder: null,
|
||||||
...requiredLikeToAbove(a.required),
|
...a,
|
||||||
})
|
...requiredLikeToAbove(a.required),
|
||||||
|
}),
|
||||||
|
asRequiredParser(number, a),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
static color<Required extends RequiredDefault<string>>(a: {
|
static color<Required extends RequiredDefault<string>, WD, CT>(a: {
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
warning?: string | null
|
warning?: string | null
|
||||||
required: Required
|
required: Required
|
||||||
}) {
|
}) {
|
||||||
return new Value({
|
return new Value<AsRequired<string, Required>, WD, CT>(
|
||||||
type: "color" as const,
|
() => ({
|
||||||
description: null,
|
type: "color" as const,
|
||||||
warning: null,
|
description: null,
|
||||||
...a,
|
warning: null,
|
||||||
...requiredLikeToAbove(a.required),
|
...a,
|
||||||
})
|
...requiredLikeToAbove(a.required),
|
||||||
|
}),
|
||||||
|
|
||||||
|
asRequiredParser(string, a),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
static datetime<Required extends RequiredDefault<string>>(a: {
|
static datetime<Required extends RequiredDefault<string>, WD, CT>(a: {
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
warning?: string | null
|
warning?: string | null
|
||||||
@@ -176,21 +227,26 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
|
|||||||
max?: string | null
|
max?: string | null
|
||||||
step?: string | null
|
step?: string | null
|
||||||
}) {
|
}) {
|
||||||
return new Value({
|
return new Value<AsRequired<string, Required>, WD, CT>(
|
||||||
type: "datetime" as const,
|
() => ({
|
||||||
description: null,
|
type: "datetime" as const,
|
||||||
warning: null,
|
description: null,
|
||||||
inputmode: "datetime-local",
|
warning: null,
|
||||||
min: null,
|
inputmode: "datetime-local",
|
||||||
max: null,
|
min: null,
|
||||||
step: null,
|
max: null,
|
||||||
...a,
|
step: null,
|
||||||
...requiredLikeToAbove(a.required),
|
...a,
|
||||||
})
|
...requiredLikeToAbove(a.required),
|
||||||
|
}),
|
||||||
|
asRequiredParser(string, a),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
static select<
|
static select<
|
||||||
Required extends RequiredDefault<string>,
|
Required extends RequiredDefault<string>,
|
||||||
B extends Record<string, string>,
|
B extends Record<string, string>,
|
||||||
|
WD,
|
||||||
|
CT,
|
||||||
>(a: {
|
>(a: {
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
@@ -198,15 +254,23 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
|
|||||||
required: Required
|
required: Required
|
||||||
values: B
|
values: B
|
||||||
}) {
|
}) {
|
||||||
return new Value({
|
return new Value<AsRequired<keyof B, Required>, WD, CT>(
|
||||||
description: null,
|
() => ({
|
||||||
warning: null,
|
description: null,
|
||||||
type: "select" as const,
|
warning: null,
|
||||||
...a,
|
type: "select" as const,
|
||||||
...requiredLikeToAbove(a.required),
|
...a,
|
||||||
})
|
...requiredLikeToAbove(a.required),
|
||||||
|
}),
|
||||||
|
asRequiredParser(
|
||||||
|
anyOf(
|
||||||
|
...Object.keys(a.values).map((x: keyof B & string) => literal(x)),
|
||||||
|
),
|
||||||
|
a,
|
||||||
|
) as any,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
static multiselect<Values extends Record<string, string>>(a: {
|
static multiselect<Values extends Record<string, string>, WD, CT>(a: {
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
warning?: string | null
|
warning?: string | null
|
||||||
@@ -215,35 +279,44 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
|
|||||||
minLength?: number | null
|
minLength?: number | null
|
||||||
maxLength?: number | null
|
maxLength?: number | null
|
||||||
}) {
|
}) {
|
||||||
return new Value({
|
return new Value<(keyof Values)[], WD, CT>(
|
||||||
type: "multiselect" as const,
|
() => ({
|
||||||
minLength: null,
|
type: "multiselect" as const,
|
||||||
maxLength: null,
|
minLength: null,
|
||||||
warning: null,
|
maxLength: null,
|
||||||
description: null,
|
warning: null,
|
||||||
...a,
|
description: null,
|
||||||
})
|
...a,
|
||||||
|
}),
|
||||||
|
arrayOf(
|
||||||
|
literals(...(Object.keys(a.values) as any as [keyof Values & string])),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
static object<Spec extends Config<InputSpec>>(
|
static object<Type extends Record<string, any>, WrapperData, ConfigType>(
|
||||||
a: {
|
a: {
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
warning?: string | null
|
warning?: string | null
|
||||||
},
|
},
|
||||||
previousSpec: Spec,
|
previousSpec: Config<Type, WrapperData, ConfigType>,
|
||||||
) {
|
) {
|
||||||
const spec = previousSpec.build() as BuilderExtract<Spec>
|
return new Value<Type, WrapperData, ConfigType>(async (options) => {
|
||||||
return new Value({
|
const spec = await previousSpec.build(options as any)
|
||||||
type: "object" as const,
|
return {
|
||||||
description: null,
|
type: "object" as const,
|
||||||
warning: null,
|
description: null,
|
||||||
...a,
|
warning: null,
|
||||||
spec,
|
...a,
|
||||||
})
|
spec,
|
||||||
|
}
|
||||||
|
}, previousSpec.validator)
|
||||||
}
|
}
|
||||||
static union<
|
static union<
|
||||||
Required extends RequiredDefault<string>,
|
Required extends RequiredDefault<string>,
|
||||||
V extends Variants<{ [key: string]: { name: string; spec: InputSpec } }>,
|
Type,
|
||||||
|
WrapperData,
|
||||||
|
ConfigType,
|
||||||
>(
|
>(
|
||||||
a: {
|
a: {
|
||||||
name: string
|
name: string
|
||||||
@@ -252,23 +325,28 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
|
|||||||
required: Required
|
required: Required
|
||||||
default?: string | null
|
default?: string | null
|
||||||
},
|
},
|
||||||
aVariants: V,
|
aVariants: Variants<Type, WrapperData, ConfigType>,
|
||||||
) {
|
) {
|
||||||
const variants = aVariants.build() as BuilderExtract<V>
|
return new Value<AsRequired<Type, Required>, WrapperData, ConfigType>(
|
||||||
return new Value({
|
async (options) => ({
|
||||||
type: "union" as const,
|
type: "union" as const,
|
||||||
description: null,
|
description: null,
|
||||||
warning: null,
|
warning: null,
|
||||||
...a,
|
...a,
|
||||||
variants,
|
variants: await aVariants.build(options as any),
|
||||||
...requiredLikeToAbove(a.required),
|
...requiredLikeToAbove(a.required),
|
||||||
})
|
}),
|
||||||
|
asRequiredParser(aVariants.validator, a),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
static list<A extends ValueSpecList>(a: List<A>) {
|
static list<Type, WrapperData, ConfigType>(
|
||||||
return new Value(a.build())
|
a: List<Type, WrapperData, ConfigType>,
|
||||||
}
|
) {
|
||||||
public validator() {
|
/// TODO
|
||||||
return guardAll(this.a)
|
return new Value<Type, WrapperData, ConfigType>(
|
||||||
|
(options) => a.build(options),
|
||||||
|
a.validator,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { InputSpec } from "../configTypes"
|
import { InputSpec, ValueSpecUnion } from "../configTypes"
|
||||||
import { BuilderExtract, IBuilder } from "./builder"
|
|
||||||
import { Config } from "."
|
import { Config } from "."
|
||||||
|
import { LazyBuild } from "./config"
|
||||||
|
import { Parser, anyOf, literals, object } from "ts-matches"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used in the the Value.select { @link './value.ts' }
|
* Used in the the Value.select { @link './value.ts' }
|
||||||
@@ -51,46 +52,55 @@ export const pruning = Value.union(
|
|||||||
);
|
);
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
export class Variants<
|
export class Variants<Type, WD, ConfigType> {
|
||||||
A extends {
|
private constructor(
|
||||||
[key: string]: {
|
public build: LazyBuild<WD, ConfigType, ValueSpecUnion["variants"]>,
|
||||||
name: string
|
public validator: Parser<unknown, Type>,
|
||||||
spec: InputSpec
|
) {}
|
||||||
}
|
// A extends {
|
||||||
},
|
// [key: string]: {
|
||||||
> extends IBuilder<A> {
|
// name: string
|
||||||
|
// spec: InputSpec
|
||||||
|
// }
|
||||||
|
// },
|
||||||
static of<
|
static of<
|
||||||
A extends {
|
TypeMap extends Record<string, Record<string, any>>,
|
||||||
[key: string]: { name: string; spec: Config<InputSpec> }
|
WrapperData,
|
||||||
},
|
ConfigType,
|
||||||
>(a: A) {
|
>(a: {
|
||||||
const variants: {
|
[K in keyof TypeMap]: {
|
||||||
[K in keyof A]: { name: string; spec: BuilderExtract<A[K]["spec"]> }
|
name: string
|
||||||
} = {} as any
|
spec: Config<TypeMap[K], WrapperData, ConfigType>
|
||||||
for (const key in a) {
|
|
||||||
const value = a[key]
|
|
||||||
variants[key] = {
|
|
||||||
name: value.name,
|
|
||||||
spec: value.spec.build() as any,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return new Variants(variants)
|
}) {
|
||||||
}
|
type TypeOut = {
|
||||||
|
[K in keyof TypeMap & string]: {
|
||||||
|
unionSelectKey: K
|
||||||
|
unionValueKey: TypeMap[K]
|
||||||
|
}
|
||||||
|
}[keyof TypeMap & string]
|
||||||
|
|
||||||
static empty() {
|
const validator = anyOf(
|
||||||
return Variants.of({})
|
...Object.entries(a).map(([name, { spec }]) =>
|
||||||
}
|
object({
|
||||||
static withVariant<K extends string, B extends InputSpec>(
|
unionSelectKey: literals(name),
|
||||||
key: K,
|
unionValueKey: spec.validator,
|
||||||
value: Config<B>,
|
}),
|
||||||
) {
|
),
|
||||||
return Variants.empty().withVariant(key, value)
|
) as Parser<unknown, TypeOut>
|
||||||
}
|
|
||||||
|
|
||||||
withVariant<K extends string, B extends InputSpec>(key: K, value: Config<B>) {
|
return new Variants<TypeOut, WrapperData, ConfigType>(async (options) => {
|
||||||
return new Variants({
|
const variants = {} as {
|
||||||
...this.a,
|
[K in keyof TypeMap]: { name: string; spec: InputSpec }
|
||||||
[key]: value.build(),
|
}
|
||||||
} as A & { [key in K]: B })
|
for (const key in a) {
|
||||||
|
const value = a[key]
|
||||||
|
variants[key] = {
|
||||||
|
name: value.name,
|
||||||
|
spec: await value.spec.build(options),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return variants
|
||||||
|
}, validator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -189,10 +189,10 @@ export type DefaultString =
|
|||||||
|
|
||||||
// sometimes the type checker needs just a little bit of help
|
// sometimes the type checker needs just a little bit of help
|
||||||
export function isValueSpecListOf<S extends ListValueSpecType>(
|
export function isValueSpecListOf<S extends ListValueSpecType>(
|
||||||
t: ValueSpecListOf<ListValueSpecType>,
|
t: ValueSpec,
|
||||||
s: S,
|
s: S,
|
||||||
): t is ValueSpecListOf<S> & { spec: ListValueSpecOf<S> } {
|
): t is ValueSpecListOf<S> & { spec: ListValueSpecOf<S> } {
|
||||||
return t.spec.type === s
|
return "spec" in t && t.spec.type === s
|
||||||
}
|
}
|
||||||
export const unionSelectKey = "unionSelectKey" as const
|
export const unionSelectKey = "unionSelectKey" as const
|
||||||
export type UnionSelectKey = typeof unionSelectKey
|
export type UnionSelectKey = typeof unionSelectKey
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Config } from "./builder"
|
|||||||
import { DeepPartial, Dependencies, Effects, ExpectedExports } from "../types"
|
import { DeepPartial, Dependencies, Effects, ExpectedExports } from "../types"
|
||||||
import { InputSpec } from "./configTypes"
|
import { InputSpec } from "./configTypes"
|
||||||
import { Utils, nullIfEmpty, once, utils } from "../util"
|
import { Utils, nullIfEmpty, once, utils } from "../util"
|
||||||
import { TypeFromProps } from "../util/propertiesMatcher"
|
|
||||||
import { GenericManifest } from "../manifest/ManifestTypes"
|
import { GenericManifest } from "../manifest/ManifestTypes"
|
||||||
import * as D from "./dependencies"
|
import * as D from "./dependencies"
|
||||||
|
|
||||||
@@ -30,18 +29,18 @@ export type Read<WD, A> = (options: {
|
|||||||
*/
|
*/
|
||||||
export function setupConfig<
|
export function setupConfig<
|
||||||
WD,
|
WD,
|
||||||
A extends Config<InputSpec>,
|
Type extends Record<string, any>,
|
||||||
Manifest extends GenericManifest,
|
Manifest extends GenericManifest,
|
||||||
>(
|
>(
|
||||||
spec: A,
|
spec: Config<Type, WD, Type>,
|
||||||
write: Save<WD, TypeFromProps<A>, Manifest>,
|
write: Save<WD, Type, Manifest>,
|
||||||
read: Read<WD, TypeFromProps<A>>,
|
read: Read<WD, Type>,
|
||||||
) {
|
) {
|
||||||
const validator = once(() => spec.validator())
|
const validator = spec.validator
|
||||||
return {
|
return {
|
||||||
setConfig: (async ({ effects, input }) => {
|
setConfig: (async ({ effects, input }) => {
|
||||||
if (!validator().test(input)) {
|
if (!validator.test(input)) {
|
||||||
await effects.console.error(String(validator().errorMessage(input)))
|
await effects.console.error(String(validator.errorMessage(input)))
|
||||||
return { error: "Set config type error for config" }
|
return { error: "Set config type error for config" }
|
||||||
}
|
}
|
||||||
await write({
|
await write({
|
||||||
@@ -51,12 +50,18 @@ export function setupConfig<
|
|||||||
dependencies: D.dependenciesSet<Manifest>(),
|
dependencies: D.dependenciesSet<Manifest>(),
|
||||||
})
|
})
|
||||||
}) as ExpectedExports.setConfig,
|
}) as ExpectedExports.setConfig,
|
||||||
getConfig: (async ({ effects, config }) => {
|
getConfig: (async ({ effects }) => {
|
||||||
|
const myUtils = utils<WD>(effects)
|
||||||
|
const configValue = nullIfEmpty(
|
||||||
|
(await read({ effects, utils: myUtils })) || null,
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
spec: spec.build(),
|
spec: await spec.build({
|
||||||
config: nullIfEmpty(
|
effects,
|
||||||
(await read({ effects, utils: utils<WD>(effects) })) || null,
|
utils: myUtils,
|
||||||
),
|
config: configValue,
|
||||||
|
}),
|
||||||
|
config: configValue,
|
||||||
}
|
}
|
||||||
}) as ExpectedExports.getConfig,
|
}) as ExpectedExports.getConfig,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export class NetworkInterfaceBuilder {
|
|||||||
id: string
|
id: string
|
||||||
description: string
|
description: string
|
||||||
ui: boolean
|
ui: boolean
|
||||||
basic?: null | { password: string; username: string }
|
basic?: null | { password: null | string; username: string }
|
||||||
path?: string
|
path?: string
|
||||||
search?: Record<string, string>
|
search?: Record<string, string>
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ export class Origin {
|
|||||||
withAuth(
|
withAuth(
|
||||||
origin?:
|
origin?:
|
||||||
| {
|
| {
|
||||||
password: string
|
password: null | string
|
||||||
username: string
|
username: string
|
||||||
}
|
}
|
||||||
| null
|
| null
|
||||||
| undefined,
|
| undefined,
|
||||||
) {
|
) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
const urlAuth = !!(origin) ? `${origin.username}:${origin.password}@` :
|
const urlAuth = !!(origin) ? `${origin.username}${origin.password != null ?`:${origin.password}`:''}@` :
|
||||||
'';
|
'';
|
||||||
return `${this.protocol}://${urlAuth}${this.host}`
|
return `${this.protocol}://${urlAuth}${this.host}`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,22 +3,21 @@ import { Config } from "../config/builder/config"
|
|||||||
import { List } from "../config/builder/list"
|
import { List } from "../config/builder/list"
|
||||||
import { Value } from "../config/builder/value"
|
import { Value } from "../config/builder/value"
|
||||||
import { Variants } from "../config/builder/variants"
|
import { Variants } from "../config/builder/variants"
|
||||||
|
import { ValueSpec } from "../config/configTypes"
|
||||||
|
import { Parser } from "ts-matches"
|
||||||
|
|
||||||
|
type test = unknown | { test: 5 }
|
||||||
describe("builder tests", () => {
|
describe("builder tests", () => {
|
||||||
test("text", () => {
|
test("text", async () => {
|
||||||
const bitcoinPropertiesBuilt: {
|
const bitcoinPropertiesBuilt: {
|
||||||
"peer-tor-address": {
|
"peer-tor-address": ValueSpec
|
||||||
name: string
|
} = await Config.of({
|
||||||
description: string | null
|
|
||||||
type: "text"
|
|
||||||
}
|
|
||||||
} = Config.of({
|
|
||||||
"peer-tor-address": Value.text({
|
"peer-tor-address": Value.text({
|
||||||
name: "Peer tor address",
|
name: "Peer tor address",
|
||||||
description: "The Tor address of the peer interface",
|
description: "The Tor address of the peer interface",
|
||||||
required: { default: null },
|
required: { default: null },
|
||||||
}),
|
}),
|
||||||
}).build()
|
}).build({} as any)
|
||||||
expect(JSON.stringify(bitcoinPropertiesBuilt)).toEqual(
|
expect(JSON.stringify(bitcoinPropertiesBuilt)).toEqual(
|
||||||
/*json*/ `{
|
/*json*/ `{
|
||||||
"peer-tor-address": {
|
"peer-tor-address": {
|
||||||
@@ -43,62 +42,62 @@ describe("builder tests", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("values", () => {
|
describe("values", () => {
|
||||||
test("toggle", () => {
|
test("toggle", async () => {
|
||||||
const value = Value.toggle({
|
const value = Value.toggle({
|
||||||
name: "Testing",
|
name: "Testing",
|
||||||
description: null,
|
description: null,
|
||||||
warning: null,
|
warning: null,
|
||||||
default: null,
|
default: null,
|
||||||
})
|
})
|
||||||
const validator = value.validator()
|
const validator = value.validator
|
||||||
validator.unsafeCast(false)
|
validator.unsafeCast(false)
|
||||||
testOutput<typeof validator._TYPE, boolean>()(null)
|
testOutput<typeof validator._TYPE, boolean>()(null)
|
||||||
})
|
})
|
||||||
test("text", () => {
|
test("text", async () => {
|
||||||
const value = Value.text({
|
const value = Value.text({
|
||||||
name: "Testing",
|
name: "Testing",
|
||||||
required: { default: null },
|
required: { default: null },
|
||||||
})
|
})
|
||||||
const validator = value.validator()
|
const validator = value.validator
|
||||||
const rawIs = value.build()
|
const rawIs = await value.build({} as any)
|
||||||
validator.unsafeCast("test text")
|
validator.unsafeCast("test text")
|
||||||
expect(() => validator.unsafeCast(null)).toThrowError()
|
expect(() => validator.unsafeCast(null)).toThrowError()
|
||||||
testOutput<typeof validator._TYPE, string>()(null)
|
testOutput<typeof validator._TYPE, string>()(null)
|
||||||
})
|
})
|
||||||
test("text", () => {
|
test("text", async () => {
|
||||||
const value = Value.text({
|
const value = Value.text({
|
||||||
name: "Testing",
|
name: "Testing",
|
||||||
required: { default: "null" },
|
required: { default: "null" },
|
||||||
})
|
})
|
||||||
const validator = value.validator()
|
const validator = value.validator
|
||||||
const rawIs = value.build()
|
const rawIs = await value.build({} as any)
|
||||||
validator.unsafeCast("test text")
|
validator.unsafeCast("test text")
|
||||||
expect(() => validator.unsafeCast(null)).toThrowError()
|
expect(() => validator.unsafeCast(null)).toThrowError()
|
||||||
testOutput<typeof validator._TYPE, string>()(null)
|
testOutput<typeof validator._TYPE, string>()(null)
|
||||||
})
|
})
|
||||||
test("optional text", () => {
|
test("optional text", async () => {
|
||||||
const value = Value.text({
|
const value = Value.text({
|
||||||
name: "Testing",
|
name: "Testing",
|
||||||
required: false,
|
required: false,
|
||||||
})
|
})
|
||||||
const validator = value.validator()
|
const validator = value.validator
|
||||||
const rawIs = value.build()
|
const rawIs = await value.build({} as any)
|
||||||
validator.unsafeCast("test text")
|
validator.unsafeCast("test text")
|
||||||
validator.unsafeCast(null)
|
validator.unsafeCast(null)
|
||||||
testOutput<typeof validator._TYPE, string | null | undefined>()(null)
|
testOutput<typeof validator._TYPE, string | null | undefined>()(null)
|
||||||
})
|
})
|
||||||
test("color", () => {
|
test("color", async () => {
|
||||||
const value = Value.color({
|
const value = Value.color({
|
||||||
name: "Testing",
|
name: "Testing",
|
||||||
required: false,
|
required: false,
|
||||||
description: null,
|
description: null,
|
||||||
warning: null,
|
warning: null,
|
||||||
})
|
})
|
||||||
const validator = value.validator()
|
const validator = value.validator
|
||||||
validator.unsafeCast("#000000")
|
validator.unsafeCast("#000000")
|
||||||
testOutput<typeof validator._TYPE, string | null | undefined>()(null)
|
testOutput<typeof validator._TYPE, string | null | undefined>()(null)
|
||||||
})
|
})
|
||||||
test("datetime", () => {
|
test("datetime", async () => {
|
||||||
const value = Value.datetime({
|
const value = Value.datetime({
|
||||||
name: "Testing",
|
name: "Testing",
|
||||||
required: { default: null },
|
required: { default: null },
|
||||||
@@ -109,11 +108,11 @@ describe("values", () => {
|
|||||||
max: null,
|
max: null,
|
||||||
step: null,
|
step: null,
|
||||||
})
|
})
|
||||||
const validator = value.validator()
|
const validator = value.validator
|
||||||
validator.unsafeCast("2021-01-01")
|
validator.unsafeCast("2021-01-01")
|
||||||
testOutput<typeof validator._TYPE, string>()(null)
|
testOutput<typeof validator._TYPE, string>()(null)
|
||||||
})
|
})
|
||||||
test("optional datetime", () => {
|
test("optional datetime", async () => {
|
||||||
const value = Value.datetime({
|
const value = Value.datetime({
|
||||||
name: "Testing",
|
name: "Testing",
|
||||||
required: false,
|
required: false,
|
||||||
@@ -124,11 +123,11 @@ describe("values", () => {
|
|||||||
max: null,
|
max: null,
|
||||||
step: null,
|
step: null,
|
||||||
})
|
})
|
||||||
const validator = value.validator()
|
const validator = value.validator
|
||||||
validator.unsafeCast("2021-01-01")
|
validator.unsafeCast("2021-01-01")
|
||||||
testOutput<typeof validator._TYPE, string | null | undefined>()(null)
|
testOutput<typeof validator._TYPE, string | null | undefined>()(null)
|
||||||
})
|
})
|
||||||
test("textarea", () => {
|
test("textarea", async () => {
|
||||||
const value = Value.textarea({
|
const value = Value.textarea({
|
||||||
name: "Testing",
|
name: "Testing",
|
||||||
required: false,
|
required: false,
|
||||||
@@ -138,11 +137,11 @@ describe("values", () => {
|
|||||||
maxLength: null,
|
maxLength: null,
|
||||||
placeholder: null,
|
placeholder: null,
|
||||||
})
|
})
|
||||||
const validator = value.validator()
|
const validator = value.validator
|
||||||
validator.unsafeCast("test text")
|
validator.unsafeCast("test text")
|
||||||
testOutput<typeof validator._TYPE, string>()(null)
|
testOutput<typeof validator._TYPE, string>()(null)
|
||||||
})
|
})
|
||||||
test("number", () => {
|
test("number", async () => {
|
||||||
const value = Value.number({
|
const value = Value.number({
|
||||||
name: "Testing",
|
name: "Testing",
|
||||||
required: { default: null },
|
required: { default: null },
|
||||||
@@ -155,11 +154,11 @@ describe("values", () => {
|
|||||||
units: null,
|
units: null,
|
||||||
placeholder: null,
|
placeholder: null,
|
||||||
})
|
})
|
||||||
const validator = value.validator()
|
const validator = value.validator
|
||||||
validator.unsafeCast(2)
|
validator.unsafeCast(2)
|
||||||
testOutput<typeof validator._TYPE, number>()(null)
|
testOutput<typeof validator._TYPE, number>()(null)
|
||||||
})
|
})
|
||||||
test("optional number", () => {
|
test("optional number", async () => {
|
||||||
const value = Value.number({
|
const value = Value.number({
|
||||||
name: "Testing",
|
name: "Testing",
|
||||||
required: false,
|
required: false,
|
||||||
@@ -172,11 +171,11 @@ describe("values", () => {
|
|||||||
units: null,
|
units: null,
|
||||||
placeholder: null,
|
placeholder: null,
|
||||||
})
|
})
|
||||||
const validator = value.validator()
|
const validator = value.validator
|
||||||
validator.unsafeCast(2)
|
validator.unsafeCast(2)
|
||||||
testOutput<typeof validator._TYPE, number | null | undefined>()(null)
|
testOutput<typeof validator._TYPE, number | null | undefined>()(null)
|
||||||
})
|
})
|
||||||
test("select", () => {
|
test("select", async () => {
|
||||||
const value = Value.select({
|
const value = Value.select({
|
||||||
name: "Testing",
|
name: "Testing",
|
||||||
required: { default: null },
|
required: { default: null },
|
||||||
@@ -187,13 +186,13 @@ describe("values", () => {
|
|||||||
description: null,
|
description: null,
|
||||||
warning: null,
|
warning: null,
|
||||||
})
|
})
|
||||||
const validator = value.validator()
|
const validator = value.validator
|
||||||
validator.unsafeCast("a")
|
validator.unsafeCast("a")
|
||||||
validator.unsafeCast("b")
|
validator.unsafeCast("b")
|
||||||
expect(() => validator.unsafeCast(null)).toThrowError()
|
expect(() => validator.unsafeCast(null)).toThrowError()
|
||||||
testOutput<typeof validator._TYPE, "a" | "b">()(null)
|
testOutput<typeof validator._TYPE, "a" | "b">()(null)
|
||||||
})
|
})
|
||||||
test("nullable select", () => {
|
test("nullable select", async () => {
|
||||||
const value = Value.select({
|
const value = Value.select({
|
||||||
name: "Testing",
|
name: "Testing",
|
||||||
required: false,
|
required: false,
|
||||||
@@ -204,13 +203,13 @@ describe("values", () => {
|
|||||||
description: null,
|
description: null,
|
||||||
warning: null,
|
warning: null,
|
||||||
})
|
})
|
||||||
const validator = value.validator()
|
const validator = value.validator
|
||||||
validator.unsafeCast("a")
|
validator.unsafeCast("a")
|
||||||
validator.unsafeCast("b")
|
validator.unsafeCast("b")
|
||||||
validator.unsafeCast(null)
|
validator.unsafeCast(null)
|
||||||
testOutput<typeof validator._TYPE, "a" | "b" | null | undefined>()(null)
|
testOutput<typeof validator._TYPE, "a" | "b" | null | undefined>()(null)
|
||||||
})
|
})
|
||||||
test("multiselect", () => {
|
test("multiselect", async () => {
|
||||||
const value = Value.multiselect({
|
const value = Value.multiselect({
|
||||||
name: "Testing",
|
name: "Testing",
|
||||||
values: {
|
values: {
|
||||||
@@ -223,12 +222,15 @@ describe("values", () => {
|
|||||||
minLength: null,
|
minLength: null,
|
||||||
maxLength: null,
|
maxLength: null,
|
||||||
})
|
})
|
||||||
const validator = value.validator()
|
const validator = value.validator
|
||||||
validator.unsafeCast([])
|
validator.unsafeCast([])
|
||||||
validator.unsafeCast(["a", "b"])
|
validator.unsafeCast(["a", "b"])
|
||||||
|
|
||||||
|
expect(() => validator.unsafeCast(["e"])).toThrowError()
|
||||||
|
expect(() => validator.unsafeCast([4])).toThrowError()
|
||||||
testOutput<typeof validator._TYPE, Array<"a" | "b">>()(null)
|
testOutput<typeof validator._TYPE, Array<"a" | "b">>()(null)
|
||||||
})
|
})
|
||||||
test("object", () => {
|
test("object", async () => {
|
||||||
const value = Value.object(
|
const value = Value.object(
|
||||||
{
|
{
|
||||||
name: "Testing",
|
name: "Testing",
|
||||||
@@ -244,11 +246,11 @@ describe("values", () => {
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const validator = value.validator()
|
const validator = value.validator
|
||||||
validator.unsafeCast({ a: true })
|
validator.unsafeCast({ a: true })
|
||||||
testOutput<typeof validator._TYPE, { a: boolean }>()(null)
|
testOutput<typeof validator._TYPE, { a: boolean }>()(null)
|
||||||
})
|
})
|
||||||
test("union", () => {
|
test("union", async () => {
|
||||||
const value = Value.union(
|
const value = Value.union(
|
||||||
{
|
{
|
||||||
name: "Testing",
|
name: "Testing",
|
||||||
@@ -271,14 +273,14 @@ describe("values", () => {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const validator = value.validator()
|
const validator = value.validator
|
||||||
validator.unsafeCast({ unionSelectKey: "a", unionValueKey: { b: false } })
|
validator.unsafeCast({ unionSelectKey: "a", unionValueKey: { b: false } })
|
||||||
type Test = typeof validator._TYPE
|
type Test = typeof validator._TYPE
|
||||||
testOutput<Test, { unionSelectKey: "a"; unionValueKey: { b: boolean } }>()(
|
testOutput<Test, { unionSelectKey: "a"; unionValueKey: { b: boolean } }>()(
|
||||||
null,
|
null,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
test("list", () => {
|
test("list", async () => {
|
||||||
const value = Value.list(
|
const value = Value.list(
|
||||||
List.number(
|
List.number(
|
||||||
{
|
{
|
||||||
@@ -289,14 +291,14 @@ describe("values", () => {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
const validator = value.validator()
|
const validator = value.validator
|
||||||
validator.unsafeCast([1, 2, 3])
|
validator.unsafeCast([1, 2, 3])
|
||||||
testOutput<typeof validator._TYPE, number[]>()(null)
|
testOutput<typeof validator._TYPE, number[]>()(null)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("Builder List", () => {
|
describe("Builder List", () => {
|
||||||
test("obj", () => {
|
test("obj", async () => {
|
||||||
const value = Value.list(
|
const value = Value.list(
|
||||||
List.obj(
|
List.obj(
|
||||||
{
|
{
|
||||||
@@ -314,11 +316,11 @@ describe("Builder List", () => {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
const validator = value.validator()
|
const validator = value.validator
|
||||||
validator.unsafeCast([{ test: true }])
|
validator.unsafeCast([{ test: true }])
|
||||||
testOutput<typeof validator._TYPE, { test: boolean }[]>()(null)
|
testOutput<typeof validator._TYPE, { test: boolean }[]>()(null)
|
||||||
})
|
})
|
||||||
test("text", () => {
|
test("text", async () => {
|
||||||
const value = Value.list(
|
const value = Value.list(
|
||||||
List.text(
|
List.text(
|
||||||
{
|
{
|
||||||
@@ -329,26 +331,14 @@ describe("Builder List", () => {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
const validator = value.validator()
|
const validator = value.validator
|
||||||
validator.unsafeCast(["test", "text"])
|
validator.unsafeCast(["test", "text"])
|
||||||
testOutput<typeof validator._TYPE, string[]>()(null)
|
testOutput<typeof validator._TYPE, string[]>()(null)
|
||||||
})
|
})
|
||||||
Value.multiselect({
|
|
||||||
name: "Media Sources",
|
|
||||||
minLength: null,
|
|
||||||
maxLength: null,
|
|
||||||
default: ["nextcloud"],
|
|
||||||
description: "List of Media Sources to use with Jellyfin",
|
|
||||||
warning: null,
|
|
||||||
values: {
|
|
||||||
nextcloud: "NextCloud",
|
|
||||||
filebrowser: "File Browser",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("Nested nullable values", () => {
|
describe("Nested nullable values", () => {
|
||||||
test("Testing text", () => {
|
test("Testing text", async () => {
|
||||||
const value = Config.of({
|
const value = Config.of({
|
||||||
a: Value.text({
|
a: Value.text({
|
||||||
name: "Temp Name",
|
name: "Temp Name",
|
||||||
@@ -357,13 +347,13 @@ describe("Nested nullable values", () => {
|
|||||||
required: false,
|
required: false,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
const validator = value.validator()
|
const validator = value.validator
|
||||||
validator.unsafeCast({ a: null })
|
validator.unsafeCast({ a: null })
|
||||||
validator.unsafeCast({ a: "test" })
|
validator.unsafeCast({ a: "test" })
|
||||||
expect(() => validator.unsafeCast({ a: 4 })).toThrowError()
|
expect(() => validator.unsafeCast({ a: 4 })).toThrowError()
|
||||||
testOutput<typeof validator._TYPE, { a: string | null | undefined }>()(null)
|
testOutput<typeof validator._TYPE, { a: string | null | undefined }>()(null)
|
||||||
})
|
})
|
||||||
test("Testing number", () => {
|
test("Testing number", async () => {
|
||||||
const value = Config.of({
|
const value = Config.of({
|
||||||
a: Value.number({
|
a: Value.number({
|
||||||
name: "Temp Name",
|
name: "Temp Name",
|
||||||
@@ -379,13 +369,13 @@ describe("Nested nullable values", () => {
|
|||||||
units: null,
|
units: null,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
const validator = value.validator()
|
const validator = value.validator
|
||||||
validator.unsafeCast({ a: null })
|
validator.unsafeCast({ a: null })
|
||||||
validator.unsafeCast({ a: 5 })
|
validator.unsafeCast({ a: 5 })
|
||||||
expect(() => validator.unsafeCast({ a: "4" })).toThrowError()
|
expect(() => validator.unsafeCast({ a: "4" })).toThrowError()
|
||||||
testOutput<typeof validator._TYPE, { a: number | null | undefined }>()(null)
|
testOutput<typeof validator._TYPE, { a: number | null | undefined }>()(null)
|
||||||
})
|
})
|
||||||
test("Testing color", () => {
|
test("Testing color", async () => {
|
||||||
const value = Config.of({
|
const value = Config.of({
|
||||||
a: Value.color({
|
a: Value.color({
|
||||||
name: "Temp Name",
|
name: "Temp Name",
|
||||||
@@ -395,13 +385,13 @@ describe("Nested nullable values", () => {
|
|||||||
warning: null,
|
warning: null,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
const validator = value.validator()
|
const validator = value.validator
|
||||||
validator.unsafeCast({ a: null })
|
validator.unsafeCast({ a: null })
|
||||||
validator.unsafeCast({ a: "5" })
|
validator.unsafeCast({ a: "5" })
|
||||||
expect(() => validator.unsafeCast({ a: 4 })).toThrowError()
|
expect(() => validator.unsafeCast({ a: 4 })).toThrowError()
|
||||||
testOutput<typeof validator._TYPE, { a: string | null | undefined }>()(null)
|
testOutput<typeof validator._TYPE, { a: string | null | undefined }>()(null)
|
||||||
})
|
})
|
||||||
test("Testing select", () => {
|
test("Testing select", async () => {
|
||||||
const value = Config.of({
|
const value = Config.of({
|
||||||
a: Value.select({
|
a: Value.select({
|
||||||
name: "Temp Name",
|
name: "Temp Name",
|
||||||
@@ -414,7 +404,7 @@ describe("Nested nullable values", () => {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
const higher = Value.select({
|
const higher = await Value.select({
|
||||||
name: "Temp Name",
|
name: "Temp Name",
|
||||||
description: "If no name is provided, the name from config will be used",
|
description: "If no name is provided, the name from config will be used",
|
||||||
required: false,
|
required: false,
|
||||||
@@ -422,15 +412,15 @@ describe("Nested nullable values", () => {
|
|||||||
values: {
|
values: {
|
||||||
a: "A",
|
a: "A",
|
||||||
},
|
},
|
||||||
}).build()
|
}).build({} as any)
|
||||||
|
|
||||||
const validator = value.validator()
|
const validator = value.validator
|
||||||
validator.unsafeCast({ a: null })
|
validator.unsafeCast({ a: null })
|
||||||
validator.unsafeCast({ a: "a" })
|
validator.unsafeCast({ a: "a" })
|
||||||
expect(() => validator.unsafeCast({ a: "4" })).toThrowError()
|
expect(() => validator.unsafeCast({ a: "4" })).toThrowError()
|
||||||
testOutput<typeof validator._TYPE, { a: "a" | null | undefined }>()(null)
|
testOutput<typeof validator._TYPE, { a: "a" | null | undefined }>()(null)
|
||||||
})
|
})
|
||||||
test("Testing multiselect", () => {
|
test("Testing multiselect", async () => {
|
||||||
const value = Config.of({
|
const value = Config.of({
|
||||||
a: Value.multiselect({
|
a: Value.multiselect({
|
||||||
name: "Temp Name",
|
name: "Temp Name",
|
||||||
@@ -446,9 +436,10 @@ describe("Nested nullable values", () => {
|
|||||||
maxLength: null,
|
maxLength: null,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
const validator = value.validator()
|
const validator = value.validator
|
||||||
validator.unsafeCast({ a: [] })
|
validator.unsafeCast({ a: [] })
|
||||||
validator.unsafeCast({ a: ["a"] })
|
validator.unsafeCast({ a: ["a"] })
|
||||||
|
expect(() => validator.unsafeCast({ a: ["4"] })).toThrowError()
|
||||||
expect(() => validator.unsafeCast({ a: "4" })).toThrowError()
|
expect(() => validator.unsafeCast({ a: "4" })).toThrowError()
|
||||||
testOutput<typeof validator._TYPE, { a: "a"[] }>()(null)
|
testOutput<typeof validator._TYPE, { a: "a"[] }>()(null)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,11 +4,14 @@ import { List } from "../config/builder/list"
|
|||||||
import { Value } from "../config/builder/value"
|
import { Value } from "../config/builder/value"
|
||||||
|
|
||||||
describe("Config Types", () => {
|
describe("Config Types", () => {
|
||||||
test("isValueSpecListOf", () => {
|
test("isValueSpecListOf", async () => {
|
||||||
const options = [List.obj, List.text, List.number]
|
const options = [List.obj, List.text, List.number]
|
||||||
for (const option of options) {
|
for (const option of options) {
|
||||||
const test = option({} as any, { spec: Config.of({}) } as any) as any
|
const test = (option as any)(
|
||||||
const someList = Value.list(test).build()
|
{} as any,
|
||||||
|
{ spec: Config.of({}) } as any,
|
||||||
|
) as any
|
||||||
|
const someList = await Value.list(test).build({} as any)
|
||||||
if (isValueSpecListOf(someList, "text")) {
|
if (isValueSpecListOf(someList, "text")) {
|
||||||
someList.spec satisfies ListValueSpecOf<"text">
|
someList.spec satisfies ListValueSpecOf<"text">
|
||||||
} else if (isValueSpecListOf(someList, "number")) {
|
} else if (isValueSpecListOf(someList, "number")) {
|
||||||
|
|||||||
@@ -423,6 +423,7 @@ oldSpecToBuilder(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
// convert this to `start-sdk/lib` for conversions
|
// convert this to `start-sdk/lib` for conversions
|
||||||
startSdk: "..",
|
startSdk: "../..",
|
||||||
|
wrapperData: "./output.wrapperData",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
1
lib/test/output.wrapperData.ts
Normal file
1
lib/test/output.wrapperData.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export type WrapperData = {}
|
||||||
@@ -15,10 +15,7 @@ export namespace ExpectedExports {
|
|||||||
input: Record<string, unknown>
|
input: Record<string, unknown>
|
||||||
}) => Promise<void>
|
}) => Promise<void>
|
||||||
/** Get configuration returns a shape that describes the format that the start9 ui will generate, and later send to the set config */
|
/** Get configuration returns a shape that describes the format that the start9 ui will generate, and later send to the set config */
|
||||||
export type getConfig = (options: {
|
export type getConfig = (options: { effects: Effects }) => Promise<ConfigRes>
|
||||||
effects: Effects
|
|
||||||
config: unknown
|
|
||||||
}) => Promise<ConfigRes>
|
|
||||||
// /** These are how we make sure the our dependency configurations are valid and if not how to fix them. */
|
// /** These are how we make sure the our dependency configurations are valid and if not how to fix them. */
|
||||||
// export type dependencies = Dependencies;
|
// export type dependencies = Dependencies;
|
||||||
/** For backing up service data though the startOS UI */
|
/** For backing up service data though the startOS UI */
|
||||||
@@ -407,7 +404,7 @@ never
|
|||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
export type EnsureWrapperDataPath<WrapperData, Path extends string> = _EnsureWrapperDataPath<WrapperData, Path, Path>
|
export type EnsureWrapperDataPath<WrapperData, Path extends string> = _EnsureWrapperDataPath<WrapperData, Path, Path>
|
||||||
|
|
||||||
/* rsync options: https://linux.die.net/man/1/rsync
|
/** rsync options: https://linux.die.net/man/1/rsync
|
||||||
*/
|
*/
|
||||||
export type BackupOptions = {
|
export type BackupOptions = {
|
||||||
delete: boolean
|
delete: boolean
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import {
|
|||||||
import { LocalBinding, LocalPort, NetworkBuilder, TorHostname } from "../mainFn"
|
import { LocalBinding, LocalPort, NetworkBuilder, TorHostname } from "../mainFn"
|
||||||
import { ExtractWrapperData } from "../types"
|
import { ExtractWrapperData } from "../types"
|
||||||
|
|
||||||
export { guardAll, typeFromProps } from "./propertiesMatcher"
|
|
||||||
export { default as nullIfEmpty } from "./nullIfEmpty"
|
export { default as nullIfEmpty } from "./nullIfEmpty"
|
||||||
export { FileHelper } from "./fileHelper"
|
export { FileHelper } from "./fileHelper"
|
||||||
export { getWrapperData } from "./getWrapperData"
|
export { getWrapperData } from "./getWrapperData"
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
* @param s
|
* @param s
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export default function nullIfEmpty(s: null | Record<string, unknown>) {
|
export default function nullIfEmpty<A extends Record<string, any>>(
|
||||||
|
s: null | A,
|
||||||
|
) {
|
||||||
if (s === null) return null
|
if (s === null) return null
|
||||||
return Object.keys(s).length === 0 ? null : s
|
return Object.keys(s).length === 0 ? null : s
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,264 +0,0 @@
|
|||||||
import * as matches from "ts-matches"
|
|
||||||
import { Parser, Validator } from "ts-matches"
|
|
||||||
import {
|
|
||||||
UnionSelectKey,
|
|
||||||
UnionValueKey,
|
|
||||||
ValueSpec as ValueSpecAny,
|
|
||||||
InputSpec,
|
|
||||||
} from "../config/configTypes"
|
|
||||||
import { Config } from "../config/builder/config"
|
|
||||||
import { _ } from "../util"
|
|
||||||
|
|
||||||
const {
|
|
||||||
string,
|
|
||||||
some,
|
|
||||||
arrayOf,
|
|
||||||
object,
|
|
||||||
dictionary,
|
|
||||||
unknown,
|
|
||||||
number,
|
|
||||||
literals,
|
|
||||||
boolean,
|
|
||||||
nill,
|
|
||||||
} = matches
|
|
||||||
|
|
||||||
type TypeToggle = "toggle"
|
|
||||||
type TypeText = "text"
|
|
||||||
type TypeTextarea = "textarea"
|
|
||||||
type TypeNumber = "number"
|
|
||||||
type TypeObject = "object"
|
|
||||||
type TypeList = "list"
|
|
||||||
type TypeSelect = "select"
|
|
||||||
type TypeMultiselect = "multiselect"
|
|
||||||
type TypeColor = "color"
|
|
||||||
type TypeDatetime = "datetime"
|
|
||||||
type TypeUnion = "union"
|
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
type GuardDefaultRequired<A, Type> =
|
|
||||||
A extends { required: false; default: null | undefined | never } ? Type | undefined | null:
|
|
||||||
Type
|
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
type GuardNumber<A> =
|
|
||||||
A extends { type: TypeNumber } ? GuardDefaultRequired<A, number> :
|
|
||||||
unknown
|
|
||||||
// prettier-ignore
|
|
||||||
type GuardText<A> =
|
|
||||||
A extends { type: TypeText } ? GuardDefaultRequired<A, string> :
|
|
||||||
unknown
|
|
||||||
// prettier-ignore
|
|
||||||
type GuardTextarea<A> =
|
|
||||||
A extends { type: TypeTextarea } ? GuardDefaultRequired<A, string> :
|
|
||||||
unknown
|
|
||||||
// prettier-ignore
|
|
||||||
type GuardToggle<A> =
|
|
||||||
A extends { type: TypeToggle } ? GuardDefaultRequired<A, boolean> :
|
|
||||||
unknown
|
|
||||||
|
|
||||||
type TrueKeyOf<T> = _<T> extends Record<string, unknown> ? keyof T : never
|
|
||||||
// prettier-ignore
|
|
||||||
type GuardObject<A> =
|
|
||||||
A extends { type: TypeObject, spec: infer B } ? (
|
|
||||||
{ [K in TrueKeyOf<B> & string]: _<GuardAll<B[K]>> }
|
|
||||||
) :
|
|
||||||
unknown
|
|
||||||
// prettier-ignore
|
|
||||||
export type GuardList<A> =
|
|
||||||
A extends { type: TypeList, spec?: { type: infer B, spec?: infer C } } ? Array<GuardAll<Omit<A, "type" | "spec"> & ({ type: B, spec: C })>> :
|
|
||||||
A extends { type: TypeList, spec?: { type: infer B } } ? Array<GuardAll<Omit<A, "type"> & ({ type: B })>> :
|
|
||||||
unknown
|
|
||||||
// prettier-ignore
|
|
||||||
type GuardSelect<A> =
|
|
||||||
A extends { type: TypeSelect, values: infer B } ? (
|
|
||||||
GuardDefaultRequired<A, TrueKeyOf<B>>
|
|
||||||
) :
|
|
||||||
unknown
|
|
||||||
// prettier-ignore
|
|
||||||
type GuardMultiselect<A> =
|
|
||||||
A extends { type: TypeMultiselect, values: infer B} ?(keyof B)[] :
|
|
||||||
unknown
|
|
||||||
// prettier-ignore
|
|
||||||
type GuardColor<A> =
|
|
||||||
A extends { type: TypeColor } ? GuardDefaultRequired<A, string> :
|
|
||||||
unknown
|
|
||||||
// prettier-ignore
|
|
||||||
type GuardDatetime<A> =
|
|
||||||
A extends { type: TypeDatetime } ? GuardDefaultRequired<A, string> :
|
|
||||||
unknown
|
|
||||||
type AsString<A> = A extends
|
|
||||||
| string
|
|
||||||
| number
|
|
||||||
| bigint
|
|
||||||
| boolean
|
|
||||||
| null
|
|
||||||
| undefined
|
|
||||||
? `${A}`
|
|
||||||
: "UnknownValue"
|
|
||||||
// prettier-ignore
|
|
||||||
type VariantValue<A> =
|
|
||||||
A extends { name: string, spec: infer B } ? TypeFromProps<_<B>> :
|
|
||||||
`neverVariantValue${AsString<A>}`
|
|
||||||
// prettier-ignore
|
|
||||||
type GuardUnion<A> =
|
|
||||||
A extends { type: TypeUnion, variants: infer Variants & Record<string, unknown> } ? (
|
|
||||||
_<{[key in keyof Variants]: {[k in UnionSelectKey]: key} & {[k in UnionValueKey]: VariantValue<Variants[key]>}}[keyof Variants]>
|
|
||||||
) :
|
|
||||||
unknown
|
|
||||||
|
|
||||||
export type GuardAll<A> = GuardNumber<A> &
|
|
||||||
GuardText<A> &
|
|
||||||
GuardTextarea<A> &
|
|
||||||
GuardToggle<A> &
|
|
||||||
GuardObject<A> &
|
|
||||||
GuardList<A> &
|
|
||||||
GuardUnion<A> &
|
|
||||||
GuardSelect<A> &
|
|
||||||
GuardMultiselect<A> &
|
|
||||||
GuardColor<A> &
|
|
||||||
GuardDatetime<A>
|
|
||||||
// prettier-ignore
|
|
||||||
export type TypeFromProps<A> =
|
|
||||||
A extends Config<infer B> ? TypeFromProps<B> :
|
|
||||||
A extends Record<string, unknown> ? { [K in keyof A & string]: _<GuardAll<A[K]>> } :
|
|
||||||
unknown;
|
|
||||||
const isType = object({ type: string })
|
|
||||||
const matchVariant = object({
|
|
||||||
name: string,
|
|
||||||
spec: unknown,
|
|
||||||
})
|
|
||||||
const recordString = dictionary([string, unknown])
|
|
||||||
const matchDefault = object({ default: unknown })
|
|
||||||
const matchRequired = object(
|
|
||||||
{
|
|
||||||
required: literals(false),
|
|
||||||
default: nill,
|
|
||||||
},
|
|
||||||
["default"],
|
|
||||||
)
|
|
||||||
const matchInteger = object({ integer: literals(true) })
|
|
||||||
const matchSpec = object({ spec: recordString })
|
|
||||||
const matchUnion = object({
|
|
||||||
variants: dictionary([string, matchVariant]),
|
|
||||||
})
|
|
||||||
const matchValues = object({
|
|
||||||
values: dictionary([string, string]),
|
|
||||||
})
|
|
||||||
|
|
||||||
function withInteger(parser: Parser<unknown, number>, value: unknown) {
|
|
||||||
if (matchInteger.test(value)) {
|
|
||||||
return parser.validate(Number.isInteger, "isIntegral")
|
|
||||||
}
|
|
||||||
return parser
|
|
||||||
}
|
|
||||||
function requiredParser<A>(parser: Parser<unknown, A>, value: unknown) {
|
|
||||||
if (matchRequired.test(value)) return parser.optional()
|
|
||||||
return parser
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* InputSpec: Tells the UI how to ask for information, verification, and will send the service a config in a shape via the spec.
|
|
||||||
* ValueSpecAny: This is any of the values in a config spec.
|
|
||||||
*
|
|
||||||
* Use this when we want to convert a value spec any into a parser for what a config will look like
|
|
||||||
* @param value
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function guardAll<A extends ValueSpecAny>(
|
|
||||||
value: A,
|
|
||||||
): Parser<unknown, GuardAll<A>> {
|
|
||||||
if (!isType.test(value)) {
|
|
||||||
return unknown as any
|
|
||||||
}
|
|
||||||
switch (value.type) {
|
|
||||||
case "toggle":
|
|
||||||
return requiredParser(boolean, value) as any
|
|
||||||
|
|
||||||
case "text":
|
|
||||||
return requiredParser(string, value) as any
|
|
||||||
|
|
||||||
case "textarea":
|
|
||||||
return requiredParser(string, value) as any
|
|
||||||
|
|
||||||
case "color":
|
|
||||||
return requiredParser(string, value) as any
|
|
||||||
|
|
||||||
case "datetime":
|
|
||||||
return requiredParser(string, value) as any
|
|
||||||
|
|
||||||
case "number":
|
|
||||||
return requiredParser(withInteger(number, value), value) as any
|
|
||||||
|
|
||||||
case "object":
|
|
||||||
if (matchSpec.test(value)) {
|
|
||||||
return requiredParser(typeFromProps(value.spec), value) as any
|
|
||||||
}
|
|
||||||
return unknown as any
|
|
||||||
|
|
||||||
case "list": {
|
|
||||||
const spec = (matchSpec.test(value) && value.spec) || {}
|
|
||||||
|
|
||||||
return requiredParser(
|
|
||||||
matches.arrayOf(guardAll(spec as any)),
|
|
||||||
value,
|
|
||||||
) as any
|
|
||||||
}
|
|
||||||
case "select":
|
|
||||||
if (matchValues.test(value)) {
|
|
||||||
const valueKeys = Object.keys(value.values)
|
|
||||||
return requiredParser(
|
|
||||||
literals(valueKeys[0], ...valueKeys),
|
|
||||||
value,
|
|
||||||
) as any
|
|
||||||
}
|
|
||||||
return unknown as any
|
|
||||||
|
|
||||||
case "multiselect":
|
|
||||||
if (matchValues.test(value)) {
|
|
||||||
const valueKeys = Object.keys(value.values)
|
|
||||||
return requiredParser(
|
|
||||||
arrayOf(literals(valueKeys[0], ...valueKeys)),
|
|
||||||
value,
|
|
||||||
) as any
|
|
||||||
}
|
|
||||||
return unknown as any
|
|
||||||
|
|
||||||
case "union":
|
|
||||||
if (matchUnion.test(value)) {
|
|
||||||
return some(
|
|
||||||
...Object.entries(value.variants)
|
|
||||||
.filter(([name]) => string.test(name))
|
|
||||||
.map(([name, { spec }]) =>
|
|
||||||
object({
|
|
||||||
unionSelectKey: literals(name),
|
|
||||||
unionValueKey: typeFromProps(spec),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
) as any
|
|
||||||
}
|
|
||||||
return unknown as any
|
|
||||||
}
|
|
||||||
|
|
||||||
return unknown as any
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* InputSpec: Tells the UI how to ask for information, verification, and will send the service a config in a shape via the spec.
|
|
||||||
* ValueSpecAny: This is any of the values in a config spec.
|
|
||||||
*
|
|
||||||
* Use this when we want to convert a config spec into a parser for what a config will look like
|
|
||||||
* @param valueDictionary
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function typeFromProps<A extends InputSpec>(
|
|
||||||
valueDictionary: A,
|
|
||||||
): Parser<unknown, TypeFromProps<A>> {
|
|
||||||
if (!recordString.test(valueDictionary)) return unknown as any
|
|
||||||
return object(
|
|
||||||
Object.fromEntries(
|
|
||||||
Object.entries(valueDictionary).map(([key, value]) => [
|
|
||||||
key,
|
|
||||||
guardAll(value),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
) as any
|
|
||||||
}
|
|
||||||
@@ -29,29 +29,40 @@ function isString(x: unknown): x is string {
|
|||||||
|
|
||||||
export default async function makeFileContentFromOld(
|
export default async function makeFileContentFromOld(
|
||||||
inputData: Promise<any> | any,
|
inputData: Promise<any> | any,
|
||||||
{ startSdk = "start-sdk", nested = true } = {},
|
{
|
||||||
|
startSdk = "start-sdk",
|
||||||
|
nested = true,
|
||||||
|
wrapperData = "../../wrapperData",
|
||||||
|
} = {},
|
||||||
) {
|
) {
|
||||||
const outputLines: string[] = []
|
const outputLines: string[] = []
|
||||||
outputLines.push(`
|
outputLines.push(`
|
||||||
import {Config, Value, List, Variants} from '${startSdk}/config/builder'
|
import {Config, Value, List, Variants} from '${startSdk}/lib/config/builder'
|
||||||
|
import {WrapperData} from '${wrapperData}'
|
||||||
`)
|
`)
|
||||||
const data = await inputData
|
const data = await inputData
|
||||||
|
|
||||||
const namedConsts = new Set(["Config", "Value", "List"])
|
const namedConsts = new Set(["Config", "Value", "List"])
|
||||||
const configName = newConst("ConfigSpec", convertInputSpec(data))
|
const configNameRaw = newConst("configSpecRaw", convertInputSpec(data))
|
||||||
const configMatcherName = newConst(
|
const configMatcherName = newConst(
|
||||||
"matchConfigSpec",
|
"matchConfigSpec",
|
||||||
`${configName}.validator()`,
|
`${configNameRaw}.validator`,
|
||||||
)
|
)
|
||||||
outputLines.push(
|
outputLines.push(
|
||||||
`export type ConfigSpec = typeof ${configMatcherName}._TYPE;`,
|
`export type ConfigSpec = typeof ${configMatcherName}._TYPE;`,
|
||||||
)
|
)
|
||||||
|
const configName = newConst(
|
||||||
|
"ConfigSpec",
|
||||||
|
`${configNameRaw} as Config<ConfigSpec, WrapperData, ConfigSpec>`,
|
||||||
|
)
|
||||||
|
|
||||||
return outputLines.join("\n")
|
return outputLines.join("\n")
|
||||||
|
|
||||||
function newConst(key: string, data: string) {
|
function newConst(key: string, data: string, type?: string) {
|
||||||
const variableName = getNextConstName(camelCase(key))
|
const variableName = getNextConstName(camelCase(key))
|
||||||
outputLines.push(`export const ${variableName} = ${data};`)
|
outputLines.push(
|
||||||
|
`export const ${variableName}${!type ? "" : `: ${type}`} = ${data};`,
|
||||||
|
)
|
||||||
return variableName
|
return variableName
|
||||||
}
|
}
|
||||||
function maybeNewConst(key: string, data: string) {
|
function maybeNewConst(key: string, data: string) {
|
||||||
|
|||||||
Reference in New Issue
Block a user