diff --git a/lib/config/builder/config.ts b/lib/config/builder/config.ts index 7ba7ccb..8824759 100644 --- a/lib/config/builder/config.ts +++ b/lib/config/builder/config.ts @@ -501,10 +501,8 @@ export class Config extends IBuilder { } static of }>(spec: B) { - // deno-lint-ignore no-explicit-any const answer: { [K in keyof B]: BuilderExtract } = {} as any; for (const key in spec) { - // deno-lint-ignore no-explicit-any answer[key] = spec[key].build() as any; } return new Config(answer); diff --git a/lib/config/builder/variants.ts b/lib/config/builder/variants.ts index 7001557..0b61b0a 100644 --- a/lib/config/builder/variants.ts +++ b/lib/config/builder/variants.ts @@ -39,17 +39,15 @@ import { Config } from "."; ``` */ export class Variants< - A extends { [key: string]: ConfigSpec } + A extends { [key: string]: ConfigSpec }, > extends IBuilder { static of< A extends { [key: string]: Config; - } + }, >(a: A) { - // deno-lint-ignore no-explicit-any const variants: { [K in keyof A]: BuilderExtract } = {} as any; for (const key in a) { - // deno-lint-ignore no-explicit-any variants[key] = a[key].build() as any; } return new Variants(variants); @@ -60,14 +58,14 @@ export class Variants< } static withVariant( key: K, - value: Config + value: Config, ) { return Variants.empty().withVariant(key, value); } withVariant( key: K, - value: Config + value: Config, ) { return new Variants({ ...this.a, diff --git a/lib/config/setup_config_export.ts b/lib/config/setup_config_export.ts index d4d8da2..5a3d702 100644 --- a/lib/config/setup_config_export.ts +++ b/lib/config/setup_config_export.ts @@ -4,6 +4,13 @@ import { ConfigSpec } from "../types/config-types"; import { nullIfEmpty, okOf } from "../util"; import { TypeFromProps } from "../util/propertiesMatcher"; +/** + * We want to setup a config export with a get and set, this + * is going to be the default helper to setup config, because it will help + * enforce that we have a spec, write, and reading. + * @param options + * @returns + */ export function setupConfigExports(options: { spec: Config; dependsOn: DependsOn; diff --git a/lib/health/healthRunner.ts b/lib/health/healthRunner.ts index 4311cfc..13b594c 100644 --- a/lib/health/healthRunner.ts +++ b/lib/health/healthRunner.ts @@ -3,7 +3,7 @@ import { object, string } from "ts-matches"; export type HealthCheck = ( effects: Types.Effects, - dateMs: number + dateMs: number, ) => Promise; export type HealthResult = | { success: string } @@ -23,16 +23,23 @@ function safelyStringify(e: unknown) { } async function timeoutHealth( effects: Types.Effects, - timeMs: number + timeMs: number, ): Promise { await effects.sleep(timeMs); return { failure: "Timed out " }; } +/** + * Health runner is usually used during the main function, and will be running in a loop. + * This health check then will be run every intervalS seconds. + * The return needs a create() + * then from there we need a start(). + * The stop function is used to stop the health check. + */ export default function healthRunner( name: string, fn: HealthCheck, - { defaultIntervalS = 60 } = {} + { defaultIntervalS = 60 } = {}, ) { return { create(effects: Types.Effects, defaultIntervalCreatedS = defaultIntervalS) { diff --git a/lib/health/util.ts b/lib/health/util.ts index 0fed391..fdc227a 100644 --- a/lib/health/util.ts +++ b/lib/health/util.ts @@ -1,15 +1,23 @@ import { Effects, ResultType } from "../types"; -import { error, errorCode, isKnownError, ok } from "../util"; +import { error, errorCode, isKnownError, ok, okOf } from "../util"; +import { HealthResult } from "./healthRunner"; + +/** + * This is a helper function to check if a web url is reachable. + * @param url + * @param createSuccess + * @returns + */ export const checkWebUrl: ( - url: string + url: string, + createSuccess?: null | ((response?: string | null) => string), ) => ( effects: Effects, - duration: number -) => Promise> = (url) => { + duration: number, +) => Promise> = (url, createSuccess = null) => { return async (effects, duration) => { let errorValue; if ( - // deno-lint-ignore no-cond-assign (errorValue = guardDurationAboveMinimum({ duration, minimumTime: 5000 })) ) { return errorValue; @@ -17,7 +25,12 @@ export const checkWebUrl: ( return await effects .fetch(url) - .then((_) => ok) + .then((x) => + okOf({ + success: + createSuccess?.(x.body) ?? `Successfully fetched URL: ${url}`, + }), + ) .catch((e) => { effects.warn(`Error while fetching URL: ${url}`); effects.error(JSON.stringify(e)); @@ -27,17 +40,38 @@ export const checkWebUrl: ( }; }; +/** + * Running a health script, is used when we want to have a simple + * script in bash or something like that. It should return something that is useful + * in {result: string} else it is considered an error + * @param param0 + * @returns + */ export const runHealthScript = - ({ command, args }: { command: string; args: string[] }) => + ({ + command, + args, + message, + }: { + command: string; + args: string[]; + message: ((result: unknown) => string) | null; + }) => async ( effects: Effects, - _duration: number - ): Promise> => { + _duration: number, + ): Promise> => { const res = await effects.runCommand({ command, args }); if ("result" in res) { - return { result: null }; + return { + result: { + success: + message?.(res) ?? + `Have ran script ${command} and the result: ${res.result}`, + }, + }; } else { - return res; + throw res; } }; diff --git a/lib/types.ts b/lib/types.ts index b47a589..0d0f7eb 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -1,9 +1,7 @@ export * as configTypes from "./types/config-types"; import { ConfigSpec } from "./types/config-types"; -// deno-lint-ignore no-namespace export namespace ExpectedExports { - // deno-lint-ignore no-unused-labels 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. */ export type setConfig = (options: { @@ -40,16 +38,6 @@ export namespace ExpectedExports { }) => Promise>; }; - /** - * Migrations are used when we are changing versions when updating/ downgrading. - * There are times that we need to move files around, and do other operations during a migration. - */ - export type migration = (options: { - effects: Effects; - input: VersionString; - args: unknown[]; - }) => Promise>; - /** * Actions are used so we can effect the service, like deleting a directory. * One old use case is to add a action where we add a file, that will then be run during the @@ -70,73 +58,26 @@ export namespace ExpectedExports { effects: Effects; started(): null; }) => Promise>; + + /** + * Every time a package completes an install, this function is called before the main. + * Can be used to do migration like things. + */ + export type init = (options: { + effects: Effects; + previousVersion: null | string; + }) => Promise>; + /** This will be ran during any time a package is uninstalled, for example during a update + * this will be called. + */ + export type uninit = (options: { + effects: Effects; + nextVersion: null | string; + }) => Promise>; } export type TimeMs = number; export type VersionString = string; -/** @deprecated */ -// deno-lint-ignore no-namespace -export namespace LegacyExpectedExports { - /** Set configuration is called after we have modified and saved the configuration in the embassy ui. Use this to make a file for the docker to read from for configuration. */ - export type setConfig = ( - effects: Effects, - input: Record - ) => Promise>; - /** Get configuration returns a shape that describes the format that the embassy ui will generate, and later send to the set config */ - export type getConfig = (effects: Effects) => Promise>; - /** These are how we make sure the our dependency configurations are valid and if not how to fix them. */ - export type dependencies = Dependencies; - /** For backing up service data though the embassyOS UI */ - export type createBackup = (effects: Effects) => Promise>; - /** For restoring service data that was previously backed up using the embassyOS UI create backup flow. Backup restores are also triggered via the embassyOS UI, or doing a system restore flow during setup. */ - export type restoreBackup = ( - effects: Effects - ) => Promise>; - /** Properties are used to get values from the docker, like a username + password, what ports we are hosting from */ - export type properties = ( - effects: Effects - ) => Promise>; - - /** Health checks are used to determine if the service is working properly after starting - * A good use case is if we are using a web server, seeing if we can get to the web server. - */ - export type health = { - /** Should be the health check id */ - [id: string]: ( - effects: Effects, - dateMs: number - ) => Promise>; - }; - - /** - * Migrations are used when we are changing versions when updating/ downgrading. - * There are times that we need to move files around, and do other operations during a migration. - */ - export type migration = ( - effects: Effects, - version: string, - ...args: unknown[] - ) => Promise>; - - /** - * Actions are used so we can effect the service, like deleting a directory. - * One old use case is to add a action where we add a file, that will then be run during the - * service starting, and that file would indicate that it would rescan all the data. - */ - export type action = { - [id: string]: ( - effects: Effects, - config?: ConfigSpec - ) => Promise>; - }; - - /** - * This is the entrypoint for the main container. Used to start up something like the service that the - * package represents, like running a bitcoind in a bitcoind-wrapper. - */ - export type main = (effects: Effects) => Promise>; -} - export type ConfigRes = { /** This should be the previous config, that way during set config we start with the previous */ config?: null | Record; @@ -184,7 +125,9 @@ export type Effects = { term(): Promise; }; + /** Uses the chown on the system */ chown(input: { volumeId: string; path: string; uid: string }): Promise; + /** Uses the chmod on the system */ chmod(input: { volumeId: string; path: string; mode: string }): Promise; sleep(timeMs: TimeMs): Promise; @@ -203,25 +146,31 @@ export type Effects = { /** Sandbox mode lets us read but not write */ is_sandboxed(): boolean; + /** Check that a file exists or not */ exists(input: { volumeId: string; path: string }): Promise; + /** Declaring that we are opening a interface on some protocal for local network */ bindLocal(options: { internalPort: number; name: string; externalPort: number; }): Promise; + /** Declaring that we are opening a interface on some protocal for tor network */ bindTor(options: { internalPort: number; name: string; externalPort: number; }): Promise; + /** Similar to the fetch api via the mdn, this is simplified but the point is + * to get something from some website, and return the response. + */ fetch( url: string, options?: { method?: "GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "PATCH"; headers?: Record; body?: string; - } + }, ): Promise<{ method: string; ok: boolean; @@ -234,6 +183,10 @@ export type Effects = { json(): Promise; }>; + /** + * Run rsync between two volumes. This is used to backup data between volumes. + * This is a long running process, and a structure that we can either wait for, or get the progress of. + */ runRsync(options: { srcVolume: string; dstVolume: string; @@ -246,15 +199,82 @@ export type Effects = { wait: () => Promise; progress: () => Promise; }; + + /** Get the address for another service for local internet*/ + getServiceLocalAddress(options: { + packageId: string; + interfaceName: string; + }): Promise; + /** Get the address for another service for tor interfaces */ + getServiceTorAddress(options: { + packageId: string; + interfaceName: string; + }): Promise; + /** + * Get the port address for another service + */ + getServicePortForward(options: { + packageId: string; + internalPort: number; + }): Promise; + + /** When we want to create a link in the front end interfaces, and example is + * exposing a url to view a web service + */ + exportAddress(options: { + /** The title of this field to be dsimplayed */ + name: string; + /** Human readable description, used as tooltip usually */ + description: string; + /** URI location */ + address: string; + id: string; + /** Defaults to false, but describes if this address can be opened in a browser as an + * ui interface + */ + ui?: boolean; + }): Promise; + + /** + *Remove an address that was exported. Used problably during main or during setConfig. + * @param options + */ + removeAddress(options: { id: string }): Promise; + + /** + * + * @param options + */ + exportAction(options: { + name: string; + description: string; + id: string; + input: null | ConfigSpec; + }): Promise; + /** + * Remove an action that was exported. Used problably during main or during setConfig. + */ + removeAction(options: { id: string }): Promise; + + getConfigured(): Promise; + /** + * This called after a valid set config as well as during init. + * @param configured + */ + setConfigured(configured: boolean): Promise; }; -// rsync options: https://linux.die.net/man/1/rsync +/* rsync options: https://linux.die.net/man/1/rsync + */ export type BackupOptions = { delete: boolean; force: boolean; ignoreExisting: boolean; exclude: string[]; }; +/** + * This is the metadata that is returned from the metadata call. + */ export type Metadata = { fileType: string; isDir: boolean; @@ -362,12 +382,12 @@ export type Dependencies = { /** 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: ConfigSpec + input: ConfigSpec, ): Promise>; /** 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: ConfigSpec + input: ConfigSpec, ): Promise>; }; }; diff --git a/lib/types/config-types.ts b/lib/types/config-types.ts index 75155dd..9a3935b 100644 --- a/lib/types/config-types.ts +++ b/lib/types/config-types.ts @@ -1,4 +1,3 @@ -// deno-lint-ignore-file ban-types export type ConfigSpec = Record; export type ValueType = diff --git a/lib/util/propertiesMatcher.ts b/lib/util/propertiesMatcher.ts index ca80158..e8c2f15 100644 --- a/lib/util/propertiesMatcher.ts +++ b/lib/util/propertiesMatcher.ts @@ -11,7 +11,6 @@ type TypePointer = "pointer"; type TypeUnion = "union"; // prettier-ignore -// deno-fmt-ignore type GuardDefaultNullable = A extends { readonly default: unknown} ? Type : A extends { readonly nullable: true} ? Type : @@ -19,24 +18,20 @@ type GuardDefaultNullable = Type // prettier-ignore -// deno-fmt-ignore type GuardNumber = A extends {readonly type:TypeNumber} ? GuardDefaultNullable : unknown // prettier-ignore -// deno-fmt-ignore type GuardString = A extends {readonly type:TypeString} ? GuardDefaultNullable : unknown // prettier-ignore -// deno-fmt-ignore type GuardBoolean = A extends {readonly type:TypeBoolean} ? GuardDefaultNullable : unknown // prettier-ignore -// deno-fmt-ignore type GuardObject = A extends {readonly type: TypeObject, readonly spec: infer B} ? ( B extends Record ? {readonly [K in keyof B & string]: _>} : @@ -45,24 +40,19 @@ type GuardObject = unknown // prettier-ignore -// deno-fmt-ignore export type GuardList = A extends {readonly type:TypeList, readonly subtype: infer B, spec?: {spec?: infer C }} ? ReadonlyArray & ({type: B, spec: C})>> : - // deno-lint-ignore ban-types A extends {readonly type:TypeList, readonly subtype: infer B, spec?: {}} ? ReadonlyArray & ({type: B})>> : unknown // prettier-ignore -// deno-fmt-ignore type GuardPointer = A extends {readonly type:TypePointer} ? (string | null) : unknown // prettier-ignore -// deno-fmt-ignore type GuardEnum = A extends {readonly type:TypeEnum, readonly values: ArrayLike} ? GuardDefaultNullable : unknown // prettier-ignore -// deno-fmt-ignore type GuardUnion = A extends {readonly type:TypeUnion, readonly tag: {id: infer Id & string}, variants: infer Variants & Record} ? {[K in keyof Variants]: {[keyType in Id & string]: K}&TypeFromProps}[keyof Variants] : unknown @@ -77,7 +67,6 @@ export type GuardAll = GuardNumber & GuardUnion & GuardEnum; // prettier-ignore -// deno-fmt-ignore export type TypeFromProps = A extends Record ? {readonly [K in keyof A & string]: _>} : unknown; @@ -118,7 +107,7 @@ function charRange(value = "") { */ export function generateDefault( generate: { charset: string; len: number }, - { random = () => Math.random() } = {} + { random = () => Math.random() } = {}, ) { const validCharSets: number[][] = generate.charset .split(",") @@ -129,7 +118,7 @@ export function generateDefault( } const max = validCharSets.reduce( (acc, x) => x.reduce((x, y) => Math.max(x, y), acc), - 0 + 0, ); let i = 0; const answer: string[] = Array(generate.len); @@ -138,7 +127,7 @@ export function generateDefault( const inRange = validCharSets.reduce( (acc, [lower, upper]) => acc || (nextValue >= lower && nextValue <= upper), - false + false, ); if (!inRange) continue; answer[i] = String.fromCharCode(nextValue); @@ -166,19 +155,17 @@ export function matchNumberWithRange(range: string) { ? "any" : left === "[" ? `greaterThanOrEqualTo${leftValue}` - : `greaterThan${leftValue}` + : `greaterThan${leftValue}`, ) .validate( - rightValue === "*" - ? (_) => true - : right === "]" - ? (x) => x <= Number(rightValue) - : (x) => x < Number(rightValue), - rightValue === "*" - ? "any" - : right === "]" - ? `lessThanOrEqualTo${rightValue}` - : `lessThan${rightValue}` + // prettier-ignore + rightValue === "*" ? (_) => true : + right === "]"? (x) => x <= Number(rightValue) : + (x) => x < Number(rightValue), + // prettier-ignore + rightValue === "*" ? "any" : + right === "]" ? `lessThanOrEqualTo${rightValue}` : + `lessThan${rightValue}`, ); } function withIntegral(parser: matches.Parser, value: unknown) { @@ -199,12 +186,12 @@ const isGenerator = matches.shape({ }).test; function defaultNullable( parser: matches.Parser, - value: unknown + value: unknown, ) { if (matchDefault.test(value)) { if (isGenerator(value.default)) { return parser.defaultTo( - parser.unsafeCast(generateDefault(value.default)) + parser.unsafeCast(generateDefault(value.default)), ); } return parser.defaultTo(value.default); @@ -222,34 +209,28 @@ function defaultNullable( * @returns */ export function guardAll( - value: A + value: A, ): matches.Parser> { if (!isType.test(value)) { - // deno-lint-ignore no-explicit-any return matches.unknown as any; } switch (value.type) { case "boolean": - // deno-lint-ignore no-explicit-any return defaultNullable(matches.boolean, value) as any; case "string": - // deno-lint-ignore no-explicit-any return defaultNullable(withPattern(value), value) as any; case "number": return defaultNullable( withIntegral(withRange(value), value), - value - // deno-lint-ignore no-explicit-any + value, ) as any; case "object": if (matchSpec.test(value)) { - // deno-lint-ignore no-explicit-any return defaultNullable(typeFromProps(value.spec), value) as any; } - // deno-lint-ignore no-explicit-any return matches.unknown as any; case "list": { @@ -261,22 +242,18 @@ export function guardAll( const subtype = matchSubType.unsafeCast(value).subtype; return defaultNullable( matches - // deno-lint-ignore no-explicit-any .arrayOf(guardAll({ type: subtype, ...spec } as any)) .validate((x) => rangeValidate(x.length), "valid length"), - value - // deno-lint-ignore no-explicit-any + value, ) as any; } case "enum": if (matchValues.test(value)) { return defaultNullable( matches.literals(value.values[0], ...value.values), - value - // deno-lint-ignore no-explicit-any + value, ) as any; } - // deno-lint-ignore no-explicit-any return matches.unknown as any; case "union": if (matchUnion.test(value)) { @@ -284,15 +261,13 @@ export function guardAll( ...Object.entries(value.variants).map(([variant, spec]) => matches .shape({ [value.tag.id]: matches.literal(variant) }) - .concat(typeFromProps(spec)) - ) // deno-lint-ignore no-explicit-any + .concat(typeFromProps(spec)), + ), ) as any; } - // deno-lint-ignore no-explicit-any return matches.unknown as any; } - // deno-lint-ignore no-explicit-any return matches.unknown as any; } /** @@ -304,17 +279,15 @@ export function guardAll( * @returns */ export function typeFromProps( - valueDictionary: A + valueDictionary: A, ): matches.Parser> { - // deno-lint-ignore no-explicit-any if (!recordString.test(valueDictionary)) return matches.unknown as any; return matches.shape( Object.fromEntries( Object.entries(valueDictionary).map(([key, value]) => [ key, guardAll(value), - ]) - ) - // deno-lint-ignore no-explicit-any + ]), + ), ) as any; } diff --git a/package-lock.json b/package-lock.json index dcda1ff..08fa0ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "start-sdk", - "version": "0.4.0-lib0.alpha2", + "version": "0.4.0-lib0.alpha3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "start-sdk", - "version": "0.4.0-lib0.alpha2", + "version": "0.4.0-lib0.alpha3", "license": "MIT", "dependencies": { "@iarna/toml": "^2.2.5", diff --git a/package.json b/package.json index b80653f..0806993 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "start-sdk", - "version": "0.4.0-lib0.alpha3", + "version": "0.4.0-lib0.alpha4", "description": "For making the patterns that are wanted in making services for the startOS.", "main": "./index.cjs", "types": "./index.d.ts",