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