backend changes

This commit is contained in:
Aiden McClelland
2025-07-22 16:48:16 -06:00
parent 4d9709eb1c
commit d3e7e37f59
28 changed files with 211 additions and 162 deletions

View File

@@ -27,6 +27,7 @@ import {
/** Used to reach out from the pure js runtime */
export type Effects = {
readonly eventId: string | null
child: (name: string) => Effects
constRetry?: () => void
isInContext: boolean

View File

@@ -1,8 +1,7 @@
import { ExtractInputSpecType, InputSpec, LazyBuild } from "./inputSpec"
import { InputSpec, LazyBuild } from "./inputSpec"
import { List } from "./list"
import { UnionRes, UnionResStaticValidatedAs, Variants } from "./variants"
import {
FilePath,
Pattern,
RandomString,
ValueSpec,
@@ -27,6 +26,12 @@ import {
} from "ts-matches"
import { DeepPartial } from "../../../types"
export const fileInfoParser = object({
path: string,
commitment: object({ hash: string, size: number }),
})
export type FileInfo = typeof fileInfoParser._TYPE
type AsRequired<T, Required extends boolean> = Required extends true
? T
: T | null
@@ -891,47 +896,54 @@ export class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type> {
}
}, spec.validator)
}
// static file<Store, Required extends boolean>(a: {
// name: string
// description?: string | null
// extensions: string[]
// required: Required
// }) {
// const buildValue = {
// type: "file" as const,
// description: null,
// warning: null,
// ...a,
// }
// return new Value<AsRequired<FilePath, Required>, Store>(
// () => ({
// ...buildValue,
// }),
// asRequiredParser(object({ filePath: string }), a),
// )
// }
// static dynamicFile<Store>(
// a: LazyBuild<
// Store,
// {
// name: string
// description?: string | null
// warning?: string | null
// extensions: string[]
// required: boolean
// }
// >,
// ) {
// return new Value<FilePath | null, Store>(
// async (options) => ({
// type: "file" as const,
// description: null,
// warning: null,
// ...(await a(options)),
// }),
// object({ filePath: string }).nullable(),
// )
// }
static file<Required extends boolean>(a: {
name: string
description?: string | null
warning?: string | null
extensions: string[]
required: Required
}) {
const buildValue = {
type: "file" as const,
description: null,
warning: null,
...a,
}
return new Value<AsRequired<FileInfo, Required>>(
() => ({
spec: {
...buildValue,
},
validator: asRequiredParser(fileInfoParser, a),
}),
asRequiredParser(fileInfoParser, a),
)
}
static dynamicFile<Required extends boolean>(
a: LazyBuild<{
name: string
description?: string | null
warning?: string | null
extensions: string[]
required: Required
}>,
) {
return new Value<AsRequired<FileInfo, Required>, FileInfo | null>(
async (options) => {
const spec = {
type: "file" as const,
description: null,
warning: null,
...(await a(options)),
}
return {
spec,
validator: asRequiredParser(fileInfoParser, spec),
}
},
fileInfoParser.nullable(),
)
}
/**
* @description Displays a dropdown, allowing for a single selection. Depending on the selection, a different object ("sub form") is presented.
* @example

View File

@@ -66,9 +66,6 @@ export type ValueSpecTextarea = {
immutable: boolean
}
export type FilePath = {
filePath: string
}
export type ValueSpecNumber = {
type: "number"
min: number | null

View File

@@ -5,9 +5,11 @@ import { once } from "../util"
import { InitScript } from "../inits"
import { Parser } from "ts-matches"
type MaybeInputSpec<Type> = {} extends Type ? null : InputSpec<Type>
export type Run<A extends Record<string, any>> = (options: {
effects: T.Effects
input: A
spec: T.inputSpecTypes.InputSpec
}) => Promise<(T.ActionResult & { version: "1" }) | null | void | undefined>
export type GetInput<A extends Record<string, any>> = (options: {
effects: T.Effects
@@ -47,11 +49,14 @@ export class Action<Id extends T.ActionId, Type extends Record<string, any>>
implements ActionInfo<Id, Type>
{
readonly _INPUT: Type = null as any as Type
private cachedParser?: Parser<unknown, Type>
private prevInputSpec: Record<
string,
{ spec: T.inputSpecTypes.InputSpec; validator: Parser<unknown, Type> }
> = {}
private constructor(
readonly id: Id,
private readonly metadataFn: MaybeFn<T.ActionMetadata>,
private readonly inputSpec: InputSpec<Type>,
private readonly inputSpec: MaybeInputSpec<Type>,
private readonly getInputFn: GetInput<Type>,
private readonly runFn: Run<Type>,
) {}
@@ -81,7 +86,7 @@ export class Action<Id extends T.ActionId, Type extends Record<string, any>>
return new Action(
id,
mapMaybeFn(metadata, (m) => ({ ...m, hasInput: false })),
InputSpec.of({}),
null,
async () => null,
run,
)
@@ -100,10 +105,15 @@ export class Action<Id extends T.ActionId, Type extends Record<string, any>>
return metadata
}
async getInput(options: { effects: T.Effects }): Promise<T.ActionInput> {
const built = await this.inputSpec.build(options)
this.cachedParser = built.validator
let spec = {}
if (this.inputSpec) {
const built = await this.inputSpec.build(options)
this.prevInputSpec[options.effects.eventId!] = built
spec = built.spec
}
return {
spec: built.spec,
eventId: options.effects.eventId!,
spec,
value:
((await this.getInputFn(options)) as
| Record<string, unknown>
@@ -115,15 +125,23 @@ export class Action<Id extends T.ActionId, Type extends Record<string, any>>
effects: T.Effects
input: Type
}): Promise<T.ActionResult | null> {
const parser =
this.cachedParser ?? (await this.inputSpec.build(options)).validator
let spec = {}
if (this.inputSpec) {
const prevInputSpec = this.prevInputSpec[options.effects.eventId!]
if (!prevInputSpec) {
throw new Error(
`getActionInput has not been called for EventID ${options.effects.eventId}`,
)
}
options.input = prevInputSpec.validator.unsafeCast(options.input)
spec = prevInputSpec.spec
}
return (
(await this.runFn({
effects: options.effects,
input: this.cachedParser
? this.cachedParser.unsafeCast(options.input)
: options.input,
})) || null
input: options.input,
spec,
})) ?? null
)
}
}

View File

@@ -1,6 +1,8 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Guid } from "./Guid"
export type ActionInput = {
eventId: Guid
spec: Record<string, unknown>
value: Record<string, unknown> | null
}

View File

@@ -1,4 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Guid } from "./Guid"
export type ProcedureId = { procedureId: Guid }
export type EventId = { eventId: Guid }

View File

@@ -2,7 +2,6 @@
import type { IpInfo } from "./IpInfo"
export type NetworkInterfaceInfo = {
inbound: boolean | null
outbound: boolean | null
public: boolean | null
ipInfo: IpInfo | null
}

View File

@@ -70,6 +70,7 @@ export { Duration } from "./Duration"
export { EchoParams } from "./EchoParams"
export { EditSignerParams } from "./EditSignerParams"
export { EncryptedWire } from "./EncryptedWire"
export { EventId } from "./EventId"
export { ExportActionParams } from "./ExportActionParams"
export { ExportServiceInterfaceParams } from "./ExportServiceInterfaceParams"
export { FileType } from "./FileType"
@@ -155,7 +156,6 @@ export { PackageVersionInfo } from "./PackageVersionInfo"
export { PasswordType } from "./PasswordType"
export { PathOrUrl } from "./PathOrUrl"
export { Percentage } from "./Percentage"
export { ProcedureId } from "./ProcedureId"
export { Progress } from "./Progress"
export { ProgressUnits } from "./ProgressUnits"
export { Public } from "./Public"

View File

@@ -46,6 +46,7 @@ type EffectsTypeChecker<T extends StringObject = Effects> = {
describe("startosTypeValidation ", () => {
test(`checking the params match`, () => {
typeEquality<EffectsTypeChecker>({
eventId: {} as never,
child: "",
isInContext: {} as never,
onLeaveContext: () => {},

View File

@@ -104,7 +104,7 @@ export class StartSdk<Manifest extends T.SDKManifest> {
// prettier-ignore
type StartSdkEffectWrapper = {
[K in keyof Omit<Effects, NestedEffects | InterfaceEffects | MainUsedEffects | CallbackEffects | AlreadyExposed>]: (effects: Effects, ...args: Parameters<Effects[K]>) => ReturnType<Effects[K]>
[K in keyof Omit<Effects, "eventId" | NestedEffects | InterfaceEffects | MainUsedEffects | CallbackEffects | AlreadyExposed>]: (effects: Effects, ...args: Parameters<Effects[K]>) => ReturnType<Effects[K]>
}
const startSdkEffectWrapper: StartSdkEffectWrapper = {
restart: (effects, ...args) => effects.restart(...args),