diff --git a/config/builder.ts b/config/builder.ts
new file mode 100644
index 0000000..dbc844c
--- /dev/null
+++ b/config/builder.ts
@@ -0,0 +1,9 @@
+export class IBuilder {
+ protected constructor(readonly a: A) {}
+
+ public build(): A {
+ return this.a;
+ }
+}
+
+export type BuilderExtract = A extends IBuilder ? B : never;
diff --git a/config/config.ts b/config/config.ts
new file mode 100644
index 0000000..ae89c38
--- /dev/null
+++ b/config/config.ts
@@ -0,0 +1,24 @@
+import { BuilderExtract, IBuilder } from "./builder.ts";
+import { Value } from "./value.ts";
+
+export class Config extends IBuilder {
+ static empty() {
+ return new Config({});
+ }
+
+ static of }>(spec: B) {
+ // deno-lint-ignore no-explicit-any
+ const answer: { [K in keyof B]: BuilderExtract } = {} 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(key: K, value: Value) {
+ return new Config({
+ ...this.a,
+ [key]: value.build(),
+ } as A & { [key in K]: B });
+ }
+}
diff --git a/config/index.test.ts b/config/index.test.ts
new file mode 100644
index 0000000..b9a9a02
--- /dev/null
+++ b/config/index.test.ts
@@ -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(": ", ":")
+ );
+});
diff --git a/config/index.ts b/config/index.ts
new file mode 100644
index 0000000..1e9cba0
--- /dev/null
+++ b/config/index.ts
@@ -0,0 +1,4 @@
+export { Config } from "./config.ts";
+export { List } from "./list.ts";
+export { Pointer } from "./pointer.ts";
+export { Value } from "./value.ts";
diff --git a/config/list.ts b/config/list.ts
new file mode 100644
index 0000000..4971222
--- /dev/null
+++ b/config/list.ts
@@ -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 extends IBuilder {
+ // deno-lint-ignore ban-types
+ static boolean & { range: string; spec: {} }>(a: A) {
+ return new List({
+ subtype: "boolean" as const,
+ ...a,
+ });
+ }
+
+ static string<
+ A extends Description & Default
+ >(a: A) {
+ return new List({
+ subtype: "string" as const,
+ ...a,
+ });
+ }
+ static number & { range: string; spec: NumberSpec }>(a: A) {
+ return new List({
+ subtype: "number" as const,
+ ...a,
+ });
+ }
+ static enum<
+ A extends Description &
+ Default & {
+ 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[]> & {
+ range: string;
+ spec: {
+ spec: Config;
+ "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 & {
+ 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 }
+ >(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 } = {} 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,
+ });
+ }
+}
diff --git a/config/pointer.ts b/config/pointer.ts
new file mode 100644
index 0000000..26ae61e
--- /dev/null
+++ b/config/pointer.ts
@@ -0,0 +1,44 @@
+import { IBuilder } from "./builder.ts";
+import { Description } from "./value.ts";
+
+export class Pointer extends IBuilder {
+ static packageTorKey(a: A) {
+ return new Pointer({
+ type: "pointer" as const,
+ subtype: "package" as const,
+ target: "tor-key" as const,
+ ...a,
+ });
+ }
+ static packageTorAddress(a: A) {
+ return new Pointer({
+ type: "pointer" as const,
+ subtype: "package" as const,
+ target: "tor-address" as const,
+ ...a,
+ });
+ }
+ static packageLanAddress(a: A) {
+ return new Pointer({
+ type: "pointer" as const,
+ subtype: "package" as const,
+ target: "lan-address" as const,
+ ...a,
+ });
+ }
+ static packageConfig(a: A) {
+ return new Pointer({
+ type: "pointer" as const,
+ subtype: "package" as const,
+ target: "config" as const,
+ ...a,
+ });
+ }
+ static system>(a: A) {
+ return new Pointer({
+ type: "pointer" as const,
+ subtype: "system" as const,
+ ...a,
+ });
+ }
+}
diff --git a/config/v2_spec.ts b/config/v2_spec.ts
new file mode 100644
index 0000000..1047832
--- /dev/null
+++ b/config/v2_spec.ts
@@ -0,0 +1,616 @@
+// deno-lint-ignore-file ban-types
+import { DefaultString } from "../types.ts";
+import { matches } from "../dependencies.ts";
+
+type Validator = matches.Validator;
+const { shape, boolean, string, dictionary, anyOf, arrayOf, every, deferred, number, recursive, unknown } = matches;
+
+export const [matchConfig, setMatchConfig] = deferred();
+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 = 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 = 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 = shape({
+ number: every(
+ matchDescription,
+ shape(
+ {
+ default: number,
+ },
+ ["default"]
+ )
+ ),
+});
+export type NumberValue = {
+ number: {
+ name: string;
+ default?: number;
+ description?: string;
+ warning?: string;
+ };
+};
+
+export const matchEnumValue: Validator = 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 = shape({
+ boolean: shape(
+ {
+ name: string,
+ default: arrayOf(boolean),
+ spec: shape({}),
+ range: string,
+
+ //optionals
+ description: string,
+ warning: string,
+ },
+ ["warning", "description"]
+ ),
+});
+const matchListStringValue: Validator = 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 = 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 = 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 = 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 = 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 = 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[] | readonly Record[];
+ 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 = 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 = 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 = 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 }
+ );
+};
+
+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 = {
+ [key in keyof A]: V2to1AnyValue;
+};
+// prettier-ignore
+// deno-fmt-ignore
+export type V2to1AnyValue =
+ 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]));
diff --git a/config/value.ts b/config/value.ts
new file mode 100644
index 0000000..c656a8d
--- /dev/null
+++ b/config/value.ts
@@ -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 = {
+ default: A;
+};
+export type NullableDefault = {
+ 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 extends IBuilder {
+ static boolean>(a: A) {
+ return new Value({
+ type: "boolean" as const,
+ ...a,
+ });
+ }
+ static string & Nullable & StringSpec>(a: A) {
+ return new Value({
+ type: "string" as const,
+ ...a,
+ });
+ }
+ static number & Nullable & NumberSpec>(a: A) {
+ return new Value({
+ type: "number" as const,
+ ...a,
+ });
+ }
+ static enum<
+ A extends Description &
+ Default & { values: readonly string[] | string[]; "value-names": Record }
+ >(a: A) {
+ return new Value({
+ type: "enum" as const,
+ ...a,
+ });
+ }
+ static object<
+ A extends Description &
+ NullableDefault> & { values: readonly string[] | string[]; "value-names": Record },
+ 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 & {
+ 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;
+ }
+ >(a: A) {
+ const { variants: previousVariants, ...rest } = a;
+ // deno-lint-ignore no-explicit-any
+ const variants: { [K in keyof Variants]: BuilderExtract } = {} 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: Pointer) {
+ return new Value(a.build());
+ }
+ static list, B>(a: A) {
+ return new Value({
+ type: "list" as const,
+ ...a.build(),
+ });
+ }
+}
diff --git a/dependencies.ts b/dependencies.ts
index 8007570..1eaec6f 100644
--- a/dependencies.ts
+++ b/dependencies.ts
@@ -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";
diff --git a/mod.ts b/mod.ts
index 23cd38c..c146c6f 100644
--- a/mod.ts
+++ b/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";
\ No newline at end of file
+export { Backups } from "./backups.ts";
diff --git a/test.ts b/test.ts
index daadb3a..afe6e4a 100644
--- a/test.ts
+++ b/test.ts
@@ -1,2 +1,3 @@
import "./emver-lite/test.ts";
import "./utils/propertiesMatcher.test.ts";
+import "./config/index.test.ts";
diff --git a/types.ts b/types.ts
index d3702a5..d24698f 100644
--- a/types.ts
+++ b/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>;
+ export type setConfig = (effects: Effects, input: Config) => Promise>;
/** Get configuration returns a shape that describes the format that the embassy ui will generate, and later send to the set config */
export type getConfig = (effects: Effects) => Promise>;
/** 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>;
/** 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>;
+ export type restoreBackup = (effects: Effects) => Promise>;
/** Properties are used to get values from the docker, like a username + password, what ports we are hosting from */
- export type properties = (
- effects: Effects,
- ) => Promise>;
+ export type properties = (effects: Effects) => Promise>;
+ /** Health checks are used to determine if the service is working properly after starting
+ * A good use case is if we are using a web server, seeing if we can get to the web server.
+ */
export type health = {
/** Should be the health check id */
- [id: string]: (
- effects: Effects,
- dateMs: number,
- ) => Promise>;
+ [id: string]: (effects: Effects, dateMs: number) => Promise>;
};
- export type migration = (
- effects: Effects,
- version: string,
- ...args: unknown[]
- ) => Promise>;
+
+ /**
+ * 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>;
+
+ /**
+ * 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>;
+ [id: string]: (effects: Effects, config?: Config) => Promise>;
};
/**
@@ -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;
+ writeFile(input: { path: string; volumeId: string; toWrite: string }): Promise;
readFile(input: { volumeId: string; path: string }): Promise;
metadata(input: { volumeId: string; path: string }): Promise;
/** Create a directory. Usable when not sandboxed */
createDir(input: { volumeId: string; path: string }): Promise;
+ /** Create a directory. Usable when not sandboxed */
+ createDir(input: { volumeId: string; path: string }): Promise;
/** Remove a directory. Usable when not sandboxed */
removeDir(input: { volumeId: string; path: string }): Promise;
removeFile(input: { volumeId: string; path: string }): Promise;
/** Write a json file into an object. Usable when not sandboxed */
- writeJsonFile(
- input: { volumeId: string; path: string; toWrite: Record },
- ): Promise;
+ writeJsonFile(input: { volumeId: string; path: string; toWrite: Record }): Promise;
/** Read a json file into an object */
- readJsonFile(
- input: { volumeId: string; path: string },
- ): Promise>;
+ readJsonFile(input: { volumeId: string; path: string }): Promise>;
- runCommand(
- input: { command: string; args?: string[]; timeoutMillis?: number },
- ): Promise>;
+ runCommand(input: { command: string; args?: string[]; timeoutMillis?: number }): Promise>;
runDaemon(input: { command: string; args?: string[] }): {
wait(): Promise>;
term(): Promise;
@@ -102,7 +93,7 @@ export type Effects = {
method?: "GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "PATCH";
headers?: Record;
body?: string;
- },
+ }
): Promise<{
method: string;
ok: boolean;
@@ -209,8 +200,8 @@ export type Target = V & {
export type UniqueBy =
| {
- any: UniqueBy[];
- }
+ any: UniqueBy[];
+ }
| string
| null;
@@ -220,24 +211,24 @@ export type WithNullable = 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;
export type ValueSpecAny =
| Tag<"boolean", WithDescription>>
+ | Tag<"string", WithDescription, DefaultString>>>
+ | Tag<"number", WithDescription, number>>>
| Tag<
- "string",
- WithDescription<
- WithNullableDefault, DefaultString>
- >
- >
- | Tag<
- "number",
- WithDescription, 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>>
| Tag<"union", WithDescription>>
| 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>
>
- | Subtype<"system", Record>
- >
- >;
+ >;
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, boolean[]>>
- >
- | Subtype<
- "string",
- WithDescription, string[]>>
- >
- | Subtype<
- "number",
- WithDescription, number[]>>
- >
- | Subtype<
- "enum",
- WithDescription, string[]>>
- >
- | Subtype<
- "object",
- WithDescription<
- WithNullableDefault, Record[]>
- >
- >
- | Subtype<
- "union",
- WithDescription, string[]>>
- >;
+ | Subtype<"boolean", WithDescription, boolean[]>>>
+ | Subtype<"string", WithDescription, string[]>>>
+ | Subtype<"number", WithDescription, number[]>>>
+ | Subtype<"enum", WithDescription, string[]>>>
+ | Subtype<"object", WithDescription, Record[]>>>
+ | Subtype<"union", WithDescription, 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 = KnownError | { result: T };
export type PackagePropertiesV2 = {