sdk updates; beta.39 (#3022)

* sdk updates; beta.39

* beta.40
This commit is contained in:
Aiden McClelland
2025-09-11 15:47:48 -06:00
committed by GitHub
parent 2f6b9dac26
commit 68414678d8
6 changed files with 239 additions and 124 deletions

View File

@@ -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" | "private"> = V extends "public"
? (HostnameInfo & { public: true }) | VisibilityFilter<Exclude<V, "public">>
: V extends "private"
?
| (HostnameInfo & { public: false })
| VisibilityFilter<Exclude<V, "private">>
: never
type KindFilter<K extends FilterKinds> = K extends "onion"
? (HostnameInfo & { kind: "onion" }) | KindFilter<Exclude<K, "onion">>
: K extends "local"
?
| (HostnameInfo & { kind: "ip"; hostname: { kind: "local" } })
| KindFilter<Exclude<K, "local">>
: K extends "domain"
?
| (HostnameInfo & { kind: "ip"; hostname: { kind: "domain" } })
| KindFilter<Exclude<K, "domain">>
: K extends "ipv4"
?
| (HostnameInfo & { kind: "ip"; hostname: { kind: "ipv4" } })
| KindFilter<Exclude<K, "ipv4">>
: K extends "ipv6"
?
| (HostnameInfo & { kind: "ip"; hostname: { kind: "ipv6" } })
| KindFilter<Exclude<K, "ipv6">>
: K extends "ip"
? KindFilter<Exclude<K, "ip"> | "ipv4" | "ipv6">
: never
type FilterReturnTy<F extends Filter> = F extends {
visibility: infer V extends "public" | "private"
}
? VisibilityFilter<V> & FilterReturnTy<Omit<F, "visibility">>
: F extends {
kind: (infer K extends FilterKinds) | (infer K extends FilterKinds)[]
}
? KindFilter<K> & FilterReturnTy<Omit<F, "kind">>
: F extends {
predicate: (h: HostnameInfo) => h is infer H extends HostnameInfo
}
? H & FilterReturnTy<Omit<F, "predicate">>
: F extends { exclude: infer E extends Filter } // MUST BE LAST
? HostnameInfo extends FilterReturnTy<E>
? HostnameInfo
: Exclude<HostnameInfo, FilterReturnTy<E>>
: HostnameInfo
type Formats = "hostname-info" | "urlstring" | "url"
type FormatReturnTy<Format extends Formats> = Format extends "hostname-info"
? HostnameInfo
type FormatReturnTy<
F extends Filter,
Format extends Formats,
> = Format extends "hostname-info"
? FilterReturnTy<F> | FormatReturnTy<F, Exclude<Format, "hostname-info">>
: Format extends "url"
? URL
: UrlString
? URL | FormatReturnTy<F, Exclude<Format, "url">>
: Format extends "urlstring"
? UrlString | FormatReturnTy<F, Exclude<Format, "urlstring">>
: never
export type Filled = {
hostnames: HostnameInfo[]
filter: <Format extends Formats = "urlstring">(
filter: Filter,
toUrl: (h: HostnameInfo) => UrlString[]
filter: <F extends Filter, Format extends Formats = "urlstring">(
filter: F,
format?: Format,
) => FormatReturnTy<Format>[]
) => FormatReturnTy<F, Format>[]
publicHostnames: HostnameInfo[]
onionHostnames: HostnameInfo[]
@@ -83,7 +138,7 @@ const negate =
const unique = <A>(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: <T extends Formats = "urlstring">(filter: Filter, format?: T) => {
const res = filterRec(hostnames, filter, false)
if (format === "hostname-info") return res as FormatReturnTy<T>[]
const urls = res.flatMap(toUrl)
if (format === "url")
return urls.map((u) => new URL(u)) as FormatReturnTy<T>[]
return urls as FormatReturnTy<T>[]
toUrl,
filter: <F extends Filter, Format extends Formats = "urlstring">(
filter: F,
format?: Format,
) => {
const filtered = filterRec(hostnames, filter, false)
let res: FormatReturnTy<F, Format>[] = 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)

View File

@@ -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 | (() => T | null)
type OptionalParamAsync<T> = () => Promise<T | null>
type OptionalParam<T> = OptionalParamSync<T> | OptionalParamAsync<T>
type AddDaemonParams<
Manifest extends T.SDKManifest,
Ids extends string,
@@ -192,6 +196,37 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
[],
)
}
private addDaemonImpl<Id extends string>(
id: Id,
daemon: Promise<
Daemon<Manifest, SubContainer<Manifest, T.Effects> | 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<Manifest, Ids | Id>(
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<Manifest extends T.SDKManifest, Ids extends string>
ErrorDuplicateId<Id> extends Id ? never :
Id extends Ids ? ErrorDuplicateId<Id> :
Id,
options: AddDaemonParams<Manifest, Ids, Id, C>,
options: OptionalParamSync<AddDaemonParams<Manifest, Ids, Id, C>>,
): Daemons<Manifest, Ids | Id>
addDaemon<Id extends string, C extends SubContainer<Manifest> | null>(
// prettier-ignore
id:
"" extends Id ? never :
ErrorDuplicateId<Id> extends Id ? never :
Id extends Ids ? ErrorDuplicateId<Id> :
Id,
options: OptionalParamAsync<AddDaemonParams<Manifest, Ids, Id, C>>,
): Promise<Daemons<Manifest, Ids | Id>>
addDaemon<Id extends string, C extends SubContainer<Manifest> | null>(
id: Id,
options: OptionalParam<AddDaemonParams<Manifest, Ids, Id, C>>,
) {
const daemon =
"daemon" in options
? Promise.resolve(options.daemon)
: Daemon.of<Manifest>()<C>(
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<Manifest, Ids | Id>(
this.effects,
this.started,
daemons,
ids,
healthDaemons,
)
const prev = this
const res = (options: AddDaemonParams<Manifest, Ids, Id, C> | null) => {
if (!options) return prev
const daemon =
"daemon" in options
? Promise.resolve(options.daemon)
: Daemon.of<Manifest>()<C>(
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<Manifest extends T.SDKManifest, Ids extends string>
* @returns a new Daemons object
*/
addOneshot<Id extends string, C extends SubContainer<Manifest> | null>(
id: "" extends Id
? never
: ErrorDuplicateId<Id> extends Id
? never
: Id extends Ids
? ErrorDuplicateId<Id>
: Id,
options: AddOneshotParams<Manifest, Ids, Id, C>,
// prettier-ignore
id:
"" extends Id ? never :
ErrorDuplicateId<Id> extends Id ? never :
Id extends Ids ? ErrorDuplicateId<Id> :
Id,
options: OptionalParamSync<AddOneshotParams<Manifest, Ids, Id, C>>,
): Daemons<Manifest, Ids | Id>
addOneshot<Id extends string, C extends SubContainer<Manifest> | null>(
// prettier-ignore
id:
"" extends Id ? never :
ErrorDuplicateId<Id> extends Id ? never :
Id extends Ids ? ErrorDuplicateId<Id> :
Id,
options: OptionalParamAsync<AddOneshotParams<Manifest, Ids, Id, C>>,
): Promise<Daemons<Manifest, Ids | Id>>
addOneshot<Id extends string, C extends SubContainer<Manifest> | null>(
id: Id,
options: OptionalParam<AddOneshotParams<Manifest, Ids, Id, C>>,
) {
const daemon = Oneshot.of<Manifest>()<C>(
this.effects,
options.subcontainer,
options.exec,
)
const healthDaemon = new HealthDaemon<Manifest>(
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<Manifest, Ids | Id>(
this.effects,
this.started,
daemons,
ids,
healthDaemons,
)
const prev = this
const res = (options: AddOneshotParams<Manifest, Ids, Id, C> | null) => {
if (!options) return prev
const daemon = Oneshot.of<Manifest>()<C>(
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<Manifest extends T.SDKManifest, Ids extends string>
* @returns a new Daemons object
*/
addHealthCheck<Id extends string>(
id: "" extends Id
? never
: ErrorDuplicateId<Id> extends Id
? never
: Id extends Ids
? ErrorDuplicateId<Id>
: Id,
options: AddHealthCheckParams<Ids, Id>,
// prettier-ignore
id:
"" extends Id ? never :
ErrorDuplicateId<Id> extends Id ? never :
Id extends Ids ? ErrorDuplicateId<Id> :
Id,
options: OptionalParamSync<AddHealthCheckParams<Ids, Id>>,
): Daemons<Manifest, Ids | Id>
addHealthCheck<Id extends string>(
// prettier-ignore
id:
"" extends Id ? never :
ErrorDuplicateId<Id> extends Id ? never :
Id extends Ids ? ErrorDuplicateId<Id> :
Id,
options: OptionalParamAsync<AddHealthCheckParams<Ids, Id>>,
): Promise<Daemons<Manifest, Ids | Id>>
addHealthCheck<Id extends string>(
id: Id,
options: OptionalParam<AddHealthCheckParams<Ids, Id>>,
) {
const healthDaemon = new HealthDaemon<Manifest>(
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<Manifest, Ids | Id>(
this.effects,
this.started,
daemons,
ids,
healthDaemons,
)
const prev = this
const res = (options: AddHealthCheckParams<Ids, Id> | 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)
}
/**

View File

@@ -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",

View File

@@ -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",