mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 04:01:58 +00:00
chore: bump sdk to beta.54, add device-info RPC, improve SDK abort handling and InputSpec filtering
- Bump SDK version to 0.4.0-beta.54 - Add `server.device-info` RPC endpoint and `s9pk select` CLI command - Extract `HardwareRequirements::is_compatible()` method, reuse in registry filtering - Add `AbortedError` class with `muteUnhandled` flag, replace generic abort errors - Handle unhandled promise rejections in container-runtime with mute support - Improve `InputSpec.filter()` with `keepByDefault` param and boolean filter values - Accept readonly tuples in `CommandType` and `splitCommand` - Remove `sync_host` calls from host API handlers (binding/address changes) - Filter mDNS hostnames by secure gateway availability - Derive mDNS enabled state from LAN IPs in web UI - Add "Open UI" action to address table, disable mDNS toggle - Hide debug details in service error component - Update rpc-toolkit docs for no-params handlers
This commit is contained in:
@@ -26,23 +26,39 @@ export type LazyBuild<ExpectedOut, Type> = (
|
||||
* Defines which keys to keep when filtering an InputSpec.
|
||||
* Use `true` to keep a field as-is, or a nested object to filter sub-fields of an object-typed field.
|
||||
*/
|
||||
export type FilterKeys<T> = {
|
||||
[K in keyof T]?: T[K] extends Record<string, any>
|
||||
? true | FilterKeys<T[K]>
|
||||
: true
|
||||
export type FilterKeys<F> = {
|
||||
[K in keyof F]?: F[K] extends Record<string, any>
|
||||
? boolean | FilterKeys<F[K]>
|
||||
: boolean
|
||||
}
|
||||
|
||||
type RetainKey<T, F, Default extends boolean> = {
|
||||
[K in keyof T]: K extends keyof F
|
||||
? F[K] extends false
|
||||
? never
|
||||
: K
|
||||
: Default extends true
|
||||
? K
|
||||
: never
|
||||
}[keyof T]
|
||||
|
||||
/**
|
||||
* Computes the resulting type after applying a {@link FilterKeys} shape to a type.
|
||||
*/
|
||||
export type ApplyFilter<T, F> = {
|
||||
[K in Extract<keyof F, keyof T>]: F[K] extends true
|
||||
? T[K]
|
||||
: T[K] extends Record<string, any>
|
||||
? F[K] extends FilterKeys<T[K]>
|
||||
? ApplyFilter<T[K], F[K]>
|
||||
: T[K]
|
||||
: T[K]
|
||||
export type ApplyFilter<T, F, Default extends boolean = false> = {
|
||||
[K in RetainKey<T, F, Default>]: K extends keyof F
|
||||
? true extends F[K]
|
||||
? F[K] extends true
|
||||
? T[K]
|
||||
: T[K] | undefined
|
||||
: T[K] extends Record<string, any>
|
||||
? F[K] extends FilterKeys<T[K]>
|
||||
? ApplyFilter<T[K], F[K]>
|
||||
: undefined
|
||||
: undefined
|
||||
: Default extends true
|
||||
? T[K]
|
||||
: undefined
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -226,14 +242,15 @@ export class InputSpec<
|
||||
* const filtered = full.filter({ name: true, settings: { debug: true } })
|
||||
* ```
|
||||
*/
|
||||
filter<F extends FilterKeys<Type>>(
|
||||
filter<F extends FilterKeys<Type>, Default extends boolean = false>(
|
||||
keys: F,
|
||||
keepByDefault?: Default,
|
||||
): InputSpec<
|
||||
ApplyFilter<Type, F> & ApplyFilter<StaticValidatedAs, F>,
|
||||
ApplyFilter<StaticValidatedAs, F>
|
||||
ApplyFilter<Type, F, Default> & ApplyFilter<StaticValidatedAs, F, Default>,
|
||||
ApplyFilter<StaticValidatedAs, F, Default>
|
||||
> {
|
||||
const newSpec: Record<string, Value<any>> = {}
|
||||
for (const k of Object.keys(keys)) {
|
||||
for (const k of Object.keys(this.spec)) {
|
||||
const filterVal = (keys as any)[k]
|
||||
const value = (this.spec as any)[k] as Value<any> | undefined
|
||||
if (!value) continue
|
||||
@@ -242,11 +259,16 @@ export class InputSpec<
|
||||
} else if (typeof filterVal === 'object' && filterVal !== null) {
|
||||
const objectMeta = value._objectSpec
|
||||
if (objectMeta) {
|
||||
const filteredInner = objectMeta.inputSpec.filter(filterVal)
|
||||
const filteredInner = objectMeta.inputSpec.filter(
|
||||
filterVal,
|
||||
keepByDefault,
|
||||
)
|
||||
newSpec[k] = Value.object(objectMeta.params, filteredInner)
|
||||
} else {
|
||||
newSpec[k] = value
|
||||
}
|
||||
} else if (keepByDefault && filterVal !== false) {
|
||||
newSpec[k] = value
|
||||
}
|
||||
}
|
||||
const newValidator = z.object(
|
||||
|
||||
@@ -145,7 +145,11 @@ export function isUseEntrypoint(
|
||||
* - An explicit argv array
|
||||
* - A {@link UseEntrypoint} to use the container's built-in entrypoint
|
||||
*/
|
||||
export type CommandType = string | [string, ...string[]] | UseEntrypoint
|
||||
export type CommandType =
|
||||
| string
|
||||
| [string, ...string[]]
|
||||
| readonly [string, ...string[]]
|
||||
| UseEntrypoint
|
||||
|
||||
/** The return type from starting a daemon — provides `wait()` and `term()` controls. */
|
||||
export type DaemonReturned = {
|
||||
|
||||
10
sdk/base/lib/util/AbortedError.ts
Normal file
10
sdk/base/lib/util/AbortedError.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export class AbortedError extends Error {
|
||||
readonly muteUnhandled = true as const
|
||||
declare cause?: unknown
|
||||
|
||||
constructor(message?: string, options?: { cause?: unknown }) {
|
||||
super(message)
|
||||
this.name = 'AbortedError'
|
||||
if (options?.cause !== undefined) this.cause = options.cause
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Effects } from '../Effects'
|
||||
import { AbortedError } from './AbortedError'
|
||||
import { DropGenerator, DropPromise } from './Drop'
|
||||
|
||||
export class GetOutboundGateway {
|
||||
@@ -38,7 +39,7 @@ export class GetOutboundGateway {
|
||||
})
|
||||
await waitForNext
|
||||
}
|
||||
return new Promise<never>((_, rej) => rej(new Error('aborted')))
|
||||
return new Promise<never>((_, rej) => rej(new AbortedError()))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Effects } from '../Effects'
|
||||
import * as T from '../types'
|
||||
import { AbortedError } from './AbortedError'
|
||||
import { DropGenerator, DropPromise } from './Drop'
|
||||
|
||||
export class GetSystemSmtp {
|
||||
@@ -39,7 +40,7 @@ export class GetSystemSmtp {
|
||||
})
|
||||
await waitForNext
|
||||
}
|
||||
return new Promise<never>((_, rej) => rej(new Error('aborted')))
|
||||
return new Promise<never>((_, rej) => rej(new AbortedError()))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
HostnameInfo,
|
||||
} from '../types'
|
||||
import { Effects } from '../Effects'
|
||||
import { AbortedError } from './AbortedError'
|
||||
import { DropGenerator, DropPromise } from './Drop'
|
||||
import { IpAddress, IPV6_LINK_LOCAL } from './ip'
|
||||
import { deepEqual } from './deepEqual'
|
||||
@@ -394,7 +395,7 @@ export class GetServiceInterface<Mapped = ServiceInterfaceFilled | null> {
|
||||
}
|
||||
await waitForNext
|
||||
}
|
||||
return new Promise<never>((_, rej) => rej(new Error('aborted')))
|
||||
return new Promise<never>((_, rej) => rej(new AbortedError()))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Effects } from '../Effects'
|
||||
import { PackageId } from '../osBindings'
|
||||
import { AbortedError } from './AbortedError'
|
||||
import { deepEqual } from './deepEqual'
|
||||
import { DropGenerator, DropPromise } from './Drop'
|
||||
import { ServiceInterfaceFilled, filledAddress } from './getServiceInterface'
|
||||
@@ -105,7 +106,7 @@ export class GetServiceInterfaces<Mapped = ServiceInterfaceFilled[]> {
|
||||
}
|
||||
await waitForNext
|
||||
}
|
||||
return new Promise<never>((_, rej) => rej(new Error('aborted')))
|
||||
return new Promise<never>((_, rej) => rej(new AbortedError()))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,5 +22,6 @@ export { splitCommand } from './splitCommand'
|
||||
export { nullIfEmpty } from './nullIfEmpty'
|
||||
export { deepMerge, partialDiff } from './deepMerge'
|
||||
export { deepEqual } from './deepEqual'
|
||||
export { AbortedError } from './AbortedError'
|
||||
export * as regexes from './regexes'
|
||||
export { stringFromStdErrOut } from './stringFromStdErrOut'
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { AllowReadonly } from '../types'
|
||||
|
||||
/**
|
||||
* Normalizes a command into an argv-style string array.
|
||||
* If given a string, wraps it as `["sh", "-c", command]`.
|
||||
@@ -13,8 +15,8 @@
|
||||
* ```
|
||||
*/
|
||||
export const splitCommand = (
|
||||
command: string | [string, ...string[]],
|
||||
command: string | AllowReadonly<[string, ...string[]]>,
|
||||
): string[] => {
|
||||
if (Array.isArray(command)) return command
|
||||
return ['sh', '-c', command]
|
||||
return ['sh', '-c', command as string]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Effects } from '../../../base/lib/Effects'
|
||||
import { Manifest, PackageId } from '../../../base/lib/osBindings'
|
||||
import { AbortedError } from '../../../base/lib/util/AbortedError'
|
||||
import { DropGenerator, DropPromise } from '../../../base/lib/util/Drop'
|
||||
import { deepEqual } from '../../../base/lib/util/deepEqual'
|
||||
|
||||
@@ -64,7 +65,7 @@ export class GetServiceManifest<Mapped = Manifest> {
|
||||
}
|
||||
await waitForNext
|
||||
}
|
||||
return new Promise<never>((_, rej) => rej(new Error('aborted')))
|
||||
return new Promise<never>((_, rej) => rej(new AbortedError()))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { T } from '..'
|
||||
import { Effects } from '../../../base/lib/Effects'
|
||||
import { AbortedError } from '../../../base/lib/util/AbortedError'
|
||||
import { DropGenerator, DropPromise } from '../../../base/lib/util/Drop'
|
||||
|
||||
export class GetSslCertificate {
|
||||
@@ -50,7 +51,7 @@ export class GetSslCertificate {
|
||||
})
|
||||
await waitForNext
|
||||
}
|
||||
return new Promise<never>((_, rej) => rej(new Error('aborted')))
|
||||
return new Promise<never>((_, rej) => rej(new AbortedError()))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as TOML from '@iarna/toml'
|
||||
import * as INI from 'ini'
|
||||
import * as T from '../../../base/lib/types'
|
||||
import * as fs from 'node:fs/promises'
|
||||
import { asError, deepEqual } from '../../../base/lib/util'
|
||||
import { AbortedError, asError, deepEqual } from '../../../base/lib/util'
|
||||
import { DropGenerator, DropPromise } from '../../../base/lib/util/Drop'
|
||||
import { PathBase } from './Volume'
|
||||
|
||||
@@ -289,7 +289,7 @@ export class FileHelper<A> {
|
||||
await onCreated(this.path).catch((e) => console.error(asError(e)))
|
||||
}
|
||||
}
|
||||
return new Promise<never>((_, rej) => rej(new Error('aborted')))
|
||||
return new Promise<never>((_, rej) => rej(new AbortedError()))
|
||||
}
|
||||
|
||||
private readOnChange<B>(
|
||||
|
||||
4
sdk/package/package-lock.json
generated
4
sdk/package/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@start9labs/start-sdk",
|
||||
"version": "0.4.0-beta.53",
|
||||
"version": "0.4.0-beta.54",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@start9labs/start-sdk",
|
||||
"version": "0.4.0-beta.53",
|
||||
"version": "0.4.0-beta.54",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@iarna/toml": "^3.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@start9labs/start-sdk",
|
||||
"version": "0.4.0-beta.53",
|
||||
"version": "0.4.0-beta.54",
|
||||
"description": "Software development kit to facilitate packaging services for StartOS",
|
||||
"main": "./package/lib/index.js",
|
||||
"types": "./package/lib/index.d.ts",
|
||||
|
||||
Reference in New Issue
Block a user