mirror of
https://github.com/Start9Labs/start-sdk.git
synced 2026-03-30 12:21:57 +00:00
chore: Update dynamic
This commit is contained in:
@@ -5,13 +5,13 @@ import { _ } from "../../util"
|
|||||||
import { Effects } from "../../types"
|
import { Effects } from "../../types"
|
||||||
import { Parser, object } from "ts-matches"
|
import { Parser, object } from "ts-matches"
|
||||||
|
|
||||||
export type LazyBuildOptions<Manifest, ConfigType> = {
|
export type LazyBuildOptions<WD, ConfigType> = {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
utils: Utils<Manifest>
|
utils: Utils<WD>
|
||||||
config: ConfigType | null
|
config: ConfigType | null
|
||||||
}
|
}
|
||||||
export type LazyBuild<Manifest, ConfigType, ExpectedOut> = (
|
export type LazyBuild<WD, ConfigType, ExpectedOut> = (
|
||||||
options: LazyBuildOptions<Manifest, ConfigType>,
|
options: LazyBuildOptions<WD, ConfigType>,
|
||||||
) => Promise<ExpectedOut> | ExpectedOut
|
) => Promise<ExpectedOut> | ExpectedOut
|
||||||
|
|
||||||
export type MaybeLazyValues<A> = LazyBuild<any, any, A> | A
|
export type MaybeLazyValues<A> = LazyBuild<any, any, A> | A
|
||||||
@@ -88,8 +88,8 @@ export class Config<Type extends Record<string, any>, WD, ConfigType> {
|
|||||||
return answer
|
return answer
|
||||||
}
|
}
|
||||||
|
|
||||||
static of<Type extends Record<string, any>, Manifest, ConfigType>(spec: {
|
static of<Type extends Record<string, any>, WrapperData, ConfigType>(spec: {
|
||||||
[K in keyof Type]: Value<Type[K], Manifest, ConfigType>
|
[K in keyof Type]: Value<Type[K], WrapperData, ConfigType>
|
||||||
}) {
|
}) {
|
||||||
const validatorObj = {} as {
|
const validatorObj = {} as {
|
||||||
[K in keyof Type]: Parser<unknown, Type[K]>
|
[K in keyof Type]: Parser<unknown, Type[K]>
|
||||||
@@ -98,6 +98,16 @@ export class Config<Type extends Record<string, any>, WD, ConfigType> {
|
|||||||
validatorObj[key] = spec[key].validator
|
validatorObj[key] = spec[key].validator
|
||||||
}
|
}
|
||||||
const validator = object(validatorObj)
|
const validator = object(validatorObj)
|
||||||
return new Config<Type, Manifest, ConfigType>(spec, validator)
|
return new Config<Type, WrapperData, ConfigType>(spec, validator)
|
||||||
|
}
|
||||||
|
|
||||||
|
static withWrapperData<WrapperData>() {
|
||||||
|
return {
|
||||||
|
of<Type extends Record<string, any>>(spec: {
|
||||||
|
[K in keyof Type]: Value<Type[K], WrapperData, Type>
|
||||||
|
}) {
|
||||||
|
return Config.of<Type, WrapperData, Type>(spec)
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Config, LazyBuild } from "./config"
|
import { Config, LazyBuild, LazyBuildOptions } from "./config"
|
||||||
import { List } from "./list"
|
import { List } from "./list"
|
||||||
import { Variants } from "./variants"
|
import { Variants } from "./variants"
|
||||||
import {
|
import {
|
||||||
@@ -70,6 +70,7 @@ function asRequiredParser<
|
|||||||
if (testForAsRequiredParser()(input)) return parser as any
|
if (testForAsRequiredParser()(input)) return parser as any
|
||||||
return parser.optional() 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.
|
||||||
@@ -114,6 +115,29 @@ export class Value<Type, WD, ConfigType> {
|
|||||||
boolean,
|
boolean,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
static dynamicToggle<WD, CT>(
|
||||||
|
a: LazyBuild<
|
||||||
|
WD,
|
||||||
|
CT,
|
||||||
|
{
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
warning?: string | null
|
||||||
|
default?: boolean | null
|
||||||
|
}
|
||||||
|
>,
|
||||||
|
) {
|
||||||
|
return new Value<boolean, WD, CT>(
|
||||||
|
async (options) => ({
|
||||||
|
description: null,
|
||||||
|
warning: null,
|
||||||
|
default: null,
|
||||||
|
type: "toggle" as const,
|
||||||
|
...(await a(options)),
|
||||||
|
}),
|
||||||
|
boolean,
|
||||||
|
)
|
||||||
|
}
|
||||||
static text<Required extends RequiredDefault<DefaultString>, WD, CT>(a: {
|
static text<Required extends RequiredDefault<DefaultString>, WD, CT>(a: {
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
@@ -146,6 +170,44 @@ export class Value<Type, WD, ConfigType> {
|
|||||||
asRequiredParser(string, a),
|
asRequiredParser(string, a),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
static dynamicText<WD, CT>(
|
||||||
|
getA: LazyBuild<
|
||||||
|
WD,
|
||||||
|
CT,
|
||||||
|
{
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
warning?: string | null
|
||||||
|
required: RequiredDefault<DefaultString>
|
||||||
|
|
||||||
|
/** Default = false */
|
||||||
|
masked?: boolean
|
||||||
|
placeholder?: string | null
|
||||||
|
minLength?: number | null
|
||||||
|
maxLength?: number | null
|
||||||
|
patterns?: Pattern[]
|
||||||
|
/** Default = 'text' */
|
||||||
|
inputmode?: ValueSpecText["inputmode"]
|
||||||
|
}
|
||||||
|
>,
|
||||||
|
) {
|
||||||
|
return new Value<string | null | undefined, WD, CT>(async (options) => {
|
||||||
|
const a = await getA(options)
|
||||||
|
return {
|
||||||
|
type: "text" as const,
|
||||||
|
description: null,
|
||||||
|
warning: null,
|
||||||
|
masked: false,
|
||||||
|
placeholder: null,
|
||||||
|
minLength: null,
|
||||||
|
maxLength: null,
|
||||||
|
patterns: [],
|
||||||
|
inputmode: "text",
|
||||||
|
...a,
|
||||||
|
...requiredLikeToAbove(a.required),
|
||||||
|
}
|
||||||
|
}, string.optional())
|
||||||
|
}
|
||||||
static textarea<WD, CT>(a: {
|
static textarea<WD, CT>(a: {
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
@@ -169,6 +231,34 @@ export class Value<Type, WD, ConfigType> {
|
|||||||
string,
|
string,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
static dynamicTextarea<WD, CT>(
|
||||||
|
getA: LazyBuild<
|
||||||
|
WD,
|
||||||
|
CT,
|
||||||
|
{
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
warning?: string | null
|
||||||
|
required: boolean
|
||||||
|
minLength?: number | null
|
||||||
|
maxLength?: number | null
|
||||||
|
placeholder?: string | null
|
||||||
|
}
|
||||||
|
>,
|
||||||
|
) {
|
||||||
|
return new Value<string, WD, CT>(async (options) => {
|
||||||
|
const a = await getA(options)
|
||||||
|
return {
|
||||||
|
description: null,
|
||||||
|
warning: null,
|
||||||
|
minLength: null,
|
||||||
|
maxLength: null,
|
||||||
|
placeholder: null,
|
||||||
|
type: "textarea" as const,
|
||||||
|
...a,
|
||||||
|
}
|
||||||
|
}, string)
|
||||||
|
}
|
||||||
static number<Required extends RequiredDefault<number>, WD, CT>(a: {
|
static number<Required extends RequiredDefault<number>, WD, CT>(a: {
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
@@ -198,6 +288,41 @@ export class Value<Type, WD, ConfigType> {
|
|||||||
asRequiredParser(number, a),
|
asRequiredParser(number, a),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
static dynamicNumber<WD, CT>(
|
||||||
|
getA: LazyBuild<
|
||||||
|
WD,
|
||||||
|
CT,
|
||||||
|
{
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
warning?: string | null
|
||||||
|
required: RequiredDefault<number>
|
||||||
|
min?: number | null
|
||||||
|
max?: number | null
|
||||||
|
/** Default = '1' */
|
||||||
|
step?: string | null
|
||||||
|
integer: boolean
|
||||||
|
units?: string | null
|
||||||
|
placeholder?: string | null
|
||||||
|
}
|
||||||
|
>,
|
||||||
|
) {
|
||||||
|
return new Value<number | null | undefined, WD, CT>(async (options) => {
|
||||||
|
const a = await getA(options)
|
||||||
|
return {
|
||||||
|
type: "number" as const,
|
||||||
|
description: null,
|
||||||
|
warning: null,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
step: null,
|
||||||
|
units: null,
|
||||||
|
placeholder: null,
|
||||||
|
...a,
|
||||||
|
...requiredLikeToAbove(a.required),
|
||||||
|
}
|
||||||
|
}, number.optional())
|
||||||
|
}
|
||||||
static color<Required extends RequiredDefault<string>, WD, CT>(a: {
|
static color<Required extends RequiredDefault<string>, WD, CT>(a: {
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
@@ -216,6 +341,30 @@ export class Value<Type, WD, ConfigType> {
|
|||||||
asRequiredParser(string, a),
|
asRequiredParser(string, a),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static dynamicColor<WD, CT>(
|
||||||
|
getA: LazyBuild<
|
||||||
|
WD,
|
||||||
|
CT,
|
||||||
|
{
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
warning?: string | null
|
||||||
|
required: RequiredDefault<string>
|
||||||
|
}
|
||||||
|
>,
|
||||||
|
) {
|
||||||
|
return new Value<string | null | undefined, WD, CT>(async (options) => {
|
||||||
|
const a = await getA(options)
|
||||||
|
return {
|
||||||
|
type: "color" as const,
|
||||||
|
description: null,
|
||||||
|
warning: null,
|
||||||
|
...a,
|
||||||
|
...requiredLikeToAbove(a.required),
|
||||||
|
}
|
||||||
|
}, string.optional())
|
||||||
|
}
|
||||||
static datetime<Required extends RequiredDefault<string>, WD, CT>(a: {
|
static datetime<Required extends RequiredDefault<string>, WD, CT>(a: {
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
@@ -242,6 +391,38 @@ export class Value<Type, WD, ConfigType> {
|
|||||||
asRequiredParser(string, a),
|
asRequiredParser(string, a),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
static dynamicDatetime<WD, CT>(
|
||||||
|
getA: LazyBuild<
|
||||||
|
WD,
|
||||||
|
CT,
|
||||||
|
{
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
warning?: string | null
|
||||||
|
required: RequiredDefault<string>
|
||||||
|
/** Default = 'datetime-local' */
|
||||||
|
inputmode?: ValueSpecDatetime["inputmode"]
|
||||||
|
min?: string | null
|
||||||
|
max?: string | null
|
||||||
|
step?: string | null
|
||||||
|
}
|
||||||
|
>,
|
||||||
|
) {
|
||||||
|
return new Value<string | null | undefined, WD, CT>(async (options) => {
|
||||||
|
const a = await getA(options)
|
||||||
|
return {
|
||||||
|
type: "datetime" as const,
|
||||||
|
description: null,
|
||||||
|
warning: null,
|
||||||
|
inputmode: "datetime-local",
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
step: null,
|
||||||
|
...a,
|
||||||
|
...requiredLikeToAbove(a.required),
|
||||||
|
}
|
||||||
|
}, string.optional())
|
||||||
|
}
|
||||||
static select<
|
static select<
|
||||||
Required extends RequiredDefault<string>,
|
Required extends RequiredDefault<string>,
|
||||||
B extends Record<string, string>,
|
B extends Record<string, string>,
|
||||||
@@ -270,6 +451,30 @@ export class Value<Type, WD, ConfigType> {
|
|||||||
) as any,
|
) as any,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
static dynamicSelect<WD, CT>(
|
||||||
|
getA: LazyBuild<
|
||||||
|
WD,
|
||||||
|
CT,
|
||||||
|
{
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
warning?: string | null
|
||||||
|
required: RequiredDefault<string>
|
||||||
|
values: Record<string, string>
|
||||||
|
}
|
||||||
|
>,
|
||||||
|
) {
|
||||||
|
return new Value<string | null | undefined, WD, CT>(async (options) => {
|
||||||
|
const a = await getA(options)
|
||||||
|
return {
|
||||||
|
description: null,
|
||||||
|
warning: null,
|
||||||
|
type: "select" as const,
|
||||||
|
...a,
|
||||||
|
...requiredLikeToAbove(a.required),
|
||||||
|
}
|
||||||
|
}, string.optional())
|
||||||
|
}
|
||||||
static multiselect<Values extends Record<string, string>, WD, CT>(a: {
|
static multiselect<Values extends Record<string, string>, WD, CT>(a: {
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
@@ -293,6 +498,33 @@ export class Value<Type, WD, ConfigType> {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
static dynamicMultiselect<WD, CT>(
|
||||||
|
getA: LazyBuild<
|
||||||
|
WD,
|
||||||
|
CT,
|
||||||
|
{
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
warning?: string | null
|
||||||
|
default: string[]
|
||||||
|
values: Record<string, string>
|
||||||
|
minLength?: number | null
|
||||||
|
maxLength?: number | null
|
||||||
|
}
|
||||||
|
>,
|
||||||
|
) {
|
||||||
|
return new Value<string[], WD, CT>(async (options) => {
|
||||||
|
const a = await getA(options)
|
||||||
|
return {
|
||||||
|
type: "multiselect" as const,
|
||||||
|
minLength: null,
|
||||||
|
maxLength: null,
|
||||||
|
warning: null,
|
||||||
|
description: null,
|
||||||
|
...a,
|
||||||
|
}
|
||||||
|
}, arrayOf(string))
|
||||||
|
}
|
||||||
static object<Type extends Record<string, any>, WrapperData, ConfigType>(
|
static object<Type extends Record<string, any>, WrapperData, ConfigType>(
|
||||||
a: {
|
a: {
|
||||||
name: string
|
name: string
|
||||||
@@ -339,6 +571,33 @@ export class Value<Type, WD, ConfigType> {
|
|||||||
asRequiredParser(aVariants.validator, a),
|
asRequiredParser(aVariants.validator, a),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
static filteredUnion<
|
||||||
|
Required extends RequiredDefault<string>,
|
||||||
|
Type,
|
||||||
|
WrapperData,
|
||||||
|
ConfigType,
|
||||||
|
>(
|
||||||
|
a: {
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
warning?: string | null
|
||||||
|
required: Required
|
||||||
|
default?: string | null
|
||||||
|
},
|
||||||
|
aVariants: Variants<Type, WrapperData, ConfigType>,
|
||||||
|
) {
|
||||||
|
return new Value<AsRequired<Type, Required>, WrapperData, ConfigType>(
|
||||||
|
async (options) => ({
|
||||||
|
type: "union" as const,
|
||||||
|
description: null,
|
||||||
|
warning: null,
|
||||||
|
...a,
|
||||||
|
variants: await aVariants.build(options as any),
|
||||||
|
...requiredLikeToAbove(a.required),
|
||||||
|
}),
|
||||||
|
asRequiredParser(aVariants.validator, a),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
static list<Type, WrapperData, ConfigType>(
|
static list<Type, WrapperData, ConfigType>(
|
||||||
a: List<Type, WrapperData, ConfigType>,
|
a: List<Type, WrapperData, ConfigType>,
|
||||||
|
|||||||
@@ -103,4 +103,23 @@ export class Variants<Type, WD, ConfigType> {
|
|||||||
return variants
|
return variants
|
||||||
}, validator)
|
}, validator)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Danger, don't filter everything!! */
|
||||||
|
disableVariants(
|
||||||
|
fn: LazyBuild<
|
||||||
|
WD,
|
||||||
|
ConfigType,
|
||||||
|
Array<Type extends { unionSelectKey: infer B } ? B : never>
|
||||||
|
>,
|
||||||
|
) {
|
||||||
|
const previousMe = this
|
||||||
|
return new Variants<Type, WD, ConfigType>(async (options) => {
|
||||||
|
const answer = { ...(await previousMe.build(options)) }
|
||||||
|
const filterValues = await fn(options)
|
||||||
|
for (const key of filterValues) {
|
||||||
|
delete answer[key as any]
|
||||||
|
}
|
||||||
|
return answer
|
||||||
|
}, this.validator)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ describe("values", () => {
|
|||||||
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("c")).toThrowError()
|
||||||
testOutput<typeof validator._TYPE, "a" | "b">()(null)
|
testOutput<typeof validator._TYPE, "a" | "b">()(null)
|
||||||
})
|
})
|
||||||
test("nullable select", async () => {
|
test("nullable select", async () => {
|
||||||
@@ -295,6 +295,280 @@ describe("values", () => {
|
|||||||
validator.unsafeCast([1, 2, 3])
|
validator.unsafeCast([1, 2, 3])
|
||||||
testOutput<typeof validator._TYPE, number[]>()(null)
|
testOutput<typeof validator._TYPE, number[]>()(null)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("dynamic", () => {
|
||||||
|
const fakeOptions = {
|
||||||
|
config: "config",
|
||||||
|
effects: "effects",
|
||||||
|
utils: "utils",
|
||||||
|
} as any
|
||||||
|
test("toggle", async () => {
|
||||||
|
const value = Value.dynamicToggle<{}, {}>(async () => ({
|
||||||
|
name: "Testing",
|
||||||
|
description: null,
|
||||||
|
warning: null,
|
||||||
|
default: null,
|
||||||
|
}))
|
||||||
|
const validator = value.validator
|
||||||
|
validator.unsafeCast(false)
|
||||||
|
expect(() => validator.unsafeCast(null)).toThrowError()
|
||||||
|
testOutput<typeof validator._TYPE, boolean>()(null)
|
||||||
|
expect(await value.build(fakeOptions)).toMatchObject({
|
||||||
|
name: "Testing",
|
||||||
|
description: null,
|
||||||
|
warning: null,
|
||||||
|
default: null,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
test("text", async () => {
|
||||||
|
const value = Value.dynamicText(async () => ({
|
||||||
|
name: "Testing",
|
||||||
|
required: { default: null },
|
||||||
|
}))
|
||||||
|
const validator = value.validator
|
||||||
|
const rawIs = await value.build({} as any)
|
||||||
|
validator.unsafeCast("test text")
|
||||||
|
validator.unsafeCast(null)
|
||||||
|
testOutput<typeof validator._TYPE, string | null | undefined>()(null)
|
||||||
|
expect(await value.build(fakeOptions)).toMatchObject({
|
||||||
|
name: "Testing",
|
||||||
|
required: true,
|
||||||
|
default: null,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
test("text", async () => {
|
||||||
|
const value = Value.dynamicText(async () => ({
|
||||||
|
name: "Testing",
|
||||||
|
required: { default: "null" },
|
||||||
|
}))
|
||||||
|
const validator = value.validator
|
||||||
|
validator.unsafeCast("test text")
|
||||||
|
validator.unsafeCast(null)
|
||||||
|
testOutput<typeof validator._TYPE, string | null | undefined>()(null)
|
||||||
|
expect(await value.build(fakeOptions)).toMatchObject({
|
||||||
|
name: "Testing",
|
||||||
|
required: true,
|
||||||
|
default: "null",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
test("optional text", async () => {
|
||||||
|
const value = Value.dynamicText(async () => ({
|
||||||
|
name: "Testing",
|
||||||
|
required: false,
|
||||||
|
}))
|
||||||
|
const validator = value.validator
|
||||||
|
const rawIs = await value.build({} as any)
|
||||||
|
validator.unsafeCast("test text")
|
||||||
|
validator.unsafeCast(null)
|
||||||
|
testOutput<typeof validator._TYPE, string | null | undefined>()(null)
|
||||||
|
expect(await value.build(fakeOptions)).toMatchObject({
|
||||||
|
name: "Testing",
|
||||||
|
required: false,
|
||||||
|
default: null,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
test("color", async () => {
|
||||||
|
const value = Value.dynamicColor<null, null>(async () => ({
|
||||||
|
name: "Testing",
|
||||||
|
required: false,
|
||||||
|
description: null,
|
||||||
|
warning: null,
|
||||||
|
}))
|
||||||
|
const validator = value.validator
|
||||||
|
validator.unsafeCast("#000000")
|
||||||
|
validator.unsafeCast(null)
|
||||||
|
testOutput<typeof validator._TYPE, string | null | undefined>()(null)
|
||||||
|
expect(await value.build(fakeOptions)).toMatchObject({
|
||||||
|
name: "Testing",
|
||||||
|
required: false,
|
||||||
|
default: null,
|
||||||
|
description: null,
|
||||||
|
warning: null,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
test("datetime", async () => {
|
||||||
|
const value = Value.dynamicDatetime<{ test: "a" }, { test2: 6 }>(
|
||||||
|
async ({ effects, utils, config }) => {
|
||||||
|
;async () => {
|
||||||
|
;(await utils.getOwnWrapperData("/test").once()) satisfies "a"
|
||||||
|
config satisfies { test2: 6 } | null
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: "Testing",
|
||||||
|
required: { default: null },
|
||||||
|
inputmode: "date",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
const validator = value.validator
|
||||||
|
validator.unsafeCast("2021-01-01")
|
||||||
|
validator.unsafeCast(null)
|
||||||
|
testOutput<typeof validator._TYPE, string | null | undefined>()(null)
|
||||||
|
expect(await value.build(fakeOptions)).toMatchObject({
|
||||||
|
name: "Testing",
|
||||||
|
required: true,
|
||||||
|
default: null,
|
||||||
|
description: null,
|
||||||
|
warning: null,
|
||||||
|
inputmode: "date",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
test("textarea", async () => {
|
||||||
|
const value = Value.dynamicTextarea(async () => ({
|
||||||
|
name: "Testing",
|
||||||
|
required: false,
|
||||||
|
description: null,
|
||||||
|
warning: null,
|
||||||
|
minLength: null,
|
||||||
|
maxLength: null,
|
||||||
|
placeholder: null,
|
||||||
|
}))
|
||||||
|
const validator = value.validator
|
||||||
|
validator.unsafeCast("test text")
|
||||||
|
expect(() => validator.unsafeCast(null)).toThrowError()
|
||||||
|
testOutput<typeof validator._TYPE, string>()(null)
|
||||||
|
expect(await value.build(fakeOptions)).toMatchObject({
|
||||||
|
name: "Testing",
|
||||||
|
required: false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
test("number", async () => {
|
||||||
|
const value = Value.dynamicNumber(() => ({
|
||||||
|
name: "Testing",
|
||||||
|
required: { default: null },
|
||||||
|
integer: false,
|
||||||
|
description: null,
|
||||||
|
warning: null,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
step: null,
|
||||||
|
units: null,
|
||||||
|
placeholder: null,
|
||||||
|
}))
|
||||||
|
const validator = value.validator
|
||||||
|
validator.unsafeCast(2)
|
||||||
|
validator.unsafeCast(null)
|
||||||
|
expect(() => validator.unsafeCast("null")).toThrowError()
|
||||||
|
testOutput<typeof validator._TYPE, number | null | undefined>()(null)
|
||||||
|
expect(await value.build(fakeOptions)).toMatchObject({
|
||||||
|
name: "Testing",
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
test("select", async () => {
|
||||||
|
const value = Value.dynamicSelect(() => ({
|
||||||
|
name: "Testing",
|
||||||
|
required: { default: null },
|
||||||
|
values: {
|
||||||
|
a: "A",
|
||||||
|
b: "B",
|
||||||
|
},
|
||||||
|
description: null,
|
||||||
|
warning: null,
|
||||||
|
}))
|
||||||
|
const validator = value.validator
|
||||||
|
validator.unsafeCast("a")
|
||||||
|
validator.unsafeCast("b")
|
||||||
|
validator.unsafeCast("c")
|
||||||
|
validator.unsafeCast(null)
|
||||||
|
testOutput<typeof validator._TYPE, string | null | undefined>()(null)
|
||||||
|
expect(await value.build(fakeOptions)).toMatchObject({
|
||||||
|
name: "Testing",
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
test("multiselect", async () => {
|
||||||
|
const value = Value.dynamicMultiselect(() => ({
|
||||||
|
name: "Testing",
|
||||||
|
values: {
|
||||||
|
a: "A",
|
||||||
|
b: "B",
|
||||||
|
},
|
||||||
|
default: [],
|
||||||
|
description: null,
|
||||||
|
warning: null,
|
||||||
|
minLength: null,
|
||||||
|
maxLength: null,
|
||||||
|
}))
|
||||||
|
const validator = value.validator
|
||||||
|
validator.unsafeCast([])
|
||||||
|
validator.unsafeCast(["a", "b"])
|
||||||
|
validator.unsafeCast(["c"])
|
||||||
|
|
||||||
|
expect(() => validator.unsafeCast([4])).toThrowError()
|
||||||
|
expect(() => validator.unsafeCast(null)).toThrowError()
|
||||||
|
testOutput<typeof validator._TYPE, Array<string>>()(null)
|
||||||
|
expect(await value.build(fakeOptions)).toMatchObject({
|
||||||
|
name: "Testing",
|
||||||
|
default: [],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe("filtering", () => {
|
||||||
|
test("union", async () => {
|
||||||
|
const value = Value.union(
|
||||||
|
{
|
||||||
|
name: "Testing",
|
||||||
|
required: { default: null },
|
||||||
|
description: null,
|
||||||
|
warning: null,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
Variants.of({
|
||||||
|
a: {
|
||||||
|
name: "a",
|
||||||
|
spec: Config.of({
|
||||||
|
b: Value.toggle({
|
||||||
|
name: "b",
|
||||||
|
description: null,
|
||||||
|
warning: null,
|
||||||
|
default: null,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
b: {
|
||||||
|
name: "b",
|
||||||
|
spec: Config.of({
|
||||||
|
b: Value.toggle({
|
||||||
|
name: "b",
|
||||||
|
description: null,
|
||||||
|
warning: null,
|
||||||
|
default: null,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}).disableVariants(() => [
|
||||||
|
"a",
|
||||||
|
// @ts-expect-error
|
||||||
|
"c",
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
const validator = value.validator
|
||||||
|
validator.unsafeCast({ unionSelectKey: "a", unionValueKey: { b: false } })
|
||||||
|
type Test = typeof validator._TYPE
|
||||||
|
testOutput<
|
||||||
|
Test,
|
||||||
|
| { unionSelectKey: "a"; unionValueKey: { b: boolean } }
|
||||||
|
| { unionSelectKey: "b"; unionValueKey: { b: boolean } }
|
||||||
|
>()(null)
|
||||||
|
|
||||||
|
const built = await value.build({} as any)
|
||||||
|
expect(built).toMatchObject({
|
||||||
|
name: "Testing",
|
||||||
|
variants: {
|
||||||
|
b: {},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(built).not.toMatchObject({
|
||||||
|
name: "Testing",
|
||||||
|
variants: {
|
||||||
|
a: {},
|
||||||
|
b: {},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("Builder List", () => {
|
describe("Builder List", () => {
|
||||||
|
|||||||
24
lib/types.ts
24
lib/types.ts
@@ -163,6 +163,30 @@ export type Effects = {
|
|||||||
toWrite: string
|
toWrite: string
|
||||||
}): Promise<void>
|
}): Promise<void>
|
||||||
readFile(input: { volumeId: string; path: string }): Promise<string>
|
readFile(input: { volumeId: string; path: string }): Promise<string>
|
||||||
|
/** Usable when not sandboxed */
|
||||||
|
appendFile(input: {
|
||||||
|
path: string
|
||||||
|
volumeId: string
|
||||||
|
toWrite: string
|
||||||
|
}): Promise<void>
|
||||||
|
/**
|
||||||
|
* Move file from src to dst
|
||||||
|
* Usable when not sandboxed */
|
||||||
|
moveFile(input: {
|
||||||
|
srcVolume: string
|
||||||
|
dstVolume: string
|
||||||
|
srcPath: string
|
||||||
|
dstPath: string
|
||||||
|
}): Promise<void>
|
||||||
|
/**
|
||||||
|
* copy from src to dst
|
||||||
|
* Usable when not sandboxed */
|
||||||
|
copyFile(input: {
|
||||||
|
srcVolume: string
|
||||||
|
dstVolume: string
|
||||||
|
srcPath: string
|
||||||
|
dstPath: string
|
||||||
|
}): Promise<void>
|
||||||
metadata(input: { volumeId: string; path: string }): Promise<Metadata>
|
metadata(input: { volumeId: string; path: string }): Promise<Metadata>
|
||||||
/** Create a directory. Usable when not sandboxed */
|
/** Create a directory. Usable when not sandboxed */
|
||||||
createDir(input: { volumeId: string; path: string }): Promise<string>
|
createDir(input: { volumeId: string; path: string }): Promise<string>
|
||||||
|
|||||||
@@ -43,18 +43,17 @@ export default async function makeFileContentFromOld(
|
|||||||
const data = await inputData
|
const data = await inputData
|
||||||
|
|
||||||
const namedConsts = new Set(["Config", "Value", "List"])
|
const namedConsts = new Set(["Config", "Value", "List"])
|
||||||
const configNameRaw = newConst("configSpecRaw", convertInputSpec(data))
|
const configName = newConst(
|
||||||
|
"configSpec",
|
||||||
|
`Config.withWrapperData<WrapperData>().of(${convertInputSpecInner(data)})`,
|
||||||
|
)
|
||||||
const configMatcherName = newConst(
|
const configMatcherName = newConst(
|
||||||
"matchConfigSpec",
|
"matchConfigSpec",
|
||||||
`${configNameRaw}.validator`,
|
`${configName}.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")
|
||||||
|
|
||||||
@@ -69,14 +68,18 @@ export default async function makeFileContentFromOld(
|
|||||||
if (nested) return data
|
if (nested) return data
|
||||||
return newConst(key, data)
|
return newConst(key, data)
|
||||||
}
|
}
|
||||||
function convertInputSpec(data: any) {
|
function convertInputSpecInner(data: any) {
|
||||||
let answer = "Config.of({"
|
let answer = "{"
|
||||||
for (const [key, value] of Object.entries(data)) {
|
for (const [key, value] of Object.entries(data)) {
|
||||||
const variableName = maybeNewConst(key, convertValueSpec(value))
|
const variableName = maybeNewConst(key, convertValueSpec(value))
|
||||||
|
|
||||||
answer += `${JSON.stringify(key)}: ${variableName},`
|
answer += `${JSON.stringify(key)}: ${variableName},`
|
||||||
}
|
}
|
||||||
return `${answer}})`
|
return `${answer}}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertInputSpec(data: any) {
|
||||||
|
return `Config.of(${convertInputSpecInner(data)})`
|
||||||
}
|
}
|
||||||
function convertValueSpec(value: any): string {
|
function convertValueSpec(value: any): string {
|
||||||
switch (value.type) {
|
switch (value.type) {
|
||||||
|
|||||||
Reference in New Issue
Block a user