mirror of
https://github.com/Start9Labs/start-sdk.git
synced 2026-03-26 02:11:56 +00:00
feat: add autoConfig/ better types for wrapperData
This commit is contained in:
@@ -1,70 +1,42 @@
|
|||||||
import deepmerge from "deepmerge";
|
import { AutoConfigure, Effects, ExpectedExports } from "../types";
|
||||||
import { AutoConfigure, Effects } from "../types";
|
import { deepEqual, deepMerge } from "../util";
|
||||||
import { Message, MaybePromise, ReadonlyDeep } from ".";
|
|
||||||
|
|
||||||
class AutoConfigBuilt<Config> implements AutoConfigure<Config> {
|
export type AutoConfigFrom = {
|
||||||
constructor(private autoConfig: AutoConfig<Config>) {}
|
[key: string]: (options: {
|
||||||
|
effects: Effects;
|
||||||
async check(effects: Effects, config: Config): Promise<void> {
|
localConfig: unknown;
|
||||||
for (const [message, configure] of this.autoConfig.getConfigures()) {
|
remoteConfig: unknown;
|
||||||
const value = await configure({ effects, config });
|
}) => Promise<void | Record<string, unknown>>;
|
||||||
if (value !== null) {
|
};
|
||||||
throw new Error(message);
|
export class AutoConfig {
|
||||||
}
|
constructor(
|
||||||
}
|
readonly configs: AutoConfigFrom,
|
||||||
}
|
readonly path: keyof AutoConfigFrom,
|
||||||
/** This is called after we know that the dependency package needs a new configuration, this would be a transform for defaults */
|
|
||||||
async autoConfigure(effects: Effects, config: Config): Promise<Config> {
|
|
||||||
const input = { effects, config };
|
|
||||||
const newOverwrites = (
|
|
||||||
await Promise.all(this.autoConfig.getConfigures().map((x) => x[1](input)))
|
|
||||||
).filter((x): x is NonNullable<typeof x> => x !== null);
|
|
||||||
return deepmerge.all([config, ...newOverwrites]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export class AutoConfig<Config> {
|
|
||||||
private constructor(
|
|
||||||
private configures: Array<
|
|
||||||
[
|
|
||||||
Message,
|
|
||||||
(
|
|
||||||
options: Readonly<{ config: Config; effects: Effects }>
|
|
||||||
) => MaybePromise<null | Partial<Config>>
|
|
||||||
]
|
|
||||||
>
|
|
||||||
) {}
|
) {}
|
||||||
getConfigures(): ReadonlyDeep<
|
|
||||||
Array<
|
|
||||||
[
|
|
||||||
Message,
|
|
||||||
(
|
|
||||||
options: Readonly<{ config: Config; effects: Effects }>
|
|
||||||
) => MaybePromise<null | Partial<Config>>
|
|
||||||
]
|
|
||||||
>
|
|
||||||
> {
|
|
||||||
return this.configures;
|
|
||||||
}
|
|
||||||
|
|
||||||
static autoConfig<Config>(
|
async check(
|
||||||
message: Message,
|
options: Parameters<AutoConfigure["check"]>[0],
|
||||||
configure: (
|
): ReturnType<AutoConfigure["check"]> {
|
||||||
options: Readonly<{ config: Config; effects: Effects }>
|
const origConfig = JSON.parse(JSON.stringify(options.localConfig));
|
||||||
) => MaybePromise<null | Partial<Config>>
|
if (
|
||||||
): AutoConfig<Config> {
|
!deepEqual(
|
||||||
return new AutoConfig([[message, configure]]);
|
origConfig,
|
||||||
|
deepMerge(
|
||||||
|
{},
|
||||||
|
options.localConfig,
|
||||||
|
await this.configs[this.path](options),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
throw new Error(`Check failed for ${this.path}`);
|
||||||
}
|
}
|
||||||
autoConfig(
|
async autoConfigure(
|
||||||
message: Message,
|
options: Parameters<AutoConfigure["autoConfigure"]>[0],
|
||||||
configure: (
|
): ReturnType<AutoConfigure["autoConfigure"]> {
|
||||||
options: Readonly<{ config: Config; effects: Effects }>
|
return deepMerge(
|
||||||
) => MaybePromise<null | Partial<Config>>
|
{},
|
||||||
): AutoConfig<Config> {
|
options.localConfig,
|
||||||
this.configures.push([message, configure]);
|
await this.configs[this.path](options),
|
||||||
return this;
|
);
|
||||||
}
|
|
||||||
|
|
||||||
build() {
|
|
||||||
return new AutoConfigBuilt(this);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
import { ExpectedExports, PackageId } from "../types";
|
|
||||||
import { AutoConfig } from "./AutoConfig";
|
|
||||||
|
|
||||||
export function autoconfigSetup<Config>(
|
|
||||||
autoconfigs: Record<PackageId, AutoConfig<Config>>
|
|
||||||
) {
|
|
||||||
const autoconfig: ExpectedExports.autoConfig<Config> = {};
|
|
||||||
|
|
||||||
for (const [id, autoconfigValue] of Object.entries(autoconfigs)) {
|
|
||||||
autoconfig[id] = autoconfigValue.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
return autoconfig;
|
|
||||||
}
|
|
||||||
@@ -6,4 +6,4 @@ export type MaybePromise<A> = Promise<A> | A;
|
|||||||
export type Message = string;
|
export type Message = string;
|
||||||
|
|
||||||
export { AutoConfig } from "./AutoConfig";
|
export { AutoConfig } from "./AutoConfig";
|
||||||
export { autoconfigSetup } from "./autoconfigSetup";
|
export { setupAutoConfig } from "./setupAutoConfig";
|
||||||
|
|||||||
9
lib/autoconfig/setupAutoConfig.ts
Normal file
9
lib/autoconfig/setupAutoConfig.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { AutoConfig, AutoConfigFrom } from "./AutoConfig";
|
||||||
|
|
||||||
|
export function setupAutoConfig<C extends AutoConfigFrom>(configs: C) {
|
||||||
|
const answer = { ...configs } as unknown as { [k in keyof C]: AutoConfig };
|
||||||
|
for (const key in configs) {
|
||||||
|
answer[key] = new AutoConfig(configs, key);
|
||||||
|
}
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
@@ -40,7 +40,7 @@ export class Backups {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private options = DEFAULT_OPTIONS,
|
private options = DEFAULT_OPTIONS,
|
||||||
private backupSet = [] as BackupSet[]
|
private backupSet = [] as BackupSet[],
|
||||||
) {}
|
) {}
|
||||||
static volumes(...volumeNames: string[]) {
|
static volumes(...volumeNames: string[]) {
|
||||||
return new Backups().addSets(
|
return new Backups().addSets(
|
||||||
@@ -49,7 +49,7 @@ export class Backups {
|
|||||||
srcPath: "./",
|
srcPath: "./",
|
||||||
dstPath: `./${srcVolume}/`,
|
dstPath: `./${srcVolume}/`,
|
||||||
dstVolume: Backups.BACKUP,
|
dstVolume: Backups.BACKUP,
|
||||||
}))
|
})),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
static addSets(...options: BackupSet[]) {
|
static addSets(...options: BackupSet[]) {
|
||||||
@@ -72,12 +72,12 @@ export class Backups {
|
|||||||
srcPath: "./",
|
srcPath: "./",
|
||||||
dstPath: `./${srcVolume}/`,
|
dstPath: `./${srcVolume}/`,
|
||||||
dstVolume: Backups.BACKUP,
|
dstVolume: Backups.BACKUP,
|
||||||
}))
|
})),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
addSets(...options: BackupSet[]) {
|
addSets(...options: BackupSet[]) {
|
||||||
options.forEach((x) =>
|
options.forEach((x) =>
|
||||||
this.backupSet.push({ ...x, options: { ...this.options, ...x.options } })
|
this.backupSet.push({ ...x, options: { ...this.options, ...x.options } }),
|
||||||
);
|
);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -98,7 +98,7 @@ export class Backups {
|
|||||||
.map((x) => x.dstPath)
|
.map((x) => x.dstPath)
|
||||||
.map((x) => x.replace(/\.\/([^]*)\//, "$1"));
|
.map((x) => x.replace(/\.\/([^]*)\//, "$1"));
|
||||||
const filteredItems = previousItems.filter(
|
const filteredItems = previousItems.filter(
|
||||||
(x) => backupPaths.indexOf(x) === -1
|
(x) => backupPaths.indexOf(x) === -1,
|
||||||
);
|
);
|
||||||
for (const itemToRemove of filteredItems) {
|
for (const itemToRemove of filteredItems) {
|
||||||
effects.error(`Trying to remove ${itemToRemove}`);
|
effects.error(`Trying to remove ${itemToRemove}`);
|
||||||
@@ -111,7 +111,7 @@ export class Backups {
|
|||||||
effects.removeFile({
|
effects.removeFile({
|
||||||
volumeId: Backups.BACKUP,
|
volumeId: Backups.BACKUP,
|
||||||
path: itemToRemove,
|
path: itemToRemove,
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
effects.warn(`Failed to remove ${itemToRemove} from backup volume`);
|
effects.warn(`Failed to remove ${itemToRemove} from backup volume`);
|
||||||
|
|||||||
@@ -65,13 +65,13 @@ export class Config<A extends InputSpec> extends IBuilder<A> {
|
|||||||
}
|
}
|
||||||
static withValue<K extends string, B extends ValueSpec>(
|
static withValue<K extends string, B extends ValueSpec>(
|
||||||
key: K,
|
key: K,
|
||||||
value: Value<B>
|
value: Value<B>,
|
||||||
) {
|
) {
|
||||||
return Config.empty().withValue(key, value);
|
return Config.empty().withValue(key, value);
|
||||||
}
|
}
|
||||||
static addValue<K extends string, B extends ValueSpec>(
|
static addValue<K extends string, B extends ValueSpec>(
|
||||||
key: K,
|
key: K,
|
||||||
value: Value<B>
|
value: Value<B>,
|
||||||
) {
|
) {
|
||||||
return Config.empty().withValue(key, value);
|
return Config.empty().withValue(key, value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export class List<A extends ValueSpecList> extends IBuilder<A> {
|
|||||||
patternDescription?: string | null;
|
patternDescription?: string | null;
|
||||||
/** Default = "text" */
|
/** Default = "text" */
|
||||||
inputmode?: ListValueSpecString["inputmode"];
|
inputmode?: ListValueSpecString["inputmode"];
|
||||||
}
|
},
|
||||||
) {
|
) {
|
||||||
const spec = {
|
const spec = {
|
||||||
type: "string" as const,
|
type: "string" as const,
|
||||||
@@ -77,7 +77,7 @@ export class List<A extends ValueSpecList> extends IBuilder<A> {
|
|||||||
range?: string;
|
range?: string;
|
||||||
units?: string | null;
|
units?: string | null;
|
||||||
placeholder?: string | null;
|
placeholder?: string | null;
|
||||||
}
|
},
|
||||||
) {
|
) {
|
||||||
const spec = {
|
const spec = {
|
||||||
type: "number" as const,
|
type: "number" as const,
|
||||||
@@ -111,7 +111,7 @@ export class List<A extends ValueSpecList> extends IBuilder<A> {
|
|||||||
spec: Spec;
|
spec: Spec;
|
||||||
displayAs?: null | string;
|
displayAs?: null | string;
|
||||||
uniqueBy?: null | UniqueBy;
|
uniqueBy?: null | UniqueBy;
|
||||||
}
|
},
|
||||||
) {
|
) {
|
||||||
const { spec: previousSpecSpec, ...restSpec } = aSpec;
|
const { spec: previousSpecSpec, ...restSpec } = aSpec;
|
||||||
const specSpec = previousSpecSpec.build() as BuilderExtract<Spec>;
|
const specSpec = previousSpecSpec.build() as BuilderExtract<Spec>;
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
|
|||||||
description?: string | null;
|
description?: string | null;
|
||||||
warning?: string | null;
|
warning?: string | null;
|
||||||
},
|
},
|
||||||
previousSpec: Spec
|
previousSpec: Spec,
|
||||||
) {
|
) {
|
||||||
const spec = previousSpec.build() as BuilderExtract<Spec>;
|
const spec = previousSpec.build() as BuilderExtract<Spec>;
|
||||||
return new Value({
|
return new Value({
|
||||||
@@ -166,7 +166,7 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
static union<
|
static union<
|
||||||
V extends Variants<{ [key: string]: { name: string; spec: InputSpec } }>
|
V extends Variants<{ [key: string]: { name: string; spec: InputSpec } }>,
|
||||||
>(
|
>(
|
||||||
a: {
|
a: {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -175,7 +175,7 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
|
|||||||
required: boolean;
|
required: boolean;
|
||||||
default?: string | null;
|
default?: string | null;
|
||||||
},
|
},
|
||||||
aVariants: V
|
aVariants: V,
|
||||||
) {
|
) {
|
||||||
const variants = aVariants.build() as BuilderExtract<V>;
|
const variants = aVariants.build() as BuilderExtract<V>;
|
||||||
return new Value({
|
return new Value({
|
||||||
|
|||||||
@@ -57,12 +57,12 @@ export class Variants<
|
|||||||
name: string;
|
name: string;
|
||||||
spec: InputSpec;
|
spec: InputSpec;
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
> extends IBuilder<A> {
|
> extends IBuilder<A> {
|
||||||
static of<
|
static of<
|
||||||
A extends {
|
A extends {
|
||||||
[key: string]: { name: string; spec: Config<InputSpec> };
|
[key: string]: { name: string; spec: Config<InputSpec> };
|
||||||
}
|
},
|
||||||
>(a: A) {
|
>(a: A) {
|
||||||
const variants: {
|
const variants: {
|
||||||
[K in keyof A]: { name: string; spec: BuilderExtract<A[K]["spec"]> };
|
[K in keyof A]: { name: string; spec: BuilderExtract<A[K]["spec"]> };
|
||||||
@@ -82,7 +82,7 @@ export class Variants<
|
|||||||
}
|
}
|
||||||
static withVariant<K extends string, B extends InputSpec>(
|
static withVariant<K extends string, B extends InputSpec>(
|
||||||
key: K,
|
key: K,
|
||||||
value: Config<B>
|
value: Config<B>,
|
||||||
) {
|
) {
|
||||||
return Variants.empty().withVariant(key, value);
|
return Variants.empty().withVariant(key, value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ export type DefaultString =
|
|||||||
// sometimes the type checker needs just a little bit of help
|
// sometimes the type checker needs just a little bit of help
|
||||||
export function isValueSpecListOf<S extends ListValueSpecType>(
|
export function isValueSpecListOf<S extends ListValueSpecType>(
|
||||||
t: ValueSpecListOf<ListValueSpecType>,
|
t: ValueSpecListOf<ListValueSpecType>,
|
||||||
s: S
|
s: S,
|
||||||
): t is ValueSpecListOf<S> & { spec: ListValueSpecOf<S> } {
|
): t is ValueSpecListOf<S> & { spec: ListValueSpecOf<S> } {
|
||||||
return t.spec.type === s;
|
return t.spec.type === s;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,25 @@
|
|||||||
import { Config } from "./builder";
|
import { Config } from "./builder";
|
||||||
import { DeepPartial, Dependencies, DependsOn, Effects, ExpectedExports } from "../types";
|
import {
|
||||||
|
DeepPartial,
|
||||||
|
Dependencies,
|
||||||
|
DependsOn,
|
||||||
|
Effects,
|
||||||
|
ExpectedExports,
|
||||||
|
} from "../types";
|
||||||
import { InputSpec } from "./configTypes";
|
import { InputSpec } from "./configTypes";
|
||||||
import { nullIfEmpty } from "../util";
|
import { nullIfEmpty } from "../util";
|
||||||
import { TypeFromProps } from "../util/propertiesMatcher";
|
import { TypeFromProps } from "../util/propertiesMatcher";
|
||||||
|
|
||||||
export type Write<A, ConfigType> = (options: { effects: Effects; input: TypeFromProps<A> }) => Promise<ConfigType>;
|
export type Write<A> = (options: {
|
||||||
export type Read<A, ConfigType> = (options: {
|
effects: Effects;
|
||||||
effects: Effects;
|
input: TypeFromProps<A>;
|
||||||
config: ConfigType;
|
}) => Promise<void>;
|
||||||
}) => Promise<null | DeepPartial<TypeFromProps<A>>>;
|
export type Read<A> = (options: {
|
||||||
export type DependenciesFn<A, ConfigType> = (options: {
|
effects: Effects;
|
||||||
|
}) => Promise<null | DeepPartial<TypeFromProps<A>>>;
|
||||||
|
export type DependenciesFn<A> = (options: {
|
||||||
effects: Effects;
|
effects: Effects;
|
||||||
input: TypeFromProps<A>;
|
input: TypeFromProps<A>;
|
||||||
config: ConfigType;
|
|
||||||
}) => Promise<Dependencies | void>;
|
}) => Promise<Dependencies | void>;
|
||||||
/**
|
/**
|
||||||
* We want to setup a config export with a get and set, this
|
* We want to setup a config export with a get and set, this
|
||||||
@@ -21,31 +28,30 @@ export type DependenciesFn<A, ConfigType> = (options: {
|
|||||||
* @param options
|
* @param options
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function setupConfigExports<A extends InputSpec, ConfigType>(options: {
|
export function setupConfigExports<A extends InputSpec>(
|
||||||
spec: Config<A>;
|
spec: Config<A>,
|
||||||
write: Write<A, ConfigType>;
|
write: Write<A>,
|
||||||
read: Read<A, ConfigType>;
|
read: Read<A>,
|
||||||
dependencies: DependenciesFn<A, ConfigType>;
|
dependencies: DependenciesFn<A>,
|
||||||
}) {
|
) {
|
||||||
const validator = options.spec.validator();
|
const validator = spec.validator();
|
||||||
return {
|
return {
|
||||||
setConfig: (async ({ effects, input }) => {
|
setConfig: (async ({ effects, input }) => {
|
||||||
if (!validator.test(input)) {
|
if (!validator.test(input)) {
|
||||||
await effects.error(String(validator.errorMessage(input)));
|
await effects.error(String(validator.errorMessage(input)));
|
||||||
return { error: "Set config type error for config" };
|
return { error: "Set config type error for config" };
|
||||||
}
|
}
|
||||||
const config = await options.write({
|
await write({
|
||||||
input: JSON.parse(JSON.stringify(input)),
|
input: JSON.parse(JSON.stringify(input)),
|
||||||
effects,
|
effects,
|
||||||
});
|
});
|
||||||
const dependencies = (await options.dependencies({ effects, input, config })) || [];
|
const dependenciesToSet = (await dependencies({ effects, input })) || [];
|
||||||
await effects.setDependencies(dependencies);
|
await effects.setDependencies(dependenciesToSet);
|
||||||
await effects.setWrapperData({ path: "config", value: config || null });
|
|
||||||
}) as ExpectedExports.setConfig,
|
}) as ExpectedExports.setConfig,
|
||||||
getConfig: (async ({ effects, config }) => {
|
getConfig: (async ({ effects, config }) => {
|
||||||
return {
|
return {
|
||||||
spec: options.spec.build(),
|
spec: spec.build(),
|
||||||
config: nullIfEmpty(await options.read({ effects, config: config as ConfigType })),
|
config: nullIfEmpty(await read({ effects })),
|
||||||
};
|
};
|
||||||
}) as ExpectedExports.getConfig,
|
}) as ExpectedExports.getConfig,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,15 +6,15 @@ import * as C from "./configTypes";
|
|||||||
export async function specToBuilderFile(
|
export async function specToBuilderFile(
|
||||||
file: string,
|
file: string,
|
||||||
inputData: Promise<InputSpec> | InputSpec,
|
inputData: Promise<InputSpec> | InputSpec,
|
||||||
options: Parameters<typeof specToBuilder>[1]
|
options: Parameters<typeof specToBuilder>[1],
|
||||||
) {
|
) {
|
||||||
await fs.writeFile(file, await specToBuilder(inputData, options), (err) =>
|
await fs.writeFile(file, await specToBuilder(inputData, options), (err) =>
|
||||||
console.error(err)
|
console.error(err),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export async function specToBuilder(
|
export async function specToBuilder(
|
||||||
inputData: Promise<InputSpec> | InputSpec,
|
inputData: Promise<InputSpec> | InputSpec,
|
||||||
{ startSdk = "start-sdk" } = {}
|
{ startSdk = "start-sdk" } = {},
|
||||||
) {
|
) {
|
||||||
const outputLines: string[] = [];
|
const outputLines: string[] = [];
|
||||||
outputLines.push(`
|
outputLines.push(`
|
||||||
@@ -26,10 +26,10 @@ export async function specToBuilder(
|
|||||||
const configName = newConst("InputSpec", convertInputSpec(data));
|
const configName = newConst("InputSpec", convertInputSpec(data));
|
||||||
const configMatcherName = newConst(
|
const configMatcherName = newConst(
|
||||||
"matchInputSpec",
|
"matchInputSpec",
|
||||||
`${configName}.validator()`
|
`${configName}.validator()`,
|
||||||
);
|
);
|
||||||
outputLines.push(
|
outputLines.push(
|
||||||
`export type InputSpec = typeof ${configMatcherName}._TYPE;`
|
`export type InputSpec = typeof ${configMatcherName}._TYPE;`,
|
||||||
);
|
);
|
||||||
|
|
||||||
return outputLines.join("\n");
|
return outputLines.join("\n");
|
||||||
@@ -73,7 +73,7 @@ export async function specToBuilder(
|
|||||||
const { variants, type, ...rest } = value;
|
const { variants, type, ...rest } = value;
|
||||||
const variantVariable = newConst(
|
const variantVariable = newConst(
|
||||||
value.name + "_variants",
|
value.name + "_variants",
|
||||||
convertVariants(variants)
|
convertVariants(variants),
|
||||||
);
|
);
|
||||||
|
|
||||||
return `Value.union(${JSON.stringify(rest)}, ${variantVariable})`;
|
return `Value.union(${JSON.stringify(rest)}, ${variantVariable})`;
|
||||||
@@ -101,7 +101,7 @@ export async function specToBuilder(
|
|||||||
warning: value.warning || null,
|
warning: value.warning || null,
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
2
|
2,
|
||||||
)}, ${JSON.stringify({
|
)}, ${JSON.stringify({
|
||||||
masked: spec?.masked || false,
|
masked: spec?.masked || false,
|
||||||
placeholder: spec?.placeholder || null,
|
placeholder: spec?.placeholder || null,
|
||||||
@@ -120,7 +120,7 @@ export async function specToBuilder(
|
|||||||
warning: value.warning || null,
|
warning: value.warning || null,
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
2
|
2,
|
||||||
)}, ${JSON.stringify({
|
)}, ${JSON.stringify({
|
||||||
range: spec?.range || null,
|
range: spec?.range || null,
|
||||||
integral: spec?.integral || false,
|
integral: spec?.integral || false,
|
||||||
@@ -131,7 +131,7 @@ export async function specToBuilder(
|
|||||||
case "object": {
|
case "object": {
|
||||||
const specName = newConst(
|
const specName = newConst(
|
||||||
value.name + "_spec",
|
value.name + "_spec",
|
||||||
convertInputSpec(spec.spec)
|
convertInputSpec(spec.spec),
|
||||||
);
|
);
|
||||||
return `List.obj({
|
return `List.obj({
|
||||||
name: ${JSON.stringify(value.name || null)},
|
name: ${JSON.stringify(value.name || null)},
|
||||||
@@ -155,7 +155,7 @@ export async function specToBuilder(
|
|||||||
name: string;
|
name: string;
|
||||||
spec: C.InputSpec;
|
spec: C.InputSpec;
|
||||||
}
|
}
|
||||||
>
|
>,
|
||||||
): string {
|
): string {
|
||||||
let answer = "Variants.of({";
|
let answer = "Variants.of({";
|
||||||
for (const [key, { name, spec }] of Object.entries(variants)) {
|
for (const [key, { name, spec }] of Object.entries(variants)) {
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ export class Checker {
|
|||||||
* Check is the function that will be given a emver or unparsed emver and should give if it follows
|
* Check is the function that will be given a emver or unparsed emver and should give if it follows
|
||||||
* a pattern
|
* a pattern
|
||||||
*/
|
*/
|
||||||
public readonly check: (value: string | EmVer) => boolean
|
public readonly check: (value: string | EmVer) => boolean,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export async function checkPortListening(
|
|||||||
{
|
{
|
||||||
error = `Port ${port} is not listening`,
|
error = `Port ${port} is not listening`,
|
||||||
message = `Port ${port} is available`,
|
message = `Port ${port} is available`,
|
||||||
} = {}
|
} = {},
|
||||||
): Promise<CheckResult> {
|
): Promise<CheckResult> {
|
||||||
const hasAddress =
|
const hasAddress =
|
||||||
containsAddress(await effects.runCommand(`cat /proc/net/tcp`), port) ||
|
containsAddress(await effects.runCommand(`cat /proc/net/tcp`), port) ||
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const checkWebUrl = async (
|
|||||||
timeout = 1000,
|
timeout = 1000,
|
||||||
successMessage = `Reached ${url}`,
|
successMessage = `Reached ${url}`,
|
||||||
errorMessage = `Error while fetching URL: ${url}`,
|
errorMessage = `Error while fetching URL: ${url}`,
|
||||||
} = {}
|
} = {},
|
||||||
): Promise<CheckResult> => {
|
): Promise<CheckResult> => {
|
||||||
return Promise.race([effects.fetch(url), timeoutPromise(timeout)])
|
return Promise.race([effects.fetch(url), timeoutPromise(timeout)])
|
||||||
.then((x) => ({
|
.then((x) => ({
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export { checkWebUrl } from "./checkWebUrl";
|
|||||||
|
|
||||||
export function timeoutPromise(ms: number, { message = "Timed out" } = {}) {
|
export function timeoutPromise(ms: number, { message = "Timed out" } = {}) {
|
||||||
return new Promise<never>((resolve, reject) =>
|
return new Promise<never>((resolve, reject) =>
|
||||||
setTimeout(() => reject(new Error(message)), ms)
|
setTimeout(() => reject(new Error(message)), ms),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export { runHealthScript };
|
export { runHealthScript };
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export const runHealthScript = async <A extends string>(
|
|||||||
errorMessage = `Error while running command: ${runCommand}`,
|
errorMessage = `Error while running command: ${runCommand}`,
|
||||||
message = (res: string) =>
|
message = (res: string) =>
|
||||||
`Have ran script ${runCommand} and the result: ${res}`,
|
`Have ran script ${runCommand} and the result: ${res}`,
|
||||||
} = {}
|
} = {},
|
||||||
): Promise<CheckResult> => {
|
): Promise<CheckResult> => {
|
||||||
const res = await Promise.race([
|
const res = await Promise.race([
|
||||||
effects.runCommand(runCommand, { timeoutMillis: timeout }),
|
effects.runCommand(runCommand, { timeoutMillis: timeout }),
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export function noMigrationsDown(): MigrationDownReceipt {
|
|||||||
return {} as MigrationDownReceipt;
|
return {} as MigrationDownReceipt;
|
||||||
}
|
}
|
||||||
export function migrationDown(
|
export function migrationDown(
|
||||||
fn: () => Promise<unknown>
|
fn: () => Promise<unknown>,
|
||||||
): MigrationDownReceipt {
|
): MigrationDownReceipt {
|
||||||
fn();
|
fn();
|
||||||
return {} as MigrationDownReceipt;
|
return {} as MigrationDownReceipt;
|
||||||
@@ -37,7 +37,7 @@ export function migrationDown(
|
|||||||
export function setupInit(
|
export function setupInit(
|
||||||
fn: (
|
fn: (
|
||||||
...args: Parameters<ExpectedExports.init>
|
...args: Parameters<ExpectedExports.init>
|
||||||
) => Promise<[MigrationReceipt, ActionReceipt]>
|
) => Promise<[MigrationReceipt, ActionReceipt]>,
|
||||||
) {
|
) {
|
||||||
const initFn: ExpectedExports.init = (...args) => fn(...args);
|
const initFn: ExpectedExports.init = (...args) => fn(...args);
|
||||||
return initFn;
|
return initFn;
|
||||||
@@ -46,7 +46,7 @@ export function setupInit(
|
|||||||
export function setupUninit(
|
export function setupUninit(
|
||||||
fn: (
|
fn: (
|
||||||
...args: Parameters<ExpectedExports.uninit>
|
...args: Parameters<ExpectedExports.uninit>
|
||||||
) => Promise<[MigrationDownReceipt]>
|
) => Promise<[MigrationDownReceipt]>,
|
||||||
) {
|
) {
|
||||||
const uninitFn: ExpectedExports.uninit = (...args) => fn(...args);
|
const uninitFn: ExpectedExports.uninit = (...args) => fn(...args);
|
||||||
return uninitFn;
|
return uninitFn;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { InterfaceReceipt } from "./interfaceReceipt";
|
|||||||
type Daemon<
|
type Daemon<
|
||||||
Ids extends string | never,
|
Ids extends string | never,
|
||||||
Command extends string,
|
Command extends string,
|
||||||
Id extends string
|
Id extends string,
|
||||||
> = {
|
> = {
|
||||||
id: Id;
|
id: Id;
|
||||||
command: ValidIfNoStupidEscape<Command> | [string, ...string[]];
|
command: ValidIfNoStupidEscape<Command> | [string, ...string[]];
|
||||||
@@ -52,7 +52,7 @@ export class Daemons<Ids extends string | never> {
|
|||||||
private constructor(
|
private constructor(
|
||||||
readonly effects: Effects,
|
readonly effects: Effects,
|
||||||
readonly started: (onTerm: () => void) => null,
|
readonly started: (onTerm: () => void) => null,
|
||||||
readonly daemons?: Daemon<Ids, "command", Ids>[]
|
readonly daemons?: Daemon<Ids, "command", Ids>[],
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
static of(config: {
|
static of(config: {
|
||||||
@@ -64,7 +64,7 @@ export class Daemons<Ids extends string | never> {
|
|||||||
return new Daemons<never>(config.effects, config.started);
|
return new Daemons<never>(config.effects, config.started);
|
||||||
}
|
}
|
||||||
addDaemon<Id extends string, Command extends string>(
|
addDaemon<Id extends string, Command extends string>(
|
||||||
newDaemon: Daemon<Ids, Command, Id>
|
newDaemon: Daemon<Ids, Command, Id>,
|
||||||
) {
|
) {
|
||||||
const daemons = ((this?.daemons ?? []) as any[]).concat(newDaemon);
|
const daemons = ((this?.daemons ?? []) as any[]).concat(newDaemon);
|
||||||
return new Daemons<Ids | Id>(this.effects, this.started, daemons);
|
return new Daemons<Ids | Id>(this.effects, this.started, daemons);
|
||||||
@@ -76,7 +76,7 @@ export class Daemons<Ids extends string | never> {
|
|||||||
const daemons = this.daemons ?? [];
|
const daemons = this.daemons ?? [];
|
||||||
for (const daemon of daemons) {
|
for (const daemon of daemons) {
|
||||||
const requiredPromise = Promise.all(
|
const requiredPromise = Promise.all(
|
||||||
daemon.requires?.map((id) => daemonsStarted[id]) ?? []
|
daemon.requires?.map((id) => daemonsStarted[id]) ?? [],
|
||||||
);
|
);
|
||||||
daemonsStarted[daemon.id] = requiredPromise.then(async () => {
|
daemonsStarted[daemon.id] = requiredPromise.then(async () => {
|
||||||
const { command } = daemon;
|
const { command } = daemon;
|
||||||
@@ -100,15 +100,15 @@ export class Daemons<Ids extends string | never> {
|
|||||||
async term() {
|
async term() {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
Object.values<Promise<DaemonReturned>>(daemonsStarted).map((x) =>
|
Object.values<Promise<DaemonReturned>>(daemonsStarted).map((x) =>
|
||||||
x.then((x) => x.term())
|
x.then((x) => x.term()),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
async wait() {
|
async wait() {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
Object.values<Promise<DaemonReturned>>(daemonsStarted).map((x) =>
|
Object.values<Promise<DaemonReturned>>(daemonsStarted).map((x) =>
|
||||||
x.then((x) => x.wait())
|
x.then((x) => x.wait()),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export class NetworkInterfaceBuilder {
|
|||||||
basic?: null | { password: string; username: string };
|
basic?: null | { password: string; username: string };
|
||||||
path?: string;
|
path?: string;
|
||||||
search?: Record<string, string>;
|
search?: Record<string, string>;
|
||||||
}
|
},
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async exportAddresses(addresses: Iterable<Origin>) {
|
async exportAddresses(addresses: Iterable<Origin>) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export class Origin {
|
|||||||
username: string;
|
username: string;
|
||||||
}
|
}
|
||||||
| null
|
| null
|
||||||
| undefined
|
| undefined,
|
||||||
) {
|
) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
const urlAuth = !!(origin) ? `${origin.username}:${origin.password}@` :
|
const urlAuth = !!(origin) ? `${origin.username}:${origin.password}@` :
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const runningMain: (
|
|||||||
fn: (o: {
|
fn: (o: {
|
||||||
effects: Effects;
|
effects: Effects;
|
||||||
started(onTerm: () => void): null;
|
started(onTerm: () => void): null;
|
||||||
}) => Promise<Daemons<any>>
|
}) => Promise<Daemons<any>>,
|
||||||
) => ExpectedExports.main = (fn) => {
|
) => ExpectedExports.main = (fn) => {
|
||||||
return async (options) => {
|
return async (options) => {
|
||||||
const result = await fn(options);
|
const result = await fn(options);
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export type UnionToIntersection<T> = ((x: T) => any) extends (x: infer R) => any
|
|||||||
export function setupPropertiesExport(
|
export function setupPropertiesExport(
|
||||||
fn: (
|
fn: (
|
||||||
...args: Parameters<ExpectedExports.properties>
|
...args: Parameters<ExpectedExports.properties>
|
||||||
) => void | Promise<void> | Promise<(PropertyGroup | PropertyString)[]>
|
) => void | Promise<void> | Promise<(PropertyGroup | PropertyString)[]>,
|
||||||
): ExpectedExports.properties {
|
): ExpectedExports.properties {
|
||||||
return (async (...args) => {
|
return (async (...args) => {
|
||||||
const result = await fn(...args);
|
const result = await fn(...args);
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ describe("builder tests", () => {
|
|||||||
}}`
|
}}`
|
||||||
.replaceAll("\n", " ")
|
.replaceAll("\n", " ")
|
||||||
.replaceAll(/\s{2,}/g, "")
|
.replaceAll(/\s{2,}/g, "")
|
||||||
.replaceAll(": ", ":")
|
.replaceAll(": ", ":"),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -121,7 +121,7 @@ describe("values", () => {
|
|||||||
a: Value.boolean({
|
a: Value.boolean({
|
||||||
name: "test",
|
name: "test",
|
||||||
}),
|
}),
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
const validator = value.validator();
|
const validator = value.validator();
|
||||||
validator.unsafeCast({ a: true });
|
validator.unsafeCast({ a: true });
|
||||||
@@ -138,7 +138,7 @@ describe("values", () => {
|
|||||||
name: "a",
|
name: "a",
|
||||||
spec: Config.of({ b: Value.boolean({ name: "b" }) }),
|
spec: Config.of({ b: Value.boolean({ name: "b" }) }),
|
||||||
},
|
},
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
const validator = value.validator();
|
const validator = value.validator();
|
||||||
validator.unsafeCast({ unionSelectKey: "a", unionValueKey: { b: false } });
|
validator.unsafeCast({ unionSelectKey: "a", unionValueKey: { b: false } });
|
||||||
@@ -156,8 +156,8 @@ describe("values", () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
integral: false,
|
integral: false,
|
||||||
}
|
},
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
const validator = value.validator();
|
const validator = value.validator();
|
||||||
validator.unsafeCast([1, 2, 3]);
|
validator.unsafeCast([1, 2, 3]);
|
||||||
@@ -174,8 +174,8 @@ describe("Builder List", () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
spec: Config.of({ test: Value.boolean({ name: "test" }) }),
|
spec: Config.of({ test: Value.boolean({ name: "test" }) }),
|
||||||
}
|
},
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
const validator = value.validator();
|
const validator = value.validator();
|
||||||
validator.unsafeCast([{ test: true }]);
|
validator.unsafeCast([{ test: true }]);
|
||||||
@@ -187,8 +187,8 @@ describe("Builder List", () => {
|
|||||||
{
|
{
|
||||||
name: "test",
|
name: "test",
|
||||||
},
|
},
|
||||||
{}
|
{},
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
const validator = value.validator();
|
const validator = value.validator();
|
||||||
validator.unsafeCast(["test", "text"]);
|
validator.unsafeCast(["test", "text"]);
|
||||||
@@ -200,8 +200,8 @@ describe("Builder List", () => {
|
|||||||
{
|
{
|
||||||
name: "test",
|
name: "test",
|
||||||
},
|
},
|
||||||
{ integral: true }
|
{ integral: true },
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
const validator = value.validator();
|
const validator = value.validator();
|
||||||
validator.unsafeCast([12, 45]);
|
validator.unsafeCast([12, 45]);
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ describe("Config Types", () => {
|
|||||||
someList.spec satisfies ListValueSpecOf<"object">;
|
someList.spec satisfies ListValueSpecOf<"object">;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Failed to figure out the type: " + JSON.stringify(someList)
|
"Failed to figure out the type: " + JSON.stringify(someList),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -407,5 +407,5 @@ writeConvertedFile(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
startSdk: "../",
|
startSdk: "../",
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ 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
|
x: infer R,
|
||||||
) => any
|
) => any
|
||||||
? R
|
? R
|
||||||
: never;
|
: never;
|
||||||
@@ -61,14 +61,14 @@ testOutput<
|
|||||||
>()(null);
|
>()(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<
|
testOutput<
|
||||||
InputSpec["testListUnion"][0]["union"][UnionValueKey]["name"],
|
InputSpec["testListUnion"][0]["union"][UnionValueKey]["name"],
|
||||||
string
|
string
|
||||||
>()(null);
|
>()(null);
|
||||||
testOutput<InputSpec["testListUnion"][0]["union"][UnionSelectKey], "lnd">()(
|
testOutput<InputSpec["testListUnion"][0]["union"][UnionSelectKey], "lnd">()(
|
||||||
null
|
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
|
||||||
@@ -139,19 +139,19 @@ 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();
|
).toThrowError();
|
||||||
expect(() =>
|
expect(() =>
|
||||||
matchInputSpec.unsafeCast(mergeDeep(validInput, { rpc: { enable: 2 } }))
|
matchInputSpec.unsafeCast(mergeDeep(validInput, { rpc: { enable: 2 } })),
|
||||||
).toThrowError();
|
).toThrowError();
|
||||||
|
|
||||||
expect(() =>
|
expect(() =>
|
||||||
matchInputSpec.unsafeCast(
|
matchInputSpec.unsafeCast(
|
||||||
mergeDeep(validInput, {
|
mergeDeep(validInput, {
|
||||||
rpc: { advanced: { serialversion: "testing" } },
|
rpc: { advanced: { serialversion: "testing" } },
|
||||||
})
|
}),
|
||||||
)
|
),
|
||||||
).toThrowError();
|
).toThrowError();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
81
lib/test/wrapperData.test.ts
Normal file
81
lib/test/wrapperData.test.ts
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import { T } from "..";
|
||||||
|
import { utils } from "../util";
|
||||||
|
|
||||||
|
type WrapperType = {
|
||||||
|
config: {
|
||||||
|
someValue: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const todo = <A>(): A => {
|
||||||
|
throw new Error("not implemented");
|
||||||
|
};
|
||||||
|
const noop = () => {};
|
||||||
|
describe("wrapperData", () => {
|
||||||
|
test.skip("types", async () => {
|
||||||
|
utils<WrapperType>(todo<T.Effects>()).setWrapperData(
|
||||||
|
"/config/someValue",
|
||||||
|
"someValue",
|
||||||
|
);
|
||||||
|
utils<WrapperType>(todo<T.Effects>()).setWrapperData(
|
||||||
|
"/config/someValue",
|
||||||
|
|
||||||
|
// @ts-expect-error Type is wrong for the setting value
|
||||||
|
5,
|
||||||
|
);
|
||||||
|
utils<WrapperType>(todo<T.Effects>()).setWrapperData(
|
||||||
|
// @ts-expect-error Path is wrong
|
||||||
|
"/config/someVae3lue",
|
||||||
|
"someValue",
|
||||||
|
);
|
||||||
|
|
||||||
|
todo<T.Effects>().setWrapperData<WrapperType, "/config/someValue">({
|
||||||
|
path: "/config/someValue",
|
||||||
|
value: "someValueIn",
|
||||||
|
});
|
||||||
|
todo<T.Effects>().setWrapperData<WrapperType, "/config/some2Value">({
|
||||||
|
//@ts-expect-error Path is wrong
|
||||||
|
path: "/config/someValue",
|
||||||
|
//@ts-expect-error Path is wrong
|
||||||
|
value: "someValueIn",
|
||||||
|
});
|
||||||
|
todo<T.Effects>().setWrapperData<WrapperType, "/config/someValue">({
|
||||||
|
//@ts-expect-error Path is wrong
|
||||||
|
path: "/config/some2Value",
|
||||||
|
value: "someValueIn",
|
||||||
|
});
|
||||||
|
|
||||||
|
(await utils<WrapperType>(todo<T.Effects>())
|
||||||
|
.getWrapperData("/config/someValue")
|
||||||
|
.const()) satisfies string;
|
||||||
|
(await utils<WrapperType>(todo<T.Effects>())
|
||||||
|
.getWrapperData("/config")
|
||||||
|
.const()) satisfies WrapperType["config"];
|
||||||
|
await utils<WrapperType>(todo<T.Effects>())
|
||||||
|
// @ts-expect-error Path is wrong
|
||||||
|
.getWrapperData("/config/somdsfeValue")
|
||||||
|
.const();
|
||||||
|
(await utils<WrapperType>(todo<T.Effects>())
|
||||||
|
.getWrapperData("/config/someValue")
|
||||||
|
// @ts-expect-error satisfies type is wrong
|
||||||
|
.const()) satisfies number;
|
||||||
|
(await utils<WrapperType>(todo<T.Effects>())
|
||||||
|
// @ts-expect-error Path is wrong
|
||||||
|
.getWrapperData("/config/")
|
||||||
|
.const()) satisfies WrapperType["config"];
|
||||||
|
|
||||||
|
(await todo<T.Effects>().getWrapperData<WrapperType, "/config/someValue">({
|
||||||
|
path: "/config/someValue",
|
||||||
|
callback: noop,
|
||||||
|
})) satisfies string;
|
||||||
|
await todo<T.Effects>().getWrapperData<WrapperType, "/config/someValue">({
|
||||||
|
// @ts-expect-error Path is wrong as in it doesn't match above
|
||||||
|
path: "/config/someV2alue",
|
||||||
|
callback: noop,
|
||||||
|
});
|
||||||
|
await todo<T.Effects>().getWrapperData<WrapperType, "/config/someV2alue">({
|
||||||
|
// @ts-expect-error Path is wrong as in it doesn't exists in wrapper type
|
||||||
|
path: "/config/someV2alue",
|
||||||
|
callback: noop,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
54
lib/types.ts
54
lib/types.ts
@@ -85,7 +85,7 @@ export namespace ExpectedExports {
|
|||||||
/** Auto configure is used to make sure that other dependencies have the values t
|
/** Auto configure is used to make sure that other dependencies have the values t
|
||||||
* that this service could use.
|
* that this service could use.
|
||||||
*/
|
*/
|
||||||
export type autoConfig<Config> = Record<PackageId, AutoConfigure<Config>>;
|
export type autoConfig = Record<PackageId, AutoConfigure>;
|
||||||
}
|
}
|
||||||
export type TimeMs = number;
|
export type TimeMs = number;
|
||||||
export type VersionString = string;
|
export type VersionString = string;
|
||||||
@@ -94,11 +94,19 @@ export type VersionString = string;
|
|||||||
* AutoConfigure is used as the value to the key of package id,
|
* AutoConfigure is used as the value to the key of package id,
|
||||||
* this is used to make sure that other dependencies have the values that this service could use.
|
* this is used to make sure that other dependencies have the values that this service could use.
|
||||||
*/
|
*/
|
||||||
export type AutoConfigure<Config> = {
|
export type AutoConfigure = {
|
||||||
/** Checks are called to make sure that our dependency is in the correct shape. If a known error is returned we know that the dependency needs modification */
|
/** Checks are called to make sure that our dependency is in the correct shape. If a known error is returned we know that the dependency needs modification */
|
||||||
check(effects: Effects, input: Config): Promise<void>;
|
check(options: {
|
||||||
|
effects: Effects;
|
||||||
|
localConfig: unknown;
|
||||||
|
remoteConfig: unknown;
|
||||||
|
}): Promise<void>;
|
||||||
/** This is called after we know that the dependency package needs a new configuration, this would be a transform for defaults */
|
/** This is called after we know that the dependency package needs a new configuration, this would be a transform for defaults */
|
||||||
autoConfigure(effects: Effects, input: Config): Promise<Config>;
|
autoConfigure(options: {
|
||||||
|
effects: Effects;
|
||||||
|
localConfig: unknown;
|
||||||
|
remoteConfig: unknown;
|
||||||
|
}): Promise<unknown>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ValidIfNoStupidEscape<A> = A extends
|
export type ValidIfNoStupidEscape<A> = A extends
|
||||||
@@ -172,14 +180,14 @@ export type Effects = {
|
|||||||
command: ValidIfNoStupidEscape<A> | [string, ...string[]],
|
command: ValidIfNoStupidEscape<A> | [string, ...string[]],
|
||||||
input?: {
|
input?: {
|
||||||
timeoutMillis?: number;
|
timeoutMillis?: number;
|
||||||
}
|
},
|
||||||
): Promise<string>;
|
): Promise<string>;
|
||||||
runShellDaemon(command: string): {
|
runShellDaemon(command: string): {
|
||||||
wait(): Promise<string>;
|
wait(): Promise<string>;
|
||||||
term(): Promise<void>;
|
term(): Promise<void>;
|
||||||
};
|
};
|
||||||
runDaemon<A extends string>(
|
runDaemon<A extends string>(
|
||||||
command: ValidIfNoStupidEscape<A> | [string, ...string[]]
|
command: ValidIfNoStupidEscape<A> | [string, ...string[]],
|
||||||
): DaemonReturned;
|
): DaemonReturned;
|
||||||
|
|
||||||
/** Uses the chown on the system */
|
/** Uses the chown on the system */
|
||||||
@@ -225,7 +233,7 @@ export type Effects = {
|
|||||||
method?: "GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "PATCH";
|
method?: "GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "PATCH";
|
||||||
headers?: Record<string, string>;
|
headers?: Record<string, string>;
|
||||||
body?: string;
|
body?: string;
|
||||||
}
|
},
|
||||||
): Promise<{
|
): Promise<{
|
||||||
method: string;
|
method: string;
|
||||||
ok: boolean;
|
ok: boolean;
|
||||||
@@ -256,19 +264,19 @@ export type Effects = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/** Get a value in a json like data, can be observed and subscribed */
|
/** Get a value in a json like data, can be observed and subscribed */
|
||||||
getWrapperData(options: {
|
getWrapperData<WrapperData = never, Path extends string = never>(options: {
|
||||||
/** If there is no packageId it is assumed the current package */
|
/** If there is no packageId it is assumed the current package */
|
||||||
packageId?: string;
|
packageId?: string;
|
||||||
/** The path defaults to root level, using the [JsonPath](https://jsonpath.com/) */
|
/** The path defaults to root level, using the [JsonPath](https://jsonpath.com/) */
|
||||||
path?: string;
|
path: Path & EnsureWrapperDataPath<WrapperData, Path>;
|
||||||
callback: (config: unknown, previousConfig: unknown) => void;
|
callback: (config: unknown, previousConfig: unknown) => void;
|
||||||
}): Promise<unknown>;
|
}): Promise<ExtractWrapperData<WrapperData, Path>>;
|
||||||
|
|
||||||
/** Used to store values that can be accessed and subscribed to */
|
/** Used to store values that can be accessed and subscribed to */
|
||||||
setWrapperData(options: {
|
setWrapperData<WrapperData = never, Path extends string = never>(options: {
|
||||||
/** Sets the value for the wrapper at the path, it will override, using the [JsonPath](https://jsonpath.com/) */
|
/** Sets the value for the wrapper at the path, it will override, using the [JsonPath](https://jsonpath.com/) */
|
||||||
path?: string;
|
path: Path & EnsureWrapperDataPath<WrapperData, Path>;
|
||||||
value: unknown;
|
value: ExtractWrapperData<WrapperData, Path>;
|
||||||
}): Promise<void>;
|
}): Promise<void>;
|
||||||
|
|
||||||
getLocalHostname(): Promise<string>;
|
getLocalHostname(): Promise<string>;
|
||||||
@@ -276,14 +284,14 @@ export type Effects = {
|
|||||||
/** Get the address for another service for tor interfaces */
|
/** Get the address for another service for tor interfaces */
|
||||||
getServiceTorHostname(
|
getServiceTorHostname(
|
||||||
interfaceId: string,
|
interfaceId: string,
|
||||||
packageId?: string
|
packageId?: string,
|
||||||
): Promise<string>;
|
): Promise<string>;
|
||||||
/**
|
/**
|
||||||
* Get the port address for another service
|
* Get the port address for another service
|
||||||
*/
|
*/
|
||||||
getServicePortForward(
|
getServicePortForward(
|
||||||
internalPort: number,
|
internalPort: number,
|
||||||
packageId?: string
|
packageId?: string,
|
||||||
): Promise<number>;
|
): Promise<number>;
|
||||||
|
|
||||||
/** When we want to create a link in the front end interfaces, and example is
|
/** When we want to create a link in the front end interfaces, and example is
|
||||||
@@ -356,7 +364,7 @@ export type Effects = {
|
|||||||
*/
|
*/
|
||||||
getSslCertificate: (
|
getSslCertificate: (
|
||||||
packageId: string,
|
packageId: string,
|
||||||
algorithm?: "ecdsa" | "ed25519"
|
algorithm?: "ecdsa" | "ed25519",
|
||||||
) => [string, string, string];
|
) => [string, string, string];
|
||||||
/**
|
/**
|
||||||
* @returns PEM encoded ssl key (ecdsa)
|
* @returns PEM encoded ssl key (ecdsa)
|
||||||
@@ -379,6 +387,20 @@ export type Effects = {
|
|||||||
shutdown(): void;
|
shutdown(): void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
export type ExtractWrapperData<WrapperData, Path extends string> =
|
||||||
|
Path extends `/${infer A }/${infer Rest }` ? (A extends keyof WrapperData ? ExtractWrapperData<WrapperData[A], `/${Rest}`> : never) :
|
||||||
|
Path extends `/${infer A }` ? (A extends keyof WrapperData ? WrapperData[A] : never) :
|
||||||
|
never
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
type _EnsureWrapperDataPath<WrapperData, Path extends string, Origin extends string> =
|
||||||
|
Path extends `/${infer A }/${infer Rest }` ? (A extends keyof WrapperData ? ExtractWrapperData<WrapperData[A], `/${Rest}`> : never) :
|
||||||
|
Path extends `/${infer A }` ? (A extends keyof WrapperData ? Origin : never) :
|
||||||
|
never
|
||||||
|
// prettier-ignore
|
||||||
|
export type EnsureWrapperDataPath<WrapperData, Path extends string> = _EnsureWrapperDataPath<WrapperData, Path, Path>
|
||||||
|
|
||||||
/* rsync options: https://linux.die.net/man/1/rsync
|
/* rsync options: https://linux.die.net/man/1/rsync
|
||||||
*/
|
*/
|
||||||
export type BackupOptions = {
|
export type BackupOptions = {
|
||||||
|
|||||||
19
lib/util/deepEqual.ts
Normal file
19
lib/util/deepEqual.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { object } from "ts-matches";
|
||||||
|
|
||||||
|
export function deepEqual(...args: unknown[]) {
|
||||||
|
if (!object.test(args[args.length - 1])) return args[args.length - 1];
|
||||||
|
const objects = args.filter(object.test);
|
||||||
|
if (objects.length === 0) {
|
||||||
|
for (const x of args) if (x !== args[0]) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (objects.length !== args.length) return false;
|
||||||
|
const allKeys = new Set(objects.flatMap((x) => Object.keys(x)));
|
||||||
|
for (const key of allKeys) {
|
||||||
|
for (const x of objects) {
|
||||||
|
if (!(key in x)) return false;
|
||||||
|
if (!deepEqual((objects[0] as any)[key], (x as any)[key])) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
16
lib/util/deepMerge.ts
Normal file
16
lib/util/deepMerge.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { object } from "ts-matches";
|
||||||
|
|
||||||
|
export function deepMerge(...args: unknown[]): unknown {
|
||||||
|
const lastItem = (args as any)[args.length - 1];
|
||||||
|
if (!object.test(lastItem)) return lastItem;
|
||||||
|
const objects = args.filter(object.test).filter((x) => !Array.isArray(x));
|
||||||
|
if (objects.length === 0) return lastItem as any;
|
||||||
|
const allKeys = new Set(objects.flatMap((x) => Object.keys(x)));
|
||||||
|
for (const key of allKeys) {
|
||||||
|
const filteredValues = objects.flatMap((x) =>
|
||||||
|
key in x ? [(x as any)[key]] : [],
|
||||||
|
);
|
||||||
|
(objects as any)[0][key] = deepMerge(...filteredValues);
|
||||||
|
}
|
||||||
|
return objects[0] as any;
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ type UnReadonly<A> = { -readonly [k in keyof A]: A[k] };
|
|||||||
declare global {
|
declare global {
|
||||||
interface Object {
|
interface Object {
|
||||||
entries<T extends {}>(
|
entries<T extends {}>(
|
||||||
this: T
|
this: T,
|
||||||
): Array<{ -readonly [K in keyof T]: [K, T[K]] }[keyof T]>;
|
): Array<{ -readonly [K in keyof T]: [K, T[K]] }[keyof T]>;
|
||||||
values<T extends {}>(this: T): Array<T[keyof T]>;
|
values<T extends {}>(this: T): Array<T[keyof T]>;
|
||||||
keys<T extends {}>(this: T): Array<keyof T>;
|
keys<T extends {}>(this: T): Array<keyof T>;
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export class FileHelper<A> {
|
|||||||
readonly path: string,
|
readonly path: string,
|
||||||
readonly volume: string,
|
readonly volume: string,
|
||||||
readonly writeData: (dataIn: A) => string,
|
readonly writeData: (dataIn: A) => string,
|
||||||
readonly readData: (stringValue: string) => A
|
readonly readData: (stringValue: string) => A,
|
||||||
) {}
|
) {}
|
||||||
async write(data: A, effects: T.Effects) {
|
async write(data: A, effects: T.Effects) {
|
||||||
let matched;
|
let matched;
|
||||||
@@ -86,21 +86,21 @@ export class FileHelper<A> {
|
|||||||
await effects.readFile({
|
await effects.readFile({
|
||||||
path: this.path,
|
path: this.path,
|
||||||
volumeId: this.volume,
|
volumeId: this.volume,
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
static raw<A>(
|
static raw<A>(
|
||||||
path: string,
|
path: string,
|
||||||
volume: string,
|
volume: string,
|
||||||
toFile: (dataIn: A) => string,
|
toFile: (dataIn: A) => string,
|
||||||
fromFile: (rawData: string) => A
|
fromFile: (rawData: string) => A,
|
||||||
) {
|
) {
|
||||||
return new FileHelper<A>(path, volume, toFile, fromFile);
|
return new FileHelper<A>(path, volume, toFile, fromFile);
|
||||||
}
|
}
|
||||||
static json<A>(
|
static json<A>(
|
||||||
path: string,
|
path: string,
|
||||||
volume: string,
|
volume: string,
|
||||||
shape: matches.Validator<unknown, A>
|
shape: matches.Validator<unknown, A>,
|
||||||
) {
|
) {
|
||||||
return new FileHelper<A>(
|
return new FileHelper<A>(
|
||||||
path,
|
path,
|
||||||
@@ -110,13 +110,13 @@ export class FileHelper<A> {
|
|||||||
},
|
},
|
||||||
(inString) => {
|
(inString) => {
|
||||||
return shape.unsafeCast(JSON.parse(inString));
|
return shape.unsafeCast(JSON.parse(inString));
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
static toml<A extends Record<string, unknown>>(
|
static toml<A extends Record<string, unknown>>(
|
||||||
path: string,
|
path: string,
|
||||||
volume: string,
|
volume: string,
|
||||||
shape: matches.Validator<unknown, A>
|
shape: matches.Validator<unknown, A>,
|
||||||
) {
|
) {
|
||||||
return new FileHelper<A>(
|
return new FileHelper<A>(
|
||||||
path,
|
path,
|
||||||
@@ -126,13 +126,13 @@ export class FileHelper<A> {
|
|||||||
},
|
},
|
||||||
(inString) => {
|
(inString) => {
|
||||||
return shape.unsafeCast(TOML.parse(inString));
|
return shape.unsafeCast(TOML.parse(inString));
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
static yaml<A extends Record<string, unknown>>(
|
static yaml<A extends Record<string, unknown>>(
|
||||||
path: string,
|
path: string,
|
||||||
volume: string,
|
volume: string,
|
||||||
shape: matches.Validator<unknown, A>
|
shape: matches.Validator<unknown, A>,
|
||||||
) {
|
) {
|
||||||
return new FileHelper<A>(
|
return new FileHelper<A>(
|
||||||
path,
|
path,
|
||||||
@@ -142,7 +142,7 @@ export class FileHelper<A> {
|
|||||||
},
|
},
|
||||||
(inString) => {
|
(inString) => {
|
||||||
return shape.unsafeCast(YAML.parse(inString));
|
return shape.unsafeCast(YAML.parse(inString));
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +1,53 @@
|
|||||||
import { Parser } from "ts-matches";
|
import { Parser } from "ts-matches";
|
||||||
import { Effects } from "../types";
|
import { Effects, EnsureWrapperDataPath, ExtractWrapperData } from "../types";
|
||||||
|
import { NoAny } from ".";
|
||||||
|
|
||||||
export function getWrapperData<A>(
|
export class WrapperData<WrapperData, Path extends string> {
|
||||||
|
constructor(
|
||||||
|
readonly effects: Effects,
|
||||||
|
readonly path: Path & EnsureWrapperDataPath<WrapperData, Path>,
|
||||||
|
readonly options: {
|
||||||
|
/** Defaults to what ever the package currently in */
|
||||||
|
packageId?: string | undefined;
|
||||||
|
} = {},
|
||||||
|
) {}
|
||||||
|
|
||||||
|
const() {
|
||||||
|
return this.effects.getWrapperData<WrapperData, Path>({
|
||||||
|
...this.options,
|
||||||
|
path: this.path as any,
|
||||||
|
callback: this.effects.restart,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
first() {
|
||||||
|
return this.effects.getWrapperData<WrapperData, Path>({
|
||||||
|
...this.options,
|
||||||
|
path: this.path as any,
|
||||||
|
callback: () => {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async *overTime() {
|
||||||
|
while (true) {
|
||||||
|
let callback: () => void;
|
||||||
|
const waitForNext = new Promise<void>((resolve) => {
|
||||||
|
callback = resolve;
|
||||||
|
});
|
||||||
|
yield await this.effects.getWrapperData<WrapperData, Path>({
|
||||||
|
...this.options,
|
||||||
|
path: this.path as any,
|
||||||
|
callback: () => callback(),
|
||||||
|
});
|
||||||
|
await waitForNext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function getWrapperData<WrapperData, Path extends string>(
|
||||||
effects: Effects,
|
effects: Effects,
|
||||||
validator: Parser<unknown, A>,
|
path: Path & EnsureWrapperDataPath<WrapperData, Path>,
|
||||||
options: {
|
options: {
|
||||||
/** Defaults to what ever the package currently in */
|
/** Defaults to what ever the package currently in */
|
||||||
packageId?: string | undefined;
|
packageId?: string | undefined;
|
||||||
/** JsonPath */
|
} = {},
|
||||||
path?: string | undefined;
|
|
||||||
} = {}
|
|
||||||
) {
|
) {
|
||||||
return {
|
return new WrapperData<WrapperData, Path>(effects, path as any, options);
|
||||||
const: () =>
|
|
||||||
effects
|
|
||||||
.getWrapperData({
|
|
||||||
...options,
|
|
||||||
callback: effects.restart,
|
|
||||||
})
|
|
||||||
.then(validator.unsafeCast),
|
|
||||||
first: () =>
|
|
||||||
effects
|
|
||||||
.getWrapperData({
|
|
||||||
...options,
|
|
||||||
callback: () => {},
|
|
||||||
})
|
|
||||||
.then(validator.unsafeCast),
|
|
||||||
overTime: async function* <A>() {
|
|
||||||
while (true) {
|
|
||||||
let callback: () => void;
|
|
||||||
const waitForNext = new Promise<void>((resolve) => {
|
|
||||||
callback = resolve;
|
|
||||||
});
|
|
||||||
yield await effects
|
|
||||||
.getWrapperData({
|
|
||||||
...options,
|
|
||||||
callback: () => callback(),
|
|
||||||
})
|
|
||||||
.then(validator.unsafeCast);
|
|
||||||
await waitForNext;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,20 +5,23 @@ import nullIfEmpty from "./nullIfEmpty";
|
|||||||
import { getWrapperData } from "./getWrapperData";
|
import { getWrapperData } from "./getWrapperData";
|
||||||
import { checkPortListening, checkWebUrl } from "../health/checkFns";
|
import { checkPortListening, checkWebUrl } from "../health/checkFns";
|
||||||
import { LocalPort, NetworkBuilder, TorHostname } from "../mainFn";
|
import { LocalPort, NetworkBuilder, TorHostname } from "../mainFn";
|
||||||
|
import { ExtractWrapperData } from "../types";
|
||||||
|
|
||||||
export { guardAll, typeFromProps } from "./propertiesMatcher";
|
export { guardAll, typeFromProps } from "./propertiesMatcher";
|
||||||
export { default as nullIfEmpty } from "./nullIfEmpty";
|
export { default as nullIfEmpty } from "./nullIfEmpty";
|
||||||
export { FileHelper } from "./fileHelper";
|
export { FileHelper } from "./fileHelper";
|
||||||
export { getWrapperData } from "./getWrapperData";
|
export { getWrapperData } from "./getWrapperData";
|
||||||
|
export { deepEqual } from "./deepEqual";
|
||||||
|
export { deepMerge } from "./deepMerge";
|
||||||
|
|
||||||
/** Used to check if the file exists before hand */
|
/** Used to check if the file exists before hand */
|
||||||
export const exists = (
|
export const exists = (
|
||||||
effects: T.Effects,
|
effects: T.Effects,
|
||||||
props: { path: string; volumeId: string }
|
props: { path: string; volumeId: string },
|
||||||
) =>
|
) =>
|
||||||
effects.metadata(props).then(
|
effects.metadata(props).then(
|
||||||
(_) => true,
|
(_) => true,
|
||||||
(_) => false
|
(_) => false,
|
||||||
);
|
);
|
||||||
|
|
||||||
export const isKnownError = (e: unknown): e is T.KnownError =>
|
export const isKnownError = (e: unknown): e is T.KnownError =>
|
||||||
@@ -26,28 +29,40 @@ export const isKnownError = (e: unknown): e is T.KnownError =>
|
|||||||
|
|
||||||
type Cdr<A> = A extends [unknown, ...infer Cdr] ? Cdr : [];
|
type Cdr<A> = A extends [unknown, ...infer Cdr] ? Cdr : [];
|
||||||
|
|
||||||
export const utils = (effects: T.Effects) => ({
|
declare const affine: unique symbol;
|
||||||
|
|
||||||
|
function withAffine<B>() {
|
||||||
|
return {} as { [affine]: B };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const utils = <WrapperData = never>(effects: T.Effects) => ({
|
||||||
readFile: <A>(fileHelper: FileHelper<A>) => fileHelper.read(effects),
|
readFile: <A>(fileHelper: FileHelper<A>) => fileHelper.read(effects),
|
||||||
writeFile: <A>(fileHelper: FileHelper<A>, data: A) =>
|
writeFile: <A>(fileHelper: FileHelper<A>, data: A) =>
|
||||||
fileHelper.write(data, effects),
|
fileHelper.write(data, effects),
|
||||||
exists: (props: { path: string; volumeId: string }) => exists(effects, props),
|
exists: (props: { path: string; volumeId: string }) => exists(effects, props),
|
||||||
nullIfEmpty,
|
nullIfEmpty,
|
||||||
getWrapperData: <A>(
|
getWrapperData: <Path extends string>(
|
||||||
validator: Parser<unknown, A>,
|
path: T.EnsureWrapperDataPath<WrapperData, Path>,
|
||||||
options: {
|
options: {
|
||||||
|
validator?: Parser<unknown, ExtractWrapperData<WrapperData, Path>>;
|
||||||
/** Defaults to what ever the package currently in */
|
/** Defaults to what ever the package currently in */
|
||||||
packageId?: string | undefined;
|
packageId?: string | undefined;
|
||||||
/** JsonPath */
|
} = {},
|
||||||
path?: string | undefined;
|
) => getWrapperData<WrapperData, Path>(effects, path as any, options),
|
||||||
} = {}
|
setWrapperData: <Path extends string | never>(
|
||||||
) => getWrapperData(effects, validator, options),
|
path: T.EnsureWrapperDataPath<WrapperData, Path>,
|
||||||
setWrapperData: <A>(
|
value: ExtractWrapperData<WrapperData, Path>,
|
||||||
value: A,
|
) => effects.setWrapperData<WrapperData, Path>({ value, path: path as any }),
|
||||||
options: { packageId?: string | undefined; path?: string | undefined } = {}
|
|
||||||
) => effects.setWrapperData({ ...options, value }),
|
|
||||||
checkPortListening: checkPortListening.bind(null, effects),
|
checkPortListening: checkPortListening.bind(null, effects),
|
||||||
checkWebUrl: checkWebUrl.bind(null, effects),
|
checkWebUrl: checkWebUrl.bind(null, effects),
|
||||||
localPort: LocalPort.bind(null, effects),
|
localPort: LocalPort.bind(null, effects),
|
||||||
networkBuilder: NetworkBuilder.of.bind(null, effects),
|
networkBuilder: NetworkBuilder.of.bind(null, effects),
|
||||||
torHostName: TorHostname.of.bind(null, effects),
|
torHostName: TorHostname.of.bind(null, effects),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
type NeverPossible = { [affine]: string };
|
||||||
|
export type NoAny<A> = NeverPossible extends A
|
||||||
|
? keyof NeverPossible extends keyof A
|
||||||
|
? never
|
||||||
|
: A
|
||||||
|
: A;
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ function charRange(value = "") {
|
|||||||
*/
|
*/
|
||||||
export function generateDefault(
|
export function generateDefault(
|
||||||
generate: { charset: string; len: number },
|
generate: { charset: string; len: number },
|
||||||
{ random = () => Math.random() } = {}
|
{ random = () => Math.random() } = {},
|
||||||
) {
|
) {
|
||||||
const validCharSets: number[][] = generate.charset
|
const validCharSets: number[][] = generate.charset
|
||||||
.split(",")
|
.split(",")
|
||||||
@@ -152,7 +152,7 @@ export function generateDefault(
|
|||||||
}
|
}
|
||||||
const max = validCharSets.reduce(
|
const max = validCharSets.reduce(
|
||||||
(acc, x) => x.reduce((x, y) => Math.max(x, y), acc),
|
(acc, x) => x.reduce((x, y) => Math.max(x, y), acc),
|
||||||
0
|
0,
|
||||||
);
|
);
|
||||||
let i = 0;
|
let i = 0;
|
||||||
const answer: string[] = Array(generate.len);
|
const answer: string[] = Array(generate.len);
|
||||||
@@ -161,7 +161,7 @@ export function generateDefault(
|
|||||||
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;
|
||||||
answer[i] = String.fromCharCode(nextValue);
|
answer[i] = String.fromCharCode(nextValue);
|
||||||
@@ -186,7 +186,7 @@ export function matchNumberWithRange(range: string) {
|
|||||||
? "any"
|
? "any"
|
||||||
: left === "["
|
: left === "["
|
||||||
? `greaterThanOrEqualTo${leftValue}`
|
? `greaterThanOrEqualTo${leftValue}`
|
||||||
: `greaterThan${leftValue}`
|
: `greaterThan${leftValue}`,
|
||||||
)
|
)
|
||||||
.validate(
|
.validate(
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
@@ -196,7 +196,7 @@ export function matchNumberWithRange(range: string) {
|
|||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
rightValue === "*" ? "any" :
|
rightValue === "*" ? "any" :
|
||||||
right === "]" ? `lessThanOrEqualTo${rightValue}` :
|
right === "]" ? `lessThanOrEqualTo${rightValue}` :
|
||||||
`lessThan${rightValue}`
|
`lessThan${rightValue}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
function withIntegral(parser: Parser<unknown, number>, value: unknown) {
|
function withIntegral(parser: Parser<unknown, number>, value: unknown) {
|
||||||
@@ -219,7 +219,7 @@ 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);
|
||||||
@@ -237,7 +237,7 @@ function defaultRequired<A>(parser: Parser<unknown, A>, value: unknown) {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function guardAll<A extends ValueSpecAny>(
|
export function guardAll<A extends ValueSpecAny>(
|
||||||
value: A
|
value: A,
|
||||||
): Parser<unknown, GuardAll<A>> {
|
): Parser<unknown, GuardAll<A>> {
|
||||||
if (!isType.test(value)) {
|
if (!isType.test(value)) {
|
||||||
return unknown as any;
|
return unknown as any;
|
||||||
@@ -255,7 +255,7 @@ export function guardAll<A extends ValueSpecAny>(
|
|||||||
case "number":
|
case "number":
|
||||||
return defaultRequired(
|
return defaultRequired(
|
||||||
withIntegral(withRange(value), value),
|
withIntegral(withRange(value), value),
|
||||||
value
|
value,
|
||||||
) as any;
|
) as any;
|
||||||
|
|
||||||
case "object":
|
case "object":
|
||||||
@@ -274,7 +274,7 @@ export function guardAll<A extends ValueSpecAny>(
|
|||||||
matches
|
matches
|
||||||
.arrayOf(guardAll(spec as any))
|
.arrayOf(guardAll(spec as any))
|
||||||
.validate((x) => rangeValidate(x.length), "valid length"),
|
.validate((x) => rangeValidate(x.length), "valid length"),
|
||||||
value
|
value,
|
||||||
) as any;
|
) as any;
|
||||||
}
|
}
|
||||||
case "select":
|
case "select":
|
||||||
@@ -282,7 +282,7 @@ export function guardAll<A extends ValueSpecAny>(
|
|||||||
const valueKeys = Object.keys(value.values);
|
const valueKeys = Object.keys(value.values);
|
||||||
return defaultRequired(
|
return defaultRequired(
|
||||||
literals(valueKeys[0], ...valueKeys),
|
literals(valueKeys[0], ...valueKeys),
|
||||||
value
|
value,
|
||||||
) as any;
|
) as any;
|
||||||
}
|
}
|
||||||
return unknown as any;
|
return unknown as any;
|
||||||
@@ -290,19 +290,19 @@ export function guardAll<A extends ValueSpecAny>(
|
|||||||
case "multiselect":
|
case "multiselect":
|
||||||
if (matchValues.test(value)) {
|
if (matchValues.test(value)) {
|
||||||
const maybeAddRangeValidate = <X extends Validator<unknown, B[]>, B>(
|
const maybeAddRangeValidate = <X extends Validator<unknown, B[]>, B>(
|
||||||
x: X
|
x: X,
|
||||||
) => {
|
) => {
|
||||||
if (!matchRange.test(value)) return x;
|
if (!matchRange.test(value)) return x;
|
||||||
return x.validate(
|
return x.validate(
|
||||||
(x) => matchNumberWithRange(value.range).test(x.length),
|
(x) => matchNumberWithRange(value.range).test(x.length),
|
||||||
"validLength"
|
"validLength",
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const valueKeys = Object.keys(value.values);
|
const valueKeys = Object.keys(value.values);
|
||||||
return defaultRequired(
|
return defaultRequired(
|
||||||
maybeAddRangeValidate(arrayOf(literals(valueKeys[0], ...valueKeys))),
|
maybeAddRangeValidate(arrayOf(literals(valueKeys[0], ...valueKeys))),
|
||||||
value
|
value,
|
||||||
) as any;
|
) as any;
|
||||||
}
|
}
|
||||||
return unknown as any;
|
return unknown as any;
|
||||||
@@ -316,8 +316,8 @@ export function guardAll<A extends ValueSpecAny>(
|
|||||||
object({
|
object({
|
||||||
unionSelectKey: literals(name),
|
unionSelectKey: literals(name),
|
||||||
unionValueKey: typeFromProps(spec),
|
unionValueKey: typeFromProps(spec),
|
||||||
})
|
}),
|
||||||
)
|
),
|
||||||
) as any;
|
) as any;
|
||||||
}
|
}
|
||||||
return unknown as any;
|
return unknown as any;
|
||||||
@@ -334,7 +334,7 @@ export function guardAll<A extends ValueSpecAny>(
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function typeFromProps<A extends InputSpec>(
|
export function typeFromProps<A extends InputSpec>(
|
||||||
valueDictionary: A
|
valueDictionary: A,
|
||||||
): Parser<unknown, TypeFromProps<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(
|
||||||
@@ -342,7 +342,7 @@ export function typeFromProps<A extends InputSpec>(
|
|||||||
Object.entries(valueDictionary).map(([key, value]) => [
|
Object.entries(valueDictionary).map(([key, value]) => [
|
||||||
key,
|
key,
|
||||||
guardAll(value),
|
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.charlie30",
|
"version": "0.4.0-lib0.charlie31",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "start-sdk",
|
"name": "start-sdk",
|
||||||
"version": "0.4.0-lib0.charlie30",
|
"version": "0.4.0-lib0.charlie31",
|
||||||
"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.charlie30",
|
"version": "0.4.0-lib0.charlie31",
|
||||||
"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",
|
||||||
@@ -26,6 +26,12 @@
|
|||||||
"ts-matches": "^5.4.1",
|
"ts-matches": "^5.4.1",
|
||||||
"yaml": "^2.2.1"
|
"yaml": "^2.2.1"
|
||||||
},
|
},
|
||||||
|
"prettier": {
|
||||||
|
"trailingComma": "all",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": false
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^29.4.0",
|
"@types/jest": "^29.4.0",
|
||||||
"@types/lodash": "^4.14.191",
|
"@types/lodash": "^4.14.191",
|
||||||
|
|||||||
@@ -6,16 +6,16 @@ import { unionSelectKey } from "../lib/config/configTypes";
|
|||||||
export async function writeConvertedFile(
|
export async function writeConvertedFile(
|
||||||
file: string,
|
file: string,
|
||||||
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,
|
inputData: Promise<any> | any,
|
||||||
{ startSdk = "start-sdk" } = {}
|
{ startSdk = "start-sdk" } = {},
|
||||||
) {
|
) {
|
||||||
const outputLines: string[] = [];
|
const outputLines: string[] = [];
|
||||||
outputLines.push(`
|
outputLines.push(`
|
||||||
@@ -27,10 +27,10 @@ export default async function makeFileContent(
|
|||||||
const configName = newConst("InputSpec", convertInputSpec(data));
|
const configName = newConst("InputSpec", convertInputSpec(data));
|
||||||
const configMatcherName = newConst(
|
const configMatcherName = newConst(
|
||||||
"matchInputSpec",
|
"matchInputSpec",
|
||||||
`${configName}.validator()`
|
`${configName}.validator()`,
|
||||||
);
|
);
|
||||||
outputLines.push(
|
outputLines.push(
|
||||||
`export type InputSpec = typeof ${configMatcherName}._TYPE;`
|
`export type InputSpec = typeof ${configMatcherName}._TYPE;`,
|
||||||
);
|
);
|
||||||
|
|
||||||
return outputLines.join("\n");
|
return outputLines.join("\n");
|
||||||
@@ -62,7 +62,7 @@ export default async function makeFileContent(
|
|||||||
placeholder: value.placeholder || null,
|
placeholder: value.placeholder || null,
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
2
|
2,
|
||||||
)})`;
|
)})`;
|
||||||
}
|
}
|
||||||
return `Value.string(${JSON.stringify(
|
return `Value.string(${JSON.stringify(
|
||||||
@@ -78,7 +78,7 @@ export default async function makeFileContent(
|
|||||||
patternDescription: value["pattern-description"] || null,
|
patternDescription: value["pattern-description"] || null,
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
2
|
2,
|
||||||
)})`;
|
)})`;
|
||||||
}
|
}
|
||||||
case "number": {
|
case "number": {
|
||||||
@@ -95,7 +95,7 @@ export default async function makeFileContent(
|
|||||||
placeholder: value.placeholder || null,
|
placeholder: value.placeholder || null,
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
2
|
2,
|
||||||
)})`;
|
)})`;
|
||||||
}
|
}
|
||||||
case "boolean": {
|
case "boolean": {
|
||||||
@@ -107,7 +107,7 @@ export default async function makeFileContent(
|
|||||||
warning: value.warning || null,
|
warning: value.warning || null,
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
2
|
2,
|
||||||
)})`;
|
)})`;
|
||||||
}
|
}
|
||||||
case "enum": {
|
case "enum": {
|
||||||
@@ -118,7 +118,7 @@ export default async function makeFileContent(
|
|||||||
const values = Object.fromEntries(
|
const values = Object.fromEntries(
|
||||||
Array.from(allValueNames)
|
Array.from(allValueNames)
|
||||||
.filter(string.test)
|
.filter(string.test)
|
||||||
.map((key) => [key, value?.spec?.["value-names"]?.[key] || key])
|
.map((key) => [key, value?.spec?.["value-names"]?.[key] || key]),
|
||||||
);
|
);
|
||||||
return `Value.select(${JSON.stringify(
|
return `Value.select(${JSON.stringify(
|
||||||
{
|
{
|
||||||
@@ -130,13 +130,13 @@ export default async function makeFileContent(
|
|||||||
values,
|
values,
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
2
|
2,
|
||||||
)} as const)`;
|
)} as const)`;
|
||||||
}
|
}
|
||||||
case "object": {
|
case "object": {
|
||||||
const specName = newConst(
|
const specName = newConst(
|
||||||
value.name + "_spec",
|
value.name + "_spec",
|
||||||
convertInputSpec(value.spec)
|
convertInputSpec(value.spec),
|
||||||
);
|
);
|
||||||
return `Value.object({
|
return `Value.object({
|
||||||
name: ${JSON.stringify(value.name || null)},
|
name: ${JSON.stringify(value.name || null)},
|
||||||
@@ -147,7 +147,7 @@ export default async function makeFileContent(
|
|||||||
case "union": {
|
case "union": {
|
||||||
const variants = newConst(
|
const variants = newConst(
|
||||||
value.name + "_variants",
|
value.name + "_variants",
|
||||||
convertVariants(value.variants, value.tag["variant-names"] || {})
|
convertVariants(value.variants, value.tag["variant-names"] || {}),
|
||||||
);
|
);
|
||||||
|
|
||||||
return `Value.union({
|
return `Value.union({
|
||||||
@@ -183,7 +183,7 @@ export default async function makeFileContent(
|
|||||||
warning: value.warning || null,
|
warning: value.warning || null,
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
2
|
2,
|
||||||
)}, ${JSON.stringify({
|
)}, ${JSON.stringify({
|
||||||
masked: value?.spec?.masked || false,
|
masked: value?.spec?.masked || false,
|
||||||
placeholder: value?.spec?.placeholder || null,
|
placeholder: value?.spec?.placeholder || null,
|
||||||
@@ -201,7 +201,7 @@ export default async function makeFileContent(
|
|||||||
warning: value.warning || null,
|
warning: value.warning || null,
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
2
|
2,
|
||||||
)}, ${JSON.stringify({
|
)}, ${JSON.stringify({
|
||||||
range: value?.spec?.range || null,
|
range: value?.spec?.range || null,
|
||||||
integral: value?.spec?.integral || false,
|
integral: value?.spec?.integral || false,
|
||||||
@@ -212,7 +212,7 @@ export default async function makeFileContent(
|
|||||||
case "enum": {
|
case "enum": {
|
||||||
const allValueNames = new Set(
|
const allValueNames = new Set(
|
||||||
...(value?.spec?.["values"] || []),
|
...(value?.spec?.["values"] || []),
|
||||||
...Object.keys(value?.spec?.["value-names"] || {})
|
...Object.keys(value?.spec?.["value-names"] || {}),
|
||||||
);
|
);
|
||||||
const values = Object.fromEntries(
|
const values = Object.fromEntries(
|
||||||
Array.from(allValueNames)
|
Array.from(allValueNames)
|
||||||
@@ -220,7 +220,7 @@ export default async function makeFileContent(
|
|||||||
.map((key: string) => [
|
.map((key: string) => [
|
||||||
key,
|
key,
|
||||||
value?.spec?.["value-names"]?.[key] || key,
|
value?.spec?.["value-names"]?.[key] || key,
|
||||||
])
|
]),
|
||||||
);
|
);
|
||||||
return `Value.multiselect(${JSON.stringify(
|
return `Value.multiselect(${JSON.stringify(
|
||||||
{
|
{
|
||||||
@@ -232,13 +232,13 @@ export default async function makeFileContent(
|
|||||||
values,
|
values,
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
2
|
2,
|
||||||
)})`;
|
)})`;
|
||||||
}
|
}
|
||||||
case "object": {
|
case "object": {
|
||||||
const specName = newConst(
|
const specName = newConst(
|
||||||
value.name + "_spec",
|
value.name + "_spec",
|
||||||
convertInputSpec(value.spec.spec)
|
convertInputSpec(value.spec.spec),
|
||||||
);
|
);
|
||||||
return `List.obj({
|
return `List.obj({
|
||||||
name: ${JSON.stringify(value.name || null)},
|
name: ${JSON.stringify(value.name || null)},
|
||||||
@@ -257,8 +257,8 @@ export default async function makeFileContent(
|
|||||||
value.name + "_variants",
|
value.name + "_variants",
|
||||||
convertVariants(
|
convertVariants(
|
||||||
value.spec.variants,
|
value.spec.variants,
|
||||||
value.spec["variant-names"] || {}
|
value.spec["variant-names"] || {},
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
const unionValueName = newConst(
|
const unionValueName = newConst(
|
||||||
value.name + "_union",
|
value.name + "_union",
|
||||||
@@ -266,13 +266,13 @@ export default async function makeFileContent(
|
|||||||
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)},
|
||||||
required: ${JSON.stringify(!(value?.spec?.tag?.nullable || false))},
|
required: ${JSON.stringify(!(value?.spec?.tag?.nullable || false))},
|
||||||
default: ${JSON.stringify(value?.spec?.default || null)},
|
default: ${JSON.stringify(value?.spec?.default || null)},
|
||||||
}, ${variants})
|
}, ${variants})
|
||||||
`
|
`,
|
||||||
);
|
);
|
||||||
const listConfig = newConst(
|
const listConfig = newConst(
|
||||||
value.name + "_list_config",
|
value.name + "_list_config",
|
||||||
@@ -280,7 +280,7 @@ export default async function makeFileContent(
|
|||||||
Config.of({
|
Config.of({
|
||||||
"union": ${unionValueName}
|
"union": ${unionValueName}
|
||||||
})
|
})
|
||||||
`
|
`,
|
||||||
);
|
);
|
||||||
return `List.obj({
|
return `List.obj({
|
||||||
name:${JSON.stringify(value.name || null)},
|
name:${JSON.stringify(value.name || null)},
|
||||||
@@ -300,7 +300,7 @@ export default async function makeFileContent(
|
|||||||
|
|
||||||
function convertVariants(
|
function convertVariants(
|
||||||
variants: Record<string, unknown>,
|
variants: Record<string, unknown>,
|
||||||
variantNames: Record<string, string>
|
variantNames: Record<string, 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)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user