diff --git a/lib/actions/setupActions.ts b/lib/actions/setupActions.ts index d31b665..cd198af 100644 --- a/lib/actions/setupActions.ts +++ b/lib/actions/setupActions.ts @@ -8,13 +8,9 @@ export function setupActions(...createdActions: CreatedAction[]) { actions[action.metaData.id] = action.exportedAction; } - const initializeActions = async (effects: Effects) => { - for (const action of createdActions) { - action.exportAction(effects); - } - }; + const actionsMetadata = createdActions.map((x) => x.metaData); return { actions, - initializeActions, + actionsMetadata, }; } diff --git a/lib/autoconfig/AutoConfig.ts b/lib/autoconfig/AutoConfig.ts index 70eff94..465feb5 100644 --- a/lib/autoconfig/AutoConfig.ts +++ b/lib/autoconfig/AutoConfig.ts @@ -1,18 +1,18 @@ -import { AutoConfigure, Effects, ExpectedExports } from "../types"; +import { AutoConfigure, DeepPartial, Effects, ExpectedExports } from "../types"; import { Utils, deepEqual, deepMerge, utils } from "../util"; -export type AutoConfigFrom = { - [key: string]: (options: { +export type AutoConfigFrom = { + [key in keyof NestedConfigs & string]: (options: { effects: Effects; localConfig: Input; - remoteConfig: unknown; + remoteConfig: NestedConfigs[key]; utils: Utils; - }) => Promise>; + }) => Promise>; }; -export class AutoConfig { +export class AutoConfig { constructor( - readonly configs: AutoConfigFrom, - readonly path: keyof AutoConfigFrom, + readonly configs: AutoConfigFrom, + readonly path: keyof AutoConfigFrom, ) {} async check( @@ -23,6 +23,7 @@ export class AutoConfig { ...options, utils: utils(options.effects), localConfig: options.localConfig as Input, + remoteConfig: options.remoteConfig as any, }; if ( !deepEqual( @@ -43,6 +44,7 @@ export class AutoConfig { ...options, utils: utils(options.effects), localConfig: options.localConfig as Input, + remoteConfig: options.remoteConfig as any, }; return deepMerge( {}, diff --git a/lib/autoconfig/setupAutoConfig.ts b/lib/autoconfig/setupAutoConfig.ts index 9b0e1c4..ade5059 100644 --- a/lib/autoconfig/setupAutoConfig.ts +++ b/lib/autoconfig/setupAutoConfig.ts @@ -1,12 +1,24 @@ +import { GenericManifest } from "../manifest/ManifestTypes"; import { AutoConfig, AutoConfigFrom } from "./AutoConfig"; -export function setupAutoConfig(configs: AutoConfigFrom) { +export function setupAutoConfig< + WD, + Input, + Manifest extends GenericManifest, + NestedConfigs extends { + [key in keyof Manifest["dependencies"]]: unknown; + }, +>(configs: AutoConfigFrom) { type C = typeof configs; const answer = { ...configs } as unknown as { - [k in keyof C]: AutoConfig; + [k in keyof C]: AutoConfig; }; for (const key in configs) { - answer[key] = new AutoConfig(configs, key); + answer[key as keyof typeof configs] = new AutoConfig< + WD, + Input, + NestedConfigs + >(configs, key as keyof typeof configs); } return answer; } diff --git a/lib/config/setupConfig.ts b/lib/config/setupConfig.ts index 5fa841b..3497f55 100644 --- a/lib/config/setupConfig.ts +++ b/lib/config/setupConfig.ts @@ -17,7 +17,7 @@ export type Save = (options: { export type Read = (options: { effects: Effects; utils: Utils; -}) => Promise>; +}) => Promise>; /** * 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 @@ -46,7 +46,9 @@ export function setupConfig>( getConfig: (async ({ effects, config }) => { return { spec: spec.build(), - config: nullIfEmpty(await read({ effects, utils: utils(effects) })), + config: nullIfEmpty( + (await read({ effects, utils: utils(effects) })) || null, + ), }; }) as ExpectedExports.getConfig, }; diff --git a/lib/emverLite/mod.ts b/lib/emverLite/mod.ts index cb7cde9..c892cc8 100644 --- a/lib/emverLite/mod.ts +++ b/lib/emverLite/mod.ts @@ -2,7 +2,7 @@ import * as matches from "ts-matches"; const starSub = /((\d+\.)*\d+)\.\*/; // prettier-ignore -export type ValidEmVer = `${'>' | '<' | '>=' | '<=' | '=' | ''}${number | '*'}${`.${number | '*'}` | ""}${`.${number | '*'}` | ""}${`.${number | '*'}` | ""}${`-${string}` | ""}`; +export type ValidEmVer = `${number | '*'}${`.${number | '*'}` | ""}${`.${number | '*'}` | ""}${`-${string}` | ""}`; function incrementLastNumber(list: number[]) { const newList = [...list]; diff --git a/lib/health/HealthCheck.ts b/lib/health/HealthCheck.ts index 6540798..53578eb 100644 --- a/lib/health/HealthCheck.ts +++ b/lib/health/HealthCheck.ts @@ -1,55 +1,43 @@ -// 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"; +import { InterfaceReceipt } from "../mainFn/interfaceReceipt"; +import { Daemon, Effects } from "../types"; +import { CheckResult } from "./checkFns/CheckResult"; +import { HealthReceipt } from "./HealthReceipt"; +import { Trigger } from "./trigger"; +import { TriggerInput } from "./trigger/TriggerInput"; +import { defaultTrigger } from "./trigger/defaultTrigger"; -// function readReciptOf(a: A) { -// return a as A & ReadyReceipt; -// } -// export function readyCheck(o: { -// effects: Effects; -// started(onTerm: () => void): null; -// interfaceReceipt: InterfaceReceipt; -// healthReceipts: Iterable; -// daemonReceipt: Daemon; -// name: string; -// trigger?: Trigger; -// fn(): Promise | CheckResult; -// onFirstSuccess?: () => () => Promise | 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, -// }); -// } +export function healthCheck(o: { + effects: Effects; + name: string; + trigger?: Trigger; + fn(): Promise | CheckResult; + onFirstSuccess?: () => () => Promise | unknown; +}) { + new Promise(async () => { + let currentValue: TriggerInput = { + lastResult: null, + hadSuccess: false, + }; + const getCurrentValue = () => currentValue; + const trigger = (o.trigger ?? defaultTrigger)(getCurrentValue); + for ( + let res = await trigger.next(); + !res.done; + res = await trigger.next() + ) { + try { + const { status, message } = await o.fn(); + await o.effects.setHealth({ + name: o.name, + status, + message, + }); + currentValue.hadSuccess = true; + currentValue.lastResult = "success"; + } catch (_) { + currentValue.lastResult = "failure"; + } + } + }); + return {} as HealthReceipt; +} diff --git a/lib/health/trigger/changeOnFirstSuccess.ts b/lib/health/trigger/changeOnFirstSuccess.ts index b8ec1a7..aa0c151 100644 --- a/lib/health/trigger/changeOnFirstSuccess.ts +++ b/lib/health/trigger/changeOnFirstSuccess.ts @@ -4,25 +4,28 @@ import { Trigger } from "./index"; export function changeOnFirstSuccess(o: { beforeFirstSuccess: Trigger; afterFirstSuccess: Trigger; -}) { - return async function* () { - const beforeFirstSuccess = o.beforeFirstSuccess(); - let currentValue: TriggerInput = yield; - beforeFirstSuccess.next(currentValue); +}): Trigger { + return async function* (getInput) { + const beforeFirstSuccess = o.beforeFirstSuccess(getInput); + yield; + let currentValue = getInput(); + beforeFirstSuccess.next(); for ( - let res = await beforeFirstSuccess.next(currentValue); + let res = await beforeFirstSuccess.next(); currentValue?.lastResult !== "success" && !res.done; - res = await beforeFirstSuccess.next(currentValue) + res = await beforeFirstSuccess.next() ) { - currentValue = yield; + yield; + currentValue = getInput(); } - const afterFirstSuccess = o.afterFirstSuccess(); + const afterFirstSuccess = o.afterFirstSuccess(getInput); for ( - let res = await afterFirstSuccess.next(currentValue); + let res = await afterFirstSuccess.next(); !res.done; - res = await afterFirstSuccess.next(currentValue) + res = await afterFirstSuccess.next() ) { - currentValue = yield; + yield; + currentValue = getInput(); } }; } diff --git a/lib/health/trigger/index.ts b/lib/health/trigger/index.ts index 6d4ec88..91f6982 100644 --- a/lib/health/trigger/index.ts +++ b/lib/health/trigger/index.ts @@ -2,4 +2,6 @@ import { TriggerInput } from "./TriggerInput"; export { changeOnFirstSuccess } from "./changeOnFirstSuccess"; export { cooldownTrigger } from "./cooldownTrigger"; -export type Trigger = () => AsyncIterator; +export type Trigger = ( + getInput: () => TriggerInput, +) => AsyncIterator; diff --git a/lib/index.ts b/lib/index.ts index 0c9fcdf..c01ca20 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -15,3 +15,4 @@ export * as properties from "./properties"; export * as autoconfig from "./autoconfig"; export * as actions from "./actions"; export * as manifest from "./manifest"; +export * as inits from "./inits"; diff --git a/lib/inits/index.ts b/lib/inits/index.ts new file mode 100644 index 0000000..eac14a9 --- /dev/null +++ b/lib/inits/index.ts @@ -0,0 +1,3 @@ +export { setupInit } from "./setupInit"; +export { setupUninstall } from "./setupUninstall"; +export { setupInstall } from "./setupInstall"; diff --git a/lib/migrations/Migration.ts b/lib/inits/migrations/Migration.ts similarity index 81% rename from lib/migrations/Migration.ts rename to lib/inits/migrations/Migration.ts index d17a893..6f84ece 100644 --- a/lib/migrations/Migration.ts +++ b/lib/inits/migrations/Migration.ts @@ -1,6 +1,6 @@ -import { ManifestVersion } from "../manifest/ManifestTypes"; -import { Effects } from "../types"; -import { Utils } from "../util"; +import { ManifestVersion } from "../../manifest/ManifestTypes"; +import { Effects } from "../../types"; +import { Utils } from "../../util"; export class Migration { constructor( diff --git a/lib/inits/migrations/setupMigrations.ts b/lib/inits/migrations/setupMigrations.ts new file mode 100644 index 0000000..2913f93 --- /dev/null +++ b/lib/inits/migrations/setupMigrations.ts @@ -0,0 +1,69 @@ +import { setupActions } from "../../actions/setupActions"; +import { EmVer } from "../../emverLite/mod"; +import { GenericManifest } from "../../manifest/ManifestTypes"; +import { ExpectedExports } from "../../types"; +import { once } from "../../util/once"; +import { Migration } from "./Migration"; + +export class Migrations { + private constructor( + readonly manifest: GenericManifest, + readonly migrations: Array>, + ) {} + private sortedMigrations = once(() => { + const migrationsAsVersions = (this.migrations as Array>).map( + (x) => [EmVer.parse(x.options.version), x] as const, + ); + migrationsAsVersions.sort((a, b) => a[0].compareForSort(b[0])); + return migrationsAsVersions; + }); + private currentVersion = once(() => EmVer.parse(this.manifest.version)); + static of>>( + manifest: GenericManifest, + ...migrations: EnsureUniqueId + ) { + return new Migrations(manifest, migrations as Array>); + } + async init({ + effects, + previousVersion, + }: Parameters[0]) { + if (!!previousVersion) { + const previousVersionEmVer = EmVer.parse(previousVersion); + for (const [_, migration] of this.sortedMigrations() + .filter((x) => x[0].greaterThan(previousVersionEmVer)) + .filter((x) => x[0].lessThanOrEqual(this.currentVersion()))) { + await migration.up({ effects }); + } + } + } + async uninit({ + effects, + nextVersion, + }: Parameters[0]) { + if (!!nextVersion) { + const nextVersionEmVer = EmVer.parse(nextVersion); + const reversed = [...this.sortedMigrations()].reverse(); + for (const [_, migration] of reversed + .filter((x) => x[0].greaterThan(nextVersionEmVer)) + .filter((x) => x[0].lessThanOrEqual(this.currentVersion()))) { + await migration.down({ effects }); + } + } + } +} + +export function setupMigrations>>( + manifest: GenericManifest, + ...migrations: EnsureUniqueId +) { + return Migrations.of(manifest, ...migrations); +} + +// prettier-ignore +export type EnsureUniqueId = + B extends [] ? A : + B extends [Migration, ...infer Rest] ? ( + id extends ids ? "One of the ids are not unique"[] : + EnsureUniqueId + ) : "There exists a migration that is not a Migration"[] diff --git a/lib/inits/setupInit.ts b/lib/inits/setupInit.ts new file mode 100644 index 0000000..5b1ccd3 --- /dev/null +++ b/lib/inits/setupInit.ts @@ -0,0 +1,24 @@ +import { ExpectedExports } from "../types"; +import { Migrations } from "./migrations/setupMigrations"; +import { Install } from "./setupInstall"; +import { Uninstall } from "./setupUninstall"; + +export function setupInit( + migrations: Migrations, + install: Install, + uninstall: Uninstall, +): { + init: ExpectedExports.init; + uninit: ExpectedExports.uninit; +} { + return { + init: async (opts) => { + await migrations.init(opts); + await install.init(opts); + }, + uninit: async (opts) => { + await migrations.uninit(opts); + await uninstall.uninit(opts); + }, + }; +} diff --git a/lib/inits/setupInstall.ts b/lib/inits/setupInstall.ts new file mode 100644 index 0000000..021f34e --- /dev/null +++ b/lib/inits/setupInstall.ts @@ -0,0 +1,24 @@ +import { Effects, ExpectedExports } from "../types"; +import { Utils, utils } from "../util"; + +export type InstallFn = (opts: { + effects: Effects; + utils: Utils; +}) => Promise; +export class Install { + private constructor(readonly fn: InstallFn) {} + static of(fn: InstallFn) { + return new Install(fn); + } + + async init({ + effects, + previousVersion, + }: Parameters[0]) { + if (!previousVersion) await this.fn({ effects, utils: utils(effects) }); + } +} + +export function setupInstall(fn: InstallFn) { + return Install.of(fn); +} diff --git a/lib/inits/setupUninstall.ts b/lib/inits/setupUninstall.ts new file mode 100644 index 0000000..ae99f35 --- /dev/null +++ b/lib/inits/setupUninstall.ts @@ -0,0 +1,24 @@ +import { Effects, ExpectedExports } from "../types"; +import { Utils, utils } from "../util"; + +export type UninstallFn = (opts: { + effects: Effects; + utils: Utils; +}) => Promise; +export class Uninstall { + private constructor(readonly fn: UninstallFn) {} + static of(fn: UninstallFn) { + return new Uninstall(fn); + } + + async uninit({ + effects, + nextVersion, + }: Parameters[0]) { + if (!nextVersion) await this.fn({ effects, utils: utils(effects) }); + } +} + +export function setupUninstall(fn: UninstallFn) { + return Uninstall.of(fn); +} diff --git a/lib/mainFn/Daemons.ts b/lib/mainFn/Daemons.ts index 540dc0c..d3141a7 100644 --- a/lib/mainFn/Daemons.ts +++ b/lib/mainFn/Daemons.ts @@ -82,11 +82,15 @@ export class Daemons { const { command } = daemon; const child = effects.runDaemon(command); - const trigger = (daemon.ready.trigger ?? defaultTrigger)(); + let currentInput = {}; + const getCurrentInput = () => currentInput; + const trigger = (daemon.ready.trigger ?? defaultTrigger)( + getCurrentInput, + ); for ( - let res = await trigger.next({}); + let res = await trigger.next(); !res.done; - res = await trigger.next({}) + res = await trigger.next() ) { const response = await daemon.ready.fn(); if (response.status === "passing") { diff --git a/lib/manifest/ManifestTypes.ts b/lib/manifest/ManifestTypes.ts index 9328bc1..7f70e44 100644 --- a/lib/manifest/ManifestTypes.ts +++ b/lib/manifest/ManifestTypes.ts @@ -57,11 +57,11 @@ export interface GenericManifest { actions: Array; alerts: { install: string | null; + update: string | null; uninstall: string | null; restore: string | null; start: string | null; stop: string | null; - update: string | null; }; dependencies: Record; } diff --git a/lib/manifest/setupManifest.ts b/lib/manifest/setupManifest.ts index d5d7199..10e7291 100644 --- a/lib/manifest/setupManifest.ts +++ b/lib/manifest/setupManifest.ts @@ -1,9 +1,15 @@ import { GenericManifest, ManifestVersion } from "./ManifestTypes"; export function setupManifest< - M extends GenericManifest & { id: Id; version: Version }, Id extends string, Version extends ManifestVersion, ->(manifest: M): M { + Dependencies extends Record, +>( + manifest: GenericManifest & { + dependencies: Dependencies; + id: Id; + version: Version; + }, +): GenericManifest & { dependencies: Dependencies; id: Id; version: Version } { return manifest; } diff --git a/lib/migrations/setupMigrations.ts b/lib/migrations/setupMigrations.ts deleted file mode 100644 index 89d2cfa..0000000 --- a/lib/migrations/setupMigrations.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { setupActions } from "../actions/setupActions"; -import { EmVer } from "../emverLite/mod"; -import { GenericManifest } from "../manifest/ManifestTypes"; -import { ExpectedExports } from "../types"; -import { once } from "../util/once"; -import { Migration } from "./Migration"; - -export function setupMigrations>>( - manifest: GenericManifest, - initializeActions: ReturnType["initializeActions"], - ...migrations: EnsureUniqueId -) { - const sortedMigrations = once(() => { - const migrationsAsVersions = (migrations as Array>).map( - (x) => [EmVer.parse(x.options.version), x] as const, - ); - migrationsAsVersions.sort((a, b) => a[0].compareForSort(b[0])); - return migrationsAsVersions; - }); - const currentVersion = once(() => EmVer.parse(manifest.version)); - const init: ExpectedExports.init = async ({ effects, previousVersion }) => { - await initializeActions(effects); - if (!!previousVersion) { - const previousVersionEmVer = EmVer.parse(previousVersion); - for (const [_, migration] of sortedMigrations() - .filter((x) => x[0].greaterThan(previousVersionEmVer)) - .filter((x) => x[0].lessThanOrEqual(currentVersion()))) { - await migration.up({ effects }); - } - } - }; - const uninit: ExpectedExports.uninit = async ({ effects, nextVersion }) => { - if (!!nextVersion) { - const nextVersionEmVer = EmVer.parse(nextVersion); - const reversed = [...sortedMigrations()].reverse(); - for (const [_, migration] of reversed - .filter((x) => x[0].greaterThan(nextVersionEmVer)) - .filter((x) => x[0].lessThanOrEqual(currentVersion()))) { - await migration.down({ effects }); - } - } - }; - return { init, uninit }; -} - -// prettier-ignore -export type EnsureUniqueId = - B extends [] ? A : - B extends [Migration, ...infer Rest] ? ( - id extends ids ? "One of the ids are not unique"[] : - EnsureUniqueId - ) : "There exists a migration that is not a Migration"[] diff --git a/lib/emverLite/emverList.test.ts b/lib/test/emverList.test.ts similarity index 94% rename from lib/emverLite/emverList.test.ts rename to lib/test/emverList.test.ts index 7509873..e65d0e4 100644 --- a/lib/emverLite/emverList.test.ts +++ b/lib/test/emverList.test.ts @@ -1,9 +1,17 @@ -import { EmVer, notRange, rangeAnd, rangeOf, rangeOr } from "./mod"; +import { EmVer, notRange, rangeAnd, rangeOf, rangeOr } from "../emverLite/mod"; describe("EmVer", () => { { { const checker = rangeOf("*"); test("rangeOf('*')", () => { + checker.check("1"); + checker.check("1.2"); + checker.check("1.2.3"); + checker.check("1.2.3.4"); + // @ts-expect-error + checker.check("1.2.3.4.5"); + // @ts-expect-error + checker.check("1.2.3.4.5.6"); expect(checker.check("1")).toEqual(true); expect(checker.check("1.2")).toEqual(true); expect(checker.check("1.2.3.4")).toEqual(true); @@ -23,6 +31,7 @@ describe("EmVer", () => { expect(checker.check("2-beta123")).toEqual(true); expect(checker.check("2")).toEqual(true); expect(checker.check("1.2.3.5")).toEqual(true); + // @ts-expect-error expect(checker.check("1.2.3.4.1")).toEqual(true); }); @@ -49,6 +58,7 @@ describe("EmVer", () => { test(`rangeOf(">=1.2.3.4") valid`, () => { expect(checker.check("2")).toEqual(true); expect(checker.check("1.2.3.5")).toEqual(true); + // @ts-expect-error expect(checker.check("1.2.3.4.1")).toEqual(true); expect(checker.check("1.2.3.4")).toEqual(true); }); @@ -63,6 +73,7 @@ describe("EmVer", () => { test(`rangeOf("<1.2.3.4") invalid`, () => { expect(checker.check("2")).toEqual(false); expect(checker.check("1.2.3.5")).toEqual(false); + // @ts-expect-error expect(checker.check("1.2.3.4.1")).toEqual(false); expect(checker.check("1.2.3.4")).toEqual(false); }); @@ -77,6 +88,7 @@ describe("EmVer", () => { test(`rangeOf("<=1.2.3.4") invalid`, () => { expect(checker.check("2")).toEqual(false); expect(checker.check("1.2.3.5")).toEqual(false); + // @ts-expect-error expect(checker.check("1.2.3.4.1")).toEqual(false); }); @@ -184,6 +196,7 @@ describe("EmVer", () => { test(`rangeOf("!>1.2.3.4") invalid`, () => { expect(checker.check("2")).toEqual(false); expect(checker.check("1.2.3.5")).toEqual(false); + // @ts-expect-error expect(checker.check("1.2.3.4.1")).toEqual(false); }); diff --git a/lib/types.ts b/lib/types.ts index 8a00e5b..e277ae8 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -437,7 +437,7 @@ export type MigrationRes = { export type ActionResult = { message: string; - value?: string; + value: null | string; copyable: boolean; qr: boolean; }; diff --git a/package-lock.json b/package-lock.json index 2fdd63f..eb0b1c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "start-sdk", - "version": "0.4.0-lib0.charlie34", + "version": "0.4.0-lib0.charlie39", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "start-sdk", - "version": "0.4.0-lib0.charlie34", + "version": "0.4.0-lib0.charlie39", "license": "MIT", "dependencies": { "@iarna/toml": "^2.2.5", diff --git a/package.json b/package.json index ed0d21f..4b9d7dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "start-sdk", - "version": "0.4.0-lib0.charlie34", + "version": "0.4.0-lib0.charlie39", "description": "For making the patterns that are wanted in making services for the startOS.", "main": "./lib/index.js", "types": "./lib/index.d.ts",