mirror of
https://github.com/Start9Labs/start-sdk.git
synced 2026-03-30 20:24:47 +00:00
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
{
|
{
|
||||||
startSdk: "start-sdk/lib",
|
StartSdk: "start-sdk/lib",
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
487
lib/StartSdk.ts
Normal file
487
lib/StartSdk.ts
Normal file
@@ -0,0 +1,487 @@
|
|||||||
|
import { ManifestVersion, SDKManifest } from "./manifest/ManifestTypes"
|
||||||
|
import { RequiredDefault, Value } from "./config/builder/value"
|
||||||
|
import { Config, ExtractConfigType, LazyBuild } from "./config/builder/config"
|
||||||
|
import {
|
||||||
|
DefaultString,
|
||||||
|
ListValueSpecText,
|
||||||
|
Pattern,
|
||||||
|
RandomString,
|
||||||
|
UniqueBy,
|
||||||
|
ValueSpecDatetime,
|
||||||
|
ValueSpecText,
|
||||||
|
} from "./config/configTypes"
|
||||||
|
import { Variants } from "./config/builder/variants"
|
||||||
|
import { CreatedAction, createAction } from "./actions/createAction"
|
||||||
|
import {
|
||||||
|
ActionMetadata,
|
||||||
|
Effects,
|
||||||
|
ActionResult,
|
||||||
|
Metadata,
|
||||||
|
BackupOptions,
|
||||||
|
DeepPartial,
|
||||||
|
} from "./types"
|
||||||
|
import { Utils } from "./util/utils"
|
||||||
|
import { DependencyConfig } from "./dependencyConfig/DependencyConfig"
|
||||||
|
import { BackupSet, Backups } from "./backup/Backups"
|
||||||
|
import { smtpConfig } from "./config/configConstants"
|
||||||
|
import { Daemons } from "./mainFn/Daemons"
|
||||||
|
import { healthCheck } from "./health/HealthCheck"
|
||||||
|
import {
|
||||||
|
checkPortListening,
|
||||||
|
containsAddress,
|
||||||
|
} from "./health/checkFns/checkPortListening"
|
||||||
|
import { checkWebUrl, runHealthScript } from "./health/checkFns"
|
||||||
|
import { List } from "./config/builder/list"
|
||||||
|
import { Migration } from "./inits/migrations/Migration"
|
||||||
|
import { Install, InstallFn, setupInstall } from "./inits/setupInstall"
|
||||||
|
import { setupActions } from "./actions/setupActions"
|
||||||
|
import { setupDependencyConfig } from "./dependencyConfig/setupDependencyConfig"
|
||||||
|
import { SetupBackupsParams, setupBackups } from "./backup/setupBackups"
|
||||||
|
import { setupInit } from "./inits/setupInit"
|
||||||
|
import {
|
||||||
|
EnsureUniqueId,
|
||||||
|
Migrations,
|
||||||
|
setupMigrations,
|
||||||
|
} from "./inits/migrations/setupMigrations"
|
||||||
|
import { Uninstall, UninstallFn, setupUninstall } from "./inits/setupUninstall"
|
||||||
|
import { setupMain } from "./mainFn"
|
||||||
|
import { defaultTrigger } from "./trigger/defaultTrigger"
|
||||||
|
import { changeOnFirstSuccess, cooldownTrigger } from "./trigger"
|
||||||
|
import setupConfig, { Read, Save } from "./config/setupConfig"
|
||||||
|
import { setupDependencyMounts } from "./dependency/setupDependencyMounts"
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
type AnyNeverCond<T extends any[], Then, Else> =
|
||||||
|
T extends [] ? Else :
|
||||||
|
T extends [never, ...Array<any>] ? Then :
|
||||||
|
T extends [any, ...infer U] ? AnyNeverCond<U,Then, Else> :
|
||||||
|
never
|
||||||
|
|
||||||
|
export class StartSdk<Manifest extends SDKManifest, Store, Vault> {
|
||||||
|
private constructor(readonly manifest: Manifest) {}
|
||||||
|
static of() {
|
||||||
|
return new StartSdk<never, never, never>(null as never)
|
||||||
|
}
|
||||||
|
withManifest<Manifest extends SDKManifest = never>(manifest: Manifest) {
|
||||||
|
return new StartSdk<Manifest, Store, Vault>(manifest)
|
||||||
|
}
|
||||||
|
withStore<Store extends Record<string, any>>() {
|
||||||
|
return new StartSdk<Manifest, Store, Vault>(this.manifest)
|
||||||
|
}
|
||||||
|
withVault<Vault extends Record<string, string>>() {
|
||||||
|
return new StartSdk<Manifest, Store, Vault>(this.manifest)
|
||||||
|
}
|
||||||
|
|
||||||
|
build(
|
||||||
|
isReady: AnyNeverCond<[Manifest, Store, Vault], "Build not ready", true>,
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
configConstants: { smtpConfig },
|
||||||
|
createAction: <
|
||||||
|
Store,
|
||||||
|
ConfigType extends
|
||||||
|
| Record<string, any>
|
||||||
|
| Config<any, any, any>
|
||||||
|
| Config<any, never, never>,
|
||||||
|
Type extends Record<string, any> = ExtractConfigType<ConfigType>,
|
||||||
|
>(
|
||||||
|
metaData: Omit<ActionMetadata, "input"> & {
|
||||||
|
input: Config<Type, Store, Vault> | Config<Type, never, never>
|
||||||
|
},
|
||||||
|
fn: (options: {
|
||||||
|
effects: Effects
|
||||||
|
utils: Utils<Store, Vault>
|
||||||
|
input: Type
|
||||||
|
}) => Promise<ActionResult>,
|
||||||
|
) => createAction<Store, Vault, ConfigType, Type>(metaData, fn),
|
||||||
|
healthCheck: {
|
||||||
|
checkPortListening,
|
||||||
|
checkWebUrl,
|
||||||
|
of: healthCheck,
|
||||||
|
runHealthScript,
|
||||||
|
},
|
||||||
|
setupActions: (...createdActions: CreatedAction<any, any, any>[]) =>
|
||||||
|
setupActions<Store, Vault>(...createdActions),
|
||||||
|
setupBackups: (...args: SetupBackupsParams<Manifest>) =>
|
||||||
|
setupBackups<Manifest>(...args),
|
||||||
|
setupConfig: <
|
||||||
|
ConfigType extends
|
||||||
|
| Config<any, Store, Vault>
|
||||||
|
| Config<any, never, never>,
|
||||||
|
Type extends Record<string, any> = ExtractConfigType<ConfigType>,
|
||||||
|
>(
|
||||||
|
spec: ConfigType,
|
||||||
|
write: Save<Store, Vault, Type, Manifest>,
|
||||||
|
read: Read<Store, Vault, Type>,
|
||||||
|
) =>
|
||||||
|
setupConfig<Store, Vault, ConfigType, Manifest, Type>(
|
||||||
|
spec,
|
||||||
|
write,
|
||||||
|
read,
|
||||||
|
),
|
||||||
|
setupConfigRead: <
|
||||||
|
ConfigSpec extends
|
||||||
|
| Config<Record<string, any>, any, any>
|
||||||
|
| Config<Record<string, never>, never, never>,
|
||||||
|
>(
|
||||||
|
_configSpec: ConfigSpec,
|
||||||
|
fn: Read<Store, Vault, ConfigSpec>,
|
||||||
|
) => fn,
|
||||||
|
setupConfigSave: <
|
||||||
|
ConfigSpec extends
|
||||||
|
| Config<Record<string, any>, any, any>
|
||||||
|
| Config<Record<string, never>, never, never>,
|
||||||
|
>(
|
||||||
|
_configSpec: ConfigSpec,
|
||||||
|
fn: Save<Store, Vault, ConfigSpec, Manifest>,
|
||||||
|
) => fn,
|
||||||
|
setupDependencyConfig: <Input extends Record<string, any>>(
|
||||||
|
config: Config<Input, Store, Vault>,
|
||||||
|
autoConfigs: {
|
||||||
|
[K in keyof Manifest["dependencies"]]: DependencyConfig<
|
||||||
|
Store,
|
||||||
|
Vault,
|
||||||
|
Input,
|
||||||
|
any
|
||||||
|
>
|
||||||
|
},
|
||||||
|
) =>
|
||||||
|
setupDependencyConfig<Store, Vault, Input, Manifest>(
|
||||||
|
config,
|
||||||
|
autoConfigs,
|
||||||
|
),
|
||||||
|
setupDependencyMounts,
|
||||||
|
setupInit: (
|
||||||
|
migrations: Migrations<Store, Vault>,
|
||||||
|
install: Install<Store, Vault>,
|
||||||
|
uninstall: Uninstall<Store, Vault>,
|
||||||
|
) => setupInit<Store, Vault>(migrations, install, uninstall),
|
||||||
|
setupInstall: (fn: InstallFn<Store, Vault>) => Install.of(fn),
|
||||||
|
setupMain: (
|
||||||
|
fn: (o: {
|
||||||
|
effects: Effects
|
||||||
|
started(onTerm: () => void): null
|
||||||
|
utils: Utils<Store, Vault, {}>
|
||||||
|
}) => Promise<Daemons<any>>,
|
||||||
|
) => setupMain<Store, Vault>(fn),
|
||||||
|
setupMigrations: <Migrations extends Array<Migration<Store, Vault, any>>>(
|
||||||
|
...migrations: EnsureUniqueId<Migrations>
|
||||||
|
) =>
|
||||||
|
setupMigrations<Store, Vault, Migrations>(this.manifest, ...migrations),
|
||||||
|
setupUninstall: (fn: UninstallFn<Store, Vault>) =>
|
||||||
|
setupUninstall<Store, Vault>(fn),
|
||||||
|
trigger: {
|
||||||
|
defaultTrigger,
|
||||||
|
cooldownTrigger,
|
||||||
|
changeOnFirstSuccess,
|
||||||
|
},
|
||||||
|
|
||||||
|
Backups: {
|
||||||
|
volumes: (...volumeNames: Array<keyof Manifest["volumes"] & string>) =>
|
||||||
|
Backups.volumes<Manifest>(...volumeNames),
|
||||||
|
addSets: (
|
||||||
|
...options: BackupSet<keyof Manifest["volumes"] & string>[]
|
||||||
|
) => Backups.addSets<Manifest>(...options),
|
||||||
|
withOptions: (options?: Partial<BackupOptions>) =>
|
||||||
|
Backups.with_options<Manifest>(options),
|
||||||
|
},
|
||||||
|
Config: {
|
||||||
|
of: <
|
||||||
|
Spec extends Record<
|
||||||
|
string,
|
||||||
|
Value<any, Store, Vault> | Value<any, never, never>
|
||||||
|
>,
|
||||||
|
>(
|
||||||
|
spec: Spec,
|
||||||
|
) => Config.of<Spec, Store, Vault>(spec),
|
||||||
|
},
|
||||||
|
Daemons: { of: Daemons.of },
|
||||||
|
DependencyConfig: {
|
||||||
|
of<
|
||||||
|
LocalConfig extends Record<string, any>,
|
||||||
|
RemoteConfig extends Record<string, any>,
|
||||||
|
>({
|
||||||
|
localConfig,
|
||||||
|
remoteConfig,
|
||||||
|
dependencyConfig,
|
||||||
|
}: {
|
||||||
|
localConfig: Config<LocalConfig, Store, Vault>
|
||||||
|
remoteConfig: Config<RemoteConfig, any, any>
|
||||||
|
dependencyConfig: (options: {
|
||||||
|
effects: Effects
|
||||||
|
localConfig: LocalConfig
|
||||||
|
remoteConfig: RemoteConfig
|
||||||
|
utils: Utils<Store, Vault>
|
||||||
|
}) => Promise<void | DeepPartial<RemoteConfig>>
|
||||||
|
}) {
|
||||||
|
return new DependencyConfig<Store, Vault, LocalConfig, RemoteConfig>(
|
||||||
|
dependencyConfig,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
List: {
|
||||||
|
text: List.text,
|
||||||
|
number: List.number,
|
||||||
|
obj: <Type extends Record<string, any>>(
|
||||||
|
a: {
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
warning?: string | null
|
||||||
|
/** Default [] */
|
||||||
|
default?: []
|
||||||
|
minLength?: number | null
|
||||||
|
maxLength?: number | null
|
||||||
|
},
|
||||||
|
aSpec: {
|
||||||
|
spec: Config<Type, Store, Vault>
|
||||||
|
displayAs?: null | string
|
||||||
|
uniqueBy?: null | UniqueBy
|
||||||
|
},
|
||||||
|
) => List.obj<Type, Store, Vault>(a, aSpec),
|
||||||
|
dynamicText: (
|
||||||
|
getA: LazyBuild<
|
||||||
|
Store,
|
||||||
|
Vault,
|
||||||
|
{
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
warning?: string | null
|
||||||
|
/** Default = [] */
|
||||||
|
default?: string[]
|
||||||
|
minLength?: number | null
|
||||||
|
maxLength?: number | null
|
||||||
|
disabled?: false | string
|
||||||
|
generate?: null | RandomString
|
||||||
|
spec: {
|
||||||
|
/** Default = false */
|
||||||
|
masked?: boolean
|
||||||
|
placeholder?: string | null
|
||||||
|
minLength?: number | null
|
||||||
|
maxLength?: number | null
|
||||||
|
patterns: Pattern[]
|
||||||
|
/** Default = "text" */
|
||||||
|
inputmode?: ListValueSpecText["inputmode"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>,
|
||||||
|
) => List.dynamicText<Store, Vault>(getA),
|
||||||
|
dynamicNumber: (
|
||||||
|
getA: LazyBuild<
|
||||||
|
Store,
|
||||||
|
Vault,
|
||||||
|
{
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
warning?: string | null
|
||||||
|
/** Default = [] */
|
||||||
|
default?: string[]
|
||||||
|
minLength?: number | null
|
||||||
|
maxLength?: number | null
|
||||||
|
disabled?: false | string
|
||||||
|
spec: {
|
||||||
|
integer: boolean
|
||||||
|
min?: number | null
|
||||||
|
max?: number | null
|
||||||
|
step?: string | null
|
||||||
|
units?: string | null
|
||||||
|
placeholder?: string | null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>,
|
||||||
|
) => List.dynamicNumber<Store, Vault>(getA),
|
||||||
|
},
|
||||||
|
Migration: {
|
||||||
|
of: <Version extends ManifestVersion>(options: {
|
||||||
|
version: Version
|
||||||
|
up: (opts: {
|
||||||
|
effects: Effects
|
||||||
|
utils: Utils<Store, Vault>
|
||||||
|
}) => Promise<void>
|
||||||
|
down: (opts: {
|
||||||
|
effects: Effects
|
||||||
|
utils: Utils<Store, Vault>
|
||||||
|
}) => Promise<void>
|
||||||
|
}) => Migration.of<Store, Vault, Version>(options),
|
||||||
|
},
|
||||||
|
Value: {
|
||||||
|
toggle: Value.toggle,
|
||||||
|
text: Value.text,
|
||||||
|
textarea: Value.textarea,
|
||||||
|
number: Value.number,
|
||||||
|
color: Value.color,
|
||||||
|
datetime: Value.datetime,
|
||||||
|
select: Value.select,
|
||||||
|
multiselect: Value.multiselect,
|
||||||
|
object: Value.object,
|
||||||
|
union: Value.union,
|
||||||
|
list: Value.list,
|
||||||
|
dynamicToggle: (
|
||||||
|
a: LazyBuild<
|
||||||
|
Store,
|
||||||
|
Vault,
|
||||||
|
{
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
warning?: string | null
|
||||||
|
default: boolean
|
||||||
|
disabled?: false | string
|
||||||
|
}
|
||||||
|
>,
|
||||||
|
) => Value.dynamicToggle<Store, Vault>(a),
|
||||||
|
dynamicText: (
|
||||||
|
getA: LazyBuild<
|
||||||
|
Store,
|
||||||
|
Vault,
|
||||||
|
{
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
warning?: string | null
|
||||||
|
required: RequiredDefault<DefaultString>
|
||||||
|
|
||||||
|
/** Default = false */
|
||||||
|
masked?: boolean
|
||||||
|
placeholder?: string | null
|
||||||
|
minLength?: number | null
|
||||||
|
maxLength?: number | null
|
||||||
|
patterns?: Pattern[]
|
||||||
|
/** Default = 'text' */
|
||||||
|
inputmode?: ValueSpecText["inputmode"]
|
||||||
|
generate?: null | RandomString
|
||||||
|
}
|
||||||
|
>,
|
||||||
|
) => Value.dynamicText<Store, Vault>(getA),
|
||||||
|
dynamicTextarea: (
|
||||||
|
getA: LazyBuild<
|
||||||
|
Store,
|
||||||
|
Vault,
|
||||||
|
{
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
warning?: string | null
|
||||||
|
required: boolean
|
||||||
|
minLength?: number | null
|
||||||
|
maxLength?: number | null
|
||||||
|
placeholder?: string | null
|
||||||
|
disabled?: false | string
|
||||||
|
generate?: null | RandomString
|
||||||
|
}
|
||||||
|
>,
|
||||||
|
) => Value.dynamicTextarea<Store, Vault>(getA),
|
||||||
|
dynamicNumber: (
|
||||||
|
getA: LazyBuild<
|
||||||
|
Store,
|
||||||
|
Vault,
|
||||||
|
{
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
warning?: string | null
|
||||||
|
required: RequiredDefault<number>
|
||||||
|
min?: number | null
|
||||||
|
max?: number | null
|
||||||
|
/** Default = '1' */
|
||||||
|
step?: string | null
|
||||||
|
integer: boolean
|
||||||
|
units?: string | null
|
||||||
|
placeholder?: string | null
|
||||||
|
disabled?: false | string
|
||||||
|
}
|
||||||
|
>,
|
||||||
|
) => Value.dynamicNumber<Store, Vault>(getA),
|
||||||
|
dynamicColor: (
|
||||||
|
getA: LazyBuild<
|
||||||
|
Store,
|
||||||
|
Vault,
|
||||||
|
{
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
warning?: string | null
|
||||||
|
required: RequiredDefault<string>
|
||||||
|
|
||||||
|
disabled?: false | string
|
||||||
|
}
|
||||||
|
>,
|
||||||
|
) => Value.dynamicColor<Store, Vault>(getA),
|
||||||
|
dynamicDatetime: (
|
||||||
|
getA: LazyBuild<
|
||||||
|
Store,
|
||||||
|
Vault,
|
||||||
|
{
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
warning?: string | null
|
||||||
|
required: RequiredDefault<string>
|
||||||
|
/** Default = 'datetime-local' */
|
||||||
|
inputmode?: ValueSpecDatetime["inputmode"]
|
||||||
|
min?: string | null
|
||||||
|
max?: string | null
|
||||||
|
step?: string | null
|
||||||
|
disabled?: false | string
|
||||||
|
}
|
||||||
|
>,
|
||||||
|
) => Value.dynamicDatetime<Store, Vault>(getA),
|
||||||
|
dynamicSelect: (
|
||||||
|
getA: LazyBuild<
|
||||||
|
Store,
|
||||||
|
Vault,
|
||||||
|
{
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
warning?: string | null
|
||||||
|
required: RequiredDefault<string>
|
||||||
|
values: Record<string, string>
|
||||||
|
disabled?: false | string
|
||||||
|
}
|
||||||
|
>,
|
||||||
|
) => Value.dynamicSelect<Store, Vault>(getA),
|
||||||
|
dynamicMultiselect: (
|
||||||
|
getA: LazyBuild<
|
||||||
|
Store,
|
||||||
|
Vault,
|
||||||
|
{
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
warning?: string | null
|
||||||
|
default: string[]
|
||||||
|
values: Record<string, string>
|
||||||
|
minLength?: number | null
|
||||||
|
maxLength?: number | null
|
||||||
|
disabled?: false | string
|
||||||
|
}
|
||||||
|
>,
|
||||||
|
) => Value.dynamicMultiselect<Store, Vault>(getA),
|
||||||
|
filteredUnion: <
|
||||||
|
Required extends RequiredDefault<string>,
|
||||||
|
Type extends Record<string, any>,
|
||||||
|
>(
|
||||||
|
getDisabledFn: LazyBuild<Store, Vault, string[]>,
|
||||||
|
a: {
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
warning?: string | null
|
||||||
|
required: Required
|
||||||
|
},
|
||||||
|
aVariants:
|
||||||
|
| Variants<Type, Store, Vault>
|
||||||
|
| Variants<Type, never, never>,
|
||||||
|
) =>
|
||||||
|
Value.filteredUnion<Required, Type, Store, Vault>(
|
||||||
|
getDisabledFn,
|
||||||
|
a,
|
||||||
|
aVariants,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Variants: {
|
||||||
|
of: <
|
||||||
|
VariantValues extends {
|
||||||
|
[K in string]: {
|
||||||
|
name: string
|
||||||
|
spec: Config<any, Store, Vault>
|
||||||
|
}
|
||||||
|
},
|
||||||
|
>(
|
||||||
|
a: VariantValues,
|
||||||
|
) => Variants.of<VariantValues, Store, Vault>(a),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,51 +1,58 @@
|
|||||||
import { Config, ExtractConfigType } from "../config/builder/config"
|
import { Config, ExtractConfigType } from "../config/builder/config"
|
||||||
import { ActionMetaData, ActionResult, Effects, ExportedAction } from "../types"
|
import { ActionMetadata, ActionResult, Effects, ExportedAction } from "../types"
|
||||||
import { Utils, utils } from "../util"
|
import { createUtils } from "../util"
|
||||||
|
import { Utils, utils } from "../util/utils"
|
||||||
|
|
||||||
export class CreatedAction<
|
export class CreatedAction<
|
||||||
WrapperData,
|
Store,
|
||||||
|
Vault,
|
||||||
ConfigType extends
|
ConfigType extends
|
||||||
| Record<string, any>
|
| Record<string, any>
|
||||||
| Config<any, WrapperData>
|
| Config<any, Store, Vault>
|
||||||
| Config<any, never>,
|
| Config<any, never, never>,
|
||||||
Type extends Record<string, any> = ExtractConfigType<ConfigType>,
|
Type extends Record<string, any> = ExtractConfigType<ConfigType>,
|
||||||
> {
|
> {
|
||||||
private constructor(
|
private constructor(
|
||||||
public readonly myMetaData: ActionMetaData,
|
public readonly myMetaData: Omit<ActionMetadata, "input">,
|
||||||
readonly fn: (options: {
|
readonly fn: (options: {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
utils: Utils<WrapperData>
|
utils: Utils<Store, Vault>
|
||||||
input: Type
|
input: Type
|
||||||
}) => Promise<ActionResult>,
|
}) => Promise<ActionResult>,
|
||||||
readonly input: Config<Type, WrapperData> | Config<Type, never>,
|
readonly input: Config<Type, Store, Vault>,
|
||||||
) {}
|
) {}
|
||||||
public validator = this.input.validator
|
public validator = this.input.validator
|
||||||
|
|
||||||
static of<
|
static of<
|
||||||
WrapperData,
|
Store,
|
||||||
|
Vault,
|
||||||
ConfigType extends
|
ConfigType extends
|
||||||
| Record<string, any>
|
| Record<string, any>
|
||||||
| Config<any, any>
|
| Config<any, any, any>
|
||||||
| Config<any, never>,
|
| Config<any, never, never>,
|
||||||
Type extends Record<string, any> = ExtractConfigType<ConfigType>,
|
Type extends Record<string, any> = ExtractConfigType<ConfigType>,
|
||||||
>(
|
>(
|
||||||
metaData: Omit<ActionMetaData, "input"> & {
|
metaData: Omit<ActionMetadata, "input"> & {
|
||||||
input: Config<Type, WrapperData> | Config<Type, never>
|
input: Config<Type, Store, Vault> | Config<Type, never, never>
|
||||||
},
|
},
|
||||||
fn: (options: {
|
fn: (options: {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
utils: Utils<WrapperData>
|
utils: Utils<Store, Vault>
|
||||||
input: Type
|
input: Type
|
||||||
}) => Promise<ActionResult>,
|
}) => Promise<ActionResult>,
|
||||||
) {
|
) {
|
||||||
const { input, ...rest } = metaData
|
const { input, ...rest } = metaData
|
||||||
return new CreatedAction<WrapperData, ConfigType, Type>(rest, fn, input)
|
return new CreatedAction<Store, Vault, ConfigType, Type>(
|
||||||
|
rest,
|
||||||
|
fn,
|
||||||
|
input as Config<Type, Store, Vault>,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
exportedAction: ExportedAction = ({ effects, input }) => {
|
exportedAction: ExportedAction = ({ effects, input }) => {
|
||||||
return this.fn({
|
return this.fn({
|
||||||
effects,
|
effects,
|
||||||
utils: utils<WrapperData>(effects),
|
utils: createUtils(effects),
|
||||||
input: this.validator.unsafeCast(input),
|
input: this.validator.unsafeCast(input),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -53,15 +60,25 @@ export class CreatedAction<
|
|||||||
run = async ({ effects, input }: { effects: Effects; input?: Type }) => {
|
run = async ({ effects, input }: { effects: Effects; input?: Type }) => {
|
||||||
return this.fn({
|
return this.fn({
|
||||||
effects,
|
effects,
|
||||||
utils: utils<WrapperData>(effects),
|
utils: createUtils(effects),
|
||||||
input: this.validator.unsafeCast(input),
|
input: this.validator.unsafeCast(input),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async ActionMetadata(options: {
|
||||||
|
effects: Effects
|
||||||
|
utils: Utils<Store, Vault>
|
||||||
|
}): Promise<ActionMetadata> {
|
||||||
|
return {
|
||||||
|
...this.myMetaData,
|
||||||
|
input: await this.input.build(options),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async getConfig({ effects }: { effects: Effects }) {
|
async getConfig({ effects }: { effects: Effects }) {
|
||||||
return this.input.build({
|
return this.input.build({
|
||||||
effects,
|
effects,
|
||||||
utils: utils<WrapperData>(effects) as any,
|
utils: createUtils(effects) as any,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { Effects, ExpectedExports, ExportedAction } from "../types"
|
import { Effects, ExpectedExports } from "../types"
|
||||||
import { ActionMetaData } from "../types"
|
import { createUtils } from "../util"
|
||||||
import { once } from "../util/once"
|
import { once } from "../util/once"
|
||||||
import { CreatedAction } from "./createAction"
|
import { CreatedAction } from "./createAction"
|
||||||
|
|
||||||
export function setupActions(...createdActions: CreatedAction<any, any>[]) {
|
export function setupActions<Store, Vault>(
|
||||||
|
...createdActions: CreatedAction<Store, Vault, any>[]
|
||||||
|
) {
|
||||||
const myActions = once(() => {
|
const myActions = once(() => {
|
||||||
const actions: Record<string, CreatedAction<any, any>> = {}
|
const actions: Record<string, CreatedAction<Store, Vault, any>> = {}
|
||||||
for (const action of createdActions) {
|
for (const action of createdActions) {
|
||||||
actions[action.myMetaData.id] = action
|
actions[action.myMetaData.id] = action
|
||||||
}
|
}
|
||||||
@@ -15,8 +17,14 @@ export function setupActions(...createdActions: CreatedAction<any, any>[]) {
|
|||||||
get actions() {
|
get actions() {
|
||||||
return myActions()
|
return myActions()
|
||||||
},
|
},
|
||||||
get actionsMetadata() {
|
async actionsMetadata({ effects }: { effects: Effects }) {
|
||||||
return createdActions.map((x) => x.myMetaData)
|
const utils = createUtils<Store, Vault>(effects)
|
||||||
|
return Promise.all(
|
||||||
|
createdActions.map((x) => x.ActionMetadata({ effects, utils })),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
|
} satisfies {
|
||||||
|
actions: ExpectedExports.actions
|
||||||
|
actionsMetadata: ExpectedExports.actionsMetadata
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
import { AutoConfigure, DeepPartial, Effects, ExpectedExports } from "../types"
|
|
||||||
import { Utils, utils } from "../util"
|
|
||||||
import { deepEqual } from "../util/deepEqual"
|
|
||||||
import { deepMerge } from "../util/deepMerge"
|
|
||||||
|
|
||||||
export type AutoConfigFrom<WD, Input, NestedConfigs> = {
|
|
||||||
[key in keyof NestedConfigs & string]: (options: {
|
|
||||||
effects: Effects
|
|
||||||
localConfig: Input
|
|
||||||
remoteConfig: NestedConfigs[key]
|
|
||||||
utils: Utils<WD>
|
|
||||||
}) => Promise<void | DeepPartial<NestedConfigs[key]>>
|
|
||||||
}
|
|
||||||
export class AutoConfig<WD, Input, NestedConfigs> {
|
|
||||||
constructor(
|
|
||||||
readonly configs: AutoConfigFrom<WD, Input, NestedConfigs>,
|
|
||||||
readonly path: keyof AutoConfigFrom<WD, Input, NestedConfigs>,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async check(
|
|
||||||
options: Parameters<AutoConfigure["check"]>[0],
|
|
||||||
): ReturnType<AutoConfigure["check"]> {
|
|
||||||
const origConfig = JSON.parse(JSON.stringify(options.localConfig))
|
|
||||||
const newOptions = {
|
|
||||||
...options,
|
|
||||||
utils: utils<WD>(options.effects),
|
|
||||||
localConfig: options.localConfig as Input,
|
|
||||||
remoteConfig: options.remoteConfig as any,
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
!deepEqual(
|
|
||||||
origConfig,
|
|
||||||
deepMerge(
|
|
||||||
{},
|
|
||||||
options.localConfig,
|
|
||||||
await this.configs[this.path](newOptions),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
throw new Error(`Check failed for ${this.path}`)
|
|
||||||
}
|
|
||||||
async autoConfigure(
|
|
||||||
options: Parameters<AutoConfigure["autoConfigure"]>[0],
|
|
||||||
): ReturnType<AutoConfigure["autoConfigure"]> {
|
|
||||||
const newOptions = {
|
|
||||||
...options,
|
|
||||||
utils: utils<WD>(options.effects),
|
|
||||||
localConfig: options.localConfig as Input,
|
|
||||||
remoteConfig: options.remoteConfig as any,
|
|
||||||
}
|
|
||||||
return deepMerge(
|
|
||||||
{},
|
|
||||||
options.localConfig,
|
|
||||||
await this.configs[this.path](newOptions),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { SDKManifest } from "../manifest/ManifestTypes"
|
|
||||||
import { AutoConfig, AutoConfigFrom } from "./AutoConfig"
|
|
||||||
|
|
||||||
export function setupAutoConfig<
|
|
||||||
WD,
|
|
||||||
Input,
|
|
||||||
Manifest extends SDKManifest,
|
|
||||||
NestedConfigs extends {
|
|
||||||
[key in keyof Manifest["dependencies"]]: unknown
|
|
||||||
},
|
|
||||||
>(configs: AutoConfigFrom<WD, Input, NestedConfigs>) {
|
|
||||||
type C = typeof configs
|
|
||||||
const answer = { ...configs } as unknown as {
|
|
||||||
[k in keyof C]: AutoConfig<WD, Input, NestedConfigs>
|
|
||||||
}
|
|
||||||
for (const key in configs) {
|
|
||||||
answer[key as keyof typeof configs] = new AutoConfig<
|
|
||||||
WD,
|
|
||||||
Input,
|
|
||||||
NestedConfigs
|
|
||||||
>(configs, key as keyof typeof configs)
|
|
||||||
}
|
|
||||||
return answer
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import { SDKManifest } from "../manifest/ManifestTypes"
|
import { SDKManifest } from "../manifest/ManifestTypes"
|
||||||
import * as T from "../types"
|
import * as T from "../types"
|
||||||
import fs from "fs"
|
|
||||||
|
|
||||||
export type BACKUP = "BACKUP"
|
export type BACKUP = "BACKUP"
|
||||||
export const DEFAULT_OPTIONS: T.BackupOptions = {
|
export const DEFAULT_OPTIONS: T.BackupOptions = {
|
||||||
@@ -9,7 +8,7 @@ export const DEFAULT_OPTIONS: T.BackupOptions = {
|
|||||||
ignoreExisting: false,
|
ignoreExisting: false,
|
||||||
exclude: [],
|
exclude: [],
|
||||||
}
|
}
|
||||||
type BackupSet<Volumes extends string> = {
|
export type BackupSet<Volumes extends string> = {
|
||||||
srcPath: string
|
srcPath: string
|
||||||
srcVolume: Volumes | BACKUP
|
srcVolume: Volumes | BACKUP
|
||||||
dstPath: string
|
dstPath: string
|
||||||
@@ -41,7 +40,7 @@ type BackupSet<Volumes extends string> = {
|
|||||||
export class Backups<M extends SDKManifest> {
|
export class Backups<M extends SDKManifest> {
|
||||||
static BACKUP: BACKUP = "BACKUP"
|
static BACKUP: BACKUP = "BACKUP"
|
||||||
|
|
||||||
constructor(
|
private constructor(
|
||||||
private options = DEFAULT_OPTIONS,
|
private options = DEFAULT_OPTIONS,
|
||||||
private backupSet = [] as BackupSet<keyof M["volumes"] & string>[],
|
private backupSet = [] as BackupSet<keyof M["volumes"] & string>[],
|
||||||
) {}
|
) {}
|
||||||
@@ -67,7 +66,9 @@ export class Backups<M extends SDKManifest> {
|
|||||||
) {
|
) {
|
||||||
return new Backups({ ...DEFAULT_OPTIONS, ...options })
|
return new Backups({ ...DEFAULT_OPTIONS, ...options })
|
||||||
}
|
}
|
||||||
set_options(options?: Partial<T.BackupOptions>) {
|
|
||||||
|
static withOptions = Backups.with_options
|
||||||
|
setOptions(options?: Partial<T.BackupOptions>) {
|
||||||
this.options = {
|
this.options = {
|
||||||
...this.options,
|
...this.options,
|
||||||
...options,
|
...options,
|
||||||
|
|||||||
@@ -1,28 +1,32 @@
|
|||||||
import { ValueSpec } from "../configTypes"
|
import { ValueSpec } from "../configTypes"
|
||||||
import { Utils } from "../../util"
|
import { Utils } from "../../util/utils"
|
||||||
import { Value } from "./value"
|
import { Value } from "./value"
|
||||||
import { _ } from "../../util"
|
import { _ } from "../../util"
|
||||||
import { Effects } from "../../types"
|
import { Effects } from "../../types"
|
||||||
import { Parser, object } from "ts-matches"
|
import { Parser, object } from "ts-matches"
|
||||||
|
|
||||||
export type LazyBuildOptions<WD> = {
|
export type LazyBuildOptions<Store, Vault> = {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
utils: Utils<WD>
|
utils: Utils<Store, Vault>
|
||||||
}
|
}
|
||||||
export type LazyBuild<WD, ExpectedOut> = (
|
export type LazyBuild<Store, Vault, ExpectedOut> = (
|
||||||
options: LazyBuildOptions<WD>,
|
options: LazyBuildOptions<Store, Vault>,
|
||||||
) => Promise<ExpectedOut> | ExpectedOut
|
) => Promise<ExpectedOut> | ExpectedOut
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
export type ExtractConfigType<A extends Record<string, any> | Config<Record<string, any>, any> | Config<Record<string, any>, never>> =
|
export type ExtractConfigType<A extends Record<string, any> | Config<Record<string, any>, any, any> | Config<Record<string, any>, never, never>> =
|
||||||
A extends Config<infer B, any> | Config<infer B, never> ? B :
|
A extends Config<infer B, any, any> | Config<infer B, never, never> ? B :
|
||||||
A
|
A
|
||||||
|
|
||||||
export type ConfigSpecOf<A extends Record<string, any>, WD = never> = {
|
export type ConfigSpecOf<
|
||||||
[K in keyof A]: Value<A[K], WD>
|
A extends Record<string, any>,
|
||||||
|
Store = never,
|
||||||
|
Vault = never,
|
||||||
|
> = {
|
||||||
|
[K in keyof A]: Value<A[K], Store, Vault>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MaybeLazyValues<A> = LazyBuild<any, A> | A
|
export type MaybeLazyValues<A> = LazyBuild<any, any, A> | A
|
||||||
/**
|
/**
|
||||||
* Configs are the specs that are used by the os configuration form for this service.
|
* Configs are the specs that are used by the os configuration form for this service.
|
||||||
* Here is an example of a simple configuration
|
* Here is an example of a simple configuration
|
||||||
@@ -79,14 +83,16 @@ export const addNodesSpec = Config.of({ hostname: hostname, port: port });
|
|||||||
|
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
export class Config<Type extends Record<string, any>, WD> {
|
export class Config<Type extends Record<string, any>, Store, Vault> {
|
||||||
private constructor(
|
private constructor(
|
||||||
private readonly spec: {
|
private readonly spec: {
|
||||||
[K in keyof Type]: Value<Type[K], WD> | Value<Type[K], never>
|
[K in keyof Type]:
|
||||||
|
| Value<Type[K], Store, Vault>
|
||||||
|
| Value<Type[K], never, never>
|
||||||
},
|
},
|
||||||
public validator: Parser<unknown, Type>,
|
public validator: Parser<unknown, Type>,
|
||||||
) {}
|
) {}
|
||||||
async build(options: LazyBuildOptions<WD>) {
|
async build(options: LazyBuildOptions<Store, Vault>) {
|
||||||
const answer = {} as {
|
const answer = {} as {
|
||||||
[K in keyof Type]: ValueSpec
|
[K in keyof Type]: ValueSpec
|
||||||
}
|
}
|
||||||
@@ -96,9 +102,14 @@ export class Config<Type extends Record<string, any>, WD> {
|
|||||||
return answer
|
return answer
|
||||||
}
|
}
|
||||||
|
|
||||||
static of<Spec extends Record<string, Value<any, any> | Value<any, never>>>(
|
static of<
|
||||||
spec: Spec,
|
Spec extends Record<
|
||||||
) {
|
string,
|
||||||
|
Value<any, Store, Vault> | Value<any, never, never>
|
||||||
|
>,
|
||||||
|
Store,
|
||||||
|
Vault,
|
||||||
|
>(spec: Spec) {
|
||||||
const validatorObj = {} as {
|
const validatorObj = {} as {
|
||||||
[K in keyof Spec]: Parser<unknown, any>
|
[K in keyof Spec]: Parser<unknown, any>
|
||||||
}
|
}
|
||||||
@@ -109,14 +120,13 @@ export class Config<Type extends Record<string, any>, WD> {
|
|||||||
return new Config<
|
return new Config<
|
||||||
{
|
{
|
||||||
[K in keyof Spec]: Spec[K] extends
|
[K in keyof Spec]: Spec[K] extends
|
||||||
| Value<infer T, any>
|
| Value<infer T, Store, Vault>
|
||||||
| Value<infer T, never>
|
| Value<infer T, never, never>
|
||||||
? T
|
? T
|
||||||
: never
|
: never
|
||||||
},
|
},
|
||||||
{
|
Store,
|
||||||
[K in keyof Spec]: Spec[K] extends Value<any, infer WD> ? WD : never
|
Vault
|
||||||
}[keyof Spec]
|
|
||||||
>(spec, validator as any)
|
>(spec, validator as any)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,12 +139,12 @@ export class Config<Type extends Record<string, any>, WD> {
|
|||||||
required: false,
|
required: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
return Config.of<WrapperData>()({
|
return Config.of<Store>()({
|
||||||
myValue: a.withWrapperData(),
|
myValue: a.withStore(),
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
withWrapperData<NewWrapperData extends WD extends never ? any : WD>() {
|
withStore<NewStore extends Store extends never ? any : Store>() {
|
||||||
return this as any as Config<Type, NewWrapperData>
|
return this as any as Config<Type, NewStore, Vault>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ export const authorizationList = List.string({
|
|||||||
export const auth = Value.list(authorizationList);
|
export const auth = Value.list(authorizationList);
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
export class List<Type, WD> {
|
export class List<Type, Store, Vault> {
|
||||||
private constructor(
|
private constructor(
|
||||||
public build: LazyBuild<WD, ValueSpecList>,
|
public build: LazyBuild<Store, Vault, ValueSpecList>,
|
||||||
public validator: Parser<unknown, Type>,
|
public validator: Parser<unknown, Type>,
|
||||||
) {}
|
) {}
|
||||||
static text(
|
static text(
|
||||||
@@ -49,7 +49,7 @@ export class List<Type, WD> {
|
|||||||
generate?: null | RandomString
|
generate?: null | RandomString
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
return new List<string[], never>(() => {
|
return new List<string[], never, never>(() => {
|
||||||
const spec = {
|
const spec = {
|
||||||
type: "text" as const,
|
type: "text" as const,
|
||||||
placeholder: null,
|
placeholder: null,
|
||||||
@@ -73,9 +73,10 @@ export class List<Type, WD> {
|
|||||||
} satisfies ValueSpecListOf<"text">
|
} satisfies ValueSpecListOf<"text">
|
||||||
}, arrayOf(string))
|
}, arrayOf(string))
|
||||||
}
|
}
|
||||||
static dynamicText<WD = never>(
|
static dynamicText<Store = never, Vault = never>(
|
||||||
getA: LazyBuild<
|
getA: LazyBuild<
|
||||||
WD,
|
Store,
|
||||||
|
Vault,
|
||||||
{
|
{
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
@@ -99,7 +100,7 @@ export class List<Type, WD> {
|
|||||||
}
|
}
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
return new List<string[], WD>(async (options) => {
|
return new List<string[], Store, Vault>(async (options) => {
|
||||||
const { spec: aSpec, ...a } = await getA(options)
|
const { spec: aSpec, ...a } = await getA(options)
|
||||||
const spec = {
|
const spec = {
|
||||||
type: "text" as const,
|
type: "text" as const,
|
||||||
@@ -143,7 +144,7 @@ export class List<Type, WD> {
|
|||||||
placeholder?: string | null
|
placeholder?: string | null
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
return new List<number[], never>(() => {
|
return new List<number[], never, never>(() => {
|
||||||
const spec = {
|
const spec = {
|
||||||
type: "number" as const,
|
type: "number" as const,
|
||||||
placeholder: null,
|
placeholder: null,
|
||||||
@@ -166,9 +167,10 @@ export class List<Type, WD> {
|
|||||||
} satisfies ValueSpecListOf<"number">
|
} satisfies ValueSpecListOf<"number">
|
||||||
}, arrayOf(number))
|
}, arrayOf(number))
|
||||||
}
|
}
|
||||||
static dynamicNumber<WD = never>(
|
static dynamicNumber<Store = never, Vault = never>(
|
||||||
getA: LazyBuild<
|
getA: LazyBuild<
|
||||||
WD,
|
Store,
|
||||||
|
Vault,
|
||||||
{
|
{
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
@@ -189,7 +191,7 @@ export class List<Type, WD> {
|
|||||||
}
|
}
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
return new List<number[], WD>(async (options) => {
|
return new List<number[], Store, Vault>(async (options) => {
|
||||||
const { spec: aSpec, ...a } = await getA(options)
|
const { spec: aSpec, ...a } = await getA(options)
|
||||||
const spec = {
|
const spec = {
|
||||||
type: "number" as const,
|
type: "number" as const,
|
||||||
@@ -213,7 +215,7 @@ export class List<Type, WD> {
|
|||||||
}
|
}
|
||||||
}, arrayOf(number))
|
}, arrayOf(number))
|
||||||
}
|
}
|
||||||
static obj<Type extends Record<string, any>, WrapperData>(
|
static obj<Type extends Record<string, any>, Store, Vault>(
|
||||||
a: {
|
a: {
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
@@ -224,12 +226,12 @@ export class List<Type, WD> {
|
|||||||
maxLength?: number | null
|
maxLength?: number | null
|
||||||
},
|
},
|
||||||
aSpec: {
|
aSpec: {
|
||||||
spec: Config<Type, WrapperData>
|
spec: Config<Type, Store, Vault>
|
||||||
displayAs?: null | string
|
displayAs?: null | string
|
||||||
uniqueBy?: null | UniqueBy
|
uniqueBy?: null | UniqueBy
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
return new List<Type[], WrapperData>(async (options) => {
|
return new List<Type[], Store, Vault>(async (options) => {
|
||||||
const { spec: previousSpecSpec, ...restSpec } = aSpec
|
const { spec: previousSpecSpec, ...restSpec } = aSpec
|
||||||
const specSpec = await previousSpecSpec.build(options)
|
const specSpec = await previousSpecSpec.build(options)
|
||||||
const spec = {
|
const spec = {
|
||||||
@@ -265,12 +267,12 @@ export class List<Type, WD> {
|
|||||||
required: false,
|
required: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
return Config.of<WrapperData>()({
|
return Config.of<Store>()({
|
||||||
myValue: a.withWrapperData(),
|
myValue: a.withStore(),
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
withWrapperData<NewWrapperData extends WD extends never ? any : WD>() {
|
withStore<NewStore extends Store extends never ? any : Store>() {
|
||||||
return this as any as List<Type, NewWrapperData>
|
return this as any as List<Type, NewStore, Vault>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
} from "ts-matches"
|
} from "ts-matches"
|
||||||
import { once } from "../../util/once"
|
import { once } from "../../util/once"
|
||||||
|
|
||||||
type RequiredDefault<A> =
|
export type RequiredDefault<A> =
|
||||||
| false
|
| false
|
||||||
| {
|
| {
|
||||||
default: A | null
|
default: A | null
|
||||||
@@ -94,9 +94,9 @@ const username = Value.string({
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
export class Value<Type, WD> {
|
export class Value<Type, Store, Vault> {
|
||||||
private constructor(
|
protected constructor(
|
||||||
public build: LazyBuild<WD, ValueSpec>,
|
public build: LazyBuild<Store, Vault, ValueSpec>,
|
||||||
public validator: Parser<unknown, Type>,
|
public validator: Parser<unknown, Type>,
|
||||||
) {}
|
) {}
|
||||||
static toggle(a: {
|
static toggle(a: {
|
||||||
@@ -108,7 +108,7 @@ export class Value<Type, WD> {
|
|||||||
Default is false */
|
Default is false */
|
||||||
immutable?: boolean
|
immutable?: boolean
|
||||||
}) {
|
}) {
|
||||||
return new Value<boolean, never>(
|
return new Value<boolean, never, never>(
|
||||||
async () => ({
|
async () => ({
|
||||||
description: null,
|
description: null,
|
||||||
warning: null,
|
warning: null,
|
||||||
@@ -120,9 +120,10 @@ export class Value<Type, WD> {
|
|||||||
boolean,
|
boolean,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
static dynamicToggle<WD = never>(
|
static dynamicToggle<Store = never, Vault = never>(
|
||||||
a: LazyBuild<
|
a: LazyBuild<
|
||||||
WD,
|
Store,
|
||||||
|
Vault,
|
||||||
{
|
{
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
@@ -132,7 +133,7 @@ export class Value<Type, WD> {
|
|||||||
}
|
}
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
return new Value<boolean, WD>(
|
return new Value<boolean, Store, Vault>(
|
||||||
async (options) => ({
|
async (options) => ({
|
||||||
description: null,
|
description: null,
|
||||||
warning: null,
|
warning: null,
|
||||||
@@ -163,7 +164,7 @@ export class Value<Type, WD> {
|
|||||||
immutable?: boolean
|
immutable?: boolean
|
||||||
generate?: null | RandomString
|
generate?: null | RandomString
|
||||||
}) {
|
}) {
|
||||||
return new Value<AsRequired<string, Required>, never>(
|
return new Value<AsRequired<string, Required>, never, never>(
|
||||||
async () => ({
|
async () => ({
|
||||||
type: "text" as const,
|
type: "text" as const,
|
||||||
description: null,
|
description: null,
|
||||||
@@ -183,9 +184,10 @@ export class Value<Type, WD> {
|
|||||||
asRequiredParser(string, a),
|
asRequiredParser(string, a),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
static dynamicText<WD = never>(
|
static dynamicText<Store = never, Vault = never>(
|
||||||
getA: LazyBuild<
|
getA: LazyBuild<
|
||||||
WD,
|
Store,
|
||||||
|
Vault,
|
||||||
{
|
{
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
@@ -204,25 +206,28 @@ export class Value<Type, WD> {
|
|||||||
}
|
}
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
return new Value<string | null | undefined, WD>(async (options) => {
|
return new Value<string | null | undefined, Store, Vault>(
|
||||||
const a = await getA(options)
|
async (options) => {
|
||||||
return {
|
const a = await getA(options)
|
||||||
type: "text" as const,
|
return {
|
||||||
description: null,
|
type: "text" as const,
|
||||||
warning: null,
|
description: null,
|
||||||
masked: false,
|
warning: null,
|
||||||
placeholder: null,
|
masked: false,
|
||||||
minLength: null,
|
placeholder: null,
|
||||||
maxLength: null,
|
minLength: null,
|
||||||
patterns: [],
|
maxLength: null,
|
||||||
inputmode: "text",
|
patterns: [],
|
||||||
disabled: false,
|
inputmode: "text",
|
||||||
immutable: false,
|
disabled: false,
|
||||||
generate: a.generate ?? null,
|
immutable: false,
|
||||||
...a,
|
generate: a.generate ?? null,
|
||||||
...requiredLikeToAbove(a.required),
|
...a,
|
||||||
}
|
...requiredLikeToAbove(a.required),
|
||||||
}, string.optional())
|
}
|
||||||
|
},
|
||||||
|
string.optional(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
static textarea(a: {
|
static textarea(a: {
|
||||||
name: string
|
name: string
|
||||||
@@ -237,7 +242,7 @@ export class Value<Type, WD> {
|
|||||||
immutable?: boolean
|
immutable?: boolean
|
||||||
generate?: null | RandomString
|
generate?: null | RandomString
|
||||||
}) {
|
}) {
|
||||||
return new Value<string, never>(
|
return new Value<string, never, never>(
|
||||||
async () =>
|
async () =>
|
||||||
({
|
({
|
||||||
description: null,
|
description: null,
|
||||||
@@ -254,9 +259,10 @@ export class Value<Type, WD> {
|
|||||||
string,
|
string,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
static dynamicTextarea<WD = never>(
|
static dynamicTextarea<Store = never, Vault = never>(
|
||||||
getA: LazyBuild<
|
getA: LazyBuild<
|
||||||
WD,
|
Store,
|
||||||
|
Vault,
|
||||||
{
|
{
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
@@ -270,7 +276,7 @@ export class Value<Type, WD> {
|
|||||||
}
|
}
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
return new Value<string, WD>(async (options) => {
|
return new Value<string, Store, Vault>(async (options) => {
|
||||||
const a = await getA(options)
|
const a = await getA(options)
|
||||||
return {
|
return {
|
||||||
description: null,
|
description: null,
|
||||||
@@ -302,7 +308,7 @@ export class Value<Type, WD> {
|
|||||||
Default is false */
|
Default is false */
|
||||||
immutable?: boolean
|
immutable?: boolean
|
||||||
}) {
|
}) {
|
||||||
return new Value<AsRequired<number, Required>, never>(
|
return new Value<AsRequired<number, Required>, never, never>(
|
||||||
() => ({
|
() => ({
|
||||||
type: "number" as const,
|
type: "number" as const,
|
||||||
description: null,
|
description: null,
|
||||||
@@ -320,9 +326,10 @@ export class Value<Type, WD> {
|
|||||||
asRequiredParser(number, a),
|
asRequiredParser(number, a),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
static dynamicNumber<WD = never>(
|
static dynamicNumber<Store = never, Vault = never>(
|
||||||
getA: LazyBuild<
|
getA: LazyBuild<
|
||||||
WD,
|
Store,
|
||||||
|
Vault,
|
||||||
{
|
{
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
@@ -339,23 +346,26 @@ export class Value<Type, WD> {
|
|||||||
}
|
}
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
return new Value<number | null | undefined, WD>(async (options) => {
|
return new Value<number | null | undefined, Store, Vault>(
|
||||||
const a = await getA(options)
|
async (options) => {
|
||||||
return {
|
const a = await getA(options)
|
||||||
type: "number" as const,
|
return {
|
||||||
description: null,
|
type: "number" as const,
|
||||||
warning: null,
|
description: null,
|
||||||
min: null,
|
warning: null,
|
||||||
max: null,
|
min: null,
|
||||||
step: null,
|
max: null,
|
||||||
units: null,
|
step: null,
|
||||||
placeholder: null,
|
units: null,
|
||||||
disabled: false,
|
placeholder: null,
|
||||||
immutable: false,
|
disabled: false,
|
||||||
...a,
|
immutable: false,
|
||||||
...requiredLikeToAbove(a.required),
|
...a,
|
||||||
}
|
...requiredLikeToAbove(a.required),
|
||||||
}, number.optional())
|
}
|
||||||
|
},
|
||||||
|
number.optional(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
static color<Required extends RequiredDefault<string>>(a: {
|
static color<Required extends RequiredDefault<string>>(a: {
|
||||||
name: string
|
name: string
|
||||||
@@ -366,7 +376,7 @@ export class Value<Type, WD> {
|
|||||||
Default is false */
|
Default is false */
|
||||||
immutable?: boolean
|
immutable?: boolean
|
||||||
}) {
|
}) {
|
||||||
return new Value<AsRequired<string, Required>, never>(
|
return new Value<AsRequired<string, Required>, never, never>(
|
||||||
() => ({
|
() => ({
|
||||||
type: "color" as const,
|
type: "color" as const,
|
||||||
description: null,
|
description: null,
|
||||||
@@ -381,9 +391,10 @@ export class Value<Type, WD> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
static dynamicColor<WD = never>(
|
static dynamicColor<Store = never, Vault = never>(
|
||||||
getA: LazyBuild<
|
getA: LazyBuild<
|
||||||
WD,
|
Store,
|
||||||
|
Vault,
|
||||||
{
|
{
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
@@ -394,18 +405,21 @@ export class Value<Type, WD> {
|
|||||||
}
|
}
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
return new Value<string | null | undefined, WD>(async (options) => {
|
return new Value<string | null | undefined, Store, Vault>(
|
||||||
const a = await getA(options)
|
async (options) => {
|
||||||
return {
|
const a = await getA(options)
|
||||||
type: "color" as const,
|
return {
|
||||||
description: null,
|
type: "color" as const,
|
||||||
warning: null,
|
description: null,
|
||||||
disabled: false,
|
warning: null,
|
||||||
immutable: false,
|
disabled: false,
|
||||||
...a,
|
immutable: false,
|
||||||
...requiredLikeToAbove(a.required),
|
...a,
|
||||||
}
|
...requiredLikeToAbove(a.required),
|
||||||
}, string.optional())
|
}
|
||||||
|
},
|
||||||
|
string.optional(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
static datetime<Required extends RequiredDefault<string>>(a: {
|
static datetime<Required extends RequiredDefault<string>>(a: {
|
||||||
name: string
|
name: string
|
||||||
@@ -421,7 +435,7 @@ export class Value<Type, WD> {
|
|||||||
Default is false */
|
Default is false */
|
||||||
immutable?: boolean
|
immutable?: boolean
|
||||||
}) {
|
}) {
|
||||||
return new Value<AsRequired<string, Required>, never>(
|
return new Value<AsRequired<string, Required>, never, never>(
|
||||||
() => ({
|
() => ({
|
||||||
type: "datetime" as const,
|
type: "datetime" as const,
|
||||||
description: null,
|
description: null,
|
||||||
@@ -438,9 +452,10 @@ export class Value<Type, WD> {
|
|||||||
asRequiredParser(string, a),
|
asRequiredParser(string, a),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
static dynamicDatetime<WD = never>(
|
static dynamicDatetime<Store = never, Vault = never>(
|
||||||
getA: LazyBuild<
|
getA: LazyBuild<
|
||||||
WD,
|
Store,
|
||||||
|
Vault,
|
||||||
{
|
{
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
@@ -455,22 +470,25 @@ export class Value<Type, WD> {
|
|||||||
}
|
}
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
return new Value<string | null | undefined, WD>(async (options) => {
|
return new Value<string | null | undefined, Store, Vault>(
|
||||||
const a = await getA(options)
|
async (options) => {
|
||||||
return {
|
const a = await getA(options)
|
||||||
type: "datetime" as const,
|
return {
|
||||||
description: null,
|
type: "datetime" as const,
|
||||||
warning: null,
|
description: null,
|
||||||
inputmode: "datetime-local",
|
warning: null,
|
||||||
min: null,
|
inputmode: "datetime-local",
|
||||||
max: null,
|
min: null,
|
||||||
step: null,
|
max: null,
|
||||||
disabled: false,
|
step: null,
|
||||||
immutable: false,
|
disabled: false,
|
||||||
...a,
|
immutable: false,
|
||||||
...requiredLikeToAbove(a.required),
|
...a,
|
||||||
}
|
...requiredLikeToAbove(a.required),
|
||||||
}, string.optional())
|
}
|
||||||
|
},
|
||||||
|
string.optional(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
static select<
|
static select<
|
||||||
Required extends RequiredDefault<string>,
|
Required extends RequiredDefault<string>,
|
||||||
@@ -485,7 +503,7 @@ export class Value<Type, WD> {
|
|||||||
Default is false */
|
Default is false */
|
||||||
immutable?: boolean
|
immutable?: boolean
|
||||||
}) {
|
}) {
|
||||||
return new Value<AsRequired<keyof B, Required>, never>(
|
return new Value<AsRequired<keyof B, Required>, never, never>(
|
||||||
() => ({
|
() => ({
|
||||||
description: null,
|
description: null,
|
||||||
warning: null,
|
warning: null,
|
||||||
@@ -503,9 +521,10 @@ export class Value<Type, WD> {
|
|||||||
) as any,
|
) as any,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
static dynamicSelect<WD = never>(
|
static dynamicSelect<Store = never, Vault = never>(
|
||||||
getA: LazyBuild<
|
getA: LazyBuild<
|
||||||
WD,
|
Store,
|
||||||
|
Vault,
|
||||||
{
|
{
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
@@ -516,18 +535,21 @@ export class Value<Type, WD> {
|
|||||||
}
|
}
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
return new Value<string | null | undefined, WD>(async (options) => {
|
return new Value<string | null | undefined, Store, Vault>(
|
||||||
const a = await getA(options)
|
async (options) => {
|
||||||
return {
|
const a = await getA(options)
|
||||||
description: null,
|
return {
|
||||||
warning: null,
|
description: null,
|
||||||
type: "select" as const,
|
warning: null,
|
||||||
disabled: false,
|
type: "select" as const,
|
||||||
immutable: false,
|
disabled: false,
|
||||||
...a,
|
immutable: false,
|
||||||
...requiredLikeToAbove(a.required),
|
...a,
|
||||||
}
|
...requiredLikeToAbove(a.required),
|
||||||
}, string.optional())
|
}
|
||||||
|
},
|
||||||
|
string.optional(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
static multiselect<Values extends Record<string, string>>(a: {
|
static multiselect<Values extends Record<string, string>>(a: {
|
||||||
name: string
|
name: string
|
||||||
@@ -541,7 +563,7 @@ export class Value<Type, WD> {
|
|||||||
Default is false */
|
Default is false */
|
||||||
immutable?: boolean
|
immutable?: boolean
|
||||||
}) {
|
}) {
|
||||||
return new Value<(keyof Values)[], never>(
|
return new Value<(keyof Values)[], never, never>(
|
||||||
() => ({
|
() => ({
|
||||||
type: "multiselect" as const,
|
type: "multiselect" as const,
|
||||||
minLength: null,
|
minLength: null,
|
||||||
@@ -557,9 +579,10 @@ export class Value<Type, WD> {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
static dynamicMultiselect<WD = never>(
|
static dynamicMultiselect<Store = never, Vault = never>(
|
||||||
getA: LazyBuild<
|
getA: LazyBuild<
|
||||||
WD,
|
Store,
|
||||||
|
Vault,
|
||||||
{
|
{
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
@@ -572,7 +595,7 @@ export class Value<Type, WD> {
|
|||||||
}
|
}
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
return new Value<string[], WD>(async (options) => {
|
return new Value<string[], Store, Vault>(async (options) => {
|
||||||
const a = await getA(options)
|
const a = await getA(options)
|
||||||
return {
|
return {
|
||||||
type: "multiselect" as const,
|
type: "multiselect" as const,
|
||||||
@@ -586,15 +609,15 @@ export class Value<Type, WD> {
|
|||||||
}
|
}
|
||||||
}, arrayOf(string))
|
}, arrayOf(string))
|
||||||
}
|
}
|
||||||
static object<Type extends Record<string, any>, WrapperData>(
|
static object<Type extends Record<string, any>, Store, Vault>(
|
||||||
a: {
|
a: {
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
warning?: string | null
|
warning?: string | null
|
||||||
},
|
},
|
||||||
previousSpec: Config<Type, WrapperData>,
|
previousSpec: Config<Type, Store, Vault>,
|
||||||
) {
|
) {
|
||||||
return new Value<Type, WrapperData>(async (options) => {
|
return new Value<Type, Store, Vault>(async (options) => {
|
||||||
const spec = await previousSpec.build(options as any)
|
const spec = await previousSpec.build(options as any)
|
||||||
return {
|
return {
|
||||||
type: "object" as const,
|
type: "object" as const,
|
||||||
@@ -605,7 +628,7 @@ export class Value<Type, WD> {
|
|||||||
}
|
}
|
||||||
}, previousSpec.validator)
|
}, previousSpec.validator)
|
||||||
}
|
}
|
||||||
static union<Required extends RequiredDefault<string>, Type, WrapperData>(
|
static union<Required extends RequiredDefault<string>, Type, Store, Vault>(
|
||||||
a: {
|
a: {
|
||||||
name: string
|
name: string
|
||||||
description?: string | null
|
description?: string | null
|
||||||
@@ -615,9 +638,9 @@ export class Value<Type, WD> {
|
|||||||
Default is false */
|
Default is false */
|
||||||
immutable?: boolean
|
immutable?: boolean
|
||||||
},
|
},
|
||||||
aVariants: Variants<Type, WrapperData>,
|
aVariants: Variants<Type, Store, Vault>,
|
||||||
) {
|
) {
|
||||||
return new Value<AsRequired<Type, Required>, WrapperData>(
|
return new Value<AsRequired<Type, Required>, Store, Vault>(
|
||||||
async (options) => ({
|
async (options) => ({
|
||||||
type: "union" as const,
|
type: "union" as const,
|
||||||
description: null,
|
description: null,
|
||||||
@@ -630,39 +653,38 @@ export class Value<Type, WD> {
|
|||||||
asRequiredParser(aVariants.validator, a),
|
asRequiredParser(aVariants.validator, a),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
static filteredUnion<WrapperData = never>(
|
static filteredUnion<
|
||||||
getDisabledFn: LazyBuild<WrapperData, string[]>,
|
Required extends RequiredDefault<string>,
|
||||||
|
Type extends Record<string, any>,
|
||||||
|
Store = never,
|
||||||
|
Vault = never,
|
||||||
|
>(
|
||||||
|
getDisabledFn: LazyBuild<Store, Vault, string[]>,
|
||||||
|
a: {
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
warning?: string | null
|
||||||
|
required: Required
|
||||||
|
},
|
||||||
|
aVariants: Variants<Type, Store, Vault> | Variants<Type, never, never>,
|
||||||
) {
|
) {
|
||||||
return <
|
return new Value<AsRequired<Type, Required>, Store, Vault>(
|
||||||
Required extends RequiredDefault<string>,
|
async (options) => ({
|
||||||
Type extends Record<string, any>,
|
type: "union" as const,
|
||||||
>(
|
description: null,
|
||||||
a: {
|
warning: null,
|
||||||
name: string
|
...a,
|
||||||
description?: string | null
|
variants: await aVariants.build(options as any),
|
||||||
warning?: string | null
|
...requiredLikeToAbove(a.required),
|
||||||
required: Required
|
disabled: (await getDisabledFn(options)) || [],
|
||||||
},
|
immutable: false,
|
||||||
aVariants: Variants<Type, WrapperData> | Variants<Type, never>,
|
}),
|
||||||
) => {
|
asRequiredParser(aVariants.validator, a),
|
||||||
return new Value<AsRequired<Type, Required>, WrapperData>(
|
)
|
||||||
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)) || [],
|
|
||||||
immutable: false,
|
|
||||||
}),
|
|
||||||
asRequiredParser(aVariants.validator, a),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static list<Type, WrapperData>(a: List<Type, WrapperData>) {
|
static list<Type, Store, Vault>(a: List<Type, Store, Vault>) {
|
||||||
return new Value<Type, WrapperData>(
|
return new Value<Type, Store, Vault>(
|
||||||
(options) => a.build(options),
|
(options) => a.build(options),
|
||||||
a.validator,
|
a.validator,
|
||||||
)
|
)
|
||||||
@@ -677,42 +699,12 @@ export class Value<Type, WD> {
|
|||||||
required: false,
|
required: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
return Config.of<WrapperData>()({
|
return Config.of<Store>()({
|
||||||
myValue: a.withWrapperData(),
|
myValue: a.withStore(),
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
withWrapperData<NewWrapperData extends WD extends never ? any : WD>() {
|
withStore<NewStore extends Store extends never ? any : Store>() {
|
||||||
return this as any as Value<Type, NewWrapperData>
|
return this as any as Value<Type, NewStore, Vault>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Wrapper = { test: 1 | "5" }
|
|
||||||
const valueA = Value.dynamicText<Wrapper>(() => ({
|
|
||||||
name: "a",
|
|
||||||
required: false,
|
|
||||||
}))
|
|
||||||
const variantForC = Variants.of({
|
|
||||||
lnd: {
|
|
||||||
name: "lnd Name",
|
|
||||||
spec: Config.of({
|
|
||||||
name: Value.text({
|
|
||||||
name: "Node Name",
|
|
||||||
required: false,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const valueC = Value.filteredUnion<Wrapper>(() => [])(
|
|
||||||
{ name: "a", required: false },
|
|
||||||
variantForC,
|
|
||||||
)
|
|
||||||
const valueB = Value.text({
|
|
||||||
name: "a",
|
|
||||||
required: false,
|
|
||||||
})
|
|
||||||
const test = Config.of({
|
|
||||||
a: valueA,
|
|
||||||
b: valueB,
|
|
||||||
c: valueC,
|
|
||||||
})
|
|
||||||
|
|||||||
@@ -51,24 +51,21 @@ export const pruning = Value.union(
|
|||||||
);
|
);
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
export class Variants<Type, WD> {
|
export class Variants<Type, Store, Vault> {
|
||||||
|
static text: any
|
||||||
private constructor(
|
private constructor(
|
||||||
public build: LazyBuild<WD, ValueSpecUnion["variants"]>,
|
public build: LazyBuild<Store, Vault, ValueSpecUnion["variants"]>,
|
||||||
public validator: Parser<unknown, Type>,
|
public validator: Parser<unknown, Type>,
|
||||||
) {}
|
) {}
|
||||||
// A extends {
|
|
||||||
// [key: string]: {
|
|
||||||
// name: string
|
|
||||||
// spec: InputSpec
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
static of<
|
static of<
|
||||||
VariantValues extends {
|
VariantValues extends {
|
||||||
[K in string]: {
|
[K in string]: {
|
||||||
name: string
|
name: string
|
||||||
spec: Config<any, any> | Config<any, never>
|
spec: Config<any, Store, Vault> | Config<any, never, never>
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Store,
|
||||||
|
Vault,
|
||||||
>(a: VariantValues) {
|
>(a: VariantValues) {
|
||||||
const validator = anyOf(
|
const validator = anyOf(
|
||||||
...Object.entries(a).map(([name, { spec }]) =>
|
...Object.entries(a).map(([name, { spec }]) =>
|
||||||
@@ -83,21 +80,14 @@ export class Variants<Type, WD> {
|
|||||||
{
|
{
|
||||||
[K in keyof VariantValues]: {
|
[K in keyof VariantValues]: {
|
||||||
unionSelectKey: K
|
unionSelectKey: K
|
||||||
unionValueKey: VariantValues[K]["spec"] extends
|
// prettier-ignore
|
||||||
| Config<infer B, any>
|
unionValueKey:
|
||||||
| Config<infer B, never>
|
VariantValues[K]["spec"] extends (Config<infer B, Store,Vault> | Config<infer B, never, never>) ? B :
|
||||||
? B
|
never
|
||||||
: never
|
|
||||||
}
|
}
|
||||||
}[keyof VariantValues],
|
}[keyof VariantValues],
|
||||||
{
|
Store,
|
||||||
[K in keyof VariantValues]: VariantValues[K] extends Config<
|
Vault
|
||||||
any,
|
|
||||||
infer C
|
|
||||||
>
|
|
||||||
? C
|
|
||||||
: never
|
|
||||||
}[keyof VariantValues]
|
|
||||||
>(async (options) => {
|
>(async (options) => {
|
||||||
const variants = {} as {
|
const variants = {} as {
|
||||||
[K in keyof VariantValues]: { name: string; spec: InputSpec }
|
[K in keyof VariantValues]: { name: string; spec: InputSpec }
|
||||||
@@ -121,12 +111,12 @@ export class Variants<Type, WD> {
|
|||||||
required: false,
|
required: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
return Config.of<WrapperData>()({
|
return Config.of<Store>()({
|
||||||
myValue: a.withWrapperData(),
|
myValue: a.withStore(),
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
withWrapperData<NewWrapperData extends WD extends never ? any : WD>() {
|
withStore<NewStore extends Store extends never ? any : Store>() {
|
||||||
return this as any as Variants<Type, NewWrapperData>
|
return this as any as Variants<Type, NewStore, Vault>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ import { Config, ConfigSpecOf } from "./builder/config"
|
|||||||
import { Value } from "./builder/value"
|
import { Value } from "./builder/value"
|
||||||
import { Variants } from "./builder/variants"
|
import { Variants } from "./builder/variants"
|
||||||
|
|
||||||
export const smtpConfig = Value.filteredUnion(async ({ effects, utils }) => {
|
export const smtpConfig = Value.filteredUnion(
|
||||||
const smtp = await utils.getSystemSmtp().once()
|
async ({ effects, utils }) => {
|
||||||
return smtp ? [] : ["system"]
|
const smtp = await utils.getSystemSmtp().once()
|
||||||
})(
|
return smtp ? [] : ["system"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "SMTP",
|
name: "SMTP",
|
||||||
description: "Optionally provide an SMTP server for sending email",
|
description: "Optionally provide an SMTP server for sending email",
|
||||||
@@ -17,7 +18,7 @@ export const smtpConfig = Value.filteredUnion(async ({ effects, utils }) => {
|
|||||||
system: { name: "System Credentials", spec: Config.of({}) },
|
system: { name: "System Credentials", spec: Config.of({}) },
|
||||||
custom: {
|
custom: {
|
||||||
name: "Custom Credentials",
|
name: "Custom Credentials",
|
||||||
spec: Config.of<ConfigSpecOf<SmtpValue>>({
|
spec: Config.of<ConfigSpecOf<SmtpValue>, never, never>({
|
||||||
server: Value.text({
|
server: Value.text({
|
||||||
name: "SMTP Server",
|
name: "SMTP Server",
|
||||||
required: {
|
required: {
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
import { SDKManifest } from "../manifest/ManifestTypes"
|
import { SDKManifest } from "../manifest/ManifestTypes"
|
||||||
import { Dependency } from "../types"
|
import { Dependency } from "../types"
|
||||||
|
|
||||||
export type Dependencies<T extends SDKManifest> = {
|
export type ConfigDependencies<T extends SDKManifest> = {
|
||||||
exists(id: keyof T["dependencies"]): Dependency
|
exists(id: keyof T["dependencies"]): Dependency
|
||||||
running(id: keyof T["dependencies"]): Dependency
|
running(id: keyof T["dependencies"]): Dependency
|
||||||
}
|
}
|
||||||
|
|
||||||
export const dependenciesSet = <T extends SDKManifest>(): Dependencies<T> => ({
|
export const configDependenciesSet = <
|
||||||
|
T extends SDKManifest,
|
||||||
|
>(): ConfigDependencies<T> => ({
|
||||||
exists(id: keyof T["dependencies"]) {
|
exists(id: keyof T["dependencies"]) {
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import "./builder"
|
import "./builder"
|
||||||
|
|
||||||
import "./setupConfig"
|
import "./setupConfig"
|
||||||
import "./dependencies"
|
import "./configDependencies"
|
||||||
import "./constants"
|
import "./configConstants"
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Effects, ExpectedExports } from "../types"
|
import { Effects, ExpectedExports } from "../types"
|
||||||
import { SDKManifest } from "../manifest/ManifestTypes"
|
import { SDKManifest } from "../manifest/ManifestTypes"
|
||||||
import * as D from "./dependencies"
|
import * as D from "./configDependencies"
|
||||||
import { Config, ExtractConfigType } from "./builder/config"
|
import { Config, ExtractConfigType } from "./builder/config"
|
||||||
import { Utils, utils } from "../util"
|
import { Utils, utils } from "../util/utils"
|
||||||
import nullIfEmpty from "../util/nullIfEmpty"
|
import nullIfEmpty from "../util/nullIfEmpty"
|
||||||
|
|
||||||
declare const dependencyProof: unique symbol
|
declare const dependencyProof: unique symbol
|
||||||
@@ -11,30 +11,32 @@ export type DependenciesReceipt = void & {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type Save<
|
export type Save<
|
||||||
WD,
|
Store,
|
||||||
|
Vault,
|
||||||
A extends
|
A extends
|
||||||
| Record<string, any>
|
| Record<string, any>
|
||||||
| Config<Record<string, any>, any>
|
| Config<Record<string, any>, any, any>
|
||||||
| Config<Record<string, never>, never>,
|
| Config<Record<string, never>, never, never>,
|
||||||
Manifest extends SDKManifest,
|
Manifest extends SDKManifest,
|
||||||
> = (options: {
|
> = (options: {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
input: ExtractConfigType<A> & Record<string, any>
|
input: ExtractConfigType<A> & Record<string, any>
|
||||||
utils: Utils<WD>
|
utils: Utils<Store, Vault>
|
||||||
dependencies: D.Dependencies<Manifest>
|
dependencies: D.ConfigDependencies<Manifest>
|
||||||
}) => Promise<{
|
}) => Promise<{
|
||||||
dependenciesReceipt: DependenciesReceipt
|
dependenciesReceipt: DependenciesReceipt
|
||||||
restart: boolean
|
restart: boolean
|
||||||
}>
|
}>
|
||||||
export type Read<
|
export type Read<
|
||||||
WD,
|
Store,
|
||||||
|
Vault,
|
||||||
A extends
|
A extends
|
||||||
| Record<string, any>
|
| Record<string, any>
|
||||||
| Config<Record<string, any>, any>
|
| Config<Record<string, any>, any, any>
|
||||||
| Config<Record<string, any>, never>,
|
| Config<Record<string, any>, never, never>,
|
||||||
> = (options: {
|
> = (options: {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
utils: Utils<WD>
|
utils: Utils<Store, Vault>
|
||||||
}) => Promise<void | (ExtractConfigType<A> & Record<string, any>)>
|
}) => Promise<void | (ExtractConfigType<A> & Record<string, any>)>
|
||||||
/**
|
/**
|
||||||
* We want to setup a config export with a get and set, this
|
* We want to setup a config export with a get and set, this
|
||||||
@@ -44,17 +46,18 @@ export type Read<
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function setupConfig<
|
export function setupConfig<
|
||||||
WD,
|
Store,
|
||||||
|
Vault,
|
||||||
ConfigType extends
|
ConfigType extends
|
||||||
| Record<string, any>
|
| Record<string, any>
|
||||||
| Config<any, any>
|
| Config<any, any, any>
|
||||||
| Config<any, never>,
|
| Config<any, never, never>,
|
||||||
Manifest extends SDKManifest,
|
Manifest extends SDKManifest,
|
||||||
Type extends Record<string, any> = ExtractConfigType<ConfigType>,
|
Type extends Record<string, any> = ExtractConfigType<ConfigType>,
|
||||||
>(
|
>(
|
||||||
spec: Config<Type, WD> | Config<Type, never>,
|
spec: Config<Type, Store, Vault> | Config<Type, never, never>,
|
||||||
write: Save<WD, Type, Manifest>,
|
write: Save<Store, Vault, Type, Manifest>,
|
||||||
read: Read<WD, Type>,
|
read: Read<Store, Vault, Type>,
|
||||||
) {
|
) {
|
||||||
const validator = spec.validator
|
const validator = spec.validator
|
||||||
return {
|
return {
|
||||||
@@ -66,15 +69,15 @@ export function setupConfig<
|
|||||||
const { restart } = await write({
|
const { restart } = await write({
|
||||||
input: JSON.parse(JSON.stringify(input)),
|
input: JSON.parse(JSON.stringify(input)),
|
||||||
effects,
|
effects,
|
||||||
utils: utils<WD>(effects),
|
utils: utils(effects),
|
||||||
dependencies: D.dependenciesSet<Manifest>(),
|
dependencies: D.configDependenciesSet<Manifest>(),
|
||||||
})
|
})
|
||||||
if (restart) {
|
if (restart) {
|
||||||
await effects.restart()
|
await effects.restart()
|
||||||
}
|
}
|
||||||
}) as ExpectedExports.setConfig,
|
}) as ExpectedExports.setConfig,
|
||||||
getConfig: (async ({ effects }) => {
|
getConfig: (async ({ effects }) => {
|
||||||
const myUtils = utils<WD>(effects)
|
const myUtils = utils<Store, Vault>(effects)
|
||||||
const configValue = nullIfEmpty(
|
const configValue = nullIfEmpty(
|
||||||
(await read({ effects, utils: myUtils })) || null,
|
(await read({ effects, utils: myUtils })) || null,
|
||||||
)
|
)
|
||||||
|
|||||||
43
lib/dependency/mountDependencies.ts
Normal file
43
lib/dependency/mountDependencies.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { Effects } from "../types"
|
||||||
|
import {
|
||||||
|
Path,
|
||||||
|
ManifestId,
|
||||||
|
VolumeName,
|
||||||
|
NamedPath,
|
||||||
|
matchPath,
|
||||||
|
} from "./setupDependencyMounts"
|
||||||
|
|
||||||
|
export type MountDependenciesOut<A> =
|
||||||
|
// prettier-ignore
|
||||||
|
A extends Path ? string : A extends Record<string, unknown> ? {
|
||||||
|
[P in keyof A]: MountDependenciesOut<A[P]>;
|
||||||
|
} : never
|
||||||
|
export async function mountDependencies<
|
||||||
|
In extends
|
||||||
|
| Record<ManifestId, Record<VolumeName, Record<NamedPath, Path>>>
|
||||||
|
| Record<VolumeName, Record<NamedPath, Path>>
|
||||||
|
| Record<NamedPath, Path>
|
||||||
|
| Path,
|
||||||
|
>(effects: Effects, value: In): Promise<MountDependenciesOut<In>> {
|
||||||
|
if (matchPath.test(value)) {
|
||||||
|
const mountPath = `${value.manifest.id}/${value.volume}/${value.name}`
|
||||||
|
|
||||||
|
return (await effects.mount({
|
||||||
|
location: {
|
||||||
|
path: mountPath,
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
packageId: value.manifest.id,
|
||||||
|
path: value.path,
|
||||||
|
readonly: value.readonly,
|
||||||
|
volumeId: value.volume,
|
||||||
|
},
|
||||||
|
})) as MountDependenciesOut<In>
|
||||||
|
}
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(value).map(([key, value]) => [
|
||||||
|
key,
|
||||||
|
mountDependencies(effects, value),
|
||||||
|
]),
|
||||||
|
) as Record<string, unknown> as MountDependenciesOut<In>
|
||||||
|
}
|
||||||
70
lib/dependency/setupDependencyMounts.ts
Normal file
70
lib/dependency/setupDependencyMounts.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import { boolean, object, string } from "ts-matches"
|
||||||
|
import { SDKManifest } from "../manifest/ManifestTypes"
|
||||||
|
import { deepMerge } from "../util/deepMerge"
|
||||||
|
|
||||||
|
export type VolumeName = string
|
||||||
|
export type NamedPath = string
|
||||||
|
export type ManifestId = string
|
||||||
|
|
||||||
|
export const matchPath = object({
|
||||||
|
name: string,
|
||||||
|
volume: string,
|
||||||
|
path: string,
|
||||||
|
manifest: object({
|
||||||
|
id: string,
|
||||||
|
}),
|
||||||
|
readonly: boolean,
|
||||||
|
})
|
||||||
|
export type Path = typeof matchPath._TYPE
|
||||||
|
export type BuildPath<M extends Path> = {
|
||||||
|
[PId in M["manifest"]["id"]]: {
|
||||||
|
[V in M["volume"]]: {
|
||||||
|
[N in M["name"]]: M
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type ValidIfNotInNested<
|
||||||
|
Building,
|
||||||
|
M extends Path,
|
||||||
|
> = Building extends BuildPath<M> ? never : M
|
||||||
|
class SetupDependencyMounts<Building> {
|
||||||
|
private constructor(readonly building: Building) {}
|
||||||
|
|
||||||
|
static of() {
|
||||||
|
return new SetupDependencyMounts({})
|
||||||
|
}
|
||||||
|
|
||||||
|
addPath<
|
||||||
|
NamedPath extends string,
|
||||||
|
VolumeName extends string,
|
||||||
|
PathNamed extends string,
|
||||||
|
M extends SDKManifest,
|
||||||
|
>(
|
||||||
|
newPath: ValidIfNotInNested<
|
||||||
|
Building,
|
||||||
|
{
|
||||||
|
name: NamedPath
|
||||||
|
volume: VolumeName
|
||||||
|
path: PathNamed
|
||||||
|
manifest: M
|
||||||
|
readonly: boolean
|
||||||
|
}
|
||||||
|
>,
|
||||||
|
) {
|
||||||
|
const building = deepMerge(this.building, {
|
||||||
|
[newPath.manifest.id]: {
|
||||||
|
[newPath.volume]: {
|
||||||
|
[newPath.name]: newPath,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}) as Building & BuildPath<typeof newPath>
|
||||||
|
return new SetupDependencyMounts(building)
|
||||||
|
}
|
||||||
|
build() {
|
||||||
|
return this.building
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setupDependencyMounts() {
|
||||||
|
return SetupDependencyMounts.of()
|
||||||
|
}
|
||||||
62
lib/dependencyConfig/DependencyConfig.ts
Normal file
62
lib/dependencyConfig/DependencyConfig.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import {
|
||||||
|
DependencyConfig as DependencyConfigType,
|
||||||
|
DeepPartial,
|
||||||
|
Effects,
|
||||||
|
} from "../types"
|
||||||
|
import { Utils, utils } from "../util/utils"
|
||||||
|
import { deepEqual } from "../util/deepEqual"
|
||||||
|
import { deepMerge } from "../util/deepMerge"
|
||||||
|
|
||||||
|
export class DependencyConfig<
|
||||||
|
Store,
|
||||||
|
Vault,
|
||||||
|
Input extends Record<string, any>,
|
||||||
|
RemoteConfig extends Record<string, any>,
|
||||||
|
> {
|
||||||
|
constructor(
|
||||||
|
readonly dependencyConfig: (options: {
|
||||||
|
effects: Effects
|
||||||
|
localConfig: Input
|
||||||
|
remoteConfig: RemoteConfig
|
||||||
|
utils: Utils<Store, Vault>
|
||||||
|
}) => Promise<void | DeepPartial<RemoteConfig>>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async check(
|
||||||
|
options: Parameters<DependencyConfigType["check"]>[0],
|
||||||
|
): ReturnType<DependencyConfigType["check"]> {
|
||||||
|
const origConfig = JSON.parse(JSON.stringify(options.localConfig))
|
||||||
|
const newOptions = {
|
||||||
|
...options,
|
||||||
|
utils: utils<Store, Vault>(options.effects),
|
||||||
|
localConfig: options.localConfig as Input,
|
||||||
|
remoteConfig: options.remoteConfig as RemoteConfig,
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!deepEqual(
|
||||||
|
origConfig,
|
||||||
|
deepMerge(
|
||||||
|
{},
|
||||||
|
options.localConfig,
|
||||||
|
await this.dependencyConfig(newOptions),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
throw new Error(`Check failed`)
|
||||||
|
}
|
||||||
|
async autoConfigure(
|
||||||
|
options: Parameters<DependencyConfigType["autoConfigure"]>[0],
|
||||||
|
): ReturnType<DependencyConfigType["autoConfigure"]> {
|
||||||
|
const newOptions = {
|
||||||
|
...options,
|
||||||
|
utils: utils<Store, Vault>(options.effects),
|
||||||
|
localConfig: options.localConfig as Input,
|
||||||
|
remoteConfig: options.remoteConfig as any,
|
||||||
|
}
|
||||||
|
return deepMerge(
|
||||||
|
{},
|
||||||
|
options.remoteConfig,
|
||||||
|
await this.dependencyConfig(newOptions),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,5 +5,5 @@ export type ReadonlyDeep<A> =
|
|||||||
export type MaybePromise<A> = Promise<A> | A
|
export type MaybePromise<A> = Promise<A> | A
|
||||||
export type Message = string
|
export type Message = string
|
||||||
|
|
||||||
import "./AutoConfig"
|
import "./DependencyConfig"
|
||||||
import "./setupAutoConfig"
|
import "./setupDependencyConfig"
|
||||||
23
lib/dependencyConfig/setupDependencyConfig.ts
Normal file
23
lib/dependencyConfig/setupDependencyConfig.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { Config } from "../config/builder/config"
|
||||||
|
import { SDKManifest } from "../manifest/ManifestTypes"
|
||||||
|
import { ExpectedExports } from "../types"
|
||||||
|
import { DependencyConfig } from "./DependencyConfig"
|
||||||
|
|
||||||
|
export function setupDependencyConfig<
|
||||||
|
Store,
|
||||||
|
Vault,
|
||||||
|
Input extends Record<string, any>,
|
||||||
|
Manifest extends SDKManifest,
|
||||||
|
>(
|
||||||
|
_config: Config<Input, Store, Vault>,
|
||||||
|
autoConfigs: {
|
||||||
|
[key in keyof Manifest["dependencies"] & string]: DependencyConfig<
|
||||||
|
Store,
|
||||||
|
Vault,
|
||||||
|
Input,
|
||||||
|
any
|
||||||
|
>
|
||||||
|
},
|
||||||
|
): ExpectedExports.dependencyConfig {
|
||||||
|
return autoConfigs
|
||||||
|
}
|
||||||
@@ -2,9 +2,9 @@ import { InterfaceReceipt } from "../mainFn/interfaceReceipt"
|
|||||||
import { Daemon, Effects } from "../types"
|
import { Daemon, Effects } from "../types"
|
||||||
import { CheckResult } from "./checkFns/CheckResult"
|
import { CheckResult } from "./checkFns/CheckResult"
|
||||||
import { HealthReceipt } from "./HealthReceipt"
|
import { HealthReceipt } from "./HealthReceipt"
|
||||||
import { Trigger } from "./trigger"
|
import { Trigger } from "../trigger"
|
||||||
import { TriggerInput } from "./trigger/TriggerInput"
|
import { TriggerInput } from "../trigger/TriggerInput"
|
||||||
import { defaultTrigger } from "./trigger/defaultTrigger"
|
import { defaultTrigger } from "../trigger/defaultTrigger"
|
||||||
import { once } from "../util/once"
|
import { once } from "../util/once"
|
||||||
|
|
||||||
export function healthCheck(o: {
|
export function healthCheck(o: {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import "@iarna/toml"
|
|||||||
import "./types"
|
import "./types"
|
||||||
import "./util"
|
import "./util"
|
||||||
import "yaml"
|
import "yaml"
|
||||||
import "./autoconfig"
|
import "./dependencyConfig"
|
||||||
import "./actions"
|
import "./actions"
|
||||||
import "./manifest"
|
import "./manifest"
|
||||||
import "./inits"
|
import "./inits"
|
||||||
|
|||||||
@@ -1,27 +1,40 @@
|
|||||||
import { ManifestVersion } from "../../manifest/ManifestTypes"
|
import { ManifestVersion } from "../../manifest/ManifestTypes"
|
||||||
import { Effects } from "../../types"
|
import { Effects } from "../../types"
|
||||||
|
import { Utils } from "../../util/utils"
|
||||||
|
|
||||||
export class Migration<Version extends ManifestVersion> {
|
export class Migration<Store, Vault, Version extends ManifestVersion> {
|
||||||
constructor(
|
constructor(
|
||||||
readonly options: {
|
readonly options: {
|
||||||
version: Version
|
version: Version
|
||||||
up: (opts: { effects: Effects }) => Promise<void>
|
up: (opts: {
|
||||||
down: (opts: { effects: Effects }) => Promise<void>
|
effects: Effects
|
||||||
|
utils: Utils<Store, Vault>
|
||||||
|
}) => Promise<void>
|
||||||
|
down: (opts: {
|
||||||
|
effects: Effects
|
||||||
|
utils: Utils<Store, Vault>
|
||||||
|
}) => Promise<void>
|
||||||
},
|
},
|
||||||
) {}
|
) {}
|
||||||
static of<Version extends ManifestVersion>(options: {
|
static of<Store, Vault, Version extends ManifestVersion>(options: {
|
||||||
version: Version
|
version: Version
|
||||||
up: (opts: { effects: Effects }) => Promise<void>
|
up: (opts: {
|
||||||
down: (opts: { effects: Effects }) => Promise<void>
|
effects: Effects
|
||||||
|
utils: Utils<Store, Vault>
|
||||||
|
}) => Promise<void>
|
||||||
|
down: (opts: {
|
||||||
|
effects: Effects
|
||||||
|
utils: Utils<Store, Vault>
|
||||||
|
}) => Promise<void>
|
||||||
}) {
|
}) {
|
||||||
return new Migration(options)
|
return new Migration<Store, Vault, Version>(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
async up(opts: { effects: Effects }) {
|
async up(opts: { effects: Effects; utils: Utils<Store, Vault> }) {
|
||||||
this.up(opts)
|
this.up(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
async down(opts: { effects: Effects }) {
|
async down(opts: { effects: Effects; utils: Utils<Store, Vault> }) {
|
||||||
this.down(opts)
|
this.down(opts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +1,44 @@
|
|||||||
import { setupActions } from "../../actions/setupActions"
|
|
||||||
import { EmVer } from "../../emverLite/mod"
|
import { EmVer } from "../../emverLite/mod"
|
||||||
import { SDKManifest } from "../../manifest/ManifestTypes"
|
import { SDKManifest } from "../../manifest/ManifestTypes"
|
||||||
import { ExpectedExports } from "../../types"
|
import { ExpectedExports } from "../../types"
|
||||||
|
import { createUtils } from "../../util"
|
||||||
import { once } from "../../util/once"
|
import { once } from "../../util/once"
|
||||||
import { Migration } from "./Migration"
|
import { Migration } from "./Migration"
|
||||||
|
|
||||||
export class Migrations {
|
export class Migrations<Store, Vault> {
|
||||||
private constructor(
|
private constructor(
|
||||||
readonly manifest: SDKManifest,
|
readonly manifest: SDKManifest,
|
||||||
readonly migrations: Array<Migration<any>>,
|
readonly migrations: Array<Migration<Store, Vault, any>>,
|
||||||
) {}
|
) {}
|
||||||
private sortedMigrations = once(() => {
|
private sortedMigrations = once(() => {
|
||||||
const migrationsAsVersions = (this.migrations as Array<Migration<any>>).map(
|
const migrationsAsVersions = (
|
||||||
(x) => [EmVer.parse(x.options.version), x] as const,
|
this.migrations as Array<Migration<Store, Vault, any>>
|
||||||
)
|
).map((x) => [EmVer.parse(x.options.version), x] as const)
|
||||||
migrationsAsVersions.sort((a, b) => a[0].compareForSort(b[0]))
|
migrationsAsVersions.sort((a, b) => a[0].compareForSort(b[0]))
|
||||||
return migrationsAsVersions
|
return migrationsAsVersions
|
||||||
})
|
})
|
||||||
private currentVersion = once(() => EmVer.parse(this.manifest.version))
|
private currentVersion = once(() => EmVer.parse(this.manifest.version))
|
||||||
static of<Migrations extends Array<Migration<any>>>(
|
static of<
|
||||||
manifest: SDKManifest,
|
Store,
|
||||||
...migrations: EnsureUniqueId<Migrations>
|
Vault,
|
||||||
) {
|
Migrations extends Array<Migration<Store, Vault, any>>,
|
||||||
return new Migrations(manifest, migrations as Array<Migration<any>>)
|
>(manifest: SDKManifest, ...migrations: EnsureUniqueId<Migrations>) {
|
||||||
|
return new Migrations(
|
||||||
|
manifest,
|
||||||
|
migrations as Array<Migration<Store, Vault, any>>,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
async init({
|
async init({
|
||||||
effects,
|
effects,
|
||||||
previousVersion,
|
previousVersion,
|
||||||
}: Parameters<ExpectedExports.init>[0]) {
|
}: Parameters<ExpectedExports.init>[0]) {
|
||||||
|
const utils = createUtils<Store, Vault>(effects)
|
||||||
if (!!previousVersion) {
|
if (!!previousVersion) {
|
||||||
const previousVersionEmVer = EmVer.parse(previousVersion)
|
const previousVersionEmVer = EmVer.parse(previousVersion)
|
||||||
for (const [_, migration] of this.sortedMigrations()
|
for (const [_, migration] of this.sortedMigrations()
|
||||||
.filter((x) => x[0].greaterThan(previousVersionEmVer))
|
.filter((x) => x[0].greaterThan(previousVersionEmVer))
|
||||||
.filter((x) => x[0].lessThanOrEqual(this.currentVersion()))) {
|
.filter((x) => x[0].lessThanOrEqual(this.currentVersion()))) {
|
||||||
await migration.up({ effects })
|
await migration.up({ effects, utils })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,29 +46,31 @@ export class Migrations {
|
|||||||
effects,
|
effects,
|
||||||
nextVersion,
|
nextVersion,
|
||||||
}: Parameters<ExpectedExports.uninit>[0]) {
|
}: Parameters<ExpectedExports.uninit>[0]) {
|
||||||
|
const utils = createUtils<Store, Vault>(effects)
|
||||||
if (!!nextVersion) {
|
if (!!nextVersion) {
|
||||||
const nextVersionEmVer = EmVer.parse(nextVersion)
|
const nextVersionEmVer = EmVer.parse(nextVersion)
|
||||||
const reversed = [...this.sortedMigrations()].reverse()
|
const reversed = [...this.sortedMigrations()].reverse()
|
||||||
for (const [_, migration] of reversed
|
for (const [_, migration] of reversed
|
||||||
.filter((x) => x[0].greaterThan(nextVersionEmVer))
|
.filter((x) => x[0].greaterThan(nextVersionEmVer))
|
||||||
.filter((x) => x[0].lessThanOrEqual(this.currentVersion()))) {
|
.filter((x) => x[0].lessThanOrEqual(this.currentVersion()))) {
|
||||||
await migration.down({ effects })
|
await migration.down({ effects, utils })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setupMigrations<Migrations extends Array<Migration<any>>>(
|
export function setupMigrations<
|
||||||
manifest: SDKManifest,
|
Store,
|
||||||
...migrations: EnsureUniqueId<Migrations>
|
Vault,
|
||||||
) {
|
Migrations extends Array<Migration<Store, Vault, any>>,
|
||||||
return Migrations.of(manifest, ...migrations)
|
>(manifest: SDKManifest, ...migrations: EnsureUniqueId<Migrations>) {
|
||||||
|
return Migrations.of<Store, Vault, Migrations>(manifest, ...migrations)
|
||||||
}
|
}
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
export type EnsureUniqueId<A, B = A, ids = never> =
|
export type EnsureUniqueId<A, B = A, ids = never> =
|
||||||
B extends [] ? A :
|
B extends [] ? A :
|
||||||
B extends [Migration<infer id>, ...infer Rest] ? (
|
B extends [Migration<any,any, infer id>, ...infer Rest] ? (
|
||||||
id extends ids ? "One of the ids are not unique"[] :
|
id extends ids ? "One of the ids are not unique"[] :
|
||||||
EnsureUniqueId<A, Rest, id | ids>
|
EnsureUniqueId<A, Rest, id | ids>
|
||||||
) : "There exists a migration that is not a Migration"[]
|
) : "There exists a migration that is not a Migration"[]
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import { Migrations } from "./migrations/setupMigrations"
|
|||||||
import { Install } from "./setupInstall"
|
import { Install } from "./setupInstall"
|
||||||
import { Uninstall } from "./setupUninstall"
|
import { Uninstall } from "./setupUninstall"
|
||||||
|
|
||||||
export function setupInit<WrapperData>(
|
export function setupInit<Store, Vault>(
|
||||||
migrations: Migrations,
|
migrations: Migrations<Store, Vault>,
|
||||||
install: Install<WrapperData>,
|
install: Install<Store, Vault>,
|
||||||
uninstall: Uninstall<WrapperData>,
|
uninstall: Uninstall<Store, Vault>,
|
||||||
): {
|
): {
|
||||||
init: ExpectedExports.init
|
init: ExpectedExports.init
|
||||||
uninit: ExpectedExports.uninit
|
uninit: ExpectedExports.uninit
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { Effects, ExpectedExports } from "../types"
|
import { Effects, ExpectedExports } from "../types"
|
||||||
import { Utils, utils } from "../util"
|
import { Utils, utils } from "../util/utils"
|
||||||
|
|
||||||
export type InstallFn<WrapperData> = (opts: {
|
export type InstallFn<Store, Vault> = (opts: {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
utils: Utils<WrapperData>
|
utils: Utils<Store, Vault>
|
||||||
}) => Promise<void>
|
}) => Promise<void>
|
||||||
export class Install<WrapperData> {
|
export class Install<Store, Vault> {
|
||||||
private constructor(readonly fn: InstallFn<WrapperData>) {}
|
private constructor(readonly fn: InstallFn<Store, Vault>) {}
|
||||||
static of<WrapperData>(fn: InstallFn<WrapperData>) {
|
static of<Store, Vault>(fn: InstallFn<Store, Vault>) {
|
||||||
return new Install(fn)
|
return new Install(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -15,10 +15,14 @@ export class Install<WrapperData> {
|
|||||||
effects,
|
effects,
|
||||||
previousVersion,
|
previousVersion,
|
||||||
}: Parameters<ExpectedExports.init>[0]) {
|
}: Parameters<ExpectedExports.init>[0]) {
|
||||||
if (!previousVersion) await this.fn({ effects, utils: utils(effects) })
|
if (!previousVersion)
|
||||||
|
await this.fn({
|
||||||
|
effects,
|
||||||
|
utils: utils(effects),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setupInstall<WrapperData>(fn: InstallFn<WrapperData>) {
|
export function setupInstall<Store, Vault>(fn: InstallFn<Store, Vault>) {
|
||||||
return Install.of(fn)
|
return Install.of(fn)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { Effects, ExpectedExports } from "../types"
|
import { Effects, ExpectedExports } from "../types"
|
||||||
import { Utils, utils } from "../util"
|
import { Utils, utils } from "../util/utils"
|
||||||
|
|
||||||
export type UninstallFn<WrapperData> = (opts: {
|
export type UninstallFn<Store, Vault> = (opts: {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
utils: Utils<WrapperData>
|
utils: Utils<Store, Vault>
|
||||||
}) => Promise<void>
|
}) => Promise<void>
|
||||||
export class Uninstall<WrapperData> {
|
export class Uninstall<Store, Vault> {
|
||||||
private constructor(readonly fn: UninstallFn<WrapperData>) {}
|
private constructor(readonly fn: UninstallFn<Store, Vault>) {}
|
||||||
static of<WrapperData>(fn: UninstallFn<WrapperData>) {
|
static of<Store, Vault>(fn: UninstallFn<Store, Vault>) {
|
||||||
return new Uninstall(fn)
|
return new Uninstall(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -15,10 +15,14 @@ export class Uninstall<WrapperData> {
|
|||||||
effects,
|
effects,
|
||||||
nextVersion,
|
nextVersion,
|
||||||
}: Parameters<ExpectedExports.uninit>[0]) {
|
}: Parameters<ExpectedExports.uninit>[0]) {
|
||||||
if (!nextVersion) await this.fn({ effects, utils: utils(effects) })
|
if (!nextVersion)
|
||||||
|
await this.fn({
|
||||||
|
effects,
|
||||||
|
utils: utils(effects),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setupUninstall<WrapperData>(fn: UninstallFn<WrapperData>) {
|
export function setupUninstall<Store, Vault>(fn: UninstallFn<Store, Vault>) {
|
||||||
return Uninstall.of(fn)
|
return Uninstall.of(fn)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { HealthReceipt } from "../health/HealthReceipt"
|
import { HealthReceipt } from "../health/HealthReceipt"
|
||||||
import { CheckResult } from "../health/checkFns"
|
import { CheckResult } from "../health/checkFns"
|
||||||
import { Trigger } from "../health/trigger"
|
import { Trigger } from "../trigger"
|
||||||
import { TriggerInput } from "../health/trigger/TriggerInput"
|
import { TriggerInput } from "../trigger/TriggerInput"
|
||||||
import { defaultTrigger } from "../health/trigger/defaultTrigger"
|
import { defaultTrigger } from "../trigger/defaultTrigger"
|
||||||
import { DaemonReturned, Effects, ValidIfNoStupidEscape } from "../types"
|
import { DaemonReturned, Effects, ValidIfNoStupidEscape } from "../types"
|
||||||
import { InterfaceReceipt } from "./interfaceReceipt"
|
import { InterfaceReceipt } from "./interfaceReceipt"
|
||||||
type Daemon<Ids extends string, Command extends string, Id extends string> = {
|
type Daemon<Ids extends string, Command extends string, Id extends string> = {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Effects, ExpectedExports } from "../types"
|
import { Effects, ExpectedExports } from "../types"
|
||||||
import { Utils, utils } from "../util"
|
import { createMainUtils } from "../util"
|
||||||
|
import { Utils, utils } from "../util/utils"
|
||||||
import { Daemons } from "./Daemons"
|
import { Daemons } from "./Daemons"
|
||||||
import "./exportInterfaces"
|
import "./exportInterfaces"
|
||||||
import "./LocalBinding"
|
import "./LocalBinding"
|
||||||
@@ -22,17 +23,17 @@ import "./Daemons"
|
|||||||
* @param fn
|
* @param fn
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const setupMain = <WrapperData>(
|
export const setupMain = <Store, Vault>(
|
||||||
fn: (o: {
|
fn: (o: {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
started(onTerm: () => void): null
|
started(onTerm: () => void): null
|
||||||
utils: Utils<WrapperData, {}>
|
utils: Utils<Store, Vault, {}>
|
||||||
}) => Promise<Daemons<any>>,
|
}) => Promise<Daemons<any>>,
|
||||||
): ExpectedExports.main => {
|
): ExpectedExports.main => {
|
||||||
return async (options) => {
|
return async (options) => {
|
||||||
const result = await fn({
|
const result = await fn({
|
||||||
...options,
|
...options,
|
||||||
utils: utils<WrapperData, {}>(options.effects),
|
utils: createMainUtils<Store, Vault>(options.effects),
|
||||||
})
|
})
|
||||||
await result.build().then((x) => x.wait())
|
await result.build().then((x) => x.wait())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ValidEmVer } from "../emverLite/mod"
|
import { ValidEmVer } from "../emverLite/mod"
|
||||||
import { ActionMetaData } from "../types"
|
import { ActionMetadata } from "../types"
|
||||||
|
|
||||||
export interface Container {
|
export interface Container {
|
||||||
/** This should be pointing to a docker container name */
|
/** This should be pointing to a docker container name */
|
||||||
@@ -14,65 +14,68 @@ export interface Container {
|
|||||||
|
|
||||||
export type ManifestVersion = ValidEmVer
|
export type ManifestVersion = ValidEmVer
|
||||||
|
|
||||||
export interface SDKManifest {
|
export type SDKManifest = {
|
||||||
/** The package identifier used by the OS. This must be unique amongst all other known packages */
|
/** The package identifier used by the OS. This must be unique amongst all other known packages */
|
||||||
id: string
|
readonly id: string
|
||||||
/** A human readable service title */
|
/** A human readable service title */
|
||||||
title: string
|
readonly title: string
|
||||||
/** Service version - accepts up to four digits, where the last confirms to revisions necessary for StartOs
|
/** Service version - accepts up to four digits, where the last confirms to revisions necessary for StartOs
|
||||||
* - see documentation: https://github.com/Start9Labs/emver-rs. This value will change with each release of
|
* - see documentation: https://github.com/Start9Labs/emver-rs. This value will change with each release of
|
||||||
* the service
|
* the service
|
||||||
*/
|
*/
|
||||||
version: ManifestVersion
|
readonly version: ManifestVersion
|
||||||
/** Release notes for the update - can be a string, paragraph or URL */
|
/** Release notes for the update - can be a string, paragraph or URL */
|
||||||
releaseNotes: string
|
readonly releaseNotes: string
|
||||||
/** The type of license for the project. Include the LICENSE in the root of the project directory. A license is required for a Start9 package.*/
|
/** The type of license for the project. Include the LICENSE in the root of the project directory. A license is required for a Start9 package.*/
|
||||||
license: string // name of license
|
readonly license: string // name of license
|
||||||
/** A list of normie (hosted, SaaS, custodial, etc) services this services intends to replace */
|
/** A list of normie (hosted, SaaS, custodial, etc) services this services intends to replace */
|
||||||
replaces: string[]
|
readonly replaces: Readonly<string[]>
|
||||||
/** The Start9 wrapper repository URL for the package. This repo contains the manifest file (this),
|
/** The Start9 wrapper repository URL for the package. This repo contains the manifest file (this),
|
||||||
* any scripts necessary for configuration, backups, actions, or health checks (more below). This key
|
* any scripts necessary for configuration, backups, actions, or health checks (more below). This key
|
||||||
* must exist. But could be embedded into the source repository
|
* must exist. But could be embedded into the source repository
|
||||||
*/
|
*/
|
||||||
wrapperRepo: string
|
readonly wrapperRepo: string
|
||||||
/** The original project repository URL. There is no upstream repo in this example */
|
/** The original project repository URL. There is no upstream repo in this example */
|
||||||
upstreamRepo: string
|
readonly upstreamRepo: string
|
||||||
/** URL to the support site / channel for the project. This key can be omitted if none exists, or it can link to the original project repository issues */
|
/** URL to the support site / channel for the project. This key can be omitted if none exists, or it can link to the original project repository issues */
|
||||||
supportSite: string
|
readonly supportSite: string
|
||||||
/** URL to the marketing site for the project. If there is no marketing site, it can link to the original project repository */
|
/** URL to the marketing site for the project. If there is no marketing site, it can link to the original project repository */
|
||||||
marketingSite: string
|
readonly marketingSite: string
|
||||||
/** URL where users can donate to the upstream project */
|
/** URL where users can donate to the upstream project */
|
||||||
donationUrl: string | null
|
readonly donationUrl: string | null
|
||||||
/**Human readable descriptions for the service. These are used throughout the StartOS user interface, primarily in the marketplace. */
|
/**Human readable descriptions for the service. These are used throughout the StartOS user interface, primarily in the marketplace. */
|
||||||
description: {
|
readonly description: {
|
||||||
/**This is the first description visible to the user in the marketplace */
|
/**This is the first description visible to the user in the marketplace */
|
||||||
short: string
|
readonly short: string
|
||||||
/** This description will display with additional details in the service's individual marketplace page */
|
/** This description will display with additional details in the service's individual marketplace page */
|
||||||
long: string
|
readonly long: string
|
||||||
}
|
}
|
||||||
/** These assets are static files necessary for packaging the service for Start9 (into an s9pk).
|
/** These assets are static files necessary for packaging the service for Start9 (into an s9pk).
|
||||||
* Each value is a path to the specified asset. If an asset is missing from this list, or otherwise
|
* Each value is a path to the specified asset. If an asset is missing from this list, or otherwise
|
||||||
* denoted, it will be defaulted to the values denoted below.
|
* denoted, it will be defaulted to the values denoted below.
|
||||||
*/
|
*/
|
||||||
assets: {
|
readonly assets: {
|
||||||
icon: string // file path
|
/** This is the file path for the icon that will be this packages icon on the ui */
|
||||||
instructions: string // file path
|
readonly icon: string
|
||||||
license: string // file path
|
/** Instructions path to be seen in the ui section of the package */
|
||||||
|
readonly instructions: string
|
||||||
|
/** license path */
|
||||||
|
readonly license: string
|
||||||
}
|
}
|
||||||
/** Defines the containers needed to run the main and mounted volumes */
|
/** Defines the containers needed to run the main and mounted volumes */
|
||||||
containers: Record<string, Container>
|
readonly containers: Record<string, Container>
|
||||||
/** This denotes any data, asset, or pointer volumes that should be connected when the "docker run" command is invoked */
|
/** This denotes any data, asset, or pointer volumes that should be connected when the "docker run" command is invoked */
|
||||||
volumes: Record<string, "data" | "assets">
|
readonly volumes: Record<string, "data" | "assets">
|
||||||
actions: Array<ActionMetaData>
|
|
||||||
alerts: {
|
readonly alerts: {
|
||||||
install: string | null
|
readonly install: string | null
|
||||||
update: string | null
|
readonly update: string | null
|
||||||
uninstall: string | null
|
readonly uninstall: string | null
|
||||||
restore: string | null
|
readonly restore: string | null
|
||||||
start: string | null
|
readonly start: string | null
|
||||||
stop: string | null
|
readonly stop: string | null
|
||||||
}
|
}
|
||||||
dependencies: Record<string, ManifestDependency>
|
readonly dependencies: Readonly<Record<string, ManifestDependency>>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ManifestDependency {
|
export interface ManifestDependency {
|
||||||
|
|||||||
61
lib/store/getStore.ts
Normal file
61
lib/store/getStore.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { Effects, EnsureStorePath } from "../types"
|
||||||
|
|
||||||
|
export class GetStore<Store, Path extends string> {
|
||||||
|
constructor(
|
||||||
|
readonly effects: Effects,
|
||||||
|
readonly path: Path & EnsureStorePath<Store, Path>,
|
||||||
|
readonly options: {
|
||||||
|
/** Defaults to what ever the package currently in */
|
||||||
|
packageId?: string | undefined
|
||||||
|
} = {},
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value of Store at the provided path. Restart the service if the value changes
|
||||||
|
*/
|
||||||
|
const() {
|
||||||
|
return this.effects.store.get<Store, Path>({
|
||||||
|
...this.options,
|
||||||
|
path: this.path as any,
|
||||||
|
callback: this.effects.restart,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Returns the value of Store at the provided path. Does nothing if the value changes
|
||||||
|
*/
|
||||||
|
once() {
|
||||||
|
return this.effects.store.get<Store, Path>({
|
||||||
|
...this.options,
|
||||||
|
path: this.path as any,
|
||||||
|
callback: () => {},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Watches the value of Store at the provided path. Takes a custom callback function to run whenever the value changes
|
||||||
|
*/
|
||||||
|
async *watch() {
|
||||||
|
while (true) {
|
||||||
|
let callback: () => void
|
||||||
|
const waitForNext = new Promise<void>((resolve) => {
|
||||||
|
callback = resolve
|
||||||
|
})
|
||||||
|
yield await this.effects.store.get<Store, Path>({
|
||||||
|
...this.options,
|
||||||
|
path: this.path as any,
|
||||||
|
callback: () => callback(),
|
||||||
|
})
|
||||||
|
await waitForNext
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function getStore<Store, Path extends string>(
|
||||||
|
effects: Effects,
|
||||||
|
path: Path & EnsureStorePath<Store, Path>,
|
||||||
|
options: {
|
||||||
|
/** Defaults to what ever the package currently in */
|
||||||
|
packageId?: string | undefined
|
||||||
|
} = {},
|
||||||
|
) {
|
||||||
|
return new GetStore<Store, Path>(effects, path as any, options)
|
||||||
|
}
|
||||||
@@ -4,9 +4,7 @@ import { List } from "../config/builder/list"
|
|||||||
import { Value } from "../config/builder/value"
|
import { Value } from "../config/builder/value"
|
||||||
import { Variants } from "../config/builder/variants"
|
import { Variants } from "../config/builder/variants"
|
||||||
import { ValueSpec } from "../config/configTypes"
|
import { ValueSpec } from "../config/configTypes"
|
||||||
import { Parser } from "ts-matches"
|
|
||||||
|
|
||||||
type test = unknown | { test: 5 }
|
|
||||||
describe("builder tests", () => {
|
describe("builder tests", () => {
|
||||||
test("text", async () => {
|
test("text", async () => {
|
||||||
const bitcoinPropertiesBuilt: {
|
const bitcoinPropertiesBuilt: {
|
||||||
@@ -299,7 +297,7 @@ describe("values", () => {
|
|||||||
utils: "utils",
|
utils: "utils",
|
||||||
} as any
|
} as any
|
||||||
test("toggle", async () => {
|
test("toggle", async () => {
|
||||||
const value = Value.dynamicToggle<{}>(async () => ({
|
const value = Value.dynamicToggle(async () => ({
|
||||||
name: "Testing",
|
name: "Testing",
|
||||||
description: null,
|
description: null,
|
||||||
warning: null,
|
warning: null,
|
||||||
@@ -364,7 +362,7 @@ describe("values", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
test("color", async () => {
|
test("color", async () => {
|
||||||
const value = Value.dynamicColor<null>(async () => ({
|
const value = Value.dynamicColor(async () => ({
|
||||||
name: "Testing",
|
name: "Testing",
|
||||||
required: false,
|
required: false,
|
||||||
description: null,
|
description: null,
|
||||||
@@ -385,7 +383,7 @@ describe("values", () => {
|
|||||||
test("datetime", async () => {
|
test("datetime", async () => {
|
||||||
const value = Value.dynamicDatetime<{ test: "a" }>(async ({ utils }) => {
|
const value = Value.dynamicDatetime<{ test: "a" }>(async ({ utils }) => {
|
||||||
;async () => {
|
;async () => {
|
||||||
;(await utils.getOwnWrapperData("/test").once()) satisfies "a"
|
;(await utils.store.getOwn("/test").once()) satisfies "a"
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -500,7 +498,8 @@ describe("values", () => {
|
|||||||
})
|
})
|
||||||
describe("filtering", () => {
|
describe("filtering", () => {
|
||||||
test("union", async () => {
|
test("union", async () => {
|
||||||
const value = Value.filteredUnion(() => ["a", "c"])(
|
const value = Value.filteredUnion(
|
||||||
|
() => ["a", "c"],
|
||||||
{
|
{
|
||||||
name: "Testing",
|
name: "Testing",
|
||||||
required: { default: null },
|
required: { default: null },
|
||||||
|
|||||||
@@ -423,7 +423,6 @@ oldSpecToBuilder(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
// convert this to `start-sdk/lib` for conversions
|
// convert this to `start-sdk/lib` for conversions
|
||||||
startSdk: "../..",
|
StartSdk: "./output.sdk",
|
||||||
wrapperData: "./output.wrapperData",
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
139
lib/test/mountDependencies.test.ts
Normal file
139
lib/test/mountDependencies.test.ts
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import { setupManifest } from "../manifest/setupManifest"
|
||||||
|
import { mountDependencies } from "../dependency/mountDependencies"
|
||||||
|
import {
|
||||||
|
BuildPath,
|
||||||
|
setupDependencyMounts,
|
||||||
|
} from "../dependency/setupDependencyMounts"
|
||||||
|
|
||||||
|
describe("mountDependencies", () => {
|
||||||
|
const clnManifest = setupManifest({
|
||||||
|
id: "cln",
|
||||||
|
title: "",
|
||||||
|
version: "1",
|
||||||
|
releaseNotes: "",
|
||||||
|
license: "",
|
||||||
|
replaces: [],
|
||||||
|
wrapperRepo: "",
|
||||||
|
upstreamRepo: "",
|
||||||
|
supportSite: "",
|
||||||
|
marketingSite: "",
|
||||||
|
donationUrl: null,
|
||||||
|
description: {
|
||||||
|
short: "",
|
||||||
|
long: "",
|
||||||
|
},
|
||||||
|
assets: {
|
||||||
|
icon: "",
|
||||||
|
instructions: "",
|
||||||
|
license: "",
|
||||||
|
},
|
||||||
|
containers: {},
|
||||||
|
volumes: { main: "data" },
|
||||||
|
alerts: {
|
||||||
|
install: null,
|
||||||
|
update: null,
|
||||||
|
uninstall: null,
|
||||||
|
restore: null,
|
||||||
|
start: null,
|
||||||
|
stop: null,
|
||||||
|
},
|
||||||
|
dependencies: {},
|
||||||
|
})
|
||||||
|
const lndManifest = setupManifest({
|
||||||
|
id: "lnd",
|
||||||
|
title: "",
|
||||||
|
version: "1",
|
||||||
|
releaseNotes: "",
|
||||||
|
license: "",
|
||||||
|
replaces: [],
|
||||||
|
wrapperRepo: "",
|
||||||
|
upstreamRepo: "",
|
||||||
|
supportSite: "",
|
||||||
|
marketingSite: "",
|
||||||
|
donationUrl: null,
|
||||||
|
description: {
|
||||||
|
short: "",
|
||||||
|
long: "",
|
||||||
|
},
|
||||||
|
assets: {
|
||||||
|
icon: "",
|
||||||
|
instructions: "",
|
||||||
|
license: "",
|
||||||
|
},
|
||||||
|
containers: {},
|
||||||
|
volumes: {},
|
||||||
|
alerts: {
|
||||||
|
install: null,
|
||||||
|
update: null,
|
||||||
|
uninstall: null,
|
||||||
|
restore: null,
|
||||||
|
start: null,
|
||||||
|
stop: null,
|
||||||
|
},
|
||||||
|
dependencies: {},
|
||||||
|
})
|
||||||
|
clnManifest.id
|
||||||
|
type test = BuildPath<{
|
||||||
|
name: "root"
|
||||||
|
manifest: typeof clnManifest
|
||||||
|
volume: "main"
|
||||||
|
path: "/"
|
||||||
|
readonly: true
|
||||||
|
}> extends BuildPath<{
|
||||||
|
name: "root"
|
||||||
|
manifest: typeof clnManifest
|
||||||
|
volume: "main2"
|
||||||
|
path: "/"
|
||||||
|
readonly: true
|
||||||
|
}>
|
||||||
|
? true
|
||||||
|
: false
|
||||||
|
|
||||||
|
test("Types work", () => {
|
||||||
|
const dependencyMounts = setupDependencyMounts()
|
||||||
|
.addPath({
|
||||||
|
name: "root",
|
||||||
|
volume: "main",
|
||||||
|
path: "/",
|
||||||
|
manifest: clnManifest,
|
||||||
|
readonly: true,
|
||||||
|
})
|
||||||
|
.addPath({
|
||||||
|
name: "root",
|
||||||
|
manifest: lndManifest,
|
||||||
|
volume: "main",
|
||||||
|
path: "/",
|
||||||
|
readonly: true,
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
;() => {
|
||||||
|
const test = mountDependencies(
|
||||||
|
null as any,
|
||||||
|
dependencyMounts,
|
||||||
|
) satisfies Promise<{
|
||||||
|
cln: {
|
||||||
|
main: {
|
||||||
|
root: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lnd: {
|
||||||
|
main: {
|
||||||
|
root: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}>
|
||||||
|
const test2 = mountDependencies(
|
||||||
|
null as any,
|
||||||
|
dependencyMounts.cln,
|
||||||
|
) satisfies Promise<{
|
||||||
|
main: { root: string }
|
||||||
|
}>
|
||||||
|
const test3 = mountDependencies(
|
||||||
|
null as any,
|
||||||
|
dependencyMounts.cln.main,
|
||||||
|
) satisfies Promise<{
|
||||||
|
root: string
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
49
lib/test/output.sdk.ts
Normal file
49
lib/test/output.sdk.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { StartSdk } from "../StartSdk"
|
||||||
|
import { setupManifest } from "../manifest/setupManifest"
|
||||||
|
|
||||||
|
export type Manifest = any
|
||||||
|
export const sdk = StartSdk.of()
|
||||||
|
.withManifest(
|
||||||
|
setupManifest({
|
||||||
|
id: "testOutput",
|
||||||
|
title: "",
|
||||||
|
version: "1.0",
|
||||||
|
releaseNotes: "",
|
||||||
|
license: "",
|
||||||
|
replaces: [],
|
||||||
|
wrapperRepo: "",
|
||||||
|
upstreamRepo: "",
|
||||||
|
supportSite: "",
|
||||||
|
marketingSite: "",
|
||||||
|
donationUrl: null,
|
||||||
|
description: {
|
||||||
|
short: "",
|
||||||
|
long: "",
|
||||||
|
},
|
||||||
|
assets: {
|
||||||
|
icon: "",
|
||||||
|
instructions: "",
|
||||||
|
license: "",
|
||||||
|
},
|
||||||
|
containers: {},
|
||||||
|
volumes: {},
|
||||||
|
alerts: {
|
||||||
|
install: null,
|
||||||
|
update: null,
|
||||||
|
uninstall: null,
|
||||||
|
restore: null,
|
||||||
|
start: null,
|
||||||
|
stop: null,
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
remoteTest: {
|
||||||
|
description: "",
|
||||||
|
requirement: { how: "", type: "opt-in" },
|
||||||
|
version: "1.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.withStore<{ storeRoot: { storeLeaf: "value" } }>()
|
||||||
|
.withVault<{ vaultRoot: "value" }>()
|
||||||
|
.build(true)
|
||||||
@@ -1 +0,0 @@
|
|||||||
export type WrapperData = {}
|
|
||||||
27
lib/test/setupDependencyConfig.test.ts
Normal file
27
lib/test/setupDependencyConfig.test.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { sdk } from "./output.sdk"
|
||||||
|
|
||||||
|
describe("setupDependencyConfig", () => {
|
||||||
|
test("test", () => {
|
||||||
|
const testConfig = sdk.Config.of({
|
||||||
|
test: sdk.Value.text({
|
||||||
|
name: "testValue",
|
||||||
|
required: false,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
const testConfig2 = sdk.Config.of({
|
||||||
|
test2: sdk.Value.text({
|
||||||
|
name: "testValue2",
|
||||||
|
required: false,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
const remoteTest = sdk.DependencyConfig.of({
|
||||||
|
localConfig: testConfig,
|
||||||
|
remoteConfig: testConfig2,
|
||||||
|
dependencyConfig: async ({}) => {},
|
||||||
|
})
|
||||||
|
sdk.setupDependencyConfig(testConfig, {
|
||||||
|
remoteTest,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,109 +1,110 @@
|
|||||||
import { Effects } from "../types"
|
import { Effects } from "../types"
|
||||||
import { utils } from "../util"
|
import { createMainUtils } from "../util"
|
||||||
|
import { utils } from "../util/utils"
|
||||||
|
|
||||||
type WrapperType = {
|
type Store = {
|
||||||
config: {
|
config: {
|
||||||
someValue: "a" | "b"
|
someValue: "a" | "b"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
type Vault = {
|
||||||
|
hello: string
|
||||||
|
}
|
||||||
const todo = <A>(): A => {
|
const todo = <A>(): A => {
|
||||||
throw new Error("not implemented")
|
throw new Error("not implemented")
|
||||||
}
|
}
|
||||||
const noop = () => {}
|
const noop = () => {}
|
||||||
describe("wrapperData", () => {
|
describe("Store", () => {
|
||||||
test("types", async () => {
|
test("types", async () => {
|
||||||
;async () => {
|
;async () => {
|
||||||
utils<WrapperType>(todo<Effects>()).setOwnWrapperData("/config", {
|
utils<Store>(todo<Effects>()).store.setOwn("/config", {
|
||||||
someValue: "a",
|
someValue: "a",
|
||||||
})
|
})
|
||||||
utils<WrapperType>(todo<Effects>()).setOwnWrapperData(
|
utils<Store>(todo<Effects>()).store.setOwn("/config/someValue", "b")
|
||||||
"/config/someValue",
|
utils<Store>(todo<Effects>()).store.setOwn("", {
|
||||||
"b",
|
|
||||||
)
|
|
||||||
utils<WrapperType>(todo<Effects>()).setOwnWrapperData("", {
|
|
||||||
config: { someValue: "b" },
|
config: { someValue: "b" },
|
||||||
})
|
})
|
||||||
utils<WrapperType>(todo<Effects>()).setOwnWrapperData(
|
utils<Store>(todo<Effects>()).store.setOwn(
|
||||||
"/config/someValue",
|
"/config/someValue",
|
||||||
|
|
||||||
// @ts-expect-error Type is wrong for the setting value
|
// @ts-expect-error Type is wrong for the setting value
|
||||||
5,
|
5,
|
||||||
)
|
)
|
||||||
utils<WrapperType>(todo<Effects>()).setOwnWrapperData(
|
utils(todo<Effects>()).store.setOwn(
|
||||||
// @ts-expect-error Path is wrong
|
// @ts-expect-error Path is wrong
|
||||||
"/config/someVae3lue",
|
"/config/someVae3lue",
|
||||||
"someValue",
|
"someValue",
|
||||||
)
|
)
|
||||||
|
|
||||||
todo<Effects>().setWrapperData<WrapperType, "/config/someValue">({
|
todo<Effects>().store.set<Store, "/config/someValue">({
|
||||||
path: "/config/someValue",
|
path: "/config/someValue",
|
||||||
value: "b",
|
value: "b",
|
||||||
})
|
})
|
||||||
todo<Effects>().setWrapperData<WrapperType, "/config/some2Value">({
|
todo<Effects>().store.set<Store, "/config/some2Value">({
|
||||||
//@ts-expect-error Path is wrong
|
//@ts-expect-error Path is wrong
|
||||||
path: "/config/someValue",
|
path: "/config/someValue",
|
||||||
//@ts-expect-error Path is wrong
|
//@ts-expect-error Path is wrong
|
||||||
value: "someValueIn",
|
value: "someValueIn",
|
||||||
})
|
})
|
||||||
todo<Effects>().setWrapperData<WrapperType, "/config/someValue">({
|
todo<Effects>().store.set<Store, "/config/someValue">({
|
||||||
//@ts-expect-error Path is wrong
|
//@ts-expect-error Path is wrong
|
||||||
path: "/config/some2Value",
|
path: "/config/some2Value",
|
||||||
value: "a",
|
value: "a",
|
||||||
})
|
})
|
||||||
;(await utils<WrapperType, {}>(todo<Effects>())
|
;(await createMainUtils<Store, Vault>(todo<Effects>())
|
||||||
.getOwnWrapperData("/config/someValue")
|
.store.getOwn("/config/someValue")
|
||||||
.const()) satisfies string
|
.const()) satisfies string
|
||||||
;(await utils<WrapperType, {}>(todo<Effects>())
|
;(await createMainUtils<Store, Vault>(todo<Effects>())
|
||||||
.getOwnWrapperData("/config")
|
.store.getOwn("/config")
|
||||||
.const()) satisfies WrapperType["config"]
|
.const()) satisfies Store["config"]
|
||||||
await utils<WrapperType, {}>(todo<Effects>())
|
await createMainUtils(todo<Effects>())
|
||||||
// @ts-expect-error Path is wrong
|
// @ts-expect-error Path is wrong
|
||||||
.getOwnWrapperData("/config/somdsfeValue")
|
.store.getOwn("/config/somdsfeValue")
|
||||||
.const()
|
.const()
|
||||||
/// ----------------- ERRORS -----------------
|
/// ----------------- ERRORS -----------------
|
||||||
|
|
||||||
utils<WrapperType>(todo<Effects>()).setOwnWrapperData("", {
|
utils<Store>(todo<Effects>()).store.setOwn("", {
|
||||||
// @ts-expect-error Type is wrong for the setting value
|
// @ts-expect-error Type is wrong for the setting value
|
||||||
config: { someValue: "notInAOrB" },
|
config: { someValue: "notInAOrB" },
|
||||||
})
|
})
|
||||||
utils<WrapperType>(todo<Effects>()).setOwnWrapperData(
|
utils<Store>(todo<Effects>()).store.setOwn(
|
||||||
"/config/someValue",
|
"/config/someValue",
|
||||||
// @ts-expect-error Type is wrong for the setting value
|
// @ts-expect-error Type is wrong for the setting value
|
||||||
"notInAOrB",
|
"notInAOrB",
|
||||||
)
|
)
|
||||||
;(await utils<WrapperType>(todo<Effects>())
|
;(await utils<Store>(todo<Effects>())
|
||||||
.getOwnWrapperData("/config/someValue")
|
.store.getOwn("/config/someValue")
|
||||||
// @ts-expect-error Const should normally not be callable
|
// @ts-expect-error Const should normally not be callable
|
||||||
.const()) satisfies string
|
.const()) satisfies string
|
||||||
;(await utils<WrapperType>(todo<Effects>())
|
;(await utils<Store>(todo<Effects>())
|
||||||
.getOwnWrapperData("/config")
|
.store.getOwn("/config")
|
||||||
// @ts-expect-error Const should normally not be callable
|
// @ts-expect-error Const should normally not be callable
|
||||||
.const()) satisfies WrapperType["config"]
|
.const()) satisfies Store["config"]
|
||||||
await utils<WrapperType>(todo<Effects>())
|
await utils<Store>(todo<Effects>())
|
||||||
// @ts-expect-error Path is wrong
|
// @ts-expect-error Path is wrong
|
||||||
.getOwnWrapperData("/config/somdsfeValue")
|
.store.getOwn("/config/somdsfeValue")
|
||||||
// @ts-expect-error Const should normally not be callable
|
// @ts-expect-error Const should normally not be callable
|
||||||
.const()
|
.const()
|
||||||
|
|
||||||
///
|
///
|
||||||
;(await utils<WrapperType>(todo<Effects>())
|
;(await utils<Store>(todo<Effects>())
|
||||||
.getOwnWrapperData("/config/someValue")
|
.store.getOwn("/config/someValue")
|
||||||
// @ts-expect-error satisfies type is wrong
|
// @ts-expect-error satisfies type is wrong
|
||||||
.const()) satisfies number
|
.const()) satisfies number
|
||||||
;(await utils<WrapperType, {}>(todo<Effects>())
|
;(await createMainUtils(todo<Effects>())
|
||||||
// @ts-expect-error Path is wrong
|
// @ts-expect-error Path is wrong
|
||||||
.getOwnWrapperData("/config/")
|
.store.getOwn("/config/")
|
||||||
.const()) satisfies WrapperType["config"]
|
.const()) satisfies Store["config"]
|
||||||
;(await todo<Effects>().getWrapperData<WrapperType, "/config/someValue">({
|
;(await todo<Effects>().store.get<Store, "/config/someValue">({
|
||||||
path: "/config/someValue",
|
path: "/config/someValue",
|
||||||
callback: noop,
|
callback: noop,
|
||||||
})) satisfies string
|
})) satisfies string
|
||||||
await todo<Effects>().getWrapperData<WrapperType, "/config/someValue">({
|
await todo<Effects>().store.get<Store, "/config/someValue">({
|
||||||
// @ts-expect-error Path is wrong as in it doesn't match above
|
// @ts-expect-error Path is wrong as in it doesn't match above
|
||||||
path: "/config/someV2alue",
|
path: "/config/someV2alue",
|
||||||
callback: noop,
|
callback: noop,
|
||||||
})
|
})
|
||||||
await todo<Effects>().getWrapperData<WrapperType, "/config/someV2alue">({
|
await todo<Effects>().store.get<Store, "/config/someV2alue">({
|
||||||
// @ts-expect-error Path is wrong as in it doesn't exists in wrapper type
|
// @ts-expect-error Path is wrong as in it doesn't exists in wrapper type
|
||||||
path: "/config/someV2alue",
|
path: "/config/someV2alue",
|
||||||
callback: noop,
|
callback: noop,
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { HealthStatus } from "../../types"
|
import { HealthStatus } from "../types"
|
||||||
|
|
||||||
export type TriggerInput = {
|
export type TriggerInput = {
|
||||||
lastResult?: HealthStatus
|
lastResult?: HealthStatus
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import { TriggerInput } from "./TriggerInput"
|
|
||||||
import { Trigger } from "./index"
|
import { Trigger } from "./index"
|
||||||
|
|
||||||
export function changeOnFirstSuccess(o: {
|
export function changeOnFirstSuccess(o: {
|
||||||
67
lib/types.ts
67
lib/types.ts
@@ -45,6 +45,10 @@ export namespace ExpectedExports {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type actionsMetadata = (options: {
|
||||||
|
effects: Effects
|
||||||
|
}) => Promise<Array<ActionMetadata>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the entrypoint for the main container. Used to start up something like the service that the
|
* This is the entrypoint for the main container. Used to start up something like the service that the
|
||||||
* package represents, like running a bitcoind in a bitcoind-wrapper.
|
* package represents, like running a bitcoind in a bitcoind-wrapper.
|
||||||
@@ -81,7 +85,7 @@ export namespace ExpectedExports {
|
|||||||
/** Auto configure is used to make sure that other dependencies have the values t
|
/** Auto configure is used to make sure that other dependencies have the values t
|
||||||
* that this service could use.
|
* that this service could use.
|
||||||
*/
|
*/
|
||||||
export type autoConfig = Record<PackageId, AutoConfigure>
|
export type dependencyConfig = Record<PackageId, DependencyConfig>
|
||||||
}
|
}
|
||||||
export type TimeMs = number
|
export type TimeMs = number
|
||||||
export type VersionString = string
|
export type VersionString = string
|
||||||
@@ -90,7 +94,7 @@ export type VersionString = string
|
|||||||
* AutoConfigure is used as the value to the key of package id,
|
* AutoConfigure is used as the value to the key of package id,
|
||||||
* this is used to make sure that other dependencies have the values that this service could use.
|
* this is used to make sure that other dependencies have the values that this service could use.
|
||||||
*/
|
*/
|
||||||
export type AutoConfigure = {
|
export type DependencyConfig = {
|
||||||
/** Checks are called to make sure that our dependency is in the correct shape. If a known error is returned we know that the dependency needs modification */
|
/** Checks are called to make sure that our dependency is in the correct shape. If a known error is returned we know that the dependency needs modification */
|
||||||
check(options: {
|
check(options: {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
@@ -150,10 +154,11 @@ export type DaemonReturned = {
|
|||||||
term(): Promise<void>
|
term(): Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ActionMetaData = {
|
export type ActionMetadata = {
|
||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
id: string
|
id: string
|
||||||
|
input: InputSpec
|
||||||
allowedStatuses: "only-running" | "only-stopped" | "any"
|
allowedStatuses: "only-running" | "only-stopped" | "any"
|
||||||
/**
|
/**
|
||||||
* So the ordering of the actions is by alphabetical order of the group, then followed by the alphabetical of the actions
|
* So the ordering of the actions is by alphabetical order of the group, then followed by the alphabetical of the actions
|
||||||
@@ -251,26 +256,27 @@ export type Effects = {
|
|||||||
progress: () => Promise<number>
|
progress: () => Promise<number>
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get a value in a json like data, can be observed and subscribed */
|
store: {
|
||||||
getWrapperData<WrapperData = never, Path extends string = never>(options: {
|
/** Get a value in a json like data, can be observed and subscribed */
|
||||||
/** If there is no packageId it is assumed the current package */
|
get<Store = never, Path extends string = never>(options: {
|
||||||
packageId?: string
|
/** If there is no packageId it is assumed the current package */
|
||||||
/** The path defaults to root level, using the [JsonPath](https://jsonpath.com/) */
|
packageId?: string
|
||||||
path: Path & EnsureWrapperDataPath<WrapperData, Path>
|
/** The path defaults to root level, using the [JsonPath](https://jsonpath.com/) */
|
||||||
callback: (config: unknown, previousConfig: unknown) => void
|
path: Path & EnsureStorePath<Store, Path>
|
||||||
}): Promise<ExtractWrapperData<WrapperData, Path>>
|
callback: (config: unknown, previousConfig: unknown) => void
|
||||||
|
}): Promise<ExtractStore<Store, Path>>
|
||||||
|
/** Used to store values that can be accessed and subscribed to */
|
||||||
|
set<Store = never, Path extends string = never>(options: {
|
||||||
|
/** Sets the value for the wrapper at the path, it will override, using the [JsonPath](https://jsonpath.com/) */
|
||||||
|
path: Path & EnsureStorePath<Store, Path>
|
||||||
|
value: ExtractStore<Store, Path>
|
||||||
|
}): Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
getSystemSmtp(input: {
|
getSystemSmtp(input: {
|
||||||
callback: (config: unknown, previousConfig: unknown) => void
|
callback: (config: unknown, previousConfig: unknown) => void
|
||||||
}): Promise<SmtpValue>
|
}): Promise<SmtpValue>
|
||||||
|
|
||||||
/** Used to store values that can be accessed and subscribed to */
|
|
||||||
setWrapperData<WrapperData = never, Path extends string = never>(options: {
|
|
||||||
/** Sets the value for the wrapper at the path, it will override, using the [JsonPath](https://jsonpath.com/) */
|
|
||||||
path: Path & EnsureWrapperDataPath<WrapperData, Path>
|
|
||||||
value: ExtractWrapperData<WrapperData, Path>
|
|
||||||
}): Promise<void>
|
|
||||||
|
|
||||||
getLocalHostname(): Promise<string>
|
getLocalHostname(): Promise<string>
|
||||||
getIPHostname(): Promise<string[]>
|
getIPHostname(): Promise<string[]>
|
||||||
/** Get the address for another service for tor interfaces */
|
/** Get the address for another service for tor interfaces */
|
||||||
@@ -314,7 +320,7 @@ export type Effects = {
|
|||||||
*
|
*
|
||||||
* @param options
|
* @param options
|
||||||
*/
|
*/
|
||||||
exportAction(options: ActionMetaData): Promise<void>
|
exportAction(options: ActionMetadata): Promise<void>
|
||||||
/**
|
/**
|
||||||
* Remove an action that was exported. Used problably during main or during setConfig.
|
* Remove an action that was exported. Used problably during main or during setConfig.
|
||||||
*/
|
*/
|
||||||
@@ -377,7 +383,8 @@ export type Effects = {
|
|||||||
|
|
||||||
mount(options: {
|
mount(options: {
|
||||||
location: {
|
location: {
|
||||||
volumeId: string
|
/** If there is no volumeId then we mount to runMedia a special mounting location */
|
||||||
|
volumeId?: string
|
||||||
path: string
|
path: string
|
||||||
}
|
}
|
||||||
target: {
|
target: {
|
||||||
@@ -386,13 +393,13 @@ export type Effects = {
|
|||||||
path: string
|
path: string
|
||||||
readonly: boolean
|
readonly: boolean
|
||||||
}
|
}
|
||||||
}): Promise<void>
|
}): Promise<string>
|
||||||
|
|
||||||
stopped(packageId?: string): Promise<boolean>
|
stopped(packageId?: string): Promise<boolean>
|
||||||
|
|
||||||
vault: {
|
vault: {
|
||||||
list(): Promise<string[]>
|
list(): Promise<string[]>
|
||||||
get(opt: { key: string }): Promise<string>
|
get(opt: { key: string; callback: () => void }): Promise<string>
|
||||||
set(opt: { key: string; value: string }): Promise<void>
|
set(opt: { key: string; value: string }): Promise<void>
|
||||||
move(opt: { fromKey: string; toKey: string }): Promise<void>
|
move(opt: { fromKey: string; toKey: string }): Promise<void>
|
||||||
delete(opt: { key: string }): Promise<void>
|
delete(opt: { key: string }): Promise<void>
|
||||||
@@ -400,20 +407,20 @@ export type Effects = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
export type ExtractWrapperData<WrapperData, Path extends string> =
|
export type ExtractStore<Store, Path extends string> =
|
||||||
Path extends `/${infer A }/${infer Rest }` ? (A extends keyof WrapperData ? ExtractWrapperData<WrapperData[A], `/${Rest}`> : never) :
|
Path extends `/${infer A }/${infer Rest }` ? (A extends keyof Store ? ExtractStore<Store[A], `/${Rest}`> : never) :
|
||||||
Path extends `/${infer A }` ? (A extends keyof WrapperData ? WrapperData[A] : never) :
|
Path extends `/${infer A }` ? (A extends keyof Store ? Store[A] : never) :
|
||||||
Path extends '' ? WrapperData :
|
Path extends '' ? Store :
|
||||||
never
|
never
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
type _EnsureWrapperDataPath<WrapperData, Path extends string, Origin extends string> =
|
type _EnsureStorePath<Store, Path extends string, Origin extends string> =
|
||||||
Path extends`/${infer A }/${infer Rest}` ? (WrapperData extends {[K in A & string]: infer NextWrapperData} ? _EnsureWrapperDataPath<NextWrapperData, `/${Rest}`, Origin> : never) :
|
Path extends`/${infer A }/${infer Rest}` ? (Store extends {[K in A & string]: infer NextStore} ? _EnsureStorePath<NextStore, `/${Rest}`, Origin> : never) :
|
||||||
Path extends `/${infer A }` ? (WrapperData extends {[K in A]: infer B} ? Origin : never) :
|
Path extends `/${infer A }` ? (Store extends {[K in A]: infer B} ? Origin : never) :
|
||||||
Path extends '' ? Origin :
|
Path extends '' ? Origin :
|
||||||
never
|
never
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
export type EnsureWrapperDataPath<WrapperData, Path extends string> = _EnsureWrapperDataPath<WrapperData, Path, Path>
|
export type EnsureStorePath<Store, Path extends string> = _EnsureStorePath<Store, Path, Path>
|
||||||
|
|
||||||
/** rsync options: https://linux.die.net/man/1/rsync
|
/** rsync options: https://linux.die.net/man/1/rsync
|
||||||
*/
|
*/
|
||||||
|
|||||||
44
lib/util/getVault.ts
Normal file
44
lib/util/getVault.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { Effects, EnsureStorePath } from "../types"
|
||||||
|
|
||||||
|
export class GetVault<Vault> {
|
||||||
|
constructor(readonly effects: Effects, readonly key: keyof Vault & string) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value of Store at the provided path. Restart the service if the value changes
|
||||||
|
*/
|
||||||
|
const() {
|
||||||
|
return this.effects.vault.get({
|
||||||
|
key: this.key,
|
||||||
|
callback: this.effects.restart,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Returns the value of Store at the provided path. Does nothing if the value changes
|
||||||
|
*/
|
||||||
|
once() {
|
||||||
|
return this.effects.vault.get({
|
||||||
|
key: this.key,
|
||||||
|
callback: () => {},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Watches the value of Store at the provided path. Takes a custom callback function to run whenever the value changes
|
||||||
|
*/
|
||||||
|
async *watch() {
|
||||||
|
while (true) {
|
||||||
|
let callback: () => void
|
||||||
|
const waitForNext = new Promise<void>((resolve) => {
|
||||||
|
callback = resolve
|
||||||
|
})
|
||||||
|
yield await this.effects.vault.get({
|
||||||
|
key: this.key,
|
||||||
|
callback: () => callback(),
|
||||||
|
})
|
||||||
|
await waitForNext
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function getVault<Vault>(effects: Effects, key: keyof Vault & string) {
|
||||||
|
return new GetVault<Vault>(effects, key)
|
||||||
|
}
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import { Parser } from "ts-matches"
|
|
||||||
import { Effects, EnsureWrapperDataPath, ExtractWrapperData } from "../types"
|
|
||||||
import { NoAny } from "."
|
|
||||||
|
|
||||||
export class GetWrapperData<WrapperData, Path extends string> {
|
|
||||||
constructor(
|
|
||||||
readonly effects: Effects,
|
|
||||||
readonly path: Path & EnsureWrapperDataPath<WrapperData, Path>,
|
|
||||||
readonly options: {
|
|
||||||
/** Defaults to what ever the package currently in */
|
|
||||||
packageId?: string | undefined
|
|
||||||
} = {},
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value of WrapperData at the provided path. Restart the service if the value changes
|
|
||||||
*/
|
|
||||||
const() {
|
|
||||||
return this.effects.getWrapperData<WrapperData, Path>({
|
|
||||||
...this.options,
|
|
||||||
path: this.path as any,
|
|
||||||
callback: this.effects.restart,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Returns the value of WrapperData at the provided path. Does nothing if the value changes
|
|
||||||
*/
|
|
||||||
once() {
|
|
||||||
return this.effects.getWrapperData<WrapperData, Path>({
|
|
||||||
...this.options,
|
|
||||||
path: this.path as any,
|
|
||||||
callback: () => {},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Watches the value of WrapperData at the provided path. Takes a custom callback function to run whenever the value changes
|
|
||||||
*/
|
|
||||||
async *watch() {
|
|
||||||
while (true) {
|
|
||||||
let callback: () => void
|
|
||||||
const waitForNext = new Promise<void>((resolve) => {
|
|
||||||
callback = resolve
|
|
||||||
})
|
|
||||||
yield await this.effects.getWrapperData<WrapperData, Path>({
|
|
||||||
...this.options,
|
|
||||||
path: this.path as any,
|
|
||||||
callback: () => callback(),
|
|
||||||
})
|
|
||||||
await waitForNext
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export function getWrapperData<WrapperData, Path extends string>(
|
|
||||||
effects: Effects,
|
|
||||||
path: Path & EnsureWrapperDataPath<WrapperData, Path>,
|
|
||||||
options: {
|
|
||||||
/** Defaults to what ever the package currently in */
|
|
||||||
packageId?: string | undefined
|
|
||||||
} = {},
|
|
||||||
) {
|
|
||||||
return new GetWrapperData<WrapperData, Path>(effects, path as any, options)
|
|
||||||
}
|
|
||||||
@@ -1,28 +1,12 @@
|
|||||||
import { Parser, string } from "ts-matches"
|
|
||||||
import * as T from "../types"
|
import * as T from "../types"
|
||||||
import FileHelper from "./fileHelper"
|
|
||||||
import nullIfEmpty from "./nullIfEmpty"
|
|
||||||
import { GetWrapperData, getWrapperData } from "./getWrapperData"
|
|
||||||
import {
|
|
||||||
CheckResult,
|
|
||||||
checkPortListening,
|
|
||||||
checkWebUrl,
|
|
||||||
} from "../health/checkFns"
|
|
||||||
import { ExtractWrapperData } from "../types"
|
|
||||||
import { GetSystemSmtp } from "./GetSystemSmtp"
|
|
||||||
|
|
||||||
import "./nullIfEmpty"
|
import "./nullIfEmpty"
|
||||||
import "./fileHelper"
|
import "./fileHelper"
|
||||||
import "./getWrapperData"
|
import "../store/getStore"
|
||||||
import "./deepEqual"
|
import "./deepEqual"
|
||||||
import "./deepMerge"
|
import "./deepMerge"
|
||||||
import "./once"
|
import "./once"
|
||||||
import { LocalBinding } from "../mainFn/LocalBinding"
|
import { utils } from "./utils"
|
||||||
import { LocalPort } from "../mainFn/LocalPort"
|
|
||||||
import { NetworkBuilder } from "../mainFn/NetworkBuilder"
|
|
||||||
import { TorHostname } from "../mainFn/TorHostname"
|
|
||||||
import { DefaultString } from "../config/configTypes"
|
|
||||||
import { getDefaultString } from "./getDefaultString"
|
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
export type FlattenIntersection<T> =
|
export type FlattenIntersection<T> =
|
||||||
@@ -37,106 +21,9 @@ export const isKnownError = (e: unknown): e is T.KnownError =>
|
|||||||
|
|
||||||
declare const affine: unique symbol
|
declare const affine: unique symbol
|
||||||
|
|
||||||
export type WrapperDataOptionals<WrapperData, Path extends string> = {
|
export const createUtils = utils
|
||||||
validator?: Parser<unknown, ExtractWrapperData<WrapperData, Path>>
|
export const createMainUtils = <Store, Vault>(effects: T.Effects) =>
|
||||||
/** Defaults to what ever the package currently in */
|
createUtils<Store, Vault, {}>(effects)
|
||||||
packageId?: string | undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Utils<WD, WrapperOverWrite = { const: never }> = {
|
|
||||||
createOrUpdateVault: (opts: {
|
|
||||||
key: string
|
|
||||||
value: string | null | undefined
|
|
||||||
generator: DefaultString
|
|
||||||
}) => Promise<null | string>
|
|
||||||
readFile: <A>(fileHelper: FileHelper<A>) => ReturnType<FileHelper<A>["read"]>
|
|
||||||
writeFile: <A>(
|
|
||||||
fileHelper: FileHelper<A>,
|
|
||||||
data: A,
|
|
||||||
) => ReturnType<FileHelper<A>["write"]>
|
|
||||||
getSystemSmtp: () => GetSystemSmtp & WrapperOverWrite
|
|
||||||
getWrapperData: <Path extends string>(
|
|
||||||
packageId: string,
|
|
||||||
path: T.EnsureWrapperDataPath<WD, Path>,
|
|
||||||
) => GetWrapperData<WD, Path> & WrapperOverWrite
|
|
||||||
getOwnWrapperData: <Path extends string>(
|
|
||||||
path: T.EnsureWrapperDataPath<WD, Path>,
|
|
||||||
) => GetWrapperData<WD, Path> & WrapperOverWrite
|
|
||||||
setOwnWrapperData: <Path extends string | never>(
|
|
||||||
path: T.EnsureWrapperDataPath<WD, Path>,
|
|
||||||
value: ExtractWrapperData<WD, Path>,
|
|
||||||
) => Promise<void>
|
|
||||||
checkPortListening(
|
|
||||||
port: number,
|
|
||||||
options: {
|
|
||||||
errorMessage: string
|
|
||||||
successMessage: string
|
|
||||||
timeoutMessage?: string
|
|
||||||
timeout?: number
|
|
||||||
},
|
|
||||||
): Promise<CheckResult>
|
|
||||||
checkWebUrl(
|
|
||||||
url: string,
|
|
||||||
options?: {
|
|
||||||
timeout?: number
|
|
||||||
successMessage?: string
|
|
||||||
errorMessage?: string
|
|
||||||
},
|
|
||||||
): Promise<CheckResult>
|
|
||||||
bindLan: (port: number) => Promise<LocalBinding>
|
|
||||||
networkBuilder: () => NetworkBuilder
|
|
||||||
torHostName: (id: string) => TorHostname
|
|
||||||
nullIfEmpty: typeof nullIfEmpty
|
|
||||||
}
|
|
||||||
export const utils = <WrapperData = never, WrapperOverWrite = { const: never }>(
|
|
||||||
effects: T.Effects,
|
|
||||||
): Utils<WrapperData, WrapperOverWrite> => ({
|
|
||||||
createOrUpdateVault: async ({
|
|
||||||
key,
|
|
||||||
value,
|
|
||||||
generator,
|
|
||||||
}: {
|
|
||||||
key: string
|
|
||||||
value: string | null | undefined
|
|
||||||
generator: DefaultString
|
|
||||||
}) => {
|
|
||||||
if (value) {
|
|
||||||
await effects.vault.set({ key, value })
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
if (await effects.vault.get({ key })) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
const newValue = getDefaultString(generator)
|
|
||||||
await effects.vault.set({ key, value: newValue })
|
|
||||||
return newValue
|
|
||||||
},
|
|
||||||
getSystemSmtp: () =>
|
|
||||||
new GetSystemSmtp(effects) as GetSystemSmtp & WrapperOverWrite,
|
|
||||||
readFile: <A>(fileHelper: FileHelper<A>) => fileHelper.read(effects),
|
|
||||||
writeFile: <A>(fileHelper: FileHelper<A>, data: A) =>
|
|
||||||
fileHelper.write(data, effects),
|
|
||||||
nullIfEmpty,
|
|
||||||
getWrapperData: <WrapperData = never, Path extends string = never>(
|
|
||||||
packageId: string,
|
|
||||||
path: T.EnsureWrapperDataPath<WrapperData, Path>,
|
|
||||||
) =>
|
|
||||||
getWrapperData<WrapperData, Path>(effects, path as any, {
|
|
||||||
packageId,
|
|
||||||
}) as any,
|
|
||||||
getOwnWrapperData: <Path extends string>(
|
|
||||||
path: T.EnsureWrapperDataPath<WrapperData, Path>,
|
|
||||||
) => getWrapperData<WrapperData, Path>(effects, path as any) as any,
|
|
||||||
setOwnWrapperData: <Path extends string | never>(
|
|
||||||
path: T.EnsureWrapperDataPath<WrapperData, Path>,
|
|
||||||
value: ExtractWrapperData<WrapperData, Path>,
|
|
||||||
) => effects.setWrapperData<WrapperData, Path>({ value, path: path as any }),
|
|
||||||
checkPortListening: checkPortListening.bind(null, effects),
|
|
||||||
checkWebUrl: checkWebUrl.bind(null, effects),
|
|
||||||
bindLan: async (port: number) => LocalPort.bindLan(effects, port),
|
|
||||||
networkBuilder: () => NetworkBuilder.of(effects),
|
|
||||||
torHostName: (id: string) => TorHostname.of(effects, id),
|
|
||||||
})
|
|
||||||
|
|
||||||
type NeverPossible = { [affine]: string }
|
type NeverPossible = { [affine]: string }
|
||||||
export type NoAny<A> = NeverPossible extends A
|
export type NoAny<A> = NeverPossible extends A
|
||||||
|
|||||||
160
lib/util/utils.ts
Normal file
160
lib/util/utils.ts
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
import * as T from "../types"
|
||||||
|
import FileHelper from "./fileHelper"
|
||||||
|
import nullIfEmpty from "./nullIfEmpty"
|
||||||
|
import {
|
||||||
|
CheckResult,
|
||||||
|
checkPortListening,
|
||||||
|
checkWebUrl,
|
||||||
|
} from "../health/checkFns"
|
||||||
|
import { ExtractStore } from "../types"
|
||||||
|
import { GetSystemSmtp } from "./GetSystemSmtp"
|
||||||
|
import { LocalBinding } from "../mainFn/LocalBinding"
|
||||||
|
import { LocalPort } from "../mainFn/LocalPort"
|
||||||
|
import { NetworkBuilder } from "../mainFn/NetworkBuilder"
|
||||||
|
import { TorHostname } from "../mainFn/TorHostname"
|
||||||
|
import { DefaultString } from "../config/configTypes"
|
||||||
|
import { getDefaultString } from "./getDefaultString"
|
||||||
|
import { GetStore, getStore } from "../store/getStore"
|
||||||
|
import { GetVault, getVault } from "./getVault"
|
||||||
|
import {
|
||||||
|
MountDependenciesOut,
|
||||||
|
mountDependencies,
|
||||||
|
} from "../dependency/mountDependencies"
|
||||||
|
import {
|
||||||
|
ManifestId,
|
||||||
|
VolumeName,
|
||||||
|
NamedPath,
|
||||||
|
Path,
|
||||||
|
} from "../dependency/setupDependencyMounts"
|
||||||
|
|
||||||
|
export type Utils<Store, Vault, WrapperOverWrite = { const: never }> = {
|
||||||
|
createOrUpdateVault: (opts: {
|
||||||
|
key: string
|
||||||
|
value: string | null | undefined
|
||||||
|
generator: DefaultString
|
||||||
|
}) => Promise<null | string>
|
||||||
|
readFile: <A>(fileHelper: FileHelper<A>) => ReturnType<FileHelper<A>["read"]>
|
||||||
|
writeFile: <A>(
|
||||||
|
fileHelper: FileHelper<A>,
|
||||||
|
data: A,
|
||||||
|
) => ReturnType<FileHelper<A>["write"]>
|
||||||
|
getSystemSmtp: () => GetSystemSmtp & WrapperOverWrite
|
||||||
|
store: {
|
||||||
|
get: <Path extends string>(
|
||||||
|
packageId: string,
|
||||||
|
path: T.EnsureStorePath<Store, Path>,
|
||||||
|
) => GetStore<Store, Path> & WrapperOverWrite
|
||||||
|
getOwn: <Path extends string>(
|
||||||
|
path: T.EnsureStorePath<Store, Path>,
|
||||||
|
) => GetStore<Store, Path> & WrapperOverWrite
|
||||||
|
setOwn: <Path extends string | never>(
|
||||||
|
path: T.EnsureStorePath<Store, Path>,
|
||||||
|
value: ExtractStore<Store, Path>,
|
||||||
|
) => Promise<void>
|
||||||
|
}
|
||||||
|
vault: {
|
||||||
|
get: (key: keyof Vault & string) => GetVault<Vault> & WrapperOverWrite
|
||||||
|
set: (key: keyof Vault & string, value: string) => Promise<void>
|
||||||
|
}
|
||||||
|
checkPortListening(
|
||||||
|
port: number,
|
||||||
|
options: {
|
||||||
|
errorMessage: string
|
||||||
|
successMessage: string
|
||||||
|
timeoutMessage?: string
|
||||||
|
timeout?: number
|
||||||
|
},
|
||||||
|
): Promise<CheckResult>
|
||||||
|
checkWebUrl(
|
||||||
|
url: string,
|
||||||
|
options?: {
|
||||||
|
timeout?: number
|
||||||
|
successMessage?: string
|
||||||
|
errorMessage?: string
|
||||||
|
},
|
||||||
|
): Promise<CheckResult>
|
||||||
|
bindLan: (port: number) => Promise<LocalBinding>
|
||||||
|
networkBuilder: () => NetworkBuilder
|
||||||
|
torHostName: (id: string) => TorHostname
|
||||||
|
nullIfEmpty: typeof nullIfEmpty
|
||||||
|
mountDependencies: <
|
||||||
|
In extends
|
||||||
|
| Record<ManifestId, Record<VolumeName, Record<NamedPath, Path>>>
|
||||||
|
| Record<VolumeName, Record<NamedPath, Path>>
|
||||||
|
| Record<NamedPath, Path>
|
||||||
|
| Path,
|
||||||
|
>(
|
||||||
|
value: In,
|
||||||
|
) => Promise<MountDependenciesOut<In>>
|
||||||
|
}
|
||||||
|
export const utils = <
|
||||||
|
Store = never,
|
||||||
|
Vault = never,
|
||||||
|
WrapperOverWrite = { const: never },
|
||||||
|
>(
|
||||||
|
effects: T.Effects,
|
||||||
|
): Utils<Store, Vault, WrapperOverWrite> => ({
|
||||||
|
createOrUpdateVault: async ({
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
generator,
|
||||||
|
}: {
|
||||||
|
key: string
|
||||||
|
value: string | null | undefined
|
||||||
|
generator: DefaultString
|
||||||
|
}) => {
|
||||||
|
if (value) {
|
||||||
|
await effects.vault.set({ key, value })
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
if (await effects.vault.get({ key, callback: noop })) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const newValue = getDefaultString(generator)
|
||||||
|
await effects.vault.set({ key, value: newValue })
|
||||||
|
return newValue
|
||||||
|
},
|
||||||
|
getSystemSmtp: () =>
|
||||||
|
new GetSystemSmtp(effects) as GetSystemSmtp & WrapperOverWrite,
|
||||||
|
readFile: <A>(fileHelper: FileHelper<A>) => fileHelper.read(effects),
|
||||||
|
writeFile: <A>(fileHelper: FileHelper<A>, data: A) =>
|
||||||
|
fileHelper.write(data, effects),
|
||||||
|
nullIfEmpty,
|
||||||
|
store: {
|
||||||
|
get: <Path extends string = never>(
|
||||||
|
packageId: string,
|
||||||
|
path: T.EnsureStorePath<Store, Path>,
|
||||||
|
) =>
|
||||||
|
getStore<Store, Path>(effects, path as any, {
|
||||||
|
packageId,
|
||||||
|
}) as any,
|
||||||
|
getOwn: <Path extends string>(path: T.EnsureStorePath<Store, Path>) =>
|
||||||
|
getStore<Store, Path>(effects, path as any) as any,
|
||||||
|
setOwn: <Path extends string | never>(
|
||||||
|
path: T.EnsureStorePath<Store, Path>,
|
||||||
|
value: ExtractStore<Store, Path>,
|
||||||
|
) => effects.store.set<Store, Path>({ value, path: path as any }),
|
||||||
|
},
|
||||||
|
checkPortListening: checkPortListening.bind(null, effects),
|
||||||
|
checkWebUrl: checkWebUrl.bind(null, effects),
|
||||||
|
bindLan: async (port: number) => LocalPort.bindLan(effects, port),
|
||||||
|
networkBuilder: () => NetworkBuilder.of(effects),
|
||||||
|
torHostName: (id: string) => TorHostname.of(effects, id),
|
||||||
|
|
||||||
|
vault: {
|
||||||
|
get: (key: keyof Vault & string) =>
|
||||||
|
getVault<Vault>(effects, key) as GetVault<Vault> & WrapperOverWrite,
|
||||||
|
set: (key: keyof Vault & string, value: string) =>
|
||||||
|
effects.vault.set({ key, value }),
|
||||||
|
},
|
||||||
|
mountDependencies: <
|
||||||
|
In extends
|
||||||
|
| Record<ManifestId, Record<VolumeName, Record<NamedPath, Path>>>
|
||||||
|
| Record<VolumeName, Record<NamedPath, Path>>
|
||||||
|
| Record<NamedPath, Path>
|
||||||
|
| Path,
|
||||||
|
>(
|
||||||
|
value: In,
|
||||||
|
) => mountDependencies(effects, value),
|
||||||
|
})
|
||||||
|
function noop(): void {}
|
||||||
18
package-lock.json
generated
18
package-lock.json
generated
@@ -11,7 +11,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iarna/toml": "^2.2.5",
|
"@iarna/toml": "^2.2.5",
|
||||||
"ts-matches": "^5.4.1",
|
"ts-matches": "^5.4.1",
|
||||||
"yaml": "^2.2.1"
|
"yaml": "^2.2.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^29.4.0",
|
"@types/jest": "^29.4.0",
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"tsc-multi": "^0.6.1",
|
"tsc-multi": "^0.6.1",
|
||||||
"tsconfig-paths": "^3.14.2",
|
"tsconfig-paths": "^3.14.2",
|
||||||
|
"typescript": "^5.0.4",
|
||||||
"vitest": "^0.29.2"
|
"vitest": "^0.29.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -4878,17 +4879,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "4.9.5",
|
"version": "5.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz",
|
||||||
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
|
"integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4.2.0"
|
"node": ">=12.20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ufo": {
|
"node_modules/ufo": {
|
||||||
@@ -5219,9 +5219,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/yaml": {
|
"node_modules/yaml": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz",
|
||||||
"integrity": "sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==",
|
"integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 14"
|
"node": ">= 14"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iarna/toml": "^2.2.5",
|
"@iarna/toml": "^2.2.5",
|
||||||
"ts-matches": "^5.4.1",
|
"ts-matches": "^5.4.1",
|
||||||
"yaml": "^2.2.1"
|
"yaml": "^2.2.2"
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"trailingComma": "all",
|
"trailingComma": "all",
|
||||||
@@ -37,6 +37,7 @@
|
|||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"tsc-multi": "^0.6.1",
|
"tsc-multi": "^0.6.1",
|
||||||
"tsconfig-paths": "^3.14.2",
|
"tsconfig-paths": "^3.14.2",
|
||||||
|
"typescript": "^5.0.4",
|
||||||
"vitest": "^0.29.2"
|
"vitest": "^0.29.2"
|
||||||
},
|
},
|
||||||
"declaration": true
|
"declaration": true
|
||||||
|
|||||||
@@ -28,19 +28,12 @@ function isString(x: unknown): x is string {
|
|||||||
|
|
||||||
export default async function makeFileContentFromOld(
|
export default async function makeFileContentFromOld(
|
||||||
inputData: Promise<any> | any,
|
inputData: Promise<any> | any,
|
||||||
{
|
{ StartSdk = "start-sdk", nested = true } = {},
|
||||||
startSdk = "start-sdk",
|
|
||||||
nested = true,
|
|
||||||
wrapperData = "../../wrapperData",
|
|
||||||
} = {},
|
|
||||||
) {
|
) {
|
||||||
const outputLines: string[] = []
|
const outputLines: string[] = []
|
||||||
outputLines.push(`
|
outputLines.push(`
|
||||||
import { Config } from "${startSdk}/lib/config/builder/config"
|
import { sdk } from "${StartSdk}"
|
||||||
import { List } from "${startSdk}/lib/config/builder/list"
|
const {Config, List, Value, Variants} = sdk
|
||||||
import { Value } from "${startSdk}/lib/config/builder/value"
|
|
||||||
import { Variants } from "${startSdk}/lib/config/builder/variants"
|
|
||||||
import {WrapperData} from '${wrapperData}'
|
|
||||||
`)
|
`)
|
||||||
const data = await inputData
|
const data = await inputData
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user