Fix StartOS 0.4 TypeScript packaging to match SDK API
This commit is contained in:
+5
@@ -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 };
|
||||
+12
@@ -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
|
||||
+1
@@ -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"}
|
||||
+222
@@ -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 {};
|
||||
+319
@@ -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
|
||||
Generated
Vendored
+1
File diff suppressed because one or more lines are too long
Generated
Vendored
+173
@@ -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>;
|
||||
Generated
Vendored
+16
@@ -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
|
||||
Generated
Vendored
+1
@@ -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"}
|
||||
+103
@@ -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>;
|
||||
}
|
||||
+121
@@ -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
|
||||
+1
@@ -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"}
|
||||
+791
@@ -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>;
|
||||
}
|
||||
+764
@@ -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
|
||||
+1
File diff suppressed because one or more lines are too long
+111
@@ -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>;
|
||||
}
|
||||
+110
@@ -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
|
||||
Generated
Vendored
+1
@@ -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"}
|
||||
Reference in New Issue
Block a user