From 5f9a9ea4ff20900dbf7b933e136ed9cd299f828a Mon Sep 17 00:00:00 2001 From: BluJ Date: Tue, 28 Mar 2023 11:37:17 -0600 Subject: [PATCH] chore: Fix the testing of the output.ts --- lib/config/builder/builder.ts | 2 +- lib/config/builder/list.ts | 6 +- lib/config/builder/value.ts | 6 +- lib/config/config-types.ts | 13 ++-- lib/health/healthRunner.ts | 21 +++--- lib/test/output.test.ts | 105 ++++++++++++++++++++++++--- lib/util/propertiesMatcher.ts | 130 ++++++++++------------------------ package-lock.json | 6 +- package.json | 5 +- scripts/oldSpecToBuilder.ts | 60 ++++------------ 10 files changed, 172 insertions(+), 182 deletions(-) diff --git a/lib/config/builder/builder.ts b/lib/config/builder/builder.ts index 56e1034..dbc844c 100644 --- a/lib/config/builder/builder.ts +++ b/lib/config/builder/builder.ts @@ -1,5 +1,5 @@ export class IBuilder { - protected constructor(readonly a: A) { } + protected constructor(readonly a: A) {} public build(): A { return this.a; diff --git a/lib/config/builder/list.ts b/lib/config/builder/list.ts index 619be93..fd986a1 100644 --- a/lib/config/builder/list.ts +++ b/lib/config/builder/list.ts @@ -1,10 +1,6 @@ import { BuilderExtract, IBuilder } from "./builder"; import { Config } from "./config"; -import { - InputSpec, - UniqueBy, - ValueSpecList, -} from "../config-types"; +import { InputSpec, UniqueBy, ValueSpecList } from "../config-types"; import { guardAll } from "../../util"; /** diff --git a/lib/config/builder/value.ts b/lib/config/builder/value.ts index 7f442be..ad4629e 100644 --- a/lib/config/builder/value.ts +++ b/lib/config/builder/value.ts @@ -14,9 +14,9 @@ import { guardAll } from "../../util"; export type DefaultString = | string | { - charset: string | null | undefined; - len: number; - }; + charset: string | null | undefined; + len: number; + }; /** * A value is going to be part of the form in the FE of the OS. diff --git a/lib/config/config-types.ts b/lib/config/config-types.ts index 94552cc..ea9fc6a 100644 --- a/lib/config/config-types.ts +++ b/lib/config/config-types.ts @@ -67,7 +67,7 @@ export interface ValueSpecBoolean extends WithStandalone { export interface ValueSpecUnion extends WithStandalone { type: "union"; nullable: boolean; - default: null | string + default: null | string; variants: Record; } @@ -105,8 +105,7 @@ export type ListValueSpecOf = T extends "string" /** represents a spec for a list */ export type ValueSpecList = ValueSpecListOf; -export interface ValueSpecListOf - extends WithStandalone { +export interface ValueSpecListOf extends WithStandalone { type: "list"; subtype: T; spec: ListValueSpecOf; @@ -123,10 +122,7 @@ export interface ValueSpecListOf } // sometimes the type checker needs just a little bit of help -export function isValueSpecListOf( - t: ValueSpecList, - s: S -): t is ValueSpecListOf { +export function isValueSpecListOf(t: ValueSpecList, s: S): t is ValueSpecListOf { return t.subtype === s; } @@ -162,4 +158,5 @@ export type UniqueBy = export type DefaultString = string | { charset: string; len: number }; -export const unionSelectKey = 'unionSelectKey' as const +export const unionSelectKey = "unionSelectKey" as const; +export type UnionSelectKey = typeof unionSelectKey; diff --git a/lib/health/healthRunner.ts b/lib/health/healthRunner.ts index 1ed073b..f610174 100644 --- a/lib/health/healthRunner.ts +++ b/lib/health/healthRunner.ts @@ -40,8 +40,8 @@ async function timeoutHealth( const defaultParams = { defaultIntervalS: 60, defaultTimeoutS: 10, - defaultDelayS: 10 -} + defaultDelayS: 10, +}; export default function healthRunner( name: string, @@ -52,15 +52,14 @@ export default function healthRunner( /** \ * All values in seconds * defaults: - * interval: 60s - * timeout: 10s - * delay: 10s - */ - create(effects: Types.Effects, { - interval = 60, - timeout = 10, - delay = 10, - } = {}) { + * interval: 60s + * timeout: 10s + * delay: 10s + */ + create( + effects: Types.Effects, + { interval = 60, timeout = 10, delay = 10 } = {} + ) { let running: any; function startFn() { clearInterval(running); diff --git a/lib/test/output.test.ts b/lib/test/output.test.ts index 8805ced..c0fe080 100644 --- a/lib/test/output.test.ts +++ b/lib/test/output.test.ts @@ -1,19 +1,108 @@ -import { InputSpec, matchInputSpec } from "./output"; +import { unionSelectKey } from "../config/config-types"; +import { InputSpec, matchInputSpec, threads } from "./output"; -type TEqual = A extends B ? (B extends A ? null : never) : never; -function testOutput(): (c: TEqual) => null { +type IfEquals = (() => G extends T ? 1 : 2) extends () => G extends U ? 1 : 2 + ? Y + : N; +function testOutput(): (c: IfEquals) => null { return () => null; } -const testValue = null as unknown; + +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; +export function mergeDeep(...sources: A) { + return _mergeDeep({}, ...sources); +} +function _mergeDeep( + target: UnionToIntersection | {}, + ...sources: A +): UnionToIntersection | {} { + if (!sources.length) return target; + const source = sources.shift(); + + if (isObject(target) && isObject(source)) { + for (const key in source) { + if (isObject((source as any)[key])) { + if (!(target as any)[key]) Object.assign(target, { [key]: {} }); + _mergeDeep((target as any)[key], (source as any)[key]); + } else { + Object.assign(target, { [key]: (source as any)[key] }); + } + } + } + + return _mergeDeep(target, ...sources); +} + // @ts-expect-error Because enable should be a boolean testOutput()(null); testOutput()(null); testOutput()(null); -testOutput()(null); -testOutput()(null); +testOutput()(null); +testOutput()(null); +testOutput()(null); +testOutput()(null); describe("Inputs", () => { - test("test", () => { - expect(true).toEqual(true); + const validInput: InputSpec = { + rpc: { + enable: true, + username: "test", + password: "test", + advanced: { + auth: ["test"], + serialversion: "segwit", + servertimeout: 6, + threads: 3, + workqueue: 9, + }, + }, + "zmq-enabled": false, + txindex: false, + wallet: { enable: false, avoidpartialspends: false, discardfee: 0.0001 }, + advanced: { + mempool: { + maxmempool: 1, + persistmempool: true, + mempoolexpiry: 23, + mempoolfullrbf: true, + }, + peers: { + listen: true, + onlyconnect: true, + onlyonion: true, + addnode: [ + { + hostname: "test", + port: 1, + }, + ], + }, + dbcache: 5, + pruning: { + [unionSelectKey]: "disabled", + }, + blockfilters: { + blockfilterindex: false, + peerblockfilters: false, + }, + bloomfilters: { peerbloomfilters: false }, + }, + }; + 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 } }))).toThrowError(); + + expect(() => + matchInputSpec.unsafeCast(mergeDeep(validInput, { rpc: { advanced: { serialversion: "testing" } } })) + ).toThrowError(); }); }); diff --git a/lib/util/propertiesMatcher.ts b/lib/util/propertiesMatcher.ts index ab49b8c..c7479a4 100644 --- a/lib/util/propertiesMatcher.ts +++ b/lib/util/propertiesMatcher.ts @@ -1,9 +1,8 @@ import * as matches from "ts-matches"; import { Parser } from "ts-matches"; -import { InputSpec, ValueSpec as ValueSpecAny } from "../config/config-types"; +import { InputSpec, unionSelectKey, ValueSpec as ValueSpecAny } from "../config/config-types"; -const { string, some, object, dictionary, unknown, number, literals, boolean } = - matches; +const { string, some, object, dictionary, unknown, number, literals, boolean } = matches; type TypeBoolean = "boolean"; type TypeString = "string"; @@ -16,69 +15,57 @@ type TypeUnion = "union"; // prettier-ignore type GuardDefaultNullable = - A extends { readonly default: unknown } ? Type : - A extends { readonly nullable: true } ? Type : - A extends { readonly nullable: false } ? Type | null | undefined : + A extends { default: unknown } ? Type : + A extends { nullable: true } ? Type : + A extends { nullable: false } ? Type | null | undefined : Type // prettier-ignore type GuardNumber = - A extends { readonly type: TypeNumber } ? GuardDefaultNullable : + A extends { type: TypeNumber } ? GuardDefaultNullable : unknown // prettier-ignore type GuardString = - A extends { readonly type: TypeString } ? GuardDefaultNullable : + A extends { type: TypeString } ? GuardDefaultNullable : unknown // prettier-ignore type GuardBoolean = - A extends { readonly type: TypeBoolean } ? GuardDefaultNullable : + A extends { type: TypeBoolean } ? GuardDefaultNullable : unknown // prettier-ignore type GuardObject = - A extends { readonly type: TypeObject, readonly spec: infer B } ? ( - B extends Record ? { readonly [K in keyof B & string]: _> } : + A extends { type: TypeObject, spec: infer B } ? ( + B extends Record ? { [K in keyof B & string]: _> } : { _error: "Invalid Spec" } ) : unknown // prettier-ignore export type GuardList = - A extends { readonly type: TypeList, readonly subtype: infer B, spec?: { spec?: infer C } } ? ReadonlyArray & ({ type: B, spec: C })>> : - A extends { readonly type: TypeList, readonly subtype: infer B, spec?: {} } ? ReadonlyArray & ({ type: B })>> : + A extends { type: TypeList, subtype: infer B, spec?: { spec?: infer C } } ? Array & ({ type: B, spec: C })>> : + A extends { type: TypeList, subtype: infer B, spec?: {} } ? Array & ({ type: B })>> : unknown // prettier-ignore type GuardSelect = - A extends { readonly type: TypeSelect, variants: infer B } ? ( + A extends { type: TypeSelect, values: infer B } ? ( B extends Record ? keyof B : never ) : unknown -const bluj: GuardSelect<{ - type: "select"; -} & { - readonly name: "Serialization Version"; - readonly description: "Return raw transaction or block hex with Segwit or non-SegWit serialization."; - readonly warning: null; - readonly default: "segwit"; - readonly nullable: false; - readonly values: { - ...; - }; -}> - // prettier-ignore - type GuardMultiselect = - A extends { readonly type: TypeMultiselect, variants: { [key in infer B & string]: string } } ?B[] : +// prettier-ignore +type GuardMultiselect = + A extends { type: TypeMultiselect, variants: { [key in infer B & string]: string } } ?B[] : unknown // prettier-ignore type VariantValue = - A extends { name: string, spec: infer B } ? { name: A['name'], spec: TypeFromProps } : + A extends { name: string, spec: infer B } ? TypeFromProps : never // prettier-ignore type GuardUnion = - A extends { readonly type: TypeUnion, variants: infer Variants & Record } ? - { [K in keyof Variants]: { [key in K]: VariantValue['spec'] } }[keyof Variants] : + A extends { type: TypeUnion, variants: infer Variants & Record } ? + { [key in keyof Variants]: _<{[unionSelectKey]: key} & VariantValue> }[keyof Variants] : unknown type _ = T; @@ -92,7 +79,7 @@ export type GuardAll = GuardNumber & GuardMultiselect; // prettier-ignore export type TypeFromProps = - A extends Record ? { readonly [K in keyof A & string]: _> } : + A extends Record ? { [K in keyof A & string]: _> } : unknown; const isType = object({ type: string }); @@ -131,28 +118,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; @@ -168,16 +145,8 @@ export function matchNumberWithRange(range: string) { 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 @@ -209,9 +178,7 @@ const isGenerator = object({ function defaultNullable(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); } @@ -227,9 +194,7 @@ 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; } @@ -241,10 +206,7 @@ export function guardAll( return defaultNullable(string, value) as any; case "number": - return defaultNullable( - withIntegral(withRange(value), value), - value - ) as any; + return defaultNullable(withIntegral(withRange(value), value), value) as any; case "object": if (matchSpec.test(value)) { @@ -254,9 +216,7 @@ 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); const subtype = matchSubType.unsafeCast(value).subtype; return defaultNullable( @@ -269,34 +229,23 @@ 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 defaultNullable(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 rangeValidate = (matchRange.test(value) && matchNumberWithRange(value.range).test) || (() => true); const valueKeys = Object.keys(value.values); return defaultNullable( - matches - .literals(valueKeys[0], ...valueKeys) - .validate((x) => rangeValidate(x.length), "valid length"), + matches.literals(valueKeys[0], ...valueKeys).validate((x) => rangeValidate(x.length), "valid length"), 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; } @@ -311,16 +260,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 8555a51..bb7b535 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "start-sdk", - "version": "0.4.0-lib0.beta1", + "version": "0.4.0-lib0.beta3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "start-sdk", - "version": "0.4.0-lib0.beta1", + "version": "0.4.0-lib0.beta3", "license": "MIT", "dependencies": { "@iarna/toml": "^2.2.5", @@ -22,7 +22,6 @@ "ts-node": "^10.9.1", "tsc-multi": "^0.6.1", "tsconfig-paths": "^3.14.2", - "typescript": "^4.9.5", "vitest": "^0.29.2" } }, @@ -4896,6 +4895,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true, + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index 519b12d..17e660a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "start-sdk", - "version": "0.4.0-lib0.beta1", + "version": "0.4.0-lib0.beta3", "description": "For making the patterns that are wanted in making services for the startOS.", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -33,8 +33,7 @@ "ts-node": "^10.9.1", "tsc-multi": "^0.6.1", "tsconfig-paths": "^3.14.2", - "typescript": "^4.9.5", "vitest": "^0.29.2" }, "declaration": true -} \ No newline at end of file +} diff --git a/scripts/oldSpecToBuilder.ts b/scripts/oldSpecToBuilder.ts index be82e86..360c059 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"); @@ -99,10 +89,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) @@ -122,10 +109,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)}, @@ -168,7 +152,7 @@ export default async function makeFileContent( name: value.name || null, range: value.range || null, spec: { - masked: value?.spec?.masked || null, + masked: value?.spec?.masked || false, placeholder: value?.spec?.placeholder || null, pattern: value?.spec?.pattern || null, patternDescription: value?.spec?.["pattern-description"] || null, @@ -225,10 +209,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)}, @@ -245,10 +226,7 @@ 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"] || {}) ); // @TODO BluJ return `List.obj({ @@ -258,15 +236,9 @@ export default async function makeFileContent( spec: { ${unionSelectKey}: { type: "union", - name: ${JSON.stringify( - value?.spec?.tag?.name || null - )}, - description: ${JSON.stringify( - value?.spec?.tag?.description || null - )}, - warning: ${JSON.stringify( - value?.spec?.tag?.warning || null - )}, + name: ${JSON.stringify(value?.spec?.tag?.name || null)}, + description: ${JSON.stringify(value?.spec?.tag?.description || null)}, + warning: ${JSON.stringify(value?.spec?.tag?.warning || null)}, variants: ${variants}, nullable: false, } @@ -283,15 +255,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}})`; }