wip: Working so far

This commit is contained in:
BluJ
2023-04-27 08:18:45 -06:00
parent 138c7f0133
commit 76f0a8b0bb
16 changed files with 438 additions and 249 deletions

View File

@@ -1,27 +1,39 @@
import { Parser } from "ts-matches";
import { Config } from "../config/builder";
import {
ActionMetaData,
ActionResult,
Effects,
ExportedAction,
} from "../types";
import { Utils, utils } from "../util";
import { Utils, once, utils } from "../util";
import { TypeFromProps } from "../util/propertiesMatcher";
import { InputSpec } from "../config/configTypes";
export class CreatedAction<WrapperData, Input> {
export class CreatedAction<WrapperData, Input extends Config<InputSpec>> {
private constructor(
readonly metaData: ActionMetaData,
private myMetaData: Omit<ActionMetaData, "input"> & { input: Input },
readonly fn: (options: {
effects: Effects;
utils: Utils<WrapperData>;
input: Input;
input: TypeFromProps<Input>;
}) => Promise<ActionResult>,
) {}
private validator = this.myMetaData.input.validator() as Parser<
unknown,
TypeFromProps<Input>
>;
metaData = {
...this.myMetaData,
input: this.myMetaData.input.build(),
};
static of<WrapperData, Input>(
metaData: ActionMetaData,
static of<WrapperData, Input extends Config<InputSpec>>(
metaData: Omit<ActionMetaData, "input"> & { input: Input },
fn: (options: {
effects: Effects;
utils: Utils<WrapperData>;
input: Input;
input: TypeFromProps<Input>;
}) => Promise<ActionResult>,
) {
return new CreatedAction<WrapperData, Input>(metaData, fn);
@@ -31,7 +43,7 @@ export class CreatedAction<WrapperData, Input> {
return this.fn({
effects,
utils: utils<WrapperData>(effects),
input: input as Input,
input: this.validator.unsafeCast(input),
});
};

View File

@@ -1,30 +1,24 @@
import { string } from "ts-matches";
import { Backups } from ".";
import { GenericManifest } from "../manifest/ManifestTypes";
import { BackupOptions, ExpectedExports } from "../types";
import { BackupOptions } from "../types";
import { _ } from "../util";
export type SetupBackupsParams<M extends GenericManifest> =
| [Partial<BackupOptions>, ...Array<keyof M["volumes"] & string>]
| Array<keyof M["volumes"] & string>;
export type SetupBackupsParams<M extends GenericManifest> = Array<
keyof M["volumes"] & string
>;
export function setupBackups<M extends GenericManifest>(
...args: SetupBackupsParams<M>
...args: _<SetupBackupsParams<M>>
) {
const [options, volumes] = splitOptions(args);
if (!options) {
return Backups.volumes(...volumes).build();
}
return Backups.with_options(options)
.volumes(...volumes)
.build();
return Backups.volumes(...args).build();
}
function splitOptions<M extends GenericManifest>(
args: SetupBackupsParams<M>,
): [null | Partial<BackupOptions>, Array<keyof M["volumes"] & string>] {
if (args.length > 0 && !string.test(args[0])) {
const [options, ...restVolumes] = args;
return [options, restVolumes as Array<keyof M["volumes"] & string>];
}
return [null, args as Array<keyof M["volumes"] & string>];
export function setupBackupsOptions<M extends GenericManifest>(
options: Partial<BackupOptions>,
...args: _<SetupBackupsParams<M>>
) {
return Backups.with_options(options)
.volumes(...args)
.build();
}

View File

@@ -1,3 +1,4 @@
import { _ } from "../../util";
export class IBuilder<A> {
protected constructor(readonly a: A) {}

View File

@@ -2,6 +2,7 @@ import { InputSpec, ValueSpec } from "../configTypes";
import { typeFromProps } from "../../util";
import { BuilderExtract, IBuilder } from "./builder";
import { Value } from "./value";
import { _ } from "../../util";
/**
* Configs are the specs that are used by the os configuration form for this service.

View File

@@ -10,11 +10,17 @@ import {
ValueSpecDatetime,
ValueSpecList,
ValueSpecNumber,
ValueSpecSelect,
ValueSpecText,
ValueSpecTextarea,
} from "../configTypes";
import { guardAll } from "../../util";
import { DefaultString } from "../configTypes";
import { _ } from "../../util";
function flatten<A>(a: A): _<A> {
return a as _<A>;
}
/**
* 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.
@@ -40,179 +46,146 @@ const username = Value.string({
export class Value<A extends ValueSpec> extends IBuilder<A> {
static toggle(a: {
name: string;
description?: string | null;
warning?: string | null;
default?: boolean | null;
description: string | null;
warning: string | null;
default: boolean | null;
}) {
return new Value({
description: null,
warning: null,
default: null,
type: "toggle" as const,
...a,
});
}
static text(a: {
name: string;
description?: string | null;
warning?: string | null;
required: boolean;
default?: DefaultString | null;
/** Default = false */
masked?: boolean;
placeholder?: string | null;
minLength?: number | null;
maxLength?: number | null;
patterns?: Pattern[];
/** Default = 'text' */
inputmode?: ValueSpecText["inputmode"];
}) {
static text<
A extends {
name: string;
description: string | null;
warning: string | null;
required: boolean;
default: DefaultString | null;
/** Default = false */
masked: boolean;
placeholder: string | null;
minLength: number | null;
maxLength: number | null;
patterns: Pattern[];
/** Default = 'text' */
inputmode: ValueSpecText["inputmode"];
},
>(a: A) {
return new Value({
type: "text" as const,
default: null,
description: null,
warning: null,
masked: false,
placeholder: null,
minLength: null,
maxLength: null,
patterns: [],
inputmode: "text",
...a,
});
}
static textarea(a: {
name: string;
description?: string | null;
warning?: string | null;
description: string | null;
warning: string | null;
required: boolean;
minLength?: number | null;
maxLength?: number | null;
placeholder?: string | null;
minLength: number | null;
maxLength: number | null;
placeholder: string | null;
}) {
return new Value({
description: null,
warning: null,
minLength: null,
maxLength: null,
placeholder: null,
type: "textarea" as const,
...a,
} as ValueSpecTextarea);
}
static number(a: {
name: string;
description?: string | null;
warning?: string | null;
required: boolean;
default?: number | null;
min?: number | null;
max?: number | null;
/** Default = '1' */
step?: string | null;
integer: boolean;
units?: string | null;
placeholder?: string | null;
}) {
static number<
A extends {
name: string;
description: string | null;
warning: string | null;
required: boolean;
default: number | null;
min: number | null;
max: number | null;
/** Default = '1' */
step: string | null;
integer: boolean;
units: string | null;
placeholder: string | null;
},
>(a: A) {
return new Value({
type: "number" as const,
description: null,
warning: null,
default: null,
min: null,
max: null,
step: null,
units: null,
placeholder: null,
...a,
} as ValueSpecNumber);
});
}
static color(a: {
name: string;
description?: string | null;
warning?: string | null;
required: boolean;
default?: number | null;
}) {
static color<
A extends {
name: string;
description: string | null;
warning: string | null;
required: boolean;
default: string | null;
},
>(a: A) {
return new Value({
type: "color" as const,
description: null,
warning: null,
default: null,
...a,
} as ValueSpecColor);
});
}
static datetime(a: {
name: string;
description?: string | null;
warning?: string | null;
description: string | null;
warning: string | null;
required: boolean;
/** Default = 'datetime-local' */
inputmode?: ValueSpecDatetime["inputmode"];
min?: string | null;
max?: string | null;
step?: string | null;
default?: number | null;
inputmode: ValueSpecDatetime["inputmode"];
min: string | null;
max: string | null;
step: string | null;
default: string | null;
}) {
return new Value({
type: "datetime" as const,
description: null,
warning: null,
inputmode: "datetime-local",
min: null,
max: null,
step: null,
default: null,
...a,
} as ValueSpecDatetime);
});
}
static select<B extends Record<string, string>>(a: {
name: string;
description?: string | null;
warning?: string | null;
required: boolean;
default?: string | null;
values: B;
}) {
static select<
A extends {
name: string;
description: string | null;
warning: string | null;
required: boolean;
default: string | null;
values: { [key: string]: string };
},
>(a: A) {
return new Value({
description: null,
warning: null,
default: null,
type: "select" as const,
...a,
});
}
static multiselect<Values extends Record<string, string>>(a: {
name: string;
description?: string | null;
warning?: string | null;
default: string[];
values: Values;
minLength?: number | null;
maxLength?: number | null;
}) {
static multiselect<
A extends {
name: string;
description: string | null;
warning: string | null;
default: string[];
values: Values;
minLength: number | null;
maxLength: number | null;
},
Values extends Record<string, string>,
>(a: A) {
return new Value({
type: "multiselect" as const,
minLength: null,
maxLength: null,
warning: null,
description: null,
...a,
});
}
static object<Spec extends Config<InputSpec>>(
a: {
name: string;
description?: string | null;
warning?: string | null;
description: string | null;
warning: string | null;
},
previousSpec: Spec,
) {
const spec = previousSpec.build() as BuilderExtract<Spec>;
return new Value({
type: "object" as const,
description: null,
warning: null,
...a,
spec,
});
@@ -222,19 +195,16 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
>(
a: {
name: string;
description?: string | null;
warning?: string | null;
description: string | null;
warning: string | null;
required: boolean;
default?: string | null;
default: string | null;
},
aVariants: V,
) {
const variants = aVariants.build() as BuilderExtract<V>;
return new Value({
type: "union" as const,
description: null,
warning: null,
default: null,
...a,
variants,
});

View File

@@ -6,10 +6,10 @@ export interface Container {
image: string;
/** These should match the manifest data volumes */
mounts: Record<string, string>;
/** if greater */
shmSizeMb?: number;
/** Default is 64mb */
shmSizeMb?: `${number}${"mb" | "gb" | "b" | "kb"}`;
/** if more than 30s to shutdown */
sigtermTimeout?: string;
sigtermTimeout?: `${number}${"s" | "m" | "h"}`;
}
export type ManifestVersion = ValidEmVer;

View File

@@ -4,12 +4,13 @@ export function setupManifest<
Id extends string,
Version extends ManifestVersion,
Dependencies extends Record<string, unknown>,
>(
manifest: GenericManifest & {
Volumes extends Record<string, unknown>,
Manifest extends GenericManifest & {
dependencies: Dependencies;
id: Id;
version: Version;
volumes: Volumes;
},
): GenericManifest & { dependencies: Dependencies; id: Id; version: Version } {
>(manifest: Manifest): Manifest {
return manifest;
}

View File

@@ -1,6 +1,5 @@
import { ExpectedExports, Properties } from "../types";
import { Utils, utils } from "../util";
import "../util/extensions";
import { PropertyGroup } from "./PropertyGroup";
import { PropertyString } from "./PropertyString";
export { PropertyGroup } from "./PropertyGroup";

View File

@@ -24,23 +24,24 @@ describe("builder tests", () => {
minLength: null,
maxLength: null,
patterns: [],
inputmode: "text",
}),
}).build();
expect(JSON.stringify(bitcoinPropertiesBuilt)).toEqual(
/*json*/ `{
"peer-tor-address": {
"type": "text",
"name": "Peer tor address",
"default": null,
"description": "The Tor address of the peer interface",
"warning": null,
"required": true,
"masked": true,
"placeholder": null,
"minLength": null,
"maxLength": null,
"patterns": [],
"inputmode":"text",
"name": "Peer tor address",
"required": true
"inputmode":"text"
}}`
.replaceAll("\n", " ")
.replaceAll(/\s{2,}/g, "")
@@ -53,6 +54,9 @@ describe("values", () => {
test("toggle", () => {
const value = Value.toggle({
name: "Testing",
description: null,
warning: null,
default: null,
});
const validator = value.validator();
validator.unsafeCast(false);
@@ -62,6 +66,33 @@ describe("values", () => {
const value = Value.text({
name: "Testing",
required: false,
description: null,
warning: null,
default: null,
masked: false,
placeholder: null,
minLength: null,
maxLength: null,
patterns: [],
inputmode: "text",
});
const validator = value.validator();
validator.unsafeCast("test text");
testOutput<typeof validator._TYPE, string | null | undefined>()(null);
});
test("text", () => {
const value = Value.text({
name: "Testing",
required: true,
description: null,
warning: null,
default: null,
masked: false,
placeholder: null,
minLength: null,
maxLength: null,
patterns: [],
inputmode: "text",
});
const validator = value.validator();
validator.unsafeCast("test text");
@@ -71,15 +102,25 @@ describe("values", () => {
const value = Value.color({
name: "Testing",
required: false,
description: null,
warning: null,
default: null,
});
const validator = value.validator();
validator.unsafeCast("#000000");
testOutput<typeof validator._TYPE, string>()(null);
testOutput<typeof validator._TYPE, string | null | undefined>()(null);
});
test("datetime", () => {
const value = Value.datetime({
name: "Testing",
required: false,
description: null,
warning: null,
inputmode: "date",
min: null,
max: null,
step: null,
default: null,
});
const validator = value.validator();
validator.unsafeCast("2021-01-01");
@@ -89,6 +130,11 @@ describe("values", () => {
const value = Value.textarea({
name: "Testing",
required: false,
description: null,
warning: null,
minLength: null,
maxLength: null,
placeholder: null,
});
const validator = value.validator();
validator.unsafeCast("test text");
@@ -99,12 +145,38 @@ describe("values", () => {
name: "Testing",
required: false,
integer: false,
description: null,
warning: null,
default: null,
min: null,
max: null,
step: null,
units: null,
placeholder: null,
});
const validator = value.validator();
validator.unsafeCast(2);
testOutput<typeof validator._TYPE, number>()(null);
testOutput<typeof validator._TYPE, number | null | undefined>()(null);
});
test("select", () => {
const value = Value.select({
name: "Testing",
required: true,
values: {
a: "A",
b: "B",
},
description: null,
warning: null,
default: null,
});
const validator = value.validator();
validator.unsafeCast("a");
validator.unsafeCast("b");
expect(() => validator.unsafeCast(null)).toThrowError();
testOutput<typeof validator._TYPE, "a" | "b">()(null);
});
test("nullable select", () => {
const value = Value.select({
name: "Testing",
required: false,
@@ -112,11 +184,15 @@ describe("values", () => {
a: "A",
b: "B",
},
description: null,
warning: null,
default: null,
});
const validator = value.validator();
validator.unsafeCast("a");
validator.unsafeCast("b");
testOutput<typeof validator._TYPE, "a" | "b">()(null);
validator.unsafeCast(null);
testOutput<typeof validator._TYPE, "a" | "b" | null | undefined>()(null);
});
test("multiselect", () => {
const value = Value.multiselect({
@@ -126,6 +202,10 @@ describe("values", () => {
b: "B",
},
default: [],
description: null,
warning: null,
minLength: null,
maxLength: null,
});
const validator = value.validator();
validator.unsafeCast([]);
@@ -136,10 +216,15 @@ describe("values", () => {
const value = Value.object(
{
name: "Testing",
description: null,
warning: null,
},
Config.of({
a: Value.toggle({
name: "test",
description: null,
warning: null,
default: null,
}),
}),
);
@@ -152,21 +237,30 @@ describe("values", () => {
{
name: "Testing",
required: true,
description: null,
warning: null,
default: null,
},
Variants.of({
a: {
name: "a",
spec: Config.of({ b: Value.toggle({ name: "b" }) }),
spec: Config.of({
b: Value.toggle({
name: "b",
description: null,
warning: null,
default: null,
}),
}),
},
}),
);
const validator = value.validator();
validator.unsafeCast({ unionSelectKey: "a", unionValueKey: { b: false } });
type Test = typeof validator._TYPE;
testOutput<
Test,
{ unionSelectKey: "a" } & { unionValueKey: { b: boolean } }
>()(null);
testOutput<Test, { unionSelectKey: "a"; unionValueKey: { b: boolean } }>()(
null,
);
});
test("list", () => {
const value = Value.list(
@@ -193,7 +287,14 @@ describe("Builder List", () => {
name: "test",
},
{
spec: Config.of({ test: Value.toggle({ name: "test" }) }),
spec: Config.of({
test: Value.toggle({
name: "test",
description: null,
warning: null,
default: null,
}),
}),
},
),
);
@@ -230,3 +331,128 @@ describe("Builder List", () => {
testOutput<typeof validator._TYPE, number[]>()(null);
});
});
describe("Nested nullable values", () => {
test("Testing text", () => {
const value = Config.of({
a: Value.text({
name: "Temp Name",
description:
"If no name is provided, the name from config will be used",
required: false,
default: null,
warning: null,
masked: false,
placeholder: null,
minLength: null,
maxLength: null,
patterns: [],
inputmode: "text",
}),
});
const validator = value.validator();
validator.unsafeCast({ a: null });
validator.unsafeCast({ a: "test" });
expect(() => validator.unsafeCast({ a: 4 })).toThrowError();
testOutput<typeof validator._TYPE, { a: string | null | undefined }>()(
null,
);
});
test("Testing number", () => {
const value = Config.of({
a: Value.number({
name: "Temp Name",
description:
"If no name is provided, the name from config will be used",
required: false,
warning: null,
placeholder: null,
integer: false,
default: null,
min: null,
max: null,
step: null,
units: null,
}),
});
const validator = value.validator();
validator.unsafeCast({ a: null });
validator.unsafeCast({ a: 5 });
expect(() => validator.unsafeCast({ a: "4" })).toThrowError();
testOutput<typeof validator._TYPE, { a: number | null | undefined }>()(
null,
);
});
test("Testing color", () => {
const value = Config.of({
a: Value.color({
name: "Temp Name",
description:
"If no name is provided, the name from config will be used",
required: false,
warning: null,
default: null,
}),
});
const validator = value.validator();
validator.unsafeCast({ a: null });
validator.unsafeCast({ a: "5" });
expect(() => validator.unsafeCast({ a: 4 })).toThrowError();
testOutput<typeof validator._TYPE, { a: string | null | undefined }>()(
null,
);
});
test("Testing select", () => {
const value = Config.of({
a: Value.select({
name: "Temp Name",
description:
"If no name is provided, the name from config will be used",
required: false,
warning: null,
default: null,
values: {
a: "A",
},
}),
});
const higher = Value.select({
name: "Temp Name",
description: "If no name is provided, the name from config will be used",
required: false,
warning: null,
default: null,
values: {
a: "A",
},
}).build();
const validator = value.validator();
validator.unsafeCast({ a: null });
validator.unsafeCast({ a: "a" });
expect(() => validator.unsafeCast({ a: "4" })).toThrowError();
testOutput<typeof validator._TYPE, { a: "a" | null | undefined }>()(null);
});
test("Testing multiselect", () => {
const value = Config.of({
a: Value.multiselect({
name: "Temp Name",
description:
"If no name is provided, the name from config will be used",
warning: null,
default: [],
values: {
a: "A",
},
minLength: null,
maxLength: null,
}),
});
const validator = value.validator();
validator.unsafeCast({ a: [] });
validator.unsafeCast({ a: ["a"] });
expect(() => validator.unsafeCast({ a: "4" })).toThrowError();
testOutput<typeof validator._TYPE, { a: "a"[] }>()(null);
});
});

View File

@@ -12,17 +12,17 @@ const todo = <A>(): A => {
const noop = () => {};
describe("wrapperData", () => {
test.skip("types", async () => {
utils<WrapperType>(todo<T.Effects>()).setWrapperData(
utils<WrapperType>(todo<T.Effects>()).setOwnWrapperData(
"/config/someValue",
"someValue",
);
utils<WrapperType>(todo<T.Effects>()).setWrapperData(
utils<WrapperType>(todo<T.Effects>()).setOwnWrapperData(
"/config/someValue",
// @ts-expect-error Type is wrong for the setting value
5,
);
utils<WrapperType>(todo<T.Effects>()).setWrapperData(
utils<WrapperType>(todo<T.Effects>()).setOwnWrapperData(
// @ts-expect-error Path is wrong
"/config/someVae3lue",
"someValue",
@@ -45,22 +45,22 @@ describe("wrapperData", () => {
});
(await utils<WrapperType>(todo<T.Effects>())
.getWrapperData("/config/someValue")
.getOwnWrapperData("/config/someValue")
.const()) satisfies string;
(await utils<WrapperType>(todo<T.Effects>())
.getWrapperData("/config")
.getOwnWrapperData("/config")
.const()) satisfies WrapperType["config"];
await utils<WrapperType>(todo<T.Effects>())
// @ts-expect-error Path is wrong
.getWrapperData("/config/somdsfeValue")
.getOwnWrapperData("/config/somdsfeValue")
.const();
(await utils<WrapperType>(todo<T.Effects>())
.getWrapperData("/config/someValue")
.getOwnWrapperData("/config/someValue")
// @ts-expect-error satisfies type is wrong
.const()) satisfies number;
(await utils<WrapperType>(todo<T.Effects>())
// @ts-expect-error Path is wrong
.getWrapperData("/config/")
.getOwnWrapperData("/config/")
.const()) satisfies WrapperType["config"];
(await todo<T.Effects>().getWrapperData<WrapperType, "/config/someValue">({

View File

@@ -437,9 +437,11 @@ export type MigrationRes = {
export type ActionResult = {
message: string;
value: null | string;
copyable: boolean;
qr: boolean;
value: null | {
value: string;
copyable: boolean;
qr: boolean;
};
};
export type SetResult = {
/** These are the unix process signals */

View File

@@ -1,43 +0,0 @@
export type UnionToIntersection<T> = (
T extends any ? (x: T) => any : never
) extends (x: infer R) => any
? R
: never;
type _<A> = A;
type UnReadonly<A> = { -readonly [k in keyof A]: A[k] };
declare global {
interface Object {
entries<T extends {}>(
this: T,
): Array<{ -readonly [K in keyof T]: [K, T[K]] }[keyof T]>;
values<T extends {}>(this: T): Array<T[keyof T]>;
keys<T extends {}>(this: T): Array<keyof T>;
}
interface Array<T> {
fromEntries(): UnionToIntersection<
T extends [infer Key, infer Value]
? { [k in Key extends string | number ? Key : never]: Value }
: never
>;
assignObject(): UnionToIntersection<T & {}>;
}
}
Object.prototype.entries = function () {
return Object.entries(this) as any;
};
Object.prototype.values = function () {
return Object.values(this) as any;
};
Object.prototype.keys = function () {
return Object.keys(this) as any;
};
Array.prototype.fromEntries = function () {
return Object.fromEntries(this) as any;
};
Array.prototype.assignObject = function () {
return Object.assign({}, ...this) as any;
};

View File

@@ -19,7 +19,7 @@ export class WrapperData<WrapperData, Path extends string> {
callback: this.effects.restart,
});
}
first() {
once() {
return this.effects.getWrapperData<WrapperData, Path>({
...this.options,
path: this.path as any,

View File

@@ -19,6 +19,14 @@ export { deepEqual } from "./deepEqual";
export { deepMerge } from "./deepMerge";
export { once } from "./once";
// prettier-ignore
export type FlattenIntersection<T> =
T extends ArrayLike<any> ? T :
T extends object ? {} & {[P in keyof T]: T[P]} :
T;
export type _<T> = FlattenIntersection<T>;
/** Used to check if the file exists before hand */
export const exists = (
effects: T.Effects,
@@ -53,10 +61,13 @@ export type Utils<WD> = {
data: A,
) => ReturnType<FileHelper<A>["write"]>;
getWrapperData: <Path extends string>(
packageId: string,
path: T.EnsureWrapperDataPath<WD, Path>,
options?: WrapperDataOptionals<WD, Path>,
) => WrapperData<WD, Path>;
setWrapperData: <Path extends string | never>(
getOwnWrapperData: <Path extends string>(
path: T.EnsureWrapperDataPath<WD, Path>,
) => WrapperData<WD, Path>;
setOwnWrapperData: <Path extends string | never>(
path: T.EnsureWrapperDataPath<WD, Path>,
value: ExtractWrapperData<WD, Path>,
) => Promise<void>;
@@ -89,15 +100,14 @@ export const utils = <WrapperData = never>(
fileHelper.write(data, effects),
exists: (props: { path: string; volumeId: string }) => exists(effects, props),
nullIfEmpty,
getWrapperData: <Path extends string>(
getWrapperData: <WrapperData = never, Path extends string = never>(
packageId: string,
path: T.EnsureWrapperDataPath<WrapperData, Path>,
options: {
validator?: Parser<unknown, ExtractWrapperData<WrapperData, Path>>;
/** Defaults to what ever the package currently in */
packageId?: string | undefined;
} = {},
) => getWrapperData<WrapperData, Path>(effects, path as any, options),
setWrapperData: <Path extends string | never>(
) => getWrapperData<WrapperData, Path>(effects, path as any, { packageId }),
getOwnWrapperData: <Path extends string>(
path: T.EnsureWrapperDataPath<WrapperData, Path>,
) => getWrapperData<WrapperData, Path>(effects, path as any),
setOwnWrapperData: <Path extends string | never>(
path: T.EnsureWrapperDataPath<WrapperData, Path>,
value: ExtractWrapperData<WrapperData, Path>,
) => effects.setWrapperData<WrapperData, Path>({ value, path: path as any }),

View File

@@ -7,6 +7,7 @@ import {
InputSpec,
} from "../config/configTypes";
import { Config } from "../config/builder/config";
import { _ } from "../util";
const {
string,
@@ -18,6 +19,7 @@ const {
number,
literals,
boolean,
nill,
} = matches;
type TypeToggle = "toggle";
@@ -34,9 +36,7 @@ type TypeUnion = "union";
// prettier-ignore
type GuardDefaultRequired<A, Type> =
A extends { default: unknown } ? Type :
A extends { required: false } ? Type :
A extends { required: true } ? Type | null | undefined :
A extends { required: false; default: null | undefined | never } ? Type | undefined | null:
Type
// prettier-ignore
@@ -55,11 +55,12 @@ type GuardTextarea<A> =
type GuardToggle<A> =
A extends { type: TypeToggle } ? GuardDefaultRequired<A, boolean> :
unknown
type TrueKeyOf<T> = _<T> extends Record<string, unknown> ? keyof T : never;
// prettier-ignore
type GuardObject<A> =
A extends { type: TypeObject, spec: infer B } ? (
B extends Record<string, unknown> ? { [K in keyof B & string]: _<GuardAll<B[K]>> } :
{ _error: "Invalid Spec" }
{ [K in TrueKeyOf<B> & string]: _<GuardAll<B[K]>> }
) :
unknown
// prettier-ignore
@@ -70,7 +71,7 @@ export type GuardList<A> =
// prettier-ignore
type GuardSelect<A> =
A extends { type: TypeSelect, values: infer B } ? (
B extends Record<string, string> ? keyof B : never
GuardDefaultRequired<A, TrueKeyOf<B>>
) :
unknown
// prettier-ignore
@@ -85,11 +86,19 @@ type GuardColor<A> =
type GuardDatetime<A> =
A extends { type: TypeDatetime } ? GuardDefaultRequired<A, string> :
unknown
type AsString<A> = A extends
| string
| number
| bigint
| boolean
| null
| undefined
? `${A}`
: "UnknownValue";
// prettier-ignore
type VariantValue<A> =
A extends { name: string, spec: infer B } ? TypeFromProps<B> :
never
A extends { name: string, spec: infer B } ? TypeFromProps<_<B>> :
`neverVariantValue${AsString<A>}`
// prettier-ignore
type GuardUnion<A> =
A extends { type: TypeUnion, variants: infer Variants & Record<string, unknown> } ? (
@@ -97,7 +106,6 @@ type GuardUnion<A> =
) :
unknown
type _<T> = T;
export type GuardAll<A> = GuardNumber<A> &
GuardText<A> &
GuardTextarea<A> &
@@ -114,7 +122,6 @@ export type TypeFromProps<A> =
A extends Config<infer B> ? TypeFromProps<B> :
A extends Record<string, unknown> ? { [K in keyof A & string]: _<GuardAll<A[K]>> } :
unknown;
const isType = object({ type: string });
const matchVariant = object({
name: string,
@@ -122,7 +129,13 @@ const matchVariant = object({
});
const recordString = dictionary([string, unknown]);
const matchDefault = object({ default: unknown });
const matchRequired = object({ required: literals(false) });
const matchRequired = object(
{
required: literals(false),
default: nill,
},
["default"],
);
const matchInteger = object({ integer: literals(true) });
const matchSpec = object({ spec: recordString });
const matchUnion = object({
@@ -139,7 +152,7 @@ function withInteger(parser: Parser<unknown, number>, value: unknown) {
return parser;
}
function requiredParser<A>(parser: Parser<unknown, A>, value: unknown) {
if (!matchRequired.test(value)) return parser.optional();
if (matchRequired.test(value)) return parser.optional();
return parser;
}

View File

@@ -67,6 +67,8 @@ export default async function makeFileContentFromOld(
warning: value.warning || null,
required: !(value.nullable || false),
placeholder: value.placeholder || null,
maxLength: null,
minLength: null,
},
null,
2,
@@ -81,6 +83,7 @@ export default async function makeFileContentFromOld(
required: !(value.nullable || false),
masked: value.masked || false,
placeholder: value.placeholder || null,
inputmode: "text",
patterns: value.pattern
? [
{