From 45192e035816200255b64dfe59803fb3671a695a Mon Sep 17 00:00:00 2001 From: BluJ Date: Wed, 5 Apr 2023 13:51:43 -0600 Subject: [PATCH] chore: Fix the tests and add new types for raw --- lib/config/builder/config.ts | 12 +- lib/config/builder/list.ts | 2 +- lib/config/builder/value.ts | 15 +-- lib/config/builder/variants.ts | 7 +- lib/config/configTypes.ts | 53 ++++++++ .../{config-types.ts => configTypesRaw.ts} | 114 ++++++++--------- lib/config/index.ts | 2 +- ...config_export.ts => setupConfigExports.ts} | 2 +- .../emverList.test.ts | 0 lib/{emver-lite => emverLite}/mod.ts | 0 lib/index.ts | 2 +- ...-builder.test.ts => configBuilder.test.ts} | 5 +- ...nfig-types.test.ts => configTypes.test.ts} | 2 +- lib/test/makeOutput.ts | 42 +++---- lib/test/output.test.ts | 51 +++----- lib/types.ts | 115 ++++-------------- lib/util/propertiesMatcher.ts | 111 +++++------------ package.json | 2 +- scripts/oldSpecToBuilder.ts | 61 +++------- 19 files changed, 222 insertions(+), 376 deletions(-) create mode 100644 lib/config/configTypes.ts rename lib/config/{config-types.ts => configTypesRaw.ts} (65%) rename lib/config/{setup_config_export.ts => setupConfigExports.ts} (96%) rename lib/{emver-lite => emverLite}/emverList.test.ts (100%) rename lib/{emver-lite => emverLite}/mod.ts (100%) rename lib/test/{config-builder.test.ts => configBuilder.test.ts} (96%) rename lib/test/{config-types.test.ts => configTypes.test.ts} (97%) diff --git a/lib/config/builder/config.ts b/lib/config/builder/config.ts index 9ebfa69..e8b2d30 100644 --- a/lib/config/builder/config.ts +++ b/lib/config/builder/config.ts @@ -1,4 +1,4 @@ -import { InputSpec, ValueSpec } from "../config-types"; +import { InputSpec, ValueSpec } from "../configTypes"; import { typeFromProps } from "../../util"; import { BuilderExtract, IBuilder } from "./builder"; import { Value } from "./value"; @@ -63,16 +63,10 @@ export class Config extends IBuilder { static empty() { return new Config({}); } - static withValue( - key: K, - value: Value - ) { + static withValue(key: K, value: Value) { return Config.empty().withValue(key, value); } - static addValue( - key: K, - value: Value - ) { + static addValue(key: K, value: Value) { return Config.empty().withValue(key, value); } diff --git a/lib/config/builder/list.ts b/lib/config/builder/list.ts index 970e6bf..d5627e6 100644 --- a/lib/config/builder/list.ts +++ b/lib/config/builder/list.ts @@ -1,6 +1,6 @@ import { BuilderExtract, IBuilder } from "./builder"; import { Config } from "./config"; -import { InputSpec, ListValueSpecNumber, ListValueSpecString, UniqueBy, ValueSpecList } from "../config-types"; +import { InputSpec, ListValueSpecNumber, ListValueSpecString, UniqueBy, ValueSpecList } from "../configTypes"; import { guardAll } from "../../util"; /** * Used as a subtype of Value.list diff --git a/lib/config/builder/value.ts b/lib/config/builder/value.ts index 0b08dc0..507ed45 100644 --- a/lib/config/builder/value.ts +++ b/lib/config/builder/value.ts @@ -9,9 +9,9 @@ import { ValueSpecList, ValueSpecNumber, ValueSpecTextarea, -} from "../config-types"; +} from "../configTypes"; import { guardAll } from "../../util"; -import { DefaultString } from "../config-types"; +import { DefaultString } from "../configTypes"; /** * 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. @@ -35,12 +35,7 @@ const username = Value.string({ ``` */ export class Value extends IBuilder { - static boolean(a: { - name: string; - description?: string | null; - warning?: string | null; - default?: boolean | null; - }) { + static boolean(a: { name: string; description?: string | null; warning?: string | null; default?: boolean | null }) { return new Value({ description: null, warning: null, @@ -166,9 +161,7 @@ export class Value extends IBuilder { spec, }); } - static union< - V extends Variants<{ [key: string]: { name: string; spec: InputSpec } }> - >( + static union>( a: { name: string; description?: string | null; diff --git a/lib/config/builder/variants.ts b/lib/config/builder/variants.ts index 1c92bb3..6819171 100644 --- a/lib/config/builder/variants.ts +++ b/lib/config/builder/variants.ts @@ -1,4 +1,4 @@ -import { InputSpec } from "../config-types"; +import { InputSpec } from "../configTypes"; import { BuilderExtract, IBuilder } from "./builder"; import { Config } from "."; @@ -80,10 +80,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/config/configTypes.ts b/lib/config/configTypes.ts new file mode 100644 index 0000000..f5c018c --- /dev/null +++ b/lib/config/configTypes.ts @@ -0,0 +1,53 @@ +import * as C from "./configTypesRaw"; + +export type ValueType = C.ValueType; +// prettier-ignore +export type WithOutOptionals = + A extends Record ? {[k in keyof A]: A[k]} : + A; + +export type InputSpec = Record>; + +export type ValueSpec = WithOutOptionals; + +/** core spec types. These types provide the metadata for performing validations */ +export type ValueSpecOf = WithOutOptionals>; + +export type ValueSpecString = WithOutOptionals; +export type ValueSpecTextarea = WithOutOptionals; +export type ValueSpecNumber = WithOutOptionals; +export type ValueSpecSelect = WithOutOptionals; +export type ValueSpecMultiselect = WithOutOptionals; +export type ValueSpecBoolean = WithOutOptionals; +export type ValueSpecUnion = WithOutOptionals; +export type ValueSpecFile = WithOutOptionals; +export type ValueSpecObject = WithOutOptionals; +export type WithStandalone = WithOutOptionals; +export type SelectBase = WithOutOptionals; +export type ListValueSpecType = WithOutOptionals; + +/** represents a spec for the values of a list */ +export type ListValueSpecOf = WithOutOptionals>; + +/** represents a spec for a list */ +export type ValueSpecList = WithOutOptionals; +export type ValueSpecListOf = WithOutOptionals>; + +// sometimes the type checker needs just a little bit of help +export function isValueSpecListOf( + t: ValueSpecListOf, + s: S +): t is ValueSpecListOf & { spec: ListValueSpecOf } { + return t.spec.type === s; +} +export type ListValueSpecString = WithOutOptionals; +export type ListValueSpecNumber = WithOutOptionals; +export type ListValueSpecObject = WithOutOptionals; +export type UniqueBy = WithOutOptionals; +export type DefaultString = WithOutOptionals; + +export const unionSelectKey = "unionSelectKey" as const; +export type UnionSelectKey = typeof unionSelectKey; + +export const unionValueKey = "unionValueKey" as const; +export type UnionValueKey = typeof unionValueKey; diff --git a/lib/config/config-types.ts b/lib/config/configTypesRaw.ts similarity index 65% rename from lib/config/config-types.ts rename to lib/config/configTypesRaw.ts index c83f19a..7bef8e0 100644 --- a/lib/config/config-types.ts +++ b/lib/config/configTypesRaw.ts @@ -1,5 +1,4 @@ -export type InputSpec = Record; - +export type InputSpecRaw = Record; export type ValueType = | "string" | "textarea" @@ -12,7 +11,6 @@ export type ValueType = | "file" | "union"; export type ValueSpec = ValueSpecOf; - /** core spec types. These types provide the metadata for performing validations */ export type ValueSpecOf = T extends "string" ? ValueSpecString @@ -35,71 +33,64 @@ export type ValueSpecOf = T extends "string" : T extends "union" ? ValueSpecUnion : never; - export interface ValueSpecString extends ListValueSpecString, WithStandalone { required: boolean; - default: DefaultString | null; + default?: DefaultString | null; } - export interface ValueSpecTextarea extends WithStandalone { type: "textarea"; - placeholder: string | null; + placeholder?: string | null; required: boolean; } - export interface ValueSpecNumber extends ListValueSpecNumber, WithStandalone { required: boolean; - default: number | null; + default?: number | null; } - export interface ValueSpecSelect extends SelectBase, WithStandalone { type: "select"; required: boolean; - default: string | null; + default?: string | null; } - export interface ValueSpecMultiselect extends SelectBase, WithStandalone { type: "multiselect"; /**'[0,1]' (inclusive) OR '[0,*)' (right unbounded), normal math rules */ - range: string; - default: string[]; + range?: string; + default?: string[]; } - export interface ValueSpecBoolean extends WithStandalone { type: "boolean"; - default: boolean | null; + default?: boolean | null; } - export interface ValueSpecUnion extends WithStandalone { type: "union"; - variants: Record; + variants: Record< + string, + { + name: string; + spec: InputSpecRaw; + } + >; required: boolean; - default: string | null; + default?: string | null; } - export interface ValueSpecFile extends WithStandalone { type: "file"; extensions: string[]; required: boolean; } - export interface ValueSpecObject extends WithStandalone { type: "object"; - spec: InputSpec; + spec: InputSpecRaw; } - export interface WithStandalone { name: string; - description: string | null; - warning: string | null; + description?: string | null; + warning?: string | null; } - export interface SelectBase { values: Record; } - export type ListValueSpecType = "string" | "number" | "object"; - /** represents a spec for the values of a list */ export type ListValueSpecOf = T extends "string" ? ListValueSpecString @@ -108,15 +99,13 @@ export type ListValueSpecOf = T extends "string" : T extends "object" ? ListValueSpecObject : never; - /** represents a spec for a list */ export type ValueSpecList = ValueSpecListOf; -export interface ValueSpecListOf - extends WithStandalone { +export interface ValueSpecListOf extends WithStandalone { type: "list"; spec: ListValueSpecOf; - range: string; // '[0,1]' (inclusive) OR '[0,*)' (right unbounded), normal math rules - default: + range?: string; + default?: | string[] | number[] | DefaultString[] @@ -126,53 +115,48 @@ export interface ValueSpecListOf | readonly DefaultString[] | readonly Record[]; } - -// sometimes the type checker needs just a little bit of help -export function isValueSpecListOf( +export declare function isValueSpecListOf( t: ValueSpecListOf, s: S -): t is ValueSpecListOf & { spec: ListValueSpecOf } { - return t.spec.type === s; -} - +): t is ValueSpecListOf & { + spec: ListValueSpecOf; +}; export interface ListValueSpecString { type: "string"; - pattern: string | null; - patternDescription: string | null; - masked: boolean; // default = false - inputmode: "text" | "email" | "tel" | "url"; // default = 'text' - placeholder: string | null; + pattern?: string | null; + patternDescription?: string | null; + masked?: boolean; + inputmode?: "text" | "email" | "tel" | "url"; + placeholder?: string | null; } - export interface ListValueSpecNumber { type: "number"; - /** '[0,1]' (inclusive) OR '[0,*)' (right unbounded), normal math rules */ - range: string; - integral: boolean; // default = false - units: string | null; - placeholder: string | null; + range?: string; + integral: boolean; + units?: string | null; + placeholder?: string | null; } - export interface ListValueSpecObject { type: "object"; /** this is a mapped type of the config object at this level, replacing the object's values with specs on those values */ - spec: InputSpec; + spec: InputSpecRaw; /** indicates whether duplicates can be permitted in the list */ uniqueBy: UniqueBy; /** this should be a handlebars template which can make use of the entire config which corresponds to 'spec' */ - displayAs: string | null; + displayAs?: string | null; } - export type UniqueBy = | null | string - | { any: readonly UniqueBy[] | UniqueBy[] } - | { all: readonly UniqueBy[] | UniqueBy[] }; - -export type DefaultString = string | { charset: string; len: number }; - -export declare const unionSelectKey: "unionSelectKey"; -export type UnionSelectKey = typeof unionSelectKey; - -export declare const unionValueKey: "unionValueKey"; -export type UnionValueKey = typeof unionValueKey; + | { + any: readonly UniqueBy[] | UniqueBy[]; + } + | { + all: readonly UniqueBy[] | UniqueBy[]; + }; +export type DefaultString = + | string + | { + charset: string; + len: number; + }; diff --git a/lib/config/index.ts b/lib/config/index.ts index 00f47ca..ebcd55a 100644 --- a/lib/config/index.ts +++ b/lib/config/index.ts @@ -1,3 +1,3 @@ export * as configBuilder from "./builder"; -export { setupConfigExports } from "./setup_config_export"; +export { setupConfigExports } from "./setupConfigExports"; diff --git a/lib/config/setup_config_export.ts b/lib/config/setupConfigExports.ts similarity index 96% rename from lib/config/setup_config_export.ts rename to lib/config/setupConfigExports.ts index b86b57f..d4cb19e 100644 --- a/lib/config/setup_config_export.ts +++ b/lib/config/setupConfigExports.ts @@ -1,6 +1,6 @@ import { Config } from "./builder"; import { DeepPartial, DependsOn, Effects, ExpectedExports } from "../types"; -import { InputSpec } from "./config-types"; +import { InputSpec } from "./configTypes"; import { nullIfEmpty } from "../util"; import { TypeFromProps } from "../util/propertiesMatcher"; diff --git a/lib/emver-lite/emverList.test.ts b/lib/emverLite/emverList.test.ts similarity index 100% rename from lib/emver-lite/emverList.test.ts rename to lib/emverLite/emverList.test.ts diff --git a/lib/emver-lite/mod.ts b/lib/emverLite/mod.ts similarity index 100% rename from lib/emver-lite/mod.ts rename to lib/emverLite/mod.ts diff --git a/lib/index.ts b/lib/index.ts index 7930563..ca3156e 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -7,6 +7,6 @@ export * as healthUtil from "./health/util"; export * as util from "./util"; export * as configBuilder from "./config/builder"; export * as backup from "./backup"; -export * as configTypes from "./config/config-types"; +export * as configTypes from "./config/configTypes"; export * as config from "./config"; export * as health from "./health"; diff --git a/lib/test/config-builder.test.ts b/lib/test/configBuilder.test.ts similarity index 96% rename from lib/test/config-builder.test.ts rename to lib/test/configBuilder.test.ts index 7f81b89..509d3e4 100644 --- a/lib/test/config-builder.test.ts +++ b/lib/test/configBuilder.test.ts @@ -3,6 +3,7 @@ import { Config } from "../config/builder/config"; import { List } from "../config/builder/list"; import { Value } from "../config/builder/value"; import { Variants } from "../config/builder/variants"; +import { Parser } from "ts-matches"; describe("builder tests", () => { test("String", () => { @@ -138,9 +139,9 @@ describe("values", () => { }) ); const validator = value.validator(); - validator.unsafeCast({ unionSelectKey: "a", unionSelectValue: { b: false } }); + validator.unsafeCast({ unionSelectKey: "a", unionValueKey: { b: false } }); type Test = typeof validator._TYPE; - testOutput()(null); + testOutput()(null); }); test("list", () => { const value = Value.list( diff --git a/lib/test/config-types.test.ts b/lib/test/configTypes.test.ts similarity index 97% rename from lib/test/config-types.test.ts rename to lib/test/configTypes.test.ts index 98a145c..ae186b4 100644 --- a/lib/test/config-types.test.ts +++ b/lib/test/configTypes.test.ts @@ -1,4 +1,4 @@ -import { ListValueSpecOf, ValueSpecList, isValueSpecListOf } from "../config/config-types"; +import { ListValueSpecOf, ValueSpecList, isValueSpecListOf } from "../config/configTypes"; import { Config } from "../config/builder/config"; import { List } from "../config/builder/list"; import { Value } from "../config/builder/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 8a1da7b..e7d59dc 100644 --- a/lib/test/output.test.ts +++ b/lib/test/output.test.ts @@ -1,9 +1,8 @@ -import { UnionSelectKey, unionSelectKey } from "../config/config-types"; -import { InputSpec, matchInputSpec } from "./output"; +import { Parser } from "ts-matches"; +import { UnionSelectKey, unionSelectKey, UnionValueKey, unionValueKey } from "../config/configTypes"; +import { InputSpec, matchInputSpec, testListUnion } from "./output"; -export 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; export function testOutput(): (c: IfEquals) => null { @@ -13,11 +12,7 @@ export function testOutput(): (c: IfEquals) => 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 +44,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); @@ -73,7 +58,7 @@ describe("Inputs", () => { const validInput: InputSpec = { testListUnion: [ { - [unionSelectKey]: { [unionSelectKey]: "lnd", name: "string" }, + union: { [unionSelectKey]: "lnd", [unionValueKey]: { name: "string" } }, }, ], rpc: { @@ -112,7 +97,8 @@ describe("Inputs", () => { }, dbcache: 5, pruning: { - [unionSelectKey]: "disabled", + unionSelectKey: "disabled", + unionValueKey: {}, }, blockfilters: { blockfilterindex: false, @@ -121,19 +107,19 @@ describe("Inputs", () => { bloomfilters: { peerbloomfilters: false }, }, }; + + test("Test just the input unions", () => { + testListUnion.validator().unsafeCast(validInput.testListUnion); + }); test("test valid input", () => { const output = matchInputSpec.unsafeCast(validInput); expect(output).toEqual(validInput); }); 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( @@ -142,6 +128,5 @@ describe("Inputs", () => { }) ) ).toThrowError(); - matchInputSpec.unsafeCast(validInput); }); }); diff --git a/lib/types.ts b/lib/types.ts index 70456ff..774213f 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -1,42 +1,27 @@ -export * as configTypes from "./config/config-types"; -import { InputSpec } from "./config/config-types"; +export * as configTypes from "./config/configTypes"; +import { InputSpec } from "./config/configTypes"; export namespace ExpectedExports { version: 1; /** Set configuration is called after we have modified and saved the configuration in the embassy ui. Use this to make a file for the docker to read from for configuration. */ - export type setConfig = (options: { - effects: Effects; - input: Record; - }) => Promise; + export type setConfig = (options: { effects: Effects; input: Record }) => Promise; /** Get configuration returns a shape that describes the format that the embassy ui will generate, and later send to the set config */ - export type getConfig = (options: { - effects: Effects; - config: unknown; - }) => Promise; + export type getConfig = (options: { effects: Effects; config: unknown }) => Promise; /** These are how we make sure the our dependency configurations are valid and if not how to fix them. */ export type dependencies = Dependencies; /** For backing up service data though the embassyOS UI */ - export type createBackup = (options: { - effects: Effects; - }) => Promise; + export type createBackup = (options: { effects: Effects }) => Promise; /** For restoring service data that was previously backed up using the embassyOS UI create backup flow. Backup restores are also triggered via the embassyOS UI, or doing a system restore flow during setup. */ - export type restoreBackup = (options: { - effects: Effects; - }) => Promise; + export type restoreBackup = (options: { effects: Effects }) => Promise; /** Properties are used to get values from the docker, like a username + password, what ports we are hosting from */ - export type properties = (options: { - effects: Effects; - }) => Promise; + export type properties = (options: { effects: Effects }) => Promise; /** Health checks are used to determine if the service is working properly after starting * A good use case is if we are using a web server, seeing if we can get to the web server. */ export type health = { /** Should be the health check id */ - [id: string]: (options: { - effects: Effects; - input: TimeMs; - }) => Promise; + [id: string]: (options: { effects: Effects; input: TimeMs }) => Promise; }; /** @@ -45,36 +30,24 @@ export namespace ExpectedExports { * service starting, and that file would indicate that it would rescan all the data. */ export type action = { - [id: string]: (options: { - effects: Effects; - input?: Record; - }) => Promise; + [id: string]: (options: { effects: Effects; input?: Record }) => Promise; }; /** * This is the entrypoint for the main container. Used to start up something like the service that the * package represents, like running a bitcoind in a bitcoind-wrapper. */ - export type main = (options: { - effects: Effects; - started(onTerm: () => void): null; - }) => Promise; + export type main = (options: { effects: Effects; started(onTerm: () => void): null }) => Promise; /** * Every time a package completes an install, this function is called before the main. * Can be used to do migration like things. */ - export type init = (options: { - effects: Effects; - previousVersion: null | string; - }) => Promise; + export type init = (options: { effects: Effects; previousVersion: null | string }) => Promise; /** This will be ran during any time a package is uninstalled, for example during a update * this will be called. */ - export type uninit = (options: { - effects: Effects; - nextVersion: null | string; - }) => Promise; + export type uninit = (options: { effects: Effects; nextVersion: null | string }) => Promise; } export type TimeMs = number; export type VersionString = string; @@ -88,11 +61,7 @@ export type ConfigRes = { /** Used to reach out from the pure js runtime */ export type Effects = { /** Usable when not sandboxed */ - writeFile(input: { - path: string; - volumeId: string; - toWrite: string; - }): Promise; + writeFile(input: { path: string; volumeId: string; toWrite: string }): Promise; readFile(input: { volumeId: string; path: string }): Promise; metadata(input: { volumeId: string; path: string }): Promise; /** Create a directory. Usable when not sandboxed */ @@ -104,27 +73,13 @@ export type Effects = { removeFile(input: { volumeId: string; path: string }): Promise; /** Write a json file into an object. Usable when not sandboxed */ - writeJsonFile(input: { - volumeId: string; - path: string; - toWrite: Record; - }): Promise; + writeJsonFile(input: { volumeId: string; path: string; toWrite: Record }): Promise; /** Read a json file into an object */ - readJsonFile(input: { - volumeId: string; - path: string; - }): Promise>; + readJsonFile(input: { volumeId: string; path: string }): Promise>; - shell( - command: string, - options?: { timeoutMillis?: number | null } - ): Promise; - runCommand(input: { - command: string; - args?: string[]; - timeoutMillis?: number; - }): Promise; + shell(command: string, options?: { timeoutMillis?: number | null }): Promise; + runCommand(input: { command: string; args?: string[]; timeoutMillis?: number }): Promise; runDaemon(input: { command: string; args?: string[] }): { wait(): Promise; term(): Promise; @@ -154,17 +109,9 @@ export type Effects = { /** Check that a file exists or not */ exists(input: { volumeId: string; path: string }): Promise; /** Declaring that we are opening a interface on some protocal for local network */ - bindLocal(options: { - internalPort: number; - name: string; - externalPort: number; - }): Promise; + bindLocal(options: { internalPort: number; name: string; externalPort: number }): Promise; /** Declaring that we are opening a interface on some protocal for tor network */ - bindTor(options: { - internalPort: number; - name: string; - externalPort: number; - }): Promise; + bindTor(options: { internalPort: number; name: string; externalPort: number }): Promise; /** Similar to the fetch api via the mdn, this is simplified but the point is * to get something from some website, and return the response. @@ -212,22 +159,13 @@ export type Effects = { }): Promise; /** Get the address for another service for local internet*/ - getServiceLocalAddress(options: { - packageId: string; - interfaceName: string; - }): Promise; + getServiceLocalAddress(options: { packageId: string; interfaceName: string }): Promise; /** Get the address for another service for tor interfaces */ - getServiceTorAddress(options: { - packageId: string; - interfaceName: string; - }): Promise; + getServiceTorAddress(options: { packageId: string; interfaceName: string }): Promise; /** * Get the port address for another service */ - getServicePortForward(options: { - packageId: string; - internalPort: number; - }): Promise; + getServicePortForward(options: { packageId: string; internalPort: number }): Promise; /** When we want to create a link in the front end interfaces, and example is * exposing a url to view a web service @@ -283,10 +221,7 @@ export type Effects = { * * @returns PEM encoded fullchain (ecdsa) */ - getSslCertificate: ( - packageId: string, - algorithm?: "ecdsa" | "ed25519" - ) => [string, string, string]; + getSslCertificate: (packageId: string, algorithm?: "ecdsa" | "ed25519") => [string, string, string]; /** * @returns PEM encoded ssl key (ecdsa) */ @@ -414,6 +349,4 @@ export type Dependencies = { }; }; -export type DeepPartial = T extends {} - ? { [P in keyof T]?: DeepPartial } - : T; +export type DeepPartial = T extends {} ? { [P in keyof T]?: DeepPartial } : T; diff --git a/lib/util/propertiesMatcher.ts b/lib/util/propertiesMatcher.ts index 1e79d71..4299eab 100644 --- a/lib/util/propertiesMatcher.ts +++ b/lib/util/propertiesMatcher.ts @@ -3,21 +3,14 @@ import { Parser, Validator } from "ts-matches"; import { Variants } from "../config/builder"; import { InputSpec, + UnionSelectKey, unionSelectKey, + unionValueKey, + UnionValueKey, ValueSpec as ValueSpecAny, -} from "../config/config-types"; +} from "../config/configTypes"; -const { - string, - some, - arrayOf, - object, - dictionary, - unknown, - number, - literals, - boolean, -} = matches; +const { string, some, arrayOf, object, dictionary, unknown, number, literals, boolean } = matches; type TypeBoolean = "boolean"; type TypeString = "string"; @@ -85,7 +78,7 @@ type VariantValue = // prettier-ignore type GuardUnion = A extends { type: TypeUnion, variants: infer Variants & Record } ? ( - _<{[key in keyof Variants]: {unionSelectKey: key} & VariantValue}[keyof Variants]> + _<{[key in keyof Variants]: {[k in UnionSelectKey]: key} & {[k in UnionValueKey]: VariantValue}}[keyof Variants]> ) : unknown @@ -139,28 +132,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; @@ -177,16 +160,8 @@ export function matchNumberWithRange(range: string) { 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 @@ -218,9 +193,7 @@ const isGenerator = object({ 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); } @@ -236,9 +209,7 @@ function defaultRequired(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; } @@ -253,10 +224,7 @@ export function guardAll( return defaultRequired(string, value) as any; case "number": - return defaultRequired( - withIntegral(withRange(value), value), - value - ) as any; + return defaultRequired(withIntegral(withRange(value), value), value) as any; case "object": if (matchSpec.test(value)) { @@ -266,53 +234,43 @@ export function guardAll( 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); return defaultRequired( - matches - .arrayOf(guardAll(spec as any)) - .validate((x) => rangeValidate(x.length), "valid length"), + matches.arrayOf(guardAll(spec as any)).validate((x) => rangeValidate(x.length), "valid length"), value ) as any; } case "select": if (matchValues.test(value)) { const valueKeys = Object.keys(value.values); - return defaultRequired( - 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 maybeAddRangeValidate = , B>( - x: X - ) => { + const maybeAddRangeValidate = , B>(x: X) => { if (!matchRange.test(value)) return x; - return x.validate( - (x) => matchNumberWithRange(value.range).test(x.length), - "validLength" - ); + return x.validate((x) => matchNumberWithRange(value.range).test(x.length), "validLength"); }; const valueKeys = Object.keys(value.values); - return defaultRequired( - maybeAddRangeValidate(arrayOf(literals(valueKeys[0], ...valueKeys))), - 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) - ) + ...Object.entries(value.variants) + .filter(([name]) => string.test(name)) + .map(([name, { spec }]) => + object({ + unionSelectKey: literals(name), + unionValueKey: typeFromProps(spec), + }) + ) ) as any; } return unknown as any; @@ -328,16 +286,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.json b/package.json index 840623a..f23deeb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "start-sdk", - "version": "0.4.0-lib0.beta9", + "version": "0.4.0-lib0.charlie1", "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 5447655..2444a4f 100644 --- a/scripts/oldSpecToBuilder.ts +++ b/scripts/oldSpecToBuilder.ts @@ -1,22 +1,17 @@ import camelCase from "lodash/camelCase"; import * as fs from "fs"; import { string } from "ts-matches"; -import { unionSelectKey } from "../lib/config/config-types"; +import { unionSelectKey } from "../lib/config/configTypes"; export async function writeConvertedFile( file: string, 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"); @@ -45,7 +35,7 @@ export default async function makeFileContent( for (const [key, value] of Object.entries(data)) { const variableName = newConst(key, convertValueSpec(value)); - answer += `"${key}": ${variableName},`; + answer += `${JSON.stringify(key)}: ${variableName},`; } return `${answer}});`; } @@ -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) @@ -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)}, @@ -174,9 +158,7 @@ export default async function makeFileContent( function convertList(value: any) { switch (value.subtype) { case "string": { - return `List.${ - value?.spec?.textarea === true ? "textarea" : "string" - }(${JSON.stringify( + return `List.${value?.spec?.textarea === true ? "textarea" : "string"}(${JSON.stringify( { name: value.name || null, range: value.range || null, @@ -235,10 +217,7 @@ 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)}, @@ -254,19 +233,14 @@ export default async function makeFileContent( 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)}, required: ${JSON.stringify(!(value?.spec?.tag?.nullable || false))}, default: ${JSON.stringify(value?.spec?.default || null)}, @@ -277,7 +251,7 @@ export default async function makeFileContent( value.name + "_list_config", ` Config.of({ - ${unionSelectKey}: ${unionValueName} + "union": ${unionValueName} }) ` ); @@ -297,16 +271,11 @@ export default async function makeFileContent( 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}})`; }