mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
Merge branch 'next/minor' of github.com:Start9Labs/start-os into next/minor
This commit is contained in:
@@ -19,7 +19,7 @@ import * as fs from "fs"
|
||||
|
||||
import { CallbackHolder } from "../Models/CallbackHolder"
|
||||
import { AllGetDependencies } from "../Interfaces/AllGetDependencies"
|
||||
import { jsonPath } from "../Models/JsonPath"
|
||||
import { jsonPath, unNestPath } from "../Models/JsonPath"
|
||||
import { RunningMain, System } from "../Interfaces/System"
|
||||
import {
|
||||
MakeMainEffects,
|
||||
@@ -52,6 +52,8 @@ const SOCKET_PARENT = "/media/startos/rpc"
|
||||
const SOCKET_PATH = "/media/startos/rpc/service.sock"
|
||||
const jsonrpc = "2.0" as const
|
||||
|
||||
const isResult = object({ result: any }).test
|
||||
|
||||
const idType = some(string, number, literal(null))
|
||||
type IdType = null | string | number
|
||||
const runType = object({
|
||||
@@ -64,7 +66,7 @@ const runType = object({
|
||||
input: any,
|
||||
timeout: number,
|
||||
},
|
||||
["timeout", "input"],
|
||||
["timeout"],
|
||||
),
|
||||
})
|
||||
const sandboxRunType = object({
|
||||
@@ -77,7 +79,7 @@ const sandboxRunType = object({
|
||||
input: any,
|
||||
timeout: number,
|
||||
},
|
||||
["timeout", "input"],
|
||||
["timeout"],
|
||||
),
|
||||
})
|
||||
const callbackType = object({
|
||||
@@ -226,27 +228,25 @@ export class RpcListener {
|
||||
const system = this.system
|
||||
const procedure = jsonPath.unsafeCast(params.procedure)
|
||||
const effects = this.getDependencies.makeProcedureEffects()(params.id)
|
||||
return handleRpc(
|
||||
id,
|
||||
system.execute(effects, {
|
||||
procedure,
|
||||
input: params.input,
|
||||
timeout: params.timeout,
|
||||
}),
|
||||
)
|
||||
const input = params.input
|
||||
const timeout = params.timeout
|
||||
const result = getResult(procedure, system, effects, timeout, input)
|
||||
|
||||
return handleRpc(id, result)
|
||||
})
|
||||
.when(sandboxRunType, async ({ id, params }) => {
|
||||
const system = this.system
|
||||
const procedure = jsonPath.unsafeCast(params.procedure)
|
||||
const effects = this.makeProcedureEffects(params.id)
|
||||
return handleRpc(
|
||||
id,
|
||||
system.sandbox(effects, {
|
||||
procedure,
|
||||
input: params.input,
|
||||
timeout: params.timeout,
|
||||
}),
|
||||
const result = getResult(
|
||||
procedure,
|
||||
system,
|
||||
effects,
|
||||
params.input,
|
||||
params.input,
|
||||
)
|
||||
|
||||
return handleRpc(id, result)
|
||||
})
|
||||
.when(callbackType, async ({ params: { callback, args } }) => {
|
||||
this.system.callCallback(callback, args)
|
||||
@@ -280,7 +280,7 @@ export class RpcListener {
|
||||
(async () => {
|
||||
if (!this._system) {
|
||||
const system = await this.getDependencies.system()
|
||||
await system.init()
|
||||
await system.containerInit()
|
||||
this._system = system
|
||||
}
|
||||
})().then((result) => ({ result })),
|
||||
@@ -342,3 +342,97 @@ export class RpcListener {
|
||||
})
|
||||
}
|
||||
}
|
||||
function getResult(
|
||||
procedure: typeof jsonPath._TYPE,
|
||||
system: System,
|
||||
effects: T.Effects,
|
||||
timeout: number | undefined,
|
||||
input: any,
|
||||
) {
|
||||
const ensureResultTypeShape = (
|
||||
result:
|
||||
| void
|
||||
| T.ConfigRes
|
||||
| T.PropertiesReturn
|
||||
| T.ActionMetadata[]
|
||||
| T.ActionResult,
|
||||
): { result: any } => {
|
||||
if (isResult(result)) return result
|
||||
return { result }
|
||||
}
|
||||
return (async () => {
|
||||
switch (procedure) {
|
||||
case "/backup/create":
|
||||
return system.createBackup(effects, timeout || null)
|
||||
case "/backup/restore":
|
||||
return system.restoreBackup(effects, timeout || null)
|
||||
case "/config/get":
|
||||
return system.getConfig(effects, timeout || null)
|
||||
case "/config/set":
|
||||
return system.setConfig(effects, input, timeout || null)
|
||||
case "/properties":
|
||||
return system.properties(effects, timeout || null)
|
||||
case "/actions/metadata":
|
||||
return system.actionsMetadata(effects)
|
||||
case "/init":
|
||||
return system.packageInit(
|
||||
effects,
|
||||
string.optional().unsafeCast(input),
|
||||
timeout || null,
|
||||
)
|
||||
case "/uninit":
|
||||
return system.packageUninit(
|
||||
effects,
|
||||
string.optional().unsafeCast(input),
|
||||
timeout || null,
|
||||
)
|
||||
default:
|
||||
const procedures = unNestPath(procedure)
|
||||
switch (true) {
|
||||
case procedures[1] === "actions" && procedures[3] === "get":
|
||||
return system.action(effects, procedures[2], input, timeout || null)
|
||||
case procedures[1] === "actions" && procedures[3] === "run":
|
||||
return system.action(effects, procedures[2], input, timeout || null)
|
||||
case procedures[1] === "dependencies" && procedures[3] === "query":
|
||||
return system.dependenciesAutoconfig(
|
||||
effects,
|
||||
procedures[2],
|
||||
input,
|
||||
timeout || null,
|
||||
)
|
||||
|
||||
case procedures[1] === "dependencies" && procedures[3] === "update":
|
||||
return system.dependenciesAutoconfig(
|
||||
effects,
|
||||
procedures[2],
|
||||
input,
|
||||
timeout || null,
|
||||
)
|
||||
}
|
||||
}
|
||||
})().then(ensureResultTypeShape, (error) =>
|
||||
matches(error)
|
||||
.when(
|
||||
object(
|
||||
{
|
||||
error: string,
|
||||
code: number,
|
||||
},
|
||||
["code"],
|
||||
{ code: 0 },
|
||||
),
|
||||
(error) => ({
|
||||
error: {
|
||||
code: error.code,
|
||||
message: error.error,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.defaultToLazy(() => ({
|
||||
error: {
|
||||
code: 0,
|
||||
message: String(error),
|
||||
},
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -61,6 +61,42 @@ const MANIFEST_LOCATION = "/usr/lib/startos/package/embassyManifest.json"
|
||||
export const EMBASSY_JS_LOCATION = "/usr/lib/startos/package/embassy.js"
|
||||
const EMBASSY_POINTER_PATH_PREFIX = "/embassyConfig" as StorePath
|
||||
|
||||
const matchResult = object({
|
||||
result: any,
|
||||
})
|
||||
const matchError = object({
|
||||
error: string,
|
||||
})
|
||||
const matchErrorCode = object<{
|
||||
"error-code": [number, string] | readonly [number, string]
|
||||
}>({
|
||||
"error-code": tuple(number, string),
|
||||
})
|
||||
|
||||
const assertNever = (
|
||||
x: never,
|
||||
message = "Not expecting to get here: ",
|
||||
): never => {
|
||||
throw new Error(message + JSON.stringify(x))
|
||||
}
|
||||
/**
|
||||
Should be changing the type for specific properties, and this is mostly a transformation for the old return types to the newer one.
|
||||
*/
|
||||
const fromReturnType = <A>(a: U.ResultType<A>): A => {
|
||||
if (matchResult.test(a)) {
|
||||
return a.result
|
||||
}
|
||||
if (matchError.test(a)) {
|
||||
console.info({ passedErrorStack: new Error().stack, error: a.error })
|
||||
throw { error: a.error }
|
||||
}
|
||||
if (matchErrorCode.test(a)) {
|
||||
const [code, message] = a["error-code"]
|
||||
throw { error: message, code }
|
||||
}
|
||||
return assertNever(a)
|
||||
}
|
||||
|
||||
const matchSetResult = object(
|
||||
{
|
||||
"depends-on": dictionary([string, array(string)]),
|
||||
@@ -206,12 +242,49 @@ export class SystemForEmbassy implements System {
|
||||
moduleCode,
|
||||
)
|
||||
}
|
||||
|
||||
constructor(
|
||||
readonly manifest: Manifest,
|
||||
readonly moduleCode: Partial<U.ExpectedExports>,
|
||||
) {}
|
||||
|
||||
async init(): Promise<void> {}
|
||||
async actionsMetadata(effects: T.Effects): Promise<T.ActionMetadata[]> {
|
||||
const actions = Object.entries(this.manifest.actions ?? {})
|
||||
return Promise.all(
|
||||
actions.map(async ([actionId, action]): Promise<T.ActionMetadata> => {
|
||||
const name = action.name ?? actionId
|
||||
const description = action.description
|
||||
const warning = action.warning ?? null
|
||||
const disabled = false
|
||||
const input = (await convertToNewConfig(action["input-spec"] as any))
|
||||
.spec
|
||||
const hasRunning = !!action["allowed-statuses"].find(
|
||||
(x) => x === "running",
|
||||
)
|
||||
const hasStopped = !!action["allowed-statuses"].find(
|
||||
(x) => x === "stopped",
|
||||
)
|
||||
// prettier-ignore
|
||||
const allowedStatuses =
|
||||
hasRunning && hasStopped ? "any":
|
||||
hasRunning ? "onlyRunning" :
|
||||
"onlyStopped"
|
||||
|
||||
const group = null
|
||||
return {
|
||||
name,
|
||||
description,
|
||||
warning,
|
||||
disabled,
|
||||
allowedStatuses,
|
||||
group,
|
||||
input,
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
async containerInit(): Promise<void> {}
|
||||
|
||||
async exit(): Promise<void> {
|
||||
if (this.currentRunning) await this.currentRunning.clean()
|
||||
@@ -235,141 +308,7 @@ export class SystemForEmbassy implements System {
|
||||
}
|
||||
}
|
||||
|
||||
async execute(
|
||||
effects: Effects,
|
||||
options: {
|
||||
procedure: JsonPath
|
||||
input?: unknown
|
||||
timeout?: number | undefined
|
||||
},
|
||||
): Promise<RpcResult> {
|
||||
return this._execute(effects, options)
|
||||
.then((x) =>
|
||||
matches(x)
|
||||
.when(
|
||||
object({
|
||||
result: any,
|
||||
}),
|
||||
(x) => x,
|
||||
)
|
||||
.when(
|
||||
object({
|
||||
error: string,
|
||||
}),
|
||||
(x) => ({
|
||||
error: {
|
||||
code: 0,
|
||||
message: x.error,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.when(
|
||||
object({
|
||||
"error-code": tuple(number, string),
|
||||
}),
|
||||
({ "error-code": [code, message] }) => ({
|
||||
error: {
|
||||
code,
|
||||
message,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.defaultTo({ result: x }),
|
||||
)
|
||||
.catch((error: unknown) => {
|
||||
if (error instanceof Error)
|
||||
return {
|
||||
error: {
|
||||
code: 0,
|
||||
message: error.name,
|
||||
data: {
|
||||
details: error.message,
|
||||
debug: `${error?.cause ?? "[noCause]"}:${error?.stack ?? "[noStack]"}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
if (matchRpcResult.test(error)) return error
|
||||
return {
|
||||
error: {
|
||||
code: 0,
|
||||
message: String(error),
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
async _execute(
|
||||
effects: Effects,
|
||||
options: {
|
||||
procedure: JsonPath
|
||||
input?: unknown
|
||||
timeout?: number | undefined
|
||||
},
|
||||
): Promise<unknown> {
|
||||
const input = options.input
|
||||
switch (options.procedure) {
|
||||
case "/backup/create":
|
||||
return this.createBackup(effects, options.timeout || null)
|
||||
case "/backup/restore":
|
||||
return this.restoreBackup(effects, options.timeout || null)
|
||||
case "/config/get":
|
||||
return this.getConfig(effects, options.timeout || null)
|
||||
case "/config/set":
|
||||
return this.setConfig(effects, input, options.timeout || null)
|
||||
case "/properties":
|
||||
return this.properties(effects, options.timeout || null)
|
||||
case "/actions/metadata":
|
||||
return todo()
|
||||
case "/init":
|
||||
return this.initProcedure(
|
||||
effects,
|
||||
string.optional().unsafeCast(input),
|
||||
options.timeout || null,
|
||||
)
|
||||
case "/uninit":
|
||||
return this.uninit(
|
||||
effects,
|
||||
string.optional().unsafeCast(input),
|
||||
options.timeout || null,
|
||||
)
|
||||
default:
|
||||
const procedures = unNestPath(options.procedure)
|
||||
switch (true) {
|
||||
case procedures[1] === "actions" && procedures[3] === "get":
|
||||
return this.action(
|
||||
effects,
|
||||
procedures[2],
|
||||
input,
|
||||
options.timeout || null,
|
||||
)
|
||||
case procedures[1] === "actions" && procedures[3] === "run":
|
||||
return this.action(
|
||||
effects,
|
||||
procedures[2],
|
||||
input,
|
||||
options.timeout || null,
|
||||
)
|
||||
case procedures[1] === "dependencies" && procedures[3] === "query":
|
||||
return null
|
||||
|
||||
case procedures[1] === "dependencies" && procedures[3] === "update":
|
||||
return this.dependenciesAutoconfig(
|
||||
effects,
|
||||
procedures[2],
|
||||
input,
|
||||
options.timeout || null,
|
||||
)
|
||||
}
|
||||
}
|
||||
throw new Error(`Could not find the path for ${options.procedure}`)
|
||||
}
|
||||
async sandbox(
|
||||
effects: Effects,
|
||||
options: { procedure: Procedure; input: unknown; timeout?: number },
|
||||
): Promise<RpcResult> {
|
||||
return this.execute(effects, options)
|
||||
}
|
||||
|
||||
private async initProcedure(
|
||||
async packageInit(
|
||||
effects: Effects,
|
||||
previousVersion: Optional<string>,
|
||||
timeoutMs: number | null,
|
||||
@@ -489,7 +428,7 @@ export class SystemForEmbassy implements System {
|
||||
})
|
||||
}
|
||||
}
|
||||
private async uninit(
|
||||
async packageUninit(
|
||||
effects: Effects,
|
||||
nextVersion: Optional<string>,
|
||||
timeoutMs: number | null,
|
||||
@@ -498,7 +437,7 @@ export class SystemForEmbassy implements System {
|
||||
await effects.setMainStatus({ status: "stopped" })
|
||||
}
|
||||
|
||||
private async createBackup(
|
||||
async createBackup(
|
||||
effects: Effects,
|
||||
timeoutMs: number | null,
|
||||
): Promise<void> {
|
||||
@@ -519,7 +458,7 @@ export class SystemForEmbassy implements System {
|
||||
await moduleCode.createBackup?.(polyfillEffects(effects, this.manifest))
|
||||
}
|
||||
}
|
||||
private async restoreBackup(
|
||||
async restoreBackup(
|
||||
effects: Effects,
|
||||
timeoutMs: number | null,
|
||||
): Promise<void> {
|
||||
@@ -543,7 +482,7 @@ export class SystemForEmbassy implements System {
|
||||
await moduleCode.restoreBackup?.(polyfillEffects(effects, this.manifest))
|
||||
}
|
||||
}
|
||||
private async getConfig(
|
||||
async getConfig(
|
||||
effects: Effects,
|
||||
timeoutMs: number | null,
|
||||
): Promise<T.ConfigRes> {
|
||||
@@ -584,7 +523,7 @@ export class SystemForEmbassy implements System {
|
||||
)) as any
|
||||
}
|
||||
}
|
||||
private async setConfig(
|
||||
async setConfig(
|
||||
effects: Effects,
|
||||
newConfigWithoutPointers: unknown,
|
||||
timeoutMs: number | null,
|
||||
@@ -676,7 +615,7 @@ export class SystemForEmbassy implements System {
|
||||
})
|
||||
}
|
||||
|
||||
private async migration(
|
||||
async migration(
|
||||
effects: Effects,
|
||||
fromVersion: string,
|
||||
timeoutMs: number | null,
|
||||
@@ -748,10 +687,10 @@ export class SystemForEmbassy implements System {
|
||||
}
|
||||
return { configured: true }
|
||||
}
|
||||
private async properties(
|
||||
async properties(
|
||||
effects: Effects,
|
||||
timeoutMs: number | null,
|
||||
): Promise<ReturnType<T.ExpectedExports.properties>> {
|
||||
): Promise<T.PropertiesReturn> {
|
||||
// TODO BLU-J set the properties ever so often
|
||||
const setConfigValue = this.manifest.properties
|
||||
if (!setConfigValue) throw new Error("There is no properties")
|
||||
@@ -779,36 +718,81 @@ export class SystemForEmbassy implements System {
|
||||
if (!method)
|
||||
throw new Error("Expecting that the method properties exists")
|
||||
const properties = matchProperties.unsafeCast(
|
||||
await method(polyfillEffects(effects, this.manifest)).then((x) => {
|
||||
if ("result" in x) return x.result
|
||||
if ("error" in x) throw new Error("Error getting config: " + x.error)
|
||||
throw new Error("Error getting config: " + x["error-code"][1])
|
||||
}),
|
||||
await method(polyfillEffects(effects, this.manifest)).then(
|
||||
fromReturnType,
|
||||
),
|
||||
)
|
||||
return asProperty(properties.data)
|
||||
}
|
||||
throw new Error(`Unknown type in the fetch properties: ${setConfigValue}`)
|
||||
}
|
||||
private async action(
|
||||
async action(
|
||||
effects: Effects,
|
||||
actionId: string,
|
||||
formData: unknown,
|
||||
timeoutMs: number | null,
|
||||
): Promise<T.ActionResult> {
|
||||
const actionProcedure = this.manifest.actions?.[actionId]?.implementation
|
||||
if (!actionProcedure) return { message: "Action not found", value: null }
|
||||
const toActionResult = ({
|
||||
message,
|
||||
value = "",
|
||||
copyable,
|
||||
qr,
|
||||
}: U.ActionResult): T.ActionResult => ({
|
||||
version: "0",
|
||||
message,
|
||||
value,
|
||||
copyable,
|
||||
qr,
|
||||
})
|
||||
if (!actionProcedure) throw Error("Action not found")
|
||||
if (actionProcedure.type === "docker") {
|
||||
const container = await DockerProcedureContainer.of(
|
||||
effects,
|
||||
this.manifest.id,
|
||||
actionProcedure,
|
||||
this.manifest.volumes,
|
||||
)
|
||||
return toActionResult(
|
||||
JSON.parse(
|
||||
(
|
||||
await container.execFail(
|
||||
[
|
||||
actionProcedure.entrypoint,
|
||||
...actionProcedure.args,
|
||||
JSON.stringify(formData),
|
||||
],
|
||||
timeoutMs,
|
||||
)
|
||||
).stdout.toString(),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
const moduleCode = await this.moduleCode
|
||||
const method = moduleCode.action?.[actionId]
|
||||
if (!method) throw new Error("Expecting that the method action exists")
|
||||
return await method(
|
||||
polyfillEffects(effects, this.manifest),
|
||||
formData as any,
|
||||
)
|
||||
.then(fromReturnType)
|
||||
.then(toActionResult)
|
||||
}
|
||||
}
|
||||
async dependenciesCheck(
|
||||
effects: Effects,
|
||||
id: string,
|
||||
oldConfig: unknown,
|
||||
timeoutMs: number | null,
|
||||
): Promise<object> {
|
||||
const actionProcedure = this.manifest.dependencies?.[id]?.config?.check
|
||||
if (!actionProcedure) return { message: "Action not found", value: null }
|
||||
if (actionProcedure.type === "docker") {
|
||||
const overlay = actionProcedure.inject
|
||||
? this.currentRunning?.mainOverlay
|
||||
: undefined
|
||||
const container = await DockerProcedureContainer.of(
|
||||
effects,
|
||||
this.manifest.id,
|
||||
actionProcedure,
|
||||
this.manifest.volumes,
|
||||
{
|
||||
overlay,
|
||||
},
|
||||
)
|
||||
return JSON.parse(
|
||||
(
|
||||
@@ -816,27 +800,32 @@ export class SystemForEmbassy implements System {
|
||||
[
|
||||
actionProcedure.entrypoint,
|
||||
...actionProcedure.args,
|
||||
JSON.stringify(formData),
|
||||
JSON.stringify(oldConfig),
|
||||
],
|
||||
timeoutMs,
|
||||
)
|
||||
).stdout.toString(),
|
||||
)
|
||||
} else {
|
||||
} else if (actionProcedure.type === "script") {
|
||||
const moduleCode = await this.moduleCode
|
||||
const method = moduleCode.action?.[actionId]
|
||||
if (!method) throw new Error("Expecting that the method action exists")
|
||||
const method = moduleCode.dependencies?.[id]?.check
|
||||
if (!method)
|
||||
throw new Error(
|
||||
`Expecting that the method dependency check ${id} exists`,
|
||||
)
|
||||
return (await method(
|
||||
polyfillEffects(effects, this.manifest),
|
||||
formData as any,
|
||||
oldConfig as any,
|
||||
).then((x) => {
|
||||
if ("result" in x) return x.result
|
||||
if ("error" in x) throw new Error("Error getting config: " + x.error)
|
||||
throw new Error("Error getting config: " + x["error-code"][1])
|
||||
})) as any
|
||||
} else {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
private async dependenciesAutoconfig(
|
||||
async dependenciesAutoconfig(
|
||||
effects: Effects,
|
||||
id: string,
|
||||
input: unknown,
|
||||
|
||||
@@ -8,6 +8,7 @@ import { T, utils } from "@start9labs/start-sdk"
|
||||
import { Volume } from "../../Models/Volume"
|
||||
import { MainEffects } from "@start9labs/start-sdk/cjs/lib/StartSdk"
|
||||
import { CallbackHolder } from "../../Models/CallbackHolder"
|
||||
import { Optional } from "ts-matches/lib/parsers/interfaces"
|
||||
|
||||
export const STARTOS_JS_LOCATION = "/usr/lib/startos/package/index.js"
|
||||
|
||||
@@ -25,6 +26,107 @@ export class SystemForStartOs implements System {
|
||||
}
|
||||
|
||||
constructor(readonly abi: T.ABI) {}
|
||||
containerInit(): Promise<void> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async packageInit(
|
||||
effects: Effects,
|
||||
previousVersion: Optional<string> = null,
|
||||
timeoutMs: number | null = null,
|
||||
): Promise<void> {
|
||||
return void (await this.abi.init({ effects }))
|
||||
}
|
||||
async packageUninit(
|
||||
effects: Effects,
|
||||
nextVersion: Optional<string> = null,
|
||||
timeoutMs: number | null = null,
|
||||
): Promise<void> {
|
||||
return void (await this.abi.uninit({ effects, nextVersion }))
|
||||
}
|
||||
async createBackup(
|
||||
effects: T.Effects,
|
||||
timeoutMs: number | null,
|
||||
): Promise<void> {
|
||||
return void (await this.abi.createBackup({
|
||||
effects,
|
||||
pathMaker: ((options) =>
|
||||
new Volume(options.volume, options.path).path) as T.PathMaker,
|
||||
}))
|
||||
}
|
||||
async restoreBackup(
|
||||
effects: T.Effects,
|
||||
timeoutMs: number | null,
|
||||
): Promise<void> {
|
||||
return void (await this.abi.restoreBackup({
|
||||
effects,
|
||||
pathMaker: ((options) =>
|
||||
new Volume(options.volume, options.path).path) as T.PathMaker,
|
||||
}))
|
||||
}
|
||||
getConfig(
|
||||
effects: T.Effects,
|
||||
timeoutMs: number | null,
|
||||
): Promise<T.ConfigRes> {
|
||||
return this.abi.getConfig({ effects })
|
||||
}
|
||||
async setConfig(
|
||||
effects: Effects,
|
||||
input: { effects: Effects; input: Record<string, unknown> },
|
||||
timeoutMs: number | null,
|
||||
): Promise<void> {
|
||||
const _: unknown = await this.abi.setConfig({ effects, input })
|
||||
return
|
||||
}
|
||||
migration(
|
||||
effects: Effects,
|
||||
fromVersion: string,
|
||||
timeoutMs: number | null,
|
||||
): Promise<T.MigrationRes> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
properties(
|
||||
effects: Effects,
|
||||
timeoutMs: number | null,
|
||||
): Promise<T.PropertiesReturn> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async action(
|
||||
effects: Effects,
|
||||
id: string,
|
||||
formData: unknown,
|
||||
timeoutMs: number | null,
|
||||
): Promise<T.ActionResult> {
|
||||
const action = (await this.abi.actions({ effects }))[id]
|
||||
if (!action) throw new Error(`Action ${id} not found`)
|
||||
return action.run({ effects })
|
||||
}
|
||||
dependenciesCheck(
|
||||
effects: Effects,
|
||||
id: string,
|
||||
oldConfig: unknown,
|
||||
timeoutMs: number | null,
|
||||
): Promise<any> {
|
||||
const dependencyConfig = this.abi.dependencyConfig[id]
|
||||
if (!dependencyConfig) throw new Error(`dependencyConfig ${id} not found`)
|
||||
return dependencyConfig.query({ effects })
|
||||
}
|
||||
async dependenciesAutoconfig(
|
||||
effects: Effects,
|
||||
id: string,
|
||||
remoteConfig: unknown,
|
||||
timeoutMs: number | null,
|
||||
): Promise<void> {
|
||||
const dependencyConfig = this.abi.dependencyConfig[id]
|
||||
if (!dependencyConfig) throw new Error(`dependencyConfig ${id} not found`)
|
||||
const queryResults = await this.getConfig(effects, timeoutMs)
|
||||
return void (await dependencyConfig.update({
|
||||
queryResults,
|
||||
remoteConfig,
|
||||
})) // TODO
|
||||
}
|
||||
async actionsMetadata(effects: T.Effects): Promise<T.ActionMetadata[]> {
|
||||
return this.abi.actionsMetadata({ effects })
|
||||
}
|
||||
|
||||
async init(): Promise<void> {}
|
||||
|
||||
@@ -72,155 +174,4 @@ export class SystemForStartOs implements System {
|
||||
this.runningMain = undefined
|
||||
}
|
||||
}
|
||||
|
||||
async execute(
|
||||
effects: Effects,
|
||||
options: {
|
||||
procedure: Procedure
|
||||
input?: unknown
|
||||
timeout?: number | undefined
|
||||
},
|
||||
): Promise<RpcResult> {
|
||||
return this._execute(effects, options)
|
||||
.then((x) =>
|
||||
matches(x)
|
||||
.when(
|
||||
object({
|
||||
result: any,
|
||||
}),
|
||||
(x) => x,
|
||||
)
|
||||
.when(
|
||||
object({
|
||||
error: string,
|
||||
}),
|
||||
(x) => ({
|
||||
error: {
|
||||
code: 0,
|
||||
message: x.error,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.when(
|
||||
object({
|
||||
"error-code": tuple(number, string),
|
||||
}),
|
||||
({ "error-code": [code, message] }) => ({
|
||||
error: {
|
||||
code,
|
||||
message,
|
||||
},
|
||||
}),
|
||||
)
|
||||
.defaultTo({ result: x }),
|
||||
)
|
||||
.catch((error: unknown) => {
|
||||
if (error instanceof Error)
|
||||
return {
|
||||
error: {
|
||||
code: 0,
|
||||
message: error.name,
|
||||
data: {
|
||||
details: error.message,
|
||||
debug: `${error?.cause ?? "[noCause]"}:${error?.stack ?? "[noStack]"}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
if (matchRpcResult.test(error)) return error
|
||||
return {
|
||||
error: {
|
||||
code: 0,
|
||||
message: String(error),
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
async _execute(
|
||||
effects: Effects | MainEffects,
|
||||
options: {
|
||||
procedure: Procedure
|
||||
input?: unknown
|
||||
timeout?: number | undefined
|
||||
},
|
||||
): Promise<unknown> {
|
||||
switch (options.procedure) {
|
||||
case "/init": {
|
||||
return this.abi.init({ effects })
|
||||
}
|
||||
case "/uninit": {
|
||||
const nextVersion = string.optional().unsafeCast(options.input) || null
|
||||
return this.abi.uninit({ effects, nextVersion })
|
||||
}
|
||||
// case "/main/start": {
|
||||
//
|
||||
// }
|
||||
// case "/main/stop": {
|
||||
// if (this.onTerm) await this.onTerm()
|
||||
// await effects.setMainStatus({ status: "stopped" })
|
||||
// delete this.onTerm
|
||||
// return duration(30, "s")
|
||||
// }
|
||||
case "/config/set": {
|
||||
const input = options.input as any // TODO
|
||||
return this.abi.setConfig({ effects, input })
|
||||
}
|
||||
case "/config/get": {
|
||||
return this.abi.getConfig({ effects })
|
||||
}
|
||||
case "/backup/create":
|
||||
return this.abi.createBackup({
|
||||
effects,
|
||||
pathMaker: ((options) =>
|
||||
new Volume(options.volume, options.path).path) as T.PathMaker,
|
||||
})
|
||||
case "/backup/restore":
|
||||
return this.abi.restoreBackup({
|
||||
effects,
|
||||
pathMaker: ((options) =>
|
||||
new Volume(options.volume, options.path).path) as T.PathMaker,
|
||||
})
|
||||
case "/actions/metadata": {
|
||||
return this.abi.actionsMetadata({ effects })
|
||||
}
|
||||
case "/properties": {
|
||||
throw new Error("TODO")
|
||||
}
|
||||
default:
|
||||
const procedures = unNestPath(options.procedure)
|
||||
const id = procedures[2]
|
||||
switch (true) {
|
||||
case procedures[1] === "actions" && procedures[3] === "get": {
|
||||
const action = (await this.abi.actions({ effects }))[id]
|
||||
if (!action) throw new Error(`Action ${id} not found`)
|
||||
return action.getConfig({ effects })
|
||||
}
|
||||
case procedures[1] === "actions" && procedures[3] === "run": {
|
||||
const action = (await this.abi.actions({ effects }))[id]
|
||||
if (!action) throw new Error(`Action ${id} not found`)
|
||||
return action.run({ effects, input: options.input as any }) // TODO
|
||||
}
|
||||
case procedures[1] === "dependencies" && procedures[3] === "query": {
|
||||
const dependencyConfig = this.abi.dependencyConfig[id]
|
||||
if (!dependencyConfig)
|
||||
throw new Error(`dependencyConfig ${id} not found`)
|
||||
const localConfig = options.input
|
||||
return dependencyConfig.query({ effects })
|
||||
}
|
||||
case procedures[1] === "dependencies" && procedures[3] === "update": {
|
||||
const dependencyConfig = this.abi.dependencyConfig[id]
|
||||
if (!dependencyConfig)
|
||||
throw new Error(`dependencyConfig ${id} not found`)
|
||||
return dependencyConfig.update(options.input as any) // TODO
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
async sandbox(
|
||||
effects: Effects,
|
||||
options: { procedure: Procedure; input?: unknown; timeout?: number },
|
||||
): Promise<RpcResult> {
|
||||
return this.execute(effects, options)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { RpcResult } from "../Adapters/RpcListener"
|
||||
import { Effects } from "../Models/Effects"
|
||||
import { CallbackHolder } from "../Models/CallbackHolder"
|
||||
import { MainEffects } from "@start9labs/start-sdk/cjs/lib/StartSdk"
|
||||
import { Optional } from "ts-matches/lib/parsers/interfaces"
|
||||
|
||||
export type Procedure =
|
||||
| "/init"
|
||||
@@ -22,28 +23,60 @@ export type ExecuteResult =
|
||||
| { ok: unknown }
|
||||
| { err: { code: number; message: string } }
|
||||
export type System = {
|
||||
init(): Promise<void>
|
||||
containerInit(): Promise<void>
|
||||
|
||||
start(effects: MainEffects): Promise<void>
|
||||
callCallback(callback: number, args: any[]): void
|
||||
stop(): Promise<void>
|
||||
|
||||
execute(
|
||||
packageInit(
|
||||
effects: Effects,
|
||||
options: {
|
||||
procedure: Procedure
|
||||
input: unknown
|
||||
timeout?: number
|
||||
},
|
||||
): Promise<RpcResult>
|
||||
sandbox(
|
||||
previousVersion: Optional<string>,
|
||||
timeoutMs: number | null,
|
||||
): Promise<void>
|
||||
packageUninit(
|
||||
effects: Effects,
|
||||
options: {
|
||||
procedure: Procedure
|
||||
input: unknown
|
||||
timeout?: number
|
||||
},
|
||||
): Promise<RpcResult>
|
||||
nextVersion: Optional<string>,
|
||||
timeoutMs: number | null,
|
||||
): Promise<void>
|
||||
|
||||
createBackup(effects: T.Effects, timeoutMs: number | null): Promise<void>
|
||||
restoreBackup(effects: T.Effects, timeoutMs: number | null): Promise<void>
|
||||
getConfig(effects: T.Effects, timeoutMs: number | null): Promise<T.ConfigRes>
|
||||
setConfig(
|
||||
effects: Effects,
|
||||
input: { effects: Effects; input: Record<string, unknown> },
|
||||
timeoutMs: number | null,
|
||||
): Promise<void>
|
||||
migration(
|
||||
effects: Effects,
|
||||
fromVersion: string,
|
||||
timeoutMs: number | null,
|
||||
): Promise<T.MigrationRes>
|
||||
properties(
|
||||
effects: Effects,
|
||||
timeoutMs: number | null,
|
||||
): Promise<T.PropertiesReturn>
|
||||
action(
|
||||
effects: Effects,
|
||||
actionId: string,
|
||||
formData: unknown,
|
||||
timeoutMs: number | null,
|
||||
): Promise<T.ActionResult>
|
||||
|
||||
dependenciesCheck(
|
||||
effects: Effects,
|
||||
id: string,
|
||||
oldConfig: unknown,
|
||||
timeoutMs: number | null,
|
||||
): Promise<any>
|
||||
dependenciesAutoconfig(
|
||||
effects: Effects,
|
||||
id: string,
|
||||
oldConfig: unknown,
|
||||
timeoutMs: number | null,
|
||||
): Promise<void>
|
||||
actionsMetadata(effects: T.Effects): Promise<T.ActionMetadata[]>
|
||||
|
||||
exit(): Promise<void>
|
||||
}
|
||||
|
||||
@@ -476,12 +476,11 @@ export type MigrationRes = {
|
||||
}
|
||||
|
||||
export type ActionResult = {
|
||||
version: "0"
|
||||
message: string
|
||||
value: null | {
|
||||
value: string
|
||||
copyable: boolean
|
||||
qr: boolean
|
||||
}
|
||||
value: string | null
|
||||
copyable: boolean
|
||||
qr: boolean
|
||||
}
|
||||
export type SetResult = {
|
||||
dependsOn: DependsOn
|
||||
|
||||
Reference in New Issue
Block a user