mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
feat: builder-style InputSpec API, prefill plumbing, and port forward fix
- Add addKey() and add() builder methods to InputSpec with InputSpecTools - Move OuterType to last generic param on Value, List, and all dynamic methods - Plumb prefill through getActionInput end-to-end (core → container-runtime → SDK) - Filter port_forwards to enabled addresses only - Bump SDK to 0.4.0-beta.50
This commit is contained in:
@@ -4,12 +4,14 @@ import { _ } from '../../../util'
|
||||
import { Effects } from '../../../Effects'
|
||||
import { Parser, object } from 'ts-matches'
|
||||
import { DeepPartial } from '../../../types'
|
||||
import { InputSpecTools, createInputSpecTools } from './inputSpecTools'
|
||||
|
||||
export type LazyBuildOptions = {
|
||||
export type LazyBuildOptions<Type> = {
|
||||
effects: Effects
|
||||
prefill: DeepPartial<Type> | null
|
||||
}
|
||||
export type LazyBuild<ExpectedOut> = (
|
||||
options: LazyBuildOptions,
|
||||
export type LazyBuild<ExpectedOut, Type> = (
|
||||
options: LazyBuildOptions<Type>,
|
||||
) => Promise<ExpectedOut> | ExpectedOut
|
||||
|
||||
// prettier-ignore
|
||||
@@ -29,7 +31,7 @@ export type InputSpecOf<A extends Record<string, any>> = {
|
||||
[K in keyof A]: Value<A[K]>
|
||||
}
|
||||
|
||||
export type MaybeLazyValues<A> = LazyBuild<A> | A
|
||||
export type MaybeLazyValues<A, T> = LazyBuild<A, T> | A
|
||||
/**
|
||||
* InputSpecs are the specs that are used by the os input specification form for this service.
|
||||
* Here is an example of a simple input specification
|
||||
@@ -98,7 +100,7 @@ export class InputSpec<
|
||||
) {}
|
||||
public _TYPE: Type = null as any as Type
|
||||
public _PARTIAL: DeepPartial<Type> = null as any as DeepPartial<Type>
|
||||
async build(options: LazyBuildOptions): Promise<{
|
||||
async build<OuterType>(options: LazyBuildOptions<OuterType>): Promise<{
|
||||
spec: {
|
||||
[K in keyof Type]: ValueSpec
|
||||
}
|
||||
@@ -121,6 +123,57 @@ export class InputSpec<
|
||||
}
|
||||
}
|
||||
|
||||
addKey<Key extends string, V extends Value<any, any, any>>(
|
||||
key: Key,
|
||||
build: V | ((tools: InputSpecTools<Type>) => V),
|
||||
): InputSpec<
|
||||
Type & { [K in Key]: V extends Value<infer T, any, any> ? T : never },
|
||||
StaticValidatedAs & {
|
||||
[K in Key]: V extends Value<any, infer S, any> ? S : never
|
||||
}
|
||||
> {
|
||||
const value =
|
||||
build instanceof Function ? build(createInputSpecTools<Type>()) : build
|
||||
const newSpec = { ...this.spec, [key]: value } as any
|
||||
const newValidator = object(
|
||||
Object.fromEntries(
|
||||
Object.entries(newSpec).map(([k, v]) => [
|
||||
k,
|
||||
(v as Value<any>).validator,
|
||||
]),
|
||||
),
|
||||
)
|
||||
return new InputSpec(newSpec, newValidator as any)
|
||||
}
|
||||
|
||||
add<AddSpec extends Record<string, Value<any, any, any>>>(
|
||||
build: AddSpec | ((tools: InputSpecTools<Type>) => AddSpec),
|
||||
): InputSpec<
|
||||
Type & {
|
||||
[K in keyof AddSpec]: AddSpec[K] extends Value<infer T, any, any>
|
||||
? T
|
||||
: never
|
||||
},
|
||||
StaticValidatedAs & {
|
||||
[K in keyof AddSpec]: AddSpec[K] extends Value<any, infer S, any>
|
||||
? S
|
||||
: never
|
||||
}
|
||||
> {
|
||||
const addedValues =
|
||||
build instanceof Function ? build(createInputSpecTools<Type>()) : build
|
||||
const newSpec = { ...this.spec, ...addedValues } as any
|
||||
const newValidator = object(
|
||||
Object.fromEntries(
|
||||
Object.entries(newSpec).map(([k, v]) => [
|
||||
k,
|
||||
(v as Value<any>).validator,
|
||||
]),
|
||||
),
|
||||
)
|
||||
return new InputSpec(newSpec, newValidator as any)
|
||||
}
|
||||
|
||||
static of<Spec extends Record<string, Value<any, any>>>(spec: Spec) {
|
||||
const validator = object(
|
||||
Object.fromEntries(
|
||||
@@ -129,10 +182,14 @@ export class InputSpec<
|
||||
)
|
||||
return new InputSpec<
|
||||
{
|
||||
[K in keyof Spec]: Spec[K] extends Value<infer T, any> ? T : never
|
||||
[K in keyof Spec]: Spec[K] extends Value<infer T, any, unknown>
|
||||
? T
|
||||
: never
|
||||
},
|
||||
{
|
||||
[K in keyof Spec]: Spec[K] extends Value<any, infer T> ? T : never
|
||||
[K in keyof Spec]: Spec[K] extends Value<any, infer T, unknown>
|
||||
? T
|
||||
: never
|
||||
}
|
||||
>(spec, validator as any)
|
||||
}
|
||||
|
||||
274
sdk/base/lib/actions/input/builder/inputSpecTools.ts
Normal file
274
sdk/base/lib/actions/input/builder/inputSpecTools.ts
Normal file
@@ -0,0 +1,274 @@
|
||||
import { InputSpec, LazyBuild } from './inputSpec'
|
||||
import { AsRequired, FileInfo, Value } from './value'
|
||||
import { List } from './list'
|
||||
import { UnionRes, UnionResStaticValidatedAs, Variants } from './variants'
|
||||
import {
|
||||
Pattern,
|
||||
RandomString,
|
||||
ValueSpecDatetime,
|
||||
ValueSpecText,
|
||||
} from '../inputSpecTypes'
|
||||
import { DefaultString } from '../inputSpecTypes'
|
||||
import { Parser } from 'ts-matches'
|
||||
import { ListValueSpecText } from '../inputSpecTypes'
|
||||
|
||||
export interface InputSpecTools<OuterType> {
|
||||
Value: BoundValue<OuterType>
|
||||
Variants: typeof Variants
|
||||
InputSpec: typeof InputSpec
|
||||
List: BoundList<OuterType>
|
||||
}
|
||||
|
||||
export interface BoundValue<OuterType> {
|
||||
// Static (non-dynamic) methods — no OuterType involved
|
||||
toggle: typeof Value.toggle
|
||||
text: typeof Value.text
|
||||
textarea: typeof Value.textarea
|
||||
number: typeof Value.number
|
||||
color: typeof Value.color
|
||||
datetime: typeof Value.datetime
|
||||
select: typeof Value.select
|
||||
multiselect: typeof Value.multiselect
|
||||
object: typeof Value.object
|
||||
file: typeof Value.file
|
||||
list: typeof Value.list
|
||||
hidden: typeof Value.hidden
|
||||
union: typeof Value.union
|
||||
|
||||
// Dynamic methods with OuterType pre-bound (last generic param removed)
|
||||
dynamicToggle(
|
||||
a: LazyBuild<
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default: boolean
|
||||
disabled?: false | string
|
||||
},
|
||||
OuterType
|
||||
>,
|
||||
): Value<boolean, boolean, OuterType>
|
||||
|
||||
dynamicText<Required extends boolean>(
|
||||
getA: LazyBuild<
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default: DefaultString | null
|
||||
required: Required
|
||||
masked?: boolean
|
||||
placeholder?: string | null
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
patterns?: Pattern[]
|
||||
inputmode?: ValueSpecText['inputmode']
|
||||
disabled?: string | false
|
||||
generate?: null | RandomString
|
||||
},
|
||||
OuterType
|
||||
>,
|
||||
): Value<AsRequired<string, Required>, string | null, OuterType>
|
||||
|
||||
dynamicTextarea<Required extends boolean>(
|
||||
getA: LazyBuild<
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default: string | null
|
||||
required: Required
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
patterns?: Pattern[]
|
||||
minRows?: number
|
||||
maxRows?: number
|
||||
placeholder?: string | null
|
||||
disabled?: false | string
|
||||
},
|
||||
OuterType
|
||||
>,
|
||||
): Value<AsRequired<string, Required>, string | null, OuterType>
|
||||
|
||||
dynamicNumber<Required extends boolean>(
|
||||
getA: LazyBuild<
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default: number | null
|
||||
required: Required
|
||||
min?: number | null
|
||||
max?: number | null
|
||||
step?: number | null
|
||||
integer: boolean
|
||||
units?: string | null
|
||||
placeholder?: string | null
|
||||
disabled?: false | string
|
||||
},
|
||||
OuterType
|
||||
>,
|
||||
): Value<AsRequired<number, Required>, number | null, OuterType>
|
||||
|
||||
dynamicColor<Required extends boolean>(
|
||||
getA: LazyBuild<
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default: string | null
|
||||
required: Required
|
||||
disabled?: false | string
|
||||
},
|
||||
OuterType
|
||||
>,
|
||||
): Value<AsRequired<string, Required>, string | null, OuterType>
|
||||
|
||||
dynamicDatetime<Required extends boolean>(
|
||||
getA: LazyBuild<
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default: string | null
|
||||
required: Required
|
||||
inputmode?: ValueSpecDatetime['inputmode']
|
||||
min?: string | null
|
||||
max?: string | null
|
||||
disabled?: false | string
|
||||
},
|
||||
OuterType
|
||||
>,
|
||||
): Value<AsRequired<string, Required>, string | null, OuterType>
|
||||
|
||||
dynamicSelect<Values extends Record<string, string>>(
|
||||
getA: LazyBuild<
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default: string
|
||||
values: Values
|
||||
disabled?: false | string | string[]
|
||||
},
|
||||
OuterType
|
||||
>,
|
||||
): Value<keyof Values & string, keyof Values & string, OuterType>
|
||||
|
||||
dynamicMultiselect<Values extends Record<string, string>>(
|
||||
getA: LazyBuild<
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default: string[]
|
||||
values: Values
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
disabled?: false | string | string[]
|
||||
},
|
||||
OuterType
|
||||
>,
|
||||
): Value<(keyof Values & string)[], (keyof Values & string)[], OuterType>
|
||||
|
||||
dynamicFile<Required extends boolean>(
|
||||
a: LazyBuild<
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
extensions: string[]
|
||||
required: Required
|
||||
},
|
||||
OuterType
|
||||
>,
|
||||
): Value<AsRequired<FileInfo, Required>, FileInfo | null, OuterType>
|
||||
|
||||
dynamicUnion<
|
||||
VariantValues extends {
|
||||
[K in string]: {
|
||||
name: string
|
||||
spec: InputSpec<any>
|
||||
}
|
||||
},
|
||||
>(
|
||||
getA: LazyBuild<
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
variants: Variants<VariantValues>
|
||||
default: keyof VariantValues & string
|
||||
disabled: string[] | false | string
|
||||
},
|
||||
OuterType
|
||||
>,
|
||||
): Value<UnionRes<VariantValues>, UnionRes<VariantValues>, OuterType>
|
||||
dynamicUnion<
|
||||
StaticVariantValues extends {
|
||||
[K in string]: {
|
||||
name: string
|
||||
spec: InputSpec<any, any>
|
||||
}
|
||||
},
|
||||
VariantValues extends StaticVariantValues,
|
||||
>(
|
||||
getA: LazyBuild<
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
variants: Variants<VariantValues>
|
||||
default: keyof VariantValues & string
|
||||
disabled: string[] | false | string
|
||||
},
|
||||
OuterType
|
||||
>,
|
||||
validator: Parser<unknown, UnionResStaticValidatedAs<StaticVariantValues>>,
|
||||
): Value<
|
||||
UnionRes<VariantValues>,
|
||||
UnionResStaticValidatedAs<StaticVariantValues>,
|
||||
OuterType
|
||||
>
|
||||
|
||||
dynamicHidden<T>(
|
||||
getParser: LazyBuild<Parser<unknown, T>, OuterType>,
|
||||
): Value<T, T, OuterType>
|
||||
}
|
||||
|
||||
export interface BoundList<OuterType> {
|
||||
text: typeof List.text
|
||||
obj: typeof List.obj
|
||||
dynamicText(
|
||||
getA: LazyBuild<
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default?: string[]
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
disabled?: false | string
|
||||
generate?: null | RandomString
|
||||
spec: {
|
||||
masked?: boolean
|
||||
placeholder?: string | null
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
patterns?: Pattern[]
|
||||
inputmode?: ListValueSpecText['inputmode']
|
||||
}
|
||||
},
|
||||
OuterType
|
||||
>,
|
||||
): List<string[], string[], OuterType>
|
||||
}
|
||||
|
||||
export function createInputSpecTools<OuterType>(): InputSpecTools<OuterType> {
|
||||
return {
|
||||
Value: Value as any as BoundValue<OuterType>,
|
||||
Variants,
|
||||
InputSpec,
|
||||
List: List as any as BoundList<OuterType>,
|
||||
}
|
||||
}
|
||||
@@ -9,12 +9,19 @@ import {
|
||||
} from '../inputSpecTypes'
|
||||
import { Parser, arrayOf, string } from 'ts-matches'
|
||||
|
||||
export class List<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
|
||||
export class List<
|
||||
Type extends StaticValidatedAs,
|
||||
StaticValidatedAs = Type,
|
||||
OuterType = unknown,
|
||||
> {
|
||||
private constructor(
|
||||
public build: LazyBuild<{
|
||||
spec: ValueSpecList
|
||||
validator: Parser<unknown, Type>
|
||||
}>,
|
||||
public build: LazyBuild<
|
||||
{
|
||||
spec: ValueSpecList
|
||||
validator: Parser<unknown, Type>
|
||||
},
|
||||
OuterType
|
||||
>,
|
||||
public readonly validator: Parser<unknown, StaticValidatedAs>,
|
||||
) {}
|
||||
readonly _TYPE: Type = null as any
|
||||
@@ -90,28 +97,31 @@ export class List<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
|
||||
}, validator)
|
||||
}
|
||||
|
||||
static dynamicText(
|
||||
getA: LazyBuild<{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default?: string[]
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
disabled?: false | string
|
||||
generate?: null | RandomString
|
||||
spec: {
|
||||
masked?: boolean
|
||||
placeholder?: string | null
|
||||
static dynamicText<OuterType = unknown>(
|
||||
getA: LazyBuild<
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default?: string[]
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
patterns?: Pattern[]
|
||||
inputmode?: ListValueSpecText['inputmode']
|
||||
}
|
||||
}>,
|
||||
disabled?: false | string
|
||||
generate?: null | RandomString
|
||||
spec: {
|
||||
masked?: boolean
|
||||
placeholder?: string | null
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
patterns?: Pattern[]
|
||||
inputmode?: ListValueSpecText['inputmode']
|
||||
}
|
||||
},
|
||||
OuterType
|
||||
>,
|
||||
) {
|
||||
const validator = arrayOf(string)
|
||||
return new List<string[]>(async (options) => {
|
||||
return new List<string[], string[], OuterType>(async (options) => {
|
||||
const { spec: aSpec, ...a } = await getA(options)
|
||||
const spec = {
|
||||
type: 'text' as const,
|
||||
|
||||
@@ -32,7 +32,7 @@ export const fileInfoParser = object({
|
||||
})
|
||||
export type FileInfo = typeof fileInfoParser._TYPE
|
||||
|
||||
type AsRequired<T, Required extends boolean> = Required extends true
|
||||
export type AsRequired<T, Required extends boolean> = Required extends true
|
||||
? T
|
||||
: T | null
|
||||
|
||||
@@ -47,12 +47,19 @@ function asRequiredParser<Type, Input extends { required: boolean }>(
|
||||
return parser.nullable() as any
|
||||
}
|
||||
|
||||
export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
|
||||
export class Value<
|
||||
Type extends StaticValidatedAs,
|
||||
StaticValidatedAs = Type,
|
||||
OuterType = unknown,
|
||||
> {
|
||||
protected constructor(
|
||||
public build: LazyBuild<{
|
||||
spec: ValueSpec
|
||||
validator: Parser<unknown, Type>
|
||||
}>,
|
||||
public build: LazyBuild<
|
||||
{
|
||||
spec: ValueSpec
|
||||
validator: Parser<unknown, Type>
|
||||
},
|
||||
OuterType
|
||||
>,
|
||||
public readonly validator: Parser<unknown, StaticValidatedAs>,
|
||||
) {}
|
||||
public _TYPE: Type = null as any as Type
|
||||
@@ -102,17 +109,20 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
|
||||
validator,
|
||||
)
|
||||
}
|
||||
static dynamicToggle(
|
||||
a: LazyBuild<{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default: boolean
|
||||
disabled?: false | string
|
||||
}>,
|
||||
static dynamicToggle<OuterType = unknown>(
|
||||
a: LazyBuild<
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default: boolean
|
||||
disabled?: false | string
|
||||
},
|
||||
OuterType
|
||||
>,
|
||||
) {
|
||||
const validator = boolean
|
||||
return new Value<boolean>(
|
||||
return new Value<boolean, boolean, OuterType>(
|
||||
async (options) => ({
|
||||
spec: {
|
||||
description: null,
|
||||
@@ -225,24 +235,27 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
|
||||
validator,
|
||||
)
|
||||
}
|
||||
static dynamicText<Required extends boolean>(
|
||||
getA: LazyBuild<{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default: DefaultString | null
|
||||
required: Required
|
||||
masked?: boolean
|
||||
placeholder?: string | null
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
patterns?: Pattern[]
|
||||
inputmode?: ValueSpecText['inputmode']
|
||||
disabled?: string | false
|
||||
generate?: null | RandomString
|
||||
}>,
|
||||
static dynamicText<Required extends boolean, OuterType = unknown>(
|
||||
getA: LazyBuild<
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default: DefaultString | null
|
||||
required: Required
|
||||
masked?: boolean
|
||||
placeholder?: string | null
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
patterns?: Pattern[]
|
||||
inputmode?: ValueSpecText['inputmode']
|
||||
disabled?: string | false
|
||||
generate?: null | RandomString
|
||||
},
|
||||
OuterType
|
||||
>,
|
||||
) {
|
||||
return new Value<AsRequired<string, Required>, string | null>(
|
||||
return new Value<AsRequired<string, Required>, string | null, OuterType>(
|
||||
async (options) => {
|
||||
const a = await getA(options)
|
||||
return {
|
||||
@@ -342,23 +355,26 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
|
||||
return { spec: built, validator }
|
||||
}, validator)
|
||||
}
|
||||
static dynamicTextarea<Required extends boolean>(
|
||||
getA: LazyBuild<{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default: string | null
|
||||
required: Required
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
patterns?: Pattern[]
|
||||
minRows?: number
|
||||
maxRows?: number
|
||||
placeholder?: string | null
|
||||
disabled?: false | string
|
||||
}>,
|
||||
static dynamicTextarea<Required extends boolean, OuterType = unknown>(
|
||||
getA: LazyBuild<
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default: string | null
|
||||
required: Required
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
patterns?: Pattern[]
|
||||
minRows?: number
|
||||
maxRows?: number
|
||||
placeholder?: string | null
|
||||
disabled?: false | string
|
||||
},
|
||||
OuterType
|
||||
>,
|
||||
) {
|
||||
return new Value<AsRequired<string, Required>, string | null>(
|
||||
return new Value<AsRequired<string, Required>, string | null, OuterType>(
|
||||
async (options) => {
|
||||
const a = await getA(options)
|
||||
return {
|
||||
@@ -461,23 +477,26 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
|
||||
validator,
|
||||
)
|
||||
}
|
||||
static dynamicNumber<Required extends boolean>(
|
||||
getA: LazyBuild<{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default: number | null
|
||||
required: Required
|
||||
min?: number | null
|
||||
max?: number | null
|
||||
step?: number | null
|
||||
integer: boolean
|
||||
units?: string | null
|
||||
placeholder?: string | null
|
||||
disabled?: false | string
|
||||
}>,
|
||||
static dynamicNumber<Required extends boolean, OuterType = unknown>(
|
||||
getA: LazyBuild<
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default: number | null
|
||||
required: Required
|
||||
min?: number | null
|
||||
max?: number | null
|
||||
step?: number | null
|
||||
integer: boolean
|
||||
units?: string | null
|
||||
placeholder?: string | null
|
||||
disabled?: false | string
|
||||
},
|
||||
OuterType
|
||||
>,
|
||||
) {
|
||||
return new Value<AsRequired<number, Required>, number | null>(
|
||||
return new Value<AsRequired<number, Required>, number | null, OuterType>(
|
||||
async (options) => {
|
||||
const a = await getA(options)
|
||||
return {
|
||||
@@ -553,17 +572,20 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
|
||||
)
|
||||
}
|
||||
|
||||
static dynamicColor<Required extends boolean>(
|
||||
getA: LazyBuild<{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default: string | null
|
||||
required: Required
|
||||
disabled?: false | string
|
||||
}>,
|
||||
static dynamicColor<Required extends boolean, OuterType = unknown>(
|
||||
getA: LazyBuild<
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default: string | null
|
||||
required: Required
|
||||
disabled?: false | string
|
||||
},
|
||||
OuterType
|
||||
>,
|
||||
) {
|
||||
return new Value<AsRequired<string, Required>, string | null>(
|
||||
return new Value<AsRequired<string, Required>, string | null, OuterType>(
|
||||
async (options) => {
|
||||
const a = await getA(options)
|
||||
return {
|
||||
@@ -647,20 +669,23 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
|
||||
validator,
|
||||
)
|
||||
}
|
||||
static dynamicDatetime<Required extends boolean>(
|
||||
getA: LazyBuild<{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default: string | null
|
||||
required: Required
|
||||
inputmode?: ValueSpecDatetime['inputmode']
|
||||
min?: string | null
|
||||
max?: string | null
|
||||
disabled?: false | string
|
||||
}>,
|
||||
static dynamicDatetime<Required extends boolean, OuterType = unknown>(
|
||||
getA: LazyBuild<
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default: string | null
|
||||
required: Required
|
||||
inputmode?: ValueSpecDatetime['inputmode']
|
||||
min?: string | null
|
||||
max?: string | null
|
||||
disabled?: false | string
|
||||
},
|
||||
OuterType
|
||||
>,
|
||||
) {
|
||||
return new Value<AsRequired<string, Required>, string | null>(
|
||||
return new Value<AsRequired<string, Required>, string | null, OuterType>(
|
||||
async (options) => {
|
||||
const a = await getA(options)
|
||||
return {
|
||||
@@ -750,34 +775,43 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
|
||||
validator,
|
||||
)
|
||||
}
|
||||
static dynamicSelect<Values extends Record<string, string>>(
|
||||
getA: LazyBuild<{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default: string
|
||||
values: Values
|
||||
disabled?: false | string | string[]
|
||||
}>,
|
||||
static dynamicSelect<
|
||||
Values extends Record<string, string>,
|
||||
OuterType = unknown,
|
||||
>(
|
||||
getA: LazyBuild<
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default: string
|
||||
values: Values
|
||||
disabled?: false | string | string[]
|
||||
},
|
||||
OuterType
|
||||
>,
|
||||
) {
|
||||
return new Value<keyof Values & string, string>(async (options) => {
|
||||
const a = await getA(options)
|
||||
return {
|
||||
spec: {
|
||||
description: null,
|
||||
warning: null,
|
||||
type: 'select' as const,
|
||||
disabled: false,
|
||||
immutable: false,
|
||||
...a,
|
||||
},
|
||||
validator: anyOf(
|
||||
...Object.keys(a.values).map((x: keyof Values & string) =>
|
||||
literal(x),
|
||||
return new Value<keyof Values & string, keyof Values & string, OuterType>(
|
||||
async (options) => {
|
||||
const a = await getA(options)
|
||||
return {
|
||||
spec: {
|
||||
description: null,
|
||||
warning: null,
|
||||
type: 'select' as const,
|
||||
disabled: false,
|
||||
immutable: false,
|
||||
...a,
|
||||
},
|
||||
validator: anyOf(
|
||||
...Object.keys(a.values).map((x: keyof Values & string) =>
|
||||
literal(x),
|
||||
),
|
||||
),
|
||||
),
|
||||
}
|
||||
}, string)
|
||||
}
|
||||
},
|
||||
string,
|
||||
)
|
||||
}
|
||||
/**
|
||||
* @description Displays a select modal with checkboxes, allowing for multiple selections.
|
||||
@@ -851,19 +885,29 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
|
||||
validator,
|
||||
)
|
||||
}
|
||||
static dynamicMultiselect<Values extends Record<string, string>>(
|
||||
getA: LazyBuild<{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default: string[]
|
||||
values: Values
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
disabled?: false | string | string[]
|
||||
}>,
|
||||
static dynamicMultiselect<
|
||||
Values extends Record<string, string>,
|
||||
OuterType = unknown,
|
||||
>(
|
||||
getA: LazyBuild<
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default: string[]
|
||||
values: Values
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
disabled?: false | string | string[]
|
||||
},
|
||||
OuterType
|
||||
>,
|
||||
) {
|
||||
return new Value<(keyof Values & string)[], string[]>(async (options) => {
|
||||
return new Value<
|
||||
(keyof Values & string)[],
|
||||
(keyof Values & string)[],
|
||||
OuterType
|
||||
>(async (options) => {
|
||||
const a = await getA(options)
|
||||
return {
|
||||
spec: {
|
||||
@@ -948,30 +992,34 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
|
||||
asRequiredParser(fileInfoParser, a),
|
||||
)
|
||||
}
|
||||
static dynamicFile<Required extends boolean>(
|
||||
a: LazyBuild<{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
extensions: string[]
|
||||
required: Required
|
||||
}>,
|
||||
) {
|
||||
return new Value<AsRequired<FileInfo, Required>, FileInfo | null>(
|
||||
async (options) => {
|
||||
const spec = {
|
||||
type: 'file' as const,
|
||||
description: null,
|
||||
warning: null,
|
||||
...(await a(options)),
|
||||
}
|
||||
return {
|
||||
spec,
|
||||
validator: asRequiredParser(fileInfoParser, spec),
|
||||
}
|
||||
static dynamicFile<Required extends boolean, OuterType = unknown>(
|
||||
a: LazyBuild<
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
extensions: string[]
|
||||
required: Required
|
||||
},
|
||||
fileInfoParser.nullable(),
|
||||
)
|
||||
OuterType
|
||||
>,
|
||||
) {
|
||||
return new Value<
|
||||
AsRequired<FileInfo, Required>,
|
||||
FileInfo | null,
|
||||
OuterType
|
||||
>(async (options) => {
|
||||
const spec = {
|
||||
type: 'file' as const,
|
||||
description: null,
|
||||
warning: null,
|
||||
...(await a(options)),
|
||||
}
|
||||
return {
|
||||
spec,
|
||||
validator: asRequiredParser(fileInfoParser, spec),
|
||||
}
|
||||
}, fileInfoParser.nullable())
|
||||
}
|
||||
/**
|
||||
* @description Displays a dropdown, allowing for a single selection. Depending on the selection, a different object ("sub form") is presented.
|
||||
@@ -1053,37 +1101,46 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
|
||||
spec: InputSpec<any>
|
||||
}
|
||||
},
|
||||
OuterType = unknown,
|
||||
>(
|
||||
getA: LazyBuild<{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
variants: Variants<VariantValues>
|
||||
default: keyof VariantValues & string
|
||||
disabled: string[] | false | string
|
||||
}>,
|
||||
): Value<UnionRes<VariantValues>, unknown>
|
||||
getA: LazyBuild<
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
variants: Variants<VariantValues>
|
||||
default: keyof VariantValues & string
|
||||
disabled: string[] | false | string
|
||||
},
|
||||
OuterType
|
||||
>,
|
||||
): Value<UnionRes<VariantValues>, UnionRes<VariantValues>, OuterType>
|
||||
static dynamicUnion<
|
||||
VariantValues extends StaticVariantValues,
|
||||
StaticVariantValues extends {
|
||||
[K in string]: {
|
||||
name: string
|
||||
spec: InputSpec<any, any>
|
||||
}
|
||||
},
|
||||
VariantValues extends StaticVariantValues,
|
||||
OuterType = unknown,
|
||||
>(
|
||||
getA: LazyBuild<{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
variants: Variants<VariantValues>
|
||||
default: keyof VariantValues & string
|
||||
disabled: string[] | false | string
|
||||
}>,
|
||||
getA: LazyBuild<
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
variants: Variants<VariantValues>
|
||||
default: keyof VariantValues & string
|
||||
disabled: string[] | false | string
|
||||
},
|
||||
OuterType
|
||||
>,
|
||||
validator: Parser<unknown, UnionResStaticValidatedAs<StaticVariantValues>>,
|
||||
): Value<
|
||||
UnionRes<VariantValues>,
|
||||
UnionResStaticValidatedAs<StaticVariantValues>
|
||||
UnionResStaticValidatedAs<StaticVariantValues>,
|
||||
OuterType
|
||||
>
|
||||
static dynamicUnion<
|
||||
VariantValues extends {
|
||||
@@ -1092,35 +1149,40 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
|
||||
spec: InputSpec<any>
|
||||
}
|
||||
},
|
||||
OuterType = unknown,
|
||||
>(
|
||||
getA: LazyBuild<{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
variants: Variants<VariantValues>
|
||||
default: keyof VariantValues & string
|
||||
disabled: string[] | false | string
|
||||
}>,
|
||||
getA: LazyBuild<
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
variants: Variants<VariantValues>
|
||||
default: keyof VariantValues & string
|
||||
disabled: string[] | false | string
|
||||
},
|
||||
OuterType
|
||||
>,
|
||||
validator: Parser<unknown, unknown> = any,
|
||||
) {
|
||||
return new Value<UnionRes<VariantValues>, typeof validator._TYPE>(
|
||||
async (options) => {
|
||||
const newValues = await getA(options)
|
||||
const built = await newValues.variants.build(options as any)
|
||||
return {
|
||||
spec: {
|
||||
type: 'union' as const,
|
||||
description: null,
|
||||
warning: null,
|
||||
...newValues,
|
||||
variants: built.spec,
|
||||
immutable: false,
|
||||
},
|
||||
validator: built.validator,
|
||||
}
|
||||
},
|
||||
validator,
|
||||
)
|
||||
return new Value<
|
||||
UnionRes<VariantValues>,
|
||||
typeof validator._TYPE,
|
||||
OuterType
|
||||
>(async (options) => {
|
||||
const newValues = await getA(options)
|
||||
const built = await newValues.variants.build(options as any)
|
||||
return {
|
||||
spec: {
|
||||
type: 'union' as const,
|
||||
description: null,
|
||||
warning: null,
|
||||
...newValues,
|
||||
variants: built.spec,
|
||||
immutable: false,
|
||||
},
|
||||
validator: built.validator,
|
||||
}
|
||||
}, validator)
|
||||
}
|
||||
/**
|
||||
* @description Presents an interface to add/remove/edit items in a list.
|
||||
@@ -1196,7 +1258,7 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
|
||||
hiddenExample: Value.hidden(),
|
||||
* ```
|
||||
*/
|
||||
static hidden<T>(): Value<T, unknown>
|
||||
static hidden<T>(): Value<T>
|
||||
static hidden<T>(parser: Parser<unknown, T>): Value<T>
|
||||
static hidden<T>(parser: Parser<unknown, T> = any) {
|
||||
return new Value<T, typeof parser._TYPE>(async () => {
|
||||
@@ -1216,8 +1278,10 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
|
||||
hiddenExample: Value.hidden(),
|
||||
* ```
|
||||
*/
|
||||
static dynamicHidden<T>(getParser: LazyBuild<Parser<unknown, T>>) {
|
||||
return new Value<T, unknown>(async (options) => {
|
||||
static dynamicHidden<T, OuterType = unknown>(
|
||||
getParser: LazyBuild<Parser<unknown, T>, OuterType>,
|
||||
) {
|
||||
return new Value<T, T, OuterType>(async (options) => {
|
||||
const validator = await getParser(options)
|
||||
return {
|
||||
spec: {
|
||||
@@ -1228,9 +1292,9 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
|
||||
}, any)
|
||||
}
|
||||
|
||||
map<U>(fn: (value: StaticValidatedAs) => U): Value<U> {
|
||||
return new Value(async (effects) => {
|
||||
const built = await this.build(effects)
|
||||
map<U>(fn: (value: StaticValidatedAs) => U): Value<U, U, OuterType> {
|
||||
return new Value<U, U, OuterType>(async (options) => {
|
||||
const built = await this.build(options)
|
||||
return {
|
||||
spec: built.spec,
|
||||
validator: built.validator.map(fn),
|
||||
|
||||
@@ -103,12 +103,16 @@ export class Variants<
|
||||
spec: InputSpec<any, any>
|
||||
}
|
||||
},
|
||||
OuterType = unknown,
|
||||
> {
|
||||
private constructor(
|
||||
public build: LazyBuild<{
|
||||
spec: ValueSpecUnion['variants']
|
||||
validator: Parser<unknown, UnionRes<VariantValues>>
|
||||
}>,
|
||||
public build: LazyBuild<
|
||||
{
|
||||
spec: ValueSpecUnion['variants']
|
||||
validator: Parser<unknown, UnionRes<VariantValues>>
|
||||
},
|
||||
OuterType
|
||||
>,
|
||||
public readonly validator: Parser<
|
||||
unknown,
|
||||
UnionResStaticValidatedAs<VariantValues>
|
||||
|
||||
@@ -13,6 +13,7 @@ export type Run<A extends Record<string, any>> = (options: {
|
||||
}) => Promise<(T.ActionResult & { version: '1' }) | null | void | undefined>
|
||||
export type GetInput<A extends Record<string, any>> = (options: {
|
||||
effects: T.Effects
|
||||
prefill: T.DeepPartial<A> | null
|
||||
}) => Promise<null | void | undefined | T.DeepPartial<A>>
|
||||
|
||||
export type MaybeFn<T> = T | ((options: { effects: T.Effects }) => Promise<T>)
|
||||
@@ -104,7 +105,10 @@ export class Action<Id extends T.ActionId, Type extends Record<string, any>>
|
||||
await options.effects.action.export({ id: this.id, metadata })
|
||||
return metadata
|
||||
}
|
||||
async getInput(options: { effects: T.Effects }): Promise<T.ActionInput> {
|
||||
async getInput(options: {
|
||||
effects: T.Effects
|
||||
prefill: T.DeepPartial<Type> | null
|
||||
}): Promise<T.ActionInput> {
|
||||
let spec = {}
|
||||
if (this.inputSpec) {
|
||||
const built = await this.inputSpec.build(options)
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
import type { ActionId } from './ActionId'
|
||||
import type { PackageId } from './PackageId'
|
||||
|
||||
export type GetActionInputParams = { packageId?: PackageId; actionId: ActionId }
|
||||
export type GetActionInputParams = {
|
||||
packageId?: PackageId
|
||||
actionId: ActionId
|
||||
prefill: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user