From 26f544718b1e6fd48f649618e9bfd07d4c27b03b Mon Sep 17 00:00:00 2001 From: BluJ Date: Fri, 31 Mar 2023 09:30:11 -0600 Subject: [PATCH] chore: Update the builder. --- lib/config/builder/index.test.ts | 127 ++++++++++++++++++- lib/config/builder/list.ts | 128 ++++++++++++------- lib/config/builder/value.ts | 205 +++++++++++++++++-------------- lib/config/builder/variants.ts | 5 +- lib/test/makeOutput.ts | 42 +++---- lib/test/output.test.ts | 39 ++---- lib/util/propertiesMatcher.ts | 133 +++++++------------- package-lock.json | 4 +- package.json | 2 +- scripts/oldSpecToBuilder.ts | 118 +++++++----------- 10 files changed, 429 insertions(+), 374 deletions(-) diff --git a/lib/config/builder/index.test.ts b/lib/config/builder/index.test.ts index 1c17282..48c9cb4 100644 --- a/lib/config/builder/index.test.ts +++ b/lib/config/builder/index.test.ts @@ -3,10 +3,16 @@ describe("test", () => { expect(true).toEqual(true); }); }); +import { values } from "lodash"; +import { Validator } from "ts-matches"; +import { testOutput } from "../../test/output.test"; import { Config } from "./config"; +import { List } from "./list"; import { Value } from "./value"; +import { Variants } from "./variants"; describe("builder tests", () => { test("String", () => { + console.log("BLUJ->"); const bitcoinPropertiesBuilt: { "peer-tor-address": { name: string; @@ -19,7 +25,7 @@ describe("builder tests", () => { default: "", description: "The Tor address of the peer interface", warning: null, - nullable: false, + required: true, masked: true, placeholder: null, pattern: null, @@ -30,15 +36,16 @@ describe("builder tests", () => { /*json*/ `{ "peer-tor-address": { "type": "string", - "name": "Peer tor address", "default": "", "description": "The Tor address of the peer interface", "warning": null, - "nullable": false, "masked": true, "placeholder": null, "pattern": null, - "patternDescription": null + "patternDescription": null, + "inputmode":"text", + "name": "Peer tor address", + "required": true }}` .replaceAll("\n", " ") .replaceAll(/\s{2,}/g, "") @@ -46,3 +53,115 @@ describe("builder tests", () => { ); }); }); + +describe("values", () => { + test("boolean", () => { + const value = Value.boolean({ + name: "Testing", + }); + const validator = value.validator(); + validator.unsafeCast(false); + testOutput()(null); + }); + test("string", () => { + const value = Value.string({ + name: "Testing", + required: false, + }); + const validator = value.validator(); + validator.unsafeCast("test text"); + testOutput()(null); + }); + test("textarea", () => { + const value = Value.textarea({ + name: "Testing", + required: false, + }); + const validator = value.validator(); + validator.unsafeCast("test text"); + testOutput()(null); + }); + test("number", () => { + const value = Value.number({ + name: "Testing", + required: false, + integral: false, + }); + const validator = value.validator(); + validator.unsafeCast(2); + testOutput()(null); + }); + test("select", () => { + const value = Value.select({ + name: "Testing", + required: false, + values: { + a: "A", + b: "B", + }, + }); + const validator = value.validator(); + validator.unsafeCast("a"); + validator.unsafeCast("b"); + testOutput()(null); + }); + test("multiselect", () => { + const value = Value.multiselect({ + name: "Testing", + values: { + a: "A", + b: "B", + }, + }); + const validator = value.validator(); + validator.unsafeCast([]); + validator.unsafeCast(["a", "b"]); + testOutput>()(null); + }); + test("object", () => { + const value = Value.object({ + name: "Testing", + spec: Config.of({ + a: Value.boolean({ + name: "test", + }), + }), + }); + const validator = value.validator(); + validator.unsafeCast({ a: true }); + testOutput()(null); + }); + test("union", () => { + const value = Value.union( + { + name: "Testing", + required: true, + }, + Variants.of({ + a: { + name: "a", + spec: Config.of({ b: Value.boolean({ name: "b" }) }), + }, + }) + ); + const validator = value.validator(); + validator.unsafeCast({ unionSelectKey: "a", b: false }); + type Test = typeof validator._TYPE; + testOutput()(null); + }); + test("list", () => { + const value = Value.list( + List.number( + { + name: "test", + }, + { + integral: false, + } + ) + ); + const validator = value.validator(); + validator.unsafeCast([1, 2, 3]); + testOutput()(null); + }); +}); diff --git a/lib/config/builder/list.ts b/lib/config/builder/list.ts index eea866a..f658d60 100644 --- a/lib/config/builder/list.ts +++ b/lib/config/builder/list.ts @@ -1,7 +1,8 @@ import { BuilderExtract, IBuilder } from "./builder"; import { Config } from "./config"; -import { InputSpec, UniqueBy, ValueSpecList } from "../config-types"; +import { InputSpec, ListValueSpecNumber, ListValueSpecString, UniqueBy, ValueSpecList } from "../config-types"; import { guardAll } from "../../util"; +import { range } from "lodash"; /** * Used as a subtype of Value.list @@ -26,76 +27,115 @@ import { guardAll } from "../../util"; ``` */ export class List extends IBuilder { - static string< - A extends { + static string( + a: { name: string; - description: string | null; - warning: string | null; - default: string[]; - range: string; - spec: { - masked: boolean; - placeholder: string | null; - pattern: string | null; - patternDescription: string | null; - }; + description?: string | null; + warning?: string | null; + /** Default = [] */ + default?: string[]; + /** Default = "(\*,\*)" */ + range?: string; + }, + aSpec: { + /** Default = false */ + masked?: boolean; + placeholder?: string | null; + pattern?: string | null; + patternDescription?: string | null; + /** Default = "text" */ + inputmode?: ListValueSpecString["inputmode"]; } - >(a: A) { + ) { + const spec = { + placeholder: null, + pattern: null, + patternDescription: null, + masked: false, + inputmode: "text" as const, + ...aSpec, + }; return new List({ + description: null, + warning: null, + default: [], type: "list" as const, subtype: "string" as const, + range: "(*,*)", ...a, + spec, }); } - static number< - A extends { + static number( + a: { name: string; - description: string | null; - warning: string | null; - default: string[]; - range: string; - spec: { - range: string; - integral: boolean; - units: string | null; - placeholder: string | null; - }; + description?: string | null; + warning?: string | null; + /** Default = [] */ + default?: string[]; + /** Default = "(\*,\*)" */ + range?: string; + }, + aSpec: { + integral: boolean; + /** Default = "(\*,\*)" */ + range?: string; + units?: string | null; + placeholder?: string | null; + /** Default = "decimal" */ + inputmode?: ListValueSpecNumber["inputmode"]; } - >(a: A) { + ) { + const spec = { + placeholder: null, + inputmode: "decimal" as const, + range: "(*,*)", + units: null, + ...aSpec, + }; return new List({ + description: null, + warning: null, + units: null, + range: "(*,*)", + default: [], type: "list" as const, subtype: "number" as const, ...a, + spec, }); } - static obj< - A extends { + static obj>( + a: { name: string; - description: string | null; - warning: string | null; + description?: string | null; + warning?: string | null; default: Record[]; - range: string; - spec: { - spec: Config; - displayAs: null | string; - uniqueBy: null | UniqueBy; - }; + /** Default = "(\*,\*)" */ + range?: string; + }, + aSpec: { + spec: Spec; + displayAs?: null | string; + uniqueBy?: null | UniqueBy; } - >(a: A) { - const { spec: previousSpec, ...rest } = a; - const { spec: previousSpecSpec, ...restSpec } = previousSpec; - const specSpec = previousSpecSpec.build() as BuilderExtract< - A["spec"]["spec"] - >; + ) { + const { spec: previousSpecSpec, ...restSpec } = aSpec; + const specSpec = previousSpecSpec.build() as BuilderExtract; const spec = { + displayAs: null, + uniqueBy: null, ...restSpec, spec: specSpec, }; const value = { spec, - ...rest, + ...a, }; return new List({ + description: null, + warning: null, + range: "(*,*)", type: "list" as const, subtype: "object" as const, ...value, diff --git a/lib/config/builder/value.ts b/lib/config/builder/value.ts index 4c41efc..635814a 100644 --- a/lib/config/builder/value.ts +++ b/lib/config/builder/value.ts @@ -4,6 +4,8 @@ import { List } from "./list"; import { Variants } from "./variants"; import { InputSpec, + ListValueSpecNumber, + ListValueSpecString, ValueSpec, ValueSpecList, ValueSpecNumber, @@ -11,14 +13,7 @@ import { ValueSpecTextarea, } from "../config-types"; import { guardAll } from "../../util"; - -export type DefaultString = - | string - | { - charset: string | null | undefined; - len: number; - }; - +import { DefaultString } from "../config-types"; /** * 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. @@ -43,132 +38,152 @@ export type DefaultString = ``` */ export class Value extends IBuilder { - static boolean< - A extends { - name: string; - description: string | null; - warning: string | null; - default: boolean | null; - } - >(a: A) { + static boolean(a: { name: string; description?: string | null; warning?: string | null; default?: boolean | null }) { return new Value({ + description: null, + warning: null, + default: null, type: "boolean" as const, ...a, }); } - static string< - A extends { - name: string; - description: string | null; - warning: string | null; - nullable: boolean; - default: DefaultString | null; - masked: boolean | null; - placeholder: string | null; - pattern: string | null; - patternDescription: string | null; - } - >(a: A) { + static string(a: { + name: string; + description?: string | null; + warning?: string | null; + required: boolean; + default?: DefaultString | null; + /** Default = false */ + masked?: boolean; + placeholder?: string | null; + pattern?: string | null; + patternDescription?: string | null; + /** Default = 'text' */ + inputmode?: ListValueSpecString["inputmode"]; + }) { return new Value({ type: "string" as const, + default: null, + description: null, + warning: null, + masked: false, + placeholder: null, + pattern: null, + patternDescription: null, + inputmode: "text", ...a, - } as ValueSpecString); + }); } - static textarea< - A extends { - name: string; - description: string | null; - warning: string | null; - nullable: boolean; - placeholder: string | null; - } - >(a: A) { + static textarea(a: { + name: string; + description?: string | null; + warning?: string | null; + required: boolean; + placeholder?: string | null; + }) { return new Value({ + description: null, + warning: null, + placeholder: null, type: "textarea" as const, ...a, } as ValueSpecTextarea); } - static number< - A extends { - name: string; - description: string | null; - warning: string | null; - nullable: boolean; - default: number | null; - range: string; - integral: boolean; - units: string | null; - placeholder: string | null; - } - >(a: A) { + static number(a: { + name: string; + description?: string | null; + warning?: string | null; + required: boolean; + default?: number | null; + /** default = "(\*,\*)" */ + range?: string; + integral: boolean; + units?: string | null; + placeholder?: string | null; + /** Default = 'decimal' */ + inputmode?: ListValueSpecNumber["inputmode"]; + }) { return new Value({ type: "number" as const, + inputmode: "decimal", + description: null, + warning: null, + default: null, + range: "(*,*)", + units: null, + placeholder: null, ...a, } as ValueSpecNumber); } - static select< - A extends { - name: string; - description: string | null; - warning: string | null; - nullable: boolean; - default: string | null; - values: B; - }, - B extends Record - >(a: A) { + static select>(a: { + name: string; + description?: string | null; + warning?: string | null; + required: boolean; + default?: string | null; + values: B; + }) { return new Value({ + description: null, + warning: null, + default: null, type: "select" as const, ...a, }); } - static multiselect< - A extends { - name: string; - description: string | null; - warning: string | null; - default: string[]; - values: Record; - range: string; - } - >(a: A) { + static multiselect>(a: { + name: string; + description?: string | null; + warning?: string | null; + default?: string[]; + values: Values; + /** default = "(\*,\*)" */ + range?: string; + }) { return new Value({ type: "multiselect" as const, + range: "(*,*)", + warning: null, + default: [], + description: null, ...a, }); } - static object< - A extends { - name: string; - description: string | null; - warning: string | null; - default: null | { [k: string]: unknown }; - spec: Config; - } - >(a: A) { + static object>(a: { + name: string; + description?: string | null; + warning?: string | null; + default?: null | { [k: string]: unknown }; + spec: Spec; + }) { const { spec: previousSpec, ...rest } = a; - const spec = previousSpec.build() as BuilderExtract; + const spec = previousSpec.build() as BuilderExtract; return new Value({ type: "object" as const, + description: null, + warning: null, + default: null, ...rest, spec, }); } - static union< - A extends { + static union>( + a: { name: string; - description: string | null; - warning: string | null; - variants: Variants<{ [key: string]: { name: string; spec: InputSpec } }>; - nullable: boolean; - default: string | null; - } - >(a: A) { - const { variants: previousVariants, ...rest } = a; - const variants = previousVariants.build() as BuilderExtract; + description?: string | null; + warning?: string | null; + required: boolean; + default?: string | null; + }, + aVariants: V + ) { + const variants = aVariants.build() as BuilderExtract; return new Value({ type: "union" as const, - ...rest, + description: null, + warning: null, + default: null, + ...a, variants, }); } diff --git a/lib/config/builder/variants.ts b/lib/config/builder/variants.ts index 9be91ef..f84ff68 100644 --- a/lib/config/builder/variants.ts +++ b/lib/config/builder/variants.ts @@ -67,10 +67,7 @@ export class Variants< static empty() { return Variants.of({}); } - static withVariant( - key: K, - value: Config - ) { + static withVariant(key: K, value: Config) { return Variants.empty().withVariant(key, value); } diff --git a/lib/test/makeOutput.ts b/lib/test/makeOutput.ts index 756ff56..1180415 100644 --- a/lib/test/makeOutput.ts +++ b/lib/test/makeOutput.ts @@ -18,8 +18,7 @@ writeConvertedFile( tag: { id: "type", name: "Type", - description: - "- LND: Lightning Network Daemon from Lightning Labs\n- CLN: Core Lightning from Blockstream\n", + description: "- LND: Lightning Network Daemon from Lightning Labs\n- CLN: Core Lightning from Blockstream\n", "variant-names": { lnd: "Lightning Network Daemon (LND)", "c-lightning": "Core Lightning (CLN)", @@ -58,8 +57,7 @@ writeConvertedFile( default: "bitcoin", masked: true, pattern: "^[a-zA-Z0-9_]+$", - "pattern-description": - "Must be alphanumeric (can contain underscore).", + "pattern-description": "Must be alphanumeric (can contain underscore).", }, password: { type: "string", @@ -71,8 +69,7 @@ writeConvertedFile( len: 20, }, pattern: '^[^\\n"]*$', - "pattern-description": - "Must not contain newline or quote characters.", + "pattern-description": "Must not contain newline or quote characters.", copyable: true, masked: true, }, @@ -84,8 +81,7 @@ writeConvertedFile( default: "bitcoin", masked: true, pattern: "^[a-zA-Z0-9_]+$", - "pattern-description": - "Must be alphanumeric (can contain underscore).", + "pattern-description": "Must be alphanumeric (can contain underscore).", textarea: true, }, advanced: { @@ -101,18 +97,15 @@ writeConvertedFile( subtype: "string", default: [], spec: { - pattern: - "^[a-zA-Z0-9_-]+:([0-9a-fA-F]{2})+\\$([0-9a-fA-F]{2})+$", - "pattern-description": - 'Each item must be of the form ":$".', + pattern: "^[a-zA-Z0-9_-]+:([0-9a-fA-F]{2})+\\$([0-9a-fA-F]{2})+$", + "pattern-description": 'Each item must be of the form ":$".', masked: false, }, range: "[0,*)", }, serialversion: { name: "Serialization Version", - description: - "Return raw transaction or block hex with Segwit or non-SegWit serialization.", + description: "Return raw transaction or block hex with Segwit or non-SegWit serialization.", type: "enum", values: ["non-segwit", "segwit"], "value-names": {}, @@ -120,8 +113,7 @@ writeConvertedFile( }, servertimeout: { name: "Rpc Server Timeout", - description: - "Number of seconds after which an uncompleted RPC call will time out.", + description: "Number of seconds after which an uncompleted RPC call will time out.", type: "number", nullable: false, range: "[5,300]", @@ -224,8 +216,7 @@ writeConvertedFile( type: "number", nullable: false, name: "Max Mempool Size", - description: - "Keep the transaction memory pool below megabytes.", + description: "Keep the transaction memory pool below megabytes.", range: "[1,*)", integral: true, units: "MiB", @@ -235,8 +226,7 @@ writeConvertedFile( type: "number", nullable: false, name: "Mempool Expiration", - description: - "Do not keep transactions in the mempool longer than hours.", + description: "Do not keep transactions in the mempool longer than hours.", range: "[1,*)", integral: true, units: "Hr", @@ -252,8 +242,7 @@ writeConvertedFile( listen: { type: "boolean", name: "Make Public", - description: - "Allow other nodes to find your server on the network.", + description: "Allow other nodes to find your server on the network.", default: true, }, onlyconnect: { @@ -293,8 +282,7 @@ writeConvertedFile( type: "number", nullable: true, name: "Port", - description: - "Port that peer is listening on for inbound p2p connections", + description: "Port that peer is listening on for inbound p2p connections", range: "[0,65535]", integral: true, }, @@ -318,8 +306,7 @@ writeConvertedFile( pruning: { type: "union", name: "Pruning Settings", - description: - "Blockchain Pruning Options\nReduce the blockchain size on disk\n", + description: "Blockchain Pruning Options\nReduce the blockchain size on disk\n", warning: "If you set pruning to Manual and your disk is smaller than the total size of the blockchain, you MUST have something running that prunes these blocks or you may overfill your disk!\nDisabling pruning will convert your node into a full archival node. This requires a resync of the entire blockchain, a process that may take several days. Make sure you have enough free disk space or you may fill up your disk.\n", tag: { @@ -341,8 +328,7 @@ writeConvertedFile( nullable: false, name: "Max Chain Size", description: "Limit of blockchain size on disk.", - warning: - "Increasing this value will require re-syncing your node.", + warning: "Increasing this value will require re-syncing your node.", default: 550, range: "[550,1000000)", integral: true, diff --git a/lib/test/output.test.ts b/lib/test/output.test.ts index 967de6e..47e8f9d 100644 --- a/lib/test/output.test.ts +++ b/lib/test/output.test.ts @@ -1,23 +1,17 @@ import { UnionSelectKey, unionSelectKey } from "../config/config-types"; import { InputSpec, matchInputSpec, threads } from "./output"; -type IfEquals = (() => G extends T - ? 1 - : 2) extends () => G extends U ? 1 : 2 +export type IfEquals = (() => G extends T ? 1 : 2) extends () => G extends U ? 1 : 2 ? Y : N; -function testOutput(): (c: IfEquals) => null { +export function testOutput(): (c: IfEquals) => null { return () => null; } function isObject(item: unknown): item is object { return !!(item && typeof item === "object" && !Array.isArray(item)); } -type UnionToIntersection = (T extends any ? (x: T) => any : never) extends ( - x: infer R -) => any - ? R - : never; +type UnionToIntersection = (T extends any ? (x: T) => any : never) extends (x: infer R) => any ? R : never; export function mergeDeep(...sources: A) { return _mergeDeep({}, ...sources); } @@ -49,21 +43,11 @@ testOutput()(null); testOutput()(null); testOutput()(null); -testOutput< - InputSpec["rpc"]["advanced"]["serialversion"], - "segwit" | "non-segwit" ->()(null); +testOutput()(null); testOutput()(null); -testOutput()( - null -); -testOutput()( - null -); -testOutput< - InputSpec["testListUnion"][0][UnionSelectKey][UnionSelectKey], - "lnd" ->()(null); +testOutput()(null); +testOutput()(null); +testOutput()(null); // prettier-ignore // @ts-expect-error Expect that the string is the one above testOutput()(null); @@ -78,6 +62,7 @@ describe("Inputs", () => { ], rpc: { enable: true, + bio: "This is a bio", username: "test", password: "test", advanced: { @@ -126,13 +111,9 @@ describe("Inputs", () => { }); test("test errors", () => { expect(() => - matchInputSpec.unsafeCast( - mergeDeep(validInput, { rpc: { advanced: { threads: 0 } } }) - ) - ).toThrowError(); - expect(() => - matchInputSpec.unsafeCast(mergeDeep(validInput, { rpc: { enable: 2 } })) + matchInputSpec.unsafeCast(mergeDeep(validInput, { rpc: { advanced: { threads: 0 } } })) ).toThrowError(); + expect(() => matchInputSpec.unsafeCast(mergeDeep(validInput, { rpc: { enable: 2 } }))).toThrowError(); expect(() => matchInputSpec.unsafeCast( diff --git a/lib/util/propertiesMatcher.ts b/lib/util/propertiesMatcher.ts index 97bfb7c..143b1a3 100644 --- a/lib/util/propertiesMatcher.ts +++ b/lib/util/propertiesMatcher.ts @@ -1,13 +1,9 @@ import * as matches from "ts-matches"; -import { Parser } from "ts-matches"; -import { - InputSpec, - unionSelectKey, - ValueSpec as ValueSpecAny, -} from "../config/config-types"; +import { Parser, Validator } from "ts-matches"; +import { Variants } from "../config/builder"; +import { InputSpec, unionSelectKey, ValueSpec as ValueSpecAny } from "../config/config-types"; -const { string, some, object, dictionary, unknown, number, literals, boolean } = - matches; +const { string, some, arrayOf, object, dictionary, unknown, number, literals, boolean } = matches; type TypeBoolean = "boolean"; type TypeString = "string"; @@ -20,28 +16,28 @@ type TypeMultiselect = "multiselect"; type TypeUnion = "union"; // prettier-ignore -type GuardDefaultNullable = +type GuardDefaultRequired = A extends { default: unknown } ? Type : - A extends { nullable: true } ? Type : - A extends { nullable: false } ? Type | null | undefined : + A extends { required: false } ? Type : + A extends { required: true } ? Type | null | undefined : Type // prettier-ignore type GuardNumber = - A extends { type: TypeNumber } ? GuardDefaultNullable : + A extends { type: TypeNumber } ? GuardDefaultRequired : unknown // prettier-ignore type GuardString = - A extends { type: TypeString } ? GuardDefaultNullable : + A extends { type: TypeString } ? GuardDefaultRequired : unknown // prettier-ignore type GuardTextarea = - A extends { type: TypeTextarea } ? GuardDefaultNullable : + A extends { type: TypeTextarea } ? GuardDefaultRequired : unknown // prettier-ignore type GuardBoolean = - A extends { type: TypeBoolean } ? GuardDefaultNullable : + A extends { type: TypeBoolean } ? GuardDefaultRequired : unknown // prettier-ignore @@ -65,7 +61,7 @@ type GuardSelect = // prettier-ignore type GuardMultiselect = - A extends { type: TypeMultiselect, variants: { [key in infer B & string]: string } } ?B[] : + A extends { type: TypeMultiselect, values: infer B} ?(keyof B)[] : unknown // prettier-ignore @@ -74,8 +70,9 @@ type VariantValue = never // prettier-ignore type GuardUnion = - A extends { type: TypeUnion, variants: infer Variants & Record } ? - { [key in keyof Variants]: _<{[unionSelectKey]: key} & VariantValue> }[keyof Variants] : + A extends { type: TypeUnion, variants: infer Variants & Record } ? ( + _<{[key in keyof Variants]: {unionSelectKey: key} & VariantValue}[keyof Variants]> + ) : unknown type _ = T; @@ -100,7 +97,7 @@ const matchVariant = object({ }); const recordString = dictionary([string, unknown]); const matchDefault = object({ default: unknown }); -const matchNullable = object({ nullable: literals(true) }); +const matchRequired = object({ required: literals(false) }); const rangeRegex = /(\[|\()(\*|(\d|\.)+),(\*|(\d|\.)+)(\]|\))/; const matchRange = object({ range: string }); const matchIntegral = object({ integral: literals(true) }); @@ -129,28 +126,18 @@ function charRange(value = "") { * @param param1 * @returns */ -export function generateDefault( - generate: { charset: string; len: number }, - { random = () => Math.random() } = {} -) { - const validCharSets: number[][] = generate.charset - .split(",") - .map(charRange) - .filter(Array.isArray); +export function generateDefault(generate: { charset: string; len: number }, { random = () => Math.random() } = {}) { + const validCharSets: number[][] = generate.charset.split(",").map(charRange).filter(Array.isArray); if (validCharSets.length === 0) { throw new Error("Expecing that we have a valid charset"); } - const max = validCharSets.reduce( - (acc, x) => x.reduce((x, y) => Math.max(x, y), acc), - 0 - ); + const max = validCharSets.reduce((acc, x) => x.reduce((x, y) => Math.max(x, y), acc), 0); let i = 0; const answer: string[] = Array(generate.len); while (i < generate.len) { const nextValue = Math.round(random() * max); const inRange = validCharSets.reduce( - (acc, [lower, upper]) => - acc || (nextValue >= lower && nextValue <= upper), + (acc, [lower, upper]) => acc || (nextValue >= lower && nextValue <= upper), false ); if (!inRange) continue; @@ -164,18 +151,11 @@ export function matchNumberWithRange(range: string) { const matched = rangeRegex.exec(range); if (!matched) return number; const [, left, leftValue, , rightValue, , right] = matched; + return number .validate( - leftValue === "*" - ? (_) => true - : left === "[" - ? (x) => x >= Number(leftValue) - : (x) => x > Number(leftValue), - leftValue === "*" - ? "any" - : left === "[" - ? `greaterThanOrEqualTo${leftValue}` - : `greaterThan${leftValue}` + leftValue === "*" ? (_) => true : left === "[" ? (x) => x >= Number(leftValue) : (x) => x > Number(leftValue), + leftValue === "*" ? "any" : left === "[" ? `greaterThanOrEqualTo${leftValue}` : `greaterThan${leftValue}` ) .validate( // prettier-ignore @@ -204,16 +184,14 @@ const isGenerator = object({ charset: string, len: number, }).test; -function defaultNullable(parser: Parser, value: unknown) { +function defaultRequired(parser: Parser, value: unknown) { if (matchDefault.test(value)) { if (isGenerator(value.default)) { - return parser.defaultTo( - parser.unsafeCast(generateDefault(value.default)) - ); + return parser.defaultTo(parser.unsafeCast(generateDefault(value.default))); } return parser.defaultTo(value.default); } - if (matchNullable.test(value)) return parser.optional(); + if (!matchRequired.test(value)) return parser.optional(); return parser; } @@ -225,42 +203,35 @@ function defaultNullable(parser: Parser, value: unknown) { * @param value * @returns */ -export function guardAll( - value: A -): Parser> { +export function guardAll(value: A): Parser> { if (!isType.test(value)) { return unknown as any; } switch (value.type) { case "boolean": - return defaultNullable(boolean, value) as any; + return defaultRequired(boolean, value) as any; case "string": - return defaultNullable(string, value) as any; + return defaultRequired(string, value) as any; case "textarea": - return defaultNullable(string, value) as any; + return defaultRequired(string, value) as any; case "number": - return defaultNullable( - withIntegral(withRange(value), value), - value - ) as any; + return defaultRequired(withIntegral(withRange(value), value), value) as any; case "object": if (matchSpec.test(value)) { - return defaultNullable(typeFromProps(value.spec), value) as any; + return defaultRequired(typeFromProps(value.spec), value) as any; } return unknown as any; case "list": { const spec = (matchSpec.test(value) && value.spec) || {}; - const rangeValidate = - (matchRange.test(value) && matchNumberWithRange(value.range).test) || - (() => true); + const rangeValidate = (matchRange.test(value) && matchNumberWithRange(value.range).test) || (() => true); const subtype = matchSubType.unsafeCast(value).subtype; - return defaultNullable( + return defaultRequired( matches .arrayOf(guardAll({ type: subtype, ...spec } as any)) .validate((x) => rangeValidate(x.length), "valid length"), @@ -270,36 +241,25 @@ export function guardAll( case "select": if (matchValues.test(value)) { const valueKeys = Object.keys(value.values); - return defaultNullable( - literals(valueKeys[0], ...valueKeys), - value - ) as any; + return defaultRequired(literals(valueKeys[0], ...valueKeys), value) as any; } return unknown as any; case "multiselect": if (matchValues.test(value)) { - const rangeValidate = - (matchRange.test(value) && matchNumberWithRange(value.range).test) || - (() => true); + const maybeAddRangeValidate = , B>(x: X) => { + if (!matchRange.test(value)) return x; + return x.validate((x) => matchNumberWithRange(value.range).test(x.length), "validLength"); + }; const valueKeys = Object.keys(value.values); - return defaultNullable( - matches - .literals(valueKeys[0], ...valueKeys) - .validate((x) => rangeValidate(x.length), "valid length"), - value - ) as any; + return defaultRequired(maybeAddRangeValidate(arrayOf(literals(valueKeys[0], ...valueKeys))), value) as any; } return unknown as any; case "union": if (matchUnion.test(value)) { - return some( - ...Object.entries(value.variants).map(([_, { spec }]) => - typeFromProps(spec) - ) - ) as any; + return some(...Object.entries(value.variants).map(([_, { spec }]) => typeFromProps(spec))) as any; } return unknown as any; } @@ -314,16 +274,9 @@ export function guardAll( * @param valueDictionary * @returns */ -export function typeFromProps( - valueDictionary: A -): Parser> { +export function typeFromProps(valueDictionary: A): Parser> { if (!recordString.test(valueDictionary)) return unknown as any; return object( - Object.fromEntries( - Object.entries(valueDictionary).map(([key, value]) => [ - key, - guardAll(value), - ]) - ) + Object.fromEntries(Object.entries(valueDictionary).map(([key, value]) => [key, guardAll(value)])) ) as any; } diff --git a/package-lock.json b/package-lock.json index 2b2161a..88afd5d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "start-sdk", - "version": "0.4.0-lib0.beta4", + "version": "0.4.0-lib0.beta5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "start-sdk", - "version": "0.4.0-lib0.beta4", + "version": "0.4.0-lib0.beta5", "license": "MIT", "dependencies": { "@iarna/toml": "^2.2.5", diff --git a/package.json b/package.json index e5e5ec2..a9d81de 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "start-sdk", - "version": "0.4.0-lib0.beta4", + "version": "0.4.0-lib0.beta5", "description": "For making the patterns that are wanted in making services for the startOS.", "main": "./lib/index.js", "types": "./lib/index.d.ts", diff --git a/scripts/oldSpecToBuilder.ts b/scripts/oldSpecToBuilder.ts index a67ae88..2514669 100644 --- a/scripts/oldSpecToBuilder.ts +++ b/scripts/oldSpecToBuilder.ts @@ -8,15 +8,10 @@ export async function writeConvertedFile( inputData: Promise | any, options: Parameters[1] ) { - await fs.writeFile(file, await makeFileContent(inputData, options), (err) => - console.error(err) - ); + await fs.writeFile(file, await makeFileContent(inputData, options), (err) => console.error(err)); } -export default async function makeFileContent( - inputData: Promise | any, - { startSdk = "start-sdk" } = {} -) { +export default async function makeFileContent(inputData: Promise | any, { startSdk = "start-sdk" } = {}) { const outputLines: string[] = []; outputLines.push(` import {Config, Value, List, Variants} from '${startSdk}/config/builder'; @@ -25,13 +20,8 @@ export default async function makeFileContent( const namedConsts = new Set(["Config", "Value", "List"]); const configName = newConst("InputSpec", convertInputSpec(data)); - const configMatcherName = newConst( - "matchInputSpec", - `${configName}.validator()` - ); - outputLines.push( - `export type InputSpec = typeof ${configMatcherName}._TYPE;` - ); + const configMatcherName = newConst("matchInputSpec", `${configName}.validator()`); + outputLines.push(`export type InputSpec = typeof ${configMatcherName}._TYPE;`); return outputLines.join("\n"); @@ -58,7 +48,7 @@ export default async function makeFileContent( name: value.name || null, description: value.description || null, warning: value.warning || null, - nullable: value.nullable || false, + required: !(value.nullable || false), placeholder: value.placeholder || null, }, null, @@ -71,8 +61,8 @@ export default async function makeFileContent( default: value.default || null, description: value.description || null, warning: value.warning || null, - nullable: value.nullable || false, - masked: value.masked || null, + required: !(value.nullable || false), + masked: value.masked || false, placeholder: value.placeholder || null, pattern: value.pattern || null, patternDescription: value["pattern-description"] || null, @@ -88,7 +78,7 @@ export default async function makeFileContent( default: value.default || null, description: value.description || null, warning: value.warning || null, - nullable: value.nullable || false, + required: !(value.nullable || false), range: value.range || null, integral: value.integral || false, units: value.units || null, @@ -111,10 +101,7 @@ export default async function makeFileContent( )})`; } case "enum": { - const allValueNames = new Set([ - ...(value?.["values"] || []), - ...Object.keys(value?.["value-names"] || {}), - ]); + const allValueNames = new Set([...(value?.["values"] || []), ...Object.keys(value?.["value-names"] || {})]); const values = Object.fromEntries( Array.from(allValueNames) .filter(string.test) @@ -126,7 +113,7 @@ export default async function makeFileContent( description: value.description || null, warning: value.warning || null, default: value.default || null, - nullable: false, + required: true, values, }, null, @@ -134,10 +121,7 @@ export default async function makeFileContent( )} as const)`; } case "object": { - const specName = newConst( - value.name + "_spec", - convertInputSpec(value.spec) - ); + const specName = newConst(value.name + "_spec", convertInputSpec(value.spec)); return `Value.object({ name: ${JSON.stringify(value.name || null)}, description: ${JSON.stringify(value.description || null)}, @@ -156,10 +140,9 @@ export default async function makeFileContent( name: ${JSON.stringify(value.name || null)}, description: ${JSON.stringify(value.tag.description || null)}, warning: ${JSON.stringify(value.tag.warning || null)}, - nullable: false, + required: true, default: ${JSON.stringify(value.default || null)}, - variants: ${variants}, - })`; + }, ${variants})`; } case "list": { const list = newConst(value.name + "_list", convertList(value)); @@ -175,43 +158,40 @@ export default async function makeFileContent( function convertList(value: any) { switch (value.subtype) { case "string": { - return `List.string(${JSON.stringify( + return `List.${value?.spec?.textarea === true ? "textarea" : "string"}(${JSON.stringify( { name: value.name || null, range: value.range || null, - spec: { - masked: value?.spec?.masked || false, - placeholder: value?.spec?.placeholder || null, - pattern: value?.spec?.pattern || null, - patternDescription: value?.spec?.["pattern-description"] || null, - textarea: value?.spec?.textarea || false, - }, default: value.default || null, description: value.description || null, warning: value.warning || null, }, null, 2 - )})`; + )}, ${JSON.stringify({ + masked: value?.spec?.masked || false, + placeholder: value?.spec?.placeholder || null, + pattern: value?.spec?.pattern || null, + patternDescription: value?.spec?.["pattern-description"] || null, + })})`; } case "number": { return `List.number(${JSON.stringify( { name: value.name || null, range: value.range || null, - spec: { - range: value?.spec?.range || null, - integral: value?.spec?.integral || false, - units: value?.spec?.units || null, - placeholder: value?.spec?.placeholder || null, - }, default: value.default || null, description: value.description || null, warning: value.warning || null, }, null, 2 - )})`; + )}, ${JSON.stringify({ + range: value?.spec?.range || null, + integral: value?.spec?.integral || false, + units: value?.spec?.units || null, + placeholder: value?.spec?.placeholder || null, + })})`; } case "enum": { const allValueNames = new Set( @@ -237,44 +217,34 @@ export default async function makeFileContent( )})`; } case "object": { - const specName = newConst( - value.name + "_spec", - convertInputSpec(value.spec.spec) - ); + const specName = newConst(value.name + "_spec", convertInputSpec(value.spec.spec)); return `List.obj({ name: ${JSON.stringify(value.name || null)}, range: ${JSON.stringify(value.range || null)}, - spec: { - spec: ${specName}, - displayAs: ${JSON.stringify(value?.spec?.["display-as"] || null)}, - uniqueBy: ${JSON.stringify(value?.spec?.["unique-by"] || null)}, - }, default: ${JSON.stringify(value.default || null)}, description: ${JSON.stringify(value.description || null)}, warning: ${JSON.stringify(value.warning || null)}, + }, { + spec: ${specName}, + displayAs: ${JSON.stringify(value?.spec?.["display-as"] || null)}, + uniqueBy: ${JSON.stringify(value?.spec?.["unique-by"] || null)}, })`; } case "union": { const variants = newConst( value.name + "_variants", - convertVariants( - value.spec.variants, - value.spec["variant-names"] || {} - ) + convertVariants(value.spec.variants, value.spec["variant-names"] || {}) ); const unionValueName = newConst( value.name + "_union", ` Value.union({ name: ${JSON.stringify(value?.spec?.tag?.name || null)}, - description: ${JSON.stringify( - value?.spec?.tag?.description || null - )}, + description: ${JSON.stringify(value?.spec?.tag?.description || null)}, warning: ${JSON.stringify(value?.spec?.tag?.warning || null)}, - variants: ${variants}, - nullable: ${JSON.stringify(value?.spec?.tag?.nullable || false)}, + required: ${JSON.stringify(!(value?.spec?.tag?.nullable || false))}, default: ${JSON.stringify(value?.spec?.default || null)}, - }) + }, ${variants}) ` ); const listConfig = newConst( @@ -289,30 +259,24 @@ export default async function makeFileContent( return `List.obj({ name:${JSON.stringify(value.name || null)}, range:${JSON.stringify(value.range || null)}, - spec: { - spec: ${listConfig}, - displayAs: ${JSON.stringify(value?.spec?.["display-as"] || null)}, - uniqueBy: ${JSON.stringify(value?.spec?.["unique-by"] || null)}, - }, default: [], description: ${JSON.stringify(value.description || null)}, warning: ${JSON.stringify(value.warning || null)}, + }, { + spec: ${listConfig}, + displayAs: ${JSON.stringify(value?.spec?.["display-as"] || null)}, + uniqueBy: ${JSON.stringify(value?.spec?.["unique-by"] || null)}, })`; } } throw new Error(`Unknown subtype "${value.subtype}"`); } - function convertVariants( - variants: Record, - variantNames: Record - ): string { + function convertVariants(variants: Record, variantNames: Record): string { let answer = "Variants.of({"; for (const [key, value] of Object.entries(variants)) { const variantSpec = newConst(key, convertInputSpec(value)); - answer += `"${key}": {name: "${ - variantNames[key] || key - }", spec: ${variantSpec}},`; + answer += `"${key}": {name: "${variantNames[key] || key}", spec: ${variantSpec}},`; } return `${answer}})`; }