/** * The renderer-facing result types produced by `classifySchema`: the discriminated * `FieldModel`, the `ClassifiedField` envelope, and the annotation/variant shapes * they carry. Kept apart from the classifier logic so both the renderer and the * validator import the vocabulary without pulling in classification code. */ import type { EnumOption } from './resolve'; import type { FormControl, FormInputMode, JsonSchema } from './types'; /** * How a string field accepts binary content as a JSON-serializable string. * Derived from a field's JSON-Schema media annotations (see the `SchemaForm` module). */ export interface MediaUpload { /** * `base64` — the emitted value is the raw base64 body (no `data:` prefix), * matching `contentEncoding: "base64"`. `data-url` — the emitted value is a * full `data:;base64,` URL, matching `format: "data-url"`. */ readonly encoding: 'base64' | 'data-url'; /** * The `contentMediaType` constraint (e.g. `image/*`, `application/pdf`), used * for MIME validation and the file picker's `accept`. `undefined` for a bare * `format: "data-url"` field with no declared type. */ readonly mediaType: string | undefined; /** A per-field byte cap from `contentMaxBytes`, if the schema pins one. */ readonly maxBytes: number | undefined; } /** * The platform date constraints classified off a `date`/`date-time` string property * (scope C). `min`/`max` are inclusive `YYYY-MM-DD` bounds (passed to the native date * control and enforced by the validator); `unavailable` is the set of excluded days * (the native control cannot disable arbitrary days, so this is validator-enforced). * A date RANGE is two ordinary date fields: on the END field, `rangeStart` names its * start field and `minDays`/`maxDays` bound the inclusive span. Absent members leave * that aspect unconstrained. */ export interface DateConstraints { readonly min: string | undefined; readonly max: string | undefined; readonly unavailable: readonly string[]; readonly rangeStart: string | undefined; readonly minDays: number | undefined; readonly maxDays: number | undefined; } /** One top-level key of the expression's input document, with a one-line gloss. */ export interface ExpressionAnnotationKey { readonly name: string; readonly gloss: string; } /** * One named jq variable the expression may read as `$name` beside its `.` input: * its blurb and a representative sample the editor's Test panel binds it to. The * `x-tai42-expression` annotation carries these under `variables`; a field whose * `.` is its only input carries none. */ export interface ExpressionAnnotationVariable { readonly name: string; readonly blurb: string; /** True when the annotation carries a `sample` for this variable — tracked * separately because a legitimate sample may be JSON `null`. */ readonly hasSample: boolean; /** The sample value the Test panel binds `$name` to. Meaningful only when * {@link hasSample} is true. */ readonly sample: unknown; } /** * The `x-tai42-expression` annotation on a string schema: the server declares * that the field's value is authored in a pipeline expression language (only jq * today) and optionally describes what `.` is for that expression. The renderer * uses it to open the visual-editor door instead of a plain text box. The shape * mirrors jq-studio's input-shape descriptor WITHOUT importing it — classification * stays a pure schema concern; the renderer maps this onto a jq field declaration. */ export interface ExpressionAnnotation { /** The declared language. An unknown language never reaches here — it classifies * as a plain string, so an older client renders a newer server's field as the * ordinary text input it always was. */ readonly language: 'jq'; /** Short chip label for what `.` is (e.g. "node envelope"). */ readonly label: string | undefined; /** One sentence describing what `.` is in this field. */ readonly blurb: string | undefined; /** The top-level keys of `.`, each with a one-liner. */ readonly keys: readonly ExpressionAnnotationKey[] | undefined; /** The named variables the expression reads as `$name` beside `.`. Absent when * the annotation declares none (the field's `.` is its only input). */ readonly variables: readonly ExpressionAnnotationVariable[] | undefined; /** What the expression must return (e.g. "true or false"). */ readonly returns: string | undefined; /** Per-field caveats. */ readonly caveats: readonly string[] | undefined; /** True when the annotation carries a `sample` — tracked separately because a * legitimate sample may be JSON `null`. */ readonly hasSample: boolean; /** A static skeleton of `.` for the Test panel. Meaningful only when * {@link hasSample} is true. */ readonly sample: unknown; } /** One selectable variant of a union field. */ export interface UnionVariant { readonly label: string; /** The discriminator tag value, or `undefined` for a non-discriminated union. */ readonly tag: unknown; /** The variant schema as written (unresolved — the renderer resolves it). */ readonly schema: JsonSchema; } /** The renderer-facing classification of a schema node. */ export type FieldModel = | { readonly kind: 'const'; readonly value: unknown } | { readonly kind: 'enum'; readonly options: readonly EnumOption[]; /** The `control` keyword off the property: `'chips'` draws the choice as a * chip grid in place of the radio/select; `undefined` keeps the default. */ readonly control: FormControl | undefined; } | { readonly kind: 'string'; readonly format: string | undefined; /** The input kind (keyboard / native control) this string draws, from the * schema's `inputMode` keyword; `undefined` for a plain text box. It * draws, it does not validate, and it outranks `format` for the control. */ readonly inputMode: FormInputMode | undefined; /** True when the `control` keyword is `'textarea'`: draw a multi-line text * control instead of a single-line box. It draws, it does not validate. */ readonly multiline: boolean; /** The `maxLength`/`minLength` JSON-Schema bounds, drawn on the control (a * native cap plus a live counter when `maxLength` is set) and enforced by * the validator. `undefined` leaves that bound unconstrained. */ readonly maxLength: number | undefined; readonly minLength: number | undefined; /** Present when media annotations opt the field into the upload control. */ readonly media: MediaUpload | undefined; /** Present when a well-formed `x-tai42-expression` annotation opts the * field into the jq expression editor (see {@link ExpressionAnnotation}). */ readonly expression: ExpressionAnnotation | undefined; /** Present on a `date`/`date-time` string carrying any platform date * constraint (bounds, unavailable days, or range pairing — scope C). */ readonly date: DateConstraints | undefined; } | { readonly kind: 'number'; readonly integer: boolean } | { readonly kind: 'boolean' } | { readonly kind: 'array'; readonly items: JsonSchema } | { // An array of a FIXED set of strings (its `items` carry an `enum`, or a per-send // option list set one): a multi-select control (a checkbox group), one checkbox per // option, value a `string[]`. An array of free strings stays the add/remove // `array` field — a multi-select needs a known option set to draw. readonly kind: 'multiselect'; readonly options: readonly EnumOption[]; /** The `control` keyword off the property: `'chips'` draws the multi-select * as a toggle-chip grid in place of the checkbox group; `undefined` keeps * the default. */ readonly control: FormControl | undefined; } | { readonly kind: 'object'; readonly properties: readonly (readonly [string, JsonSchema])[]; readonly required: ReadonlySet; } | { // A string-keyed map typed by `additionalProperties` (no fixed // `properties`), edited as key/value rows. `values` is the entry value // schema AS WRITTEN — the entry editor resolves any `$ref` itself, exactly // as `array.items` is. readonly kind: 'record'; readonly values: JsonSchema; } | { readonly kind: 'union'; readonly variants: readonly UnionVariant[]; readonly discriminator: string | undefined; } | { // A shape with no structured editor to build — a property-less object, a // bare `additionalProperties`-open object, an items-less array, an empty/ // multi-type/`allOf` schema. It is NOT undroppable: every one of these still // describes JSON, so the renderer edits it as free-form JSON in a mono // textarea rather than dead-ending on a badge. `jsonType` is the container // the schema commits to, so a valid buffer must parse to it (`'any'` = no // constraint beyond "is JSON"); `reason` records which shape fell through. readonly kind: 'json'; readonly jsonType: 'object' | 'array' | 'any'; readonly reason: string; }; /** A fully classified field: its model plus resolved metadata. */ export interface ClassifiedField { readonly model: FieldModel; /** True when JSON `null` is an accepted value (a null-union member). */ readonly nullable: boolean; /** The effective (ref-resolved) schema this classification describes. */ readonly schema: JsonSchema; readonly title: string | undefined; readonly description: string | undefined; }