mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 04:01:58 +00:00
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:
5
sdk/base/.gitignore
vendored
Normal file
5
sdk/base/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
.vscode
|
||||
dist/
|
||||
node_modules/
|
||||
lib/coverage
|
||||
lib/test/output.ts
|
||||
21
sdk/base/LICENSE
Normal file
21
sdk/base/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Start9 Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
1
sdk/base/README.md
Normal file
1
sdk/base/README.md
Normal file
@@ -0,0 +1 @@
|
||||
# See ../package/README.md
|
||||
8
sdk/base/jest.config.js
Normal file
8
sdk/base/jest.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||
module.exports = {
|
||||
preset: "ts-jest",
|
||||
automock: false,
|
||||
testEnvironment: "node",
|
||||
rootDir: "./lib/",
|
||||
modulePathIgnorePatterns: ["./dist/"],
|
||||
}
|
||||
191
sdk/base/lib/Effects.ts
Normal file
191
sdk/base/lib/Effects.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
import {
|
||||
ActionId,
|
||||
ActionInput,
|
||||
ActionMetadata,
|
||||
SetMainStatus,
|
||||
DependencyRequirement,
|
||||
CheckDependenciesResult,
|
||||
SetHealth,
|
||||
BindParams,
|
||||
HostId,
|
||||
LanInfo,
|
||||
Host,
|
||||
ExportServiceInterfaceParams,
|
||||
ServiceInterface,
|
||||
ActionRequest,
|
||||
RequestActionParams,
|
||||
} from "./osBindings"
|
||||
import { StorePath } from "./util/PathBuilder"
|
||||
import {
|
||||
PackageId,
|
||||
Dependencies,
|
||||
ServiceInterfaceId,
|
||||
SmtpValue,
|
||||
ActionResult,
|
||||
} from "./types"
|
||||
import { UrlString } from "./util/getServiceInterface"
|
||||
|
||||
/** Used to reach out from the pure js runtime */
|
||||
|
||||
export type Effects = {
|
||||
constRetry: () => void
|
||||
clearCallbacks: (
|
||||
options: { only: number[] } | { except: number[] },
|
||||
) => Promise<void>
|
||||
|
||||
// action
|
||||
action: {
|
||||
/** Define an action that can be invoked by a user or service */
|
||||
export(options: { id: ActionId; metadata: ActionMetadata }): Promise<void>
|
||||
/** Remove all exported actions */
|
||||
clear(options: { except: ActionId[] }): Promise<void>
|
||||
getInput(options: {
|
||||
packageId?: PackageId
|
||||
actionId: ActionId
|
||||
}): Promise<ActionInput | null>
|
||||
run<Input extends Record<string, unknown>>(options: {
|
||||
packageId?: PackageId
|
||||
actionId: ActionId
|
||||
input?: Input
|
||||
}): Promise<ActionResult | null>
|
||||
request<Input extends Record<string, unknown>>(
|
||||
options: RequestActionParams,
|
||||
): Promise<void>
|
||||
clearRequests(
|
||||
options: { only: ActionId[] } | { except: ActionId[] },
|
||||
): Promise<void>
|
||||
}
|
||||
|
||||
// control
|
||||
/** restart this service's main function */
|
||||
restart(): Promise<void>
|
||||
/** stop this service's main function */
|
||||
shutdown(): Promise<void>
|
||||
/** indicate to the host os what runstate the service is in */
|
||||
setMainStatus(options: SetMainStatus): Promise<void>
|
||||
|
||||
// dependency
|
||||
/** Set the dependencies of what the service needs, usually run during the inputSpec action as a best practice */
|
||||
setDependencies(options: { dependencies: Dependencies }): Promise<void>
|
||||
/** Get the list of the dependencies, both the dynamic set by the effect of setDependencies and the end result any required in the manifest */
|
||||
getDependencies(): Promise<DependencyRequirement[]>
|
||||
/** Test whether current dependency requirements are satisfied */
|
||||
checkDependencies(options: {
|
||||
packageIds?: PackageId[]
|
||||
}): Promise<CheckDependenciesResult[]>
|
||||
/** mount a volume of a dependency */
|
||||
mount(options: {
|
||||
location: string
|
||||
target: {
|
||||
packageId: string
|
||||
volumeId: string
|
||||
subpath: string | null
|
||||
readonly: boolean
|
||||
}
|
||||
}): Promise<string>
|
||||
/** Returns a list of the ids of all installed packages */
|
||||
getInstalledPackages(): Promise<string[]>
|
||||
/** grants access to certain paths in the store to dependents */
|
||||
exposeForDependents(options: { paths: string[] }): Promise<void>
|
||||
|
||||
// health
|
||||
/** sets the result of a health check */
|
||||
setHealth(o: SetHealth): Promise<void>
|
||||
|
||||
// subcontainer
|
||||
subcontainer: {
|
||||
/** A low level api used by SubContainer */
|
||||
createFs(options: {
|
||||
imageId: string
|
||||
name: string | null
|
||||
}): Promise<[string, string]>
|
||||
/** A low level api used by SubContainer */
|
||||
destroyFs(options: { guid: string }): Promise<void>
|
||||
}
|
||||
|
||||
// net
|
||||
// bind
|
||||
/** Creates a host connected to the specified port with the provided options */
|
||||
bind(options: BindParams): Promise<void>
|
||||
/** Get the port address for a service */
|
||||
getServicePortForward(options: {
|
||||
packageId?: PackageId
|
||||
hostId: HostId
|
||||
internalPort: number
|
||||
}): Promise<LanInfo>
|
||||
/** Removes all network bindings, called in the setupInputSpec */
|
||||
clearBindings(options: {
|
||||
except: { id: HostId; internalPort: number }[]
|
||||
}): Promise<void>
|
||||
// host
|
||||
/** Returns information about the specified host, if it exists */
|
||||
getHostInfo(options: {
|
||||
packageId?: PackageId
|
||||
hostId: HostId
|
||||
callback?: () => void
|
||||
}): Promise<Host | null>
|
||||
/** Returns the primary url that a user has selected for a host, if it exists */
|
||||
getPrimaryUrl(options: {
|
||||
packageId?: PackageId
|
||||
hostId: HostId
|
||||
callback?: () => void
|
||||
}): Promise<UrlString | null>
|
||||
/** Returns the IP address of the container */
|
||||
getContainerIp(): Promise<string>
|
||||
// interface
|
||||
/** Creates an interface bound to a specific host and port to show to the user */
|
||||
exportServiceInterface(options: ExportServiceInterfaceParams): Promise<void>
|
||||
/** Returns an exported service interface */
|
||||
getServiceInterface(options: {
|
||||
packageId?: PackageId
|
||||
serviceInterfaceId: ServiceInterfaceId
|
||||
callback?: () => void
|
||||
}): Promise<ServiceInterface | null>
|
||||
/** Returns all exported service interfaces for a package */
|
||||
listServiceInterfaces(options: {
|
||||
packageId?: PackageId
|
||||
callback?: () => void
|
||||
}): Promise<Record<ServiceInterfaceId, ServiceInterface>>
|
||||
/** Removes all service interfaces */
|
||||
clearServiceInterfaces(options: {
|
||||
except: ServiceInterfaceId[]
|
||||
}): Promise<void>
|
||||
// ssl
|
||||
/** Returns a PEM encoded fullchain for the hostnames specified */
|
||||
getSslCertificate: (options: {
|
||||
hostnames: string[]
|
||||
algorithm?: "ecdsa" | "ed25519"
|
||||
callback?: () => void
|
||||
}) => Promise<[string, string, string]>
|
||||
/** Returns a PEM encoded private key corresponding to the certificate for the hostnames specified */
|
||||
getSslKey: (options: {
|
||||
hostnames: string[]
|
||||
algorithm?: "ecdsa" | "ed25519"
|
||||
}) => Promise<string>
|
||||
|
||||
// store
|
||||
store: {
|
||||
/** Get a value in a json like data, can be observed and subscribed */
|
||||
get<Store = never, ExtractStore = unknown>(options: {
|
||||
/** If there is no packageId it is assumed the current package */
|
||||
packageId?: string
|
||||
/** The path defaults to root level, using the [JsonPath](https://jsonpath.com/) */
|
||||
path: StorePath
|
||||
callback?: () => void
|
||||
}): Promise<ExtractStore>
|
||||
/** Used to store values that can be accessed and subscribed to */
|
||||
set<Store = never, ExtractStore = unknown>(options: {
|
||||
/** Sets the value for the wrapper at the path, it will override, using the [JsonPath](https://jsonpath.com/) */
|
||||
path: StorePath
|
||||
value: ExtractStore
|
||||
}): Promise<void>
|
||||
}
|
||||
/** sets the version that this service's data has been migrated to */
|
||||
setDataVersion(options: { version: string }): Promise<void>
|
||||
/** returns the version that this service's data has been migrated to */
|
||||
getDataVersion(): Promise<string | null>
|
||||
|
||||
// system
|
||||
/** Returns globally configured SMTP settings, if they exist */
|
||||
getSystemSmtp(options: { callback?: () => void }): Promise<SmtpValue | null>
|
||||
}
|
||||
65
sdk/base/lib/actions/index.ts
Normal file
65
sdk/base/lib/actions/index.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import * as T from "../types"
|
||||
import * as IST from "../actions/input/inputSpecTypes"
|
||||
|
||||
export type RunActionInput<Input> =
|
||||
| Input
|
||||
| ((prev?: { spec: IST.InputSpec; value: Input | null }) => Input)
|
||||
|
||||
export const runAction = async <
|
||||
Input extends Record<string, unknown>,
|
||||
>(options: {
|
||||
effects: T.Effects
|
||||
// packageId?: T.PackageId
|
||||
actionId: T.ActionId
|
||||
input?: RunActionInput<Input>
|
||||
}) => {
|
||||
if (options.input) {
|
||||
if (options.input instanceof Function) {
|
||||
const prev = await options.effects.action.getInput({
|
||||
// packageId: options.packageId,
|
||||
actionId: options.actionId,
|
||||
})
|
||||
const input = options.input(
|
||||
prev
|
||||
? { spec: prev.spec as IST.InputSpec, value: prev.value as Input }
|
||||
: undefined,
|
||||
)
|
||||
return options.effects.action.run({
|
||||
// packageId: options.packageId,
|
||||
actionId: options.actionId,
|
||||
input,
|
||||
})
|
||||
} else {
|
||||
return options.effects.action.run({
|
||||
// packageId: options.packageId,
|
||||
actionId: options.actionId,
|
||||
input: options.input,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
return options.effects.action.run({
|
||||
// packageId: options.packageId,
|
||||
actionId: options.actionId,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
export type ActionRequest<T extends Omit<T.ActionRequest, "packageId">> =
|
||||
T extends { when: { condition: "input-not-matches" } }
|
||||
? (T extends { input: T.ActionRequestInput } ? T : "input is required for condition 'input-not-matches'")
|
||||
: T
|
||||
|
||||
export const requestAction = <
|
||||
T extends Omit<T.ActionRequest, "packageId">,
|
||||
>(options: {
|
||||
effects: T.Effects
|
||||
request: ActionRequest<T> & { replayId?: string; packageId: T.PackageId }
|
||||
}) => {
|
||||
const request = options.request
|
||||
const req = {
|
||||
...request,
|
||||
replayId: request.replayId || `${request.packageId}:${request.actionId}`,
|
||||
}
|
||||
return options.effects.action.request(req)
|
||||
}
|
||||
6
sdk/base/lib/actions/input/builder/index.ts
Normal file
6
sdk/base/lib/actions/input/builder/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { InputSpec } from "./inputSpec"
|
||||
import { List } from "./list"
|
||||
import { Value } from "./value"
|
||||
import { Variants } from "./variants"
|
||||
|
||||
export { InputSpec as InputSpec, List, Value, Variants }
|
||||
137
sdk/base/lib/actions/input/builder/inputSpec.ts
Normal file
137
sdk/base/lib/actions/input/builder/inputSpec.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import { ValueSpec } from "../inputSpecTypes"
|
||||
import { Value } from "./value"
|
||||
import { _ } from "../../../util"
|
||||
import { Effects } from "../../../Effects"
|
||||
import { Parser, object } from "ts-matches"
|
||||
|
||||
export type LazyBuildOptions<Store> = {
|
||||
effects: Effects
|
||||
}
|
||||
export type LazyBuild<Store, ExpectedOut> = (
|
||||
options: LazyBuildOptions<Store>,
|
||||
) => Promise<ExpectedOut> | ExpectedOut
|
||||
|
||||
// prettier-ignore
|
||||
export type ExtractInputSpecType<A extends Record<string, any> | InputSpec<Record<string, any>, any> | InputSpec<Record<string, any>, never>> =
|
||||
A extends InputSpec<infer B, any> | InputSpec<infer B, never> ? B :
|
||||
A
|
||||
|
||||
export type InputSpecOf<A extends Record<string, any>, Store = never> = {
|
||||
[K in keyof A]: Value<A[K], Store>
|
||||
}
|
||||
|
||||
export type MaybeLazyValues<A> = LazyBuild<any, A> | A
|
||||
/**
|
||||
* InputSpecs are the specs that are used by the os input specification form for this service.
|
||||
* Here is an example of a simple input specification
|
||||
```ts
|
||||
const smallInputSpec = InputSpec.of({
|
||||
test: Value.boolean({
|
||||
name: "Test",
|
||||
description: "This is the description for the test",
|
||||
warning: null,
|
||||
default: false,
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
The idea of an inputSpec is that now the form is going to ask for
|
||||
Test: [ ] and the value is going to be checked as a boolean.
|
||||
There are more complex values like selects, lists, and objects. See {@link Value}
|
||||
|
||||
Also, there is the ability to get a validator/parser from this inputSpec spec.
|
||||
```ts
|
||||
const matchSmallInputSpec = smallInputSpec.validator();
|
||||
type SmallInputSpec = typeof matchSmallInputSpec._TYPE;
|
||||
```
|
||||
|
||||
Here is an example of a more complex input specification which came from an input specification for a service
|
||||
that works with bitcoin, like c-lightning.
|
||||
```ts
|
||||
|
||||
export const hostname = Value.string({
|
||||
name: "Hostname",
|
||||
default: null,
|
||||
description: "Domain or IP address of bitcoin peer",
|
||||
warning: null,
|
||||
required: true,
|
||||
masked: false,
|
||||
placeholder: null,
|
||||
pattern:
|
||||
"(^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$)|((^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$)|(^[a-z2-7]{16}\\.onion$)|(^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$))",
|
||||
patternDescription:
|
||||
"Must be either a domain name, or an IPv4 or IPv6 address. Do not include protocol scheme (eg 'http://') or port.",
|
||||
});
|
||||
export const port = Value.number({
|
||||
name: "Port",
|
||||
default: null,
|
||||
description: "Port that peer is listening on for inbound p2p connections",
|
||||
warning: null,
|
||||
required: false,
|
||||
range: "[0,65535]",
|
||||
integral: true,
|
||||
units: null,
|
||||
placeholder: null,
|
||||
});
|
||||
export const addNodesSpec = InputSpec.of({ hostname: hostname, port: port });
|
||||
|
||||
```
|
||||
*/
|
||||
export class InputSpec<Type extends Record<string, any>, Store = never> {
|
||||
private constructor(
|
||||
private readonly spec: {
|
||||
[K in keyof Type]: Value<Type[K], Store> | Value<Type[K], never>
|
||||
},
|
||||
public validator: Parser<unknown, Type>,
|
||||
) {}
|
||||
async build(options: LazyBuildOptions<Store>) {
|
||||
const answer = {} as {
|
||||
[K in keyof Type]: ValueSpec
|
||||
}
|
||||
for (const k in this.spec) {
|
||||
answer[k] = await this.spec[k].build(options as any)
|
||||
}
|
||||
return answer
|
||||
}
|
||||
|
||||
static of<
|
||||
Spec extends Record<string, Value<any, Store> | Value<any, never>>,
|
||||
Store = never,
|
||||
>(spec: Spec) {
|
||||
const validatorObj = {} as {
|
||||
[K in keyof Spec]: Parser<unknown, any>
|
||||
}
|
||||
for (const key in spec) {
|
||||
validatorObj[key] = spec[key].validator
|
||||
}
|
||||
const validator = object(validatorObj)
|
||||
return new InputSpec<
|
||||
{
|
||||
[K in keyof Spec]: Spec[K] extends
|
||||
| Value<infer T, Store>
|
||||
| Value<infer T, never>
|
||||
? T
|
||||
: never
|
||||
},
|
||||
Store
|
||||
>(spec, validator as any)
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this during the times that the input needs a more specific type.
|
||||
* Used in types that the value/ variant/ list/ inputSpec is constructed somewhere else.
|
||||
```ts
|
||||
const a = InputSpec.text({
|
||||
name: "a",
|
||||
required: false,
|
||||
})
|
||||
|
||||
return InputSpec.of<Store>()({
|
||||
myValue: a.withStore(),
|
||||
})
|
||||
```
|
||||
*/
|
||||
withStore<NewStore extends Store extends never ? any : Store>() {
|
||||
return this as any as InputSpec<Type, NewStore>
|
||||
}
|
||||
}
|
||||
198
sdk/base/lib/actions/input/builder/list.ts
Normal file
198
sdk/base/lib/actions/input/builder/list.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
import { InputSpec, LazyBuild } from "./inputSpec"
|
||||
import {
|
||||
ListValueSpecText,
|
||||
Pattern,
|
||||
RandomString,
|
||||
UniqueBy,
|
||||
ValueSpecList,
|
||||
ValueSpecListOf,
|
||||
} from "../inputSpecTypes"
|
||||
import { Parser, arrayOf, string } from "ts-matches"
|
||||
|
||||
export class List<Type, Store> {
|
||||
private constructor(
|
||||
public build: LazyBuild<Store, ValueSpecList>,
|
||||
public validator: Parser<unknown, Type>,
|
||||
) {}
|
||||
|
||||
static text(
|
||||
a: {
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default?: string[]
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
},
|
||||
aSpec: {
|
||||
/**
|
||||
* @description Mask (aka camouflage) text input with dots: ● ● ●
|
||||
* @default false
|
||||
*/
|
||||
masked?: boolean
|
||||
placeholder?: string | null
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
/**
|
||||
* @description A list of regular expressions to which the text must conform to pass validation. A human readable description is provided in case the validation fails.
|
||||
* @default []
|
||||
* @example
|
||||
* ```
|
||||
[
|
||||
{
|
||||
regex: "[a-z]",
|
||||
description: "May only contain lower case letters from the English alphabet."
|
||||
}
|
||||
]
|
||||
* ```
|
||||
*/
|
||||
patterns?: Pattern[]
|
||||
/**
|
||||
* @description Informs the browser how to behave and which keyboard to display on mobile
|
||||
* @default "text"
|
||||
*/
|
||||
inputmode?: ListValueSpecText["inputmode"]
|
||||
/**
|
||||
* @description Displays a button that will generate a random string according to the provided charset and len attributes.
|
||||
*/
|
||||
generate?: null | RandomString
|
||||
},
|
||||
) {
|
||||
return new List<string[], never>(() => {
|
||||
const spec = {
|
||||
type: "text" as const,
|
||||
placeholder: null,
|
||||
minLength: null,
|
||||
maxLength: null,
|
||||
masked: false,
|
||||
inputmode: "text" as const,
|
||||
generate: null,
|
||||
patterns: aSpec.patterns || [],
|
||||
...aSpec,
|
||||
}
|
||||
const built: ValueSpecListOf<"text"> = {
|
||||
description: null,
|
||||
warning: null,
|
||||
default: [],
|
||||
type: "list" as const,
|
||||
minLength: null,
|
||||
maxLength: null,
|
||||
disabled: false,
|
||||
...a,
|
||||
spec,
|
||||
}
|
||||
return built
|
||||
}, arrayOf(string))
|
||||
}
|
||||
|
||||
static dynamicText<Store = never>(
|
||||
getA: LazyBuild<
|
||||
Store,
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default?: string[]
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
disabled?: false | string
|
||||
generate?: null | RandomString
|
||||
spec: {
|
||||
masked?: boolean
|
||||
placeholder?: string | null
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
patterns?: Pattern[]
|
||||
inputmode?: ListValueSpecText["inputmode"]
|
||||
}
|
||||
}
|
||||
>,
|
||||
) {
|
||||
return new List<string[], Store>(async (options) => {
|
||||
const { spec: aSpec, ...a } = await getA(options)
|
||||
const spec = {
|
||||
type: "text" as const,
|
||||
placeholder: null,
|
||||
minLength: null,
|
||||
maxLength: null,
|
||||
masked: false,
|
||||
inputmode: "text" as const,
|
||||
generate: null,
|
||||
patterns: aSpec.patterns || [],
|
||||
...aSpec,
|
||||
}
|
||||
const built: ValueSpecListOf<"text"> = {
|
||||
description: null,
|
||||
warning: null,
|
||||
default: [],
|
||||
type: "list" as const,
|
||||
minLength: null,
|
||||
maxLength: null,
|
||||
disabled: false,
|
||||
...a,
|
||||
spec,
|
||||
}
|
||||
return built
|
||||
}, arrayOf(string))
|
||||
}
|
||||
|
||||
static obj<Type extends Record<string, any>, Store>(
|
||||
a: {
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default?: []
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
},
|
||||
aSpec: {
|
||||
spec: InputSpec<Type, Store>
|
||||
displayAs?: null | string
|
||||
uniqueBy?: null | UniqueBy
|
||||
},
|
||||
) {
|
||||
return new List<Type[], Store>(async (options) => {
|
||||
const { spec: previousSpecSpec, ...restSpec } = aSpec
|
||||
const specSpec = await previousSpecSpec.build(options)
|
||||
const spec = {
|
||||
type: "object" as const,
|
||||
displayAs: null,
|
||||
uniqueBy: null,
|
||||
...restSpec,
|
||||
spec: specSpec,
|
||||
}
|
||||
const value = {
|
||||
spec,
|
||||
default: [],
|
||||
...a,
|
||||
}
|
||||
return {
|
||||
description: null,
|
||||
warning: null,
|
||||
minLength: null,
|
||||
maxLength: null,
|
||||
type: "list" as const,
|
||||
disabled: false,
|
||||
...value,
|
||||
}
|
||||
}, arrayOf(aSpec.spec.validator))
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this during the times that the input needs a more specific type.
|
||||
* Used in types that the value/ variant/ list/ inputSpec is constructed somewhere else.
|
||||
```ts
|
||||
const a = InputSpec.text({
|
||||
name: "a",
|
||||
required: false,
|
||||
})
|
||||
|
||||
return InputSpec.of<Store>()({
|
||||
myValue: a.withStore(),
|
||||
})
|
||||
```
|
||||
*/
|
||||
withStore<NewStore extends Store extends never ? any : Store>() {
|
||||
return this as any as List<Type, NewStore>
|
||||
}
|
||||
}
|
||||
858
sdk/base/lib/actions/input/builder/value.ts
Normal file
858
sdk/base/lib/actions/input/builder/value.ts
Normal file
@@ -0,0 +1,858 @@
|
||||
import { InputSpec, LazyBuild } from "./inputSpec"
|
||||
import { List } from "./list"
|
||||
import { Variants } from "./variants"
|
||||
import {
|
||||
FilePath,
|
||||
Pattern,
|
||||
RandomString,
|
||||
ValueSpec,
|
||||
ValueSpecDatetime,
|
||||
ValueSpecHidden,
|
||||
ValueSpecText,
|
||||
ValueSpecTextarea,
|
||||
} from "../inputSpecTypes"
|
||||
import { DefaultString } from "../inputSpecTypes"
|
||||
import { _, once } from "../../../util"
|
||||
import {
|
||||
Parser,
|
||||
any,
|
||||
anyOf,
|
||||
arrayOf,
|
||||
boolean,
|
||||
literal,
|
||||
literals,
|
||||
number,
|
||||
object,
|
||||
string,
|
||||
unknown,
|
||||
} from "ts-matches"
|
||||
|
||||
export type RequiredDefault<A> =
|
||||
| false
|
||||
| {
|
||||
default: A | null
|
||||
}
|
||||
|
||||
function requiredLikeToAbove<Input extends RequiredDefault<A>, A>(
|
||||
requiredLike: Input,
|
||||
) {
|
||||
// prettier-ignore
|
||||
return {
|
||||
required: (typeof requiredLike === 'object' ? true : requiredLike) as (
|
||||
Input extends { default: unknown} ? true:
|
||||
Input extends true ? true :
|
||||
false
|
||||
),
|
||||
default:(typeof requiredLike === 'object' ? requiredLike.default : null) as (
|
||||
Input extends { default: infer Default } ? Default :
|
||||
null
|
||||
)
|
||||
};
|
||||
}
|
||||
type AsRequired<Type, MaybeRequiredType> = MaybeRequiredType extends
|
||||
| { default: unknown }
|
||||
| never
|
||||
? Type
|
||||
: Type | null | undefined
|
||||
|
||||
const testForAsRequiredParser = once(
|
||||
() => object({ required: object({ default: unknown }) }).test,
|
||||
)
|
||||
function asRequiredParser<
|
||||
Type,
|
||||
Input,
|
||||
Return extends
|
||||
| Parser<unknown, Type>
|
||||
| Parser<unknown, Type | null | undefined>,
|
||||
>(parser: Parser<unknown, Type>, input: Input): Return {
|
||||
if (testForAsRequiredParser()(input)) return parser as any
|
||||
return parser.optional() as any
|
||||
}
|
||||
|
||||
export class Value<Type, Store> {
|
||||
protected constructor(
|
||||
public build: LazyBuild<Store, ValueSpec>,
|
||||
public validator: Parser<unknown, Type>,
|
||||
) {}
|
||||
static toggle(a: {
|
||||
name: string
|
||||
description?: string | null
|
||||
/** Presents a warning prompt before permitting the value to change. */
|
||||
warning?: string | null
|
||||
default: boolean
|
||||
/**
|
||||
* @description Once set, the value can never be changed.
|
||||
* @default false
|
||||
*/
|
||||
immutable?: boolean
|
||||
}) {
|
||||
return new Value<boolean, never>(
|
||||
async () => ({
|
||||
description: null,
|
||||
warning: null,
|
||||
type: "toggle" as const,
|
||||
disabled: false,
|
||||
immutable: a.immutable ?? false,
|
||||
...a,
|
||||
}),
|
||||
boolean,
|
||||
)
|
||||
}
|
||||
static dynamicToggle<Store = never>(
|
||||
a: LazyBuild<
|
||||
Store,
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default: boolean
|
||||
disabled?: false | string
|
||||
}
|
||||
>,
|
||||
) {
|
||||
return new Value<boolean, Store>(
|
||||
async (options) => ({
|
||||
description: null,
|
||||
warning: null,
|
||||
type: "toggle" as const,
|
||||
disabled: false,
|
||||
immutable: false,
|
||||
...(await a(options)),
|
||||
}),
|
||||
boolean,
|
||||
)
|
||||
}
|
||||
static text<Required extends RequiredDefault<DefaultString>>(a: {
|
||||
name: string
|
||||
description?: string | null
|
||||
/** Presents a warning prompt before permitting the value to change. */
|
||||
warning?: string | null
|
||||
/**
|
||||
* @description Determines if the field is required. If so, optionally provide a default value.
|
||||
* @type { false | { default: string | RandomString | null } }
|
||||
* @example required: false
|
||||
* @example required: { default: null }
|
||||
* @example required: { default: 'World' }
|
||||
* @example required: { default: { charset: 'abcdefg', len: 16 } }
|
||||
*/
|
||||
required: Required
|
||||
/**
|
||||
* @description Mask (aka camouflage) text input with dots: ● ● ●
|
||||
* @default false
|
||||
*/
|
||||
masked?: boolean
|
||||
placeholder?: string | null
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
/**
|
||||
* @description A list of regular expressions to which the text must conform to pass validation. A human readable description is provided in case the validation fails.
|
||||
* @default []
|
||||
* @example
|
||||
* ```
|
||||
[
|
||||
{
|
||||
regex: "[a-z]",
|
||||
description: "May only contain lower case letters from the English alphabet."
|
||||
}
|
||||
]
|
||||
* ```
|
||||
*/
|
||||
patterns?: Pattern[]
|
||||
/**
|
||||
* @description Informs the browser how to behave and which keyboard to display on mobile
|
||||
* @default "text"
|
||||
*/
|
||||
inputmode?: ValueSpecText["inputmode"]
|
||||
/**
|
||||
* @description Once set, the value can never be changed.
|
||||
* @default false
|
||||
*/
|
||||
immutable?: boolean
|
||||
/**
|
||||
* @description Displays a button that will generate a random string according to the provided charset and len attributes.
|
||||
*/
|
||||
generate?: RandomString | null
|
||||
}) {
|
||||
return new Value<AsRequired<string, Required>, never>(
|
||||
async () => ({
|
||||
type: "text" as const,
|
||||
description: null,
|
||||
warning: null,
|
||||
masked: false,
|
||||
placeholder: null,
|
||||
minLength: null,
|
||||
maxLength: null,
|
||||
patterns: [],
|
||||
inputmode: "text",
|
||||
disabled: false,
|
||||
immutable: a.immutable ?? false,
|
||||
generate: a.generate ?? null,
|
||||
...a,
|
||||
...requiredLikeToAbove(a.required),
|
||||
}),
|
||||
asRequiredParser(string, a),
|
||||
)
|
||||
}
|
||||
static dynamicText<Store = never>(
|
||||
getA: LazyBuild<
|
||||
Store,
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
required: RequiredDefault<DefaultString>
|
||||
masked?: boolean
|
||||
placeholder?: string | null
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
patterns?: Pattern[]
|
||||
inputmode?: ValueSpecText["inputmode"]
|
||||
disabled?: string | false
|
||||
generate?: null | RandomString
|
||||
}
|
||||
>,
|
||||
) {
|
||||
return new Value<string | null | undefined, Store>(async (options) => {
|
||||
const a = await getA(options)
|
||||
return {
|
||||
type: "text" as const,
|
||||
description: null,
|
||||
warning: null,
|
||||
masked: false,
|
||||
placeholder: null,
|
||||
minLength: null,
|
||||
maxLength: null,
|
||||
patterns: [],
|
||||
inputmode: "text",
|
||||
disabled: false,
|
||||
immutable: false,
|
||||
generate: a.generate ?? null,
|
||||
...a,
|
||||
...requiredLikeToAbove(a.required),
|
||||
}
|
||||
}, string.optional())
|
||||
}
|
||||
static textarea(a: {
|
||||
name: string
|
||||
description?: string | null
|
||||
/** Presents a warning prompt before permitting the value to change. */
|
||||
warning?: string | null
|
||||
/**
|
||||
* @description Unlike other "required" fields, for textarea this is a simple boolean.
|
||||
*/
|
||||
required: boolean
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
placeholder?: string | null
|
||||
/**
|
||||
* @description Once set, the value can never be changed.
|
||||
* @default false
|
||||
*/
|
||||
immutable?: boolean
|
||||
}) {
|
||||
return new Value<string, never>(async () => {
|
||||
const built: ValueSpecTextarea = {
|
||||
description: null,
|
||||
warning: null,
|
||||
minLength: null,
|
||||
maxLength: null,
|
||||
placeholder: null,
|
||||
type: "textarea" as const,
|
||||
disabled: false,
|
||||
immutable: a.immutable ?? false,
|
||||
...a,
|
||||
}
|
||||
return built
|
||||
}, string)
|
||||
}
|
||||
static dynamicTextarea<Store = never>(
|
||||
getA: LazyBuild<
|
||||
Store,
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
required: boolean
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
placeholder?: string | null
|
||||
disabled?: false | string
|
||||
}
|
||||
>,
|
||||
) {
|
||||
return new Value<string, Store>(async (options) => {
|
||||
const a = await getA(options)
|
||||
return {
|
||||
description: null,
|
||||
warning: null,
|
||||
minLength: null,
|
||||
maxLength: null,
|
||||
placeholder: null,
|
||||
type: "textarea" as const,
|
||||
disabled: false,
|
||||
immutable: false,
|
||||
...a,
|
||||
}
|
||||
}, string)
|
||||
}
|
||||
static number<Required extends RequiredDefault<number>>(a: {
|
||||
name: string
|
||||
description?: string | null
|
||||
/** Presents a warning prompt before permitting the value to change. */
|
||||
warning?: string | null
|
||||
/**
|
||||
* @description Determines if the field is required. If so, optionally provide a default value.
|
||||
* @type { false | { default: number | null } }
|
||||
* @example required: false
|
||||
* @example required: { default: null }
|
||||
* @example required: { default: 7 }
|
||||
*/
|
||||
required: Required
|
||||
min?: number | null
|
||||
max?: number | null
|
||||
/**
|
||||
* @description How much does the number increase/decrease when using the arrows provided by the browser.
|
||||
* @default 1
|
||||
*/
|
||||
step?: number | null
|
||||
/**
|
||||
* @description Requires the number to be an integer.
|
||||
*/
|
||||
integer: boolean
|
||||
/**
|
||||
* @description Optionally display units to the right of the input box.
|
||||
*/
|
||||
units?: string | null
|
||||
placeholder?: string | null
|
||||
/**
|
||||
* @description Once set, the value can never be changed.
|
||||
* @default false
|
||||
*/
|
||||
immutable?: boolean
|
||||
}) {
|
||||
return new Value<AsRequired<number, Required>, never>(
|
||||
() => ({
|
||||
type: "number" as const,
|
||||
description: null,
|
||||
warning: null,
|
||||
min: null,
|
||||
max: null,
|
||||
step: null,
|
||||
units: null,
|
||||
placeholder: null,
|
||||
disabled: false,
|
||||
immutable: a.immutable ?? false,
|
||||
...a,
|
||||
...requiredLikeToAbove(a.required),
|
||||
}),
|
||||
asRequiredParser(number, a),
|
||||
)
|
||||
}
|
||||
static dynamicNumber<Store = never>(
|
||||
getA: LazyBuild<
|
||||
Store,
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
required: RequiredDefault<number>
|
||||
min?: number | null
|
||||
max?: number | null
|
||||
step?: number | null
|
||||
integer: boolean
|
||||
units?: string | null
|
||||
placeholder?: string | null
|
||||
disabled?: false | string
|
||||
}
|
||||
>,
|
||||
) {
|
||||
return new Value<number | null | undefined, Store>(async (options) => {
|
||||
const a = await getA(options)
|
||||
return {
|
||||
type: "number" as const,
|
||||
description: null,
|
||||
warning: null,
|
||||
min: null,
|
||||
max: null,
|
||||
step: null,
|
||||
units: null,
|
||||
placeholder: null,
|
||||
disabled: false,
|
||||
immutable: false,
|
||||
...a,
|
||||
...requiredLikeToAbove(a.required),
|
||||
}
|
||||
}, number.optional())
|
||||
}
|
||||
static color<Required extends RequiredDefault<string>>(a: {
|
||||
name: string
|
||||
description?: string | null
|
||||
/** Presents a warning prompt before permitting the value to change. */
|
||||
warning?: string | null
|
||||
/**
|
||||
* @description Determines if the field is required. If so, optionally provide a default value.
|
||||
* @type { false | { default: string | null } }
|
||||
* @example required: false
|
||||
* @example required: { default: null }
|
||||
* @example required: { default: 'ffffff' }
|
||||
*/
|
||||
required: Required
|
||||
/**
|
||||
* @description Once set, the value can never be changed.
|
||||
* @default false
|
||||
*/
|
||||
immutable?: boolean
|
||||
}) {
|
||||
return new Value<AsRequired<string, Required>, never>(
|
||||
() => ({
|
||||
type: "color" as const,
|
||||
description: null,
|
||||
warning: null,
|
||||
disabled: false,
|
||||
immutable: a.immutable ?? false,
|
||||
...a,
|
||||
...requiredLikeToAbove(a.required),
|
||||
}),
|
||||
|
||||
asRequiredParser(string, a),
|
||||
)
|
||||
}
|
||||
|
||||
static dynamicColor<Store = never>(
|
||||
getA: LazyBuild<
|
||||
Store,
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
required: RequiredDefault<string>
|
||||
disabled?: false | string
|
||||
}
|
||||
>,
|
||||
) {
|
||||
return new Value<string | null | undefined, Store>(async (options) => {
|
||||
const a = await getA(options)
|
||||
return {
|
||||
type: "color" as const,
|
||||
description: null,
|
||||
warning: null,
|
||||
disabled: false,
|
||||
immutable: false,
|
||||
...a,
|
||||
...requiredLikeToAbove(a.required),
|
||||
}
|
||||
}, string.optional())
|
||||
}
|
||||
static datetime<Required extends RequiredDefault<string>>(a: {
|
||||
name: string
|
||||
description?: string | null
|
||||
/** Presents a warning prompt before permitting the value to change. */
|
||||
warning?: string | null
|
||||
/**
|
||||
* @description Determines if the field is required. If so, optionally provide a default value.
|
||||
* @type { false | { default: string | null } }
|
||||
* @example required: false
|
||||
* @example required: { default: null }
|
||||
* @example required: { default: '1985-12-16 18:00:00.000' }
|
||||
*/
|
||||
required: Required
|
||||
/**
|
||||
* @description Informs the browser how to behave and which date/time component to display.
|
||||
* @default "datetime-local"
|
||||
*/
|
||||
inputmode?: ValueSpecDatetime["inputmode"]
|
||||
min?: string | null
|
||||
max?: string | null
|
||||
/**
|
||||
* @description Once set, the value can never be changed.
|
||||
* @default false
|
||||
*/
|
||||
immutable?: boolean
|
||||
}) {
|
||||
return new Value<AsRequired<string, Required>, never>(
|
||||
() => ({
|
||||
type: "datetime" as const,
|
||||
description: null,
|
||||
warning: null,
|
||||
inputmode: "datetime-local",
|
||||
min: null,
|
||||
max: null,
|
||||
step: null,
|
||||
disabled: false,
|
||||
immutable: a.immutable ?? false,
|
||||
...a,
|
||||
...requiredLikeToAbove(a.required),
|
||||
}),
|
||||
asRequiredParser(string, a),
|
||||
)
|
||||
}
|
||||
static dynamicDatetime<Store = never>(
|
||||
getA: LazyBuild<
|
||||
Store,
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
required: RequiredDefault<string>
|
||||
inputmode?: ValueSpecDatetime["inputmode"]
|
||||
min?: string | null
|
||||
max?: string | null
|
||||
disabled?: false | string
|
||||
}
|
||||
>,
|
||||
) {
|
||||
return new Value<string | null | undefined, Store>(async (options) => {
|
||||
const a = await getA(options)
|
||||
return {
|
||||
type: "datetime" as const,
|
||||
description: null,
|
||||
warning: null,
|
||||
inputmode: "datetime-local",
|
||||
min: null,
|
||||
max: null,
|
||||
disabled: false,
|
||||
immutable: false,
|
||||
...a,
|
||||
...requiredLikeToAbove(a.required),
|
||||
}
|
||||
}, string.optional())
|
||||
}
|
||||
static select<
|
||||
Required extends RequiredDefault<string>,
|
||||
Values extends Record<string, string>,
|
||||
>(a: {
|
||||
name: string
|
||||
description?: string | null
|
||||
/** Presents a warning prompt before permitting the value to change. */
|
||||
warning?: string | null
|
||||
/**
|
||||
* @description Determines if the field is required. If so, optionally provide a default value from the list of values.
|
||||
* @type { false | { default: string | null } }
|
||||
* @example required: false
|
||||
* @example required: { default: null }
|
||||
* @example required: { default: 'radio1' }
|
||||
*/
|
||||
required: Required
|
||||
/**
|
||||
* @description A mapping of unique radio options to their human readable display format.
|
||||
* @example
|
||||
* ```
|
||||
{
|
||||
radio1: "Radio 1"
|
||||
radio2: "Radio 2"
|
||||
radio3: "Radio 3"
|
||||
}
|
||||
* ```
|
||||
*/
|
||||
values: Values
|
||||
/**
|
||||
* @description Once set, the value can never be changed.
|
||||
* @default false
|
||||
*/
|
||||
immutable?: boolean
|
||||
}) {
|
||||
return new Value<AsRequired<keyof Values, Required>, never>(
|
||||
() => ({
|
||||
description: null,
|
||||
warning: null,
|
||||
type: "select" as const,
|
||||
disabled: false,
|
||||
immutable: a.immutable ?? false,
|
||||
...a,
|
||||
...requiredLikeToAbove(a.required),
|
||||
}),
|
||||
asRequiredParser(
|
||||
anyOf(
|
||||
...Object.keys(a.values).map((x: keyof Values & string) =>
|
||||
literal(x),
|
||||
),
|
||||
),
|
||||
a,
|
||||
) as any,
|
||||
)
|
||||
}
|
||||
static dynamicSelect<Store = never>(
|
||||
getA: LazyBuild<
|
||||
Store,
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
required: RequiredDefault<string>
|
||||
values: Record<string, string>
|
||||
disabled?: false | string | string[]
|
||||
}
|
||||
>,
|
||||
) {
|
||||
return new Value<string | null | undefined, Store>(async (options) => {
|
||||
const a = await getA(options)
|
||||
return {
|
||||
description: null,
|
||||
warning: null,
|
||||
type: "select" as const,
|
||||
disabled: false,
|
||||
immutable: false,
|
||||
...a,
|
||||
...requiredLikeToAbove(a.required),
|
||||
}
|
||||
}, string.optional())
|
||||
}
|
||||
static multiselect<Values extends Record<string, string>>(a: {
|
||||
name: string
|
||||
description?: string | null
|
||||
/** Presents a warning prompt before permitting the value to change. */
|
||||
warning?: string | null
|
||||
/**
|
||||
* @description A simple list of which options should be checked by default.
|
||||
*/
|
||||
default: string[]
|
||||
/**
|
||||
* @description A mapping of checkbox options to their human readable display format.
|
||||
* @example
|
||||
* ```
|
||||
{
|
||||
option1: "Option 1"
|
||||
option2: "Option 2"
|
||||
option3: "Option 3"
|
||||
}
|
||||
* ```
|
||||
*/
|
||||
values: Values
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
/**
|
||||
* @description Once set, the value can never be changed.
|
||||
* @default false
|
||||
*/
|
||||
immutable?: boolean
|
||||
}) {
|
||||
return new Value<(keyof Values)[], never>(
|
||||
() => ({
|
||||
type: "multiselect" as const,
|
||||
minLength: null,
|
||||
maxLength: null,
|
||||
warning: null,
|
||||
description: null,
|
||||
disabled: false,
|
||||
immutable: a.immutable ?? false,
|
||||
...a,
|
||||
}),
|
||||
arrayOf(
|
||||
literals(...(Object.keys(a.values) as any as [keyof Values & string])),
|
||||
),
|
||||
)
|
||||
}
|
||||
static dynamicMultiselect<Store = never>(
|
||||
getA: LazyBuild<
|
||||
Store,
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
default: string[]
|
||||
values: Record<string, string>
|
||||
minLength?: number | null
|
||||
maxLength?: number | null
|
||||
disabled?: false | string | string[]
|
||||
}
|
||||
>,
|
||||
) {
|
||||
return new Value<string[], Store>(async (options) => {
|
||||
const a = await getA(options)
|
||||
return {
|
||||
type: "multiselect" as const,
|
||||
minLength: null,
|
||||
maxLength: null,
|
||||
warning: null,
|
||||
description: null,
|
||||
disabled: false,
|
||||
immutable: false,
|
||||
...a,
|
||||
}
|
||||
}, arrayOf(string))
|
||||
}
|
||||
static object<Type extends Record<string, any>, Store>(
|
||||
a: {
|
||||
name: string
|
||||
description?: string | null
|
||||
},
|
||||
spec: InputSpec<Type, Store>,
|
||||
) {
|
||||
return new Value<Type, Store>(async (options) => {
|
||||
const built = await spec.build(options as any)
|
||||
return {
|
||||
type: "object" as const,
|
||||
description: null,
|
||||
warning: null,
|
||||
...a,
|
||||
spec: built,
|
||||
}
|
||||
}, spec.validator)
|
||||
}
|
||||
static file<Store>(a: {
|
||||
name: string
|
||||
description?: string | null
|
||||
extensions: string[]
|
||||
required: boolean
|
||||
}) {
|
||||
const buildValue = {
|
||||
type: "file" as const,
|
||||
description: null,
|
||||
warning: null,
|
||||
...a,
|
||||
}
|
||||
return new Value<FilePath, Store>(
|
||||
() => ({
|
||||
...buildValue,
|
||||
}),
|
||||
asRequiredParser(object({ filePath: string }), a),
|
||||
)
|
||||
}
|
||||
static dynamicFile<Required extends boolean, Store>(
|
||||
a: LazyBuild<
|
||||
Store,
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
extensions: string[]
|
||||
required: Required
|
||||
}
|
||||
>,
|
||||
) {
|
||||
return new Value<string | null | undefined, Store>(
|
||||
async (options) => ({
|
||||
type: "file" as const,
|
||||
description: null,
|
||||
warning: null,
|
||||
...(await a(options)),
|
||||
}),
|
||||
string.optional(),
|
||||
)
|
||||
}
|
||||
static union<Required extends RequiredDefault<string>, Type, Store>(
|
||||
a: {
|
||||
name: string
|
||||
description?: string | null
|
||||
/** Presents a warning prompt before permitting the value to change. */
|
||||
warning?: string | null
|
||||
/**
|
||||
* @description Determines if the field is required. If so, optionally provide a default value from the list of variants.
|
||||
* @type { false | { default: string | null } }
|
||||
* @example required: false
|
||||
* @example required: { default: null }
|
||||
* @example required: { default: 'variant1' }
|
||||
*/
|
||||
required: Required
|
||||
/**
|
||||
* @description Once set, the value can never be changed.
|
||||
* @default false
|
||||
*/
|
||||
immutable?: boolean
|
||||
},
|
||||
aVariants: Variants<Type, Store>,
|
||||
) {
|
||||
return new Value<AsRequired<Type, Required>, Store>(
|
||||
async (options) => ({
|
||||
type: "union" as const,
|
||||
description: null,
|
||||
warning: null,
|
||||
disabled: false,
|
||||
...a,
|
||||
variants: await aVariants.build(options as any),
|
||||
...requiredLikeToAbove(a.required),
|
||||
immutable: a.immutable ?? false,
|
||||
}),
|
||||
asRequiredParser(aVariants.validator, a),
|
||||
)
|
||||
}
|
||||
static filteredUnion<
|
||||
Required extends RequiredDefault<string>,
|
||||
Type extends Record<string, any>,
|
||||
Store = never,
|
||||
>(
|
||||
getDisabledFn: LazyBuild<Store, string[] | false | string>,
|
||||
a: {
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
required: Required
|
||||
},
|
||||
aVariants: Variants<Type, Store> | Variants<Type, never>,
|
||||
) {
|
||||
return new Value<AsRequired<Type, Required>, Store>(
|
||||
async (options) => ({
|
||||
type: "union" as const,
|
||||
description: null,
|
||||
warning: null,
|
||||
...a,
|
||||
variants: await aVariants.build(options as any),
|
||||
...requiredLikeToAbove(a.required),
|
||||
disabled: (await getDisabledFn(options)) || false,
|
||||
immutable: false,
|
||||
}),
|
||||
asRequiredParser(aVariants.validator, a),
|
||||
)
|
||||
}
|
||||
static dynamicUnion<
|
||||
Required extends RequiredDefault<string>,
|
||||
Type extends Record<string, any>,
|
||||
Store = never,
|
||||
>(
|
||||
getA: LazyBuild<
|
||||
Store,
|
||||
{
|
||||
name: string
|
||||
description?: string | null
|
||||
warning?: string | null
|
||||
required: Required
|
||||
disabled: string[] | false | string
|
||||
}
|
||||
>,
|
||||
aVariants: Variants<Type, Store> | Variants<Type, never>,
|
||||
) {
|
||||
return new Value<Type | null | undefined, Store>(async (options) => {
|
||||
const newValues = await getA(options)
|
||||
return {
|
||||
type: "union" as const,
|
||||
description: null,
|
||||
warning: null,
|
||||
...newValues,
|
||||
variants: await aVariants.build(options as any),
|
||||
...requiredLikeToAbove(newValues.required),
|
||||
immutable: false,
|
||||
}
|
||||
}, aVariants.validator.optional())
|
||||
}
|
||||
|
||||
static list<Type, Store>(a: List<Type, Store>) {
|
||||
return new Value<Type, Store>((options) => a.build(options), a.validator)
|
||||
}
|
||||
|
||||
static hidden<T>(parser: Parser<unknown, T> = any) {
|
||||
return new Value<T, never>(async () => {
|
||||
const built: ValueSpecHidden = {
|
||||
type: "hidden" as const,
|
||||
}
|
||||
return built
|
||||
}, parser)
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this during the times that the input needs a more specific type.
|
||||
* Used in types that the value/ variant/ list/ inputSpec is constructed somewhere else.
|
||||
```ts
|
||||
const a = InputSpec.text({
|
||||
name: "a",
|
||||
required: false,
|
||||
})
|
||||
|
||||
return InputSpec.of<Store>()({
|
||||
myValue: a.withStore(),
|
||||
})
|
||||
```
|
||||
*/
|
||||
withStore<NewStore extends Store extends never ? any : Store>() {
|
||||
return this as any as Value<Type, NewStore>
|
||||
}
|
||||
}
|
||||
123
sdk/base/lib/actions/input/builder/variants.ts
Normal file
123
sdk/base/lib/actions/input/builder/variants.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import { ValueSpec, ValueSpecUnion } from "../inputSpecTypes"
|
||||
import { LazyBuild, InputSpec } from "./inputSpec"
|
||||
import { Parser, anyOf, literals, object } from "ts-matches"
|
||||
|
||||
/**
|
||||
* Used in the the Value.select { @link './value.ts' }
|
||||
* to indicate the type of select variants that are available. The key for the record passed in will be the
|
||||
* key to the tag.id in the Value.select
|
||||
```ts
|
||||
|
||||
export const disabled = InputSpec.of({});
|
||||
export const size = Value.number({
|
||||
name: "Max Chain Size",
|
||||
default: 550,
|
||||
description: "Limit of blockchain size on disk.",
|
||||
warning: "Increasing this value will require re-syncing your node.",
|
||||
required: true,
|
||||
range: "[550,1000000)",
|
||||
integral: true,
|
||||
units: "MiB",
|
||||
placeholder: null,
|
||||
});
|
||||
export const automatic = InputSpec.of({ size: size });
|
||||
export const size1 = Value.number({
|
||||
name: "Failsafe Chain Size",
|
||||
default: 65536,
|
||||
description: "Prune blockchain if size expands beyond this.",
|
||||
warning: null,
|
||||
required: true,
|
||||
range: "[550,1000000)",
|
||||
integral: true,
|
||||
units: "MiB",
|
||||
placeholder: null,
|
||||
});
|
||||
export const manual = InputSpec.of({ size: size1 });
|
||||
export const pruningSettingsVariants = Variants.of({
|
||||
disabled: { name: "Disabled", spec: disabled },
|
||||
automatic: { name: "Automatic", spec: automatic },
|
||||
manual: { name: "Manual", spec: manual },
|
||||
});
|
||||
export const pruning = Value.union(
|
||||
{
|
||||
name: "Pruning Settings",
|
||||
description:
|
||||
'- Disabled: Disable pruning\n- Automatic: Limit blockchain size on disk to a certain number of megabytes\n- Manual: Prune blockchain with the "pruneblockchain" RPC\n',
|
||||
warning: null,
|
||||
required: true,
|
||||
default: "disabled",
|
||||
},
|
||||
pruningSettingsVariants
|
||||
);
|
||||
```
|
||||
*/
|
||||
export class Variants<Type, Store> {
|
||||
static text: any
|
||||
private constructor(
|
||||
public build: LazyBuild<Store, ValueSpecUnion["variants"]>,
|
||||
public validator: Parser<unknown, Type>,
|
||||
) {}
|
||||
static of<
|
||||
VariantValues extends {
|
||||
[K in string]: {
|
||||
name: string
|
||||
spec: InputSpec<any, Store> | InputSpec<any, never>
|
||||
}
|
||||
},
|
||||
Store = never,
|
||||
>(a: VariantValues) {
|
||||
const validator = anyOf(
|
||||
...Object.entries(a).map(([name, { spec }]) =>
|
||||
object({
|
||||
selection: literals(name),
|
||||
value: spec.validator,
|
||||
}),
|
||||
),
|
||||
) as Parser<unknown, any>
|
||||
|
||||
return new Variants<
|
||||
{
|
||||
[K in keyof VariantValues]: {
|
||||
selection: K
|
||||
// prettier-ignore
|
||||
value:
|
||||
VariantValues[K]["spec"] extends (InputSpec<infer B, Store> | InputSpec<infer B, never>) ? B :
|
||||
never
|
||||
}
|
||||
}[keyof VariantValues],
|
||||
Store
|
||||
>(async (options) => {
|
||||
const variants = {} as {
|
||||
[K in keyof VariantValues]: {
|
||||
name: string
|
||||
spec: Record<string, ValueSpec>
|
||||
}
|
||||
}
|
||||
for (const key in a) {
|
||||
const value = a[key]
|
||||
variants[key] = {
|
||||
name: value.name,
|
||||
spec: await value.spec.build(options as any),
|
||||
}
|
||||
}
|
||||
return variants
|
||||
}, validator)
|
||||
}
|
||||
/**
|
||||
* Use this during the times that the input needs a more specific type.
|
||||
* Used in types that the value/ variant/ list/ inputSpec is constructed somewhere else.
|
||||
```ts
|
||||
const a = InputSpec.text({
|
||||
name: "a",
|
||||
required: false,
|
||||
})
|
||||
|
||||
return InputSpec.of<Store>()({
|
||||
myValue: a.withStore(),
|
||||
})
|
||||
```
|
||||
*/
|
||||
withStore<NewStore extends Store extends never ? any : Store>() {
|
||||
return this as any as Variants<Type, NewStore>
|
||||
}
|
||||
}
|
||||
3
sdk/base/lib/actions/input/index.ts
Normal file
3
sdk/base/lib/actions/input/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * as constants from "./inputSpecConstants"
|
||||
export * as types from "./inputSpecTypes"
|
||||
export * as builder from "./builder"
|
||||
80
sdk/base/lib/actions/input/inputSpecConstants.ts
Normal file
80
sdk/base/lib/actions/input/inputSpecConstants.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { SmtpValue } from "../../types"
|
||||
import { GetSystemSmtp, Patterns } from "../../util"
|
||||
import { InputSpec, InputSpecOf } from "./builder/inputSpec"
|
||||
import { Value } from "./builder/value"
|
||||
import { Variants } from "./builder/variants"
|
||||
|
||||
/**
|
||||
* Base SMTP settings, to be used by StartOS for system wide SMTP
|
||||
*/
|
||||
export const customSmtp = InputSpec.of<InputSpecOf<SmtpValue>, never>({
|
||||
server: Value.text({
|
||||
name: "SMTP Server",
|
||||
required: {
|
||||
default: null,
|
||||
},
|
||||
}),
|
||||
port: Value.number({
|
||||
name: "Port",
|
||||
required: { default: 587 },
|
||||
min: 1,
|
||||
max: 65535,
|
||||
integer: true,
|
||||
}),
|
||||
from: Value.text({
|
||||
name: "From Address",
|
||||
required: {
|
||||
default: null,
|
||||
},
|
||||
placeholder: "<name>test@example.com",
|
||||
inputmode: "email",
|
||||
patterns: [Patterns.email],
|
||||
}),
|
||||
login: Value.text({
|
||||
name: "Login",
|
||||
required: {
|
||||
default: null,
|
||||
},
|
||||
}),
|
||||
password: Value.text({
|
||||
name: "Password",
|
||||
required: false,
|
||||
masked: true,
|
||||
}),
|
||||
})
|
||||
|
||||
/**
|
||||
* For service inputSpec. Gives users 3 options for SMTP: (1) disabled, (2) use system SMTP settings, (3) use custom SMTP settings
|
||||
*/
|
||||
export const smtpInputSpec = Value.filteredUnion(
|
||||
async ({ effects }) => {
|
||||
const smtp = await new GetSystemSmtp(effects).once()
|
||||
return smtp ? [] : ["system"]
|
||||
},
|
||||
{
|
||||
name: "SMTP",
|
||||
description: "Optionally provide an SMTP server for sending emails",
|
||||
required: { default: "disabled" },
|
||||
},
|
||||
Variants.of({
|
||||
disabled: { name: "Disabled", spec: InputSpec.of({}) },
|
||||
system: {
|
||||
name: "System Credentials",
|
||||
spec: InputSpec.of({
|
||||
customFrom: Value.text({
|
||||
name: "Custom From Address",
|
||||
description:
|
||||
"A custom from address for this service. If not provided, the system from address will be used.",
|
||||
required: false,
|
||||
placeholder: "<name>test@example.com",
|
||||
inputmode: "email",
|
||||
patterns: [Patterns.email],
|
||||
}),
|
||||
}),
|
||||
},
|
||||
custom: {
|
||||
name: "Custom Credentials",
|
||||
spec: customSmtp,
|
||||
},
|
||||
}),
|
||||
)
|
||||
251
sdk/base/lib/actions/input/inputSpecTypes.ts
Normal file
251
sdk/base/lib/actions/input/inputSpecTypes.ts
Normal file
@@ -0,0 +1,251 @@
|
||||
export type InputSpec = Record<string, ValueSpec>
|
||||
export type ValueType =
|
||||
| "text"
|
||||
| "textarea"
|
||||
| "number"
|
||||
| "color"
|
||||
| "datetime"
|
||||
| "toggle"
|
||||
| "select"
|
||||
| "multiselect"
|
||||
| "list"
|
||||
| "object"
|
||||
| "file"
|
||||
| "union"
|
||||
| "hidden"
|
||||
export type ValueSpec = ValueSpecOf<ValueType>
|
||||
/** core spec types. These types provide the metadata for performing validations */
|
||||
// prettier-ignore
|
||||
export type ValueSpecOf<T extends ValueType> =
|
||||
T extends "text" ? ValueSpecText :
|
||||
T extends "textarea" ? ValueSpecTextarea :
|
||||
T extends "number" ? ValueSpecNumber :
|
||||
T extends "color" ? ValueSpecColor :
|
||||
T extends "datetime" ? ValueSpecDatetime :
|
||||
T extends "toggle" ? ValueSpecToggle :
|
||||
T extends "select" ? ValueSpecSelect :
|
||||
T extends "multiselect" ? ValueSpecMultiselect :
|
||||
T extends "list" ? ValueSpecList :
|
||||
T extends "object" ? ValueSpecObject :
|
||||
T extends "file" ? ValueSpecFile :
|
||||
T extends "union" ? ValueSpecUnion :
|
||||
T extends "hidden" ? ValueSpecHidden :
|
||||
never
|
||||
|
||||
export type ValueSpecText = {
|
||||
name: string
|
||||
description: string | null
|
||||
warning: string | null
|
||||
|
||||
type: "text"
|
||||
patterns: Pattern[]
|
||||
minLength: number | null
|
||||
maxLength: number | null
|
||||
masked: boolean
|
||||
|
||||
inputmode: "text" | "email" | "tel" | "url"
|
||||
placeholder: string | null
|
||||
|
||||
required: boolean
|
||||
default: DefaultString | null
|
||||
disabled: false | string
|
||||
generate: null | RandomString
|
||||
immutable: boolean
|
||||
}
|
||||
export type ValueSpecTextarea = {
|
||||
name: string
|
||||
description: string | null
|
||||
warning: string | null
|
||||
|
||||
type: "textarea"
|
||||
placeholder: string | null
|
||||
minLength: number | null
|
||||
maxLength: number | null
|
||||
required: boolean
|
||||
disabled: false | string
|
||||
immutable: boolean
|
||||
}
|
||||
|
||||
export type FilePath = {
|
||||
filePath: string
|
||||
}
|
||||
export type ValueSpecNumber = {
|
||||
type: "number"
|
||||
min: number | null
|
||||
max: number | null
|
||||
integer: boolean
|
||||
step: number | null
|
||||
units: string | null
|
||||
placeholder: string | null
|
||||
name: string
|
||||
description: string | null
|
||||
warning: string | null
|
||||
required: boolean
|
||||
default: number | null
|
||||
disabled: false | string
|
||||
immutable: boolean
|
||||
}
|
||||
export type ValueSpecColor = {
|
||||
name: string
|
||||
description: string | null
|
||||
warning: string | null
|
||||
|
||||
type: "color"
|
||||
required: boolean
|
||||
default: string | null
|
||||
disabled: false | string
|
||||
immutable: boolean
|
||||
}
|
||||
export type ValueSpecDatetime = {
|
||||
name: string
|
||||
description: string | null
|
||||
warning: string | null
|
||||
type: "datetime"
|
||||
required: boolean
|
||||
inputmode: "date" | "time" | "datetime-local"
|
||||
min: string | null
|
||||
max: string | null
|
||||
default: string | null
|
||||
disabled: false | string
|
||||
immutable: boolean
|
||||
}
|
||||
export type ValueSpecSelect = {
|
||||
values: Record<string, string>
|
||||
name: string
|
||||
description: string | null
|
||||
warning: string | null
|
||||
type: "select"
|
||||
required: boolean
|
||||
default: string | null
|
||||
disabled: false | string | string[]
|
||||
immutable: boolean
|
||||
}
|
||||
export type ValueSpecMultiselect = {
|
||||
values: Record<string, string>
|
||||
|
||||
name: string
|
||||
description: string | null
|
||||
warning: string | null
|
||||
|
||||
type: "multiselect"
|
||||
minLength: number | null
|
||||
maxLength: number | null
|
||||
disabled: false | string | string[]
|
||||
default: string[]
|
||||
immutable: boolean
|
||||
}
|
||||
export type ValueSpecToggle = {
|
||||
name: string
|
||||
description: string | null
|
||||
warning: string | null
|
||||
|
||||
type: "toggle"
|
||||
default: boolean | null
|
||||
disabled: false | string
|
||||
immutable: boolean
|
||||
}
|
||||
export type ValueSpecUnion = {
|
||||
name: string
|
||||
description: string | null
|
||||
warning: string | null
|
||||
|
||||
type: "union"
|
||||
variants: Record<
|
||||
string,
|
||||
{
|
||||
name: string
|
||||
spec: InputSpec
|
||||
}
|
||||
>
|
||||
disabled: false | string | string[]
|
||||
required: boolean
|
||||
default: string | null
|
||||
immutable: boolean
|
||||
}
|
||||
export type ValueSpecFile = {
|
||||
name: string
|
||||
description: string | null
|
||||
warning: string | null
|
||||
type: "file"
|
||||
extensions: string[]
|
||||
required: boolean
|
||||
}
|
||||
export type ValueSpecObject = {
|
||||
name: string
|
||||
description: string | null
|
||||
warning: string | null
|
||||
type: "object"
|
||||
spec: InputSpec
|
||||
}
|
||||
export type ValueSpecHidden = {
|
||||
type: "hidden"
|
||||
}
|
||||
export type ListValueSpecType = "text" | "object"
|
||||
// prettier-ignore
|
||||
export type ListValueSpecOf<T extends ListValueSpecType> =
|
||||
T extends "text" ? ListValueSpecText :
|
||||
T extends "object" ? ListValueSpecObject :
|
||||
never
|
||||
export type ValueSpecList = ValueSpecListOf<ListValueSpecType>
|
||||
export type ValueSpecListOf<T extends ListValueSpecType> = {
|
||||
name: string
|
||||
description: string | null
|
||||
warning: string | null
|
||||
type: "list"
|
||||
spec: ListValueSpecOf<T>
|
||||
minLength: number | null
|
||||
maxLength: number | null
|
||||
disabled: false | string
|
||||
default:
|
||||
| string[]
|
||||
| DefaultString[]
|
||||
| Record<string, unknown>[]
|
||||
| readonly string[]
|
||||
| readonly DefaultString[]
|
||||
| readonly Record<string, unknown>[]
|
||||
}
|
||||
export type Pattern = {
|
||||
regex: string
|
||||
description: string
|
||||
}
|
||||
export type ListValueSpecText = {
|
||||
type: "text"
|
||||
patterns: Pattern[]
|
||||
minLength: number | null
|
||||
maxLength: number | null
|
||||
masked: boolean
|
||||
|
||||
generate: null | RandomString
|
||||
inputmode: "text" | "email" | "tel" | "url"
|
||||
placeholder: string | null
|
||||
}
|
||||
export type ListValueSpecObject = {
|
||||
type: "object"
|
||||
spec: InputSpec
|
||||
uniqueBy: UniqueBy
|
||||
displayAs: string | null
|
||||
}
|
||||
// TODO Aiden do we really want this expressivity? Why not the below. Also what's with the "readonly" portion?
|
||||
// export type UniqueBy = null | string | { any: string[] } | { all: string[] }
|
||||
|
||||
export type UniqueBy =
|
||||
| null
|
||||
| string
|
||||
| {
|
||||
any: readonly UniqueBy[] | UniqueBy[]
|
||||
}
|
||||
| {
|
||||
all: readonly UniqueBy[] | UniqueBy[]
|
||||
}
|
||||
export type DefaultString = string | RandomString
|
||||
export type RandomString = {
|
||||
charset: string
|
||||
len: number
|
||||
}
|
||||
// sometimes the type checker needs just a little bit of help
|
||||
export function isValueSpecListOf<S extends ListValueSpecType>(
|
||||
t: ValueSpec,
|
||||
s: S,
|
||||
): t is ValueSpecListOf<S> & { spec: ListValueSpecOf<S> } {
|
||||
return "spec" in t && t.spec.type === s
|
||||
}
|
||||
152
sdk/base/lib/actions/setupActions.ts
Normal file
152
sdk/base/lib/actions/setupActions.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { InputSpec } from "./input/builder"
|
||||
import { ExtractInputSpecType } from "./input/builder/inputSpec"
|
||||
import * as T from "../types"
|
||||
|
||||
export type Run<
|
||||
A extends
|
||||
| Record<string, any>
|
||||
| InputSpec<Record<string, any>, any>
|
||||
| InputSpec<Record<string, never>, never>,
|
||||
> = (options: {
|
||||
effects: T.Effects
|
||||
input: ExtractInputSpecType<A> & Record<string, any>
|
||||
}) => Promise<T.ActionResult | null>
|
||||
export type GetInput<
|
||||
A extends
|
||||
| Record<string, any>
|
||||
| InputSpec<Record<string, any>, any>
|
||||
| InputSpec<Record<string, any>, never>,
|
||||
> = (options: {
|
||||
effects: T.Effects
|
||||
}) => Promise<null | (ExtractInputSpecType<A> & Record<string, any>)>
|
||||
|
||||
export type MaybeFn<T> = T | ((options: { effects: T.Effects }) => Promise<T>)
|
||||
function callMaybeFn<T>(
|
||||
maybeFn: MaybeFn<T>,
|
||||
options: { effects: T.Effects },
|
||||
): Promise<T> {
|
||||
if (maybeFn instanceof Function) {
|
||||
return maybeFn(options)
|
||||
} else {
|
||||
return Promise.resolve(maybeFn)
|
||||
}
|
||||
}
|
||||
function mapMaybeFn<T, U>(
|
||||
maybeFn: MaybeFn<T>,
|
||||
map: (value: T) => U,
|
||||
): MaybeFn<U> {
|
||||
if (maybeFn instanceof Function) {
|
||||
return async (...args) => map(await maybeFn(...args))
|
||||
} else {
|
||||
return map(maybeFn)
|
||||
}
|
||||
}
|
||||
|
||||
export class Action<
|
||||
Id extends T.ActionId,
|
||||
Store,
|
||||
InputSpecType extends
|
||||
| Record<string, any>
|
||||
| InputSpec<any, Store>
|
||||
| InputSpec<any, never>,
|
||||
Type extends
|
||||
ExtractInputSpecType<InputSpecType> = ExtractInputSpecType<InputSpecType>,
|
||||
> {
|
||||
private constructor(
|
||||
readonly id: Id,
|
||||
private readonly metadataFn: MaybeFn<T.ActionMetadata>,
|
||||
private readonly inputSpec: InputSpecType,
|
||||
private readonly getInputFn: GetInput<Type>,
|
||||
private readonly runFn: Run<Type>,
|
||||
) {}
|
||||
static withInput<
|
||||
Id extends T.ActionId,
|
||||
Store,
|
||||
InputSpecType extends
|
||||
| Record<string, any>
|
||||
| InputSpec<any, Store>
|
||||
| InputSpec<any, never>,
|
||||
Type extends
|
||||
ExtractInputSpecType<InputSpecType> = ExtractInputSpecType<InputSpecType>,
|
||||
>(
|
||||
id: Id,
|
||||
metadata: MaybeFn<Omit<T.ActionMetadata, "hasInput">>,
|
||||
inputSpec: InputSpecType,
|
||||
getInput: GetInput<Type>,
|
||||
run: Run<Type>,
|
||||
): Action<Id, Store, InputSpecType, Type> {
|
||||
return new Action(
|
||||
id,
|
||||
mapMaybeFn(metadata, (m) => ({ ...m, hasInput: true })),
|
||||
inputSpec,
|
||||
getInput,
|
||||
run,
|
||||
)
|
||||
}
|
||||
static withoutInput<Id extends T.ActionId, Store>(
|
||||
id: Id,
|
||||
metadata: MaybeFn<Omit<T.ActionMetadata, "hasInput">>,
|
||||
run: Run<{}>,
|
||||
): Action<Id, Store, {}, {}> {
|
||||
return new Action(
|
||||
id,
|
||||
mapMaybeFn(metadata, (m) => ({ ...m, hasInput: true })),
|
||||
{},
|
||||
async () => null,
|
||||
run,
|
||||
)
|
||||
}
|
||||
async exportMetadata(options: {
|
||||
effects: T.Effects
|
||||
}): Promise<T.ActionMetadata> {
|
||||
const metadata = await callMaybeFn(this.metadataFn, options)
|
||||
await options.effects.action.export({ id: this.id, metadata })
|
||||
return metadata
|
||||
}
|
||||
async getInput(options: { effects: T.Effects }): Promise<T.ActionInput> {
|
||||
return {
|
||||
spec: await this.inputSpec.build(options),
|
||||
value: (await this.getInputFn(options)) || null,
|
||||
}
|
||||
}
|
||||
async run(options: {
|
||||
effects: T.Effects
|
||||
input: Type
|
||||
}): Promise<T.ActionResult | null> {
|
||||
return this.runFn(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class Actions<
|
||||
Store,
|
||||
AllActions extends Record<T.ActionId, Action<T.ActionId, Store, any, any>>,
|
||||
> {
|
||||
private constructor(private readonly actions: AllActions) {}
|
||||
static of<Store>(): Actions<Store, {}> {
|
||||
return new Actions({})
|
||||
}
|
||||
addAction<A extends Action<T.ActionId, Store, any, any>>(
|
||||
action: A,
|
||||
): Actions<Store, AllActions & { [id in A["id"]]: A }> {
|
||||
return new Actions({ ...this.actions, [action.id]: action })
|
||||
}
|
||||
update(options: { effects: T.Effects }): Promise<void> {
|
||||
const updater = async (options: { effects: T.Effects }) => {
|
||||
for (let action of Object.values(this.actions)) {
|
||||
await action.exportMetadata(options)
|
||||
}
|
||||
await options.effects.action.clear({ except: Object.keys(this.actions) })
|
||||
}
|
||||
const updaterCtx = { options }
|
||||
updaterCtx.options = {
|
||||
effects: {
|
||||
...options.effects,
|
||||
constRetry: () => updater(updaterCtx.options),
|
||||
},
|
||||
}
|
||||
return updater(updaterCtx.options)
|
||||
}
|
||||
get<Id extends T.ActionId>(actionId: Id): AllActions[Id] {
|
||||
return this.actions[actionId]
|
||||
}
|
||||
}
|
||||
208
sdk/base/lib/backup/Backups.ts
Normal file
208
sdk/base/lib/backup/Backups.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
import * as T from "../types"
|
||||
import * as child_process from "child_process"
|
||||
import { asError } from "../util"
|
||||
|
||||
export const DEFAULT_OPTIONS: T.SyncOptions = {
|
||||
delete: true,
|
||||
exclude: [],
|
||||
}
|
||||
export type BackupSync<Volumes extends string> = {
|
||||
dataPath: `/media/startos/volumes/${Volumes}/${string}`
|
||||
backupPath: `/media/startos/backup/${string}`
|
||||
options?: Partial<T.SyncOptions>
|
||||
backupOptions?: Partial<T.SyncOptions>
|
||||
restoreOptions?: Partial<T.SyncOptions>
|
||||
}
|
||||
/**
|
||||
* This utility simplifies the volume backup process.
|
||||
* ```ts
|
||||
* export const { createBackup, restoreBackup } = Backups.volumes("main").build();
|
||||
* ```
|
||||
*
|
||||
* Changing the options of the rsync, (ie excludes) use either
|
||||
* ```ts
|
||||
* Backups.volumes("main").set_options({exclude: ['bigdata/']}).volumes('excludedVolume').build()
|
||||
* // or
|
||||
* Backups.with_options({exclude: ['bigdata/']}).volumes('excludedVolume').build()
|
||||
* ```
|
||||
*
|
||||
* Using the more fine control, using the addSets for more control
|
||||
* ```ts
|
||||
* Backups.addSets({
|
||||
* srcVolume: 'main', srcPath:'smallData/', dstPath: 'main/smallData/', dstVolume: : Backups.BACKUP
|
||||
* }, {
|
||||
* srcVolume: 'main', srcPath:'bigData/', dstPath: 'main/bigData/', dstVolume: : Backups.BACKUP, options: {exclude:['bigData/excludeThis']}}
|
||||
* ).build()q
|
||||
* ```
|
||||
*/
|
||||
export class Backups<M extends T.Manifest> {
|
||||
private constructor(
|
||||
private options = DEFAULT_OPTIONS,
|
||||
private restoreOptions: Partial<T.SyncOptions> = {},
|
||||
private backupOptions: Partial<T.SyncOptions> = {},
|
||||
private backupSet = [] as BackupSync<M["volumes"][number]>[],
|
||||
) {}
|
||||
|
||||
static withVolumes<M extends T.Manifest = never>(
|
||||
...volumeNames: Array<M["volumes"][number]>
|
||||
): Backups<M> {
|
||||
return Backups.withSyncs(
|
||||
...volumeNames.map((srcVolume) => ({
|
||||
dataPath: `/media/startos/volumes/${srcVolume}/` as const,
|
||||
backupPath: `/media/startos/backup/${srcVolume}/` as const,
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
static withSyncs<M extends T.Manifest = never>(
|
||||
...syncs: BackupSync<M["volumes"][number]>[]
|
||||
) {
|
||||
return syncs.reduce((acc, x) => acc.addSync(x), new Backups<M>())
|
||||
}
|
||||
|
||||
static withOptions<M extends T.Manifest = never>(
|
||||
options?: Partial<T.SyncOptions>,
|
||||
) {
|
||||
return new Backups<M>({ ...DEFAULT_OPTIONS, ...options })
|
||||
}
|
||||
|
||||
setOptions(options?: Partial<T.SyncOptions>) {
|
||||
this.options = {
|
||||
...this.options,
|
||||
...options,
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
setBackupOptions(options?: Partial<T.SyncOptions>) {
|
||||
this.backupOptions = {
|
||||
...this.backupOptions,
|
||||
...options,
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
setRestoreOptions(options?: Partial<T.SyncOptions>) {
|
||||
this.restoreOptions = {
|
||||
...this.restoreOptions,
|
||||
...options,
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
addVolume(
|
||||
volume: M["volumes"][number],
|
||||
options?: Partial<{
|
||||
options: T.SyncOptions
|
||||
backupOptions: T.SyncOptions
|
||||
restoreOptions: T.SyncOptions
|
||||
}>,
|
||||
) {
|
||||
return this.addSync({
|
||||
dataPath: `/media/startos/volumes/${volume}/` as const,
|
||||
backupPath: `/media/startos/backup/${volume}/` as const,
|
||||
...options,
|
||||
})
|
||||
}
|
||||
addSync(sync: BackupSync<M["volumes"][0]>) {
|
||||
this.backupSet.push({
|
||||
...sync,
|
||||
options: { ...this.options, ...sync.options },
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
async createBackup() {
|
||||
for (const item of this.backupSet) {
|
||||
const rsyncResults = await runRsync({
|
||||
srcPath: item.dataPath,
|
||||
dstPath: item.backupPath,
|
||||
options: {
|
||||
...this.options,
|
||||
...this.backupOptions,
|
||||
...item.options,
|
||||
...item.backupOptions,
|
||||
},
|
||||
})
|
||||
await rsyncResults.wait()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
async restoreBackup() {
|
||||
for (const item of this.backupSet) {
|
||||
const rsyncResults = await runRsync({
|
||||
srcPath: item.backupPath,
|
||||
dstPath: item.dataPath,
|
||||
options: {
|
||||
...this.options,
|
||||
...this.backupOptions,
|
||||
...item.options,
|
||||
...item.backupOptions,
|
||||
},
|
||||
})
|
||||
await rsyncResults.wait()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
async function runRsync(rsyncOptions: {
|
||||
srcPath: string
|
||||
dstPath: string
|
||||
options: T.SyncOptions
|
||||
}): Promise<{
|
||||
id: () => Promise<string>
|
||||
wait: () => Promise<null>
|
||||
progress: () => Promise<number>
|
||||
}> {
|
||||
const { srcPath, dstPath, options } = rsyncOptions
|
||||
|
||||
const command = "rsync"
|
||||
const args: string[] = []
|
||||
if (options.delete) {
|
||||
args.push("--delete")
|
||||
}
|
||||
for (const exclude of options.exclude) {
|
||||
args.push(`--exclude=${exclude}`)
|
||||
}
|
||||
args.push("-actAXH")
|
||||
args.push("--info=progress2")
|
||||
args.push("--no-inc-recursive")
|
||||
args.push(srcPath)
|
||||
args.push(dstPath)
|
||||
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(`Backups.runAsync`, asError(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 }
|
||||
}
|
||||
39
sdk/base/lib/backup/setupBackups.ts
Normal file
39
sdk/base/lib/backup/setupBackups.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Backups } from "./Backups"
|
||||
import * as T from "../types"
|
||||
import { _ } from "../util"
|
||||
|
||||
export type SetupBackupsParams<M extends T.Manifest> =
|
||||
| M["volumes"][number][]
|
||||
| ((_: { effects: T.Effects }) => Promise<Backups<M>>)
|
||||
|
||||
type SetupBackupsRes = {
|
||||
createBackup: T.ExpectedExports.createBackup
|
||||
restoreBackup: T.ExpectedExports.restoreBackup
|
||||
}
|
||||
|
||||
export function setupBackups<M extends T.Manifest>(
|
||||
options: SetupBackupsParams<M>,
|
||||
) {
|
||||
let backupsFactory: (_: { effects: T.Effects }) => Promise<Backups<M>>
|
||||
if (options instanceof Function) {
|
||||
backupsFactory = options
|
||||
} else {
|
||||
backupsFactory = async () => Backups.withVolumes(...options)
|
||||
}
|
||||
const answer: {
|
||||
createBackup: T.ExpectedExports.createBackup
|
||||
restoreBackup: T.ExpectedExports.restoreBackup
|
||||
} = {
|
||||
get createBackup() {
|
||||
return (async (options) => {
|
||||
return (await backupsFactory(options)).createBackup()
|
||||
}) as T.ExpectedExports.createBackup
|
||||
},
|
||||
get restoreBackup() {
|
||||
return (async (options) => {
|
||||
return (await backupsFactory(options)).restoreBackup()
|
||||
}) as T.ExpectedExports.restoreBackup
|
||||
},
|
||||
}
|
||||
return answer
|
||||
}
|
||||
21
sdk/base/lib/dependencies/Dependency.ts
Normal file
21
sdk/base/lib/dependencies/Dependency.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { VersionRange } from "../exver"
|
||||
|
||||
export class Dependency {
|
||||
constructor(
|
||||
readonly data:
|
||||
| {
|
||||
/** Either "running" or "exists". Does the dependency need to be running, or does it only need to exist? */
|
||||
type: "running"
|
||||
/** The acceptable version range of the dependency. */
|
||||
versionRange: VersionRange
|
||||
/** A list of the dependency's health check IDs that must be passing for the service to be satisfied. */
|
||||
healthChecks: string[]
|
||||
}
|
||||
| {
|
||||
/** Either "running" or "exists". Does the dependency need to be running, or does it only need to exist? */
|
||||
type: "exists"
|
||||
/** The acceptable version range of the dependency. */
|
||||
versionRange: VersionRange
|
||||
},
|
||||
) {}
|
||||
}
|
||||
201
sdk/base/lib/dependencies/dependencies.ts
Normal file
201
sdk/base/lib/dependencies/dependencies.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
import { ExtendedVersion, VersionRange } from "../exver"
|
||||
import { PackageId, HealthCheckId } from "../types"
|
||||
import { Effects } from "../Effects"
|
||||
|
||||
export type CheckDependencies<DependencyId extends PackageId = PackageId> = {
|
||||
installedSatisfied: (packageId: DependencyId) => boolean
|
||||
installedVersionSatisfied: (packageId: DependencyId) => boolean
|
||||
runningSatisfied: (packageId: DependencyId) => boolean
|
||||
actionsSatisfied: (packageId: DependencyId) => boolean
|
||||
healthCheckSatisfied: (
|
||||
packageId: DependencyId,
|
||||
healthCheckId: HealthCheckId,
|
||||
) => boolean
|
||||
satisfied: () => boolean
|
||||
|
||||
throwIfInstalledNotSatisfied: (packageId: DependencyId) => void
|
||||
throwIfInstalledVersionNotSatisfied: (packageId: DependencyId) => void
|
||||
throwIfRunningNotSatisfied: (packageId: DependencyId) => void
|
||||
throwIfActionsNotSatisfied: (packageId: DependencyId) => void
|
||||
throwIfHealthNotSatisfied: (
|
||||
packageId: DependencyId,
|
||||
healthCheckId?: HealthCheckId,
|
||||
) => void
|
||||
throwIfNotSatisfied: (packageId?: DependencyId) => void
|
||||
}
|
||||
export async function checkDependencies<
|
||||
DependencyId extends PackageId = PackageId,
|
||||
>(
|
||||
effects: Effects,
|
||||
packageIds?: DependencyId[],
|
||||
): Promise<CheckDependencies<DependencyId>> {
|
||||
let [dependencies, results] = await Promise.all([
|
||||
effects.getDependencies(),
|
||||
effects.checkDependencies({
|
||||
packageIds,
|
||||
}),
|
||||
])
|
||||
if (packageIds) {
|
||||
dependencies = dependencies.filter((d) =>
|
||||
(packageIds as PackageId[]).includes(d.id),
|
||||
)
|
||||
}
|
||||
|
||||
const find = (packageId: DependencyId) => {
|
||||
const dependencyRequirement = dependencies.find((d) => d.id === packageId)
|
||||
const dependencyResult = results.find((d) => d.packageId === packageId)
|
||||
if (!dependencyRequirement || !dependencyResult) {
|
||||
throw new Error(`Unknown DependencyId ${packageId}`)
|
||||
}
|
||||
return { requirement: dependencyRequirement, result: dependencyResult }
|
||||
}
|
||||
|
||||
const installedSatisfied = (packageId: DependencyId) =>
|
||||
!!find(packageId).result.installedVersion
|
||||
const installedVersionSatisfied = (packageId: DependencyId) => {
|
||||
const dep = find(packageId)
|
||||
return (
|
||||
!!dep.result.installedVersion &&
|
||||
ExtendedVersion.parse(dep.result.installedVersion).satisfies(
|
||||
VersionRange.parse(dep.requirement.versionRange),
|
||||
)
|
||||
)
|
||||
}
|
||||
const runningSatisfied = (packageId: DependencyId) => {
|
||||
const dep = find(packageId)
|
||||
return dep.requirement.kind !== "running" || dep.result.isRunning
|
||||
}
|
||||
const actionsSatisfied = (packageId: DependencyId) =>
|
||||
Object.keys(find(packageId).result.requestedActions).length === 0
|
||||
const healthCheckSatisfied = (
|
||||
packageId: DependencyId,
|
||||
healthCheckId?: HealthCheckId,
|
||||
) => {
|
||||
const dep = find(packageId)
|
||||
if (
|
||||
healthCheckId &&
|
||||
(dep.requirement.kind !== "running" ||
|
||||
!dep.requirement.healthChecks.includes(healthCheckId))
|
||||
) {
|
||||
throw new Error(`Unknown HealthCheckId ${healthCheckId}`)
|
||||
}
|
||||
const errors = Object.entries(dep.result.healthChecks)
|
||||
.filter(([id, _]) => (healthCheckId ? id === healthCheckId : true))
|
||||
.filter(([_, res]) => res.result !== "success")
|
||||
return errors.length === 0
|
||||
}
|
||||
const pkgSatisfied = (packageId: DependencyId) =>
|
||||
installedSatisfied(packageId) &&
|
||||
installedVersionSatisfied(packageId) &&
|
||||
runningSatisfied(packageId) &&
|
||||
actionsSatisfied(packageId) &&
|
||||
healthCheckSatisfied(packageId)
|
||||
const satisfied = (packageId?: DependencyId) =>
|
||||
packageId
|
||||
? pkgSatisfied(packageId)
|
||||
: dependencies.every((d) => pkgSatisfied(d.id as DependencyId))
|
||||
|
||||
const throwIfInstalledNotSatisfied = (packageId: DependencyId) => {
|
||||
const dep = find(packageId)
|
||||
if (!dep.result.installedVersion) {
|
||||
throw new Error(`${dep.result.title || packageId} is not installed`)
|
||||
}
|
||||
}
|
||||
const throwIfInstalledVersionNotSatisfied = (packageId: DependencyId) => {
|
||||
const dep = find(packageId)
|
||||
if (!dep.result.installedVersion) {
|
||||
throw new Error(`${dep.result.title || packageId} is not installed`)
|
||||
}
|
||||
if (
|
||||
![dep.result.installedVersion, ...dep.result.satisfies].find((v) =>
|
||||
ExtendedVersion.parse(v).satisfies(
|
||||
VersionRange.parse(dep.requirement.versionRange),
|
||||
),
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
`Installed version ${dep.result.installedVersion} of ${dep.result.title || packageId} does not match expected version range ${dep.requirement.versionRange}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
const throwIfRunningNotSatisfied = (packageId: DependencyId) => {
|
||||
const dep = find(packageId)
|
||||
if (dep.requirement.kind === "running" && !dep.result.isRunning) {
|
||||
throw new Error(`${dep.result.title || packageId} is not running`)
|
||||
}
|
||||
}
|
||||
const throwIfActionsNotSatisfied = (packageId: DependencyId) => {
|
||||
const dep = find(packageId)
|
||||
const reqs = Object.keys(dep.result.requestedActions)
|
||||
if (reqs.length) {
|
||||
throw new Error(
|
||||
`The following action requests have not been fulfilled: ${reqs.join(", ")}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
const throwIfHealthNotSatisfied = (
|
||||
packageId: DependencyId,
|
||||
healthCheckId?: HealthCheckId,
|
||||
) => {
|
||||
const dep = find(packageId)
|
||||
if (
|
||||
healthCheckId &&
|
||||
(dep.requirement.kind !== "running" ||
|
||||
!dep.requirement.healthChecks.includes(healthCheckId))
|
||||
) {
|
||||
throw new Error(`Unknown HealthCheckId ${healthCheckId}`)
|
||||
}
|
||||
const errors = Object.entries(dep.result.healthChecks)
|
||||
.filter(([id, _]) => (healthCheckId ? id === healthCheckId : true))
|
||||
.filter(([_, res]) => res.result !== "success")
|
||||
if (errors.length) {
|
||||
throw new Error(
|
||||
errors
|
||||
.map(
|
||||
([_, e]) =>
|
||||
`Health Check ${e.name} of ${dep.result.title || packageId} failed with status ${e.result}${e.message ? `: ${e.message}` : ""}`,
|
||||
)
|
||||
.join("; "),
|
||||
)
|
||||
}
|
||||
}
|
||||
const throwIfPkgNotSatisfied = (packageId: DependencyId) => {
|
||||
throwIfInstalledNotSatisfied(packageId)
|
||||
throwIfInstalledVersionNotSatisfied(packageId)
|
||||
throwIfRunningNotSatisfied(packageId)
|
||||
throwIfActionsNotSatisfied(packageId)
|
||||
throwIfHealthNotSatisfied(packageId)
|
||||
}
|
||||
const throwIfNotSatisfied = (packageId?: DependencyId) =>
|
||||
packageId
|
||||
? throwIfPkgNotSatisfied(packageId)
|
||||
: (() => {
|
||||
const err = dependencies.flatMap((d) => {
|
||||
try {
|
||||
throwIfPkgNotSatisfied(d.id as DependencyId)
|
||||
} catch (e) {
|
||||
if (e instanceof Error) return [e.message]
|
||||
throw e
|
||||
}
|
||||
return []
|
||||
})
|
||||
if (err.length) {
|
||||
throw new Error(err.join("; "))
|
||||
}
|
||||
})()
|
||||
|
||||
return {
|
||||
installedSatisfied,
|
||||
installedVersionSatisfied,
|
||||
runningSatisfied,
|
||||
actionsSatisfied,
|
||||
healthCheckSatisfied,
|
||||
satisfied,
|
||||
throwIfInstalledNotSatisfied,
|
||||
throwIfInstalledVersionNotSatisfied,
|
||||
throwIfRunningNotSatisfied,
|
||||
throwIfActionsNotSatisfied,
|
||||
throwIfHealthNotSatisfied,
|
||||
throwIfNotSatisfied,
|
||||
}
|
||||
}
|
||||
6
sdk/base/lib/dependencies/index.ts
Normal file
6
sdk/base/lib/dependencies/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
// prettier-ignore
|
||||
export type ReadonlyDeep<A> =
|
||||
A extends Function ? A :
|
||||
A extends {} ? { readonly [K in keyof A]: ReadonlyDeep<A[K]> } : A;
|
||||
export type MaybePromise<A> = Promise<A> | A
|
||||
export type Message = string
|
||||
56
sdk/base/lib/dependencies/setupDependencies.ts
Normal file
56
sdk/base/lib/dependencies/setupDependencies.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import * as T from "../types"
|
||||
import { Dependency } from "./Dependency"
|
||||
|
||||
type DependencyType<Manifest extends T.Manifest> = {
|
||||
[K in keyof {
|
||||
[K in keyof Manifest["dependencies"]]: Manifest["dependencies"][K]["optional"] extends false
|
||||
? K
|
||||
: never
|
||||
}]: Dependency
|
||||
} & {
|
||||
[K in keyof {
|
||||
[K in keyof Manifest["dependencies"]]: Manifest["dependencies"][K]["optional"] extends true
|
||||
? K
|
||||
: never
|
||||
}]?: Dependency
|
||||
}
|
||||
|
||||
export function setupDependencies<Manifest extends T.Manifest>(
|
||||
fn: (options: { effects: T.Effects }) => Promise<DependencyType<Manifest>>,
|
||||
): (options: { effects: T.Effects }) => Promise<void> {
|
||||
return (options: { effects: T.Effects }) => {
|
||||
const updater = async (options: { effects: T.Effects }) => {
|
||||
const dependencyType = await fn(options)
|
||||
return await options.effects.setDependencies({
|
||||
dependencies: Object.entries(dependencyType).map(
|
||||
([
|
||||
id,
|
||||
{
|
||||
data: { versionRange, ...x },
|
||||
},
|
||||
]) => ({
|
||||
id,
|
||||
...x,
|
||||
...(x.type === "running"
|
||||
? {
|
||||
kind: "running",
|
||||
healthChecks: x.healthChecks,
|
||||
}
|
||||
: {
|
||||
kind: "exists",
|
||||
}),
|
||||
versionRange: versionRange.toString(),
|
||||
}),
|
||||
),
|
||||
})
|
||||
}
|
||||
const updaterCtx = { options }
|
||||
updaterCtx.options = {
|
||||
effects: {
|
||||
...options.effects,
|
||||
constRetry: () => updater(updaterCtx.options),
|
||||
},
|
||||
}
|
||||
return updater(updaterCtx.options)
|
||||
}
|
||||
}
|
||||
99
sdk/base/lib/exver/exver.pegjs
Normal file
99
sdk/base/lib/exver/exver.pegjs
Normal file
@@ -0,0 +1,99 @@
|
||||
// #flavor:0.1.2-beta.1:0
|
||||
// !( >=1:1 && <= 2:2)
|
||||
|
||||
VersionRange
|
||||
= first:VersionRangeAtom rest:(_ ((Or / And) _)? VersionRangeAtom)*
|
||||
|
||||
Or = "||"
|
||||
|
||||
And = "&&"
|
||||
|
||||
VersionRangeAtom
|
||||
= Parens
|
||||
/ Anchor
|
||||
/ Not
|
||||
/ Any
|
||||
/ None
|
||||
|
||||
Parens
|
||||
= "(" _ expr:VersionRange _ ")" { return { type: "Parens", expr } }
|
||||
|
||||
Anchor
|
||||
= operator:CmpOp? _ version:VersionSpec { return { type: "Anchor", operator, version } }
|
||||
|
||||
VersionSpec
|
||||
= flavor:Flavor? upstream:Version downstream:( ":" Version )? { return { flavor: flavor || null, upstream, downstream: downstream ? downstream[1] : { number: [0], prerelease: [] } } }
|
||||
|
||||
Not = "!" _ value:VersionRangeAtom { return { type: "Not", value: value }}
|
||||
|
||||
Any = "*" { return { type: "Any" } }
|
||||
|
||||
None = "!" { return { type: "None" } }
|
||||
|
||||
CmpOp
|
||||
= ">=" { return ">="; }
|
||||
/ "<=" { return "<="; }
|
||||
/ ">" { return ">"; }
|
||||
/ "<" { return "<"; }
|
||||
/ "=" { return "="; }
|
||||
/ "!=" { return "!="; }
|
||||
/ "^" { return "^"; }
|
||||
/ "~" { return "~"; }
|
||||
|
||||
ExtendedVersion
|
||||
= flavor:Flavor? upstream:Version ":" downstream:Version {
|
||||
return { flavor: flavor || null, upstream, downstream }
|
||||
}
|
||||
|
||||
EmVer
|
||||
= major:Digit "." minor:Digit "." patch:Digit ("." revision:Digit)? {
|
||||
return {
|
||||
flavor: null,
|
||||
upstream: {
|
||||
number: [major, minor, patch],
|
||||
prerelease: [],
|
||||
},
|
||||
downstream: {
|
||||
number: [revision || 0],
|
||||
prerelease: [],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Flavor
|
||||
= "#" flavor:Lowercase ":" { return flavor }
|
||||
|
||||
Lowercase
|
||||
= [a-z]+ { return text() }
|
||||
|
||||
String
|
||||
= [a-zA-Z]+ { return text(); }
|
||||
|
||||
Version
|
||||
= number:VersionNumber prerelease: PreRelease? {
|
||||
return {
|
||||
number,
|
||||
prerelease: prerelease || []
|
||||
};
|
||||
}
|
||||
|
||||
PreRelease
|
||||
= "-" first:PreReleaseSegment rest:("." PreReleaseSegment)* {
|
||||
return [first].concat(rest.map(r => r[1]));
|
||||
}
|
||||
|
||||
PreReleaseSegment
|
||||
= "."? segment:(Digit / String) {
|
||||
return segment;
|
||||
}
|
||||
|
||||
VersionNumber
|
||||
= first:Digit rest:("." Digit)* {
|
||||
return [first].concat(rest.map(r => r[1]));
|
||||
}
|
||||
|
||||
Digit
|
||||
= [0-9]+ { return parseInt(text(), 10); }
|
||||
|
||||
_ "whitespace"
|
||||
= [ \t\n\r]*
|
||||
2507
sdk/base/lib/exver/exver.ts
Normal file
2507
sdk/base/lib/exver/exver.ts
Normal file
File diff suppressed because it is too large
Load Diff
454
sdk/base/lib/exver/index.ts
Normal file
454
sdk/base/lib/exver/index.ts
Normal file
@@ -0,0 +1,454 @@
|
||||
import * as P from "./exver"
|
||||
|
||||
// prettier-ignore
|
||||
export type ValidateVersion<T extends String> =
|
||||
T extends `-${infer A}` ? never :
|
||||
T extends `${infer A}-${string}` ? ValidateVersion<A> :
|
||||
T extends `${bigint}` ? unknown :
|
||||
T extends `${bigint}.${infer A}` ? ValidateVersion<A> :
|
||||
never
|
||||
|
||||
// prettier-ignore
|
||||
export type ValidateExVer<T extends string> =
|
||||
T extends `#${string}:${infer A}:${infer B}` ? ValidateVersion<A> & ValidateVersion<B> :
|
||||
T extends `${infer A}:${infer B}` ? ValidateVersion<A> & ValidateVersion<B> :
|
||||
never
|
||||
|
||||
// prettier-ignore
|
||||
export type ValidateExVers<T> =
|
||||
T extends [] ? unknown[] :
|
||||
T extends [infer A, ...infer B] ? ValidateExVer<A & string> & ValidateExVers<B> :
|
||||
never[]
|
||||
|
||||
type Anchor = {
|
||||
type: "Anchor"
|
||||
operator: P.CmpOp
|
||||
version: ExtendedVersion
|
||||
}
|
||||
|
||||
type And = {
|
||||
type: "And"
|
||||
left: VersionRange
|
||||
right: VersionRange
|
||||
}
|
||||
|
||||
type Or = {
|
||||
type: "Or"
|
||||
left: VersionRange
|
||||
right: VersionRange
|
||||
}
|
||||
|
||||
type Not = {
|
||||
type: "Not"
|
||||
value: VersionRange
|
||||
}
|
||||
|
||||
export class VersionRange {
|
||||
private constructor(public atom: Anchor | And | Or | Not | P.Any | P.None) {}
|
||||
|
||||
toString(): string {
|
||||
switch (this.atom.type) {
|
||||
case "Anchor":
|
||||
return `${this.atom.operator}${this.atom.version}`
|
||||
case "And":
|
||||
return `(${this.atom.left.toString()}) && (${this.atom.right.toString()})`
|
||||
case "Or":
|
||||
return `(${this.atom.left.toString()}) || (${this.atom.right.toString()})`
|
||||
case "Not":
|
||||
return `!(${this.atom.value.toString()})`
|
||||
case "Any":
|
||||
return "*"
|
||||
case "None":
|
||||
return "!"
|
||||
}
|
||||
}
|
||||
|
||||
private static parseAtom(atom: P.VersionRangeAtom): VersionRange {
|
||||
switch (atom.type) {
|
||||
case "Not":
|
||||
return new VersionRange({
|
||||
type: "Not",
|
||||
value: VersionRange.parseAtom(atom.value),
|
||||
})
|
||||
case "Parens":
|
||||
return VersionRange.parseRange(atom.expr)
|
||||
case "Anchor":
|
||||
return new VersionRange({
|
||||
type: "Anchor",
|
||||
operator: atom.operator || "^",
|
||||
version: new ExtendedVersion(
|
||||
atom.version.flavor,
|
||||
new Version(
|
||||
atom.version.upstream.number,
|
||||
atom.version.upstream.prerelease,
|
||||
),
|
||||
new Version(
|
||||
atom.version.downstream.number,
|
||||
atom.version.downstream.prerelease,
|
||||
),
|
||||
),
|
||||
})
|
||||
default:
|
||||
return new VersionRange(atom)
|
||||
}
|
||||
}
|
||||
|
||||
private static parseRange(range: P.VersionRange): VersionRange {
|
||||
let result = VersionRange.parseAtom(range[0])
|
||||
for (const next of range[1]) {
|
||||
switch (next[1]?.[0]) {
|
||||
case "||":
|
||||
result = new VersionRange({
|
||||
type: "Or",
|
||||
left: result,
|
||||
right: VersionRange.parseAtom(next[2]),
|
||||
})
|
||||
break
|
||||
case "&&":
|
||||
default:
|
||||
result = new VersionRange({
|
||||
type: "And",
|
||||
left: result,
|
||||
right: VersionRange.parseAtom(next[2]),
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
static parse(range: string): VersionRange {
|
||||
return VersionRange.parseRange(
|
||||
P.parse(range, { startRule: "VersionRange" }),
|
||||
)
|
||||
}
|
||||
|
||||
and(right: VersionRange) {
|
||||
return new VersionRange({ type: "And", left: this, right })
|
||||
}
|
||||
|
||||
or(right: VersionRange) {
|
||||
return new VersionRange({ type: "Or", left: this, right })
|
||||
}
|
||||
|
||||
not() {
|
||||
return new VersionRange({ type: "Not", value: this })
|
||||
}
|
||||
|
||||
static anchor(operator: P.CmpOp, version: ExtendedVersion) {
|
||||
return new VersionRange({ type: "Anchor", operator, version })
|
||||
}
|
||||
|
||||
static any() {
|
||||
return new VersionRange({ type: "Any" })
|
||||
}
|
||||
|
||||
static none() {
|
||||
return new VersionRange({ type: "None" })
|
||||
}
|
||||
|
||||
satisfiedBy(version: Version | ExtendedVersion) {
|
||||
return version.satisfies(this)
|
||||
}
|
||||
}
|
||||
|
||||
export class Version {
|
||||
constructor(
|
||||
public number: number[],
|
||||
public prerelease: (string | number)[],
|
||||
) {}
|
||||
|
||||
toString(): string {
|
||||
return `${this.number.join(".")}${this.prerelease.length > 0 ? `-${this.prerelease.join(".")}` : ""}`
|
||||
}
|
||||
|
||||
compare(other: Version): "greater" | "equal" | "less" {
|
||||
const numLen = Math.max(this.number.length, other.number.length)
|
||||
for (let i = 0; i < numLen; i++) {
|
||||
if ((this.number[i] || 0) > (other.number[i] || 0)) {
|
||||
return "greater"
|
||||
} else if ((this.number[i] || 0) < (other.number[i] || 0)) {
|
||||
return "less"
|
||||
}
|
||||
}
|
||||
|
||||
if (this.prerelease.length === 0 && other.prerelease.length !== 0) {
|
||||
return "greater"
|
||||
} else if (this.prerelease.length !== 0 && other.prerelease.length === 0) {
|
||||
return "less"
|
||||
}
|
||||
|
||||
const prereleaseLen = Math.max(this.number.length, other.number.length)
|
||||
for (let i = 0; i < prereleaseLen; i++) {
|
||||
if (typeof this.prerelease[i] === typeof other.prerelease[i]) {
|
||||
if (this.prerelease[i] > other.prerelease[i]) {
|
||||
return "greater"
|
||||
} else if (this.prerelease[i] < other.prerelease[i]) {
|
||||
return "less"
|
||||
}
|
||||
} else {
|
||||
switch (`${typeof this.prerelease[1]}:${typeof other.prerelease[i]}`) {
|
||||
case "number:string":
|
||||
return "less"
|
||||
case "string:number":
|
||||
return "greater"
|
||||
case "number:undefined":
|
||||
case "string:undefined":
|
||||
return "greater"
|
||||
case "undefined:number":
|
||||
case "undefined:string":
|
||||
return "less"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "equal"
|
||||
}
|
||||
|
||||
static parse(version: string): Version {
|
||||
const parsed = P.parse(version, { startRule: "Version" })
|
||||
return new Version(parsed.number, parsed.prerelease)
|
||||
}
|
||||
|
||||
satisfies(versionRange: VersionRange): boolean {
|
||||
return new ExtendedVersion(null, this, new Version([0], [])).satisfies(
|
||||
versionRange,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// #flavor:0.1.2-beta.1:0
|
||||
export class ExtendedVersion {
|
||||
constructor(
|
||||
public flavor: string | null,
|
||||
public upstream: Version,
|
||||
public downstream: Version,
|
||||
) {}
|
||||
|
||||
toString(): string {
|
||||
return `${this.flavor ? `#${this.flavor}:` : ""}${this.upstream.toString()}:${this.downstream.toString()}`
|
||||
}
|
||||
|
||||
compare(other: ExtendedVersion): "greater" | "equal" | "less" | null {
|
||||
if (this.flavor !== other.flavor) {
|
||||
return null
|
||||
}
|
||||
const upstreamCmp = this.upstream.compare(other.upstream)
|
||||
if (upstreamCmp !== "equal") {
|
||||
return upstreamCmp
|
||||
}
|
||||
return this.downstream.compare(other.downstream)
|
||||
}
|
||||
|
||||
compareLexicographic(other: ExtendedVersion): "greater" | "equal" | "less" {
|
||||
if ((this.flavor || "") > (other.flavor || "")) {
|
||||
return "greater"
|
||||
} else if ((this.flavor || "") > (other.flavor || "")) {
|
||||
return "less"
|
||||
} else {
|
||||
return this.compare(other)!
|
||||
}
|
||||
}
|
||||
|
||||
compareForSort(other: ExtendedVersion): 1 | 0 | -1 {
|
||||
switch (this.compareLexicographic(other)) {
|
||||
case "greater":
|
||||
return 1
|
||||
case "equal":
|
||||
return 0
|
||||
case "less":
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
greaterThan(other: ExtendedVersion): boolean {
|
||||
return this.compare(other) === "greater"
|
||||
}
|
||||
|
||||
greaterThanOrEqual(other: ExtendedVersion): boolean {
|
||||
return ["greater", "equal"].includes(this.compare(other) as string)
|
||||
}
|
||||
|
||||
equals(other: ExtendedVersion): boolean {
|
||||
return this.compare(other) === "equal"
|
||||
}
|
||||
|
||||
lessThan(other: ExtendedVersion): boolean {
|
||||
return this.compare(other) === "less"
|
||||
}
|
||||
|
||||
lessThanOrEqual(other: ExtendedVersion): boolean {
|
||||
return ["less", "equal"].includes(this.compare(other) as string)
|
||||
}
|
||||
|
||||
static parse(extendedVersion: string): ExtendedVersion {
|
||||
const parsed = P.parse(extendedVersion, { startRule: "ExtendedVersion" })
|
||||
return new ExtendedVersion(
|
||||
parsed.flavor,
|
||||
new Version(parsed.upstream.number, parsed.upstream.prerelease),
|
||||
new Version(parsed.downstream.number, parsed.downstream.prerelease),
|
||||
)
|
||||
}
|
||||
|
||||
static parseEmver(extendedVersion: string): ExtendedVersion {
|
||||
const parsed = P.parse(extendedVersion, { startRule: "EmVer" })
|
||||
return new ExtendedVersion(
|
||||
parsed.flavor,
|
||||
new Version(parsed.upstream.number, parsed.upstream.prerelease),
|
||||
new Version(parsed.downstream.number, parsed.downstream.prerelease),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an ExtendedVersion with the Upstream major version version incremented by 1
|
||||
* and sets subsequent digits to zero.
|
||||
* If no non-zero upstream digit can be found the last upstream digit will be incremented.
|
||||
*/
|
||||
incrementMajor(): ExtendedVersion {
|
||||
const majorIdx = this.upstream.number.findIndex((num: number) => num !== 0)
|
||||
|
||||
const majorNumber = this.upstream.number.map((num, idx): number => {
|
||||
if (idx > majorIdx) {
|
||||
return 0
|
||||
} else if (idx === majorIdx) {
|
||||
return num + 1
|
||||
}
|
||||
return num
|
||||
})
|
||||
|
||||
const incrementedUpstream = new Version(majorNumber, [])
|
||||
const updatedDownstream = new Version([0], [])
|
||||
|
||||
return new ExtendedVersion(
|
||||
this.flavor,
|
||||
incrementedUpstream,
|
||||
updatedDownstream,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an ExtendedVersion with the Upstream minor version version incremented by 1
|
||||
* also sets subsequent digits to zero.
|
||||
* If no non-zero upstream digit can be found the last digit will be incremented.
|
||||
*/
|
||||
incrementMinor(): ExtendedVersion {
|
||||
const majorIdx = this.upstream.number.findIndex((num: number) => num !== 0)
|
||||
let minorIdx = majorIdx === -1 ? majorIdx : majorIdx + 1
|
||||
|
||||
const majorNumber = this.upstream.number.map((num, idx): number => {
|
||||
if (idx > minorIdx) {
|
||||
return 0
|
||||
} else if (idx === minorIdx) {
|
||||
return num + 1
|
||||
}
|
||||
return num
|
||||
})
|
||||
|
||||
const incrementedUpstream = new Version(majorNumber, [])
|
||||
const updatedDownstream = new Version([0], [])
|
||||
|
||||
return new ExtendedVersion(
|
||||
this.flavor,
|
||||
incrementedUpstream,
|
||||
updatedDownstream,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a boolean indicating whether a given version satisfies the VersionRange
|
||||
* !( >= 1:1 <= 2:2) || <=#bitcoin:1.2.0-alpha:0
|
||||
*/
|
||||
satisfies(versionRange: VersionRange): boolean {
|
||||
switch (versionRange.atom.type) {
|
||||
case "Anchor":
|
||||
const otherVersion = versionRange.atom.version
|
||||
switch (versionRange.atom.operator) {
|
||||
case "=":
|
||||
return this.equals(otherVersion)
|
||||
case ">":
|
||||
return this.greaterThan(otherVersion)
|
||||
case "<":
|
||||
return this.lessThan(otherVersion)
|
||||
case ">=":
|
||||
return this.greaterThanOrEqual(otherVersion)
|
||||
case "<=":
|
||||
return this.lessThanOrEqual(otherVersion)
|
||||
case "!=":
|
||||
return !this.equals(otherVersion)
|
||||
case "^":
|
||||
const nextMajor = versionRange.atom.version.incrementMajor()
|
||||
if (
|
||||
this.greaterThanOrEqual(otherVersion) &&
|
||||
this.lessThan(nextMajor)
|
||||
) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case "~":
|
||||
const nextMinor = versionRange.atom.version.incrementMinor()
|
||||
if (
|
||||
this.greaterThanOrEqual(otherVersion) &&
|
||||
this.lessThan(nextMinor)
|
||||
) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
case "And":
|
||||
return (
|
||||
this.satisfies(versionRange.atom.left) &&
|
||||
this.satisfies(versionRange.atom.right)
|
||||
)
|
||||
case "Or":
|
||||
return (
|
||||
this.satisfies(versionRange.atom.left) ||
|
||||
this.satisfies(versionRange.atom.right)
|
||||
)
|
||||
case "Not":
|
||||
return !this.satisfies(versionRange.atom.value)
|
||||
case "Any":
|
||||
return true
|
||||
case "None":
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const testTypeExVer = <T extends string>(t: T & ValidateExVer<T>) => t
|
||||
|
||||
export const testTypeVersion = <T extends string>(t: T & ValidateVersion<T>) =>
|
||||
t
|
||||
function tests() {
|
||||
testTypeVersion("1.2.3")
|
||||
testTypeVersion("1")
|
||||
testTypeVersion("12.34.56")
|
||||
testTypeVersion("1.2-3")
|
||||
testTypeVersion("1-3")
|
||||
testTypeVersion("1-alpha")
|
||||
// @ts-expect-error
|
||||
testTypeVersion("-3")
|
||||
// @ts-expect-error
|
||||
testTypeVersion("1.2.3:1")
|
||||
// @ts-expect-error
|
||||
testTypeVersion("#cat:1:1")
|
||||
|
||||
testTypeExVer("1.2.3:1.2.3")
|
||||
testTypeExVer("1.2.3.4.5.6.7.8.9.0:1")
|
||||
testTypeExVer("100:1")
|
||||
testTypeExVer("#cat:1:1")
|
||||
testTypeExVer("1.2.3.4.5.6.7.8.9.11.22.33:1")
|
||||
testTypeExVer("1-0:1")
|
||||
testTypeExVer("1-0:1")
|
||||
// @ts-expect-error
|
||||
testTypeExVer("1.2-3")
|
||||
// @ts-expect-error
|
||||
testTypeExVer("1-3")
|
||||
// @ts-expect-error
|
||||
testTypeExVer("1.2.3.4.5.6.7.8.9.0.10:1" as string)
|
||||
// @ts-expect-error
|
||||
testTypeExVer("1.-2:1")
|
||||
// @ts-expect-error
|
||||
testTypeExVer("1..2.3:3")
|
||||
}
|
||||
12
sdk/base/lib/index.ts
Normal file
12
sdk/base/lib/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export { S9pk } from "./s9pk"
|
||||
export { VersionRange, ExtendedVersion, Version } from "./exver"
|
||||
|
||||
export * as inputSpec from "./actions/input"
|
||||
export * as ISB from "./actions/input/builder"
|
||||
export * as IST from "./actions/input/inputSpecTypes"
|
||||
export * as types from "./types"
|
||||
export * as T from "./types"
|
||||
export * as yaml from "yaml"
|
||||
export * as matches from "ts-matches"
|
||||
|
||||
export * as utils from "./util"
|
||||
4
sdk/base/lib/interfaces/AddressReceipt.ts
Normal file
4
sdk/base/lib/interfaces/AddressReceipt.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
declare const AddressProof: unique symbol
|
||||
export type AddressReceipt = {
|
||||
[AddressProof]: never
|
||||
}
|
||||
210
sdk/base/lib/interfaces/Host.ts
Normal file
210
sdk/base/lib/interfaces/Host.ts
Normal file
@@ -0,0 +1,210 @@
|
||||
import { object, string } from "ts-matches"
|
||||
import { Effects } from "../Effects"
|
||||
import { Origin } from "./Origin"
|
||||
import { AddSslOptions, BindParams } from "../osBindings"
|
||||
import { Security } from "../osBindings"
|
||||
import { BindOptions } from "../osBindings"
|
||||
import { AlpnInfo } from "../osBindings"
|
||||
|
||||
export { AddSslOptions, Security, BindOptions }
|
||||
|
||||
export const knownProtocols = {
|
||||
http: {
|
||||
secure: null,
|
||||
defaultPort: 80,
|
||||
withSsl: "https",
|
||||
alpn: { specified: ["http/1.1"] } as AlpnInfo,
|
||||
},
|
||||
https: {
|
||||
secure: { ssl: true },
|
||||
defaultPort: 443,
|
||||
},
|
||||
ws: {
|
||||
secure: null,
|
||||
defaultPort: 80,
|
||||
withSsl: "wss",
|
||||
alpn: { specified: ["http/1.1"] } as AlpnInfo,
|
||||
},
|
||||
wss: {
|
||||
secure: { ssl: true },
|
||||
defaultPort: 443,
|
||||
},
|
||||
ssh: {
|
||||
secure: { ssl: false },
|
||||
defaultPort: 22,
|
||||
},
|
||||
bitcoin: {
|
||||
secure: { ssl: false },
|
||||
defaultPort: 8333,
|
||||
},
|
||||
lightning: {
|
||||
secure: { ssl: true },
|
||||
defaultPort: 9735,
|
||||
},
|
||||
grpc: {
|
||||
secure: { ssl: true },
|
||||
defaultPort: 50051,
|
||||
},
|
||||
dns: {
|
||||
secure: { ssl: false },
|
||||
defaultPort: 53,
|
||||
},
|
||||
} as const
|
||||
|
||||
export type Scheme = string | null
|
||||
|
||||
type KnownProtocols = typeof knownProtocols
|
||||
type ProtocolsWithSslVariants = {
|
||||
[K in keyof KnownProtocols]: KnownProtocols[K] extends {
|
||||
withSsl: string
|
||||
}
|
||||
? K
|
||||
: never
|
||||
}[keyof KnownProtocols]
|
||||
type NotProtocolsWithSslVariants = Exclude<
|
||||
keyof KnownProtocols,
|
||||
ProtocolsWithSslVariants
|
||||
>
|
||||
|
||||
type BindOptionsByKnownProtocol =
|
||||
| {
|
||||
protocol: ProtocolsWithSslVariants
|
||||
preferredExternalPort?: number
|
||||
addSsl?: Partial<AddSslOptions>
|
||||
}
|
||||
| {
|
||||
protocol: NotProtocolsWithSslVariants
|
||||
preferredExternalPort?: number
|
||||
addSsl?: AddSslOptions
|
||||
}
|
||||
export type BindOptionsByProtocol = BindOptionsByKnownProtocol | BindOptions
|
||||
|
||||
export type HostKind = BindParams["kind"]
|
||||
|
||||
const hasStringProtocol = object({
|
||||
protocol: string,
|
||||
}).test
|
||||
|
||||
export class Host {
|
||||
constructor(
|
||||
readonly options: {
|
||||
effects: Effects
|
||||
kind: HostKind
|
||||
id: string
|
||||
},
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @description Use this function to bind the host to an internal port and configured options for protocol, security, and external port.
|
||||
*
|
||||
* @param internalPort - The internal port to be bound.
|
||||
* @param options - The protocol options for this binding.
|
||||
* @returns A multi-origin that is capable of exporting one or more service interfaces.
|
||||
* @example
|
||||
* In this example, we bind a previously created multi-host to port 80, then select the http protocol and request an external port of 8332.
|
||||
*
|
||||
* ```
|
||||
const uiMultiOrigin = await uiMulti.bindPort(80, {
|
||||
protocol: 'http',
|
||||
preferredExternalPort: 8332,
|
||||
})
|
||||
* ```
|
||||
*/
|
||||
async bindPort(
|
||||
internalPort: number,
|
||||
options: BindOptionsByProtocol,
|
||||
): Promise<Origin<this>> {
|
||||
if (hasStringProtocol(options)) {
|
||||
return await this.bindPortForKnown(options, internalPort)
|
||||
} else {
|
||||
return await this.bindPortForUnknown(internalPort, options)
|
||||
}
|
||||
}
|
||||
|
||||
private async bindPortForUnknown(
|
||||
internalPort: number,
|
||||
options: {
|
||||
preferredExternalPort: number
|
||||
addSsl: AddSslOptions | null
|
||||
secure: { ssl: boolean } | null
|
||||
},
|
||||
) {
|
||||
const binderOptions = {
|
||||
kind: this.options.kind,
|
||||
id: this.options.id,
|
||||
internalPort,
|
||||
...options,
|
||||
}
|
||||
await this.options.effects.bind(binderOptions)
|
||||
|
||||
return new Origin(this, internalPort, null, null)
|
||||
}
|
||||
|
||||
private async bindPortForKnown(
|
||||
options: BindOptionsByKnownProtocol,
|
||||
internalPort: number,
|
||||
) {
|
||||
const protoInfo = knownProtocols[options.protocol]
|
||||
const preferredExternalPort =
|
||||
options.preferredExternalPort ||
|
||||
knownProtocols[options.protocol].defaultPort
|
||||
const sslProto = this.getSslProto(options, protoInfo)
|
||||
const addSsl =
|
||||
sslProto && "alpn" in protoInfo
|
||||
? {
|
||||
// addXForwardedHeaders: null,
|
||||
preferredExternalPort: knownProtocols[sslProto].defaultPort,
|
||||
scheme: sslProto,
|
||||
alpn: protoInfo.alpn,
|
||||
...("addSsl" in options ? options.addSsl : null),
|
||||
}
|
||||
: null
|
||||
|
||||
const secure: Security | null = !protoInfo.secure ? null : { ssl: false }
|
||||
|
||||
await this.options.effects.bind({
|
||||
kind: this.options.kind,
|
||||
id: this.options.id,
|
||||
internalPort,
|
||||
preferredExternalPort,
|
||||
addSsl,
|
||||
secure,
|
||||
})
|
||||
|
||||
return new Origin(this, internalPort, options.protocol, sslProto)
|
||||
}
|
||||
|
||||
private getSslProto(
|
||||
options: BindOptionsByKnownProtocol,
|
||||
protoInfo: KnownProtocols[keyof KnownProtocols],
|
||||
) {
|
||||
if (inObject("noAddSsl", options) && options.noAddSsl) return null
|
||||
if ("withSsl" in protoInfo && protoInfo.withSsl) return protoInfo.withSsl
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function inObject<Key extends string>(
|
||||
key: Key,
|
||||
obj: any,
|
||||
): obj is { [K in Key]: unknown } {
|
||||
return key in obj
|
||||
}
|
||||
|
||||
// export class StaticHost extends Host {
|
||||
// constructor(options: { effects: Effects; id: string }) {
|
||||
// super({ ...options, kind: "static" })
|
||||
// }
|
||||
// }
|
||||
|
||||
// export class SingleHost extends Host {
|
||||
// constructor(options: { effects: Effects; id: string }) {
|
||||
// super({ ...options, kind: "single" })
|
||||
// }
|
||||
// }
|
||||
|
||||
export class MultiHost extends Host {
|
||||
constructor(options: { effects: Effects; id: string }) {
|
||||
super({ ...options, kind: "multi" })
|
||||
}
|
||||
}
|
||||
88
sdk/base/lib/interfaces/Origin.ts
Normal file
88
sdk/base/lib/interfaces/Origin.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { AddressInfo } from "../types"
|
||||
import { AddressReceipt } from "./AddressReceipt"
|
||||
import { Host, Scheme } from "./Host"
|
||||
import { ServiceInterfaceBuilder } from "./ServiceInterfaceBuilder"
|
||||
|
||||
export class Origin<T extends Host> {
|
||||
constructor(
|
||||
readonly host: T,
|
||||
readonly internalPort: number,
|
||||
readonly scheme: string | null,
|
||||
readonly sslScheme: string | null,
|
||||
) {}
|
||||
|
||||
build({ username, path, search, schemeOverride }: BuildOptions): AddressInfo {
|
||||
const qpEntries = Object.entries(search)
|
||||
.map(
|
||||
([key, val]) => `${encodeURIComponent(key)}=${encodeURIComponent(val)}`,
|
||||
)
|
||||
.join("&")
|
||||
|
||||
const qp = qpEntries.length ? `?${qpEntries}` : ""
|
||||
|
||||
return {
|
||||
hostId: this.host.options.id,
|
||||
internalPort: this.internalPort,
|
||||
scheme: schemeOverride ? schemeOverride.noSsl : this.scheme,
|
||||
sslScheme: schemeOverride ? schemeOverride.ssl : this.sslScheme,
|
||||
suffix: `${path}${qp}`,
|
||||
username,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description A function to register a group of origins (<PROTOCOL> :// <HOSTNAME> : <PORT>) with StartOS
|
||||
*
|
||||
* The returned addressReceipt serves as proof that the addresses were registered
|
||||
*
|
||||
* @param addressInfo
|
||||
* @returns
|
||||
*/
|
||||
async export(
|
||||
serviceInterfaces: ServiceInterfaceBuilder[],
|
||||
): Promise<AddressInfo[] & AddressReceipt> {
|
||||
const addressesInfo = []
|
||||
for (let serviceInterface of serviceInterfaces) {
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
hasPrimary,
|
||||
id,
|
||||
type,
|
||||
username,
|
||||
path,
|
||||
search,
|
||||
schemeOverride,
|
||||
masked,
|
||||
} = serviceInterface.options
|
||||
|
||||
const addressInfo = this.build({
|
||||
username,
|
||||
path,
|
||||
search,
|
||||
schemeOverride,
|
||||
})
|
||||
|
||||
await serviceInterface.options.effects.exportServiceInterface({
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
hasPrimary,
|
||||
addressInfo,
|
||||
type,
|
||||
masked,
|
||||
})
|
||||
|
||||
addressesInfo.push(addressInfo)
|
||||
}
|
||||
|
||||
return addressesInfo as AddressInfo[] & AddressReceipt
|
||||
}
|
||||
}
|
||||
|
||||
type BuildOptions = {
|
||||
schemeOverride: { ssl: Scheme; noSsl: Scheme } | null
|
||||
username: string | null
|
||||
path: string
|
||||
search: Record<string, string>
|
||||
}
|
||||
32
sdk/base/lib/interfaces/ServiceInterfaceBuilder.ts
Normal file
32
sdk/base/lib/interfaces/ServiceInterfaceBuilder.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { ServiceInterfaceType } from "../types"
|
||||
import { Effects } from "../Effects"
|
||||
import { Scheme } from "./Host"
|
||||
|
||||
/**
|
||||
* A helper class for creating a Network Interface
|
||||
*
|
||||
* Network Interfaces are collections of web addresses that expose the same API or other resource,
|
||||
* display to the user with under a common name and description.
|
||||
*
|
||||
* All URIs on an interface inherit the same ui: bool, basic auth credentials, path, and search (query) params
|
||||
*
|
||||
* @param options
|
||||
* @returns
|
||||
*/
|
||||
export class ServiceInterfaceBuilder {
|
||||
constructor(
|
||||
readonly options: {
|
||||
effects: Effects
|
||||
name: string
|
||||
id: string
|
||||
description: string
|
||||
hasPrimary: boolean
|
||||
type: ServiceInterfaceType
|
||||
username: string | null
|
||||
path: string
|
||||
search: Record<string, string>
|
||||
schemeOverride: { ssl: Scheme; noSsl: Scheme } | null
|
||||
masked: boolean
|
||||
},
|
||||
) {}
|
||||
}
|
||||
4
sdk/base/lib/interfaces/interfaceReceipt.ts
Normal file
4
sdk/base/lib/interfaces/interfaceReceipt.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
declare const InterfaceProof: unique symbol
|
||||
export type InterfaceReceipt = {
|
||||
[InterfaceProof]: never
|
||||
}
|
||||
54
sdk/base/lib/interfaces/setupInterfaces.ts
Normal file
54
sdk/base/lib/interfaces/setupInterfaces.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import * as T from "../types"
|
||||
import { AddressReceipt } from "./AddressReceipt"
|
||||
|
||||
declare const UpdateServiceInterfacesProof: unique symbol
|
||||
export type UpdateServiceInterfacesReceipt = {
|
||||
[UpdateServiceInterfacesProof]: never
|
||||
}
|
||||
|
||||
export type ServiceInterfacesReceipt = Array<T.AddressInfo[] & AddressReceipt>
|
||||
export type SetServiceInterfaces<Output extends ServiceInterfacesReceipt> =
|
||||
(opts: { effects: T.Effects }) => Promise<Output>
|
||||
export type UpdateServiceInterfaces<Output extends ServiceInterfacesReceipt> =
|
||||
(opts: {
|
||||
effects: T.Effects
|
||||
}) => Promise<Output & UpdateServiceInterfacesReceipt>
|
||||
export type SetupServiceInterfaces = <Output extends ServiceInterfacesReceipt>(
|
||||
fn: SetServiceInterfaces<Output>,
|
||||
) => UpdateServiceInterfaces<Output>
|
||||
export const NO_INTERFACE_CHANGES = {} as UpdateServiceInterfacesReceipt
|
||||
export const setupServiceInterfaces: SetupServiceInterfaces = <
|
||||
Output extends ServiceInterfacesReceipt,
|
||||
>(
|
||||
fn: SetServiceInterfaces<Output>,
|
||||
) =>
|
||||
((options: { effects: T.Effects }) => {
|
||||
const updater = async (options: { effects: T.Effects }) => {
|
||||
const bindings: T.BindId[] = []
|
||||
const interfaces: T.ServiceInterfaceId[] = []
|
||||
const res = await fn({
|
||||
effects: {
|
||||
...options.effects,
|
||||
bind: (params: T.BindParams) => {
|
||||
bindings.push({ id: params.id, internalPort: params.internalPort })
|
||||
return options.effects.bind(params)
|
||||
},
|
||||
exportServiceInterface: (params: T.ExportServiceInterfaceParams) => {
|
||||
interfaces.push(params.id)
|
||||
return options.effects.exportServiceInterface(params)
|
||||
},
|
||||
},
|
||||
})
|
||||
await options.effects.clearBindings({ except: bindings })
|
||||
await options.effects.clearServiceInterfaces({ except: interfaces })
|
||||
return res
|
||||
}
|
||||
const updaterCtx = { options }
|
||||
updaterCtx.options = {
|
||||
effects: {
|
||||
...options.effects,
|
||||
constRetry: () => updater(updaterCtx.options),
|
||||
},
|
||||
}
|
||||
return updater(updaterCtx.options)
|
||||
}) as UpdateServiceInterfaces<Output>
|
||||
7
sdk/base/lib/osBindings/AcceptSigners.ts
Normal file
7
sdk/base/lib/osBindings/AcceptSigners.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AnyVerifyingKey } from "./AnyVerifyingKey"
|
||||
|
||||
export type AcceptSigners =
|
||||
| { signer: AnyVerifyingKey }
|
||||
| { any: Array<AcceptSigners> }
|
||||
| { all: Array<AcceptSigners> }
|
||||
3
sdk/base/lib/osBindings/ActionId.ts
Normal file
3
sdk/base/lib/osBindings/ActionId.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ActionId = string
|
||||
6
sdk/base/lib/osBindings/ActionInput.ts
Normal file
6
sdk/base/lib/osBindings/ActionInput.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ActionInput = {
|
||||
spec: Record<string, unknown>
|
||||
value: Record<string, unknown> | null
|
||||
}
|
||||
13
sdk/base/lib/osBindings/ActionMetadata.ts
Normal file
13
sdk/base/lib/osBindings/ActionMetadata.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ActionVisibility } from "./ActionVisibility"
|
||||
import type { AllowedStatuses } from "./AllowedStatuses"
|
||||
|
||||
export type ActionMetadata = {
|
||||
name: string
|
||||
description: string
|
||||
warning: string | null
|
||||
visibility: ActionVisibility
|
||||
allowedStatuses: AllowedStatuses
|
||||
hasInput: boolean
|
||||
group: string | null
|
||||
}
|
||||
13
sdk/base/lib/osBindings/ActionRequest.ts
Normal file
13
sdk/base/lib/osBindings/ActionRequest.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ActionId } from "./ActionId"
|
||||
import type { ActionRequestInput } from "./ActionRequestInput"
|
||||
import type { ActionRequestTrigger } from "./ActionRequestTrigger"
|
||||
import type { PackageId } from "./PackageId"
|
||||
|
||||
export type ActionRequest = {
|
||||
packageId: PackageId
|
||||
actionId: ActionId
|
||||
description?: string
|
||||
when?: ActionRequestTrigger
|
||||
input?: ActionRequestInput
|
||||
}
|
||||
3
sdk/base/lib/osBindings/ActionRequestCondition.ts
Normal file
3
sdk/base/lib/osBindings/ActionRequestCondition.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ActionRequestCondition = "input-not-matches"
|
||||
4
sdk/base/lib/osBindings/ActionRequestEntry.ts
Normal file
4
sdk/base/lib/osBindings/ActionRequestEntry.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ActionRequest } from "./ActionRequest"
|
||||
|
||||
export type ActionRequestEntry = { request: ActionRequest; active: boolean }
|
||||
6
sdk/base/lib/osBindings/ActionRequestInput.ts
Normal file
6
sdk/base/lib/osBindings/ActionRequestInput.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ActionRequestInput = {
|
||||
kind: "partial"
|
||||
value: Record<string, unknown>
|
||||
}
|
||||
7
sdk/base/lib/osBindings/ActionRequestTrigger.ts
Normal file
7
sdk/base/lib/osBindings/ActionRequestTrigger.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ActionRequestCondition } from "./ActionRequestCondition"
|
||||
|
||||
export type ActionRequestTrigger = {
|
||||
once: boolean
|
||||
condition: ActionRequestCondition
|
||||
}
|
||||
6
sdk/base/lib/osBindings/ActionVisibility.ts
Normal file
6
sdk/base/lib/osBindings/ActionVisibility.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ActionVisibility =
|
||||
| "hidden"
|
||||
| { disabled: { reason: string } }
|
||||
| "enabled"
|
||||
4
sdk/base/lib/osBindings/AddAdminParams.ts
Normal file
4
sdk/base/lib/osBindings/AddAdminParams.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Guid } from "./Guid"
|
||||
|
||||
export type AddAdminParams = { signer: Guid }
|
||||
11
sdk/base/lib/osBindings/AddAssetParams.ts
Normal file
11
sdk/base/lib/osBindings/AddAssetParams.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AnySignature } from "./AnySignature"
|
||||
import type { Blake3Commitment } from "./Blake3Commitment"
|
||||
|
||||
export type AddAssetParams = {
|
||||
version: string
|
||||
platform: string
|
||||
url: string
|
||||
signature: AnySignature
|
||||
commitment: Blake3Commitment
|
||||
}
|
||||
9
sdk/base/lib/osBindings/AddPackageParams.ts
Normal file
9
sdk/base/lib/osBindings/AddPackageParams.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AnySignature } from "./AnySignature"
|
||||
import type { MerkleArchiveCommitment } from "./MerkleArchiveCommitment"
|
||||
|
||||
export type AddPackageParams = {
|
||||
url: string
|
||||
commitment: MerkleArchiveCommitment
|
||||
signature: AnySignature
|
||||
}
|
||||
7
sdk/base/lib/osBindings/AddSslOptions.ts
Normal file
7
sdk/base/lib/osBindings/AddSslOptions.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AlpnInfo } from "./AlpnInfo"
|
||||
|
||||
export type AddSslOptions = {
|
||||
preferredExternalPort: number
|
||||
alpn: AlpnInfo | null
|
||||
}
|
||||
8
sdk/base/lib/osBindings/AddVersionParams.ts
Normal file
8
sdk/base/lib/osBindings/AddVersionParams.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type AddVersionParams = {
|
||||
version: string
|
||||
headline: string
|
||||
releaseNotes: string
|
||||
sourceVersion: string
|
||||
}
|
||||
11
sdk/base/lib/osBindings/AddressInfo.ts
Normal file
11
sdk/base/lib/osBindings/AddressInfo.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { HostId } from "./HostId"
|
||||
|
||||
export type AddressInfo = {
|
||||
username: string | null
|
||||
hostId: HostId
|
||||
internalPort: number
|
||||
scheme: string | null
|
||||
sslScheme: string | null
|
||||
suffix: string
|
||||
}
|
||||
9
sdk/base/lib/osBindings/Alerts.ts
Normal file
9
sdk/base/lib/osBindings/Alerts.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type Alerts = {
|
||||
install: string | null
|
||||
uninstall: string | null
|
||||
restore: string | null
|
||||
start: string | null
|
||||
stop: string | null
|
||||
}
|
||||
3
sdk/base/lib/osBindings/Algorithm.ts
Normal file
3
sdk/base/lib/osBindings/Algorithm.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type Algorithm = "ecdsa" | "ed25519"
|
||||
5
sdk/base/lib/osBindings/AllPackageData.ts
Normal file
5
sdk/base/lib/osBindings/AllPackageData.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { PackageDataEntry } from "./PackageDataEntry"
|
||||
import type { PackageId } from "./PackageId"
|
||||
|
||||
export type AllPackageData = { [key: PackageId]: PackageDataEntry }
|
||||
3
sdk/base/lib/osBindings/AllowedStatuses.ts
Normal file
3
sdk/base/lib/osBindings/AllowedStatuses.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type AllowedStatuses = "only-running" | "only-stopped" | "any"
|
||||
4
sdk/base/lib/osBindings/AlpnInfo.ts
Normal file
4
sdk/base/lib/osBindings/AlpnInfo.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { MaybeUtf8String } from "./MaybeUtf8String"
|
||||
|
||||
export type AlpnInfo = "reflect" | { specified: Array<MaybeUtf8String> }
|
||||
3
sdk/base/lib/osBindings/AnySignature.ts
Normal file
3
sdk/base/lib/osBindings/AnySignature.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type AnySignature = string
|
||||
3
sdk/base/lib/osBindings/AnySigningKey.ts
Normal file
3
sdk/base/lib/osBindings/AnySigningKey.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type AnySigningKey = string
|
||||
3
sdk/base/lib/osBindings/AnyVerifyingKey.ts
Normal file
3
sdk/base/lib/osBindings/AnyVerifyingKey.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type AnyVerifyingKey = string
|
||||
3
sdk/base/lib/osBindings/ApiState.ts
Normal file
3
sdk/base/lib/osBindings/ApiState.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ApiState = "error" | "initializing" | "running"
|
||||
7
sdk/base/lib/osBindings/AttachParams.ts
Normal file
7
sdk/base/lib/osBindings/AttachParams.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { EncryptedWire } from "./EncryptedWire"
|
||||
|
||||
export type AttachParams = {
|
||||
startOsPassword: EncryptedWire | null
|
||||
guid: string
|
||||
}
|
||||
3
sdk/base/lib/osBindings/BackupProgress.ts
Normal file
3
sdk/base/lib/osBindings/BackupProgress.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type BackupProgress = { complete: boolean }
|
||||
7
sdk/base/lib/osBindings/BackupTargetFS.ts
Normal file
7
sdk/base/lib/osBindings/BackupTargetFS.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { BlockDev } from "./BlockDev"
|
||||
import type { Cifs } from "./Cifs"
|
||||
|
||||
export type BackupTargetFS =
|
||||
| ({ type: "disk" } & BlockDev)
|
||||
| ({ type: "cifs" } & Cifs)
|
||||
3
sdk/base/lib/osBindings/Base64.ts
Normal file
3
sdk/base/lib/osBindings/Base64.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type Base64 = string
|
||||
4
sdk/base/lib/osBindings/BindId.ts
Normal file
4
sdk/base/lib/osBindings/BindId.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { HostId } from "./HostId"
|
||||
|
||||
export type BindId = { id: HostId; internalPort: number }
|
||||
5
sdk/base/lib/osBindings/BindInfo.ts
Normal file
5
sdk/base/lib/osBindings/BindInfo.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { BindOptions } from "./BindOptions"
|
||||
import type { LanInfo } from "./LanInfo"
|
||||
|
||||
export type BindInfo = { enabled: boolean; options: BindOptions; lan: LanInfo }
|
||||
9
sdk/base/lib/osBindings/BindOptions.ts
Normal file
9
sdk/base/lib/osBindings/BindOptions.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AddSslOptions } from "./AddSslOptions"
|
||||
import type { Security } from "./Security"
|
||||
|
||||
export type BindOptions = {
|
||||
preferredExternalPort: number
|
||||
addSsl: AddSslOptions | null
|
||||
secure: Security | null
|
||||
}
|
||||
14
sdk/base/lib/osBindings/BindParams.ts
Normal file
14
sdk/base/lib/osBindings/BindParams.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AddSslOptions } from "./AddSslOptions"
|
||||
import type { HostId } from "./HostId"
|
||||
import type { HostKind } from "./HostKind"
|
||||
import type { Security } from "./Security"
|
||||
|
||||
export type BindParams = {
|
||||
kind: HostKind
|
||||
id: HostId
|
||||
internalPort: number
|
||||
preferredExternalPort: number
|
||||
addSsl: AddSslOptions | null
|
||||
secure: Security | null
|
||||
}
|
||||
4
sdk/base/lib/osBindings/Blake3Commitment.ts
Normal file
4
sdk/base/lib/osBindings/Blake3Commitment.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Base64 } from "./Base64"
|
||||
|
||||
export type Blake3Commitment = { hash: Base64; size: number }
|
||||
3
sdk/base/lib/osBindings/BlockDev.ts
Normal file
3
sdk/base/lib/osBindings/BlockDev.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type BlockDev = { logicalname: string }
|
||||
3
sdk/base/lib/osBindings/CallbackId.ts
Normal file
3
sdk/base/lib/osBindings/CallbackId.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type CallbackId = number
|
||||
4
sdk/base/lib/osBindings/Category.ts
Normal file
4
sdk/base/lib/osBindings/Category.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Description } from "./Description"
|
||||
|
||||
export type Category = { name: string; description: Description }
|
||||
4
sdk/base/lib/osBindings/CheckDependenciesParam.ts
Normal file
4
sdk/base/lib/osBindings/CheckDependenciesParam.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { PackageId } from "./PackageId"
|
||||
|
||||
export type CheckDependenciesParam = { packageIds?: Array<PackageId> }
|
||||
17
sdk/base/lib/osBindings/CheckDependenciesResult.ts
Normal file
17
sdk/base/lib/osBindings/CheckDependenciesResult.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ActionRequestEntry } from "./ActionRequestEntry"
|
||||
import type { HealthCheckId } from "./HealthCheckId"
|
||||
import type { NamedHealthCheckResult } from "./NamedHealthCheckResult"
|
||||
import type { PackageId } from "./PackageId"
|
||||
import type { ReplayId } from "./ReplayId"
|
||||
import type { Version } from "./Version"
|
||||
|
||||
export type CheckDependenciesResult = {
|
||||
packageId: PackageId
|
||||
title: string | null
|
||||
installedVersion: Version | null
|
||||
satisfies: Array<Version>
|
||||
isRunning: boolean
|
||||
requestedActions: { [key: ReplayId]: ActionRequestEntry }
|
||||
healthChecks: { [key: HealthCheckId]: NamedHealthCheckResult }
|
||||
}
|
||||
8
sdk/base/lib/osBindings/Cifs.ts
Normal file
8
sdk/base/lib/osBindings/Cifs.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type Cifs = {
|
||||
hostname: string
|
||||
path: string
|
||||
username: string
|
||||
password: string | null
|
||||
}
|
||||
5
sdk/base/lib/osBindings/ClearActionRequestsParams.ts
Normal file
5
sdk/base/lib/osBindings/ClearActionRequestsParams.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ClearActionRequestsParams =
|
||||
| { only: string[] }
|
||||
| { except: string[] }
|
||||
4
sdk/base/lib/osBindings/ClearActionsParams.ts
Normal file
4
sdk/base/lib/osBindings/ClearActionsParams.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ActionId } from "./ActionId"
|
||||
|
||||
export type ClearActionsParams = { except: Array<ActionId> }
|
||||
4
sdk/base/lib/osBindings/ClearBindingsParams.ts
Normal file
4
sdk/base/lib/osBindings/ClearBindingsParams.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { BindId } from "./BindId"
|
||||
|
||||
export type ClearBindingsParams = { except: Array<BindId> }
|
||||
3
sdk/base/lib/osBindings/ClearCallbacksParams.ts
Normal file
3
sdk/base/lib/osBindings/ClearCallbacksParams.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ClearCallbacksParams = { only: number[] } | { except: number[] }
|
||||
4
sdk/base/lib/osBindings/ClearServiceInterfacesParams.ts
Normal file
4
sdk/base/lib/osBindings/ClearServiceInterfacesParams.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ServiceInterfaceId } from "./ServiceInterfaceId"
|
||||
|
||||
export type ClearServiceInterfacesParams = { except: Array<ServiceInterfaceId> }
|
||||
6
sdk/base/lib/osBindings/ContactInfo.ts
Normal file
6
sdk/base/lib/osBindings/ContactInfo.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ContactInfo =
|
||||
| { email: string }
|
||||
| { matrix: string }
|
||||
| { website: string }
|
||||
7
sdk/base/lib/osBindings/CreateSubcontainerFsParams.ts
Normal file
7
sdk/base/lib/osBindings/CreateSubcontainerFsParams.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ImageId } from "./ImageId"
|
||||
|
||||
export type CreateSubcontainerFsParams = {
|
||||
imageId: ImageId
|
||||
name: string | null
|
||||
}
|
||||
5
sdk/base/lib/osBindings/CurrentDependencies.ts
Normal file
5
sdk/base/lib/osBindings/CurrentDependencies.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { CurrentDependencyInfo } from "./CurrentDependencyInfo"
|
||||
import type { PackageId } from "./PackageId"
|
||||
|
||||
export type CurrentDependencies = { [key: PackageId]: CurrentDependencyInfo }
|
||||
8
sdk/base/lib/osBindings/CurrentDependencyInfo.ts
Normal file
8
sdk/base/lib/osBindings/CurrentDependencyInfo.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { DataUrl } from "./DataUrl"
|
||||
|
||||
export type CurrentDependencyInfo = {
|
||||
title: string | null
|
||||
icon: DataUrl | null
|
||||
versionRange: string
|
||||
} & ({ kind: "exists" } | { kind: "running"; healthChecks: string[] })
|
||||
3
sdk/base/lib/osBindings/DataUrl.ts
Normal file
3
sdk/base/lib/osBindings/DataUrl.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type DataUrl = string
|
||||
8
sdk/base/lib/osBindings/DepInfo.ts
Normal file
8
sdk/base/lib/osBindings/DepInfo.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { PathOrUrl } from "./PathOrUrl"
|
||||
|
||||
export type DepInfo = {
|
||||
description: string | null
|
||||
optional: boolean
|
||||
s9pk: PathOrUrl | null
|
||||
}
|
||||
5
sdk/base/lib/osBindings/Dependencies.ts
Normal file
5
sdk/base/lib/osBindings/Dependencies.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { DepInfo } from "./DepInfo"
|
||||
import type { PackageId } from "./PackageId"
|
||||
|
||||
export type Dependencies = { [key: PackageId]: DepInfo }
|
||||
3
sdk/base/lib/osBindings/DependencyKind.ts
Normal file
3
sdk/base/lib/osBindings/DependencyKind.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type DependencyKind = "exists" | "running"
|
||||
9
sdk/base/lib/osBindings/DependencyMetadata.ts
Normal file
9
sdk/base/lib/osBindings/DependencyMetadata.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { DataUrl } from "./DataUrl"
|
||||
|
||||
export type DependencyMetadata = {
|
||||
title: string | null
|
||||
icon: DataUrl | null
|
||||
description: string | null
|
||||
optional: boolean
|
||||
}
|
||||
12
sdk/base/lib/osBindings/DependencyRequirement.ts
Normal file
12
sdk/base/lib/osBindings/DependencyRequirement.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { HealthCheckId } from "./HealthCheckId"
|
||||
import type { PackageId } from "./PackageId"
|
||||
|
||||
export type DependencyRequirement =
|
||||
| {
|
||||
kind: "running"
|
||||
id: PackageId
|
||||
healthChecks: Array<HealthCheckId>
|
||||
versionRange: string
|
||||
}
|
||||
| { kind: "exists"; id: PackageId; versionRange: string }
|
||||
3
sdk/base/lib/osBindings/Description.ts
Normal file
3
sdk/base/lib/osBindings/Description.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type Description = { short: string; long: string }
|
||||
4
sdk/base/lib/osBindings/DestroySubcontainerFsParams.ts
Normal file
4
sdk/base/lib/osBindings/DestroySubcontainerFsParams.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Guid } from "./Guid"
|
||||
|
||||
export type DestroySubcontainerFsParams = { guid: Guid }
|
||||
3
sdk/base/lib/osBindings/Duration.ts
Normal file
3
sdk/base/lib/osBindings/Duration.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type Duration = string
|
||||
3
sdk/base/lib/osBindings/EchoParams.ts
Normal file
3
sdk/base/lib/osBindings/EchoParams.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type EchoParams = { message: string }
|
||||
3
sdk/base/lib/osBindings/EncryptedWire.ts
Normal file
3
sdk/base/lib/osBindings/EncryptedWire.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type EncryptedWire = { encrypted: any }
|
||||
5
sdk/base/lib/osBindings/ExportActionParams.ts
Normal file
5
sdk/base/lib/osBindings/ExportActionParams.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ActionId } from "./ActionId"
|
||||
import type { ActionMetadata } from "./ActionMetadata"
|
||||
|
||||
export type ExportActionParams = { id: ActionId; metadata: ActionMetadata }
|
||||
14
sdk/base/lib/osBindings/ExportServiceInterfaceParams.ts
Normal file
14
sdk/base/lib/osBindings/ExportServiceInterfaceParams.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AddressInfo } from "./AddressInfo"
|
||||
import type { ServiceInterfaceId } from "./ServiceInterfaceId"
|
||||
import type { ServiceInterfaceType } from "./ServiceInterfaceType"
|
||||
|
||||
export type ExportServiceInterfaceParams = {
|
||||
id: ServiceInterfaceId
|
||||
name: string
|
||||
description: string
|
||||
hasPrimary: boolean
|
||||
masked: boolean
|
||||
addressInfo: AddressInfo
|
||||
type: ServiceInterfaceType
|
||||
}
|
||||
3
sdk/base/lib/osBindings/ExposeForDependentsParams.ts
Normal file
3
sdk/base/lib/osBindings/ExposeForDependentsParams.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ExposeForDependentsParams = { paths: string[] }
|
||||
14
sdk/base/lib/osBindings/FullIndex.ts
Normal file
14
sdk/base/lib/osBindings/FullIndex.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { DataUrl } from "./DataUrl"
|
||||
import type { Guid } from "./Guid"
|
||||
import type { OsIndex } from "./OsIndex"
|
||||
import type { PackageIndex } from "./PackageIndex"
|
||||
import type { SignerInfo } from "./SignerInfo"
|
||||
|
||||
export type FullIndex = {
|
||||
name: string | null
|
||||
icon: DataUrl | null
|
||||
package: PackageIndex
|
||||
os: OsIndex
|
||||
signers: { [key: Guid]: SignerInfo }
|
||||
}
|
||||
5
sdk/base/lib/osBindings/FullProgress.ts
Normal file
5
sdk/base/lib/osBindings/FullProgress.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { NamedProgress } from "./NamedProgress"
|
||||
import type { Progress } from "./Progress"
|
||||
|
||||
export type FullProgress = { overall: Progress; phases: Array<NamedProgress> }
|
||||
5
sdk/base/lib/osBindings/GetActionInputParams.ts
Normal file
5
sdk/base/lib/osBindings/GetActionInputParams.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ActionId } from "./ActionId"
|
||||
import type { PackageId } from "./PackageId"
|
||||
|
||||
export type GetActionInputParams = { packageId?: PackageId; actionId: ActionId }
|
||||
10
sdk/base/lib/osBindings/GetHostInfoParams.ts
Normal file
10
sdk/base/lib/osBindings/GetHostInfoParams.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { CallbackId } from "./CallbackId"
|
||||
import type { HostId } from "./HostId"
|
||||
import type { PackageId } from "./PackageId"
|
||||
|
||||
export type GetHostInfoParams = {
|
||||
hostId: HostId
|
||||
packageId?: PackageId
|
||||
callback?: CallbackId
|
||||
}
|
||||
3
sdk/base/lib/osBindings/GetOsAssetParams.ts
Normal file
3
sdk/base/lib/osBindings/GetOsAssetParams.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type GetOsAssetParams = { version: string; platform: string }
|
||||
8
sdk/base/lib/osBindings/GetOsVersionParams.ts
Normal file
8
sdk/base/lib/osBindings/GetOsVersionParams.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type GetOsVersionParams = {
|
||||
source: string | null
|
||||
target: string | null
|
||||
serverId: string | null
|
||||
arch: string | null
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user