mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +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:
2
container-runtime/package-lock.json
generated
2
container-runtime/package-lock.json
generated
@@ -38,7 +38,7 @@
|
|||||||
},
|
},
|
||||||
"../sdk/dist": {
|
"../sdk/dist": {
|
||||||
"name": "@start9labs/start-sdk",
|
"name": "@start9labs/start-sdk",
|
||||||
"version": "0.4.0-beta.49",
|
"version": "0.4.0-beta.50",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iarna/toml": "^3.0.0",
|
"@iarna/toml": "^3.0.0",
|
||||||
|
|||||||
@@ -437,6 +437,7 @@ export class RpcListener {
|
|||||||
return system.getActionInput(
|
return system.getActionInput(
|
||||||
effects,
|
effects,
|
||||||
procedures[2],
|
procedures[2],
|
||||||
|
input?.prefill ?? null,
|
||||||
timeout || null,
|
timeout || null,
|
||||||
)
|
)
|
||||||
case procedures[1] === "actions" && procedures[3] === "run":
|
case procedures[1] === "actions" && procedures[3] === "run":
|
||||||
|
|||||||
@@ -510,6 +510,7 @@ export class SystemForEmbassy implements System {
|
|||||||
async getActionInput(
|
async getActionInput(
|
||||||
effects: Effects,
|
effects: Effects,
|
||||||
actionId: string,
|
actionId: string,
|
||||||
|
_prefill: Record<string, unknown> | null,
|
||||||
timeoutMs: number | null,
|
timeoutMs: number | null,
|
||||||
): Promise<T.ActionInput | null> {
|
): Promise<T.ActionInput | null> {
|
||||||
if (actionId === "config") {
|
if (actionId === "config") {
|
||||||
|
|||||||
@@ -47,11 +47,12 @@ export class SystemForStartOs implements System {
|
|||||||
getActionInput(
|
getActionInput(
|
||||||
effects: Effects,
|
effects: Effects,
|
||||||
id: string,
|
id: string,
|
||||||
|
prefill: Record<string, unknown> | null,
|
||||||
timeoutMs: number | null,
|
timeoutMs: number | null,
|
||||||
): Promise<T.ActionInput | null> {
|
): Promise<T.ActionInput | null> {
|
||||||
const action = this.abi.actions.get(id)
|
const action = this.abi.actions.get(id)
|
||||||
if (!action) throw new Error(`Action ${id} not found`)
|
if (!action) throw new Error(`Action ${id} not found`)
|
||||||
return action.getInput({ effects })
|
return action.getInput({ effects, prefill })
|
||||||
}
|
}
|
||||||
runAction(
|
runAction(
|
||||||
effects: Effects,
|
effects: Effects,
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ export type System = {
|
|||||||
getActionInput(
|
getActionInput(
|
||||||
effects: Effects,
|
effects: Effects,
|
||||||
actionId: string,
|
actionId: string,
|
||||||
|
prefill: Record<string, unknown> | null,
|
||||||
timeoutMs: number | null,
|
timeoutMs: number | null,
|
||||||
): Promise<T.ActionInput | null>
|
): Promise<T.ActionInput | null>
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,10 @@ pub struct GetActionInputParams {
|
|||||||
pub package_id: PackageId,
|
pub package_id: PackageId,
|
||||||
#[arg(help = "help.arg.action-id")]
|
#[arg(help = "help.arg.action-id")]
|
||||||
pub action_id: ActionId,
|
pub action_id: ActionId,
|
||||||
|
#[ts(type = "Record<string, unknown> | null")]
|
||||||
|
#[serde(default)]
|
||||||
|
#[arg(skip)]
|
||||||
|
pub prefill: Option<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
@@ -75,6 +79,7 @@ pub async fn get_action_input(
|
|||||||
GetActionInputParams {
|
GetActionInputParams {
|
||||||
package_id,
|
package_id,
|
||||||
action_id,
|
action_id,
|
||||||
|
prefill,
|
||||||
}: GetActionInputParams,
|
}: GetActionInputParams,
|
||||||
) -> Result<Option<ActionInput>, Error> {
|
) -> Result<Option<ActionInput>, Error> {
|
||||||
ctx.services
|
ctx.services
|
||||||
@@ -82,7 +87,7 @@ pub async fn get_action_input(
|
|||||||
.await
|
.await
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.or_not_found(lazy_format!("Manager for {}", package_id))?
|
.or_not_found(lazy_format!("Manager for {}", package_id))?
|
||||||
.get_action_input(Guid::new(), action_id)
|
.get_action_input(Guid::new(), action_id, prefill.unwrap_or(Value::Null))
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -533,7 +533,7 @@ impl RpcContext {
|
|||||||
for (package_id, action_id) in tasks {
|
for (package_id, action_id) in tasks {
|
||||||
if let Some(service) = self.services.get(&package_id).await.as_ref() {
|
if let Some(service) = self.services.get(&package_id).await.as_ref() {
|
||||||
if let Some(input) = service
|
if let Some(input) = service
|
||||||
.get_action_input(procedure_id.clone(), action_id.clone())
|
.get_action_input(procedure_id.clone(), action_id.clone(), Value::Null)
|
||||||
.await
|
.await
|
||||||
.log_err()
|
.log_err()
|
||||||
.flatten()
|
.flatten()
|
||||||
|
|||||||
@@ -298,7 +298,7 @@ impl Model<Host> {
|
|||||||
let bindings: Bindings = this.bindings.de()?;
|
let bindings: Bindings = this.bindings.de()?;
|
||||||
let mut port_forwards = BTreeSet::new();
|
let mut port_forwards = BTreeSet::new();
|
||||||
for bind in bindings.values() {
|
for bind in bindings.values() {
|
||||||
for addr in &bind.addresses.available {
|
for addr in bind.addresses.enabled() {
|
||||||
if !addr.public {
|
if !addr.public {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ use crate::{ActionId, PackageId, ReplayId};
|
|||||||
|
|
||||||
pub(super) struct GetActionInput {
|
pub(super) struct GetActionInput {
|
||||||
id: ActionId,
|
id: ActionId,
|
||||||
|
prefill: Value,
|
||||||
}
|
}
|
||||||
impl Handler<GetActionInput> for ServiceActor {
|
impl Handler<GetActionInput> for ServiceActor {
|
||||||
type Response = Result<Option<ActionInput>, Error>;
|
type Response = Result<Option<ActionInput>, Error>;
|
||||||
@@ -26,7 +27,10 @@ impl Handler<GetActionInput> for ServiceActor {
|
|||||||
async fn handle(
|
async fn handle(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: Guid,
|
id: Guid,
|
||||||
GetActionInput { id: action_id }: GetActionInput,
|
GetActionInput {
|
||||||
|
id: action_id,
|
||||||
|
prefill,
|
||||||
|
}: GetActionInput,
|
||||||
_: &BackgroundJobQueue,
|
_: &BackgroundJobQueue,
|
||||||
) -> Self::Response {
|
) -> Self::Response {
|
||||||
let container = &self.0.persistent_container;
|
let container = &self.0.persistent_container;
|
||||||
@@ -34,7 +38,7 @@ impl Handler<GetActionInput> for ServiceActor {
|
|||||||
.execute::<Option<ActionInput>>(
|
.execute::<Option<ActionInput>>(
|
||||||
id,
|
id,
|
||||||
ProcedureName::GetActionInput(action_id),
|
ProcedureName::GetActionInput(action_id),
|
||||||
Value::Null,
|
json!({ "prefill": prefill }),
|
||||||
Some(Duration::from_secs(30)),
|
Some(Duration::from_secs(30)),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -47,6 +51,7 @@ impl Service {
|
|||||||
&self,
|
&self,
|
||||||
id: Guid,
|
id: Guid,
|
||||||
action_id: ActionId,
|
action_id: ActionId,
|
||||||
|
prefill: Value,
|
||||||
) -> Result<Option<ActionInput>, Error> {
|
) -> Result<Option<ActionInput>, Error> {
|
||||||
if !self
|
if !self
|
||||||
.seed
|
.seed
|
||||||
@@ -67,7 +72,7 @@ impl Service {
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
self.actor
|
self.actor
|
||||||
.send(id, GetActionInput { id: action_id })
|
.send(id, GetActionInput { id: action_id, prefill })
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,6 +122,10 @@ pub struct GetActionInputParams {
|
|||||||
package_id: Option<PackageId>,
|
package_id: Option<PackageId>,
|
||||||
#[arg(help = "help.arg.action-id")]
|
#[arg(help = "help.arg.action-id")]
|
||||||
action_id: ActionId,
|
action_id: ActionId,
|
||||||
|
#[ts(type = "Record<string, unknown> | null")]
|
||||||
|
#[serde(default)]
|
||||||
|
#[arg(skip)]
|
||||||
|
prefill: Option<Value>,
|
||||||
}
|
}
|
||||||
async fn get_action_input(
|
async fn get_action_input(
|
||||||
context: EffectContext,
|
context: EffectContext,
|
||||||
@@ -129,9 +133,11 @@ async fn get_action_input(
|
|||||||
procedure_id,
|
procedure_id,
|
||||||
package_id,
|
package_id,
|
||||||
action_id,
|
action_id,
|
||||||
|
prefill,
|
||||||
}: GetActionInputParams,
|
}: GetActionInputParams,
|
||||||
) -> Result<Option<ActionInput>, Error> {
|
) -> Result<Option<ActionInput>, Error> {
|
||||||
let context = context.deref()?;
|
let context = context.deref()?;
|
||||||
|
let prefill = prefill.unwrap_or(Value::Null);
|
||||||
|
|
||||||
if let Some(package_id) = package_id {
|
if let Some(package_id) = package_id {
|
||||||
context
|
context
|
||||||
@@ -142,10 +148,10 @@ async fn get_action_input(
|
|||||||
.await
|
.await
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.or_not_found(&package_id)?
|
.or_not_found(&package_id)?
|
||||||
.get_action_input(procedure_id, action_id)
|
.get_action_input(procedure_id, action_id, prefill)
|
||||||
.await
|
.await
|
||||||
} else {
|
} else {
|
||||||
context.get_action_input(procedure_id, action_id).await
|
context.get_action_input(procedure_id, action_id, prefill).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,7 +251,7 @@ async fn create_task(
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
{
|
{
|
||||||
let Some(prev) = service
|
let Some(prev) = service
|
||||||
.get_action_input(procedure_id.clone(), task.action_id.clone())
|
.get_action_input(procedure_id.clone(), task.action_id.clone(), Value::Null)
|
||||||
.await?
|
.await?
|
||||||
else {
|
else {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
|
|||||||
@@ -534,7 +534,7 @@ impl Service {
|
|||||||
.contains_key(&action_id)?
|
.contains_key(&action_id)?
|
||||||
{
|
{
|
||||||
if let Some(input) = service
|
if let Some(input) = service
|
||||||
.get_action_input(procedure_id.clone(), action_id.clone())
|
.get_action_input(procedure_id.clone(), action_id.clone(), Value::Null)
|
||||||
.await
|
.await
|
||||||
.log_err()
|
.log_err()
|
||||||
.flatten()
|
.flatten()
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ import { _ } from '../../../util'
|
|||||||
import { Effects } from '../../../Effects'
|
import { Effects } from '../../../Effects'
|
||||||
import { Parser, object } from 'ts-matches'
|
import { Parser, object } from 'ts-matches'
|
||||||
import { DeepPartial } from '../../../types'
|
import { DeepPartial } from '../../../types'
|
||||||
|
import { InputSpecTools, createInputSpecTools } from './inputSpecTools'
|
||||||
|
|
||||||
export type LazyBuildOptions = {
|
export type LazyBuildOptions<Type> = {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
|
prefill: DeepPartial<Type> | null
|
||||||
}
|
}
|
||||||
export type LazyBuild<ExpectedOut> = (
|
export type LazyBuild<ExpectedOut, Type> = (
|
||||||
options: LazyBuildOptions,
|
options: LazyBuildOptions<Type>,
|
||||||
) => Promise<ExpectedOut> | ExpectedOut
|
) => Promise<ExpectedOut> | ExpectedOut
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
@@ -29,7 +31,7 @@ export type InputSpecOf<A extends Record<string, any>> = {
|
|||||||
[K in keyof A]: Value<A[K]>
|
[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.
|
* 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
|
* 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 _TYPE: Type = null as any as Type
|
||||||
public _PARTIAL: DeepPartial<Type> = null as any as DeepPartial<Type>
|
public _PARTIAL: DeepPartial<Type> = null as any as DeepPartial<Type>
|
||||||
async build(options: LazyBuildOptions): Promise<{
|
async build<OuterType>(options: LazyBuildOptions<OuterType>): Promise<{
|
||||||
spec: {
|
spec: {
|
||||||
[K in keyof Type]: ValueSpec
|
[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) {
|
static of<Spec extends Record<string, Value<any, any>>>(spec: Spec) {
|
||||||
const validator = object(
|
const validator = object(
|
||||||
Object.fromEntries(
|
Object.fromEntries(
|
||||||
@@ -129,10 +182,14 @@ export class InputSpec<
|
|||||||
)
|
)
|
||||||
return new 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)
|
>(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'
|
} from '../inputSpecTypes'
|
||||||
import { Parser, arrayOf, string } from 'ts-matches'
|
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(
|
private constructor(
|
||||||
public build: LazyBuild<{
|
public build: LazyBuild<
|
||||||
spec: ValueSpecList
|
{
|
||||||
validator: Parser<unknown, Type>
|
spec: ValueSpecList
|
||||||
}>,
|
validator: Parser<unknown, Type>
|
||||||
|
},
|
||||||
|
OuterType
|
||||||
|
>,
|
||||||
public readonly validator: Parser<unknown, StaticValidatedAs>,
|
public readonly validator: Parser<unknown, StaticValidatedAs>,
|
||||||
) {}
|
) {}
|
||||||
readonly _TYPE: Type = null as any
|
readonly _TYPE: Type = null as any
|
||||||
@@ -90,28 +97,31 @@ export class List<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
|
|||||||
}, validator)
|
}, validator)
|
||||||
}
|
}
|
||||||
|
|
||||||
static dynamicText(
|
static dynamicText<OuterType = unknown>(
|
||||||
getA: LazyBuild<{
|
getA: LazyBuild<
|
||||||
name: string
|
{
|
||||||
description?: string | null
|
name: string
|
||||||
warning?: string | null
|
description?: string | null
|
||||||
default?: string[]
|
warning?: string | null
|
||||||
minLength?: number | null
|
default?: string[]
|
||||||
maxLength?: number | null
|
|
||||||
disabled?: false | string
|
|
||||||
generate?: null | RandomString
|
|
||||||
spec: {
|
|
||||||
masked?: boolean
|
|
||||||
placeholder?: string | null
|
|
||||||
minLength?: number | null
|
minLength?: number | null
|
||||||
maxLength?: number | null
|
maxLength?: number | null
|
||||||
patterns?: Pattern[]
|
disabled?: false | string
|
||||||
inputmode?: ListValueSpecText['inputmode']
|
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)
|
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: aSpec, ...a } = await getA(options)
|
||||||
const spec = {
|
const spec = {
|
||||||
type: 'text' as const,
|
type: 'text' as const,
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const fileInfoParser = object({
|
|||||||
})
|
})
|
||||||
export type FileInfo = typeof fileInfoParser._TYPE
|
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
|
||||||
: T | null
|
: T | null
|
||||||
|
|
||||||
@@ -47,12 +47,19 @@ function asRequiredParser<Type, Input extends { required: boolean }>(
|
|||||||
return parser.nullable() as any
|
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(
|
protected constructor(
|
||||||
public build: LazyBuild<{
|
public build: LazyBuild<
|
||||||
spec: ValueSpec
|
{
|
||||||
validator: Parser<unknown, Type>
|
spec: ValueSpec
|
||||||
}>,
|
validator: Parser<unknown, Type>
|
||||||
|
},
|
||||||
|
OuterType
|
||||||
|
>,
|
||||||
public readonly validator: Parser<unknown, StaticValidatedAs>,
|
public readonly validator: Parser<unknown, StaticValidatedAs>,
|
||||||
) {}
|
) {}
|
||||||
public _TYPE: Type = null as any as Type
|
public _TYPE: Type = null as any as Type
|
||||||
@@ -102,17 +109,20 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
|
|||||||
validator,
|
validator,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
static dynamicToggle(
|
static dynamicToggle<OuterType = unknown>(
|
||||||
a: LazyBuild<{
|
a: LazyBuild<
|
||||||
name: string
|
{
|
||||||
description?: string | null
|
name: string
|
||||||
warning?: string | null
|
description?: string | null
|
||||||
default: boolean
|
warning?: string | null
|
||||||
disabled?: false | string
|
default: boolean
|
||||||
}>,
|
disabled?: false | string
|
||||||
|
},
|
||||||
|
OuterType
|
||||||
|
>,
|
||||||
) {
|
) {
|
||||||
const validator = boolean
|
const validator = boolean
|
||||||
return new Value<boolean>(
|
return new Value<boolean, boolean, OuterType>(
|
||||||
async (options) => ({
|
async (options) => ({
|
||||||
spec: {
|
spec: {
|
||||||
description: null,
|
description: null,
|
||||||
@@ -225,24 +235,27 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
|
|||||||
validator,
|
validator,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
static dynamicText<Required extends boolean>(
|
static dynamicText<Required extends boolean, OuterType = unknown>(
|
||||||
getA: LazyBuild<{
|
getA: LazyBuild<
|
||||||
name: string
|
{
|
||||||
description?: string | null
|
name: string
|
||||||
warning?: string | null
|
description?: string | null
|
||||||
default: DefaultString | null
|
warning?: string | null
|
||||||
required: Required
|
default: DefaultString | null
|
||||||
masked?: boolean
|
required: Required
|
||||||
placeholder?: string | null
|
masked?: boolean
|
||||||
minLength?: number | null
|
placeholder?: string | null
|
||||||
maxLength?: number | null
|
minLength?: number | null
|
||||||
patterns?: Pattern[]
|
maxLength?: number | null
|
||||||
inputmode?: ValueSpecText['inputmode']
|
patterns?: Pattern[]
|
||||||
disabled?: string | false
|
inputmode?: ValueSpecText['inputmode']
|
||||||
generate?: null | RandomString
|
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) => {
|
async (options) => {
|
||||||
const a = await getA(options)
|
const a = await getA(options)
|
||||||
return {
|
return {
|
||||||
@@ -342,23 +355,26 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
|
|||||||
return { spec: built, validator }
|
return { spec: built, validator }
|
||||||
}, validator)
|
}, validator)
|
||||||
}
|
}
|
||||||
static dynamicTextarea<Required extends boolean>(
|
static dynamicTextarea<Required extends boolean, OuterType = unknown>(
|
||||||
getA: LazyBuild<{
|
getA: LazyBuild<
|
||||||
name: string
|
{
|
||||||
description?: string | null
|
name: string
|
||||||
warning?: string | null
|
description?: string | null
|
||||||
default: string | null
|
warning?: string | null
|
||||||
required: Required
|
default: string | null
|
||||||
minLength?: number | null
|
required: Required
|
||||||
maxLength?: number | null
|
minLength?: number | null
|
||||||
patterns?: Pattern[]
|
maxLength?: number | null
|
||||||
minRows?: number
|
patterns?: Pattern[]
|
||||||
maxRows?: number
|
minRows?: number
|
||||||
placeholder?: string | null
|
maxRows?: number
|
||||||
disabled?: false | string
|
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) => {
|
async (options) => {
|
||||||
const a = await getA(options)
|
const a = await getA(options)
|
||||||
return {
|
return {
|
||||||
@@ -461,23 +477,26 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
|
|||||||
validator,
|
validator,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
static dynamicNumber<Required extends boolean>(
|
static dynamicNumber<Required extends boolean, OuterType = unknown>(
|
||||||
getA: LazyBuild<{
|
getA: LazyBuild<
|
||||||
name: string
|
{
|
||||||
description?: string | null
|
name: string
|
||||||
warning?: string | null
|
description?: string | null
|
||||||
default: number | null
|
warning?: string | null
|
||||||
required: Required
|
default: number | null
|
||||||
min?: number | null
|
required: Required
|
||||||
max?: number | null
|
min?: number | null
|
||||||
step?: number | null
|
max?: number | null
|
||||||
integer: boolean
|
step?: number | null
|
||||||
units?: string | null
|
integer: boolean
|
||||||
placeholder?: string | null
|
units?: string | null
|
||||||
disabled?: false | string
|
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) => {
|
async (options) => {
|
||||||
const a = await getA(options)
|
const a = await getA(options)
|
||||||
return {
|
return {
|
||||||
@@ -553,17 +572,20 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
static dynamicColor<Required extends boolean>(
|
static dynamicColor<Required extends boolean, OuterType = unknown>(
|
||||||
getA: LazyBuild<{
|
getA: LazyBuild<
|
||||||
name: string
|
{
|
||||||
description?: string | null
|
name: string
|
||||||
warning?: string | null
|
description?: string | null
|
||||||
default: string | null
|
warning?: string | null
|
||||||
required: Required
|
default: string | null
|
||||||
disabled?: false | string
|
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) => {
|
async (options) => {
|
||||||
const a = await getA(options)
|
const a = await getA(options)
|
||||||
return {
|
return {
|
||||||
@@ -647,20 +669,23 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
|
|||||||
validator,
|
validator,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
static dynamicDatetime<Required extends boolean>(
|
static dynamicDatetime<Required extends boolean, OuterType = unknown>(
|
||||||
getA: LazyBuild<{
|
getA: LazyBuild<
|
||||||
name: string
|
{
|
||||||
description?: string | null
|
name: string
|
||||||
warning?: string | null
|
description?: string | null
|
||||||
default: string | null
|
warning?: string | null
|
||||||
required: Required
|
default: string | null
|
||||||
inputmode?: ValueSpecDatetime['inputmode']
|
required: Required
|
||||||
min?: string | null
|
inputmode?: ValueSpecDatetime['inputmode']
|
||||||
max?: string | null
|
min?: string | null
|
||||||
disabled?: false | string
|
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) => {
|
async (options) => {
|
||||||
const a = await getA(options)
|
const a = await getA(options)
|
||||||
return {
|
return {
|
||||||
@@ -750,34 +775,43 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
|
|||||||
validator,
|
validator,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
static dynamicSelect<Values extends Record<string, string>>(
|
static dynamicSelect<
|
||||||
getA: LazyBuild<{
|
Values extends Record<string, string>,
|
||||||
name: string
|
OuterType = unknown,
|
||||||
description?: string | null
|
>(
|
||||||
warning?: string | null
|
getA: LazyBuild<
|
||||||
default: string
|
{
|
||||||
values: Values
|
name: string
|
||||||
disabled?: false | string | 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) => {
|
return new Value<keyof Values & string, keyof Values & string, OuterType>(
|
||||||
const a = await getA(options)
|
async (options) => {
|
||||||
return {
|
const a = await getA(options)
|
||||||
spec: {
|
return {
|
||||||
description: null,
|
spec: {
|
||||||
warning: null,
|
description: null,
|
||||||
type: 'select' as const,
|
warning: null,
|
||||||
disabled: false,
|
type: 'select' as const,
|
||||||
immutable: false,
|
disabled: false,
|
||||||
...a,
|
immutable: false,
|
||||||
},
|
...a,
|
||||||
validator: anyOf(
|
},
|
||||||
...Object.keys(a.values).map((x: keyof Values & string) =>
|
validator: anyOf(
|
||||||
literal(x),
|
...Object.keys(a.values).map((x: keyof Values & string) =>
|
||||||
|
literal(x),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
}
|
||||||
}
|
},
|
||||||
}, string)
|
string,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @description Displays a select modal with checkboxes, allowing for multiple selections.
|
* @description Displays a select modal with checkboxes, allowing for multiple selections.
|
||||||
@@ -851,19 +885,29 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
|
|||||||
validator,
|
validator,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
static dynamicMultiselect<Values extends Record<string, string>>(
|
static dynamicMultiselect<
|
||||||
getA: LazyBuild<{
|
Values extends Record<string, string>,
|
||||||
name: string
|
OuterType = unknown,
|
||||||
description?: string | null
|
>(
|
||||||
warning?: string | null
|
getA: LazyBuild<
|
||||||
default: string[]
|
{
|
||||||
values: Values
|
name: string
|
||||||
minLength?: number | null
|
description?: string | null
|
||||||
maxLength?: number | null
|
warning?: string | null
|
||||||
disabled?: false | string | string[]
|
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)
|
const a = await getA(options)
|
||||||
return {
|
return {
|
||||||
spec: {
|
spec: {
|
||||||
@@ -948,30 +992,34 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
|
|||||||
asRequiredParser(fileInfoParser, a),
|
asRequiredParser(fileInfoParser, a),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
static dynamicFile<Required extends boolean>(
|
static dynamicFile<Required extends boolean, OuterType = unknown>(
|
||||||
a: LazyBuild<{
|
a: LazyBuild<
|
||||||
name: string
|
{
|
||||||
description?: string | null
|
name: string
|
||||||
warning?: string | null
|
description?: string | null
|
||||||
extensions: string[]
|
warning?: string | null
|
||||||
required: Required
|
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),
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
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.
|
* @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>
|
spec: InputSpec<any>
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
OuterType = unknown,
|
||||||
>(
|
>(
|
||||||
getA: LazyBuild<{
|
getA: LazyBuild<
|
||||||
name: string
|
{
|
||||||
description?: string | null
|
name: string
|
||||||
warning?: string | null
|
description?: string | null
|
||||||
variants: Variants<VariantValues>
|
warning?: string | null
|
||||||
default: keyof VariantValues & string
|
variants: Variants<VariantValues>
|
||||||
disabled: string[] | false | string
|
default: keyof VariantValues & string
|
||||||
}>,
|
disabled: string[] | false | string
|
||||||
): Value<UnionRes<VariantValues>, unknown>
|
},
|
||||||
|
OuterType
|
||||||
|
>,
|
||||||
|
): Value<UnionRes<VariantValues>, UnionRes<VariantValues>, OuterType>
|
||||||
static dynamicUnion<
|
static dynamicUnion<
|
||||||
VariantValues extends StaticVariantValues,
|
|
||||||
StaticVariantValues extends {
|
StaticVariantValues extends {
|
||||||
[K in string]: {
|
[K in string]: {
|
||||||
name: string
|
name: string
|
||||||
spec: InputSpec<any, any>
|
spec: InputSpec<any, any>
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
VariantValues extends StaticVariantValues,
|
||||||
|
OuterType = unknown,
|
||||||
>(
|
>(
|
||||||
getA: LazyBuild<{
|
getA: LazyBuild<
|
||||||
name: string
|
{
|
||||||
description?: string | null
|
name: string
|
||||||
warning?: string | null
|
description?: string | null
|
||||||
variants: Variants<VariantValues>
|
warning?: string | null
|
||||||
default: keyof VariantValues & string
|
variants: Variants<VariantValues>
|
||||||
disabled: string[] | false | string
|
default: keyof VariantValues & string
|
||||||
}>,
|
disabled: string[] | false | string
|
||||||
|
},
|
||||||
|
OuterType
|
||||||
|
>,
|
||||||
validator: Parser<unknown, UnionResStaticValidatedAs<StaticVariantValues>>,
|
validator: Parser<unknown, UnionResStaticValidatedAs<StaticVariantValues>>,
|
||||||
): Value<
|
): Value<
|
||||||
UnionRes<VariantValues>,
|
UnionRes<VariantValues>,
|
||||||
UnionResStaticValidatedAs<StaticVariantValues>
|
UnionResStaticValidatedAs<StaticVariantValues>,
|
||||||
|
OuterType
|
||||||
>
|
>
|
||||||
static dynamicUnion<
|
static dynamicUnion<
|
||||||
VariantValues extends {
|
VariantValues extends {
|
||||||
@@ -1092,35 +1149,40 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
|
|||||||
spec: InputSpec<any>
|
spec: InputSpec<any>
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
OuterType = unknown,
|
||||||
>(
|
>(
|
||||||
getA: LazyBuild<{
|
getA: LazyBuild<
|
||||||
name: string
|
{
|
||||||
description?: string | null
|
name: string
|
||||||
warning?: string | null
|
description?: string | null
|
||||||
variants: Variants<VariantValues>
|
warning?: string | null
|
||||||
default: keyof VariantValues & string
|
variants: Variants<VariantValues>
|
||||||
disabled: string[] | false | string
|
default: keyof VariantValues & string
|
||||||
}>,
|
disabled: string[] | false | string
|
||||||
|
},
|
||||||
|
OuterType
|
||||||
|
>,
|
||||||
validator: Parser<unknown, unknown> = any,
|
validator: Parser<unknown, unknown> = any,
|
||||||
) {
|
) {
|
||||||
return new Value<UnionRes<VariantValues>, typeof validator._TYPE>(
|
return new Value<
|
||||||
async (options) => {
|
UnionRes<VariantValues>,
|
||||||
const newValues = await getA(options)
|
typeof validator._TYPE,
|
||||||
const built = await newValues.variants.build(options as any)
|
OuterType
|
||||||
return {
|
>(async (options) => {
|
||||||
spec: {
|
const newValues = await getA(options)
|
||||||
type: 'union' as const,
|
const built = await newValues.variants.build(options as any)
|
||||||
description: null,
|
return {
|
||||||
warning: null,
|
spec: {
|
||||||
...newValues,
|
type: 'union' as const,
|
||||||
variants: built.spec,
|
description: null,
|
||||||
immutable: false,
|
warning: null,
|
||||||
},
|
...newValues,
|
||||||
validator: built.validator,
|
variants: built.spec,
|
||||||
}
|
immutable: false,
|
||||||
},
|
},
|
||||||
validator,
|
validator: built.validator,
|
||||||
)
|
}
|
||||||
|
}, validator)
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @description Presents an interface to add/remove/edit items in a list.
|
* @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(),
|
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>): Value<T>
|
||||||
static hidden<T>(parser: Parser<unknown, T> = any) {
|
static hidden<T>(parser: Parser<unknown, T> = any) {
|
||||||
return new Value<T, typeof parser._TYPE>(async () => {
|
return new Value<T, typeof parser._TYPE>(async () => {
|
||||||
@@ -1216,8 +1278,10 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
|
|||||||
hiddenExample: Value.hidden(),
|
hiddenExample: Value.hidden(),
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
static dynamicHidden<T>(getParser: LazyBuild<Parser<unknown, T>>) {
|
static dynamicHidden<T, OuterType = unknown>(
|
||||||
return new Value<T, unknown>(async (options) => {
|
getParser: LazyBuild<Parser<unknown, T>, OuterType>,
|
||||||
|
) {
|
||||||
|
return new Value<T, T, OuterType>(async (options) => {
|
||||||
const validator = await getParser(options)
|
const validator = await getParser(options)
|
||||||
return {
|
return {
|
||||||
spec: {
|
spec: {
|
||||||
@@ -1228,9 +1292,9 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
|
|||||||
}, any)
|
}, any)
|
||||||
}
|
}
|
||||||
|
|
||||||
map<U>(fn: (value: StaticValidatedAs) => U): Value<U> {
|
map<U>(fn: (value: StaticValidatedAs) => U): Value<U, U, OuterType> {
|
||||||
return new Value(async (effects) => {
|
return new Value<U, U, OuterType>(async (options) => {
|
||||||
const built = await this.build(effects)
|
const built = await this.build(options)
|
||||||
return {
|
return {
|
||||||
spec: built.spec,
|
spec: built.spec,
|
||||||
validator: built.validator.map(fn),
|
validator: built.validator.map(fn),
|
||||||
|
|||||||
@@ -103,12 +103,16 @@ export class Variants<
|
|||||||
spec: InputSpec<any, any>
|
spec: InputSpec<any, any>
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
OuterType = unknown,
|
||||||
> {
|
> {
|
||||||
private constructor(
|
private constructor(
|
||||||
public build: LazyBuild<{
|
public build: LazyBuild<
|
||||||
spec: ValueSpecUnion['variants']
|
{
|
||||||
validator: Parser<unknown, UnionRes<VariantValues>>
|
spec: ValueSpecUnion['variants']
|
||||||
}>,
|
validator: Parser<unknown, UnionRes<VariantValues>>
|
||||||
|
},
|
||||||
|
OuterType
|
||||||
|
>,
|
||||||
public readonly validator: Parser<
|
public readonly validator: Parser<
|
||||||
unknown,
|
unknown,
|
||||||
UnionResStaticValidatedAs<VariantValues>
|
UnionResStaticValidatedAs<VariantValues>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export type Run<A extends Record<string, any>> = (options: {
|
|||||||
}) => Promise<(T.ActionResult & { version: '1' }) | null | void | undefined>
|
}) => Promise<(T.ActionResult & { version: '1' }) | null | void | undefined>
|
||||||
export type GetInput<A extends Record<string, any>> = (options: {
|
export type GetInput<A extends Record<string, any>> = (options: {
|
||||||
effects: T.Effects
|
effects: T.Effects
|
||||||
|
prefill: T.DeepPartial<A> | null
|
||||||
}) => Promise<null | void | undefined | T.DeepPartial<A>>
|
}) => Promise<null | void | undefined | T.DeepPartial<A>>
|
||||||
|
|
||||||
export type MaybeFn<T> = T | ((options: { effects: T.Effects }) => Promise<T>)
|
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 })
|
await options.effects.action.export({ id: this.id, metadata })
|
||||||
return 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 = {}
|
let spec = {}
|
||||||
if (this.inputSpec) {
|
if (this.inputSpec) {
|
||||||
const built = await this.inputSpec.build(options)
|
const built = await this.inputSpec.build(options)
|
||||||
|
|||||||
@@ -2,4 +2,8 @@
|
|||||||
import type { ActionId } from './ActionId'
|
import type { ActionId } from './ActionId'
|
||||||
import type { PackageId } from './PackageId'
|
import type { PackageId } from './PackageId'
|
||||||
|
|
||||||
export type GetActionInputParams = { packageId?: PackageId; actionId: ActionId }
|
export type GetActionInputParams = {
|
||||||
|
packageId?: PackageId
|
||||||
|
actionId: ActionId
|
||||||
|
prefill: Record<string, unknown> | null
|
||||||
|
}
|
||||||
|
|||||||
4
sdk/package/package-lock.json
generated
4
sdk/package/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@start9labs/start-sdk",
|
"name": "@start9labs/start-sdk",
|
||||||
"version": "0.4.0-beta.49",
|
"version": "0.4.0-beta.50",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@start9labs/start-sdk",
|
"name": "@start9labs/start-sdk",
|
||||||
"version": "0.4.0-beta.49",
|
"version": "0.4.0-beta.50",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iarna/toml": "^3.0.0",
|
"@iarna/toml": "^3.0.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@start9labs/start-sdk",
|
"name": "@start9labs/start-sdk",
|
||||||
"version": "0.4.0-beta.49",
|
"version": "0.4.0-beta.50",
|
||||||
"description": "Software development kit to facilitate packaging services for StartOS",
|
"description": "Software development kit to facilitate packaging services for StartOS",
|
||||||
"main": "./package/lib/index.js",
|
"main": "./package/lib/index.js",
|
||||||
"types": "./package/lib/index.d.ts",
|
"types": "./package/lib/index.d.ts",
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ export class ActionInputModal {
|
|||||||
this.api.getActionInput({
|
this.api.getActionInput({
|
||||||
packageId: this.pkgInfo.id,
|
packageId: this.pkgInfo.id,
|
||||||
actionId: this.actionId,
|
actionId: this.actionId,
|
||||||
|
prefill: this.context.data.prefill ?? null,
|
||||||
}),
|
}),
|
||||||
).pipe(
|
).pipe(
|
||||||
map(res => {
|
map(res => {
|
||||||
|
|||||||
Reference in New Issue
Block a user