mirror of
https://github.com/Start9Labs/start-sdk.git
synced 2026-03-26 02:11:56 +00:00
feat: Add in the beginnings of the new config builders.
This commit is contained in:
9
config/builder.ts
Normal file
9
config/builder.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export class IBuilder<A> {
|
||||
protected constructor(readonly a: A) {}
|
||||
|
||||
public build(): A {
|
||||
return this.a;
|
||||
}
|
||||
}
|
||||
|
||||
export type BuilderExtract<A> = A extends IBuilder<infer B> ? B : never;
|
||||
24
config/config.ts
Normal file
24
config/config.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { BuilderExtract, IBuilder } from "./builder.ts";
|
||||
import { Value } from "./value.ts";
|
||||
|
||||
export class Config<A> extends IBuilder<A> {
|
||||
static empty() {
|
||||
return new Config({});
|
||||
}
|
||||
|
||||
static of<B extends { [key: string]: Value<unknown> }>(spec: B) {
|
||||
// deno-lint-ignore no-explicit-any
|
||||
const answer: { [K in keyof B]: BuilderExtract<B[K]> } = {} as any;
|
||||
for (const key in spec) {
|
||||
// deno-lint-ignore no-explicit-any
|
||||
answer[key] = spec[key].build() as any;
|
||||
}
|
||||
return new Config(answer);
|
||||
}
|
||||
addValue<K extends string, B>(key: K, value: Value<B>) {
|
||||
return new Config({
|
||||
...this.a,
|
||||
[key]: value.build(),
|
||||
} as A & { [key in K]: B });
|
||||
}
|
||||
}
|
||||
48
config/index.test.ts
Normal file
48
config/index.test.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Config } from "./config.ts";
|
||||
import { Pointer } from "./pointer.ts";
|
||||
import { Value } from "./value.ts";
|
||||
import { expect } from "https://deno.land/x/expect@v0.2.9/mod.ts";
|
||||
const { test } = Deno;
|
||||
|
||||
test("Pointer", () => {
|
||||
const bitcoinPropertiesBuilt: {
|
||||
"peer-tor-address": {
|
||||
name: string;
|
||||
description: string;
|
||||
type: "pointer";
|
||||
subtype: "package";
|
||||
"package-id": string;
|
||||
target: "tor-address";
|
||||
interface: string;
|
||||
};
|
||||
} = Config.empty()
|
||||
.addValue(
|
||||
"peer-tor-address",
|
||||
Value.pointer(
|
||||
Pointer.packageTorAddress({
|
||||
name: "Peer Tor Address",
|
||||
description: "The Tor address of the peer interface",
|
||||
"package-id": "bitcoind",
|
||||
interface: "peer",
|
||||
warning: null,
|
||||
})
|
||||
)
|
||||
)
|
||||
.build();
|
||||
expect(JSON.stringify(bitcoinPropertiesBuilt)).toEqual(
|
||||
/*json*/ `{
|
||||
"peer-tor-address": {
|
||||
"type": "pointer",
|
||||
"subtype": "package",
|
||||
"target": "tor-address",
|
||||
"name": "Peer Tor Address",
|
||||
"description": "The Tor address of the peer interface",
|
||||
"package-id": "bitcoind",
|
||||
"interface": "peer",
|
||||
"warning": null
|
||||
}}`
|
||||
.replaceAll("\n", " ")
|
||||
.replaceAll(/\s{2,}/g, "")
|
||||
.replaceAll(": ", ":")
|
||||
);
|
||||
});
|
||||
4
config/index.ts
Normal file
4
config/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { Config } from "./config.ts";
|
||||
export { List } from "./list.ts";
|
||||
export { Pointer } from "./pointer.ts";
|
||||
export { Value } from "./value.ts";
|
||||
116
config/list.ts
Normal file
116
config/list.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { UniqueBy } from "../types.ts";
|
||||
import { BuilderExtract, IBuilder } from "./builder.ts";
|
||||
import { Config } from "./config.ts";
|
||||
import { Default, NullableDefault, NumberSpec, StringSpec } from "./value.ts";
|
||||
import { Description } from "./value.ts";
|
||||
|
||||
export class List<A> extends IBuilder<A> {
|
||||
// deno-lint-ignore ban-types
|
||||
static boolean<A extends Description & Default<boolean[]> & { range: string; spec: {} }>(a: A) {
|
||||
return new List({
|
||||
subtype: "boolean" as const,
|
||||
...a,
|
||||
});
|
||||
}
|
||||
|
||||
static string<
|
||||
A extends Description & Default<string[] & { range: string; spec: null | { range: string; spec: StringSpec } }>
|
||||
>(a: A) {
|
||||
return new List({
|
||||
subtype: "string" as const,
|
||||
...a,
|
||||
});
|
||||
}
|
||||
static number<A extends Description & Default<number[]> & { range: string; spec: NumberSpec }>(a: A) {
|
||||
return new List({
|
||||
subtype: "number" as const,
|
||||
...a,
|
||||
});
|
||||
}
|
||||
static enum<
|
||||
A extends Description &
|
||||
Default<string[]> & {
|
||||
range: string;
|
||||
spec: {
|
||||
values: string[];
|
||||
"value-names": {
|
||||
[key: string]: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
>(a: A) {
|
||||
return new List({
|
||||
subtype: "enum" as const,
|
||||
...a,
|
||||
});
|
||||
}
|
||||
static object<
|
||||
A extends Description &
|
||||
NullableDefault<Record<string, unknown>[]> & {
|
||||
range: string;
|
||||
spec: {
|
||||
spec: Config<B>;
|
||||
"display-as": null | string;
|
||||
"unique-by": null | UniqueBy;
|
||||
};
|
||||
},
|
||||
B
|
||||
>(a: A) {
|
||||
const { spec: previousSpec, ...rest } = a;
|
||||
const { spec: previousSpecSpec, ...restSpec } = previousSpec;
|
||||
const specSpec = previousSpecSpec.build();
|
||||
const spec = {
|
||||
...restSpec,
|
||||
spec: specSpec,
|
||||
};
|
||||
const value = {
|
||||
spec,
|
||||
...rest,
|
||||
};
|
||||
return new List({
|
||||
subtype: "object" as const,
|
||||
...value,
|
||||
});
|
||||
}
|
||||
static union<
|
||||
A extends Description &
|
||||
Default<string[]> & {
|
||||
range: string;
|
||||
spec: {
|
||||
tag: {
|
||||
id: string;
|
||||
name: null | string | undefined;
|
||||
description: null | string | undefined;
|
||||
"variant-names": {
|
||||
[key: string]: string;
|
||||
};
|
||||
};
|
||||
variants: Variants;
|
||||
"display-as": null | string | undefined;
|
||||
"unique-by": null | UniqueBy | undefined;
|
||||
};
|
||||
},
|
||||
Variants extends { [key: string]: Config<unknown> }
|
||||
>(a: A) {
|
||||
const { spec: previousSpec, ...rest } = a;
|
||||
const { variants: previousVariants, ...restSpec } = previousSpec;
|
||||
// deno-lint-ignore no-explicit-any
|
||||
const variants: { [K in keyof Variants]: BuilderExtract<Variants[K]> } = {} as any;
|
||||
for (const key in previousVariants) {
|
||||
// deno-lint-ignore no-explicit-any
|
||||
variants[key] = previousVariants[key].build() as any;
|
||||
}
|
||||
const spec = {
|
||||
...restSpec,
|
||||
variants,
|
||||
};
|
||||
const value = {
|
||||
spec,
|
||||
...rest,
|
||||
};
|
||||
return new List({
|
||||
subtype: "union" as const,
|
||||
...value,
|
||||
});
|
||||
}
|
||||
}
|
||||
44
config/pointer.ts
Normal file
44
config/pointer.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { IBuilder } from "./builder.ts";
|
||||
import { Description } from "./value.ts";
|
||||
|
||||
export class Pointer<A> extends IBuilder<A> {
|
||||
static packageTorKey<A extends Description & { "package-id": string; interface: string }>(a: A) {
|
||||
return new Pointer({
|
||||
type: "pointer" as const,
|
||||
subtype: "package" as const,
|
||||
target: "tor-key" as const,
|
||||
...a,
|
||||
});
|
||||
}
|
||||
static packageTorAddress<A extends Description & { "package-id": string; interface: string }>(a: A) {
|
||||
return new Pointer({
|
||||
type: "pointer" as const,
|
||||
subtype: "package" as const,
|
||||
target: "tor-address" as const,
|
||||
...a,
|
||||
});
|
||||
}
|
||||
static packageLanAddress<A extends Description & { "package-id": string; interface: string }>(a: A) {
|
||||
return new Pointer({
|
||||
type: "pointer" as const,
|
||||
subtype: "package" as const,
|
||||
target: "lan-address" as const,
|
||||
...a,
|
||||
});
|
||||
}
|
||||
static packageConfig<A extends Description & { "package-id": string; selector: string; multi: boolean }>(a: A) {
|
||||
return new Pointer({
|
||||
type: "pointer" as const,
|
||||
subtype: "package" as const,
|
||||
target: "config" as const,
|
||||
...a,
|
||||
});
|
||||
}
|
||||
static system<A extends Description & Record<string, unknown>>(a: A) {
|
||||
return new Pointer({
|
||||
type: "pointer" as const,
|
||||
subtype: "system" as const,
|
||||
...a,
|
||||
});
|
||||
}
|
||||
}
|
||||
616
config/v2_spec.ts
Normal file
616
config/v2_spec.ts
Normal file
@@ -0,0 +1,616 @@
|
||||
// deno-lint-ignore-file ban-types
|
||||
import { DefaultString } from "../types.ts";
|
||||
import { matches } from "../dependencies.ts";
|
||||
|
||||
type Validator<A> = matches.Validator<unknown, A>;
|
||||
const { shape, boolean, string, dictionary, anyOf, arrayOf, every, deferred, number, recursive, unknown } = matches;
|
||||
|
||||
export const [matchConfig, setMatchConfig] = deferred<Config>();
|
||||
export type Config = {
|
||||
[key: string]: AnyValue;
|
||||
};
|
||||
|
||||
export const matchDescription = shape(
|
||||
{
|
||||
name: string,
|
||||
description: string,
|
||||
warning: string,
|
||||
},
|
||||
["description", "warning"]
|
||||
);
|
||||
|
||||
export const matchUniqueBy = recursive<_UniqueBy>((x) => anyOf(string, shape({ any: x }))).optional();
|
||||
type _UniqueBy = { any: _UniqueBy } | string;
|
||||
type UniqueBy = null | undefined | _UniqueBy;
|
||||
|
||||
export const matchBoolValue: Validator<BoolValue> = shape({
|
||||
boolean: every(
|
||||
matchDescription,
|
||||
shape({
|
||||
default: boolean,
|
||||
})
|
||||
),
|
||||
});
|
||||
export type BoolValue = {
|
||||
boolean: {
|
||||
name: string;
|
||||
description?: string;
|
||||
warning?: string;
|
||||
default: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export const matchDefaultString = anyOf(
|
||||
string,
|
||||
shape(
|
||||
{
|
||||
charset: string,
|
||||
len: number,
|
||||
},
|
||||
["charset"]
|
||||
)
|
||||
);
|
||||
|
||||
export const matchStringValue: Validator<StringValue> = shape({
|
||||
string: every(
|
||||
shape(
|
||||
{
|
||||
name: string,
|
||||
nullable: boolean,
|
||||
// optionals
|
||||
description: string,
|
||||
warning: string,
|
||||
default: matchDefaultString,
|
||||
copyable: boolean,
|
||||
masked: boolean,
|
||||
placeholder: string,
|
||||
},
|
||||
["description", "warning", "default", "copyable", "masked", "placeholder"]
|
||||
),
|
||||
anyOf(
|
||||
shape({}),
|
||||
shape({
|
||||
pattern: string,
|
||||
"pattern-description": string,
|
||||
})
|
||||
)
|
||||
),
|
||||
});
|
||||
export type StringValue = {
|
||||
string: {
|
||||
name: string;
|
||||
description?: string;
|
||||
warning?: string;
|
||||
default?: DefaultString;
|
||||
nullable: boolean;
|
||||
copyable?: boolean;
|
||||
masked?: boolean;
|
||||
placeholder?: string;
|
||||
} & (
|
||||
| {}
|
||||
| {
|
||||
pattern: string;
|
||||
"pattern-description": string;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const matchNumberValue: Validator<NumberValue> = shape({
|
||||
number: every(
|
||||
matchDescription,
|
||||
shape(
|
||||
{
|
||||
default: number,
|
||||
},
|
||||
["default"]
|
||||
)
|
||||
),
|
||||
});
|
||||
export type NumberValue = {
|
||||
number: {
|
||||
name: string;
|
||||
default?: number;
|
||||
description?: string;
|
||||
warning?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export const matchEnumValue: Validator<EnumValue> = shape({
|
||||
enum: shape(
|
||||
{
|
||||
name: string,
|
||||
default: string,
|
||||
values: arrayOf(string),
|
||||
"value-names": dictionary([string, string]),
|
||||
// optionals
|
||||
description: string,
|
||||
warning: string,
|
||||
},
|
||||
["description", "warning"]
|
||||
),
|
||||
});
|
||||
export type EnumValue = {
|
||||
enum: {
|
||||
name: string;
|
||||
default: string;
|
||||
values: string[] | readonly string[];
|
||||
"value-names": {
|
||||
[key: string]: string;
|
||||
};
|
||||
// optionals
|
||||
description?: string;
|
||||
warning?: string;
|
||||
};
|
||||
};
|
||||
|
||||
const matchListBooleanValue: Validator<ListValue["list"] & { boolean: unknown }> = shape({
|
||||
boolean: shape(
|
||||
{
|
||||
name: string,
|
||||
default: arrayOf(boolean),
|
||||
spec: shape({}),
|
||||
range: string,
|
||||
|
||||
//optionals
|
||||
description: string,
|
||||
warning: string,
|
||||
},
|
||||
["warning", "description"]
|
||||
),
|
||||
});
|
||||
const matchListStringValue: Validator<ListValue["list"] & { string: unknown }> = shape({
|
||||
string: shape(
|
||||
{
|
||||
name: string,
|
||||
default: arrayOf(string),
|
||||
spec: every(
|
||||
shape(
|
||||
{
|
||||
copyable: boolean,
|
||||
masked: boolean,
|
||||
placeholder: string,
|
||||
},
|
||||
["copyable", "masked", "placeholder"]
|
||||
),
|
||||
anyOf(
|
||||
shape({}),
|
||||
shape({
|
||||
pattern: string,
|
||||
"pattern-description": string,
|
||||
})
|
||||
)
|
||||
),
|
||||
range: string,
|
||||
|
||||
//optionals
|
||||
description: string,
|
||||
warning: string,
|
||||
},
|
||||
["warning", "description"]
|
||||
),
|
||||
});
|
||||
const matchListNumberValue: Validator<ListValue["list"] & { number: unknown }> = shape({
|
||||
number: shape(
|
||||
{
|
||||
name: string,
|
||||
default: arrayOf(number),
|
||||
spec: shape(
|
||||
{
|
||||
range: string,
|
||||
integral: boolean,
|
||||
units: string,
|
||||
placeholder: number,
|
||||
},
|
||||
["range", "integral", "units", "placeholder"]
|
||||
),
|
||||
range: string,
|
||||
|
||||
//optionals
|
||||
description: string,
|
||||
warning: string,
|
||||
},
|
||||
["warning", "description"]
|
||||
),
|
||||
});
|
||||
const matchListEnumValue: Validator<ListValue["list"] & { enum: unknown }> = shape({
|
||||
enum: shape(
|
||||
{
|
||||
name: string,
|
||||
default: arrayOf(string),
|
||||
spec: shape({
|
||||
values: arrayOf(string),
|
||||
"value-names": dictionary([string, string]),
|
||||
}),
|
||||
range: string,
|
||||
|
||||
//optionals
|
||||
description: string,
|
||||
warning: string,
|
||||
},
|
||||
["warning", "description"]
|
||||
),
|
||||
});
|
||||
const matchListObjectValue: Validator<ListValue["list"] & { object: unknown }> = shape({
|
||||
object: shape(
|
||||
{
|
||||
name: string,
|
||||
spec: shape(
|
||||
{
|
||||
spec: matchConfig,
|
||||
"display-as": string,
|
||||
"unique-by": matchUniqueBy,
|
||||
},
|
||||
["display-as", "unique-by"]
|
||||
),
|
||||
range: string,
|
||||
|
||||
//optionals
|
||||
default: arrayOf(dictionary([string, unknown])),
|
||||
description: string,
|
||||
warning: string,
|
||||
},
|
||||
["default", "warning", "description"]
|
||||
),
|
||||
});
|
||||
const matchListUnionValue: Validator<ListValue["list"] & { union: unknown }> = shape({
|
||||
union: shape(
|
||||
{
|
||||
name: string,
|
||||
default: arrayOf(string),
|
||||
spec: shape(
|
||||
{
|
||||
/** What tag for the specification, for tag unions */
|
||||
tag: shape(
|
||||
{
|
||||
id: string,
|
||||
name: string,
|
||||
description: string,
|
||||
"variant-names": dictionary([string, string]),
|
||||
},
|
||||
["name", "description"]
|
||||
),
|
||||
/** The possible enum values */
|
||||
variants: dictionary([string, matchConfig]),
|
||||
"display-as": string,
|
||||
"unique-by": matchUniqueBy,
|
||||
},
|
||||
["display-as", "unique-by"]
|
||||
),
|
||||
range: string,
|
||||
|
||||
//optionals
|
||||
description: string,
|
||||
warning: string,
|
||||
},
|
||||
["warning", "description"]
|
||||
),
|
||||
});
|
||||
export const matchListValue: Validator<ListValue> = shape({
|
||||
list: anyOf(
|
||||
matchListBooleanValue,
|
||||
matchListStringValue,
|
||||
matchListNumberValue,
|
||||
matchListEnumValue,
|
||||
matchListObjectValue,
|
||||
matchListUnionValue
|
||||
),
|
||||
});
|
||||
export type ListValue = {
|
||||
list:
|
||||
| {
|
||||
boolean: {
|
||||
name: string;
|
||||
default: boolean[] | readonly boolean[];
|
||||
spec: {};
|
||||
range: string;
|
||||
|
||||
//optionals
|
||||
description?: string;
|
||||
warning?: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
string: {
|
||||
name: string;
|
||||
default: string[] | readonly string[];
|
||||
spec:
|
||||
| {
|
||||
copyable?: boolean | undefined;
|
||||
masked?: boolean | undefined;
|
||||
placeholder?: string | undefined;
|
||||
}
|
||||
| ({
|
||||
pattern: string;
|
||||
"pattern-description": string;
|
||||
} & {});
|
||||
range: string;
|
||||
|
||||
//optionals
|
||||
description?: string;
|
||||
warning?: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
number: {
|
||||
name: string;
|
||||
default: number[] | readonly number[];
|
||||
spec: {
|
||||
range?: string | undefined;
|
||||
integral?: boolean | undefined;
|
||||
units?: string | undefined;
|
||||
placeholder?: number | undefined;
|
||||
};
|
||||
range: string;
|
||||
|
||||
//optionals
|
||||
description?: string;
|
||||
warning?: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
enum: {
|
||||
name: string;
|
||||
default: string[] | readonly string[];
|
||||
spec: {
|
||||
values: string[] | readonly string[];
|
||||
"value-names": {
|
||||
[key: string]: string;
|
||||
};
|
||||
};
|
||||
range: string;
|
||||
|
||||
//optionals
|
||||
description?: string;
|
||||
warning?: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
object: {
|
||||
name: string;
|
||||
spec: {
|
||||
spec: Config;
|
||||
"display-as"?: string | undefined;
|
||||
"unique-by"?: UniqueBy | undefined;
|
||||
};
|
||||
range: string;
|
||||
|
||||
//optionals
|
||||
default?: Record<string, unknown>[] | readonly Record<string, unknown>[];
|
||||
description?: string;
|
||||
warning?: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
union: {
|
||||
name: string;
|
||||
default: string[] | readonly string[];
|
||||
spec: {
|
||||
tag: {
|
||||
id: string;
|
||||
name?: string | undefined;
|
||||
description?: string | undefined;
|
||||
"variant-names": {
|
||||
[key: string]: string;
|
||||
};
|
||||
};
|
||||
variants: {
|
||||
[key: string]: Config;
|
||||
};
|
||||
"display-as"?: string | undefined;
|
||||
"unique-by"?: UniqueBy | undefined;
|
||||
};
|
||||
range: string;
|
||||
|
||||
//optionals
|
||||
description?: string;
|
||||
warning?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export const matchObjectValue: Validator<ObjectValue> = shape({
|
||||
object: shape(
|
||||
{
|
||||
name: string,
|
||||
default: matchConfig,
|
||||
values: arrayOf(string),
|
||||
"value-names": dictionary([string, string]),
|
||||
// optionals
|
||||
description: string,
|
||||
warning: string,
|
||||
},
|
||||
["description", "warning"]
|
||||
),
|
||||
});
|
||||
export type ObjectValue = {
|
||||
object: {
|
||||
name: string;
|
||||
default: Config;
|
||||
values: string[] | readonly string[];
|
||||
"value-names": {
|
||||
[key: string]: string;
|
||||
};
|
||||
// optionals
|
||||
description?: string;
|
||||
warning?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export const matchUnionValue: Validator<UnionValue> = shape({
|
||||
union: shape(
|
||||
{
|
||||
name: string,
|
||||
default: string,
|
||||
tag: shape(
|
||||
{
|
||||
id: string,
|
||||
"variant-names": dictionary([string, string]),
|
||||
// optionals
|
||||
name: string,
|
||||
description: string,
|
||||
},
|
||||
["name", "description"]
|
||||
),
|
||||
variants: dictionary([string, matchConfig]),
|
||||
|
||||
//optionals
|
||||
description: string,
|
||||
warning: string,
|
||||
"display-as": string,
|
||||
"unique-by": matchUniqueBy,
|
||||
},
|
||||
["description", "warning", "display-as", "unique-by"]
|
||||
),
|
||||
});
|
||||
export type UnionValue = {
|
||||
union: {
|
||||
name: string;
|
||||
default: string;
|
||||
tag: {
|
||||
id: string;
|
||||
name?: string | undefined;
|
||||
description?: string | undefined;
|
||||
"variant-names": {
|
||||
[key: string]: string;
|
||||
};
|
||||
};
|
||||
variants: {
|
||||
[key: string]: Config;
|
||||
};
|
||||
|
||||
//optionals
|
||||
description?: string;
|
||||
warning?: string;
|
||||
"display-as"?: string | undefined;
|
||||
"unique-by"?: UniqueBy | undefined;
|
||||
};
|
||||
};
|
||||
|
||||
export const matchPointerValue: Validator<PointerValue> = shape({
|
||||
pointer: every(
|
||||
shape(
|
||||
{
|
||||
name: string,
|
||||
|
||||
//optionals
|
||||
description: string,
|
||||
warning: string,
|
||||
},
|
||||
["description", "warning"]
|
||||
),
|
||||
anyOf(
|
||||
shape({
|
||||
package: anyOf(
|
||||
shape({ "tor-key": shape({ "package-id": string, inferface: string }) }),
|
||||
shape({ "tor-address": shape({ "package-id": string, inferface: string }) }),
|
||||
shape({ "lan-address": shape({ "package-id": string, inferface: string }) }),
|
||||
shape({ config: shape({ "package-id": string, selector: string, multi: boolean }, ["multi"]) })
|
||||
),
|
||||
}),
|
||||
shape({
|
||||
system: dictionary([string, unknown]),
|
||||
})
|
||||
)
|
||||
),
|
||||
});
|
||||
export type PointerValue = {
|
||||
pointer: {
|
||||
name: string;
|
||||
|
||||
//optionals
|
||||
description?: string;
|
||||
warning?: string;
|
||||
} & (
|
||||
| {
|
||||
package:
|
||||
| { "tor-key": { "package-id": string; inferface: string } }
|
||||
| { "tor-address": { "package-id": string; inferface: string } }
|
||||
| { "lan-address": { "package-id": string; inferface: string } }
|
||||
| { config: { "package-id": string; selector: string; multi?: boolean } };
|
||||
}
|
||||
| { system: Record<string, unknown> }
|
||||
);
|
||||
};
|
||||
|
||||
export const matchAnyValue = anyOf(
|
||||
matchBoolValue,
|
||||
matchStringValue,
|
||||
matchNumberValue,
|
||||
matchEnumValue,
|
||||
matchListValue,
|
||||
matchObjectValue,
|
||||
matchUnionValue,
|
||||
matchPointerValue
|
||||
);
|
||||
export type AnyValue = typeof matchAnyValue._TYPE;
|
||||
|
||||
export const matchSpec = dictionary([string, matchAnyValue]);
|
||||
export type Spec = typeof matchSpec._TYPE;
|
||||
|
||||
export type V2to1Spec<A extends Spec> = {
|
||||
[key in keyof A]: V2to1AnyValue<A[key]>;
|
||||
};
|
||||
// prettier-ignore
|
||||
// deno-fmt-ignore
|
||||
export type V2to1AnyValue<A extends AnyValue> =
|
||||
A extends BoolValue ? (
|
||||
{
|
||||
tag: "boolean",
|
||||
} & A["boolean"]
|
||||
) :
|
||||
A extends StringValue ? (
|
||||
{
|
||||
tag: "string",
|
||||
} & A["string"]
|
||||
) :
|
||||
A extends NumberValue ? (
|
||||
{
|
||||
tag: "number",
|
||||
} & A["number"]
|
||||
) :
|
||||
A extends EnumValue ? (
|
||||
{
|
||||
tag: "enum",
|
||||
subtype: keyof A["enum"]
|
||||
} & A["enum"][keyof A["enum"]]
|
||||
) :
|
||||
A extends ListValue ? (
|
||||
{
|
||||
tag: "list",
|
||||
} & A["list"]
|
||||
) :
|
||||
A extends ObjectValue ? (
|
||||
{
|
||||
tag: "object",
|
||||
} & A["object"]
|
||||
) :
|
||||
A extends PointerValue ? (
|
||||
{
|
||||
name: string, description?: string, warning?: string,}
|
||||
& (
|
||||
A["pointer"] extends {'package': {'tor-key': infer Target }} ? (
|
||||
{tag: "pointer",
|
||||
subtype: 'package', target: 'tor-key' } & Target
|
||||
) :
|
||||
A["pointer"] extends {'package': {'tor-address': infer Target }} ? (
|
||||
{tag: "pointer",
|
||||
subtype: 'package', target: 'tor-address' } & Target
|
||||
) :
|
||||
A["pointer"] extends {'package': {'lan-address': infer Target }} ? (
|
||||
{tag: "pointer",
|
||||
subtype: 'package', target: 'lan-address' } & Target
|
||||
) :
|
||||
A["pointer"] extends {'package': {'config': infer Target }} ? (
|
||||
{tag: "pointer",
|
||||
subtype: 'package', target: 'config' } & Target
|
||||
) :
|
||||
A["pointer"] extends {'system': infer System } ? (
|
||||
{tag: "pointer",
|
||||
subtype: 'system'} & System
|
||||
) :
|
||||
never
|
||||
)
|
||||
) :
|
||||
never
|
||||
|
||||
setMatchConfig(dictionary([string, matchAnyValue]));
|
||||
135
config/value.ts
Normal file
135
config/value.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import { BuilderExtract, IBuilder } from "./builder.ts";
|
||||
import { Config } from "./config.ts";
|
||||
import { List } from "./list.ts";
|
||||
import { Pointer } from "./pointer.ts";
|
||||
|
||||
export type Description = {
|
||||
name: string;
|
||||
description: string | null;
|
||||
warning: string | null;
|
||||
};
|
||||
export type Default<A> = {
|
||||
default: A;
|
||||
};
|
||||
export type NullableDefault<A> = {
|
||||
default: A | null;
|
||||
};
|
||||
|
||||
export type StringSpec = {
|
||||
copyable: boolean | null;
|
||||
masked: boolean | null;
|
||||
placeholder: string | null;
|
||||
} & (
|
||||
| {
|
||||
pattern: string;
|
||||
"pattern-description": string;
|
||||
}
|
||||
// deno-lint-ignore ban-types
|
||||
| {}
|
||||
);
|
||||
export type NumberSpec = {
|
||||
range: string | null;
|
||||
integral: boolean | null;
|
||||
units: string | null;
|
||||
placeholder: number | null;
|
||||
};
|
||||
export type Nullable = {
|
||||
nullable: boolean;
|
||||
};
|
||||
|
||||
type _UniqueBy =
|
||||
| null
|
||||
| string
|
||||
| {
|
||||
any: string;
|
||||
};
|
||||
|
||||
export class Value<A> extends IBuilder<A> {
|
||||
static boolean<A extends Description & Default<boolean>>(a: A) {
|
||||
return new Value({
|
||||
type: "boolean" as const,
|
||||
...a,
|
||||
});
|
||||
}
|
||||
static string<A extends Description & NullableDefault<string> & Nullable & StringSpec>(a: A) {
|
||||
return new Value({
|
||||
type: "string" as const,
|
||||
...a,
|
||||
});
|
||||
}
|
||||
static number<A extends Description & NullableDefault<number> & Nullable & NumberSpec>(a: A) {
|
||||
return new Value({
|
||||
type: "number" as const,
|
||||
...a,
|
||||
});
|
||||
}
|
||||
static enum<
|
||||
A extends Description &
|
||||
Default<string> & { values: readonly string[] | string[]; "value-names": Record<string, string> }
|
||||
>(a: A) {
|
||||
return new Value({
|
||||
type: "enum" as const,
|
||||
...a,
|
||||
});
|
||||
}
|
||||
static object<
|
||||
A extends Description &
|
||||
NullableDefault<Config<B>> & { values: readonly string[] | string[]; "value-names": Record<string, string> },
|
||||
B
|
||||
>(a: A) {
|
||||
const { default: previousDefault, ...rest } = a;
|
||||
if (previousDefault == null) {
|
||||
return new Value({
|
||||
type: "object" as const,
|
||||
...rest,
|
||||
});
|
||||
}
|
||||
return new Value({
|
||||
type: "object" as const,
|
||||
...rest,
|
||||
default: previousDefault.build(),
|
||||
});
|
||||
}
|
||||
static union<
|
||||
A extends Description &
|
||||
Default<string> & {
|
||||
tag: {
|
||||
id: string;
|
||||
name: string | null;
|
||||
description: string | null;
|
||||
"variant-names": {
|
||||
[key: string]: string;
|
||||
};
|
||||
};
|
||||
variants: Variants;
|
||||
"display-as": string | null;
|
||||
"unique-by": _UniqueBy | null;
|
||||
},
|
||||
Variants extends {
|
||||
[key: string]: Config<unknown>;
|
||||
}
|
||||
>(a: A) {
|
||||
const { variants: previousVariants, ...rest } = a;
|
||||
// deno-lint-ignore no-explicit-any
|
||||
const variants: { [K in keyof Variants]: BuilderExtract<Variants[K]> } = {} as any;
|
||||
for (const key in previousVariants) {
|
||||
// deno-lint-ignore no-explicit-any
|
||||
variants[key] = previousVariants[key].build() as any;
|
||||
}
|
||||
return new Value({
|
||||
type: "union" as const,
|
||||
...rest,
|
||||
variants,
|
||||
});
|
||||
}
|
||||
|
||||
static pointer<A>(a: Pointer<A>) {
|
||||
return new Value(a.build());
|
||||
}
|
||||
static list<A extends List<B>, B>(a: A) {
|
||||
return new Value({
|
||||
type: "list" as const,
|
||||
...a.build(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,2 @@
|
||||
export * as matches from "https://deno.land/x/ts_matches@v5.2.0/mod.ts";
|
||||
export * as matches from "https://deno.land/x/ts_matches@v5.3.0/mod.ts";
|
||||
export * as YAML from "https://deno.land/std@0.140.0/encoding/yaml.ts";
|
||||
|
||||
2
mod.ts
2
mod.ts
@@ -4,4 +4,4 @@ export * as compat from "./compat/mod.ts";
|
||||
export * as migrations from "./migrations.ts";
|
||||
export * as healthUtil from "./healthUtil.ts";
|
||||
export * as util from "./util.ts";
|
||||
export { Backups } from "./backups.ts";
|
||||
export { Backups } from "./backups.ts";
|
||||
|
||||
1
test.ts
1
test.ts
@@ -1,2 +1,3 @@
|
||||
import "./emver-lite/test.ts";
|
||||
import "./utils/propertiesMatcher.test.ts";
|
||||
import "./config/index.test.ts";
|
||||
|
||||
239
types.ts
239
types.ts
@@ -1,10 +1,7 @@
|
||||
// deno-lint-ignore no-namespace
|
||||
export namespace ExpectedExports {
|
||||
/** 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 = (
|
||||
effects: Effects,
|
||||
input: Config,
|
||||
) => Promise<ResultType<SetResult>>;
|
||||
export type setConfig = (effects: Effects, input: Config) => Promise<ResultType<SetResult>>;
|
||||
/** 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 = (effects: Effects) => Promise<ResultType<ConfigRes>>;
|
||||
/** These are how we make sure the our dependency configurations are valid and if not how to fix them. */
|
||||
@@ -12,31 +9,31 @@ export namespace ExpectedExports {
|
||||
/** For backing up service data though the embassyOS UI */
|
||||
export type createBackup = (effects: Effects) => Promise<ResultType<unknown>>;
|
||||
/** 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 = (
|
||||
effects: Effects,
|
||||
) => Promise<ResultType<unknown>>;
|
||||
export type restoreBackup = (effects: Effects) => Promise<ResultType<unknown>>;
|
||||
/** Properties are used to get values from the docker, like a username + password, what ports we are hosting from */
|
||||
export type properties = (
|
||||
effects: Effects,
|
||||
) => Promise<ResultType<Properties>>;
|
||||
export type properties = (effects: Effects) => Promise<ResultType<Properties>>;
|
||||
|
||||
/** 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]: (
|
||||
effects: Effects,
|
||||
dateMs: number,
|
||||
) => Promise<ResultType<unknown>>;
|
||||
[id: string]: (effects: Effects, dateMs: number) => Promise<ResultType<unknown>>;
|
||||
};
|
||||
export type migration = (
|
||||
effects: Effects,
|
||||
version: string,
|
||||
...args: unknown[]
|
||||
) => Promise<ResultType<MigrationRes>>;
|
||||
|
||||
/**
|
||||
* Migrations are used when we are changing versions when updating/ downgrading.
|
||||
* There are times that we need to move files around, and do other operations during a migration.
|
||||
*/
|
||||
export type migration = (effects: Effects, version: string, ...args: unknown[]) => Promise<ResultType<MigrationRes>>;
|
||||
|
||||
/**
|
||||
* Actions are used so we can effect the service, like deleting a directory.
|
||||
* One old use case is to add a action where we add a file, that will then be run during the
|
||||
* service starting, and that file would indicate that it would rescan all the data.
|
||||
*/
|
||||
export type action = {
|
||||
[id: string]: (
|
||||
effects: Effects,
|
||||
config?: Config,
|
||||
) => Promise<ResultType<ActionResult>>;
|
||||
[id: string]: (effects: Effects, config?: Config) => Promise<ResultType<ActionResult>>;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -49,30 +46,24 @@ export namespace ExpectedExports {
|
||||
/** 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<void>;
|
||||
writeFile(input: { path: string; volumeId: string; toWrite: string }): Promise<void>;
|
||||
readFile(input: { volumeId: string; path: string }): Promise<string>;
|
||||
metadata(input: { volumeId: string; path: string }): Promise<Metadata>;
|
||||
/** Create a directory. Usable when not sandboxed */
|
||||
createDir(input: { volumeId: string; path: string }): Promise<string>;
|
||||
/** Create a directory. Usable when not sandboxed */
|
||||
createDir(input: { volumeId: string; path: string }): Promise<string>;
|
||||
/** Remove a directory. Usable when not sandboxed */
|
||||
removeDir(input: { volumeId: string; path: string }): Promise<string>;
|
||||
removeFile(input: { volumeId: string; path: string }): Promise<void>;
|
||||
|
||||
/** Write a json file into an object. Usable when not sandboxed */
|
||||
writeJsonFile(
|
||||
input: { volumeId: string; path: string; toWrite: Record<string, unknown> },
|
||||
): Promise<void>;
|
||||
writeJsonFile(input: { volumeId: string; path: string; toWrite: Record<string, unknown> }): Promise<void>;
|
||||
|
||||
/** Read a json file into an object */
|
||||
readJsonFile(
|
||||
input: { volumeId: string; path: string },
|
||||
): Promise<Record<string, unknown>>;
|
||||
readJsonFile(input: { volumeId: string; path: string }): Promise<Record<string, unknown>>;
|
||||
|
||||
runCommand(
|
||||
input: { command: string; args?: string[]; timeoutMillis?: number },
|
||||
): Promise<ResultType<string>>;
|
||||
runCommand(input: { command: string; args?: string[]; timeoutMillis?: number }): Promise<ResultType<string>>;
|
||||
runDaemon(input: { command: string; args?: string[] }): {
|
||||
wait(): Promise<ResultType<string>>;
|
||||
term(): Promise<void>;
|
||||
@@ -102,7 +93,7 @@ export type Effects = {
|
||||
method?: "GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "PATCH";
|
||||
headers?: Record<string, string>;
|
||||
body?: string;
|
||||
},
|
||||
}
|
||||
): Promise<{
|
||||
method: string;
|
||||
ok: boolean;
|
||||
@@ -209,8 +200,8 @@ export type Target<T extends string, V> = V & {
|
||||
|
||||
export type UniqueBy =
|
||||
| {
|
||||
any: UniqueBy[];
|
||||
}
|
||||
any: UniqueBy[];
|
||||
}
|
||||
| string
|
||||
| null;
|
||||
|
||||
@@ -220,24 +211,24 @@ export type WithNullable<T> = T & {
|
||||
export type DefaultString =
|
||||
| string
|
||||
| {
|
||||
/** The chars available for the randome generation */
|
||||
charset?: string;
|
||||
/** Length that we generate to */
|
||||
len: number;
|
||||
};
|
||||
/** The chars available for the randome generation */
|
||||
charset?: string;
|
||||
/** Length that we generate to */
|
||||
len: number;
|
||||
};
|
||||
|
||||
export type ValueSpecString = // deno-lint-ignore ban-types
|
||||
(
|
||||
| {}
|
||||
| {
|
||||
export type ValueSpecString = (
|
||||
| never // deno-lint-ignore ban-types
|
||||
| {}
|
||||
| {
|
||||
pattern: string;
|
||||
"pattern-description": string;
|
||||
}
|
||||
) & {
|
||||
copyable?: boolean;
|
||||
masked?: boolean;
|
||||
placeholder?: string;
|
||||
};
|
||||
) & {
|
||||
copyable?: boolean;
|
||||
masked?: boolean;
|
||||
placeholder?: string;
|
||||
};
|
||||
export type ValueSpecNumber = {
|
||||
/** Something like [3,6] or [0, *) */
|
||||
range?: string;
|
||||
@@ -249,76 +240,68 @@ export type ValueSpecNumber = {
|
||||
export type ValueSpecBoolean = Record<string, unknown>;
|
||||
export type ValueSpecAny =
|
||||
| Tag<"boolean", WithDescription<WithDefault<ValueSpecBoolean, boolean>>>
|
||||
| Tag<"string", WithDescription<WithNullableDefault<WithNullable<ValueSpecString>, DefaultString>>>
|
||||
| Tag<"number", WithDescription<WithNullableDefault<WithNullable<ValueSpecNumber>, number>>>
|
||||
| Tag<
|
||||
"string",
|
||||
WithDescription<
|
||||
WithNullableDefault<WithNullable<ValueSpecString>, DefaultString>
|
||||
>
|
||||
>
|
||||
| Tag<
|
||||
"number",
|
||||
WithDescription<WithNullableDefault<WithNullable<ValueSpecNumber>, number>>
|
||||
>
|
||||
| Tag<
|
||||
"enum",
|
||||
WithDescription<
|
||||
WithDefault<
|
||||
{
|
||||
values: readonly string[] | string[];
|
||||
"value-names": {
|
||||
[key: string]: string;
|
||||
};
|
||||
},
|
||||
string
|
||||
"enum",
|
||||
WithDescription<
|
||||
WithDefault<
|
||||
{
|
||||
values: readonly string[] | string[];
|
||||
"value-names": {
|
||||
[key: string]: string;
|
||||
};
|
||||
},
|
||||
string
|
||||
>
|
||||
>
|
||||
>
|
||||
>
|
||||
| Tag<"list", ValueSpecList>
|
||||
| Tag<"object", WithDescription<WithNullableDefault<ValueSpecObject, Config>>>
|
||||
| Tag<"union", WithDescription<WithDefault<ValueSpecUnion, string>>>
|
||||
| Tag<
|
||||
"pointer",
|
||||
WithDescription<
|
||||
| Subtype<
|
||||
"package",
|
||||
| Target<
|
||||
"tor-key",
|
||||
{
|
||||
"package-id": string;
|
||||
interface: string;
|
||||
}
|
||||
>
|
||||
| Target<
|
||||
"tor-address",
|
||||
{
|
||||
"package-id": string;
|
||||
interface: string;
|
||||
}
|
||||
>
|
||||
| Target<
|
||||
"lan-address",
|
||||
{
|
||||
"package-id": string;
|
||||
interface: string;
|
||||
}
|
||||
>
|
||||
| Target<
|
||||
"config",
|
||||
{
|
||||
"package-id": string;
|
||||
selector: string;
|
||||
multi: boolean;
|
||||
}
|
||||
>
|
||||
"pointer",
|
||||
WithDescription<
|
||||
| Subtype<
|
||||
"package",
|
||||
| Target<
|
||||
"tor-key",
|
||||
{
|
||||
"package-id": string;
|
||||
interface: string;
|
||||
}
|
||||
>
|
||||
| Target<
|
||||
"tor-address",
|
||||
{
|
||||
"package-id": string;
|
||||
interface: string;
|
||||
}
|
||||
>
|
||||
| Target<
|
||||
"lan-address",
|
||||
{
|
||||
"package-id": string;
|
||||
interface: string;
|
||||
}
|
||||
>
|
||||
| Target<
|
||||
"config",
|
||||
{
|
||||
"package-id": string;
|
||||
selector: string;
|
||||
multi: boolean;
|
||||
}
|
||||
>
|
||||
>
|
||||
| Subtype<"system", Record<string, unknown>>
|
||||
>
|
||||
| Subtype<"system", Record<string, unknown>>
|
||||
>
|
||||
>;
|
||||
>;
|
||||
export type ValueSpecUnion = {
|
||||
/** What tag for the specification, for tag unions */
|
||||
tag: {
|
||||
id: string;
|
||||
name: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
"variant-names": {
|
||||
[key: string]: string;
|
||||
@@ -337,32 +320,12 @@ export type ValueSpecObject = {
|
||||
"unique-by"?: UniqueBy;
|
||||
};
|
||||
export type ValueSpecList =
|
||||
| Subtype<
|
||||
"boolean",
|
||||
WithDescription<WithDefault<ListSpec<ValueSpecBoolean>, boolean[]>>
|
||||
>
|
||||
| Subtype<
|
||||
"string",
|
||||
WithDescription<WithDefault<ListSpec<ValueSpecString>, string[]>>
|
||||
>
|
||||
| Subtype<
|
||||
"number",
|
||||
WithDescription<WithDefault<ListSpec<ValueSpecNumber>, number[]>>
|
||||
>
|
||||
| Subtype<
|
||||
"enum",
|
||||
WithDescription<WithDefault<ListSpec<ValueSpecEnum>, string[]>>
|
||||
>
|
||||
| Subtype<
|
||||
"object",
|
||||
WithDescription<
|
||||
WithNullableDefault<ListSpec<ValueSpecObject>, Record<string, unknown>[]>
|
||||
>
|
||||
>
|
||||
| Subtype<
|
||||
"union",
|
||||
WithDescription<WithDefault<ListSpec<ValueSpecUnion>, string[]>>
|
||||
>;
|
||||
| Subtype<"boolean", WithDescription<WithDefault<ListSpec<ValueSpecBoolean>, boolean[]>>>
|
||||
| Subtype<"string", WithDescription<WithDefault<ListSpec<ValueSpecString>, string[]>>>
|
||||
| Subtype<"number", WithDescription<WithDefault<ListSpec<ValueSpecNumber>, number[]>>>
|
||||
| Subtype<"enum", WithDescription<WithDefault<ListSpec<ValueSpecEnum>, string[]>>>
|
||||
| Subtype<"object", WithDescription<WithNullableDefault<ListSpec<ValueSpecObject>, Record<string, unknown>[]>>>
|
||||
| Subtype<"union", WithDescription<WithDefault<ListSpec<ValueSpecUnion>, string[]>>>;
|
||||
export type ValueSpecEnum = {
|
||||
values: string[];
|
||||
"value-names": { [key: string]: string };
|
||||
@@ -414,8 +377,8 @@ export type DependsOn = {
|
||||
export type KnownError =
|
||||
| { error: string }
|
||||
| {
|
||||
"error-code": [number, string] | readonly [number, string];
|
||||
};
|
||||
"error-code": [number, string] | readonly [number, string];
|
||||
};
|
||||
export type ResultType<T> = KnownError | { result: T };
|
||||
|
||||
export type PackagePropertiesV2 = {
|
||||
|
||||
Reference in New Issue
Block a user