diff --git a/container-runtime/package-lock.json b/container-runtime/package-lock.json index 2ae4b19bf..130667c38 100644 --- a/container-runtime/package-lock.json +++ b/container-runtime/package-lock.json @@ -38,7 +38,7 @@ }, "../sdk/dist": { "name": "@start9labs/start-sdk", - "version": "0.4.0-beta.38", + "version": "0.4.0-beta.40", "license": "MIT", "dependencies": { "@iarna/toml": "^3.0.0", diff --git a/sdk/base/lib/util/getServiceInterface.ts b/sdk/base/lib/util/getServiceInterface.ts index 430c975c0..cd74c9b8a 100644 --- a/sdk/base/lib/util/getServiceInterface.ts +++ b/sdk/base/lib/util/getServiceInterface.ts @@ -21,23 +21,78 @@ type FilterKinds = "onion" | "local" | "domain" | "ip" | "ipv4" | "ipv6" export type Filter = { visibility?: "public" | "private" kind?: FilterKinds | FilterKinds[] + predicate?: (h: HostnameInfo) => boolean exclude?: Filter } +type VisibilityFilter = V extends "public" + ? (HostnameInfo & { public: true }) | VisibilityFilter> + : V extends "private" + ? + | (HostnameInfo & { public: false }) + | VisibilityFilter> + : never +type KindFilter = K extends "onion" + ? (HostnameInfo & { kind: "onion" }) | KindFilter> + : K extends "local" + ? + | (HostnameInfo & { kind: "ip"; hostname: { kind: "local" } }) + | KindFilter> + : K extends "domain" + ? + | (HostnameInfo & { kind: "ip"; hostname: { kind: "domain" } }) + | KindFilter> + : K extends "ipv4" + ? + | (HostnameInfo & { kind: "ip"; hostname: { kind: "ipv4" } }) + | KindFilter> + : K extends "ipv6" + ? + | (HostnameInfo & { kind: "ip"; hostname: { kind: "ipv6" } }) + | KindFilter> + : K extends "ip" + ? KindFilter | "ipv4" | "ipv6"> + : never + +type FilterReturnTy = F extends { + visibility: infer V extends "public" | "private" +} + ? VisibilityFilter & FilterReturnTy> + : F extends { + kind: (infer K extends FilterKinds) | (infer K extends FilterKinds)[] + } + ? KindFilter & FilterReturnTy> + : F extends { + predicate: (h: HostnameInfo) => h is infer H extends HostnameInfo + } + ? H & FilterReturnTy> + : F extends { exclude: infer E extends Filter } // MUST BE LAST + ? HostnameInfo extends FilterReturnTy + ? HostnameInfo + : Exclude> + : HostnameInfo + type Formats = "hostname-info" | "urlstring" | "url" -type FormatReturnTy = Format extends "hostname-info" - ? HostnameInfo +type FormatReturnTy< + F extends Filter, + Format extends Formats, +> = Format extends "hostname-info" + ? FilterReturnTy | FormatReturnTy> : Format extends "url" - ? URL - : UrlString + ? URL | FormatReturnTy> + : Format extends "urlstring" + ? UrlString | FormatReturnTy> + : never export type Filled = { hostnames: HostnameInfo[] - filter: ( - filter: Filter, + toUrl: (h: HostnameInfo) => UrlString[] + + filter: ( + filter: F, format?: Format, - ) => FormatReturnTy[] + ) => FormatReturnTy[] publicHostnames: HostnameInfo[] onionHostnames: HostnameInfo[] @@ -83,7 +138,7 @@ const negate = const unique = (values: A[]) => Array.from(new Set(values)) export const addressHostToUrl = ( { scheme, sslScheme, username, suffix }: AddressInfo, - host: HostnameInfo, + hostname: HostnameInfo, ): UrlString[] => { const res = [] const fmt = (scheme: string | null, host: HostnameInfo, port: number) => { @@ -109,11 +164,11 @@ export const addressHostToUrl = ( username ? `${username}@` : "" }${hostname}${excludePort ? "" : `:${port}`}${suffix}` } - if (host.hostname.sslPort !== null) { - res.push(fmt(sslScheme, host, host.hostname.sslPort)) + if (hostname.hostname.sslPort !== null) { + res.push(fmt(sslScheme, hostname, hostname.hostname.sslPort)) } - if (host.hostname.port !== null) { - res.push(fmt(scheme, host, host.hostname.port)) + if (hostname.hostname.port !== null) { + res.push(fmt(scheme, hostname, hostname.hostname.port)) } return res @@ -124,6 +179,10 @@ function filterRec( filter: Filter, invert: boolean, ): HostnameInfo[] { + if (filter.predicate) { + const pred = filter.predicate + hostnames = hostnames.filter((h) => invert !== pred(h)) + } if (filter.visibility === "public") hostnames = hostnames.filter( (h) => invert !== (h.kind === "onion" || h.public), @@ -170,13 +229,18 @@ export const filledAddress = ( return { ...addressInfo, hostnames, - filter: (filter: Filter, format?: T) => { - const res = filterRec(hostnames, filter, false) - if (format === "hostname-info") return res as FormatReturnTy[] - const urls = res.flatMap(toUrl) - if (format === "url") - return urls.map((u) => new URL(u)) as FormatReturnTy[] - return urls as FormatReturnTy[] + toUrl, + filter: ( + filter: F, + format?: Format, + ) => { + const filtered = filterRec(hostnames, filter, false) + let res: FormatReturnTy[] = filtered as any + if (format === "hostname-info") return res + const urls = filtered.flatMap(toUrl) + if (format === "url") res = urls.map((u) => new URL(u)) as any + else res = urls as any + return res }, get publicHostnames() { return hostnames.filter((h) => h.kind === "onion" || h.public) diff --git a/sdk/package/lib/mainFn/Daemons.ts b/sdk/package/lib/mainFn/Daemons.ts index 4a8e6110a..3e83c6353 100644 --- a/sdk/package/lib/mainFn/Daemons.ts +++ b/sdk/package/lib/mainFn/Daemons.ts @@ -11,7 +11,7 @@ import * as CP from "node:child_process" export { Daemon } from "./Daemon" export { CommandController } from "./CommandController" -import { HealthDaemon } from "./HealthDaemon" +import { EXIT_SUCCESS, HealthDaemon } from "./HealthDaemon" import { Daemon } from "./Daemon" import { CommandController } from "./CommandController" import { HealthCheck } from "../health/HealthCheck" @@ -91,6 +91,10 @@ type NewDaemonParams< subcontainer: C } +type OptionalParamSync = T | (() => T | null) +type OptionalParamAsync = () => Promise +type OptionalParam = OptionalParamSync | OptionalParamAsync + type AddDaemonParams< Manifest extends T.SDKManifest, Ids extends string, @@ -192,6 +196,37 @@ export class Daemons [], ) } + + private addDaemonImpl( + id: Id, + daemon: Promise< + Daemon | null> + > | null, + requires: Ids[], + ready: Ready | typeof EXIT_SUCCESS, + ) { + const healthDaemon = new HealthDaemon( + daemon, + requires + .map((x) => this.ids.indexOf(x)) + .filter((x) => x >= 0) + .map((id) => this.healthDaemons[id]), + id, + ready, + this.effects, + ) + const daemons = daemon ? [...this.daemons, daemon] : [...this.daemons] + const ids = [...this.ids, id] as (Ids | Id)[] + const healthDaemons = [...this.healthDaemons, healthDaemon] + return new Daemons( + this.effects, + this.started, + daemons, + ids, + healthDaemons, + ) + } + /** * Returns the complete list of daemons, including the one defined here * @param id @@ -205,36 +240,42 @@ export class Daemons ErrorDuplicateId extends Id ? never : Id extends Ids ? ErrorDuplicateId : Id, - options: AddDaemonParams, + options: OptionalParamSync>, + ): Daemons + addDaemon | null>( + // prettier-ignore + id: + "" extends Id ? never : + ErrorDuplicateId extends Id ? never : + Id extends Ids ? ErrorDuplicateId : + Id, + options: OptionalParamAsync>, + ): Promise> + addDaemon | null>( + id: Id, + options: OptionalParam>, ) { - const daemon = - "daemon" in options - ? Promise.resolve(options.daemon) - : Daemon.of()( - this.effects, - options.subcontainer, - options.exec, - ) - const healthDaemon = new HealthDaemon( - daemon, - options.requires - .map((x) => this.ids.indexOf(x)) - .filter((x) => x >= 0) - .map((id) => this.healthDaemons[id]), - id, - options.ready, - this.effects, - ) - const daemons = [...this.daemons, daemon] - const ids = [...this.ids, id] as (Ids | Id)[] - const healthDaemons = [...this.healthDaemons, healthDaemon] - return new Daemons( - this.effects, - this.started, - daemons, - ids, - healthDaemons, - ) + const prev = this + const res = (options: AddDaemonParams | null) => { + if (!options) return prev + const daemon = + "daemon" in options + ? Promise.resolve(options.daemon) + : Daemon.of()( + this.effects, + options.subcontainer, + options.exec, + ) + return prev.addDaemonImpl(id, daemon, options.requires, options.ready) + } + if (options instanceof Function) { + const opts = options() + if (opts instanceof Promise) { + return opts.then(res) + } + return res(opts) + } + return res(options) } /** @@ -245,40 +286,45 @@ export class Daemons * @returns a new Daemons object */ addOneshot | null>( - id: "" extends Id - ? never - : ErrorDuplicateId extends Id - ? never - : Id extends Ids - ? ErrorDuplicateId - : Id, - options: AddOneshotParams, + // prettier-ignore + id: + "" extends Id ? never : + ErrorDuplicateId extends Id ? never : + Id extends Ids ? ErrorDuplicateId : + Id, + options: OptionalParamSync>, + ): Daemons + addOneshot | null>( + // prettier-ignore + id: + "" extends Id ? never : + ErrorDuplicateId extends Id ? never : + Id extends Ids ? ErrorDuplicateId : + Id, + options: OptionalParamAsync>, + ): Promise> + addOneshot | null>( + id: Id, + options: OptionalParam>, ) { - const daemon = Oneshot.of()( - this.effects, - options.subcontainer, - options.exec, - ) - const healthDaemon = new HealthDaemon( - daemon, - options.requires - .map((x) => this.ids.indexOf(x)) - .filter((x) => x >= 0) - .map((id) => this.healthDaemons[id]), - id, - "EXIT_SUCCESS", - this.effects, - ) - const daemons = [...this.daemons, daemon] - const ids = [...this.ids, id] as (Ids | Id)[] - const healthDaemons = [...this.healthDaemons, healthDaemon] - return new Daemons( - this.effects, - this.started, - daemons, - ids, - healthDaemons, - ) + const prev = this + const res = (options: AddOneshotParams | null) => { + if (!options) return prev + const daemon = Oneshot.of()( + this.effects, + options.subcontainer, + options.exec, + ) + return prev.addDaemonImpl(id, daemon, options.requires, EXIT_SUCCESS) + } + if (options instanceof Function) { + const opts = options() + if (opts instanceof Promise) { + return opts.then(res) + } + return res(opts) + } + return res(options) } /** @@ -288,35 +334,40 @@ export class Daemons * @returns a new Daemons object */ addHealthCheck( - id: "" extends Id - ? never - : ErrorDuplicateId extends Id - ? never - : Id extends Ids - ? ErrorDuplicateId - : Id, - options: AddHealthCheckParams, + // prettier-ignore + id: + "" extends Id ? never : + ErrorDuplicateId extends Id ? never : + Id extends Ids ? ErrorDuplicateId : + Id, + options: OptionalParamSync>, + ): Daemons + addHealthCheck( + // prettier-ignore + id: + "" extends Id ? never : + ErrorDuplicateId extends Id ? never : + Id extends Ids ? ErrorDuplicateId : + Id, + options: OptionalParamAsync>, + ): Promise> + addHealthCheck( + id: Id, + options: OptionalParam>, ) { - const healthDaemon = new HealthDaemon( - null, - options.requires - .map((x) => this.ids.indexOf(x)) - .filter((x) => x >= 0) - .map((id) => this.healthDaemons[id]), - id, - options.ready, - this.effects, - ) - const daemons = [...this.daemons] - const ids = [...this.ids, id] as (Ids | Id)[] - const healthDaemons = [...this.healthDaemons, healthDaemon] - return new Daemons( - this.effects, - this.started, - daemons, - ids, - healthDaemons, - ) + const prev = this + const res = (options: AddHealthCheckParams | null) => { + if (!options) return prev + return prev.addDaemonImpl(id, null, options.requires, EXIT_SUCCESS) + } + if (options instanceof Function) { + const opts = options() + if (opts instanceof Promise) { + return opts.then(res) + } + return res(opts) + } + return res(options) } /** diff --git a/sdk/package/package-lock.json b/sdk/package/package-lock.json index bd8865ead..aac4a6885 100644 --- a/sdk/package/package-lock.json +++ b/sdk/package/package-lock.json @@ -1,12 +1,12 @@ { "name": "@start9labs/start-sdk", - "version": "0.4.0-beta.38", + "version": "0.4.0-beta.40", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@start9labs/start-sdk", - "version": "0.4.0-beta.38", + "version": "0.4.0-beta.40", "license": "MIT", "dependencies": { "@iarna/toml": "^3.0.0", diff --git a/sdk/package/package.json b/sdk/package/package.json index 5b445fb26..577f09c57 100644 --- a/sdk/package/package.json +++ b/sdk/package/package.json @@ -1,6 +1,6 @@ { "name": "@start9labs/start-sdk", - "version": "0.4.0-beta.38", + "version": "0.4.0-beta.40", "description": "Software development kit to facilitate packaging services for StartOS", "main": "./package/lib/index.js", "types": "./package/lib/index.d.ts", diff --git a/web/projects/ui/src/app/services/api/api.fixures.ts b/web/projects/ui/src/app/services/api/api.fixures.ts index cbd277a18..1dd87b289 100644 --- a/web/projects/ui/src/app/services/api/api.fixures.ts +++ b/web/projects/ui/src/app/services/api/api.fixures.ts @@ -385,7 +385,7 @@ export namespace Mock { docsUrl: 'https://bitcoin.org', releaseNotes: 'Even better support for Bitcoin and wallets!', osVersion: '0.3.6', - sdkVersion: '0.4.0-beta.38', + sdkVersion: '0.4.0-beta.40', gitHash: 'fakehash', icon: BTC_ICON, sourceVersion: null, @@ -420,7 +420,7 @@ export namespace Mock { docsUrl: 'https://bitcoinknots.org', releaseNotes: 'Even better support for Bitcoin and wallets!', osVersion: '0.3.6', - sdkVersion: '0.4.0-beta.38', + sdkVersion: '0.4.0-beta.40', gitHash: 'fakehash', icon: BTC_ICON, sourceVersion: null, @@ -465,7 +465,7 @@ export namespace Mock { docsUrl: 'https://bitcoin.org', releaseNotes: 'Even better support for Bitcoin and wallets!', osVersion: '0.3.6', - sdkVersion: '0.4.0-beta.38', + sdkVersion: '0.4.0-beta.40', gitHash: 'fakehash', icon: BTC_ICON, sourceVersion: null, @@ -500,7 +500,7 @@ export namespace Mock { docsUrl: 'https://bitcoinknots.org', releaseNotes: 'Even better support for Bitcoin and wallets!', osVersion: '0.3.6', - sdkVersion: '0.4.0-beta.38', + sdkVersion: '0.4.0-beta.40', gitHash: 'fakehash', icon: BTC_ICON, sourceVersion: null, @@ -547,7 +547,7 @@ export namespace Mock { docsUrl: 'https://lightning.engineering/', releaseNotes: 'Upstream release to 0.17.5', osVersion: '0.3.6', - sdkVersion: '0.4.0-beta.38', + sdkVersion: '0.4.0-beta.40', gitHash: 'fakehash', icon: LND_ICON, sourceVersion: null, @@ -595,7 +595,7 @@ export namespace Mock { docsUrl: 'https://lightning.engineering/', releaseNotes: 'Upstream release to 0.17.4', osVersion: '0.3.6', - sdkVersion: '0.4.0-beta.38', + sdkVersion: '0.4.0-beta.40', gitHash: 'fakehash', icon: LND_ICON, sourceVersion: null, @@ -647,7 +647,7 @@ export namespace Mock { docsUrl: 'https://bitcoin.org', releaseNotes: 'Even better support for Bitcoin and wallets!', osVersion: '0.3.6', - sdkVersion: '0.4.0-beta.38', + sdkVersion: '0.4.0-beta.40', gitHash: 'fakehash', icon: BTC_ICON, sourceVersion: null, @@ -682,7 +682,7 @@ export namespace Mock { docsUrl: 'https://bitcoinknots.org', releaseNotes: 'Even better support for Bitcoin and wallets!', osVersion: '0.3.6', - sdkVersion: '0.4.0-beta.38', + sdkVersion: '0.4.0-beta.40', gitHash: 'fakehash', icon: BTC_ICON, sourceVersion: null, @@ -727,7 +727,7 @@ export namespace Mock { docsUrl: 'https://lightning.engineering/', releaseNotes: 'Upstream release and minor fixes.', osVersion: '0.3.6', - sdkVersion: '0.4.0-beta.38', + sdkVersion: '0.4.0-beta.40', gitHash: 'fakehash', icon: LND_ICON, sourceVersion: null, @@ -775,7 +775,7 @@ export namespace Mock { marketingSite: '', releaseNotes: 'Upstream release and minor fixes.', osVersion: '0.3.6', - sdkVersion: '0.4.0-beta.38', + sdkVersion: '0.4.0-beta.40', gitHash: 'fakehash', icon: PROXY_ICON, sourceVersion: null,