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 { import {
ActionMetaData, ActionMetaData,
ActionResult, ActionResult,
Effects, Effects,
ExportedAction, ExportedAction,
} from "../types"; } 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( private constructor(
readonly metaData: ActionMetaData, private myMetaData: Omit<ActionMetaData, "input"> & { input: Input },
readonly fn: (options: { readonly fn: (options: {
effects: Effects; effects: Effects;
utils: Utils<WrapperData>; utils: Utils<WrapperData>;
input: Input; input: TypeFromProps<Input>;
}) => Promise<ActionResult>, }) => 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>( static of<WrapperData, Input extends Config<InputSpec>>(
metaData: ActionMetaData, metaData: Omit<ActionMetaData, "input"> & { input: Input },
fn: (options: { fn: (options: {
effects: Effects; effects: Effects;
utils: Utils<WrapperData>; utils: Utils<WrapperData>;
input: Input; input: TypeFromProps<Input>;
}) => Promise<ActionResult>, }) => Promise<ActionResult>,
) { ) {
return new CreatedAction<WrapperData, Input>(metaData, fn); return new CreatedAction<WrapperData, Input>(metaData, fn);
@@ -31,7 +43,7 @@ export class CreatedAction<WrapperData, Input> {
return this.fn({ return this.fn({
effects, effects,
utils: utils<WrapperData>(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 { string } from "ts-matches";
import { Backups } from "."; import { Backups } from ".";
import { GenericManifest } from "../manifest/ManifestTypes"; import { GenericManifest } from "../manifest/ManifestTypes";
import { BackupOptions, ExpectedExports } from "../types"; import { BackupOptions } from "../types";
import { _ } from "../util";
export type SetupBackupsParams<M extends GenericManifest> = export type SetupBackupsParams<M extends GenericManifest> = Array<
| [Partial<BackupOptions>, ...Array<keyof M["volumes"] & string>] keyof M["volumes"] & string
| Array<keyof M["volumes"] & string>; >;
export function setupBackups<M extends GenericManifest>( export function setupBackups<M extends GenericManifest>(
...args: SetupBackupsParams<M> ...args: _<SetupBackupsParams<M>>
) { ) {
const [options, volumes] = splitOptions(args); return Backups.volumes(...args).build();
if (!options) {
return Backups.volumes(...volumes).build();
}
return Backups.with_options(options)
.volumes(...volumes)
.build();
} }
function splitOptions<M extends GenericManifest>( export function setupBackupsOptions<M extends GenericManifest>(
args: SetupBackupsParams<M>, options: Partial<BackupOptions>,
): [null | Partial<BackupOptions>, Array<keyof M["volumes"] & string>] { ...args: _<SetupBackupsParams<M>>
if (args.length > 0 && !string.test(args[0])) { ) {
const [options, ...restVolumes] = args; return Backups.with_options(options)
return [options, restVolumes as Array<keyof M["volumes"] & string>]; .volumes(...args)
} .build();
return [null, args as Array<keyof M["volumes"] & string>];
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -24,23 +24,24 @@ describe("builder tests", () => {
minLength: null, minLength: null,
maxLength: null, maxLength: null,
patterns: [], patterns: [],
inputmode: "text",
}), }),
}).build(); }).build();
expect(JSON.stringify(bitcoinPropertiesBuilt)).toEqual( expect(JSON.stringify(bitcoinPropertiesBuilt)).toEqual(
/*json*/ `{ /*json*/ `{
"peer-tor-address": { "peer-tor-address": {
"type": "text", "type": "text",
"name": "Peer tor address",
"default": null, "default": null,
"description": "The Tor address of the peer interface", "description": "The Tor address of the peer interface",
"warning": null, "warning": null,
"required": true,
"masked": true, "masked": true,
"placeholder": null, "placeholder": null,
"minLength": null, "minLength": null,
"maxLength": null, "maxLength": null,
"patterns": [], "patterns": [],
"inputmode":"text", "inputmode":"text"
"name": "Peer tor address",
"required": true
}}` }}`
.replaceAll("\n", " ") .replaceAll("\n", " ")
.replaceAll(/\s{2,}/g, "") .replaceAll(/\s{2,}/g, "")
@@ -53,6 +54,9 @@ describe("values", () => {
test("toggle", () => { test("toggle", () => {
const value = Value.toggle({ const value = Value.toggle({
name: "Testing", name: "Testing",
description: null,
warning: null,
default: null,
}); });
const validator = value.validator(); const validator = value.validator();
validator.unsafeCast(false); validator.unsafeCast(false);
@@ -62,6 +66,33 @@ describe("values", () => {
const value = Value.text({ const value = Value.text({
name: "Testing", name: "Testing",
required: false, 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(); const validator = value.validator();
validator.unsafeCast("test text"); validator.unsafeCast("test text");
@@ -71,15 +102,25 @@ describe("values", () => {
const value = Value.color({ const value = Value.color({
name: "Testing", name: "Testing",
required: false, required: false,
description: null,
warning: null,
default: null,
}); });
const validator = value.validator(); const validator = value.validator();
validator.unsafeCast("#000000"); validator.unsafeCast("#000000");
testOutput<typeof validator._TYPE, string>()(null); testOutput<typeof validator._TYPE, string | null | undefined>()(null);
}); });
test("datetime", () => { test("datetime", () => {
const value = Value.datetime({ const value = Value.datetime({
name: "Testing", name: "Testing",
required: false, required: false,
description: null,
warning: null,
inputmode: "date",
min: null,
max: null,
step: null,
default: null,
}); });
const validator = value.validator(); const validator = value.validator();
validator.unsafeCast("2021-01-01"); validator.unsafeCast("2021-01-01");
@@ -89,6 +130,11 @@ describe("values", () => {
const value = Value.textarea({ const value = Value.textarea({
name: "Testing", name: "Testing",
required: false, required: false,
description: null,
warning: null,
minLength: null,
maxLength: null,
placeholder: null,
}); });
const validator = value.validator(); const validator = value.validator();
validator.unsafeCast("test text"); validator.unsafeCast("test text");
@@ -99,12 +145,38 @@ describe("values", () => {
name: "Testing", name: "Testing",
required: false, required: false,
integer: false, integer: false,
description: null,
warning: null,
default: null,
min: null,
max: null,
step: null,
units: null,
placeholder: null,
}); });
const validator = value.validator(); const validator = value.validator();
validator.unsafeCast(2); validator.unsafeCast(2);
testOutput<typeof validator._TYPE, number>()(null); testOutput<typeof validator._TYPE, number | null | undefined>()(null);
}); });
test("select", () => { 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({ const value = Value.select({
name: "Testing", name: "Testing",
required: false, required: false,
@@ -112,11 +184,15 @@ describe("values", () => {
a: "A", a: "A",
b: "B", b: "B",
}, },
description: null,
warning: null,
default: null,
}); });
const validator = value.validator(); const validator = value.validator();
validator.unsafeCast("a"); validator.unsafeCast("a");
validator.unsafeCast("b"); validator.unsafeCast("b");
testOutput<typeof validator._TYPE, "a" | "b">()(null); validator.unsafeCast(null);
testOutput<typeof validator._TYPE, "a" | "b" | null | undefined>()(null);
}); });
test("multiselect", () => { test("multiselect", () => {
const value = Value.multiselect({ const value = Value.multiselect({
@@ -126,6 +202,10 @@ describe("values", () => {
b: "B", b: "B",
}, },
default: [], default: [],
description: null,
warning: null,
minLength: null,
maxLength: null,
}); });
const validator = value.validator(); const validator = value.validator();
validator.unsafeCast([]); validator.unsafeCast([]);
@@ -136,10 +216,15 @@ describe("values", () => {
const value = Value.object( const value = Value.object(
{ {
name: "Testing", name: "Testing",
description: null,
warning: null,
}, },
Config.of({ Config.of({
a: Value.toggle({ a: Value.toggle({
name: "test", name: "test",
description: null,
warning: null,
default: null,
}), }),
}), }),
); );
@@ -152,21 +237,30 @@ describe("values", () => {
{ {
name: "Testing", name: "Testing",
required: true, required: true,
description: null,
warning: null,
default: null,
}, },
Variants.of({ Variants.of({
a: { a: {
name: "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(); const validator = value.validator();
validator.unsafeCast({ unionSelectKey: "a", unionValueKey: { b: false } }); validator.unsafeCast({ unionSelectKey: "a", unionValueKey: { b: false } });
type Test = typeof validator._TYPE; type Test = typeof validator._TYPE;
testOutput< testOutput<Test, { unionSelectKey: "a"; unionValueKey: { b: boolean } }>()(
Test, null,
{ unionSelectKey: "a" } & { unionValueKey: { b: boolean } } );
>()(null);
}); });
test("list", () => { test("list", () => {
const value = Value.list( const value = Value.list(
@@ -193,7 +287,14 @@ describe("Builder List", () => {
name: "test", 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); 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 = () => {}; const noop = () => {};
describe("wrapperData", () => { describe("wrapperData", () => {
test.skip("types", async () => { test.skip("types", async () => {
utils<WrapperType>(todo<T.Effects>()).setWrapperData( utils<WrapperType>(todo<T.Effects>()).setOwnWrapperData(
"/config/someValue", "/config/someValue",
"someValue", "someValue",
); );
utils<WrapperType>(todo<T.Effects>()).setWrapperData( utils<WrapperType>(todo<T.Effects>()).setOwnWrapperData(
"/config/someValue", "/config/someValue",
// @ts-expect-error Type is wrong for the setting value // @ts-expect-error Type is wrong for the setting value
5, 5,
); );
utils<WrapperType>(todo<T.Effects>()).setWrapperData( utils<WrapperType>(todo<T.Effects>()).setOwnWrapperData(
// @ts-expect-error Path is wrong // @ts-expect-error Path is wrong
"/config/someVae3lue", "/config/someVae3lue",
"someValue", "someValue",
@@ -45,22 +45,22 @@ describe("wrapperData", () => {
}); });
(await utils<WrapperType>(todo<T.Effects>()) (await utils<WrapperType>(todo<T.Effects>())
.getWrapperData("/config/someValue") .getOwnWrapperData("/config/someValue")
.const()) satisfies string; .const()) satisfies string;
(await utils<WrapperType>(todo<T.Effects>()) (await utils<WrapperType>(todo<T.Effects>())
.getWrapperData("/config") .getOwnWrapperData("/config")
.const()) satisfies WrapperType["config"]; .const()) satisfies WrapperType["config"];
await utils<WrapperType>(todo<T.Effects>()) await utils<WrapperType>(todo<T.Effects>())
// @ts-expect-error Path is wrong // @ts-expect-error Path is wrong
.getWrapperData("/config/somdsfeValue") .getOwnWrapperData("/config/somdsfeValue")
.const(); .const();
(await utils<WrapperType>(todo<T.Effects>()) (await utils<WrapperType>(todo<T.Effects>())
.getWrapperData("/config/someValue") .getOwnWrapperData("/config/someValue")
// @ts-expect-error satisfies type is wrong // @ts-expect-error satisfies type is wrong
.const()) satisfies number; .const()) satisfies number;
(await utils<WrapperType>(todo<T.Effects>()) (await utils<WrapperType>(todo<T.Effects>())
// @ts-expect-error Path is wrong // @ts-expect-error Path is wrong
.getWrapperData("/config/") .getOwnWrapperData("/config/")
.const()) satisfies WrapperType["config"]; .const()) satisfies WrapperType["config"];
(await todo<T.Effects>().getWrapperData<WrapperType, "/config/someValue">({ (await todo<T.Effects>().getWrapperData<WrapperType, "/config/someValue">({

View File

@@ -437,9 +437,11 @@ export type MigrationRes = {
export type ActionResult = { export type ActionResult = {
message: string; message: string;
value: null | string; value: null | {
copyable: boolean; value: string;
qr: boolean; copyable: boolean;
qr: boolean;
};
}; };
export type SetResult = { export type SetResult = {
/** These are the unix process signals */ /** 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, callback: this.effects.restart,
}); });
} }
first() { once() {
return this.effects.getWrapperData<WrapperData, Path>({ return this.effects.getWrapperData<WrapperData, Path>({
...this.options, ...this.options,
path: this.path as any, path: this.path as any,

View File

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

View File

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

View File

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