@tai42/studio-sdk 15.0.0 → 16.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/explorer-body.d.ts +49 -0
- package/dist/components/explorer-body.d.ts.map +1 -0
- package/dist/components/explorer-body.js +67 -0
- package/dist/components/explorer-body.js.map +1 -0
- package/dist/components/explorer-page-size.d.ts +4 -0
- package/dist/components/explorer-page-size.d.ts.map +1 -0
- package/dist/components/explorer-page-size.js +36 -0
- package/dist/components/explorer-page-size.js.map +1 -0
- package/dist/components/explorer-tag-filter.d.ts +17 -0
- package/dist/components/explorer-tag-filter.d.ts.map +1 -0
- package/dist/components/explorer-tag-filter.js +29 -0
- package/dist/components/explorer-tag-filter.js.map +1 -0
- package/dist/components/explorer-tags.d.ts +22 -0
- package/dist/components/explorer-tags.d.ts.map +1 -0
- package/dist/components/explorer-tags.js +51 -0
- package/dist/components/explorer-tags.js.map +1 -0
- package/dist/components/explorer-view.d.ts +3 -17
- package/dist/components/explorer-view.d.ts.map +1 -1
- package/dist/components/explorer-view.js +53 -170
- package/dist/components/explorer-view.js.map +1 -1
- package/dist/components/extension-combo-builder.d.ts +0 -5
- package/dist/components/extension-combo-builder.d.ts.map +1 -1
- package/dist/components/extension-combo-builder.js +4 -42
- package/dist/components/extension-combo-builder.js.map +1 -1
- package/dist/components/extension-combo-parts.d.ts +44 -0
- package/dist/components/extension-combo-parts.d.ts.map +1 -0
- package/dist/components/extension-combo-parts.js +57 -0
- package/dist/components/extension-combo-parts.js.map +1 -0
- package/dist/components/fleet-report.d.ts.map +1 -1
- package/dist/components/fleet-report.js +51 -37
- package/dist/components/fleet-report.js.map +1 -1
- package/dist/components/icons/chrome-marks.d.ts +50 -0
- package/dist/components/icons/chrome-marks.d.ts.map +1 -0
- package/dist/components/icons/chrome-marks.js +51 -0
- package/dist/components/icons/chrome-marks.js.map +1 -0
- package/dist/components/icons/icon-frame.d.ts +29 -0
- package/dist/components/icons/icon-frame.d.ts.map +1 -0
- package/dist/components/icons/icon-frame.js +19 -0
- package/dist/components/icons/icon-frame.js.map +1 -0
- package/dist/components/icons/index.d.ts +40 -0
- package/dist/components/icons/index.d.ts.map +1 -0
- package/dist/components/icons/index.js +6 -0
- package/dist/components/icons/index.js.map +1 -0
- package/dist/components/icons/nav-map.d.ts +14 -0
- package/dist/components/icons/nav-map.d.ts.map +1 -0
- package/dist/components/icons/nav-map.js +28 -0
- package/dist/components/icons/nav-map.js.map +1 -0
- package/dist/components/icons/nav-marks.d.ts +44 -0
- package/dist/components/icons/nav-marks.d.ts.map +1 -0
- package/dist/components/icons/nav-marks.js +45 -0
- package/dist/components/icons/nav-marks.js.map +1 -0
- package/dist/components/icons/status-marks.d.ts +21 -0
- package/dist/components/icons/status-marks.d.ts.map +1 -0
- package/dist/components/icons/status-marks.js +22 -0
- package/dist/components/icons/status-marks.js.map +1 -0
- package/dist/components/icons/theme-marks.d.ts +9 -0
- package/dist/components/icons/theme-marks.d.ts.map +1 -0
- package/dist/components/icons/theme-marks.js +10 -0
- package/dist/components/icons/theme-marks.js.map +1 -0
- package/dist/components/json-diff.d.ts.map +1 -1
- package/dist/components/json-diff.js +1 -3
- package/dist/components/json-diff.js.map +1 -1
- package/dist/components/json-tree-context.d.ts +13 -0
- package/dist/components/json-tree-context.d.ts.map +1 -0
- package/dist/components/json-tree-context.js +15 -0
- package/dist/components/json-tree-context.js.map +1 -0
- package/dist/components/json-tree-model.d.ts +56 -0
- package/dist/components/json-tree-model.d.ts.map +1 -0
- package/dist/components/json-tree-model.js +125 -0
- package/dist/components/json-tree-model.js.map +1 -0
- package/dist/components/json-tree-nodes.d.ts +30 -0
- package/dist/components/json-tree-nodes.d.ts.map +1 -0
- package/dist/components/json-tree-nodes.js +118 -0
- package/dist/components/json-tree-nodes.js.map +1 -0
- package/dist/components/json-tree.d.ts.map +1 -1
- package/dist/components/json-tree.js +7 -295
- package/dist/components/json-tree.js.map +1 -1
- package/dist/components/json-value-format.d.ts +20 -0
- package/dist/components/json-value-format.d.ts.map +1 -0
- package/dist/components/json-value-format.js +63 -0
- package/dist/components/json-value-format.js.map +1 -0
- package/dist/components/markdown-blocks.d.ts +38 -0
- package/dist/components/markdown-blocks.d.ts.map +1 -0
- package/dist/components/markdown-blocks.js +168 -0
- package/dist/components/markdown-blocks.js.map +1 -0
- package/dist/components/markdown-inline.d.ts +24 -0
- package/dist/components/markdown-inline.d.ts.map +1 -0
- package/dist/components/markdown-inline.js +257 -0
- package/dist/components/markdown-inline.js.map +1 -0
- package/dist/components/markdown.d.ts +3 -52
- package/dist/components/markdown.d.ts.map +1 -1
- package/dist/components/markdown.js +4 -376
- package/dist/components/markdown.js.map +1 -1
- package/dist/components/observer-registry.d.ts +38 -0
- package/dist/components/observer-registry.d.ts.map +1 -0
- package/dist/components/observer-registry.js +131 -0
- package/dist/components/observer-registry.js.map +1 -0
- package/dist/components/overflow-measure.d.ts +38 -0
- package/dist/components/overflow-measure.d.ts.map +1 -0
- package/dist/components/overflow-measure.js +54 -0
- package/dist/components/overflow-measure.js.map +1 -0
- package/dist/components/prose-regions.d.ts +81 -0
- package/dist/components/prose-regions.d.ts.map +1 -0
- package/dist/components/prose-regions.js +128 -0
- package/dist/components/prose-regions.js.map +1 -0
- package/dist/components/scroll-region.d.ts.map +1 -1
- package/dist/components/scroll-region.js +3 -298
- package/dist/components/scroll-region.js.map +1 -1
- package/dist/components/tab-stop-parse.d.ts +53 -0
- package/dist/components/tab-stop-parse.d.ts.map +1 -0
- package/dist/components/tab-stop-parse.js +202 -0
- package/dist/components/tab-stop-parse.js.map +1 -0
- package/dist/components/templated-text-field.d.ts.map +1 -1
- package/dist/components/templated-text-field.js +10 -148
- package/dist/components/templated-text-field.js.map +1 -1
- package/dist/components/templated-text-kwargs-editor.d.ts +24 -0
- package/dist/components/templated-text-kwargs-editor.d.ts.map +1 -0
- package/dist/components/templated-text-kwargs-editor.js +48 -0
- package/dist/components/templated-text-kwargs-editor.js.map +1 -0
- package/dist/components/templated-text-kwargs.d.ts +37 -0
- package/dist/components/templated-text-kwargs.d.ts.map +1 -0
- package/dist/components/templated-text-kwargs.js +66 -0
- package/dist/components/templated-text-kwargs.js.map +1 -0
- package/dist/components/templated-text-source.d.ts +33 -0
- package/dist/components/templated-text-source.d.ts.map +1 -0
- package/dist/components/templated-text-source.js +53 -0
- package/dist/components/templated-text-source.js.map +1 -0
- package/dist/components/tool-picker-parts.d.ts +39 -0
- package/dist/components/tool-picker-parts.d.ts.map +1 -0
- package/dist/components/tool-picker-parts.js +87 -0
- package/dist/components/tool-picker-parts.js.map +1 -0
- package/dist/components/tool-picker.d.ts +1 -2
- package/dist/components/tool-picker.d.ts.map +1 -1
- package/dist/components/tool-picker.js +4 -66
- package/dist/components/tool-picker.js.map +1 -1
- package/dist/components/version-history-panel.d.ts.map +1 -1
- package/dist/components/version-history-panel.js +7 -34
- package/dist/components/version-history-panel.js.map +1 -1
- package/dist/components/version-history-rows.d.ts +48 -0
- package/dist/components/version-history-rows.d.ts.map +1 -0
- package/dist/components/version-history-rows.js +51 -0
- package/dist/components/version-history-rows.js.map +1 -0
- package/dist/guards.d.ts +6 -0
- package/dist/guards.d.ts.map +1 -0
- package/dist/guards.js +8 -0
- package/dist/guards.js.map +1 -0
- package/dist/hooks/interactions-connection.d.ts +43 -0
- package/dist/hooks/interactions-connection.d.ts.map +1 -0
- package/dist/hooks/interactions-connection.js +171 -0
- package/dist/hooks/interactions-connection.js.map +1 -0
- package/dist/hooks/interactions-stream.d.ts +80 -0
- package/dist/hooks/interactions-stream.d.ts.map +1 -0
- package/dist/hooks/interactions-stream.js +233 -0
- package/dist/hooks/interactions-stream.js.map +1 -0
- package/dist/hooks/useApi.d.ts +2409 -2293
- package/dist/hooks/useApi.d.ts.map +1 -1
- package/dist/hooks/useSse.d.ts +3 -6
- package/dist/hooks/useSse.d.ts.map +1 -1
- package/dist/hooks/useSse.js +38 -451
- package/dist/hooks/useSse.js.map +1 -1
- package/dist/navigation/app-link.d.ts +25 -0
- package/dist/navigation/app-link.d.ts.map +1 -0
- package/dist/navigation/app-link.js +33 -0
- package/dist/navigation/app-link.js.map +1 -0
- package/dist/navigation/context.d.ts +3 -20
- package/dist/navigation/context.d.ts.map +1 -1
- package/dist/navigation/context.js +7 -267
- package/dist/navigation/context.js.map +1 -1
- package/dist/navigation/entry-state.d.ts +23 -0
- package/dist/navigation/entry-state.d.ts.map +1 -0
- package/dist/navigation/entry-state.js +39 -0
- package/dist/navigation/entry-state.js.map +1 -0
- package/dist/navigation/guard-registry.d.ts +30 -0
- package/dist/navigation/guard-registry.d.ts.map +1 -0
- package/dist/navigation/guard-registry.js +46 -0
- package/dist/navigation/guard-registry.js.map +1 -0
- package/dist/navigation/guarded-navigation.d.ts +24 -0
- package/dist/navigation/guarded-navigation.d.ts.map +1 -0
- package/dist/navigation/guarded-navigation.js +102 -0
- package/dist/navigation/guarded-navigation.js.map +1 -0
- package/dist/navigation/index.d.ts +3 -2
- package/dist/navigation/index.d.ts.map +1 -1
- package/dist/navigation/index.js +2 -1
- package/dist/navigation/index.js.map +1 -1
- package/dist/navigation/types.d.ts +8 -11
- package/dist/navigation/types.d.ts.map +1 -1
- package/dist/navigation/use-back-forward-guard.d.ts +5 -0
- package/dist/navigation/use-back-forward-guard.d.ts.map +1 -0
- package/dist/navigation/use-back-forward-guard.js +82 -0
- package/dist/navigation/use-back-forward-guard.js.map +1 -0
- package/dist/plugin/registry.d.ts +6 -15
- package/dist/plugin/registry.d.ts.map +1 -1
- package/dist/plugin/registry.js +83 -75
- package/dist/plugin/registry.js.map +1 -1
- package/dist/schema-editor/SchemaEditor.d.ts +8 -1
- package/dist/schema-editor/SchemaEditor.d.ts.map +1 -1
- package/dist/schema-editor/SchemaEditor.js +2 -2
- package/dist/schema-editor/SchemaEditor.js.map +1 -1
- package/dist/schema-form/SecretRefField.d.ts +2 -3
- package/dist/schema-form/SecretRefField.d.ts.map +1 -1
- package/dist/schema-form/SecretRefField.js +23 -12
- package/dist/schema-form/SecretRefField.js.map +1 -1
- package/dist/schema-form/classify.d.ts +1 -122
- package/dist/schema-form/classify.d.ts.map +1 -1
- package/dist/schema-form/classify.js +174 -237
- package/dist/schema-form/classify.js.map +1 -1
- package/dist/schema-form/default-value.d.ts.map +1 -1
- package/dist/schema-form/default-value.js +42 -19
- package/dist/schema-form/default-value.js.map +1 -1
- package/dist/schema-form/enum-field.d.ts +1 -1
- package/dist/schema-form/enum-field.d.ts.map +1 -1
- package/dist/schema-form/field-model.d.ts +121 -0
- package/dist/schema-form/field-model.d.ts.map +1 -0
- package/dist/schema-form/field-model.js +2 -0
- package/dist/schema-form/field-model.js.map +1 -0
- package/dist/schema-form/field-node.d.ts.map +1 -1
- package/dist/schema-form/field-node.js +25 -14
- package/dist/schema-form/field-node.js.map +1 -1
- package/dist/schema-form/media-field.d.ts +1 -1
- package/dist/schema-form/media-field.d.ts.map +1 -1
- package/dist/schema-form/media-field.js +47 -28
- package/dist/schema-form/media-field.js.map +1 -1
- package/dist/schema-form/string-annotations.d.ts +20 -0
- package/dist/schema-form/string-annotations.d.ts.map +1 -0
- package/dist/schema-form/string-annotations.js +105 -0
- package/dist/schema-form/string-annotations.js.map +1 -0
- package/dist/schema-form/string-field.d.ts +1 -1
- package/dist/schema-form/string-field.d.ts.map +1 -1
- package/dist/schema-form/union-field.d.ts +1 -1
- package/dist/schema-form/union-field.d.ts.map +1 -1
- package/dist/schema-form/union-field.js.map +1 -1
- package/dist/schema-form/union.d.ts +1 -1
- package/dist/schema-form/union.d.ts.map +1 -1
- package/dist/schema-form/validate.d.ts.map +1 -1
- package/dist/schema-form/validate.js +126 -105
- package/dist/schema-form/validate.js.map +1 -1
- package/dist/state-binding/AdapterMapping.d.ts.map +1 -1
- package/dist/state-binding/AdapterMapping.js +0 -0
- package/dist/state-binding/AdapterMapping.js.map +1 -1
- package/dist/state-binding/StateAttachRow.d.ts.map +1 -1
- package/dist/state-binding/StateAttachRow.js +52 -36
- package/dist/state-binding/StateAttachRow.js.map +1 -1
- package/dist/state-binding/adapter-mapping-row.d.ts +17 -0
- package/dist/state-binding/adapter-mapping-row.d.ts.map +1 -0
- package/dist/state-binding/adapter-mapping-row.js +87 -0
- package/dist/state-binding/adapter-mapping-row.js.map +1 -0
- package/dist/structured-output/StructuredOutput.d.ts.map +1 -1
- package/dist/structured-output/StructuredOutput.js +1 -3
- package/dist/structured-output/StructuredOutput.js.map +1 -1
- package/package.json +2 -2
- package/src/components/explorer-body.tsx +239 -0
- package/src/components/explorer-page-size.ts +39 -0
- package/src/components/explorer-tag-filter.tsx +80 -0
- package/src/components/explorer-tags.ts +64 -0
- package/src/components/explorer-view.tsx +114 -366
- package/src/components/extension-combo-builder.tsx +35 -152
- package/src/components/extension-combo-parts.tsx +237 -0
- package/src/components/fleet-report.tsx +97 -54
- package/src/components/icons/chrome-marks.tsx +195 -0
- package/src/components/icons/icon-frame.tsx +50 -0
- package/src/components/icons/index.ts +84 -0
- package/src/components/icons/nav-map.ts +55 -0
- package/src/components/icons/nav-marks.tsx +205 -0
- package/src/components/icons/status-marks.tsx +46 -0
- package/src/components/icons/theme-marks.tsx +33 -0
- package/src/components/json-diff.tsx +1 -4
- package/src/components/json-tree-context.ts +26 -0
- package/src/components/json-tree-model.ts +139 -0
- package/src/components/json-tree-nodes.tsx +224 -0
- package/src/components/json-tree.tsx +15 -434
- package/src/components/json-value-format.ts +64 -0
- package/src/components/markdown-blocks.ts +198 -0
- package/src/components/markdown-inline.tsx +365 -0
- package/src/components/markdown.tsx +5 -471
- package/src/components/observer-registry.ts +139 -0
- package/src/components/overflow-measure.ts +69 -0
- package/src/components/prose-regions.ts +153 -0
- package/src/components/scroll-region.tsx +20 -360
- package/src/components/tab-stop-parse.ts +217 -0
- package/src/components/templated-text-field.tsx +38 -330
- package/src/components/templated-text-kwargs-editor.tsx +147 -0
- package/src/components/templated-text-kwargs.ts +92 -0
- package/src/components/templated-text-source.tsx +226 -0
- package/src/components/tool-picker-parts.tsx +184 -0
- package/src/components/tool-picker.tsx +22 -114
- package/src/components/version-history-panel.tsx +38 -142
- package/src/components/version-history-rows.tsx +220 -0
- package/src/guards.ts +7 -0
- package/src/hooks/interactions-connection.ts +227 -0
- package/src/hooks/interactions-stream.ts +274 -0
- package/src/hooks/useSse.ts +50 -477
- package/src/navigation/app-link.tsx +70 -0
- package/src/navigation/context.tsx +11 -330
- package/src/navigation/entry-state.ts +42 -0
- package/src/navigation/guard-registry.ts +65 -0
- package/src/navigation/guarded-navigation.ts +147 -0
- package/src/navigation/index.ts +2 -2
- package/src/navigation/types.ts +8 -11
- package/src/navigation/use-back-forward-guard.ts +90 -0
- package/src/plugin/registry.ts +111 -90
- package/src/schema-editor/SchemaEditor.tsx +9 -1
- package/src/schema-form/SecretRefField.tsx +121 -71
- package/src/schema-form/classify.ts +244 -395
- package/src/schema-form/default-value.ts +48 -17
- package/src/schema-form/enum-field.tsx +1 -1
- package/src/schema-form/field-model.ts +133 -0
- package/src/schema-form/field-node.tsx +94 -38
- package/src/schema-form/media-field.tsx +134 -65
- package/src/schema-form/string-annotations.ts +111 -0
- package/src/schema-form/string-field.tsx +1 -1
- package/src/schema-form/union-field.tsx +2 -1
- package/src/schema-form/union.ts +1 -1
- package/src/schema-form/validate.ts +184 -103
- package/src/state-binding/AdapterMapping.tsx +0 -0
- package/src/state-binding/StateAttachRow.tsx +222 -147
- package/src/state-binding/adapter-mapping-row.tsx +232 -0
- package/src/structured-output/StructuredOutput.tsx +1 -4
- package/dist/components/icons.d.ts +0 -160
- package/dist/components/icons.d.ts.map +0 -1
- package/dist/components/icons.js +0 -160
- package/dist/components/icons.js.map +0 -1
- package/src/components/icons.tsx +0 -586
|
@@ -17,133 +17,10 @@ import {
|
|
|
17
17
|
variantTag,
|
|
18
18
|
type EnumOption,
|
|
19
19
|
} from './resolve';
|
|
20
|
+
import type { ClassifiedField, UnionVariant } from './field-model';
|
|
21
|
+
import { expressionAnnotation, mediaUpload } from './string-annotations';
|
|
20
22
|
import type { JsonSchema } from './types';
|
|
21
23
|
|
|
22
|
-
/**
|
|
23
|
-
* How a string field accepts binary content as a JSON-serializable string.
|
|
24
|
-
* Derived from a field's JSON-Schema media annotations (see the `SchemaForm` module).
|
|
25
|
-
*/
|
|
26
|
-
export interface MediaUpload {
|
|
27
|
-
/**
|
|
28
|
-
* `base64` — the emitted value is the raw base64 body (no `data:` prefix),
|
|
29
|
-
* matching `contentEncoding: "base64"`. `data-url` — the emitted value is a
|
|
30
|
-
* full `data:<mime>;base64,<body>` URL, matching `format: "data-url"`.
|
|
31
|
-
*/
|
|
32
|
-
readonly encoding: 'base64' | 'data-url';
|
|
33
|
-
/**
|
|
34
|
-
* The `contentMediaType` constraint (e.g. `image/*`, `application/pdf`), used
|
|
35
|
-
* for MIME validation and the file picker's `accept`. `undefined` for a bare
|
|
36
|
-
* `format: "data-url"` field with no declared type.
|
|
37
|
-
*/
|
|
38
|
-
readonly mediaType: string | undefined;
|
|
39
|
-
/** A per-field byte cap from `contentMaxBytes`, if the schema pins one. */
|
|
40
|
-
readonly maxBytes: number | undefined;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** One top-level key of the expression's input document, with a one-line gloss. */
|
|
44
|
-
export interface ExpressionAnnotationKey {
|
|
45
|
-
readonly name: string;
|
|
46
|
-
readonly gloss: string;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* The `x-tai42-expression` annotation on a string schema: the server declares
|
|
51
|
-
* that the field's value is authored in a pipeline expression language (only jq
|
|
52
|
-
* today) and optionally describes what `.` is for that expression. The renderer
|
|
53
|
-
* uses it to open the visual-editor door instead of a plain text box. The shape
|
|
54
|
-
* mirrors jq-studio's input-shape descriptor WITHOUT importing it — classification
|
|
55
|
-
* stays a pure schema concern; the renderer maps this onto a jq field declaration.
|
|
56
|
-
*/
|
|
57
|
-
export interface ExpressionAnnotation {
|
|
58
|
-
/** The declared language. An unknown language never reaches here — it classifies
|
|
59
|
-
* as a plain string, so an older client renders a newer server's field as the
|
|
60
|
-
* ordinary text input it always was. */
|
|
61
|
-
readonly language: 'jq';
|
|
62
|
-
/** Short chip label for what `.` is (e.g. "node envelope"). */
|
|
63
|
-
readonly label: string | undefined;
|
|
64
|
-
/** One sentence describing what `.` is in this field. */
|
|
65
|
-
readonly blurb: string | undefined;
|
|
66
|
-
/** The top-level keys of `.`, each with a one-liner. */
|
|
67
|
-
readonly keys: readonly ExpressionAnnotationKey[] | undefined;
|
|
68
|
-
/** What the expression must return (e.g. "true or false"). */
|
|
69
|
-
readonly returns: string | undefined;
|
|
70
|
-
/** Per-field caveats. */
|
|
71
|
-
readonly caveats: readonly string[] | undefined;
|
|
72
|
-
/** True when the annotation carries a `sample` — tracked separately because a
|
|
73
|
-
* legitimate sample may be JSON `null`. */
|
|
74
|
-
readonly hasSample: boolean;
|
|
75
|
-
/** A static skeleton of `.` for the Test panel. Meaningful only when
|
|
76
|
-
* {@link hasSample} is true. */
|
|
77
|
-
readonly sample: unknown;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/** One selectable variant of a union field. */
|
|
81
|
-
export interface UnionVariant {
|
|
82
|
-
readonly label: string;
|
|
83
|
-
/** The discriminator tag value, or `undefined` for a non-discriminated union. */
|
|
84
|
-
readonly tag: unknown;
|
|
85
|
-
/** The variant schema as written (unresolved — the renderer resolves it). */
|
|
86
|
-
readonly schema: JsonSchema;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/** The renderer-facing classification of a schema node. */
|
|
90
|
-
export type FieldModel =
|
|
91
|
-
| { readonly kind: 'const'; readonly value: unknown }
|
|
92
|
-
| { readonly kind: 'enum'; readonly options: readonly EnumOption[] }
|
|
93
|
-
| {
|
|
94
|
-
readonly kind: 'string';
|
|
95
|
-
readonly format: string | undefined;
|
|
96
|
-
/** Present when media annotations opt the field into the upload control. */
|
|
97
|
-
readonly media: MediaUpload | undefined;
|
|
98
|
-
/** Present when a well-formed `x-tai42-expression` annotation opts the
|
|
99
|
-
* field into the jq expression editor (see {@link ExpressionAnnotation}). */
|
|
100
|
-
readonly expression: ExpressionAnnotation | undefined;
|
|
101
|
-
}
|
|
102
|
-
| { readonly kind: 'number'; readonly integer: boolean }
|
|
103
|
-
| { readonly kind: 'boolean' }
|
|
104
|
-
| { readonly kind: 'array'; readonly items: JsonSchema }
|
|
105
|
-
| {
|
|
106
|
-
readonly kind: 'object';
|
|
107
|
-
readonly properties: readonly (readonly [string, JsonSchema])[];
|
|
108
|
-
readonly required: ReadonlySet<string>;
|
|
109
|
-
}
|
|
110
|
-
| {
|
|
111
|
-
// A string-keyed map typed by `additionalProperties` (no fixed
|
|
112
|
-
// `properties`), edited as key/value rows. `values` is the entry value
|
|
113
|
-
// schema AS WRITTEN — the entry editor resolves any `$ref` itself, exactly
|
|
114
|
-
// as `array.items` is.
|
|
115
|
-
readonly kind: 'record';
|
|
116
|
-
readonly values: JsonSchema;
|
|
117
|
-
}
|
|
118
|
-
| {
|
|
119
|
-
readonly kind: 'union';
|
|
120
|
-
readonly variants: readonly UnionVariant[];
|
|
121
|
-
readonly discriminator: string | undefined;
|
|
122
|
-
}
|
|
123
|
-
| {
|
|
124
|
-
// A shape with no structured editor to build — a property-less object, a
|
|
125
|
-
// bare `additionalProperties`-open object, an items-less array, an empty/
|
|
126
|
-
// multi-type/`allOf` schema. It is NOT undroppable: every one of these still
|
|
127
|
-
// describes JSON, so the renderer edits it as free-form JSON in a mono
|
|
128
|
-
// textarea rather than dead-ending on a badge. `jsonType` is the container
|
|
129
|
-
// the schema commits to, so a valid buffer must parse to it (`'any'` = no
|
|
130
|
-
// constraint beyond "is JSON"); `reason` records which shape fell through.
|
|
131
|
-
readonly kind: 'json';
|
|
132
|
-
readonly jsonType: 'object' | 'array' | 'any';
|
|
133
|
-
readonly reason: string;
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
/** A fully classified field: its model plus resolved metadata. */
|
|
137
|
-
export interface ClassifiedField {
|
|
138
|
-
readonly model: FieldModel;
|
|
139
|
-
/** True when JSON `null` is an accepted value (a null-union member). */
|
|
140
|
-
readonly nullable: boolean;
|
|
141
|
-
/** The effective (ref-resolved) schema this classification describes. */
|
|
142
|
-
readonly schema: JsonSchema;
|
|
143
|
-
readonly title: string | undefined;
|
|
144
|
-
readonly description: string | undefined;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
24
|
function firstString(...values: readonly unknown[]): string | undefined {
|
|
148
25
|
for (const value of values) {
|
|
149
26
|
if (typeof value === 'string' && value.length > 0) return value;
|
|
@@ -151,91 +28,6 @@ function firstString(...values: readonly unknown[]): string | undefined {
|
|
|
151
28
|
return undefined;
|
|
152
29
|
}
|
|
153
30
|
|
|
154
|
-
/**
|
|
155
|
-
* Detect the media-upload annotations on a (ref-resolved) string schema. A field
|
|
156
|
-
* opts into the upload control when it carries `contentEncoding: "base64"` plus a
|
|
157
|
-
* `contentMediaType`, OR `format: "data-url"`. Anything else is a plain string.
|
|
158
|
-
*/
|
|
159
|
-
function mediaUpload(schema: JsonSchema): MediaUpload | undefined {
|
|
160
|
-
const mediaType =
|
|
161
|
-
typeof schema.contentMediaType === 'string' ? schema.contentMediaType : undefined;
|
|
162
|
-
const maxBytes =
|
|
163
|
-
typeof schema.contentMaxBytes === 'number' && schema.contentMaxBytes > 0
|
|
164
|
-
? schema.contentMaxBytes
|
|
165
|
-
: undefined;
|
|
166
|
-
if (schema.format === 'data-url') {
|
|
167
|
-
return { encoding: 'data-url', mediaType, maxBytes };
|
|
168
|
-
}
|
|
169
|
-
if (schema.contentEncoding === 'base64' && mediaType !== undefined) {
|
|
170
|
-
return { encoding: 'base64', mediaType, maxBytes };
|
|
171
|
-
}
|
|
172
|
-
return undefined;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const EXPRESSION_KEYWORD = 'x-tai42-expression';
|
|
176
|
-
|
|
177
|
-
/** A parsed-JSON plain object (arrays and `null` are `typeof 'object'` too). */
|
|
178
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
179
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Detect the `x-tai42-expression` annotation on a (ref-resolved) string schema,
|
|
184
|
-
* mirroring {@link mediaUpload}: a well-formed annotation opts the field into the
|
|
185
|
-
* jq expression editor; anything else — absent, non-object, unknown language, or
|
|
186
|
-
* any wrongly-typed member — yields `undefined`, so the field renders EXACTLY as
|
|
187
|
-
* the plain string input it is today. Malformed payloads NEVER throw: a bad
|
|
188
|
-
* annotation is a server-side authoring bug, and degrading to the ordinary text
|
|
189
|
-
* box keeps the form usable while staying byte-identical to the unannotated
|
|
190
|
-
* rendering.
|
|
191
|
-
*/
|
|
192
|
-
function expressionAnnotation(schema: JsonSchema): ExpressionAnnotation | undefined {
|
|
193
|
-
const raw: unknown = schema[EXPRESSION_KEYWORD];
|
|
194
|
-
if (!isRecord(raw)) return undefined;
|
|
195
|
-
// The language gate doubles as the forward-compatibility valve: a future
|
|
196
|
-
// language this client does not understand degrades to a plain string field.
|
|
197
|
-
if (raw.language !== 'jq') return undefined;
|
|
198
|
-
|
|
199
|
-
const { label, blurb, returns } = raw;
|
|
200
|
-
if (label !== undefined && typeof label !== 'string') return undefined;
|
|
201
|
-
if (blurb !== undefined && typeof blurb !== 'string') return undefined;
|
|
202
|
-
if (returns !== undefined && typeof returns !== 'string') return undefined;
|
|
203
|
-
|
|
204
|
-
let keys: ExpressionAnnotationKey[] | undefined;
|
|
205
|
-
if (raw.keys !== undefined) {
|
|
206
|
-
if (!Array.isArray(raw.keys)) return undefined;
|
|
207
|
-
keys = [];
|
|
208
|
-
for (const entry of raw.keys as readonly unknown[]) {
|
|
209
|
-
if (!isRecord(entry) || typeof entry.name !== 'string' || typeof entry.gloss !== 'string') {
|
|
210
|
-
return undefined;
|
|
211
|
-
}
|
|
212
|
-
keys.push({ name: entry.name, gloss: entry.gloss });
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
let caveats: string[] | undefined;
|
|
217
|
-
if (raw.caveats !== undefined) {
|
|
218
|
-
if (!Array.isArray(raw.caveats)) return undefined;
|
|
219
|
-
caveats = [];
|
|
220
|
-
for (const entry of raw.caveats as readonly unknown[]) {
|
|
221
|
-
if (typeof entry !== 'string') return undefined;
|
|
222
|
-
caveats.push(entry);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// `sample` is any JSON — including `null` — so presence is tracked, not typed.
|
|
227
|
-
return {
|
|
228
|
-
language: 'jq',
|
|
229
|
-
label,
|
|
230
|
-
blurb,
|
|
231
|
-
keys,
|
|
232
|
-
returns,
|
|
233
|
-
caveats,
|
|
234
|
-
hasSample: 'sample' in raw,
|
|
235
|
-
sample: raw.sample,
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
|
|
239
31
|
/**
|
|
240
32
|
* Land a shape the form has no structured editor for on the free-form JSON
|
|
241
33
|
* fallback (a mono JSON textarea) rather than a dead badge — a field is never
|
|
@@ -254,132 +46,250 @@ function jsonFallback(
|
|
|
254
46
|
return { model: { kind: 'json', jsonType, reason }, nullable, schema, title, description };
|
|
255
47
|
}
|
|
256
48
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
49
|
+
// const pins a single value. Null-acceptance is fully decidable here: the one
|
|
50
|
+
// permitted value either IS null or it is not. Without this, a `const: null`
|
|
51
|
+
// field would seed to null and then fail the validator's upstream "must not be
|
|
52
|
+
// null" gate before the equality check ever ran — permanently invalid.
|
|
53
|
+
function classifyConst(
|
|
54
|
+
resolved: JsonSchema,
|
|
55
|
+
title: string | undefined,
|
|
56
|
+
description: string | undefined,
|
|
57
|
+
): ClassifiedField {
|
|
58
|
+
return {
|
|
59
|
+
model: { kind: 'const', value: resolved.const },
|
|
60
|
+
nullable: resolved.const === null,
|
|
61
|
+
schema: resolved,
|
|
62
|
+
title,
|
|
63
|
+
description,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
266
66
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
67
|
+
// enum → a fixed choice list.
|
|
68
|
+
function classifyEnum(
|
|
69
|
+
resolved: JsonSchema,
|
|
70
|
+
title: string | undefined,
|
|
71
|
+
description: string | undefined,
|
|
72
|
+
): ClassifiedField {
|
|
73
|
+
const enumValues = resolved.enum ?? [];
|
|
74
|
+
const options: EnumOption[] = enumValues.map((value) => ({ value, label: scalarLabel(value) }));
|
|
75
|
+
const nullable = enumValues.some((value) => value === null);
|
|
76
|
+
return { model: { kind: 'enum', options }, nullable, schema: resolved, title, description };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// anyOf/oneOf union normalization.
|
|
80
|
+
function classifyUnion(
|
|
81
|
+
resolved: JsonSchema,
|
|
82
|
+
members: readonly JsonSchema[],
|
|
83
|
+
root: JsonSchema,
|
|
84
|
+
title: string | undefined,
|
|
85
|
+
description: string | undefined,
|
|
86
|
+
): ClassifiedField {
|
|
87
|
+
const nonNull = members.filter((member) => !isNullSchema(resolveRef(member, root)));
|
|
88
|
+
const nullable = nonNull.length !== members.length;
|
|
89
|
+
const soleNonNull = nonNull[0];
|
|
90
|
+
if (soleNonNull === undefined) {
|
|
91
|
+
// Every member is `null` — degenerate, but `null` is still valid, so edit
|
|
92
|
+
// it as JSON (nullable) rather than dead-ending on a badge.
|
|
93
|
+
return jsonFallback(
|
|
94
|
+
resolved,
|
|
95
|
+
'any',
|
|
96
|
+
'union with no non-null members',
|
|
276
97
|
title,
|
|
277
98
|
description,
|
|
278
|
-
|
|
99
|
+
true,
|
|
100
|
+
);
|
|
279
101
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
if (resolved.enum !== undefined) {
|
|
283
|
-
const options: EnumOption[] = resolved.enum.map((value) => ({
|
|
284
|
-
value,
|
|
285
|
-
label: scalarLabel(value),
|
|
286
|
-
}));
|
|
287
|
-
const nullable = resolved.enum.some((value) => value === null);
|
|
288
|
-
return { model: { kind: 'enum', options }, nullable, schema: resolved, title, description };
|
|
102
|
+
if (nonNull.length === 1) {
|
|
103
|
+
return classifySoleUnionMember(soleNonNull, resolved, root, nullable, title, description);
|
|
289
104
|
}
|
|
105
|
+
const discriminator = resolved.discriminator?.propertyName;
|
|
106
|
+
const variants: UnionVariant[] = nonNull.map((member, index) => {
|
|
107
|
+
const tag = discriminator === undefined ? undefined : variantTag(member, discriminator, root);
|
|
108
|
+
return { label: variantLabel(member, index, tag, root), tag, schema: member };
|
|
109
|
+
});
|
|
110
|
+
return {
|
|
111
|
+
model: { kind: 'union', variants, discriminator },
|
|
112
|
+
nullable,
|
|
113
|
+
schema: resolved,
|
|
114
|
+
title,
|
|
115
|
+
description,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
290
118
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
return { label: variantLabel(member, index, tag, root), tag, schema: member };
|
|
338
|
-
});
|
|
119
|
+
// A null-union of exactly one non-null member: delegate to that member.
|
|
120
|
+
function classifySoleUnionMember(
|
|
121
|
+
soleNonNull: JsonSchema,
|
|
122
|
+
resolved: JsonSchema,
|
|
123
|
+
root: JsonSchema,
|
|
124
|
+
nullable: boolean,
|
|
125
|
+
title: string | undefined,
|
|
126
|
+
description: string | undefined,
|
|
127
|
+
): ClassifiedField {
|
|
128
|
+
const inner = classifySchema(soleNonNull, root);
|
|
129
|
+
// Media annotations (`contentEncoding`/`contentMediaType`/`format: data-url`)
|
|
130
|
+
// that land on the OUTER null-union wrapper of an OPTIONAL field would
|
|
131
|
+
// otherwise be lost when delegating to the inner string member, silently
|
|
132
|
+
// degrading an optional upload field to a plain text box. Re-detect them on
|
|
133
|
+
// the wrapper and attach to the inner string model. The expression
|
|
134
|
+
// annotation gets the same treatment for the same reason: an optional
|
|
135
|
+
// annotated field must keep its editor door.
|
|
136
|
+
const model =
|
|
137
|
+
inner.model.kind === 'string'
|
|
138
|
+
? {
|
|
139
|
+
...inner.model,
|
|
140
|
+
media: inner.model.media ?? mediaUpload(resolved),
|
|
141
|
+
expression: inner.model.expression ?? expressionAnnotation(resolved),
|
|
142
|
+
}
|
|
143
|
+
: inner.model;
|
|
144
|
+
return {
|
|
145
|
+
model,
|
|
146
|
+
nullable: nullable || inner.nullable,
|
|
147
|
+
schema: inner.schema,
|
|
148
|
+
title: title ?? inner.title,
|
|
149
|
+
description: description ?? inner.description,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// allOf: Pydantic wraps a single $ref this way; a multi-member intersection is
|
|
154
|
+
// not a form-renderable construct.
|
|
155
|
+
function classifyAllOf(
|
|
156
|
+
resolved: JsonSchema,
|
|
157
|
+
root: JsonSchema,
|
|
158
|
+
title: string | undefined,
|
|
159
|
+
description: string | undefined,
|
|
160
|
+
): ClassifiedField {
|
|
161
|
+
const allOf = resolved.allOf ?? [];
|
|
162
|
+
const soleAllOf = allOf[0];
|
|
163
|
+
if (allOf.length === 1 && soleAllOf !== undefined) {
|
|
164
|
+
const inner = classifySchema(soleAllOf, root);
|
|
339
165
|
return {
|
|
340
|
-
model:
|
|
341
|
-
nullable,
|
|
342
|
-
schema:
|
|
166
|
+
model: inner.model,
|
|
167
|
+
nullable: inner.nullable,
|
|
168
|
+
schema: inner.schema,
|
|
169
|
+
title: title ?? inner.title,
|
|
170
|
+
description: description ?? inner.description,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
// Whether the intersection accepts `null` is not statically decidable here
|
|
174
|
+
// (it does iff EVERY member does), and the json escape hatch only enforces
|
|
175
|
+
// what it KNOWS — so stay permissive like `jsonType: 'any'` itself and let
|
|
176
|
+
// the server-side contract be the authority, rather than rejecting a null
|
|
177
|
+
// the schema may well permit (e.g. an intersection of open schemas).
|
|
178
|
+
return jsonFallback(
|
|
179
|
+
resolved,
|
|
180
|
+
'any',
|
|
181
|
+
'allOf intersection of multiple schemas',
|
|
182
|
+
title,
|
|
183
|
+
description,
|
|
184
|
+
true,
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function classifyString(
|
|
189
|
+
resolved: JsonSchema,
|
|
190
|
+
title: string | undefined,
|
|
191
|
+
description: string | undefined,
|
|
192
|
+
nullable: boolean,
|
|
193
|
+
): ClassifiedField {
|
|
194
|
+
return {
|
|
195
|
+
model: {
|
|
196
|
+
kind: 'string',
|
|
197
|
+
format: resolved.format,
|
|
198
|
+
media: mediaUpload(resolved),
|
|
199
|
+
expression: expressionAnnotation(resolved),
|
|
200
|
+
},
|
|
201
|
+
nullable,
|
|
202
|
+
schema: resolved,
|
|
203
|
+
title,
|
|
204
|
+
description,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function classifyArray(
|
|
209
|
+
resolved: JsonSchema,
|
|
210
|
+
title: string | undefined,
|
|
211
|
+
description: string | undefined,
|
|
212
|
+
nullable: boolean,
|
|
213
|
+
): ClassifiedField {
|
|
214
|
+
if (resolved.items === undefined) {
|
|
215
|
+
// A bare array with no item schema: no per-item editor to build, but the
|
|
216
|
+
// value is still a JSON array — edit it whole as free-form JSON.
|
|
217
|
+
return jsonFallback(
|
|
218
|
+
resolved,
|
|
219
|
+
'array',
|
|
220
|
+
'array without an items schema',
|
|
343
221
|
title,
|
|
344
222
|
description,
|
|
345
|
-
|
|
223
|
+
nullable,
|
|
224
|
+
);
|
|
346
225
|
}
|
|
226
|
+
return {
|
|
227
|
+
model: { kind: 'array', items: resolved.items },
|
|
228
|
+
nullable,
|
|
229
|
+
schema: resolved,
|
|
230
|
+
title,
|
|
231
|
+
description,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
347
234
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
235
|
+
function classifyObject(
|
|
236
|
+
resolved: JsonSchema,
|
|
237
|
+
title: string | undefined,
|
|
238
|
+
description: string | undefined,
|
|
239
|
+
nullable: boolean,
|
|
240
|
+
): ClassifiedField {
|
|
241
|
+
if (resolved.properties === undefined) {
|
|
242
|
+
// A free-form object typed by a VALUE SCHEMA (`additionalProperties` is a
|
|
243
|
+
// schema, not `false`/`true`/absent) is a string→X map — renderable as
|
|
244
|
+
// key/value rows. A boolean/absent `additionalProperties` carries no value
|
|
245
|
+
// type to build an entry editor from, so it falls through to the free-form
|
|
246
|
+
// JSON editor (constrained to a JSON object).
|
|
247
|
+
// Read as `unknown`: the value is parsed JSON, where `additionalProperties:
|
|
248
|
+
// null` is possible and `typeof null === 'object'` would otherwise classify
|
|
249
|
+
// it as a record with a `null` value schema.
|
|
250
|
+
const additional: unknown = resolved.additionalProperties;
|
|
251
|
+
if (typeof additional === 'object' && additional !== null) {
|
|
354
252
|
return {
|
|
355
|
-
model:
|
|
356
|
-
nullable
|
|
357
|
-
schema:
|
|
358
|
-
title
|
|
359
|
-
description
|
|
253
|
+
model: { kind: 'record', values: additional as JsonSchema },
|
|
254
|
+
nullable,
|
|
255
|
+
schema: resolved,
|
|
256
|
+
title,
|
|
257
|
+
description,
|
|
360
258
|
};
|
|
361
259
|
}
|
|
362
|
-
// Whether the intersection accepts `null` is not statically decidable here
|
|
363
|
-
// (it does iff EVERY member does), and the json escape hatch only enforces
|
|
364
|
-
// what it KNOWS — so stay permissive like `jsonType: 'any'` itself and let
|
|
365
|
-
// the server-side contract be the authority, rather than rejecting a null
|
|
366
|
-
// the schema may well permit (e.g. an intersection of open schemas).
|
|
367
260
|
return jsonFallback(
|
|
368
261
|
resolved,
|
|
369
|
-
'
|
|
370
|
-
'
|
|
262
|
+
'object',
|
|
263
|
+
'free-form object with no property schema',
|
|
371
264
|
title,
|
|
372
265
|
description,
|
|
373
|
-
|
|
266
|
+
nullable,
|
|
374
267
|
);
|
|
375
268
|
}
|
|
269
|
+
const properties: (readonly [string, JsonSchema])[] = Object.entries(resolved.properties);
|
|
270
|
+
const required = new Set(resolved.required ?? []);
|
|
271
|
+
return {
|
|
272
|
+
model: { kind: 'object', properties, required },
|
|
273
|
+
nullable,
|
|
274
|
+
schema: resolved,
|
|
275
|
+
title,
|
|
276
|
+
description,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
376
279
|
|
|
377
|
-
|
|
280
|
+
// Type-driven classification: a single non-null JSON type maps to its structured
|
|
281
|
+
// editor; no type, several types, or an unrecognized one lands on the JSON fallback.
|
|
282
|
+
function classifyByType(
|
|
283
|
+
resolved: JsonSchema,
|
|
284
|
+
title: string | undefined,
|
|
285
|
+
description: string | undefined,
|
|
286
|
+
): ClassifiedField {
|
|
378
287
|
const types = typeList(resolved);
|
|
379
288
|
const nonNullTypes = types.filter((type) => type !== 'null');
|
|
380
289
|
const nullableByType = nonNullTypes.length !== types.length;
|
|
381
290
|
|
|
382
|
-
|
|
291
|
+
const type = nonNullTypes[0];
|
|
292
|
+
if (nonNullTypes.length === 0 || type === undefined) {
|
|
383
293
|
// No `type` at all (an open `{}` schema, or `type: "null"` alone) accepts any
|
|
384
294
|
// JSON — the classic "any" field. Edit it as free-form JSON. Nullable is
|
|
385
295
|
// always true here: `type: "null"` declares it, and an open schema with no
|
|
@@ -404,32 +314,9 @@ export function classifySchema(raw: JsonSchema, root: JsonSchema): ClassifiedFie
|
|
|
404
314
|
nullableByType,
|
|
405
315
|
);
|
|
406
316
|
}
|
|
407
|
-
|
|
408
|
-
const type = nonNullTypes[0];
|
|
409
|
-
if (type === undefined) {
|
|
410
|
-
return jsonFallback(
|
|
411
|
-
resolved,
|
|
412
|
-
'any',
|
|
413
|
-
'schema declares no renderable type',
|
|
414
|
-
title,
|
|
415
|
-
description,
|
|
416
|
-
nullableByType,
|
|
417
|
-
);
|
|
418
|
-
}
|
|
419
317
|
switch (type) {
|
|
420
318
|
case 'string':
|
|
421
|
-
return
|
|
422
|
-
model: {
|
|
423
|
-
kind: 'string',
|
|
424
|
-
format: resolved.format,
|
|
425
|
-
media: mediaUpload(resolved),
|
|
426
|
-
expression: expressionAnnotation(resolved),
|
|
427
|
-
},
|
|
428
|
-
nullable: nullableByType,
|
|
429
|
-
schema: resolved,
|
|
430
|
-
title,
|
|
431
|
-
description,
|
|
432
|
-
};
|
|
319
|
+
return classifyString(resolved, title, description, nullableByType);
|
|
433
320
|
case 'number':
|
|
434
321
|
return {
|
|
435
322
|
model: { kind: 'number', integer: false },
|
|
@@ -454,66 +341,10 @@ export function classifySchema(raw: JsonSchema, root: JsonSchema): ClassifiedFie
|
|
|
454
341
|
title,
|
|
455
342
|
description,
|
|
456
343
|
};
|
|
457
|
-
case 'array':
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
return jsonFallback(
|
|
462
|
-
resolved,
|
|
463
|
-
'array',
|
|
464
|
-
'array without an items schema',
|
|
465
|
-
title,
|
|
466
|
-
description,
|
|
467
|
-
nullableByType,
|
|
468
|
-
);
|
|
469
|
-
}
|
|
470
|
-
return {
|
|
471
|
-
model: { kind: 'array', items: resolved.items },
|
|
472
|
-
nullable: nullableByType,
|
|
473
|
-
schema: resolved,
|
|
474
|
-
title,
|
|
475
|
-
description,
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
|
-
case 'object': {
|
|
479
|
-
if (resolved.properties === undefined) {
|
|
480
|
-
// A free-form object typed by a VALUE SCHEMA (`additionalProperties` is a
|
|
481
|
-
// schema, not `false`/`true`/absent) is a string→X map — renderable as
|
|
482
|
-
// key/value rows. A boolean/absent `additionalProperties` carries no value
|
|
483
|
-
// type to build an entry editor from, so it falls through to the free-form
|
|
484
|
-
// JSON editor (constrained to a JSON object).
|
|
485
|
-
// Read as `unknown`: the value is parsed JSON, where `additionalProperties:
|
|
486
|
-
// null` is possible and `typeof null === 'object'` would otherwise classify
|
|
487
|
-
// it as a record with a `null` value schema.
|
|
488
|
-
const additional: unknown = resolved.additionalProperties;
|
|
489
|
-
if (typeof additional === 'object' && additional !== null) {
|
|
490
|
-
return {
|
|
491
|
-
model: { kind: 'record', values: additional as JsonSchema },
|
|
492
|
-
nullable: nullableByType,
|
|
493
|
-
schema: resolved,
|
|
494
|
-
title,
|
|
495
|
-
description,
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
return jsonFallback(
|
|
499
|
-
resolved,
|
|
500
|
-
'object',
|
|
501
|
-
'free-form object with no property schema',
|
|
502
|
-
title,
|
|
503
|
-
description,
|
|
504
|
-
nullableByType,
|
|
505
|
-
);
|
|
506
|
-
}
|
|
507
|
-
const properties: (readonly [string, JsonSchema])[] = Object.entries(resolved.properties);
|
|
508
|
-
const required = new Set(resolved.required ?? []);
|
|
509
|
-
return {
|
|
510
|
-
model: { kind: 'object', properties, required },
|
|
511
|
-
nullable: nullableByType,
|
|
512
|
-
schema: resolved,
|
|
513
|
-
title,
|
|
514
|
-
description,
|
|
515
|
-
};
|
|
516
|
-
}
|
|
344
|
+
case 'array':
|
|
345
|
+
return classifyArray(resolved, title, description, nullableByType);
|
|
346
|
+
case 'object':
|
|
347
|
+
return classifyObject(resolved, title, description, nullableByType);
|
|
517
348
|
default:
|
|
518
349
|
return jsonFallback(
|
|
519
350
|
resolved,
|
|
@@ -525,3 +356,21 @@ export function classifySchema(raw: JsonSchema, root: JsonSchema): ClassifiedFie
|
|
|
525
356
|
);
|
|
526
357
|
}
|
|
527
358
|
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Classify a schema node (resolving `$ref` first). `wrapper` metadata (a
|
|
362
|
+
* ref-site title/description) is threaded down so a `{ $ref, title }` or a
|
|
363
|
+
* null-union wrapper keeps its property-level label.
|
|
364
|
+
*/
|
|
365
|
+
export function classifySchema(raw: JsonSchema, root: JsonSchema): ClassifiedField {
|
|
366
|
+
const resolved = resolveRef(raw, root);
|
|
367
|
+
const title = firstString(raw.title, resolved.title);
|
|
368
|
+
const description = firstString(raw.description, resolved.description);
|
|
369
|
+
|
|
370
|
+
if ('const' in resolved) return classifyConst(resolved, title, description);
|
|
371
|
+
if (resolved.enum !== undefined) return classifyEnum(resolved, title, description);
|
|
372
|
+
const members = unionMembers(resolved);
|
|
373
|
+
if (members !== undefined) return classifyUnion(resolved, members, root, title, description);
|
|
374
|
+
if (resolved.allOf !== undefined) return classifyAllOf(resolved, root, title, description);
|
|
375
|
+
return classifyByType(resolved, title, description);
|
|
376
|
+
}
|