Fix StartOS 0.4 TypeScript packaging to match SDK API

This commit is contained in:
MacPro
2026-04-09 15:10:44 -05:00
parent 68ec875ee7
commit 8298c083c7
3436 changed files with 867051 additions and 92 deletions
@@ -0,0 +1,5 @@
import { InputSpec } from './inputSpec';
import { List } from './list';
import { Value } from './value';
import { Variants } from './variants';
export { InputSpec as InputSpec, List, Value, Variants };
@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Variants = exports.Value = exports.List = exports.InputSpec = void 0;
const inputSpec_1 = require("./inputSpec");
Object.defineProperty(exports, "InputSpec", { enumerable: true, get: function () { return inputSpec_1.InputSpec; } });
const list_1 = require("./list");
Object.defineProperty(exports, "List", { enumerable: true, get: function () { return list_1.List; } });
const value_1 = require("./value");
Object.defineProperty(exports, "Value", { enumerable: true, get: function () { return value_1.Value; } });
const variants_1 = require("./variants");
Object.defineProperty(exports, "Variants", { enumerable: true, get: function () { return variants_1.Variants; } });
//# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../base/lib/actions/input/builder/index.ts"],"names":[],"mappings":";;;AAAA,2CAAuC;AAKjB,0FALb,qBAAS,OAKa;AAJ/B,iCAA6B;AAII,qFAJxB,WAAI,OAIwB;AAHrC,mCAA+B;AAGQ,sFAH9B,aAAK,OAG8B;AAF5C,yCAAqC;AAES,yFAFrC,mBAAQ,OAEqC"}
@@ -0,0 +1,222 @@
import { ValueSpec } from '../inputSpecTypes';
import { Value } from './value';
import { Effects } from '../../../Effects';
import { z } from 'zod';
import { DeepPartial } from '../../../types';
import { InputSpecTools } from './inputSpecTools';
/** Options passed to a lazy builder function when resolving dynamic form field values. */
export type LazyBuildOptions<Type> = {
/** The effects interface for runtime operations (e.g. reading files, querying state). */
effects: Effects;
/** Previously saved form data to pre-fill the form with, or `null` for fresh creation. */
prefill: DeepPartial<Type> | null;
};
/**
* A function that lazily produces a value, potentially using effects and prefill data.
* Used by `dynamic*` variants of {@link Value} to compute form field options at runtime.
*/
export type LazyBuild<ExpectedOut, Type> = (options: LazyBuildOptions<Type>) => Promise<ExpectedOut> | ExpectedOut;
/**
* Defines which keys to keep when filtering an InputSpec.
* Use `true` to keep a field as-is, or a nested object to filter sub-fields of an object-typed field.
*/
export type FilterKeys<F> = {
[K in keyof F]?: F[K] extends Record<string, any> ? boolean | FilterKeys<F[K]> : boolean;
};
type RetainKey<T, F, Default extends boolean> = {
[K in keyof T]: K extends keyof F ? F[K] extends false ? never : K : Default extends true ? K : never;
}[keyof T];
/**
* Computes the resulting type after applying a {@link FilterKeys} shape to a type.
*/
export type ApplyFilter<T, F, Default extends boolean = false> = {
[K in RetainKey<T, F, Default>]: K extends keyof F ? true extends F[K] ? F[K] extends true ? T[K] : T[K] | undefined : T[K] extends Record<string, any> ? F[K] extends FilterKeys<T[K]> ? ApplyFilter<T[K], F[K]> : undefined : undefined : Default extends true ? T[K] : undefined;
};
/**
* Computes the union of all valid key-path tuples through a nested type.
* Each tuple represents a path from root to a field, recursing into object-typed sub-fields.
*/
export type KeyPaths<T> = {
[K in keyof T & string]: T[K] extends any[] ? [K] : T[K] extends Record<string, any> ? [K] | [K, ...KeyPaths<T[K]>] : [K];
}[keyof T & string];
/** Extracts the runtime type from an {@link InputSpec}. */
export type ExtractInputSpecType<A extends InputSpec<Record<string, any>, any>> = A extends InputSpec<infer B, any> ? B : never;
/** Extracts the static validation type from an {@link InputSpec}. */
export type ExtractInputSpecStaticValidatedAs<A extends InputSpec<any, Record<string, any>>> = A extends InputSpec<any, infer B> ? B : never;
/** Maps an object type to a record of {@link Value} entries for use with `InputSpec.of`. */
export type InputSpecOf<A extends Record<string, any>> = {
[K in keyof A]: Value<A[K]>;
};
/** A value that is either directly provided or lazily computed via a {@link LazyBuild} function. */
export type MaybeLazyValues<A, T> = LazyBuild<A, T> | A;
/**
* InputSpecs are the specs that are used by the os input specification form for this service.
* Here is an example of a simple input specification
```ts
const smallInputSpec = InputSpec.of({
test: Value.boolean({
name: "Test",
description: "This is the description for the test",
warning: null,
default: false,
}),
});
```
The idea of an inputSpec is that now the form is going to ask for
Test: [ ] and the value is going to be checked as a boolean.
There are more complex values like selects, lists, and objects. See {@link Value}
Also, there is the ability to get a validator/parser from this inputSpec spec.
```ts
const matchSmallInputSpec = smallInputSpec.validator();
type SmallInputSpec = typeof matchSmallInputSpec._TYPE;
```
Here is an example of a more complex input specification which came from an input specification for a service
that works with bitcoin, like c-lightning.
```ts
export const hostname = Value.string({
name: "Hostname",
default: null,
description: "Domain or IP address of bitcoin peer",
warning: null,
required: true,
masked: false,
placeholder: null,
pattern:
"(^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$)|((^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$)|(^[a-z2-7]{16}\\.onion$)|(^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$))",
patternDescription:
"Must be either a domain name, or an IPv4 or IPv6 address. Do not include protocol scheme (eg 'http://') or port.",
});
export const port = Value.number({
name: "Port",
default: null,
description: "Port that peer is listening on for inbound p2p connections",
warning: null,
required: false,
range: "[0,65535]",
integral: true,
units: null,
placeholder: null,
});
export const addNodesSpec = InputSpec.of({ hostname: hostname, port: port });
```
*/
export declare class InputSpec<Type extends StaticValidatedAs, StaticValidatedAs extends Record<string, any> = Type> {
private readonly spec;
readonly validator: z.ZodType<StaticValidatedAs>;
private constructor();
_TYPE: Type;
_PARTIAL: DeepPartial<Type>;
readonly partialValidator: z.ZodType<DeepPartial<StaticValidatedAs>>;
/**
* Builds the runtime form specification and combined Zod validator from this InputSpec's fields.
*
* @returns An object containing the resolved `spec` (field specs keyed by name) and a combined `validator`
*/
build<OuterType>(options: LazyBuildOptions<OuterType>): Promise<{
spec: {
[K in keyof Type]: ValueSpec;
};
validator: z.ZodType<Type>;
}>;
/**
* Adds multiple fields to this spec at once, returning a new `InputSpec` with extended types.
*
* @param build - A record of {@link Value} entries, or a function receiving typed tools that returns one
*/
add<AddSpec extends Record<string, Value<any, any, any>>>(build: AddSpec | ((tools: InputSpecTools<Type>) => AddSpec)): InputSpec<Type & {
[K in keyof AddSpec]: AddSpec[K] extends Value<infer T, any, any> ? T : never;
}, StaticValidatedAs & {
[K in keyof AddSpec]: AddSpec[K] extends Value<any, infer S, any> ? S : never;
}>;
/**
* Returns a new InputSpec containing only the specified keys.
* Use `true` to keep a field as-is, or a nested object to filter sub-fields of object-typed fields.
*
* @example
* ```ts
* const full = InputSpec.of({
* name: Value.text({ name: 'Name', required: true, default: null }),
* settings: Value.object({ name: 'Settings' }, InputSpec.of({
* debug: Value.toggle({ name: 'Debug', default: false }),
* port: Value.number({ name: 'Port', required: true, default: 8080, integer: true }),
* })),
* })
* const filtered = full.filter({ name: true, settings: { debug: true } })
* ```
*/
filter<F extends FilterKeys<Type>, Default extends boolean = false>(keys: F, keepByDefault?: Default): InputSpec<ApplyFilter<Type, F, Default> & ApplyFilter<StaticValidatedAs, F, Default>, ApplyFilter<StaticValidatedAs, F, Default>>;
/**
* Returns a new InputSpec with the specified keys disabled.
* Use `true` to disable a field, or a nested object to disable sub-fields of object-typed fields.
* All fields remain in the spec — disabled fields simply cannot be edited by the user.
*
* @param keys - Which fields to disable, using the same shape as {@link FilterKeys}
* @param message - The reason the fields are disabled, displayed to the user
*
* @example
* ```ts
* const spec = InputSpec.of({
* name: Value.text({ name: 'Name', required: true, default: null }),
* settings: Value.object({ name: 'Settings' }, InputSpec.of({
* debug: Value.toggle({ name: 'Debug', default: false }),
* port: Value.number({ name: 'Port', required: true, default: 8080, integer: true }),
* })),
* })
* const disabled = spec.disable({ name: true, settings: { debug: true } }, 'Managed by the system')
* ```
*/
disable(keys: FilterKeys<Type>, message: string): InputSpec<Type, StaticValidatedAs>;
/**
* Resolves a key path to its corresponding display name path.
* Each key is mapped to the `name` property of its built {@link ValueSpec}.
* Recurses into `Value.object` sub-specs for nested paths.
*
* @param path - Typed tuple of field keys (e.g. `["settings", "debug"]`)
* @param options - Build options providing effects and prefill data
* @returns Array of display names (e.g. `["Settings", "Debug"]`)
*/
namePath<OuterType>(path: KeyPaths<Type>, options: LazyBuildOptions<OuterType>): Promise<string[]>;
/**
* Resolves a key path to the description of the target field.
* Recurses into `Value.object` sub-specs for nested paths.
*
* @param path - Typed tuple of field keys (e.g. `["settings", "debug"]`)
* @param options - Build options providing effects and prefill data
* @returns The description string, or `null` if the field has no description or was not found
*/
description<OuterType>(path: KeyPaths<Type>, options: LazyBuildOptions<OuterType>): Promise<string | null>;
/**
* Returns a new InputSpec filtered to only include keys present in the given partial object.
* For nested `Value.object` fields, recurses into the partial value to filter sub-fields.
*
* @param partial - A deep-partial object whose defined keys determine which fields to keep
*/
filterFromPartial(partial: DeepPartial<Type>): InputSpec<DeepPartial<Type> & DeepPartial<StaticValidatedAs>, DeepPartial<StaticValidatedAs>>;
/**
* Returns a new InputSpec with fields disabled based on which keys are present in the given partial object.
* For nested `Value.object` fields, recurses into the partial value to disable sub-fields.
* All fields remain in the spec — disabled fields simply cannot be edited by the user.
*
* @param partial - A deep-partial object whose defined keys determine which fields to disable
* @param message - The reason the fields are disabled, displayed to the user
*/
disableFromPartial(partial: DeepPartial<Type>, message: string): InputSpec<Type, StaticValidatedAs>;
/**
* Creates an `InputSpec` from a plain record of {@link Value} entries.
*
* @example
* ```ts
* const spec = InputSpec.of({
* username: Value.text({ name: 'Username', required: true, default: null }),
* verbose: Value.toggle({ name: 'Verbose Logging', default: false }),
* })
* ```
*/
static of<Spec extends Record<string, Value<any, any>>>(spec: Spec): InputSpec<{ [K in keyof Spec]: Spec[K] extends Value<infer T extends any, any, unknown> ? T : never; }, { [K_1 in keyof Spec]: Spec[K_1] extends Value<any, infer T_1, unknown> ? T_1 : never; }>;
}
export {};
@@ -0,0 +1,319 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.InputSpec = void 0;
const value_1 = require("./value");
const zod_1 = require("zod");
const zod_deep_partial_1 = require("zod-deep-partial");
const inputSpecTools_1 = require("./inputSpecTools");
/**
* InputSpecs are the specs that are used by the os input specification form for this service.
* Here is an example of a simple input specification
```ts
const smallInputSpec = InputSpec.of({
test: Value.boolean({
name: "Test",
description: "This is the description for the test",
warning: null,
default: false,
}),
});
```
The idea of an inputSpec is that now the form is going to ask for
Test: [ ] and the value is going to be checked as a boolean.
There are more complex values like selects, lists, and objects. See {@link Value}
Also, there is the ability to get a validator/parser from this inputSpec spec.
```ts
const matchSmallInputSpec = smallInputSpec.validator();
type SmallInputSpec = typeof matchSmallInputSpec._TYPE;
```
Here is an example of a more complex input specification which came from an input specification for a service
that works with bitcoin, like c-lightning.
```ts
export const hostname = Value.string({
name: "Hostname",
default: null,
description: "Domain or IP address of bitcoin peer",
warning: null,
required: true,
masked: false,
placeholder: null,
pattern:
"(^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$)|((^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$)|(^[a-z2-7]{16}\\.onion$)|(^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$))",
patternDescription:
"Must be either a domain name, or an IPv4 or IPv6 address. Do not include protocol scheme (eg 'http://') or port.",
});
export const port = Value.number({
name: "Port",
default: null,
description: "Port that peer is listening on for inbound p2p connections",
warning: null,
required: false,
range: "[0,65535]",
integral: true,
units: null,
placeholder: null,
});
export const addNodesSpec = InputSpec.of({ hostname: hostname, port: port });
```
*/
class InputSpec {
constructor(spec, validator) {
this.spec = spec;
this.validator = validator;
this._TYPE = null;
this._PARTIAL = null;
this.partialValidator = (0, zod_deep_partial_1.zodDeepPartial)(this.validator);
}
/**
* Builds the runtime form specification and combined Zod validator from this InputSpec's fields.
*
* @returns An object containing the resolved `spec` (field specs keyed by name) and a combined `validator`
*/
async build(options) {
const answer = {};
const validator = {};
for (const k in this.spec) {
const built = await this.spec[k].build(options);
answer[k] = built.spec;
validator[k] = built.validator;
}
return {
spec: answer,
validator: zod_1.z.object(validator),
};
}
/**
* Adds multiple fields to this spec at once, returning a new `InputSpec` with extended types.
*
* @param build - A record of {@link Value} entries, or a function receiving typed tools that returns one
*/
add(build) {
const addedValues = build instanceof Function ? build((0, inputSpecTools_1.createInputSpecTools)()) : build;
const newSpec = { ...this.spec, ...addedValues };
const newValidator = zod_1.z.object(Object.fromEntries(Object.entries(newSpec).map(([k, v]) => [
k,
v.validator,
])));
return new InputSpec(newSpec, newValidator);
}
/**
* Returns a new InputSpec containing only the specified keys.
* Use `true` to keep a field as-is, or a nested object to filter sub-fields of object-typed fields.
*
* @example
* ```ts
* const full = InputSpec.of({
* name: Value.text({ name: 'Name', required: true, default: null }),
* settings: Value.object({ name: 'Settings' }, InputSpec.of({
* debug: Value.toggle({ name: 'Debug', default: false }),
* port: Value.number({ name: 'Port', required: true, default: 8080, integer: true }),
* })),
* })
* const filtered = full.filter({ name: true, settings: { debug: true } })
* ```
*/
filter(keys, keepByDefault) {
const newSpec = {};
for (const k of Object.keys(this.spec)) {
const filterVal = keys[k];
const value = this.spec[k];
if (!value)
continue;
if (filterVal === true) {
newSpec[k] = value;
}
else if (typeof filterVal === 'object' && filterVal !== null) {
const objectMeta = value._objectSpec;
if (objectMeta) {
const filteredInner = objectMeta.inputSpec.filter(filterVal, keepByDefault);
newSpec[k] = value_1.Value.object(objectMeta.params, filteredInner);
}
else {
newSpec[k] = value;
}
}
else if (keepByDefault && filterVal !== false) {
newSpec[k] = value;
}
}
const newValidator = zod_1.z.object(Object.fromEntries(Object.entries(newSpec).map(([k, v]) => [k, v.validator])));
return new InputSpec(newSpec, newValidator);
}
/**
* Returns a new InputSpec with the specified keys disabled.
* Use `true` to disable a field, or a nested object to disable sub-fields of object-typed fields.
* All fields remain in the spec — disabled fields simply cannot be edited by the user.
*
* @param keys - Which fields to disable, using the same shape as {@link FilterKeys}
* @param message - The reason the fields are disabled, displayed to the user
*
* @example
* ```ts
* const spec = InputSpec.of({
* name: Value.text({ name: 'Name', required: true, default: null }),
* settings: Value.object({ name: 'Settings' }, InputSpec.of({
* debug: Value.toggle({ name: 'Debug', default: false }),
* port: Value.number({ name: 'Port', required: true, default: 8080, integer: true }),
* })),
* })
* const disabled = spec.disable({ name: true, settings: { debug: true } }, 'Managed by the system')
* ```
*/
disable(keys, message) {
const newSpec = {};
for (const k in this.spec) {
const filterVal = keys[k];
const value = this.spec[k];
if (!filterVal) {
newSpec[k] = value;
}
else if (filterVal === true) {
newSpec[k] = value.withDisabled(message);
}
else if (typeof filterVal === 'object' && filterVal !== null) {
const objectMeta = value._objectSpec;
if (objectMeta) {
const disabledInner = objectMeta.inputSpec.disable(filterVal, message);
newSpec[k] = value_1.Value.object(objectMeta.params, disabledInner);
}
else {
newSpec[k] = value.withDisabled(message);
}
}
}
const newValidator = zod_1.z.object(Object.fromEntries(Object.entries(newSpec).map(([k, v]) => [k, v.validator])));
return new InputSpec(newSpec, newValidator);
}
/**
* Resolves a key path to its corresponding display name path.
* Each key is mapped to the `name` property of its built {@link ValueSpec}.
* Recurses into `Value.object` sub-specs for nested paths.
*
* @param path - Typed tuple of field keys (e.g. `["settings", "debug"]`)
* @param options - Build options providing effects and prefill data
* @returns Array of display names (e.g. `["Settings", "Debug"]`)
*/
async namePath(path, options) {
if (path.length === 0)
return [];
const [key, ...rest] = path;
const value = this.spec[key];
if (!value)
return [];
const built = await value.build(options);
const name = 'name' in built.spec ? built.spec.name : key;
if (rest.length === 0)
return [name];
const objectMeta = value._objectSpec;
if (objectMeta) {
const innerNames = await objectMeta.inputSpec.namePath(rest, options);
return [name, ...innerNames];
}
return [name];
}
/**
* Resolves a key path to the description of the target field.
* Recurses into `Value.object` sub-specs for nested paths.
*
* @param path - Typed tuple of field keys (e.g. `["settings", "debug"]`)
* @param options - Build options providing effects and prefill data
* @returns The description string, or `null` if the field has no description or was not found
*/
async description(path, options) {
if (path.length === 0)
return null;
const [key, ...rest] = path;
const value = this.spec[key];
if (!value)
return null;
if (rest.length === 0) {
const built = await value.build(options);
return 'description' in built.spec
? built.spec.description
: null;
}
const objectMeta = value._objectSpec;
if (objectMeta) {
return objectMeta.inputSpec.description(rest, options);
}
return null;
}
/**
* Returns a new InputSpec filtered to only include keys present in the given partial object.
* For nested `Value.object` fields, recurses into the partial value to filter sub-fields.
*
* @param partial - A deep-partial object whose defined keys determine which fields to keep
*/
filterFromPartial(partial) {
const newSpec = {};
for (const k of Object.keys(partial)) {
const value = this.spec[k];
if (!value)
continue;
const objectMeta = value._objectSpec;
if (objectMeta) {
const partialVal = partial[k];
if (typeof partialVal === 'object' && partialVal !== null) {
const filteredInner = objectMeta.inputSpec.filterFromPartial(partialVal);
newSpec[k] = value_1.Value.object(objectMeta.params, filteredInner);
continue;
}
}
newSpec[k] = value;
}
const newValidator = zod_1.z.object(Object.fromEntries(Object.entries(newSpec).map(([k, v]) => [k, v.validator])));
return new InputSpec(newSpec, newValidator);
}
/**
* Returns a new InputSpec with fields disabled based on which keys are present in the given partial object.
* For nested `Value.object` fields, recurses into the partial value to disable sub-fields.
* All fields remain in the spec — disabled fields simply cannot be edited by the user.
*
* @param partial - A deep-partial object whose defined keys determine which fields to disable
* @param message - The reason the fields are disabled, displayed to the user
*/
disableFromPartial(partial, message) {
const newSpec = {};
for (const k in this.spec) {
const value = this.spec[k];
if (!(k in partial)) {
newSpec[k] = value;
continue;
}
const objectMeta = value._objectSpec;
if (objectMeta) {
const partialVal = partial[k];
if (typeof partialVal === 'object' && partialVal !== null) {
const disabledInner = objectMeta.inputSpec.disableFromPartial(partialVal, message);
newSpec[k] = value_1.Value.object(objectMeta.params, disabledInner);
continue;
}
}
newSpec[k] = value.withDisabled(message);
}
const newValidator = zod_1.z.object(Object.fromEntries(Object.entries(newSpec).map(([k, v]) => [k, v.validator])));
return new InputSpec(newSpec, newValidator);
}
/**
* Creates an `InputSpec` from a plain record of {@link Value} entries.
*
* @example
* ```ts
* const spec = InputSpec.of({
* username: Value.text({ name: 'Username', required: true, default: null }),
* verbose: Value.toggle({ name: 'Verbose Logging', default: false }),
* })
* ```
*/
static of(spec) {
const validator = zod_1.z.object(Object.fromEntries(Object.entries(spec).map(([k, v]) => [k, v.validator])));
return new InputSpec(spec, validator);
}
}
exports.InputSpec = InputSpec;
//# sourceMappingURL=inputSpec.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,173 @@
import { InputSpec, LazyBuild } from './inputSpec';
import { AsRequired, FileInfo, Value } from './value';
import { List } from './list';
import { UnionRes, UnionResStaticValidatedAs, Variants } from './variants';
import { Pattern, RandomString, ValueSpecDatetime, ValueSpecText } from '../inputSpecTypes';
import { DefaultString } from '../inputSpecTypes';
import { z } from 'zod';
import { ListValueSpecText } from '../inputSpecTypes';
export interface InputSpecTools<OuterType> {
Value: BoundValue<OuterType>;
Variants: typeof Variants;
InputSpec: typeof InputSpec;
List: BoundList<OuterType>;
}
export interface BoundValue<OuterType> {
toggle: typeof Value.toggle;
text: typeof Value.text;
textarea: typeof Value.textarea;
number: typeof Value.number;
color: typeof Value.color;
datetime: typeof Value.datetime;
select: typeof Value.select;
multiselect: typeof Value.multiselect;
object: typeof Value.object;
file: typeof Value.file;
list: typeof Value.list;
hidden: typeof Value.hidden;
union: typeof Value.union;
dynamicToggle(a: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: boolean;
disabled?: false | string;
}, OuterType>): Value<boolean, boolean, OuterType>;
dynamicText<Required extends boolean>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: DefaultString | null;
required: Required;
masked?: boolean;
placeholder?: string | null;
minLength?: number | null;
maxLength?: number | null;
patterns?: Pattern[];
inputmode?: ValueSpecText['inputmode'];
disabled?: string | false;
generate?: null | RandomString;
}, OuterType>): Value<AsRequired<string, Required>, string | null, OuterType>;
dynamicTextarea<Required extends boolean>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: string | null;
required: Required;
minLength?: number | null;
maxLength?: number | null;
patterns?: Pattern[];
minRows?: number;
maxRows?: number;
placeholder?: string | null;
disabled?: false | string;
}, OuterType>): Value<AsRequired<string, Required>, string | null, OuterType>;
dynamicNumber<Required extends boolean>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: number | null;
required: Required;
min?: number | null;
max?: number | null;
step?: number | null;
integer: boolean;
units?: string | null;
placeholder?: string | null;
disabled?: false | string;
}, OuterType>): Value<AsRequired<number, Required>, number | null, OuterType>;
dynamicColor<Required extends boolean>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: string | null;
required: Required;
disabled?: false | string;
}, OuterType>): Value<AsRequired<string, Required>, string | null, OuterType>;
dynamicDatetime<Required extends boolean>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: string | null;
required: Required;
inputmode?: ValueSpecDatetime['inputmode'];
min?: string | null;
max?: string | null;
disabled?: false | string;
}, OuterType>): Value<AsRequired<string, Required>, string | null, OuterType>;
dynamicSelect<Values extends Record<string, string>>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: string;
values: Values;
disabled?: false | string | string[];
}, OuterType>): Value<keyof Values & string, keyof Values & string, OuterType>;
dynamicMultiselect<Values extends Record<string, string>>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: string[];
values: Values;
minLength?: number | null;
maxLength?: number | null;
disabled?: false | string | string[];
}, OuterType>): Value<(keyof Values & string)[], (keyof Values & string)[], OuterType>;
dynamicFile<Required extends boolean>(a: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
extensions: string[];
required: Required;
}, OuterType>): Value<AsRequired<FileInfo, Required>, FileInfo | null, OuterType>;
dynamicUnion<VariantValues extends {
[K in string]: {
name: string;
spec: InputSpec<any>;
};
}>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
variants: Variants<VariantValues>;
default: keyof VariantValues & string;
disabled: string[] | false | string;
}, OuterType>): Value<UnionRes<VariantValues>, UnionRes<VariantValues>, OuterType>;
dynamicUnion<StaticVariantValues extends {
[K in string]: {
name: string;
spec: InputSpec<any, any>;
};
}, VariantValues extends StaticVariantValues>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
variants: Variants<VariantValues>;
default: keyof VariantValues & string;
disabled: string[] | false | string;
}, OuterType>, validator: z.ZodType<UnionResStaticValidatedAs<StaticVariantValues>>): Value<UnionRes<VariantValues>, UnionResStaticValidatedAs<StaticVariantValues>, OuterType>;
dynamicHidden<T>(getParser: LazyBuild<z.ZodType<T>, OuterType>): Value<T, T, OuterType>;
}
export interface BoundList<OuterType> {
text: typeof List.text;
obj: typeof List.obj;
dynamicText(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default?: string[];
minLength?: number | null;
maxLength?: number | null;
disabled?: false | string;
generate?: null | RandomString;
spec: {
masked?: boolean;
placeholder?: string | null;
minLength?: number | null;
maxLength?: number | null;
patterns?: Pattern[];
inputmode?: ListValueSpecText['inputmode'];
};
}, OuterType>): List<string[], string[], OuterType>;
}
export declare function createInputSpecTools<OuterType>(): InputSpecTools<OuterType>;
@@ -0,0 +1,16 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createInputSpecTools = createInputSpecTools;
const inputSpec_1 = require("./inputSpec");
const value_1 = require("./value");
const list_1 = require("./list");
const variants_1 = require("./variants");
function createInputSpecTools() {
return {
Value: value_1.Value,
Variants: variants_1.Variants,
InputSpec: inputSpec_1.InputSpec,
List: list_1.List,
};
}
//# sourceMappingURL=inputSpecTools.js.map
@@ -0,0 +1 @@
{"version":3,"file":"inputSpecTools.js","sourceRoot":"","sources":["../../../../../../base/lib/actions/input/builder/inputSpecTools.ts"],"names":[],"mappings":";;AA0QA,oDAOC;AAjRD,2CAAkD;AAClD,mCAAqD;AACrD,iCAA6B;AAC7B,yCAA0E;AAuQ1E,SAAgB,oBAAoB;IAClC,OAAO;QACL,KAAK,EAAE,aAAqC;QAC5C,QAAQ,EAAR,mBAAQ;QACR,SAAS,EAAT,qBAAS;QACT,IAAI,EAAE,WAAmC;KAC1C,CAAA;AACH,CAAC"}
@@ -0,0 +1,103 @@
import { InputSpec, LazyBuild } from './inputSpec';
import { ListValueSpecText, Pattern, RandomString, UniqueBy, ValueSpecList } from '../inputSpecTypes';
import { z } from 'zod';
/**
* Builder class for defining list-type form fields.
*
* A list presents an interface to add, remove, and reorder items. Items can be
* either text strings ({@link List.text}) or structured objects ({@link List.obj}).
*
* Used with {@link Value.list} to include a list field in an {@link InputSpec}.
*/
export declare class List<Type extends StaticValidatedAs, StaticValidatedAs = Type, OuterType = unknown> {
build: LazyBuild<{
spec: ValueSpecList;
validator: z.ZodType<Type>;
}, OuterType>;
readonly validator: z.ZodType<StaticValidatedAs>;
private constructor();
readonly _TYPE: Type;
/**
* Creates a list of text input items.
*
* @param a - List-level options (name, description, min/max length, defaults)
* @param aSpec - Item-level options (patterns, input mode, masking, generation)
*/
static text(a: {
name: string;
description?: string | null;
warning?: string | null;
default?: string[];
minLength?: number | null;
maxLength?: number | null;
}, aSpec: {
/**
* @description Mask (aka camouflage) text input with dots: ● ● ●
* @default false
*/
masked?: boolean;
placeholder?: string | null;
minLength?: number | null;
maxLength?: number | null;
/**
* @description A list of regular expressions to which the text must conform to pass validation. A human readable description is provided in case the validation fails.
* @default []
* @example
* ```
[
{
regex: "[a-z]",
description: "May only contain lower case letters from the English alphabet."
}
]
* ```
*/
patterns?: Pattern[];
/**
* @description Informs the browser how to behave and which keyboard to display on mobile
* @default "text"
*/
inputmode?: ListValueSpecText['inputmode'];
/**
* @description Displays a button that will generate a random string according to the provided charset and len attributes.
*/
generate?: null | RandomString;
}): List<string[], string[], unknown>;
/** Like {@link List.text} but options are resolved lazily at runtime via a builder function. */
static dynamicText<OuterType = unknown>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default?: string[];
minLength?: number | null;
maxLength?: number | null;
disabled?: false | string;
generate?: null | RandomString;
spec: {
masked?: boolean;
placeholder?: string | null;
minLength?: number | null;
maxLength?: number | null;
patterns?: Pattern[];
inputmode?: ListValueSpecText['inputmode'];
};
}, OuterType>): List<string[], string[], OuterType>;
/**
* Creates a list of structured object items, each defined by a nested {@link InputSpec}.
*
* @param a - List-level options (name, description, min/max length)
* @param aSpec - Item-level options (the nested spec, display expression, uniqueness constraint)
*/
static obj<Type extends StaticValidatedAs, StaticValidatedAs extends Record<string, any>>(a: {
name: string;
description?: string | null;
warning?: string | null;
default?: [];
minLength?: number | null;
maxLength?: number | null;
}, aSpec: {
spec: InputSpec<Type, StaticValidatedAs>;
displayAs?: null | string;
uniqueBy?: null | UniqueBy;
}): List<Type[], StaticValidatedAs[], unknown>;
}
@@ -0,0 +1,121 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.List = void 0;
const zod_1 = require("zod");
/**
* Builder class for defining list-type form fields.
*
* A list presents an interface to add, remove, and reorder items. Items can be
* either text strings ({@link List.text}) or structured objects ({@link List.obj}).
*
* Used with {@link Value.list} to include a list field in an {@link InputSpec}.
*/
class List {
constructor(build, validator) {
this.build = build;
this.validator = validator;
this._TYPE = null;
}
/**
* Creates a list of text input items.
*
* @param a - List-level options (name, description, min/max length, defaults)
* @param aSpec - Item-level options (patterns, input mode, masking, generation)
*/
static text(a, aSpec) {
const validator = zod_1.z.array(zod_1.z.string());
return new List(() => {
const spec = {
type: 'text',
placeholder: null,
minLength: null,
maxLength: null,
masked: false,
inputmode: 'text',
generate: null,
patterns: aSpec.patterns || [],
...aSpec,
};
const built = {
description: null,
warning: null,
default: [],
type: 'list',
minLength: null,
maxLength: null,
disabled: false,
...a,
spec,
};
return { spec: built, validator };
}, validator);
}
/** Like {@link List.text} but options are resolved lazily at runtime via a builder function. */
static dynamicText(getA) {
const validator = zod_1.z.array(zod_1.z.string());
return new List(async (options) => {
const { spec: aSpec, ...a } = await getA(options);
const spec = {
type: 'text',
placeholder: null,
minLength: null,
maxLength: null,
masked: false,
inputmode: 'text',
generate: null,
patterns: aSpec.patterns || [],
...aSpec,
};
const built = {
description: null,
warning: null,
default: [],
type: 'list',
minLength: null,
maxLength: null,
disabled: false,
...a,
spec,
};
return { spec: built, validator };
}, validator);
}
/**
* Creates a list of structured object items, each defined by a nested {@link InputSpec}.
*
* @param a - List-level options (name, description, min/max length)
* @param aSpec - Item-level options (the nested spec, display expression, uniqueness constraint)
*/
static obj(a, aSpec) {
return new List(async (options) => {
const { spec: previousSpecSpec, ...restSpec } = aSpec;
const built = await previousSpecSpec.build(options);
const spec = {
type: 'object',
displayAs: null,
uniqueBy: null,
...restSpec,
spec: built.spec,
};
const value = {
spec,
default: [],
...a,
};
return {
spec: {
description: null,
warning: null,
minLength: null,
maxLength: null,
type: 'list',
disabled: false,
...value,
},
validator: zod_1.z.array(built.validator),
};
}, zod_1.z.array(aSpec.spec.validator));
}
}
exports.List = List;
//# sourceMappingURL=list.js.map
@@ -0,0 +1 @@
{"version":3,"file":"list.js","sourceRoot":"","sources":["../../../../../../base/lib/actions/input/builder/list.ts"],"names":[],"mappings":";;;AASA,6BAAuB;AAEvB;;;;;;;GAOG;AACH,MAAa,IAAI;IAKf,YACS,KAMN,EACe,SAAuC;QAPhD,UAAK,GAAL,KAAK,CAMX;QACe,cAAS,GAAT,SAAS,CAA8B;QAEhD,UAAK,GAAS,IAAW,CAAA;IAD/B,CAAC;IAGJ;;;;;OAKG;IACH,MAAM,CAAC,IAAI,CACT,CAOC,EACD,KAgCC;QAED,MAAM,SAAS,GAAG,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,EAAE,CAAC,CAAA;QACrC,OAAO,IAAI,IAAI,CAAW,GAAG,EAAE;YAC7B,MAAM,IAAI,GAAG;gBACX,IAAI,EAAE,MAAe;gBACrB,WAAW,EAAE,IAAI;gBACjB,SAAS,EAAE,IAAI;gBACf,SAAS,EAAE,IAAI;gBACf,MAAM,EAAE,KAAK;gBACb,SAAS,EAAE,MAAe;gBAC1B,QAAQ,EAAE,IAAI;gBACd,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,EAAE;gBAC9B,GAAG,KAAK;aACT,CAAA;YACD,MAAM,KAAK,GAA4B;gBACrC,WAAW,EAAE,IAAI;gBACjB,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,EAAE;gBACX,IAAI,EAAE,MAAe;gBACrB,SAAS,EAAE,IAAI;gBACf,SAAS,EAAE,IAAI;gBACf,QAAQ,EAAE,KAAK;gBACf,GAAG,CAAC;gBACJ,IAAI;aACL,CAAA;YACD,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAA;QACnC,CAAC,EAAE,SAAS,CAAC,CAAA;IACf,CAAC;IAED,gGAAgG;IAChG,MAAM,CAAC,WAAW,CAChB,IAoBC;QAED,MAAM,SAAS,GAAG,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,EAAE,CAAC,CAAA;QACrC,OAAO,IAAI,IAAI,CAAgC,KAAK,EAAE,OAAO,EAAE,EAAE;YAC/D,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,EAAE,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,CAAA;YACjD,MAAM,IAAI,GAAG;gBACX,IAAI,EAAE,MAAe;gBACrB,WAAW,EAAE,IAAI;gBACjB,SAAS,EAAE,IAAI;gBACf,SAAS,EAAE,IAAI;gBACf,MAAM,EAAE,KAAK;gBACb,SAAS,EAAE,MAAe;gBAC1B,QAAQ,EAAE,IAAI;gBACd,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,EAAE;gBAC9B,GAAG,KAAK;aACT,CAAA;YACD,MAAM,KAAK,GAA4B;gBACrC,WAAW,EAAE,IAAI;gBACjB,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,EAAE;gBACX,IAAI,EAAE,MAAe;gBACrB,SAAS,EAAE,IAAI;gBACf,SAAS,EAAE,IAAI;gBACf,QAAQ,EAAE,KAAK;gBACf,GAAG,CAAC;gBACJ,IAAI;aACL,CAAA;YAED,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAA;QACnC,CAAC,EAAE,SAAS,CAAC,CAAA;IACf,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,GAAG,CAIR,CAOC,EACD,KAIC;QAED,OAAO,IAAI,IAAI,CAA8B,KAAK,EAAE,OAAO,EAAE,EAAE;YAC7D,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,GAAG,QAAQ,EAAE,GAAG,KAAK,CAAA;YACrD,MAAM,KAAK,GAAG,MAAM,gBAAgB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;YACnD,MAAM,IAAI,GAAG;gBACX,IAAI,EAAE,QAAiB;gBACvB,SAAS,EAAE,IAAI;gBACf,QAAQ,EAAE,IAAI;gBACd,GAAG,QAAQ;gBACX,IAAI,EAAE,KAAK,CAAC,IAAI;aACjB,CAAA;YACD,MAAM,KAAK,GAAG;gBACZ,IAAI;gBACJ,OAAO,EAAE,EAAE;gBACX,GAAG,CAAC;aACL,CAAA;YACD,OAAO;gBACL,IAAI,EAAE;oBACJ,WAAW,EAAE,IAAI;oBACjB,OAAO,EAAE,IAAI;oBACb,SAAS,EAAE,IAAI;oBACf,SAAS,EAAE,IAAI;oBACf,IAAI,EAAE,MAAe;oBACrB,QAAQ,EAAE,KAAK;oBACf,GAAG,KAAK;iBACT;gBACD,SAAS,EAAE,OAAC,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC;aACpC,CAAA;QACH,CAAC,EAAE,OAAC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAA;IACnC,CAAC;CACF;AAzMD,oBAyMC"}
@@ -0,0 +1,791 @@
import { InputSpec, LazyBuild } from './inputSpec';
import { List } from './list';
import { UnionRes, UnionResStaticValidatedAs, Variants } from './variants';
import { Pattern, RandomString, ValueSpec, ValueSpecDatetime, ValueSpecText } from '../inputSpecTypes';
import { DefaultString } from '../inputSpecTypes';
import { z } from 'zod';
import { DeepPartial } from '../../../types';
/** Zod schema for a file upload result — validates `{ path, commitment: { hash, size } }`. */
export declare const fileInfoParser: z.ZodObject<{
path: z.ZodString;
commitment: z.ZodObject<{
hash: z.ZodString;
size: z.ZodNumber;
}, z.core.$strip>;
}, z.core.$strip>;
/** The parsed result of a file upload, containing the file path and its content commitment (hash + size). */
export type FileInfo = z.infer<typeof fileInfoParser>;
/** Conditional type: returns `T` if `Required` is `true`, otherwise `T | null`. */
export type AsRequired<T, Required extends boolean> = Required extends true ? T : T | null;
/**
* Core builder class for defining a single form field in a service configuration spec.
*
* Each static factory method (e.g. `Value.text()`, `Value.toggle()`, `Value.select()`) creates
* a typed `Value` instance representing a specific field type. Dynamic variants (e.g. `Value.dynamicText()`)
* allow the field options to be computed lazily at runtime.
*
* Use with {@link InputSpec} to compose complete form specifications.
*
* @typeParam Type - The runtime type this field produces when filled in
* @typeParam StaticValidatedAs - The compile-time validated type (usually same as Type)
* @typeParam OuterType - The parent form's type context (used by dynamic variants)
*/
export declare class Value<Type extends StaticValidatedAs, StaticValidatedAs = Type, OuterType = unknown> {
build: LazyBuild<{
spec: ValueSpec;
validator: z.ZodType<Type>;
}, OuterType>;
readonly validator: z.ZodType<StaticValidatedAs>;
protected constructor(build: LazyBuild<{
spec: ValueSpec;
validator: z.ZodType<Type>;
}, OuterType>, validator: z.ZodType<StaticValidatedAs>);
_TYPE: Type;
_PARTIAL: DeepPartial<Type>;
/** @internal Used by {@link InputSpec.filter} to support nested filtering of object-typed fields. */
_objectSpec?: {
inputSpec: InputSpec<any, any>;
params: {
name: string;
description?: string | null;
};
};
/**
* @description Displays a boolean toggle to enable/disable
* @example
* ```
toggleExample: Value.toggle({
// required
name: 'Toggle Example',
default: true,
// optional
description: null,
warning: null,
immutable: false,
}),
* ```
*/
static toggle(a: {
name: string;
description?: string | null;
/** Presents a warning prompt before permitting the value to change. */
warning?: string | null;
default: boolean;
/**
* @description Once set, the value can never be changed.
* @default false
*/
immutable?: boolean;
}): Value<boolean, boolean, unknown>;
/** Like {@link Value.toggle} but options are resolved lazily at runtime via a builder function. */
static dynamicToggle<OuterType = unknown>(a: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: boolean;
disabled?: false | string;
}, OuterType>): Value<boolean, boolean, OuterType>;
/**
* @description Displays a text input field
* @example
* ```
textExample: Value.text({
// required
name: 'Text Example',
required: false,
default: null,
// optional
description: null,
placeholder: null,
warning: null,
generate: null,
inputmode: 'text',
masked: false,
minLength: null,
maxLength: null,
patterns: [],
immutable: false,
}),
* ```
*/
static text<Required extends boolean>(a: {
name: string;
description?: string | null;
/** Presents a warning prompt before permitting the value to change. */
warning?: string | null;
/**
* provide a default value.
* @type { string | RandomString | null }
* @example default: null
* @example default: 'World'
* @example default: { charset: 'abcdefg', len: 16 }
*/
default: string | RandomString | null;
required: Required;
/**
* @description Mask (aka camouflage) text input with dots: ● ● ●
* @default false
*/
masked?: boolean;
placeholder?: string | null;
minLength?: number | null;
maxLength?: number | null;
/**
* @description A list of regular expressions to which the text must conform to pass validation. A human readable description is provided in case the validation fails.
* @default []
* @example
* ```
[
{
regex: "[a-z]",
description: "May only contain lower case letters from the English alphabet."
}
]
* ```
*/
patterns?: Pattern[];
/**
* @description Informs the browser how to behave and which keyboard to display on mobile
* @default "text"
*/
inputmode?: ValueSpecText['inputmode'];
/**
* @description Once set, the value can never be changed.
* @default false
*/
immutable?: boolean;
/**
* @description Displays a button that will generate a random string according to the provided charset and len attributes.
*/
generate?: RandomString | null;
}): Value<AsRequired<string, Required>, AsRequired<string, Required>, unknown>;
/** Like {@link Value.text} but options are resolved lazily at runtime via a builder function. */
static dynamicText<Required extends boolean, OuterType = unknown>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: DefaultString | null;
required: Required;
masked?: boolean;
placeholder?: string | null;
minLength?: number | null;
maxLength?: number | null;
patterns?: Pattern[];
inputmode?: ValueSpecText['inputmode'];
disabled?: string | false;
generate?: null | RandomString;
}, OuterType>): Value<AsRequired<string, Required>, string | null, OuterType>;
/**
* @description Displays a large textarea field for long form entry.
* @example
* ```
textareaExample: Value.textarea({
// required
name: 'Textarea Example',
required: false,
default: null,
// optional
description: null,
placeholder: null,
warning: null,
minLength: null,
maxLength: null,
minRows: 3
maxRows: 6
immutable: false,
}),
* ```
*/
static textarea<Required extends boolean>(a: {
name: string;
description?: string | null;
/** Presents a warning prompt before permitting the value to change. */
warning?: string | null;
default: string | null;
required: Required;
minLength?: number | null;
maxLength?: number | null;
/**
* @description A list of regular expressions to which the text must conform to pass validation. A human readable description is provided in case the validation fails.
* @default []
* @example
* ```
[
{
regex: "[a-z]",
description: "May only contain lower case letters from the English alphabet."
}
]
* ```
*/
patterns?: Pattern[];
/** Defaults to 3 */
minRows?: number;
/** Maximum number of rows before scroll appears. Defaults to 6 */
maxRows?: number;
placeholder?: string | null;
/**
* @description Once set, the value can never be changed.
* @default false
*/
immutable?: boolean;
}): Value<AsRequired<string, Required>, AsRequired<string, Required>, unknown>;
/** Like {@link Value.textarea} but options are resolved lazily at runtime via a builder function. */
static dynamicTextarea<Required extends boolean, OuterType = unknown>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: string | null;
required: Required;
minLength?: number | null;
maxLength?: number | null;
patterns?: Pattern[];
minRows?: number;
maxRows?: number;
placeholder?: string | null;
disabled?: false | string;
}, OuterType>): Value<AsRequired<string, Required>, string | null, OuterType>;
/**
* @description Displays a number input field
* @example
* ```
numberExample: Value.number({
// required
name: 'Number Example',
required: false,
default: null,
integer: true,
// optional
description: null,
placeholder: null,
warning: null,
min: null,
max: null,
immutable: false,
step: null,
units: null,
}),
* ```
*/
static number<Required extends boolean>(a: {
name: string;
description?: string | null;
/** Presents a warning prompt before permitting the value to change. */
warning?: string | null;
/**
* @description optionally provide a default value.
* @type { default: number | null }
* @example default: null
* @example default: 7
*/
default: number | null;
required: Required;
min?: number | null;
max?: number | null;
/**
* @description How much does the number increase/decrease when using the arrows provided by the browser.
* @default 1
*/
step?: number | null;
/**
* @description Requires the number to be an integer.
*/
integer: boolean;
/**
* @description Optionally display units to the right of the input box.
*/
units?: string | null;
placeholder?: string | null;
/**
* @description Once set, the value can never be changed.
* @default false
*/
immutable?: boolean;
}): Value<AsRequired<number, Required>, AsRequired<number, Required>, unknown>;
/** Like {@link Value.number} but options are resolved lazily at runtime via a builder function. */
static dynamicNumber<Required extends boolean, OuterType = unknown>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: number | null;
required: Required;
min?: number | null;
max?: number | null;
step?: number | null;
integer: boolean;
units?: string | null;
placeholder?: string | null;
disabled?: false | string;
}, OuterType>): Value<AsRequired<number, Required>, number | null, OuterType>;
/**
* @description Displays a browser-native color selector.
* @example
* ```
colorExample: Value.color({
// required
name: 'Color Example',
required: false,
default: null,
// optional
description: null,
warning: null,
immutable: false,
}),
* ```
*/
static color<Required extends boolean>(a: {
name: string;
description?: string | null;
/** Presents a warning prompt before permitting the value to change. */
warning?: string | null;
/**
* @description optionally provide a default value.
* @type { default: string | null }
* @example default: null
* @example default: 'ffffff'
*/
default: string | null;
required: Required;
/**
* @description Once set, the value can never be changed.
* @default false
*/
immutable?: boolean;
}): Value<AsRequired<string, Required>, AsRequired<string, Required>, unknown>;
/** Like {@link Value.color} but options are resolved lazily at runtime via a builder function. */
static dynamicColor<Required extends boolean, OuterType = unknown>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: string | null;
required: Required;
disabled?: false | string;
}, OuterType>): Value<AsRequired<string, Required>, string | null, OuterType>;
/**
* @description Displays a browser-native date/time selector.
* @example
* ```
datetimeExample: Value.datetime({
// required
name: 'Datetime Example',
required: false,
default: null,
// optional
description: null,
warning: null,
immutable: false,
inputmode: 'datetime-local',
min: null,
max: null,
}),
* ```
*/
static datetime<Required extends boolean>(a: {
name: string;
description?: string | null;
/** Presents a warning prompt before permitting the value to change. */
warning?: string | null;
/**
* @description optionally provide a default value.
* @type { default: string | null }
* @example default: null
* @example default: '1985-12-16 18:00:00.000'
*/
default: string | null;
required: Required;
/**
* @description Informs the browser how to behave and which date/time component to display.
* @default "datetime-local"
*/
inputmode?: ValueSpecDatetime['inputmode'];
min?: string | null;
max?: string | null;
/**
* @description Once set, the value can never be changed.
* @default false
*/
immutable?: boolean;
}): Value<AsRequired<string, Required>, AsRequired<string, Required>, unknown>;
/** Like {@link Value.datetime} but options are resolved lazily at runtime via a builder function. */
static dynamicDatetime<Required extends boolean, OuterType = unknown>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: string | null;
required: Required;
inputmode?: ValueSpecDatetime['inputmode'];
min?: string | null;
max?: string | null;
disabled?: false | string;
}, OuterType>): Value<AsRequired<string, Required>, string | null, OuterType>;
/**
* @description Displays a select modal with radio buttons, allowing for a single selection.
* @example
* ```
selectExample: Value.select({
// required
name: 'Select Example',
default: 'radio1',
values: {
radio1: 'Radio 1',
radio2: 'Radio 2',
},
// optional
description: null,
warning: null,
immutable: false,
disabled: false,
}),
* ```
*/
static select<Values extends Record<string, string>>(a: {
name: string;
description?: string | null;
/** Presents a warning prompt before permitting the value to change. */
warning?: string | null;
/**
* @description Determines if the field is required. If so, optionally provide a default value from the list of values.
* @type { (keyof Values & string) | null }
* @example default: null
* @example default: 'radio1'
*/
default: keyof Values & string;
/**
* @description A mapping of unique radio options to their human readable display format.
* @example
* ```
{
radio1: "Radio 1"
radio2: "Radio 2"
radio3: "Radio 3"
}
* ```
*/
values: Values;
/**
* @description Once set, the value can never be changed.
* @default false
*/
immutable?: boolean;
}): Value<keyof Values & string, keyof Values & string, unknown>;
/** Like {@link Value.select} but options are resolved lazily at runtime via a builder function. */
static dynamicSelect<Values extends Record<string, string>, OuterType = unknown>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: string;
values: Values;
disabled?: false | string | string[];
}, OuterType>): Value<keyof Values & string, keyof Values & string, OuterType>;
/**
* @description Displays a select modal with checkboxes, allowing for multiple selections.
* @example
* ```
multiselectExample: Value.multiselect({
// required
name: 'Multiselect Example',
values: {
option1: 'Option 1',
option2: 'Option 2',
},
default: [],
// optional
description: null,
warning: null,
immutable: false,
disabled: false,
minlength: null,
maxLength: null,
}),
* ```
*/
static multiselect<Values extends Record<string, string>>(a: {
name: string;
description?: string | null;
/** Presents a warning prompt before permitting the value to change. */
warning?: string | null;
/**
* @description A simple list of which options should be checked by default.
*/
default: (keyof Values & string)[];
/**
* @description A mapping of checkbox options to their human readable display format.
* @example
* ```
{
option1: "Option 1"
option2: "Option 2"
option3: "Option 3"
}
* ```
*/
values: Values;
minLength?: number | null;
maxLength?: number | null;
/**
* @description Once set, the value can never be changed.
* @default false
*/
immutable?: boolean;
}): Value<(keyof Values & string)[], (keyof Values & string)[], unknown>;
/** Like {@link Value.multiselect} but options are resolved lazily at runtime via a builder function. */
static dynamicMultiselect<Values extends Record<string, string>, OuterType = unknown>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
default: string[];
values: Values;
minLength?: number | null;
maxLength?: number | null;
disabled?: false | string | string[];
}, OuterType>): Value<(keyof Values & string)[], (keyof Values & string)[], OuterType>;
/**
* @description Display a collapsable grouping of additional fields, a "sub form". The second value is the inputSpec spec for the sub form.
* @example
* ```
objectExample: Value.object(
{
// required
name: 'Object Example',
// optional
description: null,
warning: null,
},
InputSpec.of({}),
),
* ```
*/
static object<Type extends StaticValidatedAs, StaticValidatedAs extends Record<string, any>>(a: {
name: string;
description?: string | null;
}, spec: InputSpec<Type, StaticValidatedAs>): Value<Type, StaticValidatedAs, unknown>;
/**
* Displays a file upload input field.
*
* @param a.extensions - Allowed file extensions (e.g. `[".pem", ".crt"]`)
* @param a.required - Whether a file must be selected
*/
static file<Required extends boolean>(a: {
name: string;
description?: string | null;
warning?: string | null;
extensions: string[];
required: Required;
}): Value<AsRequired<{
path: string;
commitment: {
hash: string;
size: number;
};
}, Required>, AsRequired<{
path: string;
commitment: {
hash: string;
size: number;
};
}, Required>, unknown>;
/** Like {@link Value.file} but options are resolved lazily at runtime via a builder function. */
static dynamicFile<Required extends boolean, OuterType = unknown>(a: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
extensions: string[];
required: Required;
}, OuterType>): Value<AsRequired<{
path: string;
commitment: {
hash: string;
size: number;
};
}, Required>, {
path: string;
commitment: {
hash: string;
size: number;
};
} | null, OuterType>;
/**
* @description Displays a dropdown, allowing for a single selection. Depending on the selection, a different object ("sub form") is presented.
* @example
* ```
unionExample: Value.union(
{
// required
name: 'Union Example',
default: 'option1',
// optional
description: null,
warning: null,
disabled: false,
immutable: false,
},
Variants.of({
option1: {
name: 'Option 1',
spec: InputSpec.of({}),
},
option2: {
name: 'Option 2',
spec: InputSpec.of({}),
},
}),
),
* ```
*/
static union<VariantValues extends {
[K in string]: {
name: string;
spec: InputSpec<any>;
};
}>(a: {
name: string;
description?: string | null;
/** Presents a warning prompt before permitting the value to change. */
warning?: string | null;
variants: Variants<VariantValues>;
/**
* @description Provide a default value from the list of variants.
* @type { string }
* @example default: 'variant1'
*/
default: keyof VariantValues & string;
/**
* @description Once set, the value can never be changed.
* @default false
*/
immutable?: boolean;
}): Value<UnionRes<VariantValues, keyof VariantValues & string>, UnionResStaticValidatedAs<VariantValues, keyof VariantValues & string>, unknown>;
/** Like {@link Value.union} but options (including which variants are available) are resolved lazily at runtime. */
static dynamicUnion<VariantValues extends {
[K in string]: {
name: string;
spec: InputSpec<any>;
};
}, OuterType = unknown>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
variants: Variants<VariantValues>;
default: keyof VariantValues & string;
disabled: string[] | false | string;
}, OuterType>): Value<UnionRes<VariantValues>, UnionRes<VariantValues>, OuterType>;
/** Like {@link Value.union} but options are resolved lazily, with an explicit static validator type. */
static dynamicUnion<StaticVariantValues extends {
[K in string]: {
name: string;
spec: InputSpec<any, any>;
};
}, VariantValues extends StaticVariantValues, OuterType = unknown>(getA: LazyBuild<{
name: string;
description?: string | null;
warning?: string | null;
variants: Variants<VariantValues>;
default: keyof VariantValues & string;
disabled: string[] | false | string;
}, OuterType>, validator: z.ZodType<UnionResStaticValidatedAs<StaticVariantValues>>): Value<UnionRes<VariantValues>, UnionResStaticValidatedAs<StaticVariantValues>, OuterType>;
/**
* @description Presents an interface to add/remove/edit items in a list.
* @example
* In this example, we create a list of text inputs.
*
* ```
listExampleText: Value.list(
List.text(
{
// required
name: 'Text List',
// optional
description: null,
warning: null,
default: [],
minLength: null,
maxLength: null,
},
{
// required
patterns: [],
// optional
placeholder: null,
generate: null,
inputmode: 'url',
masked: false,
minLength: null,
maxLength: null,
},
),
),
* ```
* @example
* In this example, we create a list of objects.
*
* ```
listExampleObject: Value.list(
List.obj(
{
// required
name: 'Object List',
// optional
description: null,
warning: null,
default: [],
minLength: null,
maxLength: null,
},
{
// required
spec: InputSpec.of({}),
// optional
displayAs: null,
uniqueBy: null,
},
),
),
* ```
*/
static list<Type>(a: List<Type>): Value<Type, Type, unknown>;
/**
* @description Provides a way to define a hidden field with a static value. Useful for tracking
* @example
* ```
hiddenExample: Value.hidden(),
* ```
*/
static hidden<T>(): Value<T>;
static hidden<T>(parser: z.ZodType<T>): Value<T>;
/**
* @description Provides a way to define a hidden field with a static value. Useful for tracking
* @example
* ```
hiddenExample: Value.hidden(),
* ```
*/
static dynamicHidden<T, OuterType = unknown>(getParser: LazyBuild<z.ZodType<T>, OuterType>): Value<T, T, OuterType>;
/**
* Returns a new Value that produces the same field spec but with `disabled` set to the given message.
* The field remains in the form but cannot be edited by the user.
*
* @param message - The reason the field is disabled, displayed to the user
*/
withDisabled(message: string): Value<Type, StaticValidatedAs, OuterType>;
/**
* Transforms the validated output value using a mapping function.
* The form field itself remains unchanged, but the value is transformed after validation.
*
* @param fn - A function to transform the validated value
*/
map<U>(fn: (value: StaticValidatedAs) => U): Value<U, U, OuterType>;
}
@@ -0,0 +1,764 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Value = exports.fileInfoParser = void 0;
const util_1 = require("../../../util");
const zod_1 = require("zod");
/** Build a union-of-literals validator from object keys, falling back to z.string() when empty */
function literalKeysValidator(values) {
const keys = Object.keys(values);
if (keys.length === 0)
return zod_1.z.string();
return zod_1.z.union(keys.map((x) => zod_1.z.literal(x)));
}
/** Zod schema for a file upload result — validates `{ path, commitment: { hash, size } }`. */
exports.fileInfoParser = zod_1.z.object({
path: zod_1.z.string(),
commitment: zod_1.z.object({ hash: zod_1.z.string(), size: zod_1.z.number() }),
});
const testForAsRequiredParser = (0, util_1.once)(() => (v) => zod_1.z.object({ required: zod_1.z.literal(true) }).safeParse(v).success);
function asRequiredParser(parser, input) {
if (testForAsRequiredParser()(input))
return parser;
return parser.nullable();
}
/**
* Core builder class for defining a single form field in a service configuration spec.
*
* Each static factory method (e.g. `Value.text()`, `Value.toggle()`, `Value.select()`) creates
* a typed `Value` instance representing a specific field type. Dynamic variants (e.g. `Value.dynamicText()`)
* allow the field options to be computed lazily at runtime.
*
* Use with {@link InputSpec} to compose complete form specifications.
*
* @typeParam Type - The runtime type this field produces when filled in
* @typeParam StaticValidatedAs - The compile-time validated type (usually same as Type)
* @typeParam OuterType - The parent form's type context (used by dynamic variants)
*/
class Value {
constructor(build, validator) {
this.build = build;
this.validator = validator;
this._TYPE = null;
this._PARTIAL = null;
}
/**
* @description Displays a boolean toggle to enable/disable
* @example
* ```
toggleExample: Value.toggle({
// required
name: 'Toggle Example',
default: true,
// optional
description: null,
warning: null,
immutable: false,
}),
* ```
*/
static toggle(a) {
const validator = zod_1.z.boolean();
return new Value(async () => ({
spec: {
description: null,
warning: null,
type: 'toggle',
disabled: false,
immutable: a.immutable ?? false,
...a,
},
validator,
}), validator);
}
/** Like {@link Value.toggle} but options are resolved lazily at runtime via a builder function. */
static dynamicToggle(a) {
const validator = zod_1.z.boolean();
return new Value(async (options) => ({
spec: {
description: null,
warning: null,
type: 'toggle',
disabled: false,
immutable: false,
...(await a(options)),
},
validator,
}), validator);
}
/**
* @description Displays a text input field
* @example
* ```
textExample: Value.text({
// required
name: 'Text Example',
required: false,
default: null,
// optional
description: null,
placeholder: null,
warning: null,
generate: null,
inputmode: 'text',
masked: false,
minLength: null,
maxLength: null,
patterns: [],
immutable: false,
}),
* ```
*/
static text(a) {
const validator = asRequiredParser(zod_1.z.string(), a);
return new Value(async () => ({
spec: {
type: 'text',
description: null,
warning: null,
masked: false,
placeholder: null,
minLength: null,
maxLength: null,
patterns: [],
inputmode: 'text',
disabled: false,
immutable: a.immutable ?? false,
generate: a.generate ?? null,
...a,
},
validator,
}), validator);
}
/** Like {@link Value.text} but options are resolved lazily at runtime via a builder function. */
static dynamicText(getA) {
return new Value(async (options) => {
const a = await getA(options);
return {
spec: {
type: 'text',
description: null,
warning: null,
masked: false,
placeholder: null,
minLength: null,
maxLength: null,
patterns: [],
inputmode: 'text',
disabled: false,
immutable: false,
generate: a.generate ?? null,
...a,
},
validator: asRequiredParser(zod_1.z.string(), a),
};
}, zod_1.z.string().nullable());
}
/**
* @description Displays a large textarea field for long form entry.
* @example
* ```
textareaExample: Value.textarea({
// required
name: 'Textarea Example',
required: false,
default: null,
// optional
description: null,
placeholder: null,
warning: null,
minLength: null,
maxLength: null,
minRows: 3
maxRows: 6
immutable: false,
}),
* ```
*/
static textarea(a) {
const validator = asRequiredParser(zod_1.z.string(), a);
return new Value(async () => {
const built = {
description: null,
warning: null,
minLength: null,
maxLength: null,
patterns: [],
minRows: 3,
maxRows: 6,
placeholder: null,
type: 'textarea',
disabled: false,
immutable: a.immutable ?? false,
...a,
};
return { spec: built, validator };
}, validator);
}
/** Like {@link Value.textarea} but options are resolved lazily at runtime via a builder function. */
static dynamicTextarea(getA) {
return new Value(async (options) => {
const a = await getA(options);
return {
spec: {
description: null,
warning: null,
minLength: null,
maxLength: null,
patterns: [],
minRows: 3,
maxRows: 6,
placeholder: null,
type: 'textarea',
disabled: false,
immutable: false,
...a,
},
validator: asRequiredParser(zod_1.z.string(), a),
};
}, zod_1.z.string().nullable());
}
/**
* @description Displays a number input field
* @example
* ```
numberExample: Value.number({
// required
name: 'Number Example',
required: false,
default: null,
integer: true,
// optional
description: null,
placeholder: null,
warning: null,
min: null,
max: null,
immutable: false,
step: null,
units: null,
}),
* ```
*/
static number(a) {
const validator = asRequiredParser(zod_1.z.number(), a);
return new Value(() => ({
spec: {
type: 'number',
description: null,
warning: null,
min: null,
max: null,
step: null,
units: null,
placeholder: null,
disabled: false,
immutable: a.immutable ?? false,
...a,
},
validator,
}), validator);
}
/** Like {@link Value.number} but options are resolved lazily at runtime via a builder function. */
static dynamicNumber(getA) {
return new Value(async (options) => {
const a = await getA(options);
return {
spec: {
type: 'number',
description: null,
warning: null,
min: null,
max: null,
step: null,
units: null,
placeholder: null,
disabled: false,
immutable: false,
...a,
},
validator: asRequiredParser(zod_1.z.number(), a),
};
}, zod_1.z.number().nullable());
}
/**
* @description Displays a browser-native color selector.
* @example
* ```
colorExample: Value.color({
// required
name: 'Color Example',
required: false,
default: null,
// optional
description: null,
warning: null,
immutable: false,
}),
* ```
*/
static color(a) {
const validator = asRequiredParser(zod_1.z.string(), a);
return new Value(() => ({
spec: {
type: 'color',
description: null,
warning: null,
disabled: false,
immutable: a.immutable ?? false,
...a,
},
validator,
}), validator);
}
/** Like {@link Value.color} but options are resolved lazily at runtime via a builder function. */
static dynamicColor(getA) {
return new Value(async (options) => {
const a = await getA(options);
return {
spec: {
type: 'color',
description: null,
warning: null,
disabled: false,
immutable: false,
...a,
},
validator: asRequiredParser(zod_1.z.string(), a),
};
}, zod_1.z.string().nullable());
}
/**
* @description Displays a browser-native date/time selector.
* @example
* ```
datetimeExample: Value.datetime({
// required
name: 'Datetime Example',
required: false,
default: null,
// optional
description: null,
warning: null,
immutable: false,
inputmode: 'datetime-local',
min: null,
max: null,
}),
* ```
*/
static datetime(a) {
const validator = asRequiredParser(zod_1.z.string(), a);
return new Value(() => ({
spec: {
type: 'datetime',
description: null,
warning: null,
inputmode: 'datetime-local',
min: null,
max: null,
step: null,
disabled: false,
immutable: a.immutable ?? false,
...a,
},
validator,
}), validator);
}
/** Like {@link Value.datetime} but options are resolved lazily at runtime via a builder function. */
static dynamicDatetime(getA) {
return new Value(async (options) => {
const a = await getA(options);
return {
spec: {
type: 'datetime',
description: null,
warning: null,
inputmode: 'datetime-local',
min: null,
max: null,
disabled: false,
immutable: false,
...a,
},
validator: asRequiredParser(zod_1.z.string(), a),
};
}, zod_1.z.string().nullable());
}
/**
* @description Displays a select modal with radio buttons, allowing for a single selection.
* @example
* ```
selectExample: Value.select({
// required
name: 'Select Example',
default: 'radio1',
values: {
radio1: 'Radio 1',
radio2: 'Radio 2',
},
// optional
description: null,
warning: null,
immutable: false,
disabled: false,
}),
* ```
*/
static select(a) {
const validator = literalKeysValidator(a.values);
return new Value(() => ({
spec: {
description: null,
warning: null,
type: 'select',
disabled: false,
immutable: a.immutable ?? false,
...a,
},
validator,
}), validator);
}
/** Like {@link Value.select} but options are resolved lazily at runtime via a builder function. */
static dynamicSelect(getA) {
return new Value(async (options) => {
const a = await getA(options);
return {
spec: {
description: null,
warning: null,
type: 'select',
disabled: false,
immutable: false,
...a,
},
validator: literalKeysValidator(a.values),
};
}, zod_1.z.string());
}
/**
* @description Displays a select modal with checkboxes, allowing for multiple selections.
* @example
* ```
multiselectExample: Value.multiselect({
// required
name: 'Multiselect Example',
values: {
option1: 'Option 1',
option2: 'Option 2',
},
default: [],
// optional
description: null,
warning: null,
immutable: false,
disabled: false,
minlength: null,
maxLength: null,
}),
* ```
*/
static multiselect(a) {
const validator = zod_1.z.array(literalKeysValidator(a.values));
return new Value(() => ({
spec: {
type: 'multiselect',
minLength: null,
maxLength: null,
warning: null,
description: null,
disabled: false,
immutable: a.immutable ?? false,
...a,
},
validator,
}), validator);
}
/** Like {@link Value.multiselect} but options are resolved lazily at runtime via a builder function. */
static dynamicMultiselect(getA) {
return new Value(async (options) => {
const a = await getA(options);
return {
spec: {
type: 'multiselect',
minLength: null,
maxLength: null,
warning: null,
description: null,
disabled: false,
immutable: false,
...a,
},
validator: zod_1.z.array(literalKeysValidator(a.values)),
};
}, zod_1.z.array(zod_1.z.string()));
}
/**
* @description Display a collapsable grouping of additional fields, a "sub form". The second value is the inputSpec spec for the sub form.
* @example
* ```
objectExample: Value.object(
{
// required
name: 'Object Example',
// optional
description: null,
warning: null,
},
InputSpec.of({}),
),
* ```
*/
static object(a, spec) {
const value = new Value(async (options) => {
const built = await spec.build(options);
return {
spec: {
type: 'object',
description: null,
warning: null,
...a,
spec: built.spec,
},
validator: built.validator,
};
}, spec.validator);
value._objectSpec = { inputSpec: spec, params: a };
return value;
}
/**
* Displays a file upload input field.
*
* @param a.extensions - Allowed file extensions (e.g. `[".pem", ".crt"]`)
* @param a.required - Whether a file must be selected
*/
static file(a) {
const buildValue = {
type: 'file',
description: null,
warning: null,
...a,
};
return new Value(() => ({
spec: {
...buildValue,
},
validator: asRequiredParser(exports.fileInfoParser, a),
}), asRequiredParser(exports.fileInfoParser, a));
}
/** Like {@link Value.file} but options are resolved lazily at runtime via a builder function. */
static dynamicFile(a) {
return new Value(async (options) => {
const spec = {
type: 'file',
description: null,
warning: null,
...(await a(options)),
};
return {
spec,
validator: asRequiredParser(exports.fileInfoParser, spec),
};
}, exports.fileInfoParser.nullable());
}
/**
* @description Displays a dropdown, allowing for a single selection. Depending on the selection, a different object ("sub form") is presented.
* @example
* ```
unionExample: Value.union(
{
// required
name: 'Union Example',
default: 'option1',
// optional
description: null,
warning: null,
disabled: false,
immutable: false,
},
Variants.of({
option1: {
name: 'Option 1',
spec: InputSpec.of({}),
},
option2: {
name: 'Option 2',
spec: InputSpec.of({}),
},
}),
),
* ```
*/
static union(a) {
return new Value(async (options) => {
const built = await a.variants.build(options);
return {
spec: {
type: 'union',
description: null,
warning: null,
disabled: false,
...a,
variants: built.spec,
immutable: a.immutable ?? false,
},
validator: built.validator,
};
}, a.variants.validator);
}
static dynamicUnion(getA, validator = zod_1.z.any()) {
return new Value(async (options) => {
const newValues = await getA(options);
const built = await newValues.variants.build(options);
return {
spec: {
type: 'union',
description: null,
warning: null,
...newValues,
variants: built.spec,
immutable: false,
},
validator: built.validator,
};
}, validator);
}
/**
* @description Presents an interface to add/remove/edit items in a list.
* @example
* In this example, we create a list of text inputs.
*
* ```
listExampleText: Value.list(
List.text(
{
// required
name: 'Text List',
// optional
description: null,
warning: null,
default: [],
minLength: null,
maxLength: null,
},
{
// required
patterns: [],
// optional
placeholder: null,
generate: null,
inputmode: 'url',
masked: false,
minLength: null,
maxLength: null,
},
),
),
* ```
* @example
* In this example, we create a list of objects.
*
* ```
listExampleObject: Value.list(
List.obj(
{
// required
name: 'Object List',
// optional
description: null,
warning: null,
default: [],
minLength: null,
maxLength: null,
},
{
// required
spec: InputSpec.of({}),
// optional
displayAs: null,
uniqueBy: null,
},
),
),
* ```
*/
static list(a) {
return new Value((options) => a.build(options), a.validator);
}
static hidden(parser = zod_1.z.any()) {
return new Value(async () => {
return {
spec: {
type: 'hidden',
},
validator: parser,
};
}, parser);
}
/**
* @description Provides a way to define a hidden field with a static value. Useful for tracking
* @example
* ```
hiddenExample: Value.hidden(),
* ```
*/
static dynamicHidden(getParser) {
return new Value(async (options) => {
const validator = await getParser(options);
return {
spec: {
type: 'hidden',
},
validator,
};
}, zod_1.z.any());
}
/**
* Returns a new Value that produces the same field spec but with `disabled` set to the given message.
* The field remains in the form but cannot be edited by the user.
*
* @param message - The reason the field is disabled, displayed to the user
*/
withDisabled(message) {
const original = this;
const v = new Value(async (options) => {
const built = await original.build(options);
return {
spec: { ...built.spec, disabled: message },
validator: built.validator,
};
}, this.validator);
v._objectSpec = this._objectSpec;
return v;
}
/**
* Transforms the validated output value using a mapping function.
* The form field itself remains unchanged, but the value is transformed after validation.
*
* @param fn - A function to transform the validated value
*/
map(fn) {
return new Value(async (options) => {
const built = await this.build(options);
return {
spec: built.spec,
validator: built.validator.transform(fn),
};
}, this.validator.transform(fn));
}
}
exports.Value = Value;
//# sourceMappingURL=value.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,111 @@
import { DeepPartial } from '../../../types';
import { ValueSpecUnion } from '../inputSpecTypes';
import { LazyBuild, InputSpec, ExtractInputSpecType, ExtractInputSpecStaticValidatedAs } from './inputSpec';
import { z } from 'zod';
/**
* The runtime result type of a discriminated union form field.
* Contains `selection` (the chosen variant key), `value` (the variant's form data),
* and optionally `other` (partial data from previously selected variants).
*/
export type UnionRes<VariantValues extends {
[K in string]: {
name: string;
spec: InputSpec<any>;
};
}, K extends keyof VariantValues & string = keyof VariantValues & string> = {
[key in keyof VariantValues]: {
selection: key;
value: ExtractInputSpecType<VariantValues[key]['spec']>;
other?: {
[key2 in Exclude<keyof VariantValues & string, key>]?: DeepPartial<ExtractInputSpecType<VariantValues[key2]['spec']>>;
};
};
}[K];
/** Like {@link UnionRes} but using the static (Zod-inferred) validated types. */
export type UnionResStaticValidatedAs<VariantValues extends {
[K in string]: {
name: string;
spec: InputSpec<any>;
};
}, K extends keyof VariantValues & string = keyof VariantValues & string> = {
[key in keyof VariantValues]: {
selection: key;
value: ExtractInputSpecStaticValidatedAs<VariantValues[key]['spec']>;
other?: {
[key2 in Exclude<keyof VariantValues & string, key>]?: DeepPartial<ExtractInputSpecStaticValidatedAs<VariantValues[key2]['spec']>>;
};
};
}[K];
/**
* Used in the the Value.select { @link './value.ts' }
* to indicate the type of select variants that are available. The key for the record passed in will be the
* key to the tag.id in the Value.select
```ts
export const disabled = InputSpec.of({});
export const size = Value.number({
name: "Max Chain Size",
default: 550,
description: "Limit of blockchain size on disk.",
warning: "Increasing this value will require re-syncing your node.",
required: true,
range: "[550,1000000)",
integral: true,
units: "MiB",
placeholder: null,
});
export const automatic = InputSpec.of({ size: size });
export const size1 = Value.number({
name: "Failsafe Chain Size",
default: 65536,
description: "Prune blockchain if size expands beyond this.",
warning: null,
required: true,
range: "[550,1000000)",
integral: true,
units: "MiB",
placeholder: null,
});
export const manual = InputSpec.of({ size: size1 });
export const pruningSettingsVariants = Variants.of({
disabled: { name: "Disabled", spec: disabled },
automatic: { name: "Automatic", spec: automatic },
manual: { name: "Manual", spec: manual },
});
export const pruning = Value.union(
{
name: "Pruning Settings",
description:
'- Disabled: Disable pruning\n- Automatic: Limit blockchain size on disk to a certain number of megabytes\n- Manual: Prune blockchain with the "pruneblockchain" RPC\n',
warning: null,
default: "disabled",
},
pruningSettingsVariants
);
```
*/
export declare class Variants<VariantValues extends {
[K in string]: {
name: string;
spec: InputSpec<any, any>;
};
}, OuterType = unknown> {
build: LazyBuild<{
spec: ValueSpecUnion['variants'];
validator: z.ZodType<UnionRes<VariantValues>>;
}, OuterType>;
readonly validator: z.ZodType<UnionResStaticValidatedAs<VariantValues>>;
private constructor();
readonly _TYPE: UnionRes<VariantValues>;
/**
* Creates a `Variants` instance from a record mapping variant keys to their display name and form spec.
*
* @param a - A record of `{ name: string, spec: InputSpec }` entries, one per variant
*/
static of<VariantValues extends {
[K in string]: {
name: string;
spec: InputSpec<any>;
};
}>(a: VariantValues): Variants<VariantValues, unknown>;
}
@@ -0,0 +1,110 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Variants = void 0;
const zod_1 = require("zod");
/**
* Used in the the Value.select { @link './value.ts' }
* to indicate the type of select variants that are available. The key for the record passed in will be the
* key to the tag.id in the Value.select
```ts
export const disabled = InputSpec.of({});
export const size = Value.number({
name: "Max Chain Size",
default: 550,
description: "Limit of blockchain size on disk.",
warning: "Increasing this value will require re-syncing your node.",
required: true,
range: "[550,1000000)",
integral: true,
units: "MiB",
placeholder: null,
});
export const automatic = InputSpec.of({ size: size });
export const size1 = Value.number({
name: "Failsafe Chain Size",
default: 65536,
description: "Prune blockchain if size expands beyond this.",
warning: null,
required: true,
range: "[550,1000000)",
integral: true,
units: "MiB",
placeholder: null,
});
export const manual = InputSpec.of({ size: size1 });
export const pruningSettingsVariants = Variants.of({
disabled: { name: "Disabled", spec: disabled },
automatic: { name: "Automatic", spec: automatic },
manual: { name: "Manual", spec: manual },
});
export const pruning = Value.union(
{
name: "Pruning Settings",
description:
'- Disabled: Disable pruning\n- Automatic: Limit blockchain size on disk to a certain number of megabytes\n- Manual: Prune blockchain with the "pruneblockchain" RPC\n',
warning: null,
default: "disabled",
},
pruningSettingsVariants
);
```
*/
class Variants {
constructor(build, validator) {
this.build = build;
this.validator = validator;
this._TYPE = null;
}
/**
* Creates a `Variants` instance from a record mapping variant keys to their display name and form spec.
*
* @param a - A record of `{ name: string, spec: InputSpec }` entries, one per variant
*/
static of(a) {
const staticValidators = {};
for (const key in a) {
const value = a[key];
staticValidators[key] = value.spec.validator;
}
const other = zod_1.z
.object(Object.fromEntries(Object.entries(staticValidators).map(([k, v]) => [
k,
zod_1.z.any().optional(),
])))
.optional();
return new Variants(async (options) => {
const validators = {};
const variants = {};
for (const key in a) {
const value = a[key];
const built = await value.spec.build(options);
variants[key] = {
name: value.name,
spec: built.spec,
};
validators[key] = built.validator;
}
const other = zod_1.z
.object(Object.fromEntries(Object.entries(validators).map(([k, v]) => [
k,
zod_1.z.any().optional(),
])))
.optional();
return {
spec: variants,
validator: zod_1.z.union(Object.entries(validators).map(([k, v]) => zod_1.z.object({
selection: zod_1.z.literal(k),
value: v,
other,
}))),
};
}, zod_1.z.union(Object.entries(staticValidators).map(([k, v]) => zod_1.z.object({
selection: zod_1.z.literal(k),
value: v,
other,
}))));
}
}
exports.Variants = Variants;
//# sourceMappingURL=variants.js.map
@@ -0,0 +1 @@
{"version":3,"file":"variants.js","sourceRoot":"","sources":["../../../../../../base/lib/actions/input/builder/variants.ts"],"names":[],"mappings":";;;AAQA,6BAAuB;AAgDvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,MAAa,QAAQ;IASnB,YACS,KAMN,EACe,SAEf;QATM,UAAK,GAAL,KAAK,CAMX;QACe,cAAS,GAAT,SAAS,CAExB;QAEM,UAAK,GAA4B,IAAW,CAAA;IADlD,CAAC;IAEJ;;;;OAIG;IACH,MAAM,CAAC,EAAE,CAOP,CAAgB;QAChB,MAAM,gBAAgB,GAAG,EAIxB,CAAA;QACD,KAAK,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAA;YACpB,gBAAgB,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,SAAS,CAAA;QAC9C,CAAC;QACD,MAAM,KAAK,GAAG,OAAC;aACZ,MAAM,CACL,MAAM,CAAC,WAAW,CAChB,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC;YAC/C,CAAC;YACD,OAAC,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;SACnB,CAAC,CACH,CACF;aACA,QAAQ,EAAE,CAAA;QACb,OAAO,IAAI,QAAQ,CACjB,KAAK,EAAE,OAAO,EAAE,EAAE;YAChB,MAAM,UAAU,GAAG,EAIlB,CAAA;YACD,MAAM,QAAQ,GAAG,EAKhB,CAAA;YACD,KAAK,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;gBACpB,MAAM,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAA;gBACpB,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAc,CAAC,CAAA;gBACpD,QAAQ,CAAC,GAAG,CAAC,GAAG;oBACd,IAAI,EAAE,KAAK,CAAC,IAAI;oBAChB,IAAI,EAAE,KAAK,CAAC,IAAI;iBACjB,CAAA;gBACD,UAAU,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,SAAS,CAAA;YACnC,CAAC;YACD,MAAM,KAAK,GAAG,OAAC;iBACZ,MAAM,CACL,MAAM,CAAC,WAAW,CAChB,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC;gBACzC,CAAC;gBACD,OAAC,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;aACnB,CAAC,CACH,CACF;iBACA,QAAQ,EAAE,CAAA;YACb,OAAO;gBACL,IAAI,EAAE,QAAQ;gBACd,SAAS,EAAE,OAAC,CAAC,KAAK,CAChB,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CACxC,OAAC,CAAC,MAAM,CAAC;oBACP,SAAS,EAAE,OAAC,CAAC,OAAO,CAAC,CAAC,CAAC;oBACvB,KAAK,EAAE,CAAC;oBACR,KAAK;iBACN,CAAC,CAC4D,CAC1D;aACT,CAAA;QACH,CAAC,EACD,OAAC,CAAC,KAAK,CACL,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAC9C,OAAC,CAAC,MAAM,CAAC;YACP,SAAS,EAAE,OAAC,CAAC,OAAO,CAAC,CAAC,CAAC;YACvB,KAAK,EAAE,CAAC;YACR,KAAK;SACN,CAAC,CAC4D,CAC1D,CACT,CAAA;IACH,CAAC;CACF;AA9GD,4BA8GC"}