chore: Getting in the action builder

This commit is contained in:
BluJ
2023-04-24 12:19:12 -06:00
parent 72a7b02814
commit 0e3bf38ed4
8 changed files with 143 additions and 26 deletions

View File

@@ -0,0 +1,43 @@
import {
ActionMetaData,
ActionResult,
Effects,
ExportedAction,
} from "../types";
import { Utils, utils } from "../util";
export class CreatedAction<WrapperData, Input> {
private constructor(
readonly metaData: ActionMetaData,
readonly fn: (options: {
effects: Effects;
utils: Utils<WrapperData>;
input: Input;
}) => Promise<ActionResult>,
) {}
static of<WrapperData, Input>(
metaData: ActionMetaData,
fn: (options: {
effects: Effects;
utils: Utils<WrapperData>;
input: Input;
}) => Promise<ActionResult>,
) {
return new CreatedAction<WrapperData, Input>(metaData, fn);
}
exportedAction: ExportedAction = ({ effects, input }) => {
return this.fn({
effects,
utils: utils<WrapperData>(effects),
input: input as Input,
});
};
async exportAction(effects: Effects) {
await effects.exportAction(this.metaData);
}
}
export const createAction = CreatedAction.of;

3
lib/actions/index.ts Normal file
View File

@@ -0,0 +1,3 @@
export { CreatedAction, createAction } from "./createAction";
export { setupActions } from "./setupActions";

View File

@@ -0,0 +1,20 @@
import { Effects, ExpectedExports, ExportedAction } from "../types";
import { ActionMetaData } from "../types";
import { CreatedAction } from "./createAction";
export function setupActions(...createdActions: CreatedAction<any, any>[]) {
const actions: Record<string, ExportedAction> = {};
for (const action of createdActions) {
actions[action.metaData.id] = action.exportedAction;
}
const manifestActions = async (effects: Effects) => {
for (const action of createdActions) {
action.exportAction(effects);
}
};
return {
actions,
manifestActions,
};
}

View File

@@ -1,5 +1,5 @@
export * as configBuilder from "./builder";
export { setupConfigExports } from "./setupConfigExports";
export { setupConfig as setupConfigExports } from "./setupConfig";
export { specToBuilder, specToBuilderFile } from "./specToBuilder";
export * as dependencies from "./dependencies";

View File

@@ -22,7 +22,7 @@ export type DependenciesFn<A> = (options: {
* @param options
* @returns
*/
export function setupConfigExports<A extends InputSpec>(
export function setupConfig<A extends InputSpec>(
spec: Config<A>,
write: Write<A>,
read: Read<A>,
@@ -51,4 +51,4 @@ export function setupConfigExports<A extends InputSpec>(
};
}
export default setupConfigExports;
export default setupConfig;

View File

@@ -13,3 +13,4 @@ export * as util from "./util";
export * as YAML from "yaml";
export * as properties from "./properties";
export * as autoconfig from "./autoconfig";
export * as actions from "./actions";

View File

@@ -2,6 +2,11 @@ export * as configTypes from "./config/configTypes";
import { InputSpec } from "./config/configTypes";
import { ActionReceipt } from "./init";
export type ExportedAction = (options: {
effects: Effects;
input?: Record<string, unknown>;
}) => Promise<ActionResult>;
export namespace ExpectedExports {
version: 1;
/** 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. */
@@ -42,11 +47,8 @@ export namespace ExpectedExports {
* 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]: (options: {
effects: Effects;
input?: Record<string, unknown>;
}) => Promise<ActionResult>;
export type actions = {
[id: string]: ExportedAction;
};
/**
@@ -145,6 +147,18 @@ export type DaemonReturned = {
term(): Promise<void>;
};
export type ActionMetaData = {
name: string;
description: string;
id: string;
input: null | InputSpec;
runningOnly: boolean;
/**
* So the ordering of the actions is by alphabetical order of the group, then followed by the alphabetical of the actions
*/
group?: string;
};
/** Used to reach out from the pure js runtime */
export type Effects = {
/** Usable when not sandboxed */
@@ -335,17 +349,7 @@ export type Effects = {
*
* @param options
*/
exportAction(options: {
name: string;
description: string;
id: string;
input: null | InputSpec;
runningOnly: boolean;
/**
* So the ordering of the actions is by alphabetical order of the group, then followed by the alphabetical of the actions
*/
group?: string;
}): Promise<void & ActionReceipt>;
exportAction(options: ActionMetaData): Promise<void & ActionReceipt>;
/**
* Remove an action that was exported. Used problably during main or during setConfig.
*/
@@ -432,7 +436,6 @@ export type MigrationRes = {
};
export type ActionResult = {
version: "0";
message: string;
value?: string;
copyable: boolean;

View File

@@ -2,8 +2,12 @@ import { Parser } from "ts-matches";
import * as T from "../types";
import FileHelper from "./fileHelper";
import nullIfEmpty from "./nullIfEmpty";
import { getWrapperData } from "./getWrapperData";
import { checkPortListening, checkWebUrl } from "../health/checkFns";
import { WrapperData, getWrapperData } from "./getWrapperData";
import {
CheckResult,
checkPortListening,
checkWebUrl,
} from "../health/checkFns";
import { LocalPort, NetworkBuilder, TorHostname } from "../mainFn";
import { ExtractWrapperData } from "../types";
@@ -35,7 +39,50 @@ function withAffine<B>() {
return {} as { [affine]: B };
}
export const utils = <WrapperData = never>(effects: T.Effects) => ({
export type WrapperDataOptionals<WrapperData, Path extends string> = {
validator?: Parser<unknown, ExtractWrapperData<WrapperData, Path>>;
/** Defaults to what ever the package currently in */
packageId?: string | undefined;
};
export type Utils<WD> = {
readFile: <A>(fileHelper: FileHelper<A>) => ReturnType<FileHelper<A>["read"]>;
writeFile: <A>(
fileHelper: FileHelper<A>,
data: A,
) => ReturnType<FileHelper<A>["write"]>;
getWrapperData: <Path extends string>(
path: T.EnsureWrapperDataPath<WD, Path>,
options?: WrapperDataOptionals<WD, Path>,
) => WrapperData<WD, Path>;
setWrapperData: <Path extends string | never>(
path: T.EnsureWrapperDataPath<WD, Path>,
value: ExtractWrapperData<WD, Path>,
) => Promise<void>;
checkPortListening(
port: number,
options?: {
error?: string;
message?: string;
},
): Promise<CheckResult>;
checkWebUrl(
url: string,
options?: {
timeout?: number;
successMessage?: string;
errorMessage?: string;
},
): Promise<CheckResult>;
localPort: (id: string) => LocalPort;
networkBuilder: () => NetworkBuilder;
torHostName: (id: string) => TorHostname;
exists: (props: { path: string; volumeId: string }) => Promise<boolean>;
nullIfEmpty: typeof nullIfEmpty;
};
export const utils = <WrapperData = never>(
effects: T.Effects,
): Utils<WrapperData> => ({
readFile: <A>(fileHelper: FileHelper<A>) => fileHelper.read(effects),
writeFile: <A>(fileHelper: FileHelper<A>, data: A) =>
fileHelper.write(data, effects),
@@ -55,9 +102,9 @@ export const utils = <WrapperData = never>(effects: T.Effects) => ({
) => effects.setWrapperData<WrapperData, Path>({ value, path: path as any }),
checkPortListening: checkPortListening.bind(null, effects),
checkWebUrl: checkWebUrl.bind(null, effects),
localPort: LocalPort.bind(null, effects),
networkBuilder: NetworkBuilder.of.bind(null, effects),
torHostName: TorHostname.of.bind(null, effects),
localPort: (id: string) => new LocalPort(effects, id),
networkBuilder: () => NetworkBuilder.of(effects),
torHostName: (id: string) => TorHostname.of(effects, id),
});
type NeverPossible = { [affine]: string };