Refactor/actions (#2733)

* store, properties, manifest

* interfaces

* init and backups

* fix init and backups

* file models

* more versions

* dependencies

* config except dynamic types

* clean up config

* remove disabled from non-dynamic vaues

* actions

* standardize example code block formats

* wip: actions refactor

Co-authored-by: Jade <Blu-J@users.noreply.github.com>

* commit types

* fix types

* update types

* update action request type

* update apis

* add description to actionrequest

* clean up imports

* revert package json

* chore: Remove the recursive to the index

* chore: Remove the other thing I was testing

* flatten action requests

* update container runtime with new config paradigm

* new actions strategy

* seems to be working

* misc backend fixes

* fix fe bugs

* only show breakages if breakages

* only show success modal if result

* don't panic on failed removal

* hide config from actions page

* polyfill autoconfig

* use metadata strategy for actions instead of prev

* misc fixes

* chore: split the sdk into 2 libs (#2736)

* follow sideload progress (#2718)

* follow sideload progress

* small bugfix

* shareReplay with no refcount false

* don't wrap sideload progress in RPCResult

* dont present toast

---------

Co-authored-by: Aiden McClelland <me@drbonez.dev>

* chore: Add the initial of the creation of the two sdk

* chore: Add in the baseDist

* chore: Add in the baseDist

* chore: Get the web and the runtime-container running

* chore: Remove the empty file

* chore: Fix it so the container-runtime works

---------

Co-authored-by: Matt Hill <MattDHill@users.noreply.github.com>
Co-authored-by: Aiden McClelland <me@drbonez.dev>

* misc fixes

* update todos

* minor clean up

* fix link script

* update node version in CI test

* fix node version syntax in ci build

* wip: fixing callbacks

* fix sdk makefile dependencies

* add support for const outside of main

* update apis

* don't panic!

* Chore: Capture weird case on rpc, and log that

* fix procedure id issue

* pass input value for dep auto config

* handle disabled and warning for actions

* chore: Fix for link not having node_modules

* sdk fixes

* fix build

* fix build

* fix build

---------

Co-authored-by: Matt Hill <mattnine@protonmail.com>
Co-authored-by: Jade <Blu-J@users.noreply.github.com>
Co-authored-by: J H <dragondef@gmail.com>
Co-authored-by: Jade <2364004+Blu-J@users.noreply.github.com>
Co-authored-by: Matt Hill <MattDHill@users.noreply.github.com>
This commit is contained in:
Aiden McClelland
2024-09-25 16:12:52 -06:00
committed by GitHub
parent eec5cf6b65
commit db0695126f
469 changed files with 16218 additions and 10485 deletions

View File

@@ -20,7 +20,6 @@
"node-fetch": "^3.1.0",
"ts-matches": "^5.5.1",
"tslib": "^2.5.3",
"tslog": "^4.9.3",
"typescript": "^5.1.3",
"yaml": "^2.3.1"
},
@@ -36,9 +35,36 @@
"typescript": ">5.2"
}
},
"../sdk/baseDist": {
"name": "@start9labs/start-sdk-base",
"extraneous": true,
"license": "MIT",
"dependencies": {
"@iarna/toml": "^2.2.5",
"@noble/curves": "^1.4.0",
"@noble/hashes": "^1.4.0",
"isomorphic-fetch": "^3.0.0",
"lodash.merge": "^4.6.2",
"mime": "^4.0.3",
"ts-matches": "^5.5.1",
"yaml": "^2.2.2"
},
"devDependencies": {
"@types/jest": "^29.4.0",
"@types/lodash.merge": "^4.6.2",
"jest": "^29.4.3",
"peggy": "^3.0.2",
"prettier": "^3.2.5",
"ts-jest": "^29.0.5",
"ts-node": "^10.9.1",
"ts-pegjs": "^4.2.1",
"tsx": "^4.7.1",
"typescript": "^5.0.4"
}
},
"../sdk/dist": {
"name": "@start9labs/start-sdk",
"version": "0.3.6-alpha6",
"version": "0.3.6-alpha8",
"license": "MIT",
"dependencies": {
"@iarna/toml": "^2.2.5",
@@ -5628,17 +5654,6 @@
"version": "2.6.3",
"license": "0BSD"
},
"node_modules/tslog": {
"version": "4.9.3",
"resolved": "https://registry.npmjs.org/tslog/-/tslog-4.9.3.tgz",
"integrity": "sha512-oDWuGVONxhVEBtschLf2cs/Jy8i7h1T+CpdkTNWQgdAF7DhRo2G8vMCgILKe7ojdEkLhICWgI1LYSSKaJsRgcw==",
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/fullstack-build/tslog?sponsor=1"
}
},
"node_modules/type-check": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
@@ -9758,11 +9773,6 @@
"tslib": {
"version": "2.6.3"
},
"tslog": {
"version": "4.9.3",
"resolved": "https://registry.npmjs.org/tslog/-/tslog-4.9.3.tgz",
"integrity": "sha512-oDWuGVONxhVEBtschLf2cs/Jy8i7h1T+CpdkTNWQgdAF7DhRo2G8vMCgILKe7ojdEkLhICWgI1LYSSKaJsRgcw=="
},
"type-check": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",

View File

@@ -1,86 +0,0 @@
## Testing
So, we are going to
1. create a fake server
2. pretend socket server os (while the fake server is running)
3. Run a fake effects system (while 1/2 are running)
In order to simulate that we created a server like the start-os and
a fake server (in this case I am using syncthing-wrapper)
### TODO
Undo the packing that I have done earlier, and hijack the embassy.js to use the bundle service + code
Converting embassy.js -> service.js
```sequence {theme="hand"}
startOs ->> startInit.js: Rpc Call
startInit.js ->> service.js: Rpc Converted into js code
```
### Create a fake server
```bash
run_test () {
(
set -e
libs=/home/jh/Projects/start-os/libs/start_init
sockets=/tmp/start9
service=/home/jh/Projects/syncthing-wrapper
docker run \
-v $libs:/libs \
-v $service:/service \
-w /libs \
--rm node:18-alpine \
sh -c "
npm i &&
npm run bundle:esbuild &&
npm run bundle:service
"
docker run \
-v ./libs/start_init/:/libs \
-w /libs \
--rm node:18-alpine \
sh -c "
npm i &&
npm run bundle:esbuild
"
rm -rf $sockets || true
mkdir -p $sockets/sockets
cd $service
docker run \
-v $libs:/start-init \
-v $sockets:/start9 \
--rm -it $(docker build -q .) sh -c "
apk add nodejs &&
node /start-init/bundleEs.js
"
)
}
run_test
```
### Pretend Socket Server OS
First we are going to create our fake server client with the bash then send it the json possible data
```bash
sudo socat - unix-client:/tmp/start9/sockets/rpc.sock
```
<!-- prettier-ignore -->
```json
{"id":"a","method":"run","params":{"methodName":"/dependencyMounts","methodArgs":[]}}
{"id":"a","method":"run","params":{"methodName":"/actions/test","methodArgs":{"input":{"id": 1}}}}
{"id":"b","method":"run","params":{"methodName":"/actions/test","methodArgs":{"id": 1}}}
```

View File

@@ -4,7 +4,7 @@ import { object, string, number, literals, some, unknown } from "ts-matches"
import { Effects } from "../Models/Effects"
import { CallbackHolder } from "../Models/CallbackHolder"
import { MainEffects } from "@start9labs/start-sdk/cjs/lib/StartSdk"
import { asError } from "@start9labs/start-sdk/base/lib/util"
const matchRpcError = object({
error: object(
{
@@ -35,7 +35,8 @@ let hostSystemId = 0
export type EffectContext = {
procedureId: string | null
callbacks: CallbackHolder | null
callbacks?: CallbackHolder
constRetry: () => void
}
const rpcRoundFor =
@@ -50,7 +51,7 @@ const rpcRoundFor =
JSON.stringify({
id,
method,
params: { ...params, procedureId },
params: { ...params, procedureId: procedureId || undefined },
}) + "\n",
)
})
@@ -67,7 +68,7 @@ const rpcRoundFor =
let message = res.error.message
console.error(
"Error in host RPC:",
utils.asError({ method, params }),
utils.asError({ method, params, error: res.error }),
)
if (string.test(res.error.data)) {
message += ": " + res.error.data
@@ -100,9 +101,49 @@ const rpcRoundFor =
})
}
function makeEffects(context: EffectContext): Effects {
export function makeEffects(context: EffectContext): Effects {
const rpcRound = rpcRoundFor(context.procedureId)
const self: Effects = {
constRetry: context.constRetry,
clearCallbacks(...[options]: Parameters<T.Effects["clearCallbacks"]>) {
return rpcRound("clear-callbacks", {
...options,
}) as ReturnType<T.Effects["clearCallbacks"]>
},
action: {
clear(...[options]: Parameters<T.Effects["action"]["clear"]>) {
return rpcRound("action.clear", {
...options,
}) as ReturnType<T.Effects["action"]["clear"]>
},
export(...[options]: Parameters<T.Effects["action"]["export"]>) {
return rpcRound("action.export", {
...options,
}) as ReturnType<T.Effects["action"]["export"]>
},
getInput(...[options]: Parameters<T.Effects["action"]["getInput"]>) {
return rpcRound("action.get-input", {
...options,
}) as ReturnType<T.Effects["action"]["getInput"]>
},
request(...[options]: Parameters<T.Effects["action"]["request"]>) {
return rpcRound("action.request", {
...options,
}) as ReturnType<T.Effects["action"]["request"]>
},
run(...[options]: Parameters<T.Effects["action"]["run"]>) {
return rpcRound("action.run", {
...options,
}) as ReturnType<T.Effects["action"]["run"]>
},
clearRequests(
...[options]: Parameters<T.Effects["action"]["clearRequests"]>
) {
return rpcRound("action.clear-requests", {
...options,
}) as ReturnType<T.Effects["action"]["clearRequests"]>
},
},
bind(...[options]: Parameters<T.Effects["bind"]>) {
return rpcRound("bind", {
...options,
@@ -138,16 +179,6 @@ function makeEffects(context: EffectContext): Effects {
>
},
},
executeAction(...[options]: Parameters<T.Effects["executeAction"]>) {
return rpcRound("execute-action", options) as ReturnType<
T.Effects["executeAction"]
>
},
exportAction(...[options]: Parameters<T.Effects["exportAction"]>) {
return rpcRound("export-action", options) as ReturnType<
T.Effects["exportAction"]
>
},
exportServiceInterface: ((
...[options]: Parameters<Effects["exportServiceInterface"]>
) => {
@@ -162,11 +193,6 @@ function makeEffects(context: EffectContext): Effects {
T.Effects["exposeForDependents"]
>
},
getConfigured(...[]: Parameters<T.Effects["getConfigured"]>) {
return rpcRound("get-configured", {}) as ReturnType<
T.Effects["getConfigured"]
>
},
getContainerIp(...[]: Parameters<T.Effects["getContainerIp"]>) {
return rpcRound("get-container-ip", {}) as ReturnType<
T.Effects["getContainerIp"]
@@ -230,19 +256,9 @@ function makeEffects(context: EffectContext): Effects {
mount(...[options]: Parameters<T.Effects["mount"]>) {
return rpcRound("mount", options) as ReturnType<T.Effects["mount"]>
},
clearActions(...[]: Parameters<T.Effects["clearActions"]>) {
return rpcRound("clear-actions", {}) as ReturnType<
T.Effects["clearActions"]
>
},
restart(...[]: Parameters<T.Effects["restart"]>) {
return rpcRound("restart", {}) as ReturnType<T.Effects["restart"]>
},
setConfigured(...[configured]: Parameters<T.Effects["setConfigured"]>) {
return rpcRound("set-configured", { configured }) as ReturnType<
T.Effects["setConfigured"]
>
},
setDependencies(
dependencies: Parameters<T.Effects["setDependencies"]>[0],
): ReturnType<T.Effects["setDependencies"]> {
@@ -299,18 +315,3 @@ function makeEffects(context: EffectContext): Effects {
}
return self
}
export function makeProcedureEffects(procedureId: string): Effects {
return makeEffects({ procedureId, callbacks: null })
}
export function makeMainEffects(): MainEffects {
const rpcRound = rpcRoundFor(null)
return {
_type: "main",
clearCallbacks: () => {
return rpcRound("clearCallbacks", {}) as Promise<void>
},
...makeEffects({ procedureId: null, callbacks: new CallbackHolder() }),
}
}

View File

@@ -14,17 +14,14 @@ import {
anyOf,
} from "ts-matches"
import { types as T } from "@start9labs/start-sdk"
import { types as T, utils } from "@start9labs/start-sdk"
import * as fs from "fs"
import { CallbackHolder } from "../Models/CallbackHolder"
import { AllGetDependencies } from "../Interfaces/AllGetDependencies"
import { jsonPath, unNestPath } from "../Models/JsonPath"
import { RunningMain, System } from "../Interfaces/System"
import {
MakeMainEffects,
MakeProcedureEffects,
} from "../Interfaces/MakeEffects"
import { System } from "../Interfaces/System"
import { makeEffects } from "./EffectCreator"
type MaybePromise<T> = T | Promise<T>
export const matchRpcResult = anyOf(
object({ result: any }),
@@ -55,33 +52,39 @@ 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({
id: idType,
method: literal("execute"),
params: object(
{
id: string,
procedure: string,
input: any,
timeout: number,
},
["timeout"],
),
})
const sandboxRunType = object({
id: idType,
method: literal("sandbox"),
params: object(
{
id: string,
procedure: string,
input: any,
timeout: number,
},
["timeout"],
),
})
type IdType = null | string | number | undefined
const runType = object(
{
id: idType,
method: literal("execute"),
params: object(
{
id: string,
procedure: string,
input: any,
timeout: number,
},
["timeout"],
),
},
["id"],
)
const sandboxRunType = object(
{
id: idType,
method: literal("sandbox"),
params: object(
{
id: string,
procedure: string,
input: any,
timeout: number,
},
["timeout"],
),
},
["id"],
)
const callbackType = object({
method: literal("callback"),
params: object({
@@ -89,29 +92,44 @@ const callbackType = object({
args: array,
}),
})
const initType = object({
id: idType,
method: literal("init"),
})
const startType = object({
id: idType,
method: literal("start"),
})
const stopType = object({
id: idType,
method: literal("stop"),
})
const exitType = object({
id: idType,
method: literal("exit"),
})
const evalType = object({
id: idType,
method: literal("eval"),
params: object({
script: string,
}),
})
const initType = object(
{
id: idType,
method: literal("init"),
},
["id"],
)
const startType = object(
{
id: idType,
method: literal("start"),
},
["id"],
)
const stopType = object(
{
id: idType,
method: literal("stop"),
},
["id"],
)
const exitType = object(
{
id: idType,
method: literal("exit"),
},
["id"],
)
const evalType = object(
{
id: idType,
method: literal("eval"),
params: object({
script: string,
}),
},
["id"],
)
const jsonParse = (x: string) => JSON.parse(x)
@@ -144,8 +162,7 @@ const hasId = object({ id: idType }).test
export class RpcListener {
unixSocketServer = net.createServer(async (server) => {})
private _system: System | undefined
private _makeProcedureEffects: MakeProcedureEffects | undefined
private _makeMainEffects: MakeMainEffects | undefined
private callbacks: CallbackHolder | undefined
constructor(readonly getDependencies: AllGetDependencies) {
if (!fs.existsSync(SOCKET_PARENT)) {
@@ -212,18 +229,33 @@ export class RpcListener {
return this._system
}
private get makeProcedureEffects() {
if (!this._makeProcedureEffects) {
this._makeProcedureEffects = this.getDependencies.makeProcedureEffects()
private callbackHolders: Map<string, CallbackHolder> = new Map()
private removeCallbackHolderFor(procedure: string) {
const prev = this.callbackHolders.get(procedure)
if (prev) {
this.callbackHolders.delete(procedure)
this.callbacks?.removeChild(prev)
}
return this._makeProcedureEffects
}
private callbackHolderFor(procedure: string): CallbackHolder {
this.removeCallbackHolderFor(procedure)
const callbackHolder = this.callbacks!.child()
this.callbackHolders.set(procedure, callbackHolder)
return callbackHolder
}
private get makeMainEffects() {
if (!this._makeMainEffects) {
this._makeMainEffects = this.getDependencies.makeMainEffects()
callCallback(callback: number, args: any[]): void {
if (this.callbacks) {
this.callbacks
.callCallback(callback, args)
.catch((error) =>
console.error(`callback ${callback} failed`, utils.asError(error)),
)
} else {
console.warn(
`callback ${callback} ignored because system is not initialized`,
)
}
return this._makeMainEffects
}
private dealWithInput(input: unknown): MaybePromise<SocketResponse> {
@@ -231,40 +263,49 @@ export class RpcListener {
.when(runType, async ({ id, params }) => {
const system = this.system
const procedure = jsonPath.unsafeCast(params.procedure)
const effects = this.getDependencies.makeProcedureEffects()(params.id)
const input = params.input
const timeout = params.timeout
const result = getResult(procedure, system, effects, timeout, input)
const { input, timeout, id: procedureId } = params
const result = this.getResult(
procedure,
system,
procedureId,
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)
const result = getResult(
const { input, timeout, id: procedureId } = params
const result = this.getResult(
procedure,
system,
effects,
params.input,
params.input,
procedureId,
timeout,
input,
)
return handleRpc(id, result)
})
.when(callbackType, async ({ params: { callback, args } }) => {
this.system.callCallback(callback, args)
this.callCallback(callback, args)
return null
})
.when(startType, async ({ id }) => {
const callbacks = this.callbackHolderFor("main")
const effects = makeEffects({
procedureId: null,
callbacks,
constRetry: () => {},
})
return handleRpc(
id,
this.system
.start(this.makeMainEffects())
.then((result) => ({ result })),
this.system.start(effects).then((result) => ({ result })),
)
})
.when(stopType, async ({ id }) => {
this.removeCallbackHolderFor("main")
return handleRpc(
id,
this.system.stop().then((result) => ({ result })),
@@ -284,7 +325,20 @@ export class RpcListener {
(async () => {
if (!this._system) {
const system = await this.getDependencies.system()
await system.containerInit()
this.callbacks = new CallbackHolder(
makeEffects({
procedureId: null,
constRetry: () => {},
}),
)
const callbacks = this.callbackHolderFor("containerInit")
await system.containerInit(
makeEffects({
procedureId: null,
callbacks,
constRetry: () => {},
}),
)
this._system = system
}
})().then((result) => ({ result })),
@@ -316,17 +370,20 @@ export class RpcListener {
})(),
)
})
.when(shape({ id: idType, method: string }), ({ id, method }) => ({
jsonrpc,
id,
error: {
code: -32601,
message: `Method not found`,
data: {
details: method,
.when(
shape({ id: idType, method: string }, ["id"]),
({ id, method }) => ({
jsonrpc,
id,
error: {
code: -32601,
message: `Method not found`,
data: {
details: method,
},
},
},
}))
}),
)
.defaultToLazy(() => {
console.warn(
@@ -345,98 +402,84 @@ 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,
)
}
private getResult(
procedure: typeof jsonPath._TYPE,
system: System,
procedureId: string,
timeout: number | undefined,
input: any,
) {
const ensureResultTypeShape = (
result: void | T.ActionInput | T.PropertiesReturn | T.ActionResult | null,
): { result: any } => {
if (isResult(result)) return result
return { result }
}
})().then(ensureResultTypeShape, (error) =>
matches(error)
.when(
object(
{
error: string,
code: number,
},
["code"],
{ code: 0 },
),
(error) => ({
const callbacks = this.callbackHolderFor(procedure)
const effects = makeEffects({
procedureId,
callbacks,
constRetry: () => {},
})
return (async () => {
switch (procedure) {
case "/backup/create":
return system.createBackup(effects, timeout || null)
case "/backup/restore":
return system.restoreBackup(effects, timeout || null)
case "/properties":
return system.properties(effects, timeout || null)
case "/packageInit":
return system.packageInit(effects, timeout || null)
case "/packageUninit":
return system.packageUninit(
effects,
string.optional().unsafeCast(input),
timeout || null,
)
default:
const procedures = unNestPath(procedure)
switch (true) {
case procedures[1] === "actions" && procedures[3] === "getInput":
return system.getActionInput(
effects,
procedures[2],
timeout || null,
)
case procedures[1] === "actions" && procedures[3] === "run":
return system.runAction(
effects,
procedures[2],
input.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: error.code,
message: error.error,
code: 0,
message: String(error),
},
}),
)
.defaultToLazy(() => ({
error: {
code: 0,
message: String(error),
},
})),
)
})),
)
}
}

View File

@@ -8,7 +8,7 @@ import {
CommandOptions,
ExecOptions,
ExecSpawnable,
} from "@start9labs/start-sdk/cjs/lib/util/SubContainer"
} from "@start9labs/start-sdk/package/lib/util/SubContainer"
export const exec = promisify(cp.exec)
export const execFile = promisify(cp.execFile)

View File

@@ -2,11 +2,10 @@ import { polyfillEffects } from "./polyfillEffects"
import { DockerProcedureContainer } from "./DockerProcedureContainer"
import { SystemForEmbassy } from "."
import { T, utils } from "@start9labs/start-sdk"
import { Daemon } from "@start9labs/start-sdk/cjs/lib/mainFn/Daemon"
import { Daemon } from "@start9labs/start-sdk/package/lib/mainFn/Daemon"
import { Effects } from "../../../Models/Effects"
import { off } from "node:process"
import { CommandController } from "@start9labs/start-sdk/cjs/lib/mainFn/CommandController"
import { asError } from "@start9labs/start-sdk/cjs/lib/util"
import { CommandController } from "@start9labs/start-sdk/package/lib/mainFn/CommandController"
const EMBASSY_HEALTH_INTERVAL = 15 * 1000
const EMBASSY_PROPERTIES_LOOP = 30 * 1000
@@ -137,7 +136,7 @@ export class MainLoop {
delete this.healthLoops
await main?.daemon
.stop()
.catch((e) => console.error(`Main loop error`, utils.asError(e)))
.catch((e: unknown) => console.error(`Main loop error`, utils.asError(e)))
this.effects.setMainStatus({ status: "stopped" })
if (healthLoops) healthLoops.forEach((x) => clearInterval(x.interval))
}
@@ -155,7 +154,7 @@ export class MainLoop {
result: "starting",
message: null,
})
.catch((e) => console.error(asError(e)))
.catch((e) => console.error(utils.asError(e)))
const interval = setInterval(async () => {
const actionProcedure = value
const timeChanged = Date.now() - start

View File

@@ -2,8 +2,8 @@ import { ExtendedVersion, types as T, utils } from "@start9labs/start-sdk"
import * as fs from "fs/promises"
import { polyfillEffects } from "./polyfillEffects"
import { Duration, duration, fromDuration } from "../../../Models/Duration"
import { System, Procedure } from "../../../Interfaces/System"
import { fromDuration } from "../../../Models/Duration"
import { System } from "../../../Interfaces/System"
import { matchManifest, Manifest } from "./matchManifest"
import * as childProcess from "node:child_process"
import { DockerProcedureContainer } from "./DockerProcedureContainer"
@@ -27,19 +27,12 @@ import {
Parser,
array,
} from "ts-matches"
import { JsonPath, unNestPath } from "../../../Models/JsonPath"
import { RpcResult, matchRpcResult } from "../../RpcListener"
import { CT } from "@start9labs/start-sdk"
import {
AddSslOptions,
BindOptions,
} from "@start9labs/start-sdk/cjs/lib/osBindings"
import { AddSslOptions } from "@start9labs/start-sdk/base/lib/osBindings"
import {
BindOptionsByProtocol,
Host,
MultiHost,
} from "@start9labs/start-sdk/cjs/lib/interfaces/Host"
import { ServiceInterfaceBuilder } from "@start9labs/start-sdk/cjs/lib/interfaces/ServiceInterfaceBuilder"
} from "@start9labs/start-sdk/base/lib/interfaces/Host"
import { ServiceInterfaceBuilder } from "@start9labs/start-sdk/base/lib/interfaces/ServiceInterfaceBuilder"
import { Effects } from "../../../Models/Effects"
import {
OldConfigSpec,
@@ -48,18 +41,16 @@ import {
transformNewConfigToOld,
transformOldConfigToNew,
} from "./transformConfigSpec"
import { MainEffects } from "@start9labs/start-sdk/cjs/lib/StartSdk"
import { StorePath } from "@start9labs/start-sdk/cjs/lib/store/PathBuilder"
import { partialDiff } from "@start9labs/start-sdk/base/lib/util"
type Optional<A> = A | undefined | null
function todo(): never {
throw new Error("Not implemented")
}
const execFile = promisify(childProcess.execFile)
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 EMBASSY_POINTER_PATH_PREFIX = "/embassyConfig" as utils.StorePath
const matchResult = object({
result: any,
@@ -248,50 +239,21 @@ export class SystemForEmbassy implements System {
readonly moduleCode: Partial<U.ExpectedExports>,
) {}
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(effects: Effects): Promise<void> {
for (let depId in this.manifest.dependencies) {
if (this.manifest.dependencies[depId].config) {
await this.dependenciesAutoconfig(effects, depId, null)
}
}
}
async containerInit(): Promise<void> {}
async exit(): Promise<void> {
if (this.currentRunning) await this.currentRunning.clean()
delete this.currentRunning
}
async start(effects: MainEffects): Promise<void> {
async start(effects: T.Effects): Promise<void> {
effects.constRetry = utils.once(() => effects.restart())
if (!!this.currentRunning) return
this.currentRunning = await MainLoop.of(this, effects)
@@ -308,13 +270,18 @@ export class SystemForEmbassy implements System {
}
}
async packageInit(
effects: Effects,
previousVersion: Optional<string>,
timeoutMs: number | null,
): Promise<void> {
if (previousVersion)
await this.migration(effects, previousVersion, timeoutMs)
async packageInit(effects: Effects, timeoutMs: number | null): Promise<void> {
const previousVersion = await effects.getDataVersion()
if (previousVersion) {
if (
(await this.migration(effects, previousVersion, timeoutMs)).configured
) {
await effects.action.clearRequests({ only: ["needs-config"] })
}
await effects.setDataVersion({
version: ExtendedVersion.parseEmver(this.manifest.version).toString(),
})
}
await effects.setMainStatus({ status: "stopped" })
await this.exportActions(effects)
await this.exportNetwork(effects)
@@ -400,10 +367,57 @@ export class SystemForEmbassy implements System {
)
}
}
async getActionInput(
effects: Effects,
actionId: string,
timeoutMs: number | null,
): Promise<T.ActionInput | null> {
if (actionId === "config") {
const config = await this.getConfig(effects, timeoutMs)
return { spec: config.spec, value: config.config }
} else {
const oldSpec = this.manifest.actions?.[actionId]?.["input-spec"]
if (!oldSpec) return null
return {
spec: transformConfigSpec(oldSpec as OldConfigSpec),
value: null,
}
}
}
async runAction(
effects: Effects,
actionId: string,
input: unknown,
timeoutMs: number | null,
): Promise<T.ActionResult | null> {
if (actionId === "config") {
await this.setConfig(effects, input, timeoutMs)
return null
} else {
return this.action(effects, actionId, input, timeoutMs)
}
}
async exportActions(effects: Effects) {
const manifest = this.manifest
if (!manifest.actions) return
for (const [actionId, action] of Object.entries(manifest.actions)) {
const actions = {
...manifest.actions,
}
if (manifest.config) {
actions.config = {
name: "Configure",
description: "Edit the configuration of this service",
"allowed-statuses": ["running", "stopped"],
"input-spec": {},
implementation: { type: "script", args: [] },
}
await effects.action.request({
packageId: this.manifest.id,
actionId: "config",
replayId: "needs-config",
description: "This service must be configured before it can be run",
})
}
for (const [actionId, action] of Object.entries(actions)) {
const hasRunning = !!action["allowed-statuses"].find(
(x) => x === "running",
)
@@ -412,21 +426,22 @@ export class SystemForEmbassy implements System {
)
// prettier-ignore
const allowedStatuses = hasRunning && hasStopped ? "any":
hasRunning ? "onlyRunning" :
"onlyStopped"
await effects.exportAction({
hasRunning ? "only-running" :
"only-stopped"
await effects.action.export({
id: actionId,
metadata: {
name: action.name,
description: action.description,
warning: action.warning || null,
input: action["input-spec"] as CT.InputSpec,
disabled: false,
visibility: "enabled",
allowedStatuses,
hasInput: !!action["input-spec"],
group: null,
},
})
}
await effects.action.clear({ except: Object.keys(actions) })
}
async packageUninit(
effects: Effects,
@@ -483,10 +498,7 @@ export class SystemForEmbassy implements System {
await moduleCode.restoreBackup?.(polyfillEffects(effects, this.manifest))
}
}
async getConfig(
effects: Effects,
timeoutMs: number | null,
): Promise<T.ConfigRes> {
async getConfig(effects: Effects, timeoutMs: number | null) {
return this.getConfigUncleaned(effects, timeoutMs).then(convertToNewConfig)
}
private async getConfigUncleaned(
@@ -614,7 +626,7 @@ export class SystemForEmbassy implements System {
effects: Effects,
fromVersion: string,
timeoutMs: number | null,
): Promise<T.MigrationRes> {
): Promise<{ configured: boolean }> {
const fromEmver = ExtendedVersion.parseEmver(fromVersion)
const currentEmver = ExtendedVersion.parseEmver(this.manifest.version)
if (!this.manifest.migrations) return { configured: true }
@@ -828,24 +840,44 @@ export class SystemForEmbassy implements System {
async dependenciesAutoconfig(
effects: Effects,
id: string,
input: unknown,
timeoutMs: number | null,
): Promise<void> {
const oldConfig = object({ remoteConfig: any }).unsafeCast(
input,
).remoteConfig
// TODO: docker
const oldConfig = (await effects.store.get({
packageId: id,
path: EMBASSY_POINTER_PATH_PREFIX,
callback: () => {
this.dependenciesAutoconfig(effects, id, timeoutMs)
},
})) as U.Config
const moduleCode = await this.moduleCode
const method = moduleCode.dependencies?.[id]?.autoConfigure
if (!method) return
return (await method(
const newConfig = (await method(
polyfillEffects(effects, this.manifest),
oldConfig,
JSON.parse(JSON.stringify(oldConfig)),
).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
const diff = partialDiff(oldConfig, newConfig)
if (diff) {
await effects.action.request({
actionId: "config",
packageId: id,
replayId: `${id}/config`,
description: `Configure this dependency for the needs of ${this.manifest.title}`,
input: {
kind: "partial",
value: diff.diff,
},
when: {
condition: "input-not-matches",
once: false,
},
})
}
}
}
@@ -1020,9 +1052,7 @@ function extractServiceInterfaceId(manifest: Manifest, specInterface: string) {
const serviceInterfaceId = `${specInterface}-${internalPort}`
return serviceInterfaceId
}
async function convertToNewConfig(
value: OldGetConfigRes,
): Promise<T.ConfigRes> {
async function convertToNewConfig(value: OldGetConfigRes) {
const valueSpec: OldConfigSpec = matchOldConfigSpec.unsafeCast(value.spec)
const spec = transformConfigSpec(valueSpec)
if (!value.config) return { spec, config: null }

View File

@@ -42,6 +42,7 @@ const matchAction = object(
export const matchManifest = object(
{
id: string,
title: string,
version: string,
main: matchDockerProcedure,
assets: object(

View File

@@ -105,12 +105,14 @@ export const polyfillEffects = (
args?: string[] | undefined
timeoutMillis?: number | undefined
}): Promise<oet.ResultType<string>> {
const commands: [string, ...string[]] = [command, ...(args || [])]
return startSdk
.runCommand(
effects,
{ id: manifest.main.image },
[command, ...(args || [])],
commands,
{},
commands.join(" "),
)
.then((x: any) => ({
stderr: x.stderr.toString(),
@@ -154,11 +156,17 @@ export const polyfillEffects = (
path: string
uid: string
}): Promise<null> {
const commands: [string, ...string[]] = [
"chown",
"--recursive",
input.uid,
`/drive/${input.path}`,
]
await startSdk
.runCommand(
effects,
{ id: manifest.main.image },
["chown", "--recursive", input.uid, `/drive/${input.path}`],
commands,
{
mounts: [
{
@@ -172,6 +180,7 @@ export const polyfillEffects = (
},
],
},
commands.join(" "),
)
.then((x: any) => ({
stderr: x.stderr.toString(),
@@ -189,11 +198,17 @@ export const polyfillEffects = (
path: string
mode: string
}): Promise<null> {
const commands: [string, ...string[]] = [
"chmod",
"--recursive",
input.mode,
`/drive/${input.path}`,
]
await startSdk
.runCommand(
effects,
{ id: manifest.main.image },
["chmod", "--recursive", input.mode, `/drive/${input.path}`],
commands,
{
mounts: [
{
@@ -207,6 +222,7 @@ export const polyfillEffects = (
},
],
},
commands.join(" "),
)
.then((x: any) => ({
stderr: x.stderr.toString(),

View File

@@ -1,4 +1,4 @@
import { CT } from "@start9labs/start-sdk"
import { IST } from "@start9labs/start-sdk"
import {
dictionary,
object,
@@ -15,9 +15,9 @@ import {
literal,
} from "ts-matches"
export function transformConfigSpec(oldSpec: OldConfigSpec): CT.InputSpec {
export function transformConfigSpec(oldSpec: OldConfigSpec): IST.InputSpec {
return Object.entries(oldSpec).reduce((inputSpec, [key, oldVal]) => {
let newVal: CT.ValueSpec
let newVal: IST.ValueSpec
if (oldVal.type === "boolean") {
newVal = {
@@ -124,7 +124,7 @@ export function transformConfigSpec(oldSpec: OldConfigSpec): CT.InputSpec {
spec: transformConfigSpec(matchOldConfigSpec.unsafeCast(spec)),
},
}),
{} as Record<string, { name: string; spec: CT.InputSpec }>,
{} as Record<string, { name: string; spec: IST.InputSpec }>,
),
disabled: false,
required: true,
@@ -141,7 +141,7 @@ export function transformConfigSpec(oldSpec: OldConfigSpec): CT.InputSpec {
...inputSpec,
[key]: newVal,
}
}, {} as CT.InputSpec)
}, {} as IST.InputSpec)
}
export function transformOldConfigToNew(
@@ -233,10 +233,10 @@ export function transformNewConfigToOld(
function getListSpec(
oldVal: OldValueSpecList,
): CT.ValueSpecMultiselect | CT.ValueSpecList {
): IST.ValueSpecMultiselect | IST.ValueSpecList {
const range = Range.from(oldVal.range)
let partial: Omit<CT.ValueSpecList, "type" | "spec" | "default"> = {
let partial: Omit<IST.ValueSpecList, "type" | "spec" | "default"> = {
name: oldVal.name,
description: oldVal.description || null,
warning: oldVal.warning || null,

View File

@@ -6,16 +6,13 @@ import { RpcResult, matchRpcResult } from "../RpcListener"
import { duration } from "../../Models/Duration"
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"
type RunningMain = {
effects: MainEffects
stop: () => Promise<void>
callbacks: CallbackHolder
}
export class SystemForStartOs implements System {
@@ -25,23 +22,24 @@ export class SystemForStartOs implements System {
return new SystemForStartOs(require(STARTOS_JS_LOCATION))
}
constructor(readonly abi: T.ABI) {}
containerInit(): Promise<void> {
throw new Error("Method not implemented.")
constructor(readonly abi: T.ABI) {
this
}
async containerInit(effects: Effects): Promise<void> {
return void (await this.abi.containerInit({ effects }))
}
async packageInit(
effects: Effects,
previousVersion: Optional<string> = null,
timeoutMs: number | null = null,
): Promise<void> {
return void (await this.abi.init({ effects }))
return void (await this.abi.packageInit({ effects }))
}
async packageUninit(
effects: Effects,
nextVersion: Optional<string> = null,
timeoutMs: number | null = null,
): Promise<void> {
return void (await this.abi.uninit({ effects, nextVersion }))
return void (await this.abi.packageUninit({ effects, nextVersion }))
}
async createBackup(
effects: T.Effects,
@@ -49,8 +47,6 @@ export class SystemForStartOs implements System {
): Promise<void> {
return void (await this.abi.createBackup({
effects,
pathMaker: ((options) =>
new Volume(options.volume, options.path).path) as T.PathMaker,
}))
}
async restoreBackup(
@@ -59,80 +55,38 @@ export class SystemForStartOs implements System {
): 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(
getActionInput(
effects: Effects,
id: string,
formData: unknown,
timeoutMs: number | null,
): Promise<T.ActionResult> {
const action = (await this.abi.actions({ effects }))[id]
): Promise<T.ActionInput | null> {
const action = this.abi.actions.get(id)
if (!action) throw new Error(`Action ${id} not found`)
return action.run({ effects })
return action.getInput({ effects })
}
dependenciesCheck(
runAction(
effects: Effects,
id: string,
oldConfig: unknown,
input: 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 })
): Promise<T.ActionResult | null> {
const action = this.abi.actions.get(id)
if (!action) throw new Error(`Action ${id} not found`)
return action.run({ effects, input })
}
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> {}
async exit(): Promise<void> {}
async start(effects: MainEffects): Promise<void> {
async start(effects: Effects): Promise<void> {
effects.constRetry = utils.once(() => effects.restart())
if (this.runningMain) await this.stop()
let mainOnTerm: () => Promise<void> | undefined
const started = async (onTerm: () => Promise<void>) => {
@@ -141,36 +95,21 @@ export class SystemForStartOs implements System {
}
const daemons = await (
await this.abi.main({
effects: effects as MainEffects,
effects,
started,
})
).build()
this.runningMain = {
effects,
stop: async () => {
if (mainOnTerm) await mainOnTerm()
await daemons.term()
},
callbacks: new CallbackHolder(),
}
}
callCallback(callback: number, args: any[]): void {
if (this.runningMain) {
this.runningMain.callbacks
.callCallback(callback, args)
.catch((error) =>
console.error(`callback ${callback} failed`, utils.asError(error)),
)
} else {
console.warn(`callback ${callback} ignored because system is not running`)
}
}
async stop(): Promise<void> {
if (this.runningMain) {
await this.runningMain.stop()
await this.runningMain.effects.clearCallbacks()
this.runningMain = undefined
}
}

View File

@@ -1,7 +1,4 @@
import { GetDependency } from "./GetDependency"
import { System } from "./System"
import { MakeMainEffects, MakeProcedureEffects } from "./MakeEffects"
export type AllGetDependencies = GetDependency<"system", Promise<System>> &
GetDependency<"makeProcedureEffects", MakeProcedureEffects> &
GetDependency<"makeMainEffects", MakeMainEffects>
export type AllGetDependencies = GetDependency<"system", Promise<System>>

View File

@@ -1,4 +0,0 @@
import { Effects } from "../Models/Effects"
import { MainEffects } from "@start9labs/start-sdk/cjs/lib/StartSdk"
export type MakeProcedureEffects = (procedureId: string) => Effects
export type MakeMainEffects = () => MainEffects

View File

@@ -1,39 +1,27 @@
import { types as T } from "@start9labs/start-sdk"
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"
| "/uninit"
| "/config/set"
| "/config/get"
| "/packageInit"
| "/packageUninit"
| "/backup/create"
| "/backup/restore"
| "/actions/metadata"
| "/properties"
| `/actions/${string}/get`
| `/actions/${string}/getInput`
| `/actions/${string}/run`
| `/dependencies/${string}/query`
| `/dependencies/${string}/update`
export type ExecuteResult =
| { ok: unknown }
| { err: { code: number; message: string } }
export type System = {
containerInit(): Promise<void>
containerInit(effects: T.Effects): Promise<void>
start(effects: MainEffects): Promise<void>
callCallback(callback: number, args: any[]): void
start(effects: T.Effects): Promise<void>
stop(): Promise<void>
packageInit(
effects: Effects,
previousVersion: Optional<string>,
timeoutMs: number | null,
): Promise<void>
packageInit(effects: Effects, timeoutMs: number | null): Promise<void>
packageUninit(
effects: Effects,
nextVersion: Optional<string>,
@@ -42,41 +30,21 @@ export type System = {
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(
runAction(
effects: Effects,
actionId: string,
formData: unknown,
input: unknown,
timeoutMs: number | null,
): Promise<T.ActionResult>
dependenciesCheck(
): Promise<T.ActionResult | null>
getActionInput(
effects: Effects,
id: string,
oldConfig: unknown,
actionId: string,
timeoutMs: number | null,
): Promise<any>
dependenciesAutoconfig(
effects: Effects,
id: string,
oldConfig: unknown,
timeoutMs: number | null,
): Promise<void>
actionsMetadata(effects: T.Effects): Promise<T.ActionMetadata[]>
): Promise<T.ActionInput | null>
exit(): Promise<void>
}

View File

@@ -1,9 +1,22 @@
import { T } from "@start9labs/start-sdk"
const CallbackIdCell = { inc: 0 }
const callbackRegistry = new FinalizationRegistry(
async (options: { cbs: Map<number, Function>; effects: T.Effects }) => {
await options.effects.clearCallbacks({
only: Array.from(options.cbs.keys()),
})
},
)
export class CallbackHolder {
constructor() {}
private inc = 0
constructor(private effects?: T.Effects) {}
private callbacks = new Map<number, Function>()
private children: WeakRef<CallbackHolder>[] = []
private newId() {
return this.inc++
return CallbackIdCell.inc++
}
addCallback(callback?: Function) {
if (!callback) {
@@ -11,12 +24,38 @@ export class CallbackHolder {
}
const id = this.newId()
this.callbacks.set(id, callback)
if (this.effects)
callbackRegistry.register(this, {
cbs: this.callbacks,
effects: this.effects,
})
return id
}
child(): CallbackHolder {
const child = new CallbackHolder()
this.children.push(new WeakRef(child))
return child
}
removeChild(child: CallbackHolder) {
this.children = this.children.filter((c) => {
const ref = c.deref()
return ref && ref !== child
})
}
private getCallback(index: number): Function | undefined {
let callback = this.callbacks.get(index)
if (callback) this.callbacks.delete(index)
else {
for (let i = 0; i < this.children.length; i++) {
callback = this.children[i].deref()?.getCallback(index)
if (callback) return callback
}
}
return callback
}
callCallback(index: number, args: any[]): Promise<unknown> {
const callback = this.callbacks.get(index)
if (!callback) throw new Error(`Callback ${index} does not exist`)
this.callbacks.delete(index)
const callback = this.getCallback(index)
if (!callback) return Promise.resolve()
return Promise.resolve().then(() => callback(...args))
}
}

View File

@@ -1,9 +1,7 @@
import { literals, some, string } from "ts-matches"
type NestedPath<A extends string, B extends string> = `/${A}/${string}/${B}`
type NestedPaths =
| NestedPath<"actions", "run" | "get">
| NestedPath<"dependencies", "query" | "update">
type NestedPaths = NestedPath<"actions", "run" | "getInput">
// prettier-ignore
type UnNestPaths<A> =
A extends `${infer A}/${infer B}` ? [...UnNestPaths<A>, ... UnNestPaths<B>] :
@@ -15,21 +13,14 @@ export function unNestPath<A extends string>(a: A): UnNestPaths<A> {
function isNestedPath(path: string): path is NestedPaths {
const paths = path.split("/")
if (paths.length !== 4) return false
if (paths[1] === "actions" && (paths[3] === "run" || paths[3] === "get"))
return true
if (
paths[1] === "dependencies" &&
(paths[3] === "query" || paths[3] === "update")
)
if (paths[1] === "actions" && (paths[3] === "run" || paths[3] === "getInput"))
return true
return false
}
export const jsonPath = some(
literals(
"/init",
"/uninit",
"/config/set",
"/config/get",
"/packageInit",
"/packageUninit",
"/backup/create",
"/backup/restore",
"/actions/metadata",

View File

@@ -1,13 +1,10 @@
import { RpcListener } from "./Adapters/RpcListener"
import { SystemForEmbassy } from "./Adapters/Systems/SystemForEmbassy"
import { makeMainEffects, makeProcedureEffects } from "./Adapters/EffectCreator"
import { AllGetDependencies } from "./Interfaces/AllGetDependencies"
import { getSystem } from "./Adapters/Systems"
const getDependencies: AllGetDependencies = {
system: getSystem,
makeProcedureEffects: () => makeProcedureEffects,
makeMainEffects: () => makeMainEffects,
}
new RpcListener(getDependencies)