mirror of
https://github.com/Start9Labs/start-sdk.git
synced 2026-03-26 02:11:56 +00:00
wip: Working so far
This commit is contained in:
@@ -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),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { _ } from "../../util";
|
||||
export class IBuilder<A> {
|
||||
protected constructor(readonly a: A) {}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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: {
|
||||
static text<
|
||||
A extends {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
warning?: string | null;
|
||||
description: string | null;
|
||||
warning: string | null;
|
||||
required: boolean;
|
||||
default?: DefaultString | null;
|
||||
default: DefaultString | null;
|
||||
/** Default = false */
|
||||
masked?: boolean;
|
||||
placeholder?: string | null;
|
||||
minLength?: number | null;
|
||||
maxLength?: number | null;
|
||||
patterns?: Pattern[];
|
||||
masked: boolean;
|
||||
placeholder: string | null;
|
||||
minLength: number | null;
|
||||
maxLength: number | null;
|
||||
patterns: Pattern[];
|
||||
/** Default = 'text' */
|
||||
inputmode?: ValueSpecText["inputmode"];
|
||||
}) {
|
||||
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: {
|
||||
static number<
|
||||
A extends {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
warning?: string | null;
|
||||
description: string | null;
|
||||
warning: string | null;
|
||||
required: boolean;
|
||||
default?: number | null;
|
||||
min?: number | null;
|
||||
max?: number | null;
|
||||
default: number | null;
|
||||
min: number | null;
|
||||
max: number | null;
|
||||
/** Default = '1' */
|
||||
step?: string | null;
|
||||
step: string | null;
|
||||
integer: boolean;
|
||||
units?: string | null;
|
||||
placeholder?: string | null;
|
||||
}) {
|
||||
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: {
|
||||
static color<
|
||||
A extends {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
warning?: string | null;
|
||||
description: string | null;
|
||||
warning: string | null;
|
||||
required: boolean;
|
||||
default?: number | null;
|
||||
}) {
|
||||
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: {
|
||||
static select<
|
||||
A extends {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
warning?: string | null;
|
||||
description: string | null;
|
||||
warning: string | null;
|
||||
required: boolean;
|
||||
default?: string | null;
|
||||
values: B;
|
||||
}) {
|
||||
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: {
|
||||
static multiselect<
|
||||
A extends {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
warning?: string | null;
|
||||
description: string | null;
|
||||
warning: string | null;
|
||||
default: string[];
|
||||
values: Values;
|
||||
minLength?: number | null;
|
||||
maxLength?: number | null;
|
||||
}) {
|
||||
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,
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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">({
|
||||
|
||||
@@ -437,10 +437,12 @@ export type MigrationRes = {
|
||||
|
||||
export type ActionResult = {
|
||||
message: string;
|
||||
value: null | string;
|
||||
value: null | {
|
||||
value: string;
|
||||
copyable: boolean;
|
||||
qr: boolean;
|
||||
};
|
||||
};
|
||||
export type SetResult = {
|
||||
/** These are the unix process signals */
|
||||
signal:
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
? [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user