{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "protoform-core", "title": "Protoform core", "description": "Framework-free field model, Standard Schema integration, and form-library validation adapters.", "dependencies": [ "@standard-schema/spec" ], "registryDependencies": [ "@protoform/protoform-license" ], "files": [ { "path": "registry/base-nova/protoform/lib/core/field-model.ts", "content": "/**\n * Schema-agnostic field model: the IR every schema provider produces and\n * the AutoForm engine consumes.\n *\n * Providers (protobuf today; Zod/Valibot/ArkType later) parse their native\n * schema into `ParsedSchema` and populate `FieldRenderHints` from whatever\n * metadata their ecosystem carries (proto annotations, schema descriptions,\n * library-specific registries). The rendering engine reads ONLY this model,\n * never provider-native handles. Provider-native data (for example proto\n * descriptors and validation rules) stays in `FieldConfig.customData`,\n * which only provider-specific code may interpret.\n */\n\n/** A conditional UI rule evaluated against current form values (CEL today). */\nexport interface UiRule {\n expression?: string;\n id?: string;\n message?: string;\n}\n\nexport interface OptionGroup {\n label?: string;\n options: [value: string, label: string][];\n}\n\n/** Values a field label or description may hold in the schema layer. The React layer widens this with ReactNode. */\nexport type Renderable = string | number | boolean | null | undefined;\n\n/** HTML input attributes forwarded to the rendered control. */\nexport interface InputProps {\n [attribute: string]: string | number | boolean | undefined;\n}\n\n/**\n * Provider-private data attached to a field. Only code from the provider\n * that produced the schema may interpret the remaining properties; the\n * rendering engine must not reach into this.\n */\nexport interface ProviderCustomData {\n /** Discriminator naming the provider that produced this field (for example \"proto\"). */\n source?: string;\n [key: string]: unknown;\n}\n\n/** The value bag a form works over: field names to arbitrary user input. */\nexport interface FormValues {\n [field: string]: unknown;\n}\n\n/** How protobuf conversion treats empty entries in a repeated string field. */\nexport type EmptyRepeatedStringPolicy = \"discard\" | \"preserve\";\n\n/**\n * Render-driving metadata, independent of any schema system.\n * Everything here answers \"how should this field look and behave\",\n * never \"how is this field validated\" (validation flows through\n * Standard Schema).\n */\nexport interface FieldRenderHints {\n /** Explicit simple/advanced classification override. */\n advanced?: boolean;\n /** Restrict JSON/field-mask style inputs to these paths. */\n allowedPaths?: string[];\n /** Control-type override; a key into the consumer's control registry. */\n control?: string;\n /** Named data source id for dropdown-style controls. */\n dataProvider?: string;\n /** The schema marks this field as deprecated. */\n deprecated?: boolean;\n /** Concise one-liner shown below the input. */\n description?: string;\n disabledWhen?: UiRule[];\n docsUrl?: string;\n /** Enable file drag-and-drop into the field value. */\n dropzone?: boolean;\n /** Keep blank repeated-string rows instead of discarding them during conversion. */\n emptyRepeatedStringPolicy?: EmptyRepeatedStringPolicy;\n example?: string;\n /** Detailed help text (tooltip). */\n help?: string;\n /** HTML input `type` hint (for example `email`, `url`, `number`). */\n inputType?: string;\n /** JSON-ish payload rendering mode for structured values. */\n jsonKind?: \"struct\" | \"value\" | \"listValue\" | \"any\";\n maxItems?: number;\n maxPairs?: number;\n minItems?: number;\n minPairs?: number;\n optionGroups?: OptionGroup[];\n optionLabels?: Record;\n placeholder?: string;\n secretScope?: string;\n sensitive?: boolean;\n /** Stepper step id this field belongs to. */\n step?: string;\n /** Label used in review/summary contexts instead of the field label. */\n summaryLabel?: string;\n /** Tri-state controls: the unset state is meaningful and selectable. */\n supportsUnset?: boolean;\n visibleWhen?: UiRule[];\n}\n\nexport interface FieldConfig<\n FieldTypes = string,\n CustomData extends ProviderCustomData = ProviderCustomData,\n> {\n customData?: CustomData;\n description?: Renderable;\n /** Keep blank repeated-string rows instead of discarding them during conversion. */\n emptyRepeatedStringPolicy?: EmptyRepeatedStringPolicy;\n fieldType?: FieldTypes;\n inputProps?: InputProps;\n label?: Renderable;\n order?: number;\n}\n\nexport interface ParsedField {\n default?: unknown;\n description?: Renderable;\n fieldConfig?: FieldConfig;\n hints?: FieldRenderHints;\n key: string;\n options?: [value: string, label: string][];\n required: boolean;\n schema?: ParsedField[];\n type: string;\n}\n\nexport interface ParsedSchema {\n fields: ParsedField[];\n}\n\nexport interface SchemaValidationError {\n message: string;\n path: (string | number)[];\n}\n\nexport type SchemaValidation =\n | { success: true; data: unknown }\n | { success: false; errors: SchemaValidationError[] };\n\nexport interface SchemaValidationContext {\n /** Aborted when a newer validation supersedes this run or its form unmounts. */\n signal: AbortSignal;\n}\n\nexport interface SchemaProvider {\n getDefaultValues: () => FormValues;\n parseSchema: () => ParsedSchema;\n validateSchema: (\n values: Values,\n context?: SchemaValidationContext\n ) => SchemaValidation | Promise;\n}\n\n/** Read a field's render hints; single accessor so call sites never reach into provider customData. */\nexport function getFieldHints(\n field: ParsedField\n): FieldRenderHints | undefined {\n return field.hints;\n}\n", "type": "registry:lib" }, { "path": "registry/base-nova/protoform/lib/core/form-library-adapters.ts", "content": "import type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\nexport type FormValidationErrorValue =\n | FormValidationErrors\n | FormValidationErrorValue[]\n | string\n | undefined;\n\n/** Nested error shape consumed by Formik and Final Form. */\nexport interface FormValidationErrors {\n [key: string]: FormValidationErrorValue;\n [key: number]: FormValidationErrorValue;\n [key: symbol]: FormValidationErrorValue;\n}\n\n/** Configure Standard Schema validation and form-library error mapping. */\nexport interface FormValidatorOptions extends StandardSchemaV1.Options {\n /** Override the library-specific key used for issues without a field path. */\n rootErrorKey?: PropertyKey;\n}\n\nexport type FormValidator = (\n values: Input\n) => FormValidationErrors | Promise;\n\ntype ErrorContainer = FormValidationErrors | FormValidationErrorValue[];\n\nfunction isPromiseLike(value: unknown): value is PromiseLike {\n return (\n (typeof value === \"object\" || typeof value === \"function\") &&\n value !== null &&\n typeof Reflect.get(value, \"then\") === \"function\"\n );\n}\n\nfunction isErrorContainer(value: unknown): value is ErrorContainer {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction readOwn(container: ErrorContainer, key: PropertyKey): unknown {\n return Object.hasOwn(container, key)\n ? Reflect.get(container, key)\n : undefined;\n}\n\nfunction writeOwn(\n container: ErrorContainer,\n key: PropertyKey,\n value: FormValidationErrorValue\n) {\n Object.defineProperty(container, key, {\n configurable: true,\n enumerable: true,\n value,\n writable: true,\n });\n}\n\nfunction appendMessage(\n container: ErrorContainer,\n key: PropertyKey,\n message: string\n) {\n const current = readOwn(container, key);\n if (typeof current === \"string\") {\n const messages = current.split(\"\\n\");\n writeOwn(\n container,\n key,\n messages.includes(message) ? current : `${current}\\n${message}`\n );\n return;\n }\n if (isErrorContainer(current)) {\n appendMessage(current, \"_error\", message);\n return;\n }\n writeOwn(container, key, message);\n}\n\nfunction getPathKey(\n segment: PropertyKey | StandardSchemaV1.PathSegment\n): PropertyKey {\n return typeof segment === \"object\" ? segment.key : segment;\n}\n\nfunction createContainer(nextKey: PropertyKey): ErrorContainer {\n return typeof nextKey === \"number\" ? [] : {};\n}\n\nfunction addIssue(\n errors: FormValidationErrors,\n issue: StandardSchemaV1.Issue,\n rootErrorKey: PropertyKey\n) {\n const path = issue.path?.map(getPathKey) ?? [];\n if (path.length === 0) {\n appendMessage(errors, rootErrorKey, issue.message);\n return;\n }\n\n let container: ErrorContainer = errors;\n for (let index = 0; index < path.length; index += 1) {\n const key = path[index];\n if (key === undefined) {\n return;\n }\n if (index === path.length - 1) {\n appendMessage(container, key, issue.message);\n return;\n }\n\n const nextKey = path[index + 1];\n if (nextKey === undefined) {\n return;\n }\n const current = readOwn(container, key);\n if (isErrorContainer(current)) {\n container = current;\n continue;\n }\n\n const next = createContainer(nextKey);\n if (typeof current === \"string\") {\n appendMessage(next, \"_error\", current);\n }\n writeOwn(container, key, next);\n container = next;\n }\n}\n\n/** Convert every Standard Schema issue into a nested, prototype-safe form error tree. */\nexport function standardSchemaIssuesToFormErrors(\n issues: readonly StandardSchemaV1.Issue[],\n options?: FormValidatorOptions\n): FormValidationErrors {\n const errors: FormValidationErrors = {};\n const rootErrorKey = options?.rootErrorKey ?? \"_error\";\n for (const issue of issues) {\n addIssue(errors, issue, rootErrorKey);\n }\n return errors;\n}\n\nfunction resultToErrors(\n result: StandardSchemaV1.Result,\n options: FormValidatorOptions | undefined\n): FormValidationErrors {\n return result.issues\n ? standardSchemaIssuesToFormErrors(result.issues, options)\n : {};\n}\n\nfunction getStandardSchemaOptions(\n options: FormValidatorOptions | undefined\n): StandardSchemaV1.Options | undefined {\n return options?.libraryOptions === undefined\n ? undefined\n : { libraryOptions: options.libraryOptions };\n}\n\nfunction createValidator(\n schema: StandardSchemaV1,\n options: FormValidatorOptions | undefined\n): FormValidator {\n return (values) => {\n const result = schema[\"~standard\"].validate(\n values,\n getStandardSchemaOptions(options)\n );\n return isPromiseLike>(result)\n ? Promise.resolve(result).then((resolved) =>\n resultToErrors(resolved, options)\n )\n : resultToErrors(result, options);\n };\n}\n\n/** Create Formik form-level validation with `_form` as the default root error key. */\nexport function createFormikValidator(\n schema: StandardSchemaV1,\n options?: FormValidatorOptions\n): FormValidator {\n return createValidator(schema, {\n ...options,\n rootErrorKey: options?.rootErrorKey ?? \"_form\",\n });\n}\n\n/** Create Final Form whole-record validation. Pass Final Form's `FORM_ERROR` as `rootErrorKey`. */\nexport function createFinalFormValidator(\n schema: StandardSchemaV1,\n options?: FormValidatorOptions\n): FormValidator {\n return createValidator(schema, options);\n}\n", "type": "registry:lib" }, { "path": "registry/base-nova/protoform/lib/core/index.ts", "content": "export {\n type FieldConfig,\n type EmptyRepeatedStringPolicy,\n type FieldRenderHints,\n type FormValues,\n getFieldHints,\n type InputProps,\n type OptionGroup,\n type ParsedField,\n type ParsedSchema,\n type ProviderCustomData,\n type Renderable,\n type SchemaProvider,\n type SchemaValidation,\n type SchemaValidationContext,\n type SchemaValidationError,\n type UiRule,\n} from \"./field-model.js\";\nexport {\n createFinalFormValidator,\n createFormikValidator,\n type FormValidationErrors,\n type FormValidator,\n type FormValidatorOptions,\n standardSchemaIssuesToFormErrors,\n} from \"./form-library-adapters.js\";\nexport { isStandardSchema, type StandardSchemaV1 } from \"./standard-schema.js\";\n", "type": "registry:lib" }, { "path": "registry/base-nova/protoform/lib/core/standard-schema.ts", "content": "import type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\nexport type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\n/**\n * Narrow an unknown value to a Standard Schema v1 implementation.\n *\n * This is the seam every validation source in protoform flows through:\n * the protobuf provider exposes protovalidate+CEL as a Standard Schema,\n * and future providers (Zod, Valibot, ArkType) already conform natively.\n */\nexport function isStandardSchema(value: unknown): value is StandardSchemaV1 {\n if (\n (typeof value !== \"object\" && typeof value !== \"function\") ||\n value === null\n ) {\n return false;\n }\n const marker = (value as { \"~standard\"?: unknown })[\"~standard\"];\n if (typeof marker !== \"object\" || marker === null) {\n return false;\n }\n const props = marker as Partial;\n return (\n props.version === 1 &&\n typeof props.vendor === \"string\" &&\n typeof props.validate === \"function\"\n );\n}\n", "type": "registry:lib" } ], "type": "registry:lib" }