Chore/refactoring effects (#2644)

* fix mac build

* wip

* chore: Update the effects to get rid of bad pattern

* chore: Some small changes

---------

Co-authored-by: Aiden McClelland <me@drbonez.dev>
This commit is contained in:
Jade
2024-06-14 14:16:12 -06:00
committed by GitHub
parent 3f380fa0da
commit bb514d6216
10 changed files with 704 additions and 693 deletions

View File

@@ -31,274 +31,273 @@ type RpcError = typeof matchRpcError._TYPE
const SOCKET_PATH = "/media/startos/rpc/host.sock"
const MAIN = "/main" as const
export class HostSystemStartOs implements Effects {
procedureId: string | null = null
static of(callbackHolder: CallbackHolder) {
return new HostSystemStartOs(callbackHolder)
}
constructor(readonly callbackHolder: CallbackHolder) {}
id = 0
rpcRound<K extends keyof Effects | "getStore" | "setStore">(
method: K,
params: Record<string, unknown>,
) {
const id = this.id++
const client = net.createConnection({ path: SOCKET_PATH }, () => {
client.write(
JSON.stringify({
id,
method,
params: { ...params, procedureId: this.procedureId },
}) + "\n",
)
})
let bufs: Buffer[] = []
return new Promise((resolve, reject) => {
client.on("data", (data) => {
try {
bufs.push(data)
if (data.reduce((acc, x) => acc || x == 10, false)) {
const res: unknown = JSON.parse(
Buffer.concat(bufs).toString().split("\n")[0],
)
if (testRpcError(res)) {
let message = res.error.message
console.error({ method, params, hostSystemStartOs: true })
if (string.test(res.error.data)) {
message += ": " + res.error.data
console.error(res.error.data)
let hostSystemId = 0
export const hostSystemStartOs =
(callbackHolder: CallbackHolder) =>
(procedureId: null | string): Effects => {
const rpcRound = <K extends keyof Effects | "getStore" | "setStore">(
method: K,
params: Record<string, unknown>,
) => {
const id = hostSystemId++
const client = net.createConnection({ path: SOCKET_PATH }, () => {
client.write(
JSON.stringify({
id,
method,
params: { ...params, procedureId: procedureId },
}) + "\n",
)
})
let bufs: Buffer[] = []
return new Promise((resolve, reject) => {
client.on("data", (data) => {
try {
bufs.push(data)
if (data.reduce((acc, x) => acc || x == 10, false)) {
const res: unknown = JSON.parse(
Buffer.concat(bufs).toString().split("\n")[0],
)
if (testRpcError(res)) {
let message = res.error.message
console.error({ method, params, hostSystemStartOs: true })
if (string.test(res.error.data)) {
message += ": " + res.error.data
console.error(res.error.data)
} else {
if (res.error.data?.details) {
message += ": " + res.error.data.details
console.error(res.error.data.details)
}
if (res.error.data?.debug) {
message += "\n" + res.error.data.debug
console.error("Debug: " + res.error.data.debug)
}
}
reject(new Error(`${message}@${method}`))
} else if (testRpcResult(res)) {
resolve(res.result)
} else {
if (res.error.data?.details) {
message += ": " + res.error.data.details
console.error(res.error.data.details)
}
if (res.error.data?.debug) {
message += "\n" + res.error.data.debug
console.error("Debug: " + res.error.data.debug)
}
reject(new Error(`malformed response ${JSON.stringify(res)}`))
}
reject(new Error(`${message}@${method}`))
} else if (testRpcResult(res)) {
resolve(res.result)
} else {
reject(new Error(`malformed response ${JSON.stringify(res)}`))
}
} catch (error) {
reject(error)
}
} catch (error) {
client.end()
})
client.on("error", (error) => {
reject(error)
}
client.end()
})
})
client.on("error", (error) => {
reject(error)
})
})
}
bind(...[options]: Parameters<T.Effects["bind"]>) {
return this.rpcRound("bind", {
...options,
stack: new Error().stack,
}) as ReturnType<T.Effects["bind"]>
}
clearBindings(...[]: Parameters<T.Effects["clearBindings"]>) {
return this.rpcRound("clearBindings", {}) as ReturnType<
T.Effects["clearBindings"]
>
}
clearServiceInterfaces(
...[]: Parameters<T.Effects["clearServiceInterfaces"]>
) {
return this.rpcRound("clearServiceInterfaces", {}) as ReturnType<
T.Effects["clearServiceInterfaces"]
>
}
createOverlayedImage(options: {
imageId: string
}): Promise<[string, string]> {
return this.rpcRound("createOverlayedImage", options) as ReturnType<
T.Effects["createOverlayedImage"]
>
}
destroyOverlayedImage(options: { guid: string }): Promise<void> {
return this.rpcRound("destroyOverlayedImage", options) as ReturnType<
T.Effects["destroyOverlayedImage"]
>
}
executeAction(...[options]: Parameters<T.Effects["executeAction"]>) {
return this.rpcRound("executeAction", options) as ReturnType<
T.Effects["executeAction"]
>
}
exists(...[packageId]: Parameters<T.Effects["exists"]>) {
return this.rpcRound("exists", packageId) as ReturnType<T.Effects["exists"]>
}
exportAction(...[options]: Parameters<T.Effects["exportAction"]>) {
return this.rpcRound("exportAction", options) as ReturnType<
T.Effects["exportAction"]
>
}
exportServiceInterface: Effects["exportServiceInterface"] = (
...[options]: Parameters<Effects["exportServiceInterface"]>
) => {
return this.rpcRound("exportServiceInterface", options) as ReturnType<
T.Effects["exportServiceInterface"]
>
}
exposeForDependents(
...[options]: Parameters<T.Effects["exposeForDependents"]>
) {
return this.rpcRound("exposeForDependents", options) as ReturnType<
T.Effects["exposeForDependents"]
>
}
getConfigured(...[]: Parameters<T.Effects["getConfigured"]>) {
return this.rpcRound("getConfigured", {}) as ReturnType<
T.Effects["getConfigured"]
>
}
getContainerIp(...[]: Parameters<T.Effects["getContainerIp"]>) {
return this.rpcRound("getContainerIp", {}) as ReturnType<
T.Effects["getContainerIp"]
>
}
getHostInfo: Effects["getHostInfo"] = (...[allOptions]: any[]) => {
const options = {
...allOptions,
callback: this.callbackHolder.addCallback(allOptions.callback),
}
return this.rpcRound("getHostInfo", options) as ReturnType<
T.Effects["getHostInfo"]
> as any
}
getServiceInterface(
...[options]: Parameters<T.Effects["getServiceInterface"]>
) {
return this.rpcRound("getServiceInterface", {
...options,
callback: this.callbackHolder.addCallback(options.callback),
}) as ReturnType<T.Effects["getServiceInterface"]>
}
const self: Effects = {
bind(...[options]: Parameters<T.Effects["bind"]>) {
return rpcRound("bind", {
...options,
stack: new Error().stack,
}) as ReturnType<T.Effects["bind"]>
},
clearBindings(...[]: Parameters<T.Effects["clearBindings"]>) {
return rpcRound("clearBindings", {}) as ReturnType<
T.Effects["clearBindings"]
>
},
clearServiceInterfaces(
...[]: Parameters<T.Effects["clearServiceInterfaces"]>
) {
return rpcRound("clearServiceInterfaces", {}) as ReturnType<
T.Effects["clearServiceInterfaces"]
>
},
createOverlayedImage(options: {
imageId: string
}): Promise<[string, string]> {
return rpcRound("createOverlayedImage", options) as ReturnType<
T.Effects["createOverlayedImage"]
>
},
destroyOverlayedImage(options: { guid: string }): Promise<void> {
return rpcRound("destroyOverlayedImage", options) as ReturnType<
T.Effects["destroyOverlayedImage"]
>
},
executeAction(...[options]: Parameters<T.Effects["executeAction"]>) {
return rpcRound("executeAction", options) as ReturnType<
T.Effects["executeAction"]
>
},
exists(...[packageId]: Parameters<T.Effects["exists"]>) {
return rpcRound("exists", packageId) as ReturnType<T.Effects["exists"]>
},
exportAction(...[options]: Parameters<T.Effects["exportAction"]>) {
return rpcRound("exportAction", options) as ReturnType<
T.Effects["exportAction"]
>
},
exportServiceInterface: ((
...[options]: Parameters<Effects["exportServiceInterface"]>
) => {
return rpcRound("exportServiceInterface", options) as ReturnType<
T.Effects["exportServiceInterface"]
>
}) as Effects["exportServiceInterface"],
exposeForDependents(
...[options]: Parameters<T.Effects["exposeForDependents"]>
) {
return rpcRound("exposeForDependents", options) as ReturnType<
T.Effects["exposeForDependents"]
>
},
getConfigured(...[]: Parameters<T.Effects["getConfigured"]>) {
return rpcRound("getConfigured", {}) as ReturnType<
T.Effects["getConfigured"]
>
},
getContainerIp(...[]: Parameters<T.Effects["getContainerIp"]>) {
return rpcRound("getContainerIp", {}) as ReturnType<
T.Effects["getContainerIp"]
>
},
getHostInfo: ((...[allOptions]: any[]) => {
const options = {
...allOptions,
callback: callbackHolder.addCallback(allOptions.callback),
}
return rpcRound("getHostInfo", options) as ReturnType<
T.Effects["getHostInfo"]
> as any
}) as Effects["getHostInfo"],
getServiceInterface(
...[options]: Parameters<T.Effects["getServiceInterface"]>
) {
return rpcRound("getServiceInterface", {
...options,
callback: callbackHolder.addCallback(options.callback),
}) as ReturnType<T.Effects["getServiceInterface"]>
},
getPrimaryUrl(...[options]: Parameters<T.Effects["getPrimaryUrl"]>) {
return this.rpcRound("getPrimaryUrl", {
...options,
callback: this.callbackHolder.addCallback(options.callback),
}) as ReturnType<T.Effects["getPrimaryUrl"]>
}
getServicePortForward(
...[options]: Parameters<T.Effects["getServicePortForward"]>
) {
return this.rpcRound("getServicePortForward", options) as ReturnType<
T.Effects["getServicePortForward"]
>
}
getSslCertificate(options: Parameters<T.Effects["getSslCertificate"]>[0]) {
return this.rpcRound("getSslCertificate", options) as ReturnType<
T.Effects["getSslCertificate"]
>
}
getSslKey(options: Parameters<T.Effects["getSslKey"]>[0]) {
return this.rpcRound("getSslKey", options) as ReturnType<
T.Effects["getSslKey"]
>
}
getSystemSmtp(...[options]: Parameters<T.Effects["getSystemSmtp"]>) {
return this.rpcRound("getSystemSmtp", {
...options,
callback: this.callbackHolder.addCallback(options.callback),
}) as ReturnType<T.Effects["getSystemSmtp"]>
}
listServiceInterfaces(
...[options]: Parameters<T.Effects["listServiceInterfaces"]>
) {
return this.rpcRound("listServiceInterfaces", {
...options,
callback: this.callbackHolder.addCallback(options.callback),
}) as ReturnType<T.Effects["listServiceInterfaces"]>
}
mount(...[options]: Parameters<T.Effects["mount"]>) {
return this.rpcRound("mount", options) as ReturnType<T.Effects["mount"]>
}
removeAction(...[options]: Parameters<T.Effects["removeAction"]>) {
return this.rpcRound("removeAction", options) as ReturnType<
T.Effects["removeAction"]
>
}
removeAddress(...[options]: Parameters<T.Effects["removeAddress"]>) {
return this.rpcRound("removeAddress", options) as ReturnType<
T.Effects["removeAddress"]
>
}
restart(...[]: Parameters<T.Effects["restart"]>) {
return this.rpcRound("restart", {}) as ReturnType<T.Effects["restart"]>
}
running(...[packageId]: Parameters<T.Effects["running"]>) {
return this.rpcRound("running", { packageId }) as ReturnType<
T.Effects["running"]
>
}
// runRsync(...[options]: Parameters<T.Effects[""]>) {
//
// return this.rpcRound('executeAction', options) as ReturnType<T.Effects["executeAction"]>
//
// return this.rpcRound('executeAction', options) as ReturnType<T.Effects["executeAction"]>
// }
setConfigured(...[configured]: Parameters<T.Effects["setConfigured"]>) {
return this.rpcRound("setConfigured", { configured }) as ReturnType<
T.Effects["setConfigured"]
>
}
setDependencies(
dependencies: Parameters<T.Effects["setDependencies"]>[0],
): ReturnType<T.Effects["setDependencies"]> {
return this.rpcRound("setDependencies", dependencies) as ReturnType<
T.Effects["setDependencies"]
>
}
checkDependencies(
options: Parameters<T.Effects["checkDependencies"]>[0],
): ReturnType<T.Effects["checkDependencies"]> {
return this.rpcRound("checkDependencies", options) as ReturnType<
T.Effects["checkDependencies"]
>
}
getDependencies(): ReturnType<T.Effects["getDependencies"]> {
return this.rpcRound("getDependencies", {}) as ReturnType<
T.Effects["getDependencies"]
>
}
setHealth(...[options]: Parameters<T.Effects["setHealth"]>) {
return this.rpcRound("setHealth", options) as ReturnType<
T.Effects["setHealth"]
>
}
getPrimaryUrl(...[options]: Parameters<T.Effects["getPrimaryUrl"]>) {
return rpcRound("getPrimaryUrl", {
...options,
callback: callbackHolder.addCallback(options.callback),
}) as ReturnType<T.Effects["getPrimaryUrl"]>
},
getServicePortForward(
...[options]: Parameters<T.Effects["getServicePortForward"]>
) {
return rpcRound("getServicePortForward", options) as ReturnType<
T.Effects["getServicePortForward"]
>
},
getSslCertificate(
options: Parameters<T.Effects["getSslCertificate"]>[0],
) {
return rpcRound("getSslCertificate", options) as ReturnType<
T.Effects["getSslCertificate"]
>
},
getSslKey(options: Parameters<T.Effects["getSslKey"]>[0]) {
return rpcRound("getSslKey", options) as ReturnType<
T.Effects["getSslKey"]
>
},
getSystemSmtp(...[options]: Parameters<T.Effects["getSystemSmtp"]>) {
return rpcRound("getSystemSmtp", {
...options,
callback: callbackHolder.addCallback(options.callback),
}) as ReturnType<T.Effects["getSystemSmtp"]>
},
listServiceInterfaces(
...[options]: Parameters<T.Effects["listServiceInterfaces"]>
) {
return rpcRound("listServiceInterfaces", {
...options,
callback: callbackHolder.addCallback(options.callback),
}) as ReturnType<T.Effects["listServiceInterfaces"]>
},
mount(...[options]: Parameters<T.Effects["mount"]>) {
return rpcRound("mount", options) as ReturnType<T.Effects["mount"]>
},
removeAction(...[options]: Parameters<T.Effects["removeAction"]>) {
return rpcRound("removeAction", options) as ReturnType<
T.Effects["removeAction"]
>
},
removeAddress(...[options]: Parameters<T.Effects["removeAddress"]>) {
return rpcRound("removeAddress", options) as ReturnType<
T.Effects["removeAddress"]
>
},
restart(...[]: Parameters<T.Effects["restart"]>) {
return rpcRound("restart", {}) as ReturnType<T.Effects["restart"]>
},
running(...[packageId]: Parameters<T.Effects["running"]>) {
return rpcRound("running", { packageId }) as ReturnType<
T.Effects["running"]
>
},
// runRsync(...[options]: Parameters<T.Effects[""]>) {
//
// return rpcRound('executeAction', options) as ReturnType<T.Effects["executeAction"]>
//
// return rpcRound('executeAction', options) as ReturnType<T.Effects["executeAction"]>
// }
setConfigured(...[configured]: Parameters<T.Effects["setConfigured"]>) {
return rpcRound("setConfigured", { configured }) as ReturnType<
T.Effects["setConfigured"]
>
},
setDependencies(
dependencies: Parameters<T.Effects["setDependencies"]>[0],
): ReturnType<T.Effects["setDependencies"]> {
return rpcRound("setDependencies", dependencies) as ReturnType<
T.Effects["setDependencies"]
>
},
checkDependencies(
options: Parameters<T.Effects["checkDependencies"]>[0],
): ReturnType<T.Effects["checkDependencies"]> {
return rpcRound("checkDependencies", options) as ReturnType<
T.Effects["checkDependencies"]
>
},
getDependencies(): ReturnType<T.Effects["getDependencies"]> {
return rpcRound("getDependencies", {}) as ReturnType<
T.Effects["getDependencies"]
>
},
setHealth(...[options]: Parameters<T.Effects["setHealth"]>) {
return rpcRound("setHealth", options) as ReturnType<
T.Effects["setHealth"]
>
},
setMainStatus(o: { status: "running" | "stopped" }): Promise<void> {
return this.rpcRound("setMainStatus", o) as ReturnType<
T.Effects["setHealth"]
>
}
setMainStatus(o: { status: "running" | "stopped" }): Promise<void> {
return rpcRound("setMainStatus", o) as ReturnType<
T.Effects["setHealth"]
>
},
shutdown(...[]: Parameters<T.Effects["shutdown"]>) {
return this.rpcRound("shutdown", {}) as ReturnType<T.Effects["shutdown"]>
shutdown(...[]: Parameters<T.Effects["shutdown"]>) {
return rpcRound("shutdown", {}) as ReturnType<T.Effects["shutdown"]>
},
stopped(...[packageId]: Parameters<T.Effects["stopped"]>) {
return rpcRound("stopped", { packageId }) as ReturnType<
T.Effects["stopped"]
>
},
store: {
get: async (options: any) =>
rpcRound("getStore", {
...options,
callback: callbackHolder.addCallback(options.callback),
}) as any,
set: async (options: any) =>
rpcRound("setStore", options) as ReturnType<
T.Effects["store"]["set"]
>,
} as T.Effects["store"],
}
return self
}
stopped(...[packageId]: Parameters<T.Effects["stopped"]>) {
return this.rpcRound("stopped", { packageId }) as ReturnType<
T.Effects["stopped"]
>
}
store: T.Effects["store"] = {
get: async (options: any) =>
this.rpcRound("getStore", {
...options,
callback: this.callbackHolder.addCallback(options.callback),
}) as any,
set: async (options: any) =>
this.rpcRound("setStore", options) as ReturnType<
T.Effects["store"]["set"]
>,
}
}

View File

@@ -247,7 +247,7 @@ export class RpcListener {
})),
)
.when(exitType, async ({ id }) => {
if (this._system) await this._system.exit(this.effects)
if (this._system) await this._system.exit(this.effects(null))
delete this._system
delete this._effects

View File

@@ -1,9 +1,10 @@
import { PolyfillEffects } from "./polyfillEffects"
import { polyfillEffects } from "./polyfillEffects"
import { DockerProcedureContainer } from "./DockerProcedureContainer"
import { SystemForEmbassy } from "."
import { HostSystemStartOs } from "../../HostSystemStartOs"
import { hostSystemStartOs } from "../../HostSystemStartOs"
import { Daemons, T, daemons } from "@start9labs/start-sdk"
import { Daemon } from "@start9labs/start-sdk/cjs/lib/mainFn/Daemon"
import { Effects } from "../../../Models/Effects"
const EMBASSY_HEALTH_INTERVAL = 15 * 1000
const EMBASSY_PROPERTIES_LOOP = 30 * 1000
@@ -27,7 +28,7 @@ export class MainLoop {
| undefined
constructor(
readonly system: SystemForEmbassy,
readonly effects: HostSystemStartOs,
readonly effects: Effects,
) {
this.healthLoops = this.constructHealthLoops()
this.mainEvent = this.constructMainEvent()
@@ -65,7 +66,7 @@ export class MainLoop {
}
}
private async setupInterfaces(effects: HostSystemStartOs) {
private async setupInterfaces(effects: T.Effects) {
for (const interfaceId in this.system.manifest.interfaces) {
const iface = this.system.manifest.interfaces[interfaceId]
const internalPorts = new Set<number>()
@@ -211,7 +212,7 @@ export class MainLoop {
}
const result = await method(
new PolyfillEffects(effects, this.system.manifest),
polyfillEffects(effects, this.system.manifest),
timeChanged,
)

View File

@@ -1,7 +1,7 @@
import { types as T, utils, EmVer } from "@start9labs/start-sdk"
import * as fs from "fs/promises"
import { PolyfillEffects } from "./polyfillEffects"
import { polyfillEffects } from "./polyfillEffects"
import { Duration, duration, fromDuration } from "../../../Models/Duration"
import { System } from "../../../Interfaces/System"
import { matchManifest, Manifest, Procedure } from "./matchManifest"
@@ -27,7 +27,7 @@ import {
Parser,
array,
} from "ts-matches"
import { HostSystemStartOs } from "../../HostSystemStartOs"
import { hostSystemStartOs } from "../../HostSystemStartOs"
import { JsonPath, unNestPath } from "../../../Models/JsonPath"
import { RpcResult, matchRpcResult } from "../../RpcListener"
import { CT } from "@start9labs/start-sdk"
@@ -41,6 +41,7 @@ import {
MultiHost,
} from "@start9labs/start-sdk/cjs/lib/interfaces/Host"
import { ServiceInterfaceBuilder } from "@start9labs/start-sdk/cjs/lib/interfaces/ServiceInterfaceBuilder"
import { Effects } from "../../../Models/Effects"
type Optional<A> = A | undefined | null
function todo(): never {
@@ -197,7 +198,7 @@ export class SystemForEmbassy implements System {
readonly moduleCode: Partial<U.ExpectedExports>,
) {}
async execute(
effects: HostSystemStartOs,
effectCreator: ReturnType<typeof hostSystemStartOs>,
options: {
id: string
procedure: JsonPath
@@ -205,8 +206,7 @@ export class SystemForEmbassy implements System {
timeout?: number | undefined
},
): Promise<RpcResult> {
effects = Object.create(effects)
effects.procedureId = options.id
const effects = effectCreator(options.id)
return this._execute(effects, options)
.then((x) =>
matches(x)
@@ -261,12 +261,12 @@ export class SystemForEmbassy implements System {
}
})
}
async exit(effects: HostSystemStartOs): Promise<void> {
async exit(): Promise<void> {
if (this.currentRunning) await this.currentRunning.clean()
delete this.currentRunning
}
async _execute(
effects: HostSystemStartOs,
effects: Effects,
options: {
procedure: JsonPath
input: unknown
@@ -340,7 +340,7 @@ export class SystemForEmbassy implements System {
throw new Error(`Could not find the path for ${options.procedure}`)
}
private async init(
effects: HostSystemStartOs,
effects: Effects,
previousVersion: Optional<string>,
timeoutMs: number | null,
): Promise<void> {
@@ -350,7 +350,7 @@ export class SystemForEmbassy implements System {
await this.exportActions(effects)
await this.exportNetwork(effects)
}
async exportNetwork(effects: HostSystemStartOs) {
async exportNetwork(effects: Effects) {
for (const [id, interfaceValue] of Object.entries(
this.manifest.interfaces,
)) {
@@ -428,7 +428,7 @@ export class SystemForEmbassy implements System {
)
}
}
async exportActions(effects: HostSystemStartOs) {
async exportActions(effects: Effects) {
const manifest = this.manifest
if (!manifest.actions) return
for (const [actionId, action] of Object.entries(manifest.actions)) {
@@ -457,7 +457,7 @@ export class SystemForEmbassy implements System {
}
}
private async uninit(
effects: HostSystemStartOs,
effects: Effects,
nextVersion: Optional<string>,
timeoutMs: number | null,
): Promise<void> {
@@ -465,7 +465,7 @@ export class SystemForEmbassy implements System {
await effects.setMainStatus({ status: "stopped" })
}
private async mainStart(
effects: HostSystemStartOs,
effects: Effects,
timeoutMs: number | null,
): Promise<void> {
if (!!this.currentRunning) return
@@ -473,7 +473,7 @@ export class SystemForEmbassy implements System {
this.currentRunning = new MainLoop(this, effects)
}
private async mainStop(
effects: HostSystemStartOs,
effects: Effects,
timeoutMs: number | null,
): Promise<Duration> {
const { currentRunning } = this
@@ -491,7 +491,7 @@ export class SystemForEmbassy implements System {
return durationValue
}
private async createBackup(
effects: HostSystemStartOs,
effects: Effects,
timeoutMs: number | null,
): Promise<void> {
const backup = this.manifest.backup.create
@@ -503,13 +503,11 @@ export class SystemForEmbassy implements System {
await container.execFail([backup.entrypoint, ...backup.args], timeoutMs)
} else {
const moduleCode = await this.moduleCode
await moduleCode.createBackup?.(
new PolyfillEffects(effects, this.manifest),
)
await moduleCode.createBackup?.(polyfillEffects(effects, this.manifest))
}
}
private async restoreBackup(
effects: HostSystemStartOs,
effects: Effects,
timeoutMs: number | null,
): Promise<void> {
const restoreBackup = this.manifest.backup.restore
@@ -528,19 +526,17 @@ export class SystemForEmbassy implements System {
)
} else {
const moduleCode = await this.moduleCode
await moduleCode.restoreBackup?.(
new PolyfillEffects(effects, this.manifest),
)
await moduleCode.restoreBackup?.(polyfillEffects(effects, this.manifest))
}
}
private async getConfig(
effects: HostSystemStartOs,
effects: Effects,
timeoutMs: number | null,
): Promise<T.ConfigRes> {
return this.getConfigUncleaned(effects, timeoutMs).then(removePointers)
}
private async getConfigUncleaned(
effects: HostSystemStartOs,
effects: Effects,
timeoutMs: number | null,
): Promise<T.ConfigRes> {
const config = this.manifest.config?.get
@@ -564,7 +560,7 @@ export class SystemForEmbassy implements System {
const moduleCode = await this.moduleCode
const method = moduleCode.getConfig
if (!method) throw new Error("Expecting that the method getConfig exists")
return (await method(new PolyfillEffects(effects, this.manifest)).then(
return (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)
@@ -574,7 +570,7 @@ export class SystemForEmbassy implements System {
}
}
private async setConfig(
effects: HostSystemStartOs,
effects: Effects,
newConfigWithoutPointers: unknown,
timeoutMs: number | null,
): Promise<void> {
@@ -617,7 +613,7 @@ export class SystemForEmbassy implements System {
const answer = matchSetResult.unsafeCast(
await method(
new PolyfillEffects(effects, this.manifest),
polyfillEffects(effects, this.manifest),
newConfig as U.Config,
).then((x): T.SetResult => {
if ("result" in x)
@@ -636,7 +632,7 @@ export class SystemForEmbassy implements System {
}
}
private async setConfigSetConfig(
effects: HostSystemStartOs,
effects: Effects,
dependsOn: { [x: string]: readonly string[] },
) {
await effects.setDependencies({
@@ -660,7 +656,7 @@ export class SystemForEmbassy implements System {
}
private async migration(
effects: HostSystemStartOs,
effects: Effects,
fromVersion: string,
timeoutMs: number | null,
): Promise<T.MigrationRes> {
@@ -713,7 +709,7 @@ export class SystemForEmbassy implements System {
if (!method)
throw new Error("Expecting that the method migration exists")
return (await method(
new PolyfillEffects(effects, this.manifest),
polyfillEffects(effects, this.manifest),
fromVersion as string,
).then((x) => {
if ("result" in x) return x.result
@@ -725,7 +721,7 @@ export class SystemForEmbassy implements System {
return { configured: true }
}
private async properties(
effects: HostSystemStartOs,
effects: Effects,
timeoutMs: number | null,
): Promise<ReturnType<T.ExpectedExports.properties>> {
// TODO BLU-J set the properties ever so often
@@ -754,7 +750,7 @@ export class SystemForEmbassy implements System {
if (!method)
throw new Error("Expecting that the method properties exists")
const properties = matchProperties.unsafeCast(
await method(new PolyfillEffects(effects, this.manifest)).then((x) => {
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])
@@ -765,7 +761,7 @@ export class SystemForEmbassy implements System {
throw new Error(`Unknown type in the fetch properties: ${setConfigValue}`)
}
private async action(
effects: HostSystemStartOs,
effects: Effects,
actionId: string,
formData: unknown,
timeoutMs: number | null,
@@ -795,7 +791,7 @@ export class SystemForEmbassy implements System {
const method = moduleCode.action?.[actionId]
if (!method) throw new Error("Expecting that the method action exists")
return (await method(
new PolyfillEffects(effects, this.manifest),
polyfillEffects(effects, this.manifest),
formData as any,
).then((x) => {
if ("result" in x) return x.result
@@ -805,7 +801,7 @@ export class SystemForEmbassy implements System {
}
}
private async dependenciesCheck(
effects: HostSystemStartOs,
effects: Effects,
id: string,
oldConfig: unknown,
timeoutMs: number | null,
@@ -838,7 +834,7 @@ export class SystemForEmbassy implements System {
`Expecting that the method dependency check ${id} exists`,
)
return (await method(
new PolyfillEffects(effects, this.manifest),
polyfillEffects(effects, this.manifest),
oldConfig as any,
).then((x) => {
if ("result" in x) return x.result
@@ -850,7 +846,7 @@ export class SystemForEmbassy implements System {
}
}
private async dependenciesAutoconfig(
effects: HostSystemStartOs,
effects: Effects,
id: string,
oldConfig: unknown,
timeoutMs: number | null,
@@ -863,7 +859,7 @@ export class SystemForEmbassy implements System {
`Expecting that the method dependency autoConfigure ${id} exists`,
)
return (await method(
new PolyfillEffects(effects, this.manifest),
polyfillEffects(effects, this.manifest),
oldConfig as any,
).then((x) => {
if ("result" in x) return x.result
@@ -961,7 +957,7 @@ function cleanConfigFromPointers<C, S>(
}
async function updateConfig(
effects: HostSystemStartOs,
effects: Effects,
manifest: Manifest,
spec: unknown,
mutConfigValue: unknown,

View File

@@ -4,390 +4,175 @@ import { Volume } from "../../../Models/Volume"
import * as child_process from "child_process"
import { promisify } from "util"
import { daemons, startSdk, T } from "@start9labs/start-sdk"
import { HostSystemStartOs } from "../../HostSystemStartOs"
import "isomorphic-fetch"
import { Manifest } from "./matchManifest"
import { DockerProcedureContainer } from "./DockerProcedureContainer"
import * as cp from "child_process"
import { Effects } from "../../../Models/Effects"
export const execFile = promisify(cp.execFile)
export class PolyfillEffects implements oet.Effects {
constructor(
readonly effects: HostSystemStartOs,
private manifest: Manifest,
) {}
async writeFile(input: {
path: string
volumeId: string
toWrite: string
}): Promise<void> {
await fs.writeFile(
new Volume(input.volumeId, input.path).path,
input.toWrite,
)
}
async readFile(input: { volumeId: string; path: string }): Promise<string> {
return (
await fs.readFile(new Volume(input.volumeId, input.path).path)
).toString()
}
async metadata(input: {
volumeId: string
path: string
}): Promise<oet.Metadata> {
const stats = await fs.stat(new Volume(input.volumeId, input.path).path)
return {
fileType: stats.isFile() ? "file" : "directory",
gid: stats.gid,
uid: stats.uid,
mode: stats.mode,
isDir: stats.isDirectory(),
isFile: stats.isFile(),
isSymlink: stats.isSymbolicLink(),
len: stats.size,
readonly: (stats.mode & 0o200) > 0,
}
}
async createDir(input: { volumeId: string; path: string }): Promise<string> {
const path = new Volume(input.volumeId, input.path).path
await fs.mkdir(path, { recursive: true })
return path
}
async readDir(input: { volumeId: string; path: string }): Promise<string[]> {
return fs.readdir(new Volume(input.volumeId, input.path).path)
}
async removeDir(input: { volumeId: string; path: string }): Promise<string> {
const path = new Volume(input.volumeId, input.path).path
await fs.rmdir(new Volume(input.volumeId, input.path).path, {
recursive: true,
})
return path
}
removeFile(input: { volumeId: string; path: string }): Promise<void> {
return fs.rm(new Volume(input.volumeId, input.path).path)
}
async writeJsonFile(input: {
volumeId: string
path: string
toWrite: Record<string, unknown>
}): Promise<void> {
await fs.writeFile(
new Volume(input.volumeId, input.path).path,
JSON.stringify(input.toWrite),
)
}
async readJsonFile(input: {
volumeId: string
path: string
}): Promise<Record<string, unknown>> {
return JSON.parse(
(
export const polyfillEffects = (
effects: Effects,
manifest: Manifest,
): oet.Effects => {
const self = {
effects,
manifest,
async writeFile(input: {
path: string
volumeId: string
toWrite: string
}): Promise<void> {
await fs.writeFile(
new Volume(input.volumeId, input.path).path,
input.toWrite,
)
},
async readFile(input: { volumeId: string; path: string }): Promise<string> {
return (
await fs.readFile(new Volume(input.volumeId, input.path).path)
).toString(),
)
}
runCommand({
command,
args,
timeoutMillis,
}: {
command: string
args?: string[] | undefined
timeoutMillis?: number | undefined
}): Promise<oet.ResultType<string>> {
return startSdk
.runCommand(
this.effects,
{ id: this.manifest.main.image },
[command, ...(args || [])],
{},
).toString()
},
async metadata(input: {
volumeId: string
path: string
}): Promise<oet.Metadata> {
const stats = await fs.stat(new Volume(input.volumeId, input.path).path)
return {
fileType: stats.isFile() ? "file" : "directory",
gid: stats.gid,
uid: stats.uid,
mode: stats.mode,
isDir: stats.isDirectory(),
isFile: stats.isFile(),
isSymlink: stats.isSymbolicLink(),
len: stats.size,
readonly: (stats.mode & 0o200) > 0,
}
},
async createDir(input: {
volumeId: string
path: string
}): Promise<string> {
const path = new Volume(input.volumeId, input.path).path
await fs.mkdir(path, { recursive: true })
return path
},
async readDir(input: {
volumeId: string
path: string
}): Promise<string[]> {
return fs.readdir(new Volume(input.volumeId, input.path).path)
},
async removeDir(input: {
volumeId: string
path: string
}): Promise<string> {
const path = new Volume(input.volumeId, input.path).path
await fs.rmdir(new Volume(input.volumeId, input.path).path, {
recursive: true,
})
return path
},
removeFile(input: { volumeId: string; path: string }): Promise<void> {
return fs.rm(new Volume(input.volumeId, input.path).path)
},
async writeJsonFile(input: {
volumeId: string
path: string
toWrite: Record<string, unknown>
}): Promise<void> {
await fs.writeFile(
new Volume(input.volumeId, input.path).path,
JSON.stringify(input.toWrite),
)
.then((x: any) => ({
stderr: x.stderr.toString(),
stdout: x.stdout.toString(),
}))
.then((x: any) =>
!!x.stderr ? { error: x.stderr } : { result: x.stdout },
},
async readJsonFile(input: {
volumeId: string
path: string
}): Promise<Record<string, unknown>> {
return JSON.parse(
(
await fs.readFile(new Volume(input.volumeId, input.path).path)
).toString(),
)
}
runDaemon(input: { command: string; args?: string[] | undefined }): {
wait(): Promise<oet.ResultType<string>>
term(): Promise<void>
} {
const dockerProcedureContainer = DockerProcedureContainer.of(
this.effects,
this.manifest.main,
this.manifest.volumes,
)
const daemon = dockerProcedureContainer.then((dockerProcedureContainer) =>
daemons.runCommand()(
this.effects,
{ id: this.manifest.main.image },
[input.command, ...(input.args || [])],
{
overlay: dockerProcedureContainer.overlay,
},
),
)
return {
wait: () =>
daemon.then((daemon) =>
daemon.wait().then(() => {
return { result: "" }
}),
},
runCommand({
command,
args,
timeoutMillis,
}: {
command: string
args?: string[] | undefined
timeoutMillis?: number | undefined
}): Promise<oet.ResultType<string>> {
return startSdk
.runCommand(
effects,
{ id: manifest.main.image },
[command, ...(args || [])],
{},
)
.then((x: any) => ({
stderr: x.stderr.toString(),
stdout: x.stdout.toString(),
}))
.then((x: any) =>
!!x.stderr ? { error: x.stderr } : { result: x.stdout },
)
},
runDaemon(input: { command: string; args?: string[] | undefined }): {
wait(): Promise<oet.ResultType<string>>
term(): Promise<void>
} {
const dockerProcedureContainer = DockerProcedureContainer.of(
effects,
manifest.main,
manifest.volumes,
)
const daemon = dockerProcedureContainer.then((dockerProcedureContainer) =>
daemons.runCommand()(
effects,
{ id: manifest.main.image },
[input.command, ...(input.args || [])],
{
overlay: dockerProcedureContainer.overlay,
},
),
term: () => daemon.then((daemon) => daemon.term()),
}
}
async chown(input: {
volumeId: string
path: string
uid: string
}): Promise<null> {
await startSdk
.runCommand(
this.effects,
{ id: this.manifest.main.image },
["chown", "--recursive", input.uid, `/drive/${input.path}`],
{
mounts: [
{
path: "/drive",
options: {
type: "volume",
id: input.volumeId,
subpath: null,
readonly: false,
},
},
],
},
)
.then((x: any) => ({
stderr: x.stderr.toString(),
stdout: x.stdout.toString(),
}))
.then((x: any) => {
if (!!x.stderr) {
throw new Error(x.stderr)
}
})
return null
}
async chmod(input: {
volumeId: string
path: string
mode: string
}): Promise<null> {
await startSdk
.runCommand(
this.effects,
{ id: this.manifest.main.image },
["chmod", "--recursive", input.mode, `/drive/${input.path}`],
{
mounts: [
{
path: "/drive",
options: {
type: "volume",
id: input.volumeId,
subpath: null,
readonly: false,
return {
wait: () =>
daemon.then((daemon) =>
daemon.wait().then(() => {
return { result: "" }
}),
),
term: () => daemon.then((daemon) => daemon.term()),
}
},
async chown(input: {
volumeId: string
path: string
uid: string
}): Promise<null> {
await startSdk
.runCommand(
effects,
{ id: manifest.main.image },
["chown", "--recursive", input.uid, `/drive/${input.path}`],
{
mounts: [
{
path: "/drive",
options: {
type: "volume",
id: input.volumeId,
subpath: null,
readonly: false,
},
},
},
],
},
)
.then((x: any) => ({
stderr: x.stderr.toString(),
stdout: x.stdout.toString(),
}))
.then((x: any) => {
if (!!x.stderr) {
throw new Error(x.stderr)
}
})
return null
}
sleep(timeMs: number): Promise<null> {
return new Promise((resolve) => setTimeout(resolve, timeMs))
}
trace(whatToPrint: string): void {
console.trace(whatToPrint)
}
warn(whatToPrint: string): void {
console.warn(whatToPrint)
}
error(whatToPrint: string): void {
console.error(whatToPrint)
}
debug(whatToPrint: string): void {
console.debug(whatToPrint)
}
info(whatToPrint: string): void {
console.log(false)
}
is_sandboxed(): boolean {
return false
}
exists(input: { volumeId: string; path: string }): Promise<boolean> {
return this.metadata(input)
.then(() => true)
.catch(() => false)
}
async fetch(
url: string,
options?:
| {
method?:
| "GET"
| "POST"
| "PUT"
| "DELETE"
| "HEAD"
| "PATCH"
| undefined
headers?: Record<string, string> | undefined
body?: string | undefined
}
| undefined,
): Promise<{
method: string
ok: boolean
status: number
headers: Record<string, string>
body?: string | null | undefined
text(): Promise<string>
json(): Promise<unknown>
}> {
const fetched = await fetch(url, options)
return {
method: fetched.type,
ok: fetched.ok,
status: fetched.status,
headers: Object.fromEntries(fetched.headers.entries()),
body: await fetched.text(),
text: () => fetched.text(),
json: () => fetched.json(),
}
}
runRsync(rsyncOptions: {
srcVolume: string
dstVolume: string
srcPath: string
dstPath: string
options: oet.BackupOptions
}): {
id: () => Promise<string>
wait: () => Promise<null>
progress: () => Promise<number>
} {
let secondRun: ReturnType<typeof this._runRsync> | undefined
let firstRun = this._runRsync(rsyncOptions)
let waitValue = firstRun.wait().then((x) => {
secondRun = this._runRsync(rsyncOptions)
return secondRun.wait()
})
const id = async () => {
return secondRun?.id?.() ?? firstRun.id()
}
const wait = () => waitValue
const progress = async () => {
const secondProgress = secondRun?.progress?.()
if (secondProgress) {
return (await secondProgress) / 2.0 + 0.5
}
return (await firstRun.progress()) / 2.0
}
return { id, wait, progress }
}
_runRsync(rsyncOptions: {
srcVolume: string
dstVolume: string
srcPath: string
dstPath: string
options: oet.BackupOptions
}): {
id: () => Promise<string>
wait: () => Promise<null>
progress: () => Promise<number>
} {
const { srcVolume, dstVolume, srcPath, dstPath, options } = rsyncOptions
const command = "rsync"
const args: string[] = []
if (options.delete) {
args.push("--delete")
}
if (options.force) {
args.push("--force")
}
if (options.ignoreExisting) {
args.push("--ignore-existing")
}
for (const exclude of options.exclude) {
args.push(`--exclude=${exclude}`)
}
args.push("-actAXH")
args.push("--info=progress2")
args.push("--no-inc-recursive")
args.push(new Volume(srcVolume, srcPath).path)
args.push(new Volume(dstVolume, dstPath).path)
const spawned = child_process.spawn(command, args, { detached: true })
let percentage = 0.0
spawned.stdout.on("data", (data: unknown) => {
const lines = String(data).replace("\r", "\n").split("\n")
for (const line of lines) {
const parsed = /$([0-9.]+)%/.exec(line)?.[1]
if (!parsed) continue
percentage = Number.parseFloat(parsed)
}
})
spawned.stderr.on("data", (data: unknown) => {
console.error(String(data))
})
const id = async () => {
const pid = spawned.pid
if (pid === undefined) {
throw new Error("rsync process has no pid")
}
return String(pid)
}
const waitPromise = new Promise<null>((resolve, reject) => {
spawned.on("exit", (code: any) => {
if (code === 0) {
resolve(null)
} else {
reject(new Error(`rsync exited with code ${code}`))
}
})
})
const wait = () => waitPromise
const progress = () => Promise.resolve(percentage)
return { id, wait, progress }
}
async diskUsage(
options?: { volumeId: string; path: string } | undefined,
): Promise<{ used: number; total: number }> {
const output = await execFile("df", ["--block-size=1", "-P", "/"])
.then((x: any) => ({
stderr: x.stderr.toString(),
stdout: x.stdout.toString(),
}))
.then((x: any) => {
if (!!x.stderr) {
throw new Error(x.stderr)
}
return parseDfOutput(x.stdout)
})
if (!!options) {
const used = await execFile("du", [
"-s",
"--block-size=1",
"-P",
new Volume(options.volumeId, options.path).path,
])
],
},
)
.then((x: any) => ({
stderr: x.stderr.toString(),
stdout: x.stdout.toString(),
@@ -396,15 +181,244 @@ export class PolyfillEffects implements oet.Effects {
if (!!x.stderr) {
throw new Error(x.stderr)
}
return Number.parseInt(x.stdout.split(/\s+/)[0])
})
return null
},
async chmod(input: {
volumeId: string
path: string
mode: string
}): Promise<null> {
await startSdk
.runCommand(
effects,
{ id: manifest.main.image },
["chmod", "--recursive", input.mode, `/drive/${input.path}`],
{
mounts: [
{
path: "/drive",
options: {
type: "volume",
id: input.volumeId,
subpath: null,
readonly: false,
},
},
],
},
)
.then((x: any) => ({
stderr: x.stderr.toString(),
stdout: x.stdout.toString(),
}))
.then((x: any) => {
if (!!x.stderr) {
throw new Error(x.stderr)
}
})
return null
},
sleep(timeMs: number): Promise<null> {
return new Promise((resolve) => setTimeout(resolve, timeMs))
},
trace(whatToPrint: string): void {
console.trace(whatToPrint)
},
warn(whatToPrint: string): void {
console.warn(whatToPrint)
},
error(whatToPrint: string): void {
console.error(whatToPrint)
},
debug(whatToPrint: string): void {
console.debug(whatToPrint)
},
info(whatToPrint: string): void {
console.log(false)
},
is_sandboxed(): boolean {
return false
},
exists(input: { volumeId: string; path: string }): Promise<boolean> {
return self
.metadata(input)
.then(() => true)
.catch(() => false)
},
async fetch(
url: string,
options?:
| {
method?:
| "GET"
| "POST"
| "PUT"
| "DELETE"
| "HEAD"
| "PATCH"
| undefined
headers?: Record<string, string> | undefined
body?: string | undefined
}
| undefined,
): Promise<{
method: string
ok: boolean
status: number
headers: Record<string, string>
body?: string | null | undefined
text(): Promise<string>
json(): Promise<unknown>
}> {
const fetched = await fetch(url, options)
return {
...output,
used,
method: fetched.type,
ok: fetched.ok,
status: fetched.status,
headers: Object.fromEntries(fetched.headers.entries()),
body: await fetched.text(),
text: () => fetched.text(),
json: () => fetched.json(),
}
}
return output
},
runRsync(rsyncOptions: {
srcVolume: string
dstVolume: string
srcPath: string
dstPath: string
options: oet.BackupOptions
}): {
id: () => Promise<string>
wait: () => Promise<null>
progress: () => Promise<number>
} {
let secondRun: ReturnType<typeof self._runRsync> | undefined
let firstRun = self._runRsync(rsyncOptions)
let waitValue = firstRun.wait().then((x) => {
secondRun = self._runRsync(rsyncOptions)
return secondRun.wait()
})
const id = async () => {
return secondRun?.id?.() ?? firstRun.id()
}
const wait = () => waitValue
const progress = async () => {
const secondProgress = secondRun?.progress?.()
if (secondProgress) {
return (await secondProgress) / 2.0 + 0.5
}
return (await firstRun.progress()) / 2.0
}
return { id, wait, progress }
},
_runRsync(rsyncOptions: {
srcVolume: string
dstVolume: string
srcPath: string
dstPath: string
options: oet.BackupOptions
}): {
id: () => Promise<string>
wait: () => Promise<null>
progress: () => Promise<number>
} {
const { srcVolume, dstVolume, srcPath, dstPath, options } = rsyncOptions
const command = "rsync"
const args: string[] = []
if (options.delete) {
args.push("--delete")
}
if (options.force) {
args.push("--force")
}
if (options.ignoreExisting) {
args.push("--ignore-existing")
}
for (const exclude of options.exclude) {
args.push(`--exclude=${exclude}`)
}
args.push("-actAXH")
args.push("--info=progress2")
args.push("--no-inc-recursive")
args.push(new Volume(srcVolume, srcPath).path)
args.push(new Volume(dstVolume, dstPath).path)
const spawned = child_process.spawn(command, args, { detached: true })
let percentage = 0.0
spawned.stdout.on("data", (data: unknown) => {
const lines = String(data).replace("\r", "\n").split("\n")
for (const line of lines) {
const parsed = /$([0-9.]+)%/.exec(line)?.[1]
if (!parsed) continue
percentage = Number.parseFloat(parsed)
}
})
spawned.stderr.on("data", (data: unknown) => {
console.error(String(data))
})
const id = async () => {
const pid = spawned.pid
if (pid === undefined) {
throw new Error("rsync process has no pid")
}
return String(pid)
}
const waitPromise = new Promise<null>((resolve, reject) => {
spawned.on("exit", (code: any) => {
if (code === 0) {
resolve(null)
} else {
reject(new Error(`rsync exited with code ${code}`))
}
})
})
const wait = () => waitPromise
const progress = () => Promise.resolve(percentage)
return { id, wait, progress }
},
async diskUsage(
options?: { volumeId: string; path: string } | undefined,
): Promise<{ used: number; total: number }> {
const output = await execFile("df", ["--block-size=1", "-P", "/"])
.then((x: any) => ({
stderr: x.stderr.toString(),
stdout: x.stdout.toString(),
}))
.then((x: any) => {
if (!!x.stderr) {
throw new Error(x.stderr)
}
return parseDfOutput(x.stdout)
})
if (!!options) {
const used = await execFile("du", [
"-s",
"--block-size=1",
"-P",
new Volume(options.volumeId, options.path).path,
])
.then((x: any) => ({
stderr: x.stderr.toString(),
stdout: x.stdout.toString(),
}))
.then((x: any) => {
if (!!x.stderr) {
throw new Error(x.stderr)
}
return Number.parseInt(x.stdout.split(/\s+/)[0])
})
return {
...output,
used,
}
}
return output
},
}
return self
}
function parseDfOutput(output: string): { used: number; total: number } {

View File

@@ -1,7 +1,7 @@
import { ExecuteResult, System } from "../../Interfaces/System"
import { unNestPath } from "../../Models/JsonPath"
import matches, { any, number, object, string, tuple } from "ts-matches"
import { HostSystemStartOs } from "../HostSystemStartOs"
import { hostSystemStartOs } from "../HostSystemStartOs"
import { Effects } from "../../Models/Effects"
import { RpcResult, matchRpcResult } from "../RpcListener"
import { duration } from "../../Models/Duration"
@@ -15,7 +15,7 @@ export class SystemForStartOs implements System {
}
constructor(readonly abi: T.ABI) {}
async execute(
effects: HostSystemStartOs,
effectCreator: ReturnType<typeof hostSystemStartOs>,
options: {
id: string
procedure:
@@ -36,8 +36,7 @@ export class SystemForStartOs implements System {
timeout?: number | undefined
},
): Promise<RpcResult> {
effects = Object.create(effects)
effects.procedureId = options.id
const effects = effectCreator(options.id)
return this._execute(effects, options)
.then((x) =>
matches(x)

View File

@@ -2,6 +2,7 @@ import { types as T } from "@start9labs/start-sdk"
import { CallbackHolder } from "../Models/CallbackHolder"
import { Effects } from "../Models/Effects"
export type HostSystem = Effects
export type GetHostSystem = (callbackHolder: CallbackHolder) => HostSystem
export type GetHostSystem = (
callbackHolder: CallbackHolder,
) => (procedureId: null | string) => Effects

View File

@@ -1,7 +1,7 @@
import { types as T } from "@start9labs/start-sdk"
import { JsonPath } from "../Models/JsonPath"
import { HostSystemStartOs } from "../Adapters/HostSystemStartOs"
import { RpcResult } from "../Adapters/RpcListener"
import { hostSystemStartOs } from "../Adapters/HostSystemStartOs"
export type ExecuteResult =
| { ok: unknown }
| { err: { code: number; message: string } }
@@ -12,7 +12,7 @@ export interface System {
// stop(effects: Effects, options: { timeout: number, signal?: number }): Promise<void>
execute(
effects: T.Effects,
effectCreator: ReturnType<typeof hostSystemStartOs>,
options: {
id: string
procedure: JsonPath

View File

@@ -1,12 +1,12 @@
import { RpcListener } from "./Adapters/RpcListener"
import { SystemForEmbassy } from "./Adapters/Systems/SystemForEmbassy"
import { HostSystemStartOs } from "./Adapters/HostSystemStartOs"
import { hostSystemStartOs } from "./Adapters/HostSystemStartOs"
import { AllGetDependencies } from "./Interfaces/AllGetDependencies"
import { getSystem } from "./Adapters/Systems"
const getDependencies: AllGetDependencies = {
system: getSystem,
hostSystem: () => HostSystemStartOs.of,
hostSystem: () => hostSystemStartOs,
}
new RpcListener(getDependencies)

View File

@@ -16,7 +16,8 @@ mkdir -p ./firmware/$PLATFORM
cd ./firmware/$PLATFORM
mapfile -t firmwares <<< "$(jq -c ".[] | select(.platform[] | contains(\"$PLATFORM\"))" ../../build/lib/firmware.json)"
firmwares=()
while IFS= read -r line; do firmwares+=("$line"); done < <(jq -c ".[] | select(.platform[] | contains(\"$PLATFORM\"))" ../../build/lib/firmware.json)
for firmware in "${firmwares[@]}"; do
if [ -n "$firmware" ]; then
id=$(echo "$firmware" | jq --raw-output '.id')