This commit is contained in:
lovebird 2025-02-20 18:14:21 +01:00
parent c157c125b9
commit 734879e8f0
10 changed files with 7 additions and 796 deletions

View File

@ -9,3 +9,4 @@ export * from './constants.js';
export * from './fs/_glob.js';
export * from './component.js';
export * from './logger.js';
export * from './schemas/index.js';

View File

@ -9,4 +9,5 @@ export * from './constants.js';
export * from './fs/_glob.js';
export * from './component.js';
export * from './logger.js';
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYyxTQUFTLENBQUE7QUFDdkIsY0FBYyxhQUFhLENBQUE7QUFDM0IsY0FBYyxTQUFTLENBQUE7QUFDdkIsY0FBYyxZQUFZLENBQUE7QUFDMUIsY0FBYyxnQkFBZ0IsQ0FBQTtBQUM5QixjQUFjLGNBQWMsQ0FBQTtBQUM1QixjQUFjLFlBQVksQ0FBQTtBQUMxQixjQUFjLGdCQUFnQixDQUFBO0FBQzlCLGNBQWMsZUFBZSxDQUFBO0FBQzdCLGNBQWMsZ0JBQWdCLENBQUE7QUFDOUIsY0FBYyxhQUFhLENBQUEifQ==
export * from './schemas/index.js';
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYyxTQUFTLENBQUE7QUFDdkIsY0FBYyxhQUFhLENBQUE7QUFDM0IsY0FBYyxTQUFTLENBQUE7QUFDdkIsY0FBYyxZQUFZLENBQUE7QUFDMUIsY0FBYyxnQkFBZ0IsQ0FBQTtBQUM5QixjQUFjLGNBQWMsQ0FBQTtBQUM1QixjQUFjLFlBQVksQ0FBQTtBQUMxQixjQUFjLGdCQUFnQixDQUFBO0FBQzlCLGNBQWMsZUFBZSxDQUFBO0FBQzdCLGNBQWMsZ0JBQWdCLENBQUE7QUFDOUIsY0FBYyxhQUFhLENBQUE7QUFDM0IsY0FBYyxvQkFBb0IsQ0FBQSJ9

View File

@ -61,8 +61,9 @@
},
"types": "index.d.ts",
"dependencies": {
"@polymech/core": "link:..\\core",
"@polymech/fs": "link:..\\fs",
"@polymech/core": "file:../core",
"@polymech/fs": "file:../fs",
"@repo/typescript-config": "file:../typescript-config",
"@schemastore/package": "^0.0.10",
"env-var": "^7.5.0",
"glob": "^10.4.5",
@ -74,12 +75,10 @@
"tslog": "^3.3.3",
"tsup": "^8.3.5",
"yargs": "^17.7.2",
"zod": "^3.24.1",
"zod-to-json-schema": "^3.24.1",
"zod-to-ts": "^1.2.0"
},
"devDependencies": {
"@repo/typescript-config": "workspace:*",
"@types/node": "^22.12.0",
"typescript": "^5.7.3"
},

View File

@ -9,3 +9,4 @@ export * from './constants.js'
export * from './fs/_glob.js'
export * from './component.js'
export * from './logger.js'
export * from './schemas/index.js'

View File

@ -1,221 +0,0 @@
import * as path from 'path'
import * as CLI from 'yargs'
import { z, ZodTypeAny, ZodObject, ZodEffects, ZodOptional, ZodDefault } from 'zod'
import { sync as writeFS } from '@polymech/fs/write'
import { zodToTs, printNode } from 'zod-to-ts'
import { zodToJsonSchema } from "zod-to-json-schema"
import { logger } from '@/logger.js'
export * from './path.js'
export * from './zod_map.js'
type InnerType<T> = T extends ZodEffects<infer U> ? InnerType<U> : T
type GetInnerType<T extends ZodTypeAny> = T extends ZodObject<any>
? T
: T extends ZodEffects<ZodObject<any>>
? InnerType<T>
: never;
export const generate_interfaces = (schemas: ZodObject<any>[], dst: string) => {
const types = schemas.map(schema => `export interface ${schema.description || 'IOptions'} ${printNode(zodToTs(schema).node)}`)
writeFS(dst, types.join('\n'))
}
export const enumerateHelpStrings = (schema: ZodTypeAny, path: string[] = [], logger: any): void => {
if (schema instanceof ZodObject) {
for (const key in schema.shape) {
const nestedSchema = schema.shape[key];
enumerateHelpStrings(nestedSchema, [...path, key], logger)
}
} else {
const description = schema._def.description;
if (description) {
logger.debug(`\t ${path.join('.')}: ${description}`)
}
}
}
export const yargsDefaults = (yargs: CLI.Argv) => yargs.parserConfiguration({ "camel-case-expansion": false })
export const getInnerSchema = (schema: ZodTypeAny): ZodTypeAny => {
while (schema instanceof ZodEffects) {
schema = schema._def.schema
}
return schema
}
export const getInnerType = (type: ZodTypeAny) => {
while (type instanceof ZodOptional) {
type = type._def.innerType
}
while (type._def.typeName === 'ZodDefault' || type._def.typeName === 'ZodOptional') {
type = type._def.innerType;
}
return type._def.typeName
}
export const getDefaultValue = (schema: ZodTypeAny) => {
if (schema instanceof ZodDefault) {
return schema._def.defaultValue();
}
return undefined;
}
export const getFieldDefaultValue = (schema: ZodTypeAny): any | undefined => {
if(!schema){
return undefined
}
if (schema._def.typeName === 'ZodDefault') {
return schema._def.defaultValue();
}
if (schema instanceof ZodOptional) {
return getFieldDefaultValue(schema.unwrap());
}
if (schema instanceof ZodEffects) {
return getFieldDefaultValue(schema._def.schema);
}
if(typeof schema._def){
return getFieldDefaultValue(schema._def.schema)
}
return undefined;
}
export const getDescription = (schema: ZodTypeAny): string | undefined =>{
if(!schema){
return undefined
}
if (schema._def.description) {
return schema._def.description;
}
if (schema instanceof ZodOptional) {
return getDescription(schema.unwrap());
}
if (schema instanceof ZodEffects) {
return getDescription(schema._def.schema);
}
if(typeof schema._def){
return getDescription(schema._def.schema)
}
return undefined;
}
export const toYargs = (yargs: CLI.Argv, zodSchema: ZodObject<any>, options?: {
onKey?: (yargs: CLI.Argv, key: string, options:any) => any
}) => {
yargsDefaults(yargs)
try {
const shape = zodSchema.shape
for (const key in shape) {
const zodField = shape[key] as ZodTypeAny
const innerDef = getInnerSchema(zodField)
if (!innerDef) {
continue
}
let type: 'string' | 'boolean' | 'number' | undefined;
const inner_type = getInnerType(innerDef)
let descriptionExtra = ''
switch (inner_type) {
case 'ZodString':
type = 'string'
break
case 'ZodBoolean':
type = 'boolean'
break
case 'ZodNumber':
type = 'number'
break
case 'ZodOptional':
case 'ZodEnum':
type = getInnerType(innerDef)
if (innerDef._def.typeName === 'ZodEnum') {
descriptionExtra = `\n\t ${innerDef._def.values.join(' \n\t ')}`
}
break
}
const defaultValue = getFieldDefaultValue(zodField)
let handled = false
const args = {
type,
default: defaultValue,
describe: `${zodField._def.description || ''} ${descriptionExtra}`.trim()
}
if(options?.onKey){
handled = options.onKey(yargs, key, args)
}
if(!handled){
yargs.option(key,args)
}
}
return yargs
} catch (error) {
logger.error('Error processing schema:', error)
return yargs
}
}
/////////////////////////////////////////////////////////
//
// Schema Writers
//
const extension = (file: string) => path.parse(file).ext
const json = (data: any, file: string, name: string, options: {}) => writeFS(file, data.map((s) => zodToJsonSchema(s, name)))
export const WRITERS =
{
'.json': json
}
export const writer = (file: string) => WRITERS[extension(file)]
export const write = (schemas: ZodObject<any>[], file: string, name: string, options: {}) => {
if (!WRITERS[extension(file)]) {
logger.error(`No writer found for file extension: ${extension(file)} : file: ${file}`)
return
}
logger.debug(`Writing schema to ${file} : ${name}`)
try {
writer(file)(schemas, file, name, options)
} catch (e) {
logger.trace(`Error writing schema to ${file} : ${name}`, e, e.stack, e.message)
}
}
////////////////////////////////////////////////////////////////////
//
// Schema Combinators
export const combineValidatorsOr = (validators: z.ZodTypeAny[]) => {
return z.string().refine((value) => {
const errors = [];
const isValid = validators.some((validator) => {
try {
validator.parse(value)
return true;
} catch (err) {
errors.push(err.errors)
return false;
}
});
if (!isValid) {
throw new z.ZodError(errors.flat())
}
return true;
}, 'Invalid value for all provided validators')
}
export const combineValidatorsOrUsingZod = (validators: z.ZodTypeAny[]) => {
return validators.reduce((acc, validator) => acc.or(validator));
};
export const combineValidatorsOrUsingZod2 = (validators: z.ZodTypeAny[]) => {
return validators.reduce((acc, validator) => {
return acc.or(validator).refine((value) => {
try {
acc.parse(value);
return true;
} catch (errAcc) {
try {
validator.parse(value);
return true;
} catch (errValidator) {
throw new z.ZodError([...errAcc.errors, ...errValidator.errors]);
}
}
})
})
}

View File

@ -1,20 +0,0 @@
/*
export const openapi = (data: ZodObject<any>[], file: string, name: string, options: {}) => {
const registry = new OpenAPIRegistry()
data.forEach((s) => registry.register(s.description, s))
const generator = new OpenApiGeneratorV3(registry.definitions)
const component = generator.generateComponents()
// const content = stringifyYAML(component)
return component
}
*/
/*
const yaml = (data: ZodObject<any>[], file: string, name: string, options: {}) => {
const registry = new OpenAPIRegistry()
data.forEach((s) => registry.register(s.description, s))
const generator = new OpenApiGeneratorV3(registry.definitions)
const component = generator.generateComponents()
logger.debug(`Writing schema to ${file} : ${name}`,component)
writeFS(file,stringifyYAML(component))
}
*/

View File

@ -1,253 +0,0 @@
import { z, ZodTypeAny } from 'zod'
import * as path from 'path'
import { accessSync, constants, lstatSync, existsSync } from 'fs'
import { isString } from '@polymech/core/primitives'
import { sync as exists } from '@polymech/fs/exists'
import { sync as read } from '@polymech/fs/read'
import { logger } from '@/logger.js'
import { DEFAULT_VARS, resolve, template } from '@/variables.js'
import { getDescription } from '@/shemas/index.js'
import { isFile } from '@/lib/fs.js'
type TResult = { resolved: string, source: string, value: unknown }
type TRefine = (src: string, ctx: any, variables: Record<string, string>) => string | z.ZodNever
type TTransform = (src: string, variables?: Record<string, string>) => string | TResult
type TExtend = { refine: Array<TRefine>, transform: Array<TTransform> }
const DefaultPathSchemaBase = z.string().describe('Path to a file or directory')
const PathErrorMessages = {
INVALID_INPUT: 'INVALID_INPUT: ${inputPath}',
PATH_DOES_NOT_EXIST: 'Path does not exist ${inputPath} = ${resolvedPath}',
DIRECTORY_NOT_WRITABLE: 'Directory is not writable ${inputPath} = ${resolvedPath}',
NOT_A_DIRECTORY: 'Path is not a directory or does not exist ${inputPath} = ${resolvedPath}',
NOT_A_JSON_FILE: 'File is not a JSON file or does not exist ${inputPath} = ${resolvedPath}',
PATH_NOT_ABSOLUTE: 'Path is not absolute ${inputPath} = ${resolvedPath}',
PATH_NOT_RELATIVE: 'Path is not relative ${inputPath} = ${resolvedPath}',
} as const
export enum E_PATH {
ENSURE_PATH_EXISTS = 1,
INVALID_INPUT,
ENSURE_DIRECTORY_WRITABLE,
ENSURE_FILE_IS_JSON,
ENSURE_PATH_IS_ABSOLUTE,
ENSURE_PATH_IS_RELATIVE,
GET_PATH_INFO
}
export const Transformers:Record<string,any> = {
resolve: (val: string, variables: Record<string, string> = {}) => {
if (!val) {
return null
}
return {
resolved: path.resolve(resolve(val, false, variables)),
source: val
}
},
json: (val: string | { resolved: string, source: string }, variables: Record<string, string> = {}) => {
if (!val) {
return null
}
const resolved = path.resolve(resolve(isString(val) ? val : val.source, false, variables))
return {
resolved,
source: val,
value: read(resolved, 'json')
}
},
string: (val: string | { resolved: string, source: string }, variables: Record<string, string> = {}) => {
if (!val) {
return null
}
let src = isString(val) ? val : val.source
src = resolve(src, false, variables)
const resolved = path.resolve(src)
if (!exists(resolved) || !isFile(resolved)) {
return {
resolved,
source: val,
value: null
}
}
else {
let value = null
try {
value = read(resolved, 'string')
} catch (e) {
logger.error('Failed to read file', { resolved, source: val, error: e.message })
}
return {
resolved,
source: val,
value
}
}
}
}
export const TransformersDescription = [
{
description: 'RESOLVE_PATH',
fn: Transformers.resolve
},
{
description: 'READ_JSON',
fn: Transformers.json
},
{
description: 'READ_STRING',
fn: Transformers.string
}
]
const extendType = (type: ZodTypeAny, extend: TExtend, variables: Record<string, string> = {}) => {
if (Array.isArray(extend.refine)) {
for (const refine of extend.refine) {
type = type.refine(refine as any)
}
} else {
type = type.refine(extend.refine)
}
if (Array.isArray(extend.transform)) {
for (const transform of extend.transform) {
type = type.transform((val) => transform(val, variables))
}
} else {
type = type.transform(extend.transform)
}
return type
}
const extendTypeDescription = (type: ZodTypeAny, extension: TExtend, variables: Record<string, string> = {}) => {
const description = getDescription(type) || ''
let transformerDescriptions = 'Transformers:\n'
if (Array.isArray(extension.transform)) {
for (const transform of extension.transform) {
transformerDescriptions += transformerDescription(transform) + '\n'
}
} else {
transformerDescriptions += transformerDescription(extension.transform) + '\n'
}
type = type.describe(description + '\n' + transformerDescriptions)
return type
}
const transformerDescription = (fn: TTransform) => {
const description = TransformersDescription.find((t) => t.fn === fn)
return description ? description.description : 'Unknown'
}
export const extendSchema = (baseSchema: z.ZodObject<any>, extend: Record<string, any>) => {
const baseShape = baseSchema.shape
const extendedShape: Record<string, ZodTypeAny> = { ...baseShape }
for (const [key, refines] of Object.entries(extend)) {
if (!baseShape[key])
continue
let fieldSchema = baseShape[key]
if (Array.isArray(refines.refine)) {
for (const refine of refines.refine) {
fieldSchema = fieldSchema.superRefine(refine)
}
} else {
fieldSchema = fieldSchema.superRefine(refines)
}
if (Array.isArray(refines.transform)) {
for (const transform of refines.transform) {
fieldSchema = fieldSchema.transform((val) => transform(val))
}
} else {
fieldSchema = fieldSchema.transform(refines.transform)
}
extendedShape[key] = fieldSchema
}
return z.object(extendedShape)
}
export const ENSURE_DIRECTORY_WRITABLE = (inputPath: string, ctx: any, variables: Record<string, string>) => {
const resolvedPath = path.resolve(resolve(inputPath, false, variables))
const parts = path.parse(resolvedPath)
if (resolvedPath && existsSync(parts.dir) && lstatSync(parts.dir).isDirectory()) {
try {
accessSync(resolvedPath, constants.W_OK)
return resolvedPath
} catch (e) {
ctx.addIssue({
code: E_PATH.ENSURE_DIRECTORY_WRITABLE,
message: template(PathErrorMessages.DIRECTORY_NOT_WRITABLE, { inputPath, resolvedPath })
})
return z.NEVER
}
} else {
ctx.addIssue({
code: E_PATH.ENSURE_DIRECTORY_WRITABLE,
message: template(PathErrorMessages.NOT_A_DIRECTORY, { inputPath, resolvedPath })
})
return z.NEVER
}
}
export const IS_VALID_STRING = (inputPath: string) => isString(inputPath)
export const ENSURE_PATH_EXISTS = (inputPath: string, ctx: any, variables: Record<string, string>) => {
if (!inputPath || !ctx) {
return z.NEVER
}
if (!isString(inputPath)) {
ctx.addIssue({
code: E_PATH.INVALID_INPUT,
message: template(PathErrorMessages.INVALID_INPUT, {})
})
return z.NEVER
}
const resolvedPath = path.resolve(resolve(inputPath, false, variables))
if (!exists(resolvedPath)) {
ctx.addIssue({
code: E_PATH.ENSURE_PATH_EXISTS,
message: template(PathErrorMessages.PATH_DOES_NOT_EXIST, { inputPath, resolvedPath })
})
return z.NEVER
}
return resolvedPath
}
export const test = () => {
const BaseCompilerOptions = () => z.object({
root: DefaultPathSchemaBase.default(`${process.cwd()}`)
})
const ret = extendSchema(BaseCompilerOptions(), {
root: {
refine: [
(val, ctx) => ENSURE_DIRECTORY_WRITABLE(val, ctx, DEFAULT_VARS({ exampleVar: 'exampleValue' })),
(val, ctx) => ENSURE_PATH_EXISTS(val, ctx, DEFAULT_VARS({ exampleVar: 'exampleValue' }))
],
transform: [
(val) => path.resolve(resolve(val, false, DEFAULT_VARS({ exampleVar: 'exampleValue' })))
]
}
})
return ret
}
export const Templates:Record<string,any> =
{
json: {
refine: [IS_VALID_STRING, ENSURE_PATH_EXISTS],
transform: [Transformers.resolve, Transformers.json]
},
string: {
refine: [ENSURE_PATH_EXISTS],
transform: [Transformers.resolve, Transformers.string]
}
}
export const extend = (baseSchema: ZodTypeAny, template: any, variables: Record<string, string> = {}) => {
const type = extendType(baseSchema, template, variables)
return extendTypeDescription(type, template, variables)
}

View File

@ -1,187 +0,0 @@
export enum FLAG {
/**
* Instruct for no additional extra processing
* @constant
* @type int
*/
NONE = 0x00000000,
/**
* Will instruct the pre/post processor to base-64 decode or encode
* @constant
* @type int
*/
BASE_64 = 0x00000001,
/**
* Post/Pre process the value with a user function
* @constant
* @type int
*/
USE_FUNCTION = 0x00000002,
/**
* Replace variables with local scope's variables during the post/pre process
* @constant
* @type int
*/
REPLACE_VARIABLES = 0x00000004,
/**
* Replace variables with local scope's variables during the post/pre process but evaluate the whole string
* as Javascript
* @constant
* @type int
*/
REPLACE_VARIABLES_EVALUATED = 0x00000008,
/**
* Will instruct the pre/post processor to escpape evaluated or replaced variables or expressions
* @constant
* @type int
*/
ESCAPE = 0x00000010,
/**
* Will instruct the pre/post processor to replace block calls with oridinary vanilla script
* @constant
* @type int
*/
REPLACE_BLOCK_CALLS = 0x00000020,
/**
* Will instruct the pre/post processor to remove variable delimitters/placeholders from the final string
* @constant
* @type int
*/
REMOVE_DELIMTTERS = 0x00000040,
/**
* Will instruct the pre/post processor to remove "[" ,"]" , "(" , ")" , "{", "}" , "*" , "+" , "."
* @constant
* @type int
*/
ESCAPE_SPECIAL_CHARS = 0x00000080,
/**
* Will instruct the pre/post processor to use regular expressions over string substitution
* @constant
* @type int
*/
USE_REGEX = 0x00000100,
/**
* Will instruct the pre/post processor to use Filtrex (custom bison parser, needs xexpression) over string substitution
* @constant
* @type int
*/
USE_FILTREX = 0x00000200,
/**
* Cascade entry. There are cases where #USE_FUNCTION is not enough or we'd like to avoid further type checking.
* @constant
* @type int
*/
CASCADE = 0x00000400,
/**
* Cascade entry. There are cases where #USE_FUNCTION is not enough or we'd like to avoid further type checking.
* @constant
* @type int
*/
EXPRESSION = 0x00000800,
/**
* Dont parse anything
* @constant
* @type int
*/
DONT_PARSE = 0x000001000,
/**
* Convert to hex
* @constant
* @type int
*/
TO_HEX = 0x000002000,
/**
* Convert to hex
* @constant
* @type int
*/
REPLACE_HEX = 0x000004000,
/**
* Wait for finish
* @constant
* @type int
*/
WAIT = 0x000008000,
/**
* Wait for finish
* @constant
* @type int
*/
DONT_ESCAPE = 0x000010000,
/**
* Flag to mark the maximum core bit mask, after here its user land
* @constant
* @type int
*/
END = 0x000020000
}
export enum EType
{
Number = 'Number',
String = 'String',
Boolean = 'Boolean',
Date = 'Date',
TimeStamp = 'TimeStamp',
Duration = 'Duration',
Url = 'Url',
UrlScheme = 'Url-Scheme',
Asset = 'Asset',
Symbol = 'Symbol',
Value = 'Value',
Values = 'Values',
Attribute = 'Attribute',
Parameter = 'Parameter',
Operation = 'Operation',
ParameterOperation = 'ParameterOperation',
Template = 'Template',
Arguments = 'Arguments'
}
export type TVector2D = [number, number];
export type TVector3D = [number, number, number];
export type TBBox = [TVector3D, TVector3D];
export type TQuaternion = [number, number, number, number];
export type TFlags = Record<string, bigint>;
export type TExpression = string | [string | RegExp, { [key: string]: any }];
export type TOptions = { flags?: TFlags | { [key: string]: any } };
export interface IUrlScheme {
url: string;
options?: { [key: string]: any };
}
export interface IAsset {
urlScheme: IUrlScheme;
options?: { [key: string]: any };
}
export type TSelector = TExpression | [TExpression, { [key: string]: any }];
export interface ITypeInfo {
type: string;
symbol: bigint;
}
export interface IRef {
key: string | string;
struct: { [key: string]: any };
}
export interface IAttribute {
type: ITypeInfo;
value: bigint;
}
export interface IParameter {
type: ITypeInfo;
value: bigint;
}
export interface IParameterOperation {
param1: bigint;
param2: bigint;
operation: bigint;
}
export type TTemplate = string | [ITypeInfo | TSelector, { [key: string]: any }];
export type TArguments = { [key: string]: any } | any[];

View File

@ -1 +0,0 @@
//import { zodToJsonSchema } from "zod-to-json-schema"

View File

@ -1,109 +0,0 @@
import { z, ZodObject, ZodTypeAny } from 'zod';
/**
* Manages a collection of Zod schema properties
* and combines them into a single Zod object schema.
*
* @template MetaType The type of metadata you want to store for each field.
* Defaults to Record<string, unknown> if not provided.
*/
export class ZodMetaMap<MetaType = Record<string, unknown>> {
private fieldMap = new Map<
string,
{ schema: ZodTypeAny; metadata?: MetaType }
>();
/**
* Adds a Zod schema under a specific key (property name),
* optionally attaching typed metadata.
*
* @param key - The name of the property in the root object.
* @param schema - The Zod schema for that property.
* @param metadata - Optional metadata object (type MetaType).
*/
add<T extends ZodTypeAny>(key: string, schema: T, metadata?: MetaType): this {
this.fieldMap.set(key, { schema, metadata });
return this;
}
/**
* Builds and returns a root Zod object
* that combines all properties which were added.
*/
root(): ZodObject<Record<string, ZodTypeAny>> {
const shape: Record<string, ZodTypeAny> = {};
for (const [key, { schema }] of this.fieldMap.entries()) {
shape[key] = schema;
}
return z.object(shape);
}
/**
* Retrieves the metadata for a specific key, if any.
*/
getMetadata(key: string): MetaType | undefined {
return this.fieldMap.get(key)?.metadata;
}
/**
* Static factory method: creates a SchemaMetaManager
* while letting you optionally specify the MetaType.
*
* Usage:
* const manager = SchemaMetaManager.create<MyFieldMeta>();
*/
static create<MT = Record<string, unknown>>(): ZodMetaMap<MT> {
return new ZodMetaMap<MT>();
}
/**
* Returns a basic UiSchema object that RJSF can use to render form controls.
*
* - Adds a top-level "ui:submitButtonOptions" (example).
* - For each field, we set `ui:title` (uppercase key),
* `ui:description` (from Zod's .describe() if available),
* and a naive placeholder from the default value (if parse(undefined) succeeds).
*/
getUISchema(): Record<string, unknown> {
// Start with some top-level UI schema config (optional)
const uiSchema: Record<string, unknown> = {
'ui:submitButtonOptions': {
props: {
disabled: false,
className: 'btn btn-info',
},
norender: false,
submitText: 'Submit',
},
};
for (const [key, { schema }] of this.fieldMap.entries()) {
let fieldUi: Record<string, unknown> = { };
// Use the Zod description if available
// (Accessing `._def.description` is private/hacky, but commonly done.)
const sAny = schema as any;
if (sAny?._def?.description) {
fieldUi['ui:description'] = sAny._def.description;
}
// RJSF usually reads 'title' from JSON schema. But if you want
// to override it in UI schema, you can do so:
fieldUi['ui:title'] = key[0].toUpperCase() + key.substr(1).toLowerCase()
// If the Zod schema allows a default, we can parse(undefined) to get it.
try {
const defaultVal = schema.parse(undefined);
// There's no official 'ui:default' in RJSF, but you could do a placeholder:
fieldUi['ui:placeholder'] = defaultVal;
} catch {
// no default
}
fieldUi = {
...fieldUi,
...this.getMetadata(key),
}
uiSchema[key] = fieldUi;
}
return uiSchema;
}
}