From 76f0a8b0bb0345d90c34b943310ec2fa20ee2416 Mon Sep 17 00:00:00 2001 From: BluJ Date: Thu, 27 Apr 2023 08:18:45 -0600 Subject: [PATCH] wip: Working so far --- lib/actions/createAction.ts | 28 ++-- lib/backup/setupBackups.ts | 34 ++--- lib/config/builder/builder.ts | 1 + lib/config/builder/config.ts | 1 + lib/config/builder/value.ts | 216 ++++++++++++---------------- lib/manifest/ManifestTypes.ts | 6 +- lib/manifest/setupManifest.ts | 7 +- lib/properties/index.ts | 3 +- lib/test/configBuilder.test.ts | 250 +++++++++++++++++++++++++++++++-- lib/test/wrapperData.test.ts | 16 +-- lib/types.ts | 8 +- lib/util/extensions.ts | 43 ------ lib/util/getWrapperData.ts | 2 +- lib/util/index.ts | 30 ++-- lib/util/propertiesMatcher.ts | 39 +++-- scripts/oldSpecToBuilder.ts | 3 + 16 files changed, 438 insertions(+), 249 deletions(-) delete mode 100644 lib/util/extensions.ts diff --git a/lib/actions/createAction.ts b/lib/actions/createAction.ts index 6f96e53..f6e139c 100644 --- a/lib/actions/createAction.ts +++ b/lib/actions/createAction.ts @@ -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 { +export class CreatedAction> { private constructor( - readonly metaData: ActionMetaData, + private myMetaData: Omit & { input: Input }, readonly fn: (options: { effects: Effects; utils: Utils; - input: Input; + input: TypeFromProps; }) => Promise, ) {} + private validator = this.myMetaData.input.validator() as Parser< + unknown, + TypeFromProps + >; + metaData = { + ...this.myMetaData, + input: this.myMetaData.input.build(), + }; - static of( - metaData: ActionMetaData, + static of>( + metaData: Omit & { input: Input }, fn: (options: { effects: Effects; utils: Utils; - input: Input; + input: TypeFromProps; }) => Promise, ) { return new CreatedAction(metaData, fn); @@ -31,7 +43,7 @@ export class CreatedAction { return this.fn({ effects, utils: utils(effects), - input: input as Input, + input: this.validator.unsafeCast(input), }); }; diff --git a/lib/backup/setupBackups.ts b/lib/backup/setupBackups.ts index 0327028..f9d4189 100644 --- a/lib/backup/setupBackups.ts +++ b/lib/backup/setupBackups.ts @@ -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 = - | [Partial, ...Array] - | Array; +export type SetupBackupsParams = Array< + keyof M["volumes"] & string +>; export function setupBackups( - ...args: SetupBackupsParams + ...args: _> ) { - 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( - args: SetupBackupsParams, -): [null | Partial, Array] { - if (args.length > 0 && !string.test(args[0])) { - const [options, ...restVolumes] = args; - return [options, restVolumes as Array]; - } - return [null, args as Array]; +export function setupBackupsOptions( + options: Partial, + ...args: _> +) { + return Backups.with_options(options) + .volumes(...args) + .build(); } diff --git a/lib/config/builder/builder.ts b/lib/config/builder/builder.ts index dbc844c..86045b8 100644 --- a/lib/config/builder/builder.ts +++ b/lib/config/builder/builder.ts @@ -1,3 +1,4 @@ +import { _ } from "../../util"; export class IBuilder { protected constructor(readonly a: A) {} diff --git a/lib/config/builder/config.ts b/lib/config/builder/config.ts index 4741f40..7c12ab7 100644 --- a/lib/config/builder/config.ts +++ b/lib/config/builder/config.ts @@ -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. diff --git a/lib/config/builder/value.ts b/lib/config/builder/value.ts index 7c8ec93..3172ac7 100644 --- a/lib/config/builder/value.ts +++ b/lib/config/builder/value.ts @@ -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): _ { + return a as _; +} /** * 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 extends IBuilder { 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>(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>(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, + >(a: A) { return new Value({ type: "multiselect" as const, - minLength: null, - maxLength: null, - warning: null, - description: null, ...a, }); } static object>( a: { name: string; - description?: string | null; - warning?: string | null; + description: string | null; + warning: string | null; }, previousSpec: Spec, ) { const spec = previousSpec.build() as BuilderExtract; return new Value({ type: "object" as const, - description: null, - warning: null, ...a, spec, }); @@ -222,19 +195,16 @@ export class Value extends IBuilder { >( 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; return new Value({ type: "union" as const, - description: null, - warning: null, - default: null, ...a, variants, }); diff --git a/lib/manifest/ManifestTypes.ts b/lib/manifest/ManifestTypes.ts index 3878eb2..49ca1e8 100644 --- a/lib/manifest/ManifestTypes.ts +++ b/lib/manifest/ManifestTypes.ts @@ -6,10 +6,10 @@ export interface Container { image: string; /** These should match the manifest data volumes */ mounts: Record; - /** 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; diff --git a/lib/manifest/setupManifest.ts b/lib/manifest/setupManifest.ts index 10e7291..455611d 100644 --- a/lib/manifest/setupManifest.ts +++ b/lib/manifest/setupManifest.ts @@ -4,12 +4,13 @@ export function setupManifest< Id extends string, Version extends ManifestVersion, Dependencies extends Record, ->( - manifest: GenericManifest & { + Volumes extends Record, + Manifest extends GenericManifest & { dependencies: Dependencies; id: Id; version: Version; + volumes: Volumes; }, -): GenericManifest & { dependencies: Dependencies; id: Id; version: Version } { +>(manifest: Manifest): Manifest { return manifest; } diff --git a/lib/properties/index.ts b/lib/properties/index.ts index cb0d363..819a331 100644 --- a/lib/properties/index.ts +++ b/lib/properties/index.ts @@ -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"; diff --git a/lib/test/configBuilder.test.ts b/lib/test/configBuilder.test.ts index 397aed6..efe2ef7 100644 --- a/lib/test/configBuilder.test.ts +++ b/lib/test/configBuilder.test.ts @@ -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()(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()(null); + testOutput()(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()(null); + testOutput()(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()(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()(null); + validator.unsafeCast(null); + testOutput()(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()( + 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()(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()( + 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()( + 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()( + 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()(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()(null); + }); +}); diff --git a/lib/test/wrapperData.test.ts b/lib/test/wrapperData.test.ts index 4cb2b12..2cdd2de 100644 --- a/lib/test/wrapperData.test.ts +++ b/lib/test/wrapperData.test.ts @@ -12,17 +12,17 @@ const todo = (): A => { const noop = () => {}; describe("wrapperData", () => { test.skip("types", async () => { - utils(todo()).setWrapperData( + utils(todo()).setOwnWrapperData( "/config/someValue", "someValue", ); - utils(todo()).setWrapperData( + utils(todo()).setOwnWrapperData( "/config/someValue", // @ts-expect-error Type is wrong for the setting value 5, ); - utils(todo()).setWrapperData( + utils(todo()).setOwnWrapperData( // @ts-expect-error Path is wrong "/config/someVae3lue", "someValue", @@ -45,22 +45,22 @@ describe("wrapperData", () => { }); (await utils(todo()) - .getWrapperData("/config/someValue") + .getOwnWrapperData("/config/someValue") .const()) satisfies string; (await utils(todo()) - .getWrapperData("/config") + .getOwnWrapperData("/config") .const()) satisfies WrapperType["config"]; await utils(todo()) // @ts-expect-error Path is wrong - .getWrapperData("/config/somdsfeValue") + .getOwnWrapperData("/config/somdsfeValue") .const(); (await utils(todo()) - .getWrapperData("/config/someValue") + .getOwnWrapperData("/config/someValue") // @ts-expect-error satisfies type is wrong .const()) satisfies number; (await utils(todo()) // @ts-expect-error Path is wrong - .getWrapperData("/config/") + .getOwnWrapperData("/config/") .const()) satisfies WrapperType["config"]; (await todo().getWrapperData({ diff --git a/lib/types.ts b/lib/types.ts index 3f44e49..f0b3058 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -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 */ diff --git a/lib/util/extensions.ts b/lib/util/extensions.ts deleted file mode 100644 index 686b3ec..0000000 --- a/lib/util/extensions.ts +++ /dev/null @@ -1,43 +0,0 @@ -export type UnionToIntersection = ( - T extends any ? (x: T) => any : never -) extends (x: infer R) => any - ? R - : never; -type _ = A; -type UnReadonly = { -readonly [k in keyof A]: A[k] }; -declare global { - interface Object { - entries( - this: T, - ): Array<{ -readonly [K in keyof T]: [K, T[K]] }[keyof T]>; - values(this: T): Array; - keys(this: T): Array; - } - interface Array { - fromEntries(): UnionToIntersection< - T extends [infer Key, infer Value] - ? { [k in Key extends string | number ? Key : never]: Value } - : never - >; - assignObject(): UnionToIntersection; - } -} - -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; -}; diff --git a/lib/util/getWrapperData.ts b/lib/util/getWrapperData.ts index e428203..5c98f95 100644 --- a/lib/util/getWrapperData.ts +++ b/lib/util/getWrapperData.ts @@ -19,7 +19,7 @@ export class WrapperData { callback: this.effects.restart, }); } - first() { + once() { return this.effects.getWrapperData({ ...this.options, path: this.path as any, diff --git a/lib/util/index.ts b/lib/util/index.ts index d808f3e..628838d 100644 --- a/lib/util/index.ts +++ b/lib/util/index.ts @@ -19,6 +19,14 @@ export { deepEqual } from "./deepEqual"; export { deepMerge } from "./deepMerge"; export { once } from "./once"; +// prettier-ignore +export type FlattenIntersection = +T extends ArrayLike ? T : +T extends object ? {} & {[P in keyof T]: T[P]} : + T; + +export type _ = FlattenIntersection; + /** Used to check if the file exists before hand */ export const exists = ( effects: T.Effects, @@ -53,10 +61,13 @@ export type Utils = { data: A, ) => ReturnType["write"]>; getWrapperData: ( + packageId: string, path: T.EnsureWrapperDataPath, - options?: WrapperDataOptionals, ) => WrapperData; - setWrapperData: ( + getOwnWrapperData: ( + path: T.EnsureWrapperDataPath, + ) => WrapperData; + setOwnWrapperData: ( path: T.EnsureWrapperDataPath, value: ExtractWrapperData, ) => Promise; @@ -89,15 +100,14 @@ export const utils = ( fileHelper.write(data, effects), exists: (props: { path: string; volumeId: string }) => exists(effects, props), nullIfEmpty, - getWrapperData: ( + getWrapperData: ( + packageId: string, path: T.EnsureWrapperDataPath, - options: { - validator?: Parser>; - /** Defaults to what ever the package currently in */ - packageId?: string | undefined; - } = {}, - ) => getWrapperData(effects, path as any, options), - setWrapperData: ( + ) => getWrapperData(effects, path as any, { packageId }), + getOwnWrapperData: ( + path: T.EnsureWrapperDataPath, + ) => getWrapperData(effects, path as any), + setOwnWrapperData: ( path: T.EnsureWrapperDataPath, value: ExtractWrapperData, ) => effects.setWrapperData({ value, path: path as any }), diff --git a/lib/util/propertiesMatcher.ts b/lib/util/propertiesMatcher.ts index 17ebc4c..337dfa2 100644 --- a/lib/util/propertiesMatcher.ts +++ b/lib/util/propertiesMatcher.ts @@ -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 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 = type GuardToggle = A extends { type: TypeToggle } ? GuardDefaultRequired : unknown + +type TrueKeyOf = _ extends Record ? keyof T : never; // prettier-ignore type GuardObject = A extends { type: TypeObject, spec: infer B } ? ( - B extends Record ? { [K in keyof B & string]: _> } : - { _error: "Invalid Spec" } + { [K in TrueKeyOf & string]: _> } ) : unknown // prettier-ignore @@ -70,7 +71,7 @@ export type GuardList = // prettier-ignore type GuardSelect = A extends { type: TypeSelect, values: infer B } ? ( - B extends Record ? keyof B : never + GuardDefaultRequired> ) : unknown // prettier-ignore @@ -85,11 +86,19 @@ type GuardColor = type GuardDatetime = A extends { type: TypeDatetime } ? GuardDefaultRequired : unknown - +type AsString = A extends + | string + | number + | bigint + | boolean + | null + | undefined + ? `${A}` + : "UnknownValue"; // prettier-ignore type VariantValue = - A extends { name: string, spec: infer B } ? TypeFromProps : - never + A extends { name: string, spec: infer B } ? TypeFromProps<_> : + `neverVariantValue${AsString}` // prettier-ignore type GuardUnion = A extends { type: TypeUnion, variants: infer Variants & Record } ? ( @@ -97,7 +106,6 @@ type GuardUnion = ) : unknown -type _ = T; export type GuardAll = GuardNumber & GuardText & GuardTextarea & @@ -114,7 +122,6 @@ export type TypeFromProps = A extends Config ? TypeFromProps : A extends Record ? { [K in keyof A & string]: _> } : 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, value: unknown) { return parser; } function requiredParser(parser: Parser, value: unknown) { - if (!matchRequired.test(value)) return parser.optional(); + if (matchRequired.test(value)) return parser.optional(); return parser; } diff --git a/scripts/oldSpecToBuilder.ts b/scripts/oldSpecToBuilder.ts index d0d8721..3cfc492 100644 --- a/scripts/oldSpecToBuilder.ts +++ b/scripts/oldSpecToBuilder.ts @@ -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 ? [ {