mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +00:00
add error status (#2746)
* add error status * update types * ṗ̶̰̙̓͒̈́ͅü̵̢̙̫̣ŗ̷̪̺̺͛g̴̲͉͎̬̒̇e̵̪̎̅͌ ̶̡̜̘͐͛t̶͎͍̣̿̍̐h̴͕̩͗̈́̎̑e̵͚͒̂͝ ̸̛͙̦͈͝v̶̱͙̬̽̔ọ̶̧̡̒̓i̸̬̲͍̋̈́d̴͉̀ * fix some extra voids * add `package.rebuild` * introduce error status and pkg rebuild and fix mocks * minor fixes * fix build --------- Co-authored-by: Matt Hill <mattnine@protonmail.com>
This commit is contained in:
@@ -31,14 +31,14 @@ export type Effects = {
|
||||
constRetry: () => void
|
||||
clearCallbacks: (
|
||||
options: { only: number[] } | { except: number[] },
|
||||
) => Promise<void>
|
||||
) => Promise<null>
|
||||
|
||||
// action
|
||||
action: {
|
||||
/** Define an action that can be invoked by a user or service */
|
||||
export(options: { id: ActionId; metadata: ActionMetadata }): Promise<void>
|
||||
export(options: { id: ActionId; metadata: ActionMetadata }): Promise<null>
|
||||
/** Remove all exported actions */
|
||||
clear(options: { except: ActionId[] }): Promise<void>
|
||||
clear(options: { except: ActionId[] }): Promise<null>
|
||||
getInput(options: {
|
||||
packageId?: PackageId
|
||||
actionId: ActionId
|
||||
@@ -50,23 +50,23 @@ export type Effects = {
|
||||
}): Promise<ActionResult | null>
|
||||
request<Input extends Record<string, unknown>>(
|
||||
options: RequestActionParams,
|
||||
): Promise<void>
|
||||
): Promise<null>
|
||||
clearRequests(
|
||||
options: { only: ActionId[] } | { except: ActionId[] },
|
||||
): Promise<void>
|
||||
): Promise<null>
|
||||
}
|
||||
|
||||
// control
|
||||
/** restart this service's main function */
|
||||
restart(): Promise<void>
|
||||
restart(): Promise<null>
|
||||
/** stop this service's main function */
|
||||
shutdown(): Promise<void>
|
||||
shutdown(): Promise<null>
|
||||
/** indicate to the host os what runstate the service is in */
|
||||
setMainStatus(options: SetMainStatus): Promise<void>
|
||||
setMainStatus(options: SetMainStatus): Promise<null>
|
||||
|
||||
// dependency
|
||||
/** Set the dependencies of what the service needs, usually run during the inputSpec action as a best practice */
|
||||
setDependencies(options: { dependencies: Dependencies }): Promise<void>
|
||||
setDependencies(options: { dependencies: Dependencies }): Promise<null>
|
||||
/** Get the list of the dependencies, both the dynamic set by the effect of setDependencies and the end result any required in the manifest */
|
||||
getDependencies(): Promise<DependencyRequirement[]>
|
||||
/** Test whether current dependency requirements are satisfied */
|
||||
@@ -86,11 +86,11 @@ export type Effects = {
|
||||
/** Returns a list of the ids of all installed packages */
|
||||
getInstalledPackages(): Promise<string[]>
|
||||
/** grants access to certain paths in the store to dependents */
|
||||
exposeForDependents(options: { paths: string[] }): Promise<void>
|
||||
exposeForDependents(options: { paths: string[] }): Promise<null>
|
||||
|
||||
// health
|
||||
/** sets the result of a health check */
|
||||
setHealth(o: SetHealth): Promise<void>
|
||||
setHealth(o: SetHealth): Promise<null>
|
||||
|
||||
// subcontainer
|
||||
subcontainer: {
|
||||
@@ -100,13 +100,13 @@ export type Effects = {
|
||||
name: string | null
|
||||
}): Promise<[string, string]>
|
||||
/** A low level api used by SubContainer */
|
||||
destroyFs(options: { guid: string }): Promise<void>
|
||||
destroyFs(options: { guid: string }): Promise<null>
|
||||
}
|
||||
|
||||
// net
|
||||
// bind
|
||||
/** Creates a host connected to the specified port with the provided options */
|
||||
bind(options: BindParams): Promise<void>
|
||||
bind(options: BindParams): Promise<null>
|
||||
/** Get the port address for a service */
|
||||
getServicePortForward(options: {
|
||||
packageId?: PackageId
|
||||
@@ -116,7 +116,7 @@ export type Effects = {
|
||||
/** Removes all network bindings, called in the setupInputSpec */
|
||||
clearBindings(options: {
|
||||
except: { id: HostId; internalPort: number }[]
|
||||
}): Promise<void>
|
||||
}): Promise<null>
|
||||
// host
|
||||
/** Returns information about the specified host, if it exists */
|
||||
getHostInfo(options: {
|
||||
@@ -134,7 +134,7 @@ export type Effects = {
|
||||
getContainerIp(): Promise<string>
|
||||
// interface
|
||||
/** Creates an interface bound to a specific host and port to show to the user */
|
||||
exportServiceInterface(options: ExportServiceInterfaceParams): Promise<void>
|
||||
exportServiceInterface(options: ExportServiceInterfaceParams): Promise<null>
|
||||
/** Returns an exported service interface */
|
||||
getServiceInterface(options: {
|
||||
packageId?: PackageId
|
||||
@@ -149,7 +149,7 @@ export type Effects = {
|
||||
/** Removes all service interfaces */
|
||||
clearServiceInterfaces(options: {
|
||||
except: ServiceInterfaceId[]
|
||||
}): Promise<void>
|
||||
}): Promise<null>
|
||||
// ssl
|
||||
/** Returns a PEM encoded fullchain for the hostnames specified */
|
||||
getSslCertificate: (options: {
|
||||
@@ -178,10 +178,10 @@ export type Effects = {
|
||||
/** Sets the value for the wrapper at the path, it will override, using the [JsonPath](https://jsonpath.com/) */
|
||||
path: StorePath
|
||||
value: ExtractStore
|
||||
}): Promise<void>
|
||||
}): Promise<null>
|
||||
}
|
||||
/** sets the version that this service's data has been migrated to */
|
||||
setDataVersion(options: { version: string }): Promise<void>
|
||||
setDataVersion(options: { version: string }): Promise<null>
|
||||
/** returns the version that this service's data has been migrated to */
|
||||
getDataVersion(): Promise<string | null>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { InputSpec } from "./input/builder"
|
||||
import { ExtractInputSpecType } from "./input/builder/inputSpec"
|
||||
import * as T from "../types"
|
||||
import { once } from "../util"
|
||||
|
||||
export type Run<
|
||||
A extends
|
||||
@@ -130,21 +131,19 @@ export class Actions<
|
||||
): Actions<Store, AllActions & { [id in A["id"]]: A }> {
|
||||
return new Actions({ ...this.actions, [action.id]: action })
|
||||
}
|
||||
update(options: { effects: T.Effects }): Promise<void> {
|
||||
const updater = async (options: { effects: T.Effects }) => {
|
||||
for (let action of Object.values(this.actions)) {
|
||||
await action.exportMetadata(options)
|
||||
}
|
||||
await options.effects.action.clear({ except: Object.keys(this.actions) })
|
||||
async update(options: { effects: T.Effects }): Promise<null> {
|
||||
options.effects = {
|
||||
...options.effects,
|
||||
constRetry: once(() => {
|
||||
this.update(options) // yes, this reuses the options object, but the const retry function will be overwritten each time, so the once-ness is not a problem
|
||||
}),
|
||||
}
|
||||
const updaterCtx = { options }
|
||||
updaterCtx.options = {
|
||||
effects: {
|
||||
...options.effects,
|
||||
constRetry: () => updater(updaterCtx.options),
|
||||
},
|
||||
for (let action of Object.values(this.actions)) {
|
||||
await action.exportMetadata(options)
|
||||
}
|
||||
return updater(updaterCtx.options)
|
||||
await options.effects.action.clear({ except: Object.keys(this.actions) })
|
||||
|
||||
return null
|
||||
}
|
||||
get<Id extends T.ActionId>(actionId: Id): AllActions[Id] {
|
||||
return this.actions[actionId]
|
||||
|
||||
@@ -13,15 +13,15 @@ export type CheckDependencies<DependencyId extends PackageId = PackageId> = {
|
||||
) => boolean
|
||||
satisfied: () => boolean
|
||||
|
||||
throwIfInstalledNotSatisfied: (packageId: DependencyId) => void
|
||||
throwIfInstalledVersionNotSatisfied: (packageId: DependencyId) => void
|
||||
throwIfRunningNotSatisfied: (packageId: DependencyId) => void
|
||||
throwIfActionsNotSatisfied: (packageId: DependencyId) => void
|
||||
throwIfInstalledNotSatisfied: (packageId: DependencyId) => null
|
||||
throwIfInstalledVersionNotSatisfied: (packageId: DependencyId) => null
|
||||
throwIfRunningNotSatisfied: (packageId: DependencyId) => null
|
||||
throwIfActionsNotSatisfied: (packageId: DependencyId) => null
|
||||
throwIfHealthNotSatisfied: (
|
||||
packageId: DependencyId,
|
||||
healthCheckId?: HealthCheckId,
|
||||
) => void
|
||||
throwIfNotSatisfied: (packageId?: DependencyId) => void
|
||||
) => null
|
||||
throwIfNotSatisfied: (packageId?: DependencyId) => null
|
||||
}
|
||||
export async function checkDependencies<
|
||||
DependencyId extends PackageId = PackageId,
|
||||
@@ -100,6 +100,7 @@ export async function checkDependencies<
|
||||
if (!dep.result.installedVersion) {
|
||||
throw new Error(`${dep.result.title || packageId} is not installed`)
|
||||
}
|
||||
return null
|
||||
}
|
||||
const throwIfInstalledVersionNotSatisfied = (packageId: DependencyId) => {
|
||||
const dep = find(packageId)
|
||||
@@ -117,12 +118,14 @@ export async function checkDependencies<
|
||||
`Installed version ${dep.result.installedVersion} of ${dep.result.title || packageId} does not match expected version range ${dep.requirement.versionRange}`,
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
const throwIfRunningNotSatisfied = (packageId: DependencyId) => {
|
||||
const dep = find(packageId)
|
||||
if (dep.requirement.kind === "running" && !dep.result.isRunning) {
|
||||
throw new Error(`${dep.result.title || packageId} is not running`)
|
||||
}
|
||||
return null
|
||||
}
|
||||
const throwIfActionsNotSatisfied = (packageId: DependencyId) => {
|
||||
const dep = find(packageId)
|
||||
@@ -132,6 +135,7 @@ export async function checkDependencies<
|
||||
`The following action requests have not been fulfilled: ${reqs.join(", ")}`,
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
const throwIfHealthNotSatisfied = (
|
||||
packageId: DependencyId,
|
||||
@@ -158,6 +162,7 @@ export async function checkDependencies<
|
||||
.join("; "),
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
const throwIfPkgNotSatisfied = (packageId: DependencyId) => {
|
||||
throwIfInstalledNotSatisfied(packageId)
|
||||
@@ -165,6 +170,7 @@ export async function checkDependencies<
|
||||
throwIfRunningNotSatisfied(packageId)
|
||||
throwIfActionsNotSatisfied(packageId)
|
||||
throwIfHealthNotSatisfied(packageId)
|
||||
return null
|
||||
}
|
||||
const throwIfNotSatisfied = (packageId?: DependencyId) =>
|
||||
packageId
|
||||
@@ -182,6 +188,7 @@ export async function checkDependencies<
|
||||
if (err.length) {
|
||||
throw new Error(err.join("; "))
|
||||
}
|
||||
return null
|
||||
})()
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as T from "../types"
|
||||
import { once } from "../util"
|
||||
import { Dependency } from "./Dependency"
|
||||
|
||||
type DependencyType<Manifest extends T.Manifest> = {
|
||||
@@ -17,40 +18,38 @@ type DependencyType<Manifest extends T.Manifest> = {
|
||||
|
||||
export function setupDependencies<Manifest extends T.Manifest>(
|
||||
fn: (options: { effects: T.Effects }) => Promise<DependencyType<Manifest>>,
|
||||
): (options: { effects: T.Effects }) => Promise<void> {
|
||||
return (options: { effects: T.Effects }) => {
|
||||
const updater = async (options: { effects: T.Effects }) => {
|
||||
const dependencyType = await fn(options)
|
||||
return await options.effects.setDependencies({
|
||||
dependencies: Object.entries(dependencyType).map(
|
||||
([
|
||||
id,
|
||||
{
|
||||
data: { versionRange, ...x },
|
||||
},
|
||||
]) => ({
|
||||
id,
|
||||
...x,
|
||||
...(x.type === "running"
|
||||
? {
|
||||
kind: "running",
|
||||
healthChecks: x.healthChecks,
|
||||
}
|
||||
: {
|
||||
kind: "exists",
|
||||
}),
|
||||
versionRange: versionRange.toString(),
|
||||
}),
|
||||
),
|
||||
})
|
||||
): (options: { effects: T.Effects }) => Promise<null> {
|
||||
const cell = { updater: async (_: { effects: T.Effects }) => null }
|
||||
cell.updater = async (options: { effects: T.Effects }) => {
|
||||
options.effects = {
|
||||
...options.effects,
|
||||
constRetry: once(() => {
|
||||
cell.updater(options)
|
||||
}),
|
||||
}
|
||||
const updaterCtx = { options }
|
||||
updaterCtx.options = {
|
||||
effects: {
|
||||
...options.effects,
|
||||
constRetry: () => updater(updaterCtx.options),
|
||||
},
|
||||
}
|
||||
return updater(updaterCtx.options)
|
||||
const dependencyType = await fn(options)
|
||||
return await options.effects.setDependencies({
|
||||
dependencies: Object.entries(dependencyType).map(
|
||||
([
|
||||
id,
|
||||
{
|
||||
data: { versionRange, ...x },
|
||||
},
|
||||
]) => ({
|
||||
id,
|
||||
...x,
|
||||
...(x.type === "running"
|
||||
? {
|
||||
kind: "running",
|
||||
healthChecks: x.healthChecks,
|
||||
}
|
||||
: {
|
||||
kind: "exists",
|
||||
}),
|
||||
versionRange: versionRange.toString(),
|
||||
}),
|
||||
),
|
||||
})
|
||||
}
|
||||
return cell.updater
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as T from "../types"
|
||||
import { once } from "../util"
|
||||
import { AddressReceipt } from "./AddressReceipt"
|
||||
|
||||
declare const UpdateServiceInterfacesProof: unique symbol
|
||||
@@ -21,34 +22,36 @@ export const setupServiceInterfaces: SetupServiceInterfaces = <
|
||||
Output extends ServiceInterfacesReceipt,
|
||||
>(
|
||||
fn: SetServiceInterfaces<Output>,
|
||||
) =>
|
||||
((options: { effects: T.Effects }) => {
|
||||
const updater = async (options: { effects: T.Effects }) => {
|
||||
const bindings: T.BindId[] = []
|
||||
const interfaces: T.ServiceInterfaceId[] = []
|
||||
const res = await fn({
|
||||
effects: {
|
||||
...options.effects,
|
||||
bind: (params: T.BindParams) => {
|
||||
bindings.push({ id: params.id, internalPort: params.internalPort })
|
||||
return options.effects.bind(params)
|
||||
},
|
||||
exportServiceInterface: (params: T.ExportServiceInterfaceParams) => {
|
||||
interfaces.push(params.id)
|
||||
return options.effects.exportServiceInterface(params)
|
||||
},
|
||||
},
|
||||
})
|
||||
await options.effects.clearBindings({ except: bindings })
|
||||
await options.effects.clearServiceInterfaces({ except: interfaces })
|
||||
return res
|
||||
) => {
|
||||
const cell = {
|
||||
updater: (async (options: { effects: T.Effects }) =>
|
||||
[] as any as Output) as UpdateServiceInterfaces<Output>,
|
||||
}
|
||||
cell.updater = (async (options: { effects: T.Effects }) => {
|
||||
options.effects = {
|
||||
...options.effects,
|
||||
constRetry: once(() => {
|
||||
cell.updater(options)
|
||||
}),
|
||||
}
|
||||
const updaterCtx = { options }
|
||||
updaterCtx.options = {
|
||||
const bindings: T.BindId[] = []
|
||||
const interfaces: T.ServiceInterfaceId[] = []
|
||||
const res = await fn({
|
||||
effects: {
|
||||
...options.effects,
|
||||
constRetry: () => updater(updaterCtx.options),
|
||||
bind: (params: T.BindParams) => {
|
||||
bindings.push({ id: params.id, internalPort: params.internalPort })
|
||||
return options.effects.bind(params)
|
||||
},
|
||||
exportServiceInterface: (params: T.ExportServiceInterfaceParams) => {
|
||||
interfaces.push(params.id)
|
||||
return options.effects.exportServiceInterface(params)
|
||||
},
|
||||
},
|
||||
}
|
||||
return updater(updaterCtx.options)
|
||||
})
|
||||
await options.effects.clearBindings({ except: bindings })
|
||||
await options.effects.clearServiceInterfaces({ except: interfaces })
|
||||
return res
|
||||
}) as UpdateServiceInterfaces<Output>
|
||||
return cell.updater
|
||||
}
|
||||
|
||||
@@ -4,6 +4,12 @@ import type { NamedHealthCheckResult } from "./NamedHealthCheckResult"
|
||||
import type { StartStop } from "./StartStop"
|
||||
|
||||
export type MainStatus =
|
||||
| {
|
||||
main: "error"
|
||||
onRebuild: StartStop
|
||||
message: string
|
||||
debug: string | null
|
||||
}
|
||||
| { main: "stopped" }
|
||||
| { main: "restarting" }
|
||||
| { main: "restoring" }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DataUrl, Manifest, MerkleArchiveCommitment } from "../osBindings"
|
||||
import { ArrayBufferReader, MerkleArchive } from "./merkleArchive"
|
||||
import mime from "mime"
|
||||
import mime from "mime-types"
|
||||
|
||||
const magicAndVersion = new Uint8Array([59, 59, 2])
|
||||
|
||||
@@ -52,13 +52,14 @@ export class S9pk {
|
||||
async icon(): Promise<DataUrl> {
|
||||
const iconName = Object.keys(this.archive.contents.contents).find(
|
||||
(name) =>
|
||||
name.startsWith("icon.") && mime.getType(name)?.startsWith("image/"),
|
||||
name.startsWith("icon.") &&
|
||||
(mime.contentType(name) || null)?.startsWith("image/"),
|
||||
)
|
||||
if (!iconName) {
|
||||
throw new Error("no icon found in archive")
|
||||
}
|
||||
return (
|
||||
`data:${mime.getType(iconName)};base64,` +
|
||||
`data:${mime.contentType(iconName)};base64,` +
|
||||
Buffer.from(
|
||||
await this.archive.contents.getPath([iconName])!.verifiedFileContents(),
|
||||
).toString("base64")
|
||||
|
||||
@@ -54,7 +54,7 @@ export namespace ExpectedExports {
|
||||
*/
|
||||
export type main = (options: {
|
||||
effects: Effects
|
||||
started(onTerm: () => PromiseLike<void>): PromiseLike<void>
|
||||
started(onTerm: () => PromiseLike<void>): PromiseLike<null>
|
||||
}) => Promise<DaemonBuildable>
|
||||
|
||||
/**
|
||||
@@ -118,7 +118,7 @@ export type DaemonReceipt = {
|
||||
}
|
||||
export type Daemon = {
|
||||
wait(): Promise<string>
|
||||
term(): Promise<void>
|
||||
term(): Promise<null>
|
||||
[DaemonProof]: never
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ export type CommandType = string | [string, ...string[]]
|
||||
|
||||
export type DaemonReturned = {
|
||||
wait(): Promise<unknown>
|
||||
term(options?: { signal?: Signals; timeout?: number }): Promise<void>
|
||||
term(options?: { signal?: Signals; timeout?: number }): Promise<null>
|
||||
}
|
||||
|
||||
export declare const hostName: unique symbol
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { boolean } from "ts-matches"
|
||||
|
||||
export type Vertex<VMetadata = void, EMetadata = void> = {
|
||||
export type Vertex<VMetadata = null, EMetadata = null> = {
|
||||
metadata: VMetadata
|
||||
edges: Array<Edge<EMetadata, VMetadata>>
|
||||
}
|
||||
|
||||
export type Edge<EMetadata = void, VMetadata = void> = {
|
||||
export type Edge<EMetadata = null, VMetadata = null> = {
|
||||
metadata: EMetadata
|
||||
from: Vertex<VMetadata, EMetadata>
|
||||
to: Vertex<VMetadata, EMetadata>
|
||||
}
|
||||
|
||||
export class Graph<VMetadata = void, EMetadata = void> {
|
||||
export class Graph<VMetadata = null, EMetadata = null> {
|
||||
private readonly vertices: Array<Vertex<VMetadata, EMetadata>> = []
|
||||
constructor() {}
|
||||
addVertex(
|
||||
@@ -46,7 +46,7 @@ export class Graph<VMetadata = void, EMetadata = void> {
|
||||
}
|
||||
findVertex(
|
||||
predicate: (vertex: Vertex<VMetadata, EMetadata>) => boolean,
|
||||
): Generator<Vertex<VMetadata, EMetadata>, void> {
|
||||
): Generator<Vertex<VMetadata, EMetadata>, null> {
|
||||
const veritces = this.vertices
|
||||
function* gen() {
|
||||
for (let vertex of veritces) {
|
||||
@@ -54,6 +54,7 @@ export class Graph<VMetadata = void, EMetadata = void> {
|
||||
yield vertex
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
return gen()
|
||||
}
|
||||
@@ -75,13 +76,13 @@ export class Graph<VMetadata = void, EMetadata = void> {
|
||||
from:
|
||||
| Vertex<VMetadata, EMetadata>
|
||||
| ((vertex: Vertex<VMetadata, EMetadata>) => boolean),
|
||||
): Generator<Vertex<VMetadata, EMetadata>, void> {
|
||||
): Generator<Vertex<VMetadata, EMetadata>, null> {
|
||||
const visited: Array<Vertex<VMetadata, EMetadata>> = []
|
||||
function* rec(
|
||||
vertex: Vertex<VMetadata, EMetadata>,
|
||||
): Generator<Vertex<VMetadata, EMetadata>, void> {
|
||||
): Generator<Vertex<VMetadata, EMetadata>, null> {
|
||||
if (visited.includes(vertex)) {
|
||||
return
|
||||
return null
|
||||
}
|
||||
visited.push(vertex)
|
||||
yield vertex
|
||||
@@ -99,6 +100,7 @@ export class Graph<VMetadata = void, EMetadata = void> {
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
if (from instanceof Function) {
|
||||
@@ -115,6 +117,7 @@ export class Graph<VMetadata = void, EMetadata = void> {
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
})()
|
||||
} else {
|
||||
return rec(from)
|
||||
@@ -124,13 +127,13 @@ export class Graph<VMetadata = void, EMetadata = void> {
|
||||
to:
|
||||
| Vertex<VMetadata, EMetadata>
|
||||
| ((vertex: Vertex<VMetadata, EMetadata>) => boolean),
|
||||
): Generator<Vertex<VMetadata, EMetadata>, void> {
|
||||
): Generator<Vertex<VMetadata, EMetadata>, null> {
|
||||
const visited: Array<Vertex<VMetadata, EMetadata>> = []
|
||||
function* rec(
|
||||
vertex: Vertex<VMetadata, EMetadata>,
|
||||
): Generator<Vertex<VMetadata, EMetadata>, void> {
|
||||
): Generator<Vertex<VMetadata, EMetadata>, null> {
|
||||
if (visited.includes(vertex)) {
|
||||
return
|
||||
return null
|
||||
}
|
||||
visited.push(vertex)
|
||||
yield vertex
|
||||
@@ -148,6 +151,7 @@ export class Graph<VMetadata = void, EMetadata = void> {
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
if (to instanceof Function) {
|
||||
@@ -164,6 +168,7 @@ export class Graph<VMetadata = void, EMetadata = void> {
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
})()
|
||||
} else {
|
||||
return rec(to)
|
||||
@@ -176,7 +181,7 @@ export class Graph<VMetadata = void, EMetadata = void> {
|
||||
to:
|
||||
| Vertex<VMetadata, EMetadata>
|
||||
| ((vertex: Vertex<VMetadata, EMetadata>) => boolean),
|
||||
): Array<Edge<EMetadata, VMetadata>> | void {
|
||||
): Array<Edge<EMetadata, VMetadata>> | null {
|
||||
const isDone =
|
||||
to instanceof Function
|
||||
? to
|
||||
@@ -186,12 +191,12 @@ export class Graph<VMetadata = void, EMetadata = void> {
|
||||
function* check(
|
||||
vertex: Vertex<VMetadata, EMetadata>,
|
||||
path: Array<Edge<EMetadata, VMetadata>>,
|
||||
): Generator<undefined, Array<Edge<EMetadata, VMetadata>> | undefined> {
|
||||
): Generator<undefined, Array<Edge<EMetadata, VMetadata>> | null> {
|
||||
if (isDone(vertex)) {
|
||||
return path
|
||||
}
|
||||
if (visited.includes(vertex)) {
|
||||
return
|
||||
return null
|
||||
}
|
||||
visited.push(vertex)
|
||||
yield
|
||||
@@ -213,6 +218,7 @@ export class Graph<VMetadata = void, EMetadata = void> {
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
if (from instanceof Function) {
|
||||
@@ -240,5 +246,6 @@ export class Graph<VMetadata = void, EMetadata = void> {
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
37
sdk/base/package-lock.json
generated
37
sdk/base/package-lock.json
generated
@@ -13,13 +13,14 @@
|
||||
"@noble/hashes": "^1.4.0",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"mime": "^4.0.3",
|
||||
"mime-types": "^2.1.35",
|
||||
"ts-matches": "^5.5.1",
|
||||
"yaml": "^2.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.4.0",
|
||||
"@types/lodash.merge": "^4.6.2",
|
||||
"@types/mime-types": "^2.1.4",
|
||||
"jest": "^29.4.3",
|
||||
"peggy": "^3.0.2",
|
||||
"prettier": "^3.2.5",
|
||||
@@ -1249,6 +1250,13 @@
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/mime-types": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz",
|
||||
"integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "18.15.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.10.tgz",
|
||||
@@ -3106,18 +3114,25 @@
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-4.0.3.tgz",
|
||||
"integrity": "sha512-KgUb15Oorc0NEKPbvfa0wRU+PItIEZmiv+pyAO2i0oTIVTJhlzMclU7w4RXWQrSOVH5ax/p/CkIO7KI4OyFJTQ==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa"
|
||||
],
|
||||
"bin": {
|
||||
"mime": "bin/cli.js"
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-fn": {
|
||||
|
||||
@@ -21,14 +21,14 @@
|
||||
},
|
||||
"homepage": "https://github.com/Start9Labs/start-sdk#readme",
|
||||
"dependencies": {
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"mime": "^4.0.3",
|
||||
"ts-matches": "^5.5.1",
|
||||
"yaml": "^2.2.2",
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@noble/curves": "^1.4.0",
|
||||
"@noble/hashes": "^1.4.0"
|
||||
"@noble/hashes": "^1.4.0",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"mime-types": "^2.1.35",
|
||||
"ts-matches": "^5.5.1",
|
||||
"yaml": "^2.2.2"
|
||||
},
|
||||
"prettier": {
|
||||
"trailingComma": "all",
|
||||
@@ -39,6 +39,7 @@
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.4.0",
|
||||
"@types/lodash.merge": "^4.6.2",
|
||||
"@types/mime-types": "^2.1.4",
|
||||
"jest": "^29.4.3",
|
||||
"peggy": "^3.0.2",
|
||||
"prettier": "^3.2.5",
|
||||
|
||||
@@ -563,7 +563,7 @@ export class StartSdk<Manifest extends T.Manifest, Store> {
|
||||
setupMain: (
|
||||
fn: (o: {
|
||||
effects: Effects
|
||||
started(onTerm: () => PromiseLike<void>): PromiseLike<void>
|
||||
started(onTerm: () => PromiseLike<void>): PromiseLike<null>
|
||||
}) => Promise<Daemons<Manifest, any>>,
|
||||
) => setupMain<Manifest, Store>(fn),
|
||||
/**
|
||||
@@ -657,12 +657,12 @@ export class StartSdk<Manifest extends T.Manifest, Store> {
|
||||
) => InputSpec.of<Spec, Store>(spec),
|
||||
},
|
||||
Daemons: {
|
||||
of(inputSpec: {
|
||||
of(options: {
|
||||
effects: Effects
|
||||
started: (onTerm: () => PromiseLike<void>) => PromiseLike<void>
|
||||
started: (onTerm: () => PromiseLike<void>) => PromiseLike<null>
|
||||
healthReceipts: HealthReceipt[]
|
||||
}) {
|
||||
return Daemons.of<Manifest>(inputSpec)
|
||||
return Daemons.of<Manifest>(options)
|
||||
},
|
||||
},
|
||||
List: {
|
||||
|
||||
@@ -12,7 +12,7 @@ export function setupInit<Manifest extends T.Manifest, Store>(
|
||||
install: Install<Manifest, Store>,
|
||||
uninstall: Uninstall<Manifest, Store>,
|
||||
setServiceInterfaces: UpdateServiceInterfaces<any>,
|
||||
setDependencies: (options: { effects: T.Effects }) => Promise<void>,
|
||||
setDependencies: (options: { effects: T.Effects }) => Promise<null>,
|
||||
actions: Actions<Store, any>,
|
||||
exposedStore: ExposedStorePaths,
|
||||
): {
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as T from "../../../base/lib/types"
|
||||
|
||||
export type InstallFn<Manifest extends T.Manifest, Store> = (opts: {
|
||||
effects: T.Effects
|
||||
}) => Promise<void>
|
||||
}) => Promise<null>
|
||||
export class Install<Manifest extends T.Manifest, Store> {
|
||||
private constructor(readonly fn: InstallFn<Manifest, Store>) {}
|
||||
static of<Manifest extends T.Manifest, Store>(
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as T from "../../../base/lib/types"
|
||||
|
||||
export type UninstallFn<Manifest extends T.Manifest, Store> = (opts: {
|
||||
effects: T.Effects
|
||||
}) => Promise<void>
|
||||
}) => Promise<null>
|
||||
export class Uninstall<Manifest extends T.Manifest, Store> {
|
||||
private constructor(readonly fn: UninstallFn<Manifest, Store>) {}
|
||||
static of<Manifest extends T.Manifest, Store>(
|
||||
|
||||
@@ -43,8 +43,8 @@ export class CommandController {
|
||||
| undefined
|
||||
cwd?: string | undefined
|
||||
user?: string | undefined
|
||||
onStdout?: (x: Buffer) => void
|
||||
onStderr?: (x: Buffer) => void
|
||||
onStdout?: (x: Buffer) => null
|
||||
onStderr?: (x: Buffer) => null
|
||||
},
|
||||
) => {
|
||||
const commands = splitCommand(command)
|
||||
|
||||
@@ -37,8 +37,8 @@ export class Daemon {
|
||||
| undefined
|
||||
cwd?: string | undefined
|
||||
user?: string | undefined
|
||||
onStdout?: (x: Buffer) => void
|
||||
onStderr?: (x: Buffer) => void
|
||||
onStdout?: (x: Buffer) => null
|
||||
onStderr?: (x: Buffer) => null
|
||||
sigtermTimeout?: number
|
||||
},
|
||||
) => {
|
||||
|
||||
@@ -74,7 +74,7 @@ export class Daemons<Manifest extends T.Manifest, Ids extends string>
|
||||
{
|
||||
private constructor(
|
||||
readonly effects: T.Effects,
|
||||
readonly started: (onTerm: () => PromiseLike<void>) => PromiseLike<void>,
|
||||
readonly started: (onTerm: () => PromiseLike<void>) => PromiseLike<null>,
|
||||
readonly daemons: Promise<Daemon>[],
|
||||
readonly ids: Ids[],
|
||||
readonly healthDaemons: HealthDaemon[],
|
||||
@@ -86,17 +86,17 @@ export class Daemons<Manifest extends T.Manifest, Ids extends string>
|
||||
*
|
||||
* Daemons run in the order they are defined, with latter daemons being capable of
|
||||
* depending on prior daemons
|
||||
* @param inputSpec
|
||||
* @param options
|
||||
* @returns
|
||||
*/
|
||||
static of<Manifest extends T.Manifest>(inputSpec: {
|
||||
static of<Manifest extends T.Manifest>(options: {
|
||||
effects: T.Effects
|
||||
started: (onTerm: () => PromiseLike<void>) => PromiseLike<void>
|
||||
started: (onTerm: () => PromiseLike<void>) => PromiseLike<null>
|
||||
healthReceipts: HealthReceipt[]
|
||||
}) {
|
||||
return new Daemons<Manifest, never>(
|
||||
inputSpec.effects,
|
||||
inputSpec.started,
|
||||
options.effects,
|
||||
options.started,
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
|
||||
@@ -81,7 +81,7 @@ export class HealthDaemon {
|
||||
}
|
||||
}
|
||||
|
||||
private healthCheckCleanup: (() => void) | null = null
|
||||
private healthCheckCleanup: (() => null) | null = null
|
||||
private turnOffHealthCheck() {
|
||||
this.healthCheckCleanup?.()
|
||||
}
|
||||
@@ -125,6 +125,7 @@ export class HealthDaemon {
|
||||
this.healthCheckCleanup = () => {
|
||||
setStatus({ done: true })
|
||||
this.healthCheckCleanup = null
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ export const DEFAULT_SIGTERM_TIMEOUT = 30_000
|
||||
export const setupMain = <Manifest extends T.Manifest, Store>(
|
||||
fn: (o: {
|
||||
effects: T.Effects
|
||||
started(onTerm: () => PromiseLike<void>): PromiseLike<void>
|
||||
started(onTerm: () => PromiseLike<void>): PromiseLike<null>
|
||||
}) => Promise<Daemons<Manifest, any>>,
|
||||
): T.ExpectedExports.main => {
|
||||
return async (options) => {
|
||||
|
||||
@@ -27,7 +27,7 @@ const TIMES_TO_WAIT_FOR_PROC = 100
|
||||
* case where the subcontainer isn't owned by the process, the subcontainer shouldn't be destroyed.
|
||||
*/
|
||||
export interface ExecSpawnable {
|
||||
get destroy(): undefined | (() => Promise<void>)
|
||||
get destroy(): undefined | (() => Promise<null>)
|
||||
exec(
|
||||
command: string[],
|
||||
options?: CommandOptions & ExecOptions,
|
||||
@@ -47,7 +47,7 @@ export interface ExecSpawnable {
|
||||
export class SubContainer implements ExecSpawnable {
|
||||
private leader: cp.ChildProcess
|
||||
private leaderExited: boolean = false
|
||||
private waitProc: () => Promise<void>
|
||||
private waitProc: () => Promise<null>
|
||||
private constructor(
|
||||
readonly effects: T.Effects,
|
||||
readonly imageId: T.ImageId,
|
||||
@@ -79,7 +79,7 @@ export class SubContainer implements ExecSpawnable {
|
||||
}
|
||||
await wait(1)
|
||||
}
|
||||
resolve()
|
||||
resolve(null)
|
||||
}),
|
||||
)
|
||||
}
|
||||
@@ -180,12 +180,12 @@ export class SubContainer implements ExecSpawnable {
|
||||
if (this.leaderExited) {
|
||||
return
|
||||
}
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
return new Promise<null>((resolve, reject) => {
|
||||
try {
|
||||
let timeout = setTimeout(() => this.leader.kill("SIGKILL"), 30000)
|
||||
this.leader.on("exit", () => {
|
||||
clearTimeout(timeout)
|
||||
resolve()
|
||||
resolve(null)
|
||||
})
|
||||
if (!this.leader.kill("SIGTERM")) {
|
||||
reject(new Error("kill(2) failed"))
|
||||
@@ -201,6 +201,7 @@ export class SubContainer implements ExecSpawnable {
|
||||
const guid = this.guid
|
||||
await this.killLeader()
|
||||
await this.effects.subcontainer.destroyFs({ guid })
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,16 +246,16 @@ export class SubContainer implements ExecSpawnable {
|
||||
options || {},
|
||||
)
|
||||
if (options?.input) {
|
||||
await new Promise<void>((resolve, reject) =>
|
||||
await new Promise<null>((resolve, reject) =>
|
||||
child.stdin.write(options.input, (e) => {
|
||||
if (e) {
|
||||
reject(e)
|
||||
} else {
|
||||
resolve()
|
||||
resolve(null)
|
||||
}
|
||||
}),
|
||||
)
|
||||
await new Promise<void>((resolve) => child.stdin.end(resolve))
|
||||
await new Promise<null>((resolve) => child.stdin.end(resolve))
|
||||
}
|
||||
const pid = child.pid
|
||||
const stdout = { data: "" as string | Buffer }
|
||||
|
||||
Reference in New Issue
Block a user