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"}
+3
View File
@@ -0,0 +1,3 @@
export * as constants from './inputSpecConstants';
export * as types from './inputSpecTypes';
export * as builder from './builder';
+40
View File
@@ -0,0 +1,40 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.builder = exports.types = exports.constants = void 0;
exports.constants = __importStar(require("./inputSpecConstants"));
exports.types = __importStar(require("./inputSpecTypes"));
exports.builder = __importStar(require("./builder"));
//# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../base/lib/actions/input/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,kEAAiD;AACjD,0DAAyC;AACzC,qDAAoC"}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,247 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.smtpShape = exports.smtpInputSpec = exports.systemSmtpSpec = exports.smtpProviderVariants = exports.customSmtp = void 0;
exports.smtpPrefill = smtpPrefill;
const util_1 = require("../../util");
const inputSpec_1 = require("./builder/inputSpec");
const value_1 = require("./builder/value");
const variants_1 = require("./builder/variants");
const zod_1 = require("zod");
const securityVariants = variants_1.Variants.of({
tls: {
name: 'TLS',
spec: inputSpec_1.InputSpec.of({
port: value_1.Value.dynamicText(async () => ({
name: 'Port',
required: true,
default: '465',
disabled: 'Fixed for TLS',
})),
}),
},
starttls: {
name: 'STARTTLS',
spec: inputSpec_1.InputSpec.of({
port: value_1.Value.select({
name: 'Port',
default: '587',
values: { '25': '25', '587': '587', '2525': '2525' },
}),
}),
},
});
/**
* Creates an SMTP field spec with provider-specific defaults pre-filled.
*/
function smtpFields(defaults = {}) {
const hostSpec = value_1.Value.text({
name: 'Host',
required: true,
default: defaults.host ?? null,
placeholder: 'smtp.example.com',
});
return inputSpec_1.InputSpec.of({
host: defaults.hostDisabled
? hostSpec.withDisabled('Fixed for this provider')
: hostSpec,
security: value_1.Value.union({
name: 'Connection Security',
default: defaults.security ?? 'tls',
variants: securityVariants,
}),
from: value_1.Value.text({
name: 'From Address',
required: true,
default: null,
placeholder: 'Example Name <test@example.com>',
patterns: [util_1.Patterns.emailWithName],
}),
username: value_1.Value.text({
name: 'Username',
required: true,
default: null,
}),
password: value_1.Value.text({
name: 'Password',
required: false,
default: null,
masked: true,
}),
});
}
/**
* Base SMTP settings with no provider-specific defaults.
*/
exports.customSmtp = smtpFields();
/**
* Provider presets for SMTP configuration.
* Each variant has SMTP fields pre-filled with the provider's recommended settings.
*/
exports.smtpProviderVariants = variants_1.Variants.of({
gmail: {
name: 'Gmail',
spec: smtpFields({
host: 'smtp.gmail.com',
security: 'tls',
hostDisabled: true,
}),
},
ses: {
name: 'Amazon SES',
spec: smtpFields({
host: 'email-smtp.us-east-1.amazonaws.com',
security: 'tls',
}),
},
sendgrid: {
name: 'SendGrid',
spec: smtpFields({
host: 'smtp.sendgrid.net',
security: 'tls',
hostDisabled: true,
}),
},
mailgun: {
name: 'Mailgun',
spec: smtpFields({
host: 'smtp.mailgun.org',
security: 'tls',
hostDisabled: true,
}),
},
protonmail: {
name: 'Proton Mail',
spec: smtpFields({
host: 'smtp.protonmail.ch',
security: 'tls',
hostDisabled: true,
}),
},
other: {
name: 'Other',
spec: exports.customSmtp,
},
});
/**
* System SMTP settings with provider presets.
* Wraps smtpProviderVariants in a union for use by the system email settings page.
*/
exports.systemSmtpSpec = inputSpec_1.InputSpec.of({
provider: value_1.Value.union({
name: 'Provider',
default: 'gmail',
variants: exports.smtpProviderVariants,
}),
});
const smtpVariants = variants_1.Variants.of({
disabled: { name: 'Disabled', spec: inputSpec_1.InputSpec.of({}) },
system: {
name: 'System Credentials',
spec: inputSpec_1.InputSpec.of({
customFrom: value_1.Value.text({
name: 'Custom From Address',
description: 'A custom from address for this service. If not provided, the system from address will be used.',
required: false,
default: null,
placeholder: 'Name <test@example.com>',
patterns: [util_1.Patterns.emailWithName],
}),
}),
},
custom: {
name: 'Custom Credentials',
spec: inputSpec_1.InputSpec.of({
provider: value_1.Value.union({
name: 'Provider',
default: null,
variants: exports.smtpProviderVariants,
}),
}),
},
});
/**
* For service inputSpec. Gives users 3 options for SMTP: (1) disabled, (2) use system SMTP settings, (3) use custom SMTP settings with provider presets
*/
exports.smtpInputSpec = value_1.Value.dynamicUnion(async ({ effects }) => {
const smtp = await new util_1.GetSystemSmtp(effects).once();
const disabled = smtp ? [] : ['system'];
return {
name: 'SMTP',
description: 'Optionally provide an SMTP server for sending emails',
default: 'disabled',
disabled,
variants: smtpVariants,
};
}, smtpVariants.validator);
const securityShape = zod_1.z
.object({
selection: zod_1.z.enum(['tls', 'starttls']).catch('tls'),
value: zod_1.z.object({ port: zod_1.z.string().catch('465') }).catch({ port: '465' }),
})
.catch({ selection: 'tls', value: { port: '465' } });
const providerShape = zod_1.z
.object({
selection: zod_1.z.string().catch('other'),
value: zod_1.z
.object({
host: zod_1.z.string().catch(''),
from: zod_1.z.string().catch(''),
username: zod_1.z.string().catch(''),
password: zod_1.z.string().nullable().optional().catch(null),
security: securityShape,
})
.catch({
host: '',
from: '',
username: '',
password: null,
security: securityShape.parse(undefined),
}),
})
.catch({
selection: 'other',
value: {
host: '',
from: '',
username: '',
password: null,
security: securityShape.parse(undefined),
},
});
/**
* Zod schema for persisting SMTP selection in a store file model.
* Use this instead of `smtpInputSpec.validator` to avoid cross-zod-instance issues.
*/
exports.smtpShape = zod_1.z
.discriminatedUnion('selection', [
zod_1.z.object({
selection: zod_1.z.literal('disabled'),
value: zod_1.z.object({}).catch({}),
}),
zod_1.z.object({
selection: zod_1.z.literal('system'),
value: zod_1.z
.object({ customFrom: zod_1.z.string().nullable().optional().catch(null) })
.catch({ customFrom: null }),
}),
zod_1.z.object({
selection: zod_1.z.literal('custom'),
value: zod_1.z
.object({ provider: providerShape })
.catch({ provider: providerShape.parse(undefined) }),
}),
])
.catch({ selection: 'disabled', value: {} });
/**
* Convert a stored SmtpSelection to a value suitable for prefilling smtpInputSpec.
*
* The stored type (SmtpSelection from smtpShape) uses flat unions for provider/security
* selection, while the input spec (smtpInputSpec) uses distributed discriminated unions
* (UnionRes). These are structurally incompatible in TypeScript's type system, even
* though the runtime values are identical. This function bridges the two types so that
* service code doesn't need `as any`.
*/
function smtpPrefill(smtp) {
return smtp || undefined;
}
//# sourceMappingURL=inputSpecConstants.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,285 @@
/**
* A record mapping field keys to their {@link ValueSpec} definitions.
* This is the root shape of a dynamic form specification — it defines the complete set
* of configurable fields for a service or action.
*/
export type InputSpec = Record<string, ValueSpec>;
/**
* The discriminator for all supported form field types.
*/
export type ValueType = 'text' | 'textarea' | 'number' | 'color' | 'datetime' | 'toggle' | 'select' | 'multiselect' | 'list' | 'object' | 'file' | 'union' | 'hidden';
/** Union of all concrete form field spec types. Discriminate on the `type` field. */
export type ValueSpec = ValueSpecOf<ValueType>;
/** core spec types. These types provide the metadata for performing validations */
export type ValueSpecOf<T extends ValueType> = T extends "text" ? ValueSpecText : T extends "textarea" ? ValueSpecTextarea : T extends "number" ? ValueSpecNumber : T extends "color" ? ValueSpecColor : T extends "datetime" ? ValueSpecDatetime : T extends "toggle" ? ValueSpecToggle : T extends "select" ? ValueSpecSelect : T extends "multiselect" ? ValueSpecMultiselect : T extends "list" ? ValueSpecList : T extends "object" ? ValueSpecObject : T extends "file" ? ValueSpecFile : T extends "union" ? ValueSpecUnion : T extends "hidden" ? ValueSpecHidden : never;
/** Spec for a single-line text input field. */
export type ValueSpecText = {
/** Display label for the field. */
name: string;
/** Optional help text displayed below the field. */
description: string | null;
/** Optional warning message displayed to the user. */
warning: string | null;
type: 'text';
/** Regex patterns used to validate the input value. */
patterns: Pattern[];
/** Minimum character length, or `null` for no minimum. */
minLength: number | null;
/** Maximum character length, or `null` for no maximum. */
maxLength: number | null;
/** Whether the field should obscure input (e.g. for passwords). */
masked: boolean;
/** HTML input mode hint for mobile keyboards. */
inputmode: 'text' | 'email' | 'tel' | 'url';
/** Placeholder text shown when the field is empty. */
placeholder: string | null;
/** Whether the field must have a value. */
required: boolean;
/** Default value, which may be a literal string or a {@link RandomString} generation spec. */
default: DefaultString | null;
/** `false` if editable, or a string message explaining why the field is disabled. */
disabled: false | string;
/** If set, provides a "generate" button that fills the field with a random string matching this spec. */
generate: null | RandomString;
/** Whether the field value cannot be changed after initial configuration. */
immutable: boolean;
};
/** Spec for a multi-line textarea input field. */
export type ValueSpecTextarea = {
name: string;
description: string | null;
warning: string | null;
type: 'textarea';
/** Regex patterns used to validate the input value. */
patterns: Pattern[];
placeholder: string | null;
minLength: number | null;
maxLength: number | null;
/** Minimum number of visible rows. */
minRows: number;
/** Maximum number of visible rows before scrolling. */
maxRows: number;
required: boolean;
default: string | null;
disabled: false | string;
immutable: boolean;
};
/** Spec for a numeric input field. */
export type ValueSpecNumber = {
type: 'number';
/** Minimum allowed value, or `null` for unbounded. */
min: number | null;
/** Maximum allowed value, or `null` for unbounded. */
max: number | null;
/** Whether only whole numbers are accepted. */
integer: boolean;
/** Step increment for the input spinner, or `null` for any precision. */
step: number | null;
/** Display label for the unit (e.g. `"MB"`, `"seconds"`), shown next to the field. */
units: string | null;
placeholder: string | null;
name: string;
description: string | null;
warning: string | null;
required: boolean;
default: number | null;
disabled: false | string;
immutable: boolean;
};
/** Spec for a browser-native color picker field. */
export type ValueSpecColor = {
name: string;
description: string | null;
warning: string | null;
type: 'color';
required: boolean;
/** Default hex color string (e.g. `"#ff0000"`), or `null`. */
default: string | null;
disabled: false | string;
immutable: boolean;
};
/** Spec for a date, time, or datetime input field. */
export type ValueSpecDatetime = {
name: string;
description: string | null;
warning: string | null;
type: 'datetime';
required: boolean;
/** Controls which kind of picker is displayed. */
inputmode: 'date' | 'time' | 'datetime-local';
/** Minimum selectable date/time as an ISO string, or `null`. */
min: string | null;
/** Maximum selectable date/time as an ISO string, or `null`. */
max: string | null;
default: string | null;
disabled: false | string;
immutable: boolean;
};
/** Spec for a single-select field displayed as radio buttons in a modal. */
export type ValueSpecSelect = {
/** Map of option keys to display labels. */
values: Record<string, string>;
name: string;
description: string | null;
warning: string | null;
type: 'select';
default: string | null;
/** `false` if all enabled, a string disabling the whole field, or an array of disabled option keys. */
disabled: false | string | string[];
immutable: boolean;
};
/** Spec for a multi-select field displayed as checkboxes in a modal. */
export type ValueSpecMultiselect = {
/** Map of option keys to display labels. */
values: Record<string, string>;
name: string;
description: string | null;
warning: string | null;
type: 'multiselect';
/** Minimum number of selections required, or `null`. */
minLength: number | null;
/** Maximum number of selections allowed, or `null`. */
maxLength: number | null;
/** `false` if all enabled, a string disabling the whole field, or an array of disabled option keys. */
disabled: false | string | string[];
/** Array of option keys selected by default. */
default: string[];
immutable: boolean;
};
/** Spec for a boolean toggle (on/off switch). */
export type ValueSpecToggle = {
name: string;
description: string | null;
warning: string | null;
type: 'toggle';
default: boolean | null;
disabled: false | string;
immutable: boolean;
};
/**
* Spec for a discriminated union field — displays a dropdown for variant selection,
* and each variant can have its own nested sub-form.
*/
export type ValueSpecUnion = {
name: string;
description: string | null;
warning: string | null;
type: 'union';
/** Map of variant keys to their display name and nested form spec. */
variants: Record<string, {
/** Display name for this variant in the dropdown. */
name: string;
/** Nested form spec shown when this variant is selected. */
spec: InputSpec;
}>;
/** `false` if all enabled, a string disabling the whole field, or an array of disabled variant keys. */
disabled: false | string | string[];
default: string | null;
immutable: boolean;
};
/** Spec for a file upload input field. */
export type ValueSpecFile = {
name: string;
description: string | null;
warning: string | null;
type: 'file';
/** Allowed file extensions (e.g. `[".pem", ".crt"]`). */
extensions: string[];
required: boolean;
};
/** Spec for a collapsible grouping of nested fields (a "sub-form"). */
export type ValueSpecObject = {
name: string;
description: string | null;
warning: string | null;
type: 'object';
/** The nested form spec containing this object's fields. */
spec: InputSpec;
};
/** Spec for a hidden field — not displayed to the user but included in the form data. */
export type ValueSpecHidden = {
type: 'hidden';
};
/** The two supported list item types. */
export type ListValueSpecType = 'text' | 'object';
/** Maps a {@link ListValueSpecType} to its concrete list item spec. */
export type ListValueSpecOf<T extends ListValueSpecType> = T extends "text" ? ListValueSpecText : T extends "object" ? ListValueSpecObject : never;
/** A list field spec — union of text-list and object-list variants. */
export type ValueSpecList = ValueSpecListOf<ListValueSpecType>;
/**
* Spec for a list field — an interface to add, remove, and edit items in an ordered collection.
* The `spec` field determines whether list items are text strings or structured objects.
*/
export type ValueSpecListOf<T extends ListValueSpecType> = {
name: string;
description: string | null;
warning: string | null;
type: 'list';
/** The item spec — determines whether this is a list of text values or objects. */
spec: ListValueSpecOf<T>;
/** Minimum number of items, or `null` for no minimum. */
minLength: number | null;
/** Maximum number of items, or `null` for no maximum. */
maxLength: number | null;
disabled: false | string;
/** Default list items to populate on creation. */
default: string[] | DefaultString[] | Record<string, unknown>[] | readonly string[] | readonly DefaultString[] | readonly Record<string, unknown>[];
};
/** A regex validation pattern with a human-readable description of what it enforces. */
export type Pattern = {
/** The regex pattern string (without delimiters). */
regex: string;
/** A user-facing explanation shown when validation fails (e.g. `"Must be a valid email"`). */
description: string;
};
/** Spec for text items within a list field. */
export type ListValueSpecText = {
type: 'text';
patterns: Pattern[];
minLength: number | null;
maxLength: number | null;
masked: boolean;
generate: null | RandomString;
inputmode: 'text' | 'email' | 'tel' | 'url';
placeholder: string | null;
};
/** Spec for object items within a list field. */
export type ListValueSpecObject = {
type: 'object';
/** The form spec for each object item. */
spec: InputSpec;
/** Defines how uniqueness is determined among list items. */
uniqueBy: UniqueBy;
/** An expression used to generate the display string for each item in the list summary (e.g. a key path). */
displayAs: string | null;
};
/**
* Describes how list items determine uniqueness.
* - `null`: no uniqueness constraint
* - `string`: unique by a specific field key
* - `{ any: UniqueBy[] }`: unique if any of the sub-constraints match
* - `{ all: UniqueBy[] }`: unique if all sub-constraints match together
*/
export type UniqueBy = null | string | {
any: readonly UniqueBy[] | UniqueBy[];
} | {
all: readonly UniqueBy[] | UniqueBy[];
};
/** A default value that is either a literal string or a {@link RandomString} generation spec. */
export type DefaultString = string | RandomString;
/** Spec for generating a random string — used for default passwords, API keys, etc. */
export type RandomString = {
/** The character set to draw from (e.g. `"a-zA-Z0-9"`). */
charset: string;
/** The length of the generated string. */
len: number;
};
/**
* Type guard that narrows a {@link ValueSpec} to a {@link ValueSpecListOf} of a specific item type.
*
* @param t - The value spec to check
* @param s - The list item type to narrow to (`"text"` or `"object"`)
*/
export declare function isValueSpecListOf<S extends ListValueSpecType>(t: ValueSpec, s: S): t is ValueSpecListOf<S> & {
spec: ListValueSpecOf<S>;
};
@@ -0,0 +1,13 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isValueSpecListOf = isValueSpecListOf;
/**
* Type guard that narrows a {@link ValueSpec} to a {@link ValueSpecListOf} of a specific item type.
*
* @param t - The value spec to check
* @param s - The list item type to narrow to (`"text"` or `"object"`)
*/
function isValueSpecListOf(t, s) {
return 'spec' in t && t.spec.type === s;
}
//# sourceMappingURL=inputSpecTypes.js.map
@@ -0,0 +1 @@
{"version":3,"file":"inputSpecTypes.js","sourceRoot":"","sources":["../../../../../base/lib/actions/input/inputSpecTypes.ts"],"names":[],"mappings":";;AAoVA,8CAKC;AAXD;;;;;GAKG;AACH,SAAgB,iBAAiB,CAC/B,CAAY,EACZ,CAAI;IAEJ,OAAO,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,CAAA;AACzC,CAAC"}