chore: Update the types for changes that Matt wanted with sdk + examples

This commit is contained in:
J H
2024-03-20 20:28:31 -06:00
parent c74bdc97ca
commit 4a27128a1c
12 changed files with 212 additions and 103 deletions

View File

@@ -1,4 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { DependencyKind } from "./DependencyKind";
export interface DependencyRequirement { id: string, kind: DependencyKind, healthChecks: string[], }
export type DependencyRequirement = { "kind": "running", id: string, healthChecks: string[], versionSpec: string, url: string, } | { "kind": "exists", id: string, versionSpec: string, url: string, };

View File

@@ -412,7 +412,7 @@ pub struct StaticDependencyInfo {
pub icon: DataUrl<'static>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[serde(rename_all = "kebab-case")]
#[serde(tag = "kind")]
pub enum CurrentDependencyInfo {

View File

@@ -1,10 +1,10 @@
use std::collections::BTreeSet;
use std::ffi::OsString;
use std::net::Ipv4Addr;
use std::os::unix::process::CommandExt;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::{Arc, Weak};
use std::net::Ipv4Addr;
use clap::builder::ValueParserFactory;
use clap::Parser;
@@ -765,8 +765,8 @@ async fn get_configured(context: EffectContext, _: Empty) -> Result<Value, Error
let package = peeked
.as_public()
.as_package_data()
.as_idx(&package_id)
.or_not_found(&package_id)?
.as_idx(package_id)
.or_not_found(package_id)?
.as_status()
.as_configured()
.de()?;
@@ -1070,28 +1070,39 @@ enum DependencyKind {
}
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[serde(rename_all = "camelCase", tag = "kind")]
#[ts(export)]
struct DependencyRequirement {
#[ts(type = "string")]
id: PackageId,
kind: DependencyKind,
#[serde(default)]
#[ts(type = "string[]")]
health_checks: BTreeSet<HealthCheckId>,
enum DependencyRequirement {
Running {
#[ts(type = "string")]
id: PackageId,
#[ts(type = "string[]")]
#[serde(rename = "healthChecks")]
health_checks: BTreeSet<HealthCheckId>,
#[serde(rename = "versionSpec")]
version_spec: String,
url: String,
},
Exists {
#[ts(type = "string")]
id: PackageId,
#[serde(rename = "versionSpec")]
version_spec: String,
url: String,
},
}
// filebrowser:exists,bitcoind:running:foo+bar+baz
impl FromStr for DependencyRequirement {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.split_once(':') {
Some((id, "e")) | Some((id, "exists")) => Ok(Self {
Some((id, "e")) | Some((id, "exists")) => Ok(Self::Exists {
id: id.parse()?,
kind: DependencyKind::Exists,
health_checks: BTreeSet::new(),
url: "".to_string(),
version_spec: "*".to_string(),
}),
Some((id, rest)) => {
let health_checks = match rest.split_once(":") {
let health_checks = match rest.split_once(':') {
Some(("r", rest)) | Some(("running", rest)) => rest
.split('+')
.map(|id| id.parse().map_err(Error::from))
@@ -1108,16 +1119,18 @@ impl FromStr for DependencyRequirement {
)),
},
}?;
Ok(Self {
Ok(Self::Running {
id: id.parse()?,
kind: DependencyKind::Running,
health_checks,
url: "".to_string(),
version_spec: "*".to_string(),
})
}
None => Ok(Self {
None => Ok(Self::Running {
id: s.parse()?,
kind: DependencyKind::Running,
health_checks: BTreeSet::new(),
url: "".to_string(),
version_spec: "*".to_string(),
}),
}
}
@@ -1149,23 +1162,19 @@ async fn set_dependencies(
let dependencies = CurrentDependencies(
dependencies
.into_iter()
.map(
|DependencyRequirement {
id,
kind,
health_checks,
}| {
(
id,
match kind {
DependencyKind::Exists => CurrentDependencyInfo::Exists,
DependencyKind::Running => {
CurrentDependencyInfo::Running { health_checks }
}
},
)
},
)
.map(|dependency| match dependency {
DependencyRequirement::Exists {
id,
url,
version_spec,
} => (id, CurrentDependencyInfo::Exists),
DependencyRequirement::Running {
id,
health_checks,
url,
version_spec,
} => (id, CurrentDependencyInfo::Running { health_checks }),
})
.collect(),
);
for (dep, entry) in db.as_public_mut().as_package_data_mut().as_entries_mut()? {

18
sdk/lib/Dependency.ts Normal file
View File

@@ -0,0 +1,18 @@
import { Checker } from "./emverLite/mod"
export class Dependency {
constructor(
readonly data:
| {
type: "running"
versionSpec: Checker
url: string
healthChecks: string[]
}
| {
type: "exists"
versionSpec: Checker
url: string
},
) {}
}

View File

@@ -50,7 +50,11 @@ import { Uninstall, UninstallFn, setupUninstall } from "./inits/setupUninstall"
import { setupMain } from "./mainFn"
import { defaultTrigger } from "./trigger/defaultTrigger"
import { changeOnFirstSuccess, cooldownTrigger } from "./trigger"
import setupConfig, { Read, Save } from "./config/setupConfig"
import setupConfig, {
DependenciesReceipt,
Read,
Save,
} from "./config/setupConfig"
import {
InterfacesReceipt,
SetInterfaces,
@@ -72,6 +76,9 @@ import { getStore } from "./store/getStore"
import { CommandOptions, MountOptions, Overlay } from "./util/Overlay"
import { splitCommand } from "./util/splitCommand"
import { Mounts } from "./mainFn/Mounts"
import { Dependency } from "./Dependency"
import * as T from "./types"
import { Checker, EmVer } from "./emverLite/mod"
// prettier-ignore
type AnyNeverCond<T extends any[], Then, Else> =
@@ -104,6 +111,20 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
}
build(isReady: AnyNeverCond<[Manifest, Store], "Build not ready", true>) {
type DependencyType = {
[K in keyof {
[K in keyof Manifest["dependencies"]]: Manifest["dependencies"][K]["optional"] extends false
? K
: never
}]: Dependency
} & {
[K in keyof {
[K in keyof Manifest["dependencies"]]: Manifest["dependencies"][K]["optional"] extends true
? K
: never
}]?: Dependency
}
return {
serviceInterface: {
getOwn: <E extends Effects>(effects: E, id: ServiceInterfaceId) =>
@@ -233,6 +254,11 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
HealthCheck: {
of: healthCheck,
},
Dependency: {
of(data: Dependency["data"]) {
return new Dependency({ ...data })
},
},
healthCheck: {
checkPortListening,
checkWebUrl,
@@ -278,12 +304,48 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
> | null
},
) => setupDependencyConfig<Store, Input, Manifest>(config, autoConfigs),
setupDependencies: <Input extends Record<string, any>>(
fn: (options: {
effects: Effects
input: Input | null
}) => Promise<DependencyType>,
) => {
return async (options: { effects: Effects; input: Input }) => {
const dependencyType = await fn(options)
return await options.effects.setDependencies({
dependencies: Object.entries(dependencyType).map(
([
id,
{
data: { versionSpec, ...x },
},
]) => ({
id,
...x,
...(x.type === "running"
? {
kind: "running",
healthChecks: x.healthChecks,
}
: {
kind: "exists",
}),
versionSpec: versionSpec.range,
}),
),
})
}
},
setupExports: (fn: SetupExports<Store>) => fn,
setupInit: (
migrations: Migrations<Manifest, Store>,
install: Install<Manifest, Store>,
uninstall: Uninstall<Manifest, Store>,
setInterfaces: SetInterfaces<Manifest, Store, any, any>,
setDependencies: (options: {
effects: Effects
input: any
}) => Promise<DependenciesReceipt>,
setupExports: SetupExports<Store>,
) =>
setupInit<Manifest, Store>(
@@ -292,6 +354,7 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
uninstall,
setInterfaces,
setupExports,
setDependencies,
),
setupInstall: (fn: InstallFn<Manifest, Store>) => Install.of(fn),
setupInterfaces: <
@@ -346,6 +409,9 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
spec: Spec,
) => Config.of<Spec, Store>(spec),
},
Checker: {
parse: Checker.parse,
},
Daemons: {
of(config: {
effects: Effects
@@ -360,13 +426,17 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
LocalConfig extends Record<string, any>,
RemoteConfig extends Record<string, any>,
>({
localConfig,
remoteConfig,
localConfigSpec,
remoteConfigSpec,
dependencyConfig,
update,
}: {
localConfig: Config<LocalConfig, Store> | Config<LocalConfig, never>
remoteConfig: Config<RemoteConfig, any> | Config<RemoteConfig, never>
localConfigSpec:
| Config<LocalConfig, Store>
| Config<LocalConfig, never>
remoteConfigSpec:
| Config<RemoteConfig, any>
| Config<RemoteConfig, never>
dependencyConfig: (options: {
effects: Effects
localConfig: LocalConfig
@@ -381,6 +451,10 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
>(dependencyConfig, update)
},
},
EmVer: {
from: EmVer.from,
parse: EmVer.parse,
},
List: {
text: List.text,
number: List.number,

View File

@@ -163,7 +163,7 @@ export class EmVer {
}
toString() {
return `${this.values.join(".")}${this.extra ? `-${this.extra}` : ""}`
return `${this.values.join(".")}${this.extra ? `-${this.extra}` : ""}` as ValidEmVer
}
}
@@ -179,6 +179,7 @@ export class Checker {
* @returns
*/
static parse(range: string | Checker): Checker {
console.log(`Parser (${range})`)
if (range instanceof Checker) {
return range
}
@@ -193,10 +194,12 @@ export class Checker {
return new Checker((version) => {
EmVer.from(version)
return true
})
}, range)
}
if (range.startsWith("!!")) return Checker.parse(range.substring(2))
if (range.startsWith("!")) {
return Checker.parse(range.substring(1)).not()
const tempValue = Checker.parse(range.substring(1))
return new Checker((x) => !tempValue.check(x), range)
}
const starSubMatches = starSub.exec(range)
if (starSubMatches != null) {
@@ -210,7 +213,7 @@ export class Checker {
!v.greaterThan(emVarUpper) &&
!v.equals(emVarUpper)
)
})
}, range)
}
switch (range.substring(0, 2)) {
@@ -219,14 +222,14 @@ export class Checker {
return new Checker((version) => {
const v = EmVer.from(version)
return v.greaterThanOrEqual(emVar)
})
}, range)
}
case "<=": {
const emVar = EmVer.parse(range.substring(2))
return new Checker((version) => {
const v = EmVer.from(version)
return v.lessThanOrEqual(emVar)
})
}, range)
}
}
@@ -236,21 +239,21 @@ export class Checker {
return new Checker((version) => {
const v = EmVer.from(version)
return v.greaterThan(emVar)
})
}, range)
}
case "<": {
const emVar = EmVer.parse(range.substring(1))
return new Checker((version) => {
const v = EmVer.from(version)
return v.lessThan(emVar)
})
}, range)
}
case "=": {
const emVar = EmVer.parse(range.substring(1))
return new Checker((version) => {
const v = EmVer.from(version)
return v.equals(emVar)
})
}, `=${emVar.toString()}`)
}
}
throw new Error("Couldn't parse range: " + range)
@@ -261,40 +264,53 @@ export class Checker {
* a pattern
*/
public readonly check: (value: ValidEmVer | EmVer) => boolean,
private readonly _range: string,
) {}
get range() {
return this._range as ValidEmVerRange
}
/**
* Used when we want the `and` condition with another checker
*/
public and(...others: (Checker | string)[]): Checker {
return new Checker((value) => {
if (!this.check(value)) {
return false
}
for (const other of others) {
if (!Checker.parse(other).check(value)) {
const othersCheck = others.map(Checker.parse)
return new Checker(
(value) => {
if (!this.check(value)) {
return false
}
}
return true
})
for (const other of othersCheck) {
if (!other.check(value)) {
return false
}
}
return true
},
othersCheck.map((x) => x._range).join(" && "),
)
}
/**
* Used when we want the `or` condition with another checker
*/
public or(...others: (Checker | string)[]): Checker {
return new Checker((value) => {
if (this.check(value)) {
return true
}
for (const other of others) {
if (Checker.parse(other).check(value)) {
const othersCheck = others.map(Checker.parse)
return new Checker(
(value) => {
if (this.check(value)) {
return true
}
}
return false
})
for (const other of othersCheck) {
if (other.check(value)) {
return true
}
}
return false
},
othersCheck.map((x) => x._range).join(" || "),
)
}
/**
@@ -302,6 +318,7 @@ export class Checker {
* @returns
*/
public not(): Checker {
return new Checker((value) => !this.check(value))
let newRange = `!${this._range}`
return Checker.parse(newRange)
}
}

View File

@@ -1,6 +1,12 @@
import { DependenciesReceipt } from "../config/setupConfig"
import { SetInterfaces } from "../interfaces/setupInterfaces"
import { SDKManifest } from "../manifest/ManifestTypes"
import { ExpectedExports, ExposeUiPaths, ExposeUiPathsAll } from "../types"
import {
Effects,
ExpectedExports,
ExposeUiPaths,
ExposeUiPathsAll,
} from "../types"
import { Migrations } from "./migrations/setupMigrations"
import { SetupExports } from "./setupExports"
import { Install } from "./setupInstall"
@@ -12,6 +18,10 @@ export function setupInit<Manifest extends SDKManifest, Store>(
uninstall: Uninstall<Manifest, Store>,
setInterfaces: SetInterfaces<Manifest, Store, any, any>,
setupExports: SetupExports<Store>,
setDependencies: (options: {
effects: Effects
input: any
}) => Promise<DependenciesReceipt>,
): {
init: ExpectedExports.init
uninit: ExpectedExports.uninit
@@ -27,6 +37,7 @@ export function setupInit<Manifest extends SDKManifest, Store>(
const { services, ui } = await setupExports(opts)
await opts.effects.exposeForDependents({ paths: services })
await opts.effects.exposeUi(forExpose(ui))
await setDependencies({ effects: opts.effects, input: null })
},
uninit: async (opts) => {
await migrations.uninit(opts)

View File

@@ -75,31 +75,13 @@ export type SDKManifest = {
}
export interface ManifestDependency {
/** The range of versions that would satisfy the dependency
*
* ie: >=3.4.5 <4.0.0
*/
version: string
/**
* A human readable explanation on what the dependency is used for
*/
description: string | null
requirement:
| {
type: "opt-in"
/**
* The human readable explanation on how to opt-in to the dependency
*/
how: string
}
| {
type: "opt-out"
/**
* The human readable explanation on how to opt-out to the dependency
*/
how: string
}
| {
type: "required"
}
/**
* Determines if the dependency is optional or not. Times that optional that are good include such situations
* such as being able to toggle other services or to use a different service for the same purpose.
*/
optional: boolean
}

View File

@@ -414,8 +414,7 @@ describe("values", () => {
dependencies: {
remoteTest: {
description: "",
requirement: { how: "", type: "opt-in" },
version: "1.0",
optional: true,
},
},
}),

View File

@@ -35,8 +35,7 @@ export const sdk = StartSdk.of()
dependencies: {
remoteTest: {
description: "",
requirement: { how: "", type: "opt-in" },
version: "1.0",
optional: false,
},
},
}),

View File

@@ -16,8 +16,8 @@ describe("setupDependencyConfig", () => {
}),
})
const remoteTest = sdk.DependencyConfig.of({
localConfig: testConfig,
remoteConfig: testConfig2,
localConfigSpec: testConfig,
remoteConfigSpec: testConfig2,
dependencyConfig: async ({}) => {},
})
sdk.setupDependencyConfig(testConfig, {

View File

@@ -600,7 +600,8 @@ export type KnownError =
export type Dependency = {
id: PackageId
kind: DependencyKind
versionSpec: string
url: string
} & ({ kind: "exists" } | { kind: "running"; healthChecks: string[] })
export type Dependencies = Array<Dependency>