mirror of
https://github.com/Start9Labs/start-sdk.git
synced 2026-03-26 02:11:56 +00:00
chore: Update the types
This commit is contained in:
@@ -35,7 +35,12 @@ const username = Value.string({
|
||||
```
|
||||
*/
|
||||
export class Value<A extends ValueSpec> extends IBuilder<A> {
|
||||
static boolean(a: { name: string; description?: string | null; warning?: string | null; default?: boolean | null }) {
|
||||
static boolean(a: {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
warning?: string | null;
|
||||
default?: boolean | null;
|
||||
}) {
|
||||
return new Value({
|
||||
description: null,
|
||||
warning: null,
|
||||
@@ -160,7 +165,9 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
|
||||
spec,
|
||||
});
|
||||
}
|
||||
static union<V extends Variants<{ [key: string]: { name: string; spec: InputSpec } }>>(
|
||||
static union<
|
||||
V extends Variants<{ [key: string]: { name: string; spec: InputSpec } }>
|
||||
>(
|
||||
a: {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
|
||||
@@ -8,9 +8,14 @@ export async function specToBuilderFile(
|
||||
inputData: Promise<InputSpecRaw> | InputSpecRaw,
|
||||
options: Parameters<typeof specToBuilder>[1]
|
||||
) {
|
||||
await fs.writeFile(file, await specToBuilder(inputData, options), (err) => console.error(err));
|
||||
await fs.writeFile(file, await specToBuilder(inputData, options), (err) =>
|
||||
console.error(err)
|
||||
);
|
||||
}
|
||||
export async function specToBuilder(inputData: Promise<InputSpecRaw> | InputSpecRaw, { startSdk = "start-sdk" } = {}) {
|
||||
export async function specToBuilder(
|
||||
inputData: Promise<InputSpecRaw> | InputSpecRaw,
|
||||
{ startSdk = "start-sdk" } = {}
|
||||
) {
|
||||
const outputLines: string[] = [];
|
||||
outputLines.push(`
|
||||
import {Config, Value, List, Variants} from '${startSdk}/config/builder';
|
||||
@@ -19,8 +24,13 @@ export async function specToBuilder(inputData: Promise<InputSpecRaw> | InputSpec
|
||||
|
||||
const namedConsts = new Set(["Config", "Value", "List"]);
|
||||
const configName = newConst("InputSpec", convertInputSpec(data));
|
||||
const configMatcherName = newConst("matchInputSpec", `${configName}.validator()`);
|
||||
outputLines.push(`export type InputSpec = typeof ${configMatcherName}._TYPE;`);
|
||||
const configMatcherName = newConst(
|
||||
"matchInputSpec",
|
||||
`${configName}.validator()`
|
||||
);
|
||||
outputLines.push(
|
||||
`export type InputSpec = typeof ${configMatcherName}._TYPE;`
|
||||
);
|
||||
|
||||
return outputLines.join("\n");
|
||||
|
||||
@@ -61,7 +71,10 @@ export async function specToBuilder(inputData: Promise<InputSpecRaw> | InputSpec
|
||||
}
|
||||
case "union": {
|
||||
const { variants, type, ...rest } = value;
|
||||
const variantVariable = newConst(value.name + "_variants", convertVariants(variants));
|
||||
const variantVariable = newConst(
|
||||
value.name + "_variants",
|
||||
convertVariants(variants)
|
||||
);
|
||||
|
||||
return `Value.union(${JSON.stringify(rest)}, ${variantVariable})`;
|
||||
}
|
||||
@@ -116,7 +129,10 @@ export async function specToBuilder(inputData: Promise<InputSpecRaw> | InputSpec
|
||||
})})`;
|
||||
}
|
||||
case "object": {
|
||||
const specName = newConst(value.name + "_spec", convertInputSpec(spec.spec));
|
||||
const specName = newConst(
|
||||
value.name + "_spec",
|
||||
convertInputSpec(spec.spec)
|
||||
);
|
||||
return `List.obj({
|
||||
name: ${JSON.stringify(value.name || null)},
|
||||
range: ${JSON.stringify(value.range || null)},
|
||||
|
||||
@@ -3,5 +3,4 @@ export * as trigger from "./trigger";
|
||||
|
||||
export { TriggerInput } from "./trigger/TriggerInput";
|
||||
export { HealthReceipt } from "./HealthReceipt";
|
||||
export { readyCheck } from "./readyCheck";
|
||||
export { ReadyProof } from "./ReadyProof";
|
||||
export { ReadyProof } from "../mainFn/ReadyProof";
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import { InterfaceReceipt } from "../mainFn/interfaceReceipt";
|
||||
import { Daemon, Effects } from "../types";
|
||||
import { CheckResult } from "./checkFns/CheckResult";
|
||||
import { ReadyReceipt } from "./ReadyProof";
|
||||
import { HealthReceipt } from "./HealthReceipt";
|
||||
import { Trigger } from "./trigger";
|
||||
import { TriggerInput } from "./trigger/TriggerInput";
|
||||
import { defaultTrigger } from "./trigger/defaultTrigger";
|
||||
|
||||
function readReciptOf<A extends { daemon: Daemon }>(a: A) {
|
||||
return a as A & ReadyReceipt;
|
||||
}
|
||||
export function readyCheck(o: {
|
||||
effects: Effects;
|
||||
started(onTerm: () => void): null;
|
||||
interfaceReceipt: InterfaceReceipt;
|
||||
healthReceipts: Iterable<HealthReceipt>;
|
||||
daemonReceipt: Daemon;
|
||||
name: string;
|
||||
trigger?: Trigger;
|
||||
fn(): Promise<CheckResult> | CheckResult;
|
||||
onFirstSuccess?: () => () => Promise<unknown> | unknown;
|
||||
}) {
|
||||
new Promise(async () => {
|
||||
const trigger = (o.trigger ?? defaultTrigger)();
|
||||
let currentValue: TriggerInput = {
|
||||
lastResult: null,
|
||||
hadSuccess: false,
|
||||
};
|
||||
for (
|
||||
let res = await trigger.next(currentValue);
|
||||
!res.done;
|
||||
res = await trigger.next(currentValue)
|
||||
) {
|
||||
try {
|
||||
const { status, message } = await o.fn();
|
||||
if (!currentValue.hadSuccess) {
|
||||
await o.started(o?.onFirstSuccess ?? (() => o.daemonReceipt.term()));
|
||||
}
|
||||
await o.effects.setHealth({
|
||||
name: o.name,
|
||||
status,
|
||||
message,
|
||||
});
|
||||
currentValue.hadSuccess = true;
|
||||
currentValue.lastResult = "success";
|
||||
} catch (_) {
|
||||
currentValue.lastResult = "failure";
|
||||
}
|
||||
}
|
||||
});
|
||||
return readReciptOf({
|
||||
daemon: o.daemonReceipt,
|
||||
});
|
||||
}
|
||||
79
lib/mainFn/Daemons.ts
Normal file
79
lib/mainFn/Daemons.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { HealthReceipt, ReadyProof } from "../health";
|
||||
import { CheckResult } from "../health/checkFns";
|
||||
import { Trigger } from "../health/trigger";
|
||||
import { Effects, ValidIfNoStupidEscape } from "../types";
|
||||
import { InterfaceReceipt } from "./interfaceReceipt";
|
||||
import { RunningMainRet } from "./RunningMainRet";
|
||||
type Daemon<
|
||||
Ids extends string | never,
|
||||
Command extends string,
|
||||
Id extends string
|
||||
> = {
|
||||
id: Id;
|
||||
command: ValidIfNoStupidEscape<Command> | [string, ...string[]];
|
||||
|
||||
ready: {
|
||||
display: null | {
|
||||
name: "Websocket Live";
|
||||
message: "The websocket is live";
|
||||
};
|
||||
fn: () => Promise<CheckResult> | CheckResult;
|
||||
trigger?: Trigger;
|
||||
};
|
||||
requires?: Exclude<Ids, Id>[];
|
||||
};
|
||||
|
||||
const todo = <A>(): A => {
|
||||
throw new Error("TODO");
|
||||
};
|
||||
/**
|
||||
* Used during the main of a function, it allows us to describe and ensure a set of daemons are running.
|
||||
* With the dependency, we are using this like an init system, where we can ensure that a daemon is running
|
||||
* The return type of this is used in the runningMain
|
||||
```ts
|
||||
Daemons.with({
|
||||
effects,
|
||||
started,
|
||||
interfacesReceipt,
|
||||
})
|
||||
.addDaemon({
|
||||
id: "nostr",
|
||||
command: "./nostr-rs-relay --db /data",
|
||||
ready: {
|
||||
display: {
|
||||
name: "Websocket Live",
|
||||
message: "The websocket is live",
|
||||
},
|
||||
fn: () => checkPortListening(effects, 8080),
|
||||
},
|
||||
})
|
||||
.build()
|
||||
```
|
||||
*/
|
||||
export class Daemons<Ids extends string | never> {
|
||||
private constructor(
|
||||
readonly effects: Effects,
|
||||
readonly started: (onTerm: () => void) => null,
|
||||
readonly daemons?: Daemon<Ids, "command", never>[]
|
||||
) {}
|
||||
|
||||
static with(config: {
|
||||
effects: Effects;
|
||||
started: (onTerm: () => void) => null;
|
||||
interfaceReceipt: InterfaceReceipt;
|
||||
healthReceipts: HealthReceipt[];
|
||||
}) {
|
||||
return new Daemons<never>(config.effects, config.started);
|
||||
}
|
||||
addDaemon<Id extends string, Command extends string>(
|
||||
newDaemon: Daemon<Ids, Command, Id>
|
||||
) {
|
||||
const daemons = [...(this?.daemons ?? [])];
|
||||
daemons.push(newDaemon as any);
|
||||
return new Daemons<Ids | Id>(this.effects, this.started, daemons);
|
||||
}
|
||||
|
||||
build() {
|
||||
return todo<RunningMainRet>();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Daemon } from "../types";
|
||||
import { ReadyProof } from "../health/ReadyProof";
|
||||
import { ReadyProof } from "./ReadyProof";
|
||||
|
||||
export type RunningMainRet = {
|
||||
[ReadyProof]: never;
|
||||
|
||||
@@ -9,6 +9,18 @@ export { Origin } from "./Origin";
|
||||
export { TorBinding } from "./TorBinding";
|
||||
export { TorHostname } from "./TorHostname";
|
||||
|
||||
export { Daemons } from "./Daemons";
|
||||
|
||||
/**
|
||||
* Used to ensure that the main function is running with the valid proofs.
|
||||
* We first do the folowing order of things
|
||||
* 1. We get the interfaces
|
||||
* 2. We setup all the commands to setup the system
|
||||
* 3. We create the health checks
|
||||
* 4. We setup the daemons init system
|
||||
* @param fn
|
||||
* @returns
|
||||
*/
|
||||
export const runningMain: (
|
||||
fn: (o: {
|
||||
effects: Effects;
|
||||
|
||||
@@ -113,14 +113,16 @@ describe("values", () => {
|
||||
testOutput<typeof validator._TYPE, Array<"a" | "b">>()(null);
|
||||
});
|
||||
test("object", () => {
|
||||
const value = Value.object({
|
||||
const value = Value.object(
|
||||
{
|
||||
name: "Testing",
|
||||
spec: Config.of({
|
||||
},
|
||||
Config.of({
|
||||
a: Value.boolean({
|
||||
name: "test",
|
||||
}),
|
||||
}),
|
||||
});
|
||||
})
|
||||
);
|
||||
const validator = value.validator();
|
||||
validator.unsafeCast({ a: true });
|
||||
testOutput<typeof validator._TYPE, { a: boolean }>()(null);
|
||||
|
||||
25
lib/types.ts
25
lib/types.ts
@@ -79,7 +79,7 @@ export namespace ExpectedExports {
|
||||
export type TimeMs = number;
|
||||
export type VersionString = string;
|
||||
|
||||
type ValidIfNoStupidEscape<A> = A extends
|
||||
export type ValidIfNoStupidEscape<A> = A extends
|
||||
| `${string}'"'"'${string}`
|
||||
| `${string}\\"${string}`
|
||||
? never
|
||||
@@ -236,23 +236,20 @@ export type Effects = {
|
||||
callback?: (config: unknown, previousConfig: unknown) => void;
|
||||
}): Promise<unknown>;
|
||||
|
||||
/** Get the address for another service for local internet*/
|
||||
getServiceLocalAddress(options: {
|
||||
packageId: string;
|
||||
interfaceName: string;
|
||||
}): Promise<string>;
|
||||
getLocalHostname(): Promise<string>;
|
||||
getIPHostname(): Promise<string>;
|
||||
/** Get the address for another service for tor interfaces */
|
||||
getServiceTorAddress(options: {
|
||||
packageId: string;
|
||||
interfaceName: string;
|
||||
}): Promise<string>;
|
||||
getServiceTorHostname(
|
||||
interfaceId: string,
|
||||
packageId?: string
|
||||
): Promise<string>;
|
||||
/**
|
||||
* Get the port address for another service
|
||||
*/
|
||||
getServicePortForward(options: {
|
||||
packageId: string;
|
||||
internalPort: number;
|
||||
}): Promise<string>;
|
||||
getServicePortForward(
|
||||
internalPort: number,
|
||||
packageId?: string
|
||||
): Promise<number>;
|
||||
|
||||
/** When we want to create a link in the front end interfaces, and example is
|
||||
* exposing a url to view a web service
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "start-sdk",
|
||||
"version": "0.4.0-lib0.charlie10",
|
||||
"version": "0.4.0-lib0.charlie11",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "start-sdk",
|
||||
"version": "0.4.0-lib0.charlie10",
|
||||
"version": "0.4.0-lib0.charlie11",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@iarna/toml": "^2.2.5",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "start-sdk",
|
||||
"version": "0.4.0-lib0.charlie10",
|
||||
"version": "0.4.0-lib0.charlie11",
|
||||
"description": "For making the patterns that are wanted in making services for the startOS.",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
|
||||
@@ -8,10 +8,15 @@ export async function writeConvertedFile(
|
||||
inputData: Promise<any> | any,
|
||||
options: Parameters<typeof makeFileContent>[1]
|
||||
) {
|
||||
await fs.writeFile(file, await makeFileContent(inputData, options), (err) => console.error(err));
|
||||
await fs.writeFile(file, await makeFileContent(inputData, options), (err) =>
|
||||
console.error(err)
|
||||
);
|
||||
}
|
||||
|
||||
export default async function makeFileContent(inputData: Promise<any> | any, { startSdk = "start-sdk" } = {}) {
|
||||
export default async function makeFileContent(
|
||||
inputData: Promise<any> | any,
|
||||
{ startSdk = "start-sdk" } = {}
|
||||
) {
|
||||
const outputLines: string[] = [];
|
||||
outputLines.push(`
|
||||
import {Config, Value, List, Variants} from '${startSdk}/config/builder';
|
||||
@@ -20,8 +25,13 @@ export default async function makeFileContent(inputData: Promise<any> | any, { s
|
||||
|
||||
const namedConsts = new Set(["Config", "Value", "List"]);
|
||||
const configName = newConst("InputSpec", convertInputSpec(data));
|
||||
const configMatcherName = newConst("matchInputSpec", `${configName}.validator()`);
|
||||
outputLines.push(`export type InputSpec = typeof ${configMatcherName}._TYPE;`);
|
||||
const configMatcherName = newConst(
|
||||
"matchInputSpec",
|
||||
`${configName}.validator()`
|
||||
);
|
||||
outputLines.push(
|
||||
`export type InputSpec = typeof ${configMatcherName}._TYPE;`
|
||||
);
|
||||
|
||||
return outputLines.join("\n");
|
||||
|
||||
@@ -101,7 +111,10 @@ export default async function makeFileContent(inputData: Promise<any> | any, { s
|
||||
)})`;
|
||||
}
|
||||
case "enum": {
|
||||
const allValueNames = new Set([...(value?.["values"] || []), ...Object.keys(value?.["value-names"] || {})]);
|
||||
const allValueNames = new Set([
|
||||
...(value?.["values"] || []),
|
||||
...Object.keys(value?.["value-names"] || {}),
|
||||
]);
|
||||
const values = Object.fromEntries(
|
||||
Array.from(allValueNames)
|
||||
.filter(string.test)
|
||||
@@ -121,12 +134,14 @@ export default async function makeFileContent(inputData: Promise<any> | any, { s
|
||||
)} as const)`;
|
||||
}
|
||||
case "object": {
|
||||
const specName = newConst(value.name + "_spec", convertInputSpec(value.spec));
|
||||
const specName = newConst(
|
||||
value.name + "_spec",
|
||||
convertInputSpec(value.spec)
|
||||
);
|
||||
return `Value.object({
|
||||
name: ${JSON.stringify(value.name || null)},
|
||||
description: ${JSON.stringify(value.description || null)},
|
||||
warning: ${JSON.stringify(value.warning || null)},
|
||||
default: ${JSON.stringify(value.default || null)},
|
||||
}, ${specName})`;
|
||||
}
|
||||
case "union": {
|
||||
@@ -157,7 +172,9 @@ export default async function makeFileContent(inputData: Promise<any> | any, { s
|
||||
function convertList(value: any) {
|
||||
switch (value.subtype) {
|
||||
case "string": {
|
||||
return `List.${value?.spec?.textarea === true ? "textarea" : "string"}(${JSON.stringify(
|
||||
return `List.${
|
||||
value?.spec?.textarea === true ? "textarea" : "string"
|
||||
}(${JSON.stringify(
|
||||
{
|
||||
name: value.name || null,
|
||||
range: value.range || null,
|
||||
@@ -216,7 +233,10 @@ export default async function makeFileContent(inputData: Promise<any> | any, { s
|
||||
)})`;
|
||||
}
|
||||
case "object": {
|
||||
const specName = newConst(value.name + "_spec", convertInputSpec(value.spec.spec));
|
||||
const specName = newConst(
|
||||
value.name + "_spec",
|
||||
convertInputSpec(value.spec.spec)
|
||||
);
|
||||
return `List.obj({
|
||||
name: ${JSON.stringify(value.name || null)},
|
||||
range: ${JSON.stringify(value.range || null)},
|
||||
@@ -232,14 +252,19 @@ export default async function makeFileContent(inputData: Promise<any> | any, { s
|
||||
case "union": {
|
||||
const variants = newConst(
|
||||
value.name + "_variants",
|
||||
convertVariants(value.spec.variants, value.spec["variant-names"] || {})
|
||||
convertVariants(
|
||||
value.spec.variants,
|
||||
value.spec["variant-names"] || {}
|
||||
)
|
||||
);
|
||||
const unionValueName = newConst(
|
||||
value.name + "_union",
|
||||
`
|
||||
Value.union({
|
||||
name: ${JSON.stringify(value?.spec?.tag?.name || null)},
|
||||
description: ${JSON.stringify(value?.spec?.tag?.description || null)},
|
||||
description: ${JSON.stringify(
|
||||
value?.spec?.tag?.description || null
|
||||
)},
|
||||
warning: ${JSON.stringify(value?.spec?.tag?.warning || null)},
|
||||
required: ${JSON.stringify(!(value?.spec?.tag?.nullable || false))},
|
||||
default: ${JSON.stringify(value?.spec?.default || null)},
|
||||
@@ -270,11 +295,16 @@ export default async function makeFileContent(inputData: Promise<any> | any, { s
|
||||
throw new Error(`Unknown subtype "${value.subtype}"`);
|
||||
}
|
||||
|
||||
function convertVariants(variants: Record<string, unknown>, variantNames: Record<string, string>): string {
|
||||
function convertVariants(
|
||||
variants: Record<string, unknown>,
|
||||
variantNames: Record<string, string>
|
||||
): string {
|
||||
let answer = "Variants.of({";
|
||||
for (const [key, value] of Object.entries(variants)) {
|
||||
const variantSpec = newConst(key, convertInputSpec(value));
|
||||
answer += `"${key}": {name: "${variantNames[key] || key}", spec: ${variantSpec}},`;
|
||||
answer += `"${key}": {name: "${
|
||||
variantNames[key] || key
|
||||
}", spec: ${variantSpec}},`;
|
||||
}
|
||||
return `${answer}})`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user