@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
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detect the annotations that opt a (ref-resolved) string schema out of the plain
|
|
3
|
+
* text box: the media-upload annotations (`contentEncoding`/`contentMediaType`/
|
|
4
|
+
* `format: "data-url"`) and the `x-tai42-expression` expression-editor annotation.
|
|
5
|
+
* Both degrade to `undefined` on anything malformed, so an annotated field never
|
|
6
|
+
* renders differently from the plain string it falls back to.
|
|
7
|
+
*/
|
|
8
|
+
import { isRecord } from '../guards';
|
|
9
|
+
import type { ExpressionAnnotation, ExpressionAnnotationKey, MediaUpload } from './field-model';
|
|
10
|
+
import type { JsonSchema } from './types';
|
|
11
|
+
|
|
12
|
+
const EXPRESSION_KEYWORD = 'x-tai42-expression';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Detect the media-upload annotations on a (ref-resolved) string schema. A field
|
|
16
|
+
* opts into the upload control when it carries `contentEncoding: "base64"` plus a
|
|
17
|
+
* `contentMediaType`, OR `format: "data-url"`. Anything else is a plain string.
|
|
18
|
+
*/
|
|
19
|
+
export function mediaUpload(schema: JsonSchema): MediaUpload | undefined {
|
|
20
|
+
const mediaType =
|
|
21
|
+
typeof schema.contentMediaType === 'string' ? schema.contentMediaType : undefined;
|
|
22
|
+
const maxBytes =
|
|
23
|
+
typeof schema.contentMaxBytes === 'number' && schema.contentMaxBytes > 0
|
|
24
|
+
? schema.contentMaxBytes
|
|
25
|
+
: undefined;
|
|
26
|
+
if (schema.format === 'data-url') {
|
|
27
|
+
return { encoding: 'data-url', mediaType, maxBytes };
|
|
28
|
+
}
|
|
29
|
+
if (schema.contentEncoding === 'base64' && mediaType !== undefined) {
|
|
30
|
+
return { encoding: 'base64', mediaType, maxBytes };
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** An optional string member: absent is fine, a non-string is a malformed annotation. */
|
|
36
|
+
type StringOrInvalid =
|
|
37
|
+
{ readonly ok: true; readonly value: string | undefined } | { readonly ok: false };
|
|
38
|
+
|
|
39
|
+
function optionalString(value: unknown): StringOrInvalid {
|
|
40
|
+
if (value === undefined) return { ok: true, value: undefined };
|
|
41
|
+
if (typeof value === 'string') return { ok: true, value };
|
|
42
|
+
return { ok: false };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Parse the optional `keys` array: absent → `undefined`, malformed → `null` (reject). */
|
|
46
|
+
function parseAnnotationKeys(
|
|
47
|
+
value: unknown,
|
|
48
|
+
): readonly ExpressionAnnotationKey[] | undefined | null {
|
|
49
|
+
if (value === undefined) return undefined;
|
|
50
|
+
if (!Array.isArray(value)) return null;
|
|
51
|
+
const keys: ExpressionAnnotationKey[] = [];
|
|
52
|
+
for (const entry of value as readonly unknown[]) {
|
|
53
|
+
if (!isRecord(entry) || typeof entry.name !== 'string' || typeof entry.gloss !== 'string') {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
keys.push({ name: entry.name, gloss: entry.gloss });
|
|
57
|
+
}
|
|
58
|
+
return keys;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Parse the optional `caveats` array: absent → `undefined`, malformed → `null` (reject). */
|
|
62
|
+
function parseCaveatList(value: unknown): readonly string[] | undefined | null {
|
|
63
|
+
if (value === undefined) return undefined;
|
|
64
|
+
if (!Array.isArray(value)) return null;
|
|
65
|
+
const caveats: string[] = [];
|
|
66
|
+
for (const entry of value as readonly unknown[]) {
|
|
67
|
+
if (typeof entry !== 'string') return null;
|
|
68
|
+
caveats.push(entry);
|
|
69
|
+
}
|
|
70
|
+
return caveats;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Detect the `x-tai42-expression` annotation on a (ref-resolved) string schema,
|
|
75
|
+
* mirroring {@link mediaUpload}: a well-formed annotation opts the field into the
|
|
76
|
+
* jq expression editor; anything else — absent, non-object, unknown language, or
|
|
77
|
+
* any wrongly-typed member — yields `undefined`, so the field renders EXACTLY as
|
|
78
|
+
* the plain string input it is today. Malformed payloads NEVER throw: a bad
|
|
79
|
+
* annotation is a server-side authoring bug, and degrading to the ordinary text
|
|
80
|
+
* box keeps the form usable while staying byte-identical to the unannotated
|
|
81
|
+
* rendering.
|
|
82
|
+
*/
|
|
83
|
+
export function expressionAnnotation(schema: JsonSchema): ExpressionAnnotation | undefined {
|
|
84
|
+
const raw: unknown = schema[EXPRESSION_KEYWORD];
|
|
85
|
+
if (!isRecord(raw)) return undefined;
|
|
86
|
+
// The language gate doubles as the forward-compatibility valve: a future
|
|
87
|
+
// language this client does not understand degrades to a plain string field.
|
|
88
|
+
if (raw.language !== 'jq') return undefined;
|
|
89
|
+
|
|
90
|
+
const label = optionalString(raw.label);
|
|
91
|
+
const blurb = optionalString(raw.blurb);
|
|
92
|
+
const returns = optionalString(raw.returns);
|
|
93
|
+
if (!label.ok || !blurb.ok || !returns.ok) return undefined;
|
|
94
|
+
|
|
95
|
+
const keys = parseAnnotationKeys(raw.keys);
|
|
96
|
+
if (keys === null) return undefined;
|
|
97
|
+
const caveats = parseCaveatList(raw.caveats);
|
|
98
|
+
if (caveats === null) return undefined;
|
|
99
|
+
|
|
100
|
+
// `sample` is any JSON — including `null` — so presence is tracked, not typed.
|
|
101
|
+
return {
|
|
102
|
+
language: 'jq',
|
|
103
|
+
label: label.value,
|
|
104
|
+
blurb: blurb.value,
|
|
105
|
+
keys,
|
|
106
|
+
returns: returns.value,
|
|
107
|
+
caveats,
|
|
108
|
+
hasSample: 'sample' in raw,
|
|
109
|
+
sample: raw.sample,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
@@ -26,7 +26,7 @@ import { Field } from '../components/field';
|
|
|
26
26
|
import { AlertTriangleIcon } from '../components/icons';
|
|
27
27
|
import { TextInput, Textarea } from '../components/inputs';
|
|
28
28
|
import { errorMessage } from '../errors';
|
|
29
|
-
import type { ExpressionAnnotation, MediaUpload } from './
|
|
29
|
+
import type { ExpressionAnnotation, MediaUpload } from './field-model';
|
|
30
30
|
import type { ExpressionFieldComponent, ExpressionInputShape } from './context';
|
|
31
31
|
import { CompletionProviderContext, ExpressionFieldContext } from './context';
|
|
32
32
|
import { MediaField } from './media-field';
|
|
@@ -11,7 +11,8 @@ import type { ReactNode } from 'react';
|
|
|
11
11
|
|
|
12
12
|
import { Field } from '../components/field';
|
|
13
13
|
import { Select } from '../components/select';
|
|
14
|
-
import { classifySchema
|
|
14
|
+
import { classifySchema } from './classify';
|
|
15
|
+
import type { FieldModel, UnionVariant } from './field-model';
|
|
15
16
|
import { skeletonValueForSchema } from './default-value';
|
|
16
17
|
import { FieldGroup } from './field-group';
|
|
17
18
|
import { FieldNode } from './field-node';
|
package/src/schema-form/union.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* active (an unset/`null` value, or nothing matches).
|
|
7
7
|
*/
|
|
8
8
|
import { resolveRef } from './resolve';
|
|
9
|
-
import type { UnionVariant } from './
|
|
9
|
+
import type { UnionVariant } from './field-model';
|
|
10
10
|
import type { JsonSchema } from './types';
|
|
11
11
|
|
|
12
12
|
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* malformed value — a malformed value IS the error it reports.
|
|
7
7
|
*/
|
|
8
8
|
import { classifySchema } from './classify';
|
|
9
|
+
import type { FieldModel } from './field-model';
|
|
10
|
+
import { isRecord } from '../guards';
|
|
9
11
|
import { decodedByteSize, effectiveMaxBytes, overCapMessage } from './media';
|
|
10
12
|
import { scalarLabel } from './resolve';
|
|
11
13
|
import { activeVariantIndex } from './union';
|
|
@@ -21,14 +23,20 @@ export interface ValidateOptions {
|
|
|
21
23
|
readonly maxUploadBytes?: number;
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
/** The invariants every per-node validator shares while walking one value tree. */
|
|
27
|
+
interface ValidateCtx {
|
|
28
|
+
/** The document root, for `$ref` resolution during re-classification. */
|
|
29
|
+
readonly root: JsonSchema;
|
|
30
|
+
/** The path-keyed error bag every validator writes into. */
|
|
31
|
+
readonly errors: Record<string, string>;
|
|
32
|
+
/** The host's default media byte cap (a field's own cap still overrides it). */
|
|
33
|
+
readonly maxUploadBytes: number | undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
24
36
|
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
25
37
|
const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
26
38
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
27
39
|
|
|
28
|
-
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
29
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
40
|
function joinPath(path: string, key: string): string {
|
|
33
41
|
return path === '' ? key : `${path}.${key}`;
|
|
34
42
|
}
|
|
@@ -58,127 +66,200 @@ function formatError(format: string, value: string): string | undefined {
|
|
|
58
66
|
}
|
|
59
67
|
}
|
|
60
68
|
|
|
61
|
-
|
|
62
|
-
|
|
69
|
+
type ModelOf<K extends FieldModel['kind']> = Extract<FieldModel, { kind: K }>;
|
|
70
|
+
|
|
71
|
+
// A free-form JSON field: any JSON is accepted, subject only to the container the
|
|
72
|
+
// schema commits to (mirrors the renderer's inline check).
|
|
73
|
+
function validateJson(
|
|
74
|
+
model: ModelOf<'json'>,
|
|
75
|
+
value: unknown,
|
|
76
|
+
path: string,
|
|
77
|
+
ctx: ValidateCtx,
|
|
78
|
+
): void {
|
|
79
|
+
if (model.jsonType === 'object' && !isRecord(value)) {
|
|
80
|
+
ctx.errors[path] = 'must be a JSON object';
|
|
81
|
+
} else if (model.jsonType === 'array' && !Array.isArray(value)) {
|
|
82
|
+
ctx.errors[path] = 'must be a JSON array';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function validateConst(
|
|
87
|
+
model: ModelOf<'const'>,
|
|
88
|
+
value: unknown,
|
|
89
|
+
path: string,
|
|
90
|
+
ctx: ValidateCtx,
|
|
91
|
+
): void {
|
|
92
|
+
if (value !== model.value) ctx.errors[path] = `must equal ${scalarLabel(model.value)}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function validateEnum(
|
|
96
|
+
model: ModelOf<'enum'>,
|
|
97
|
+
value: unknown,
|
|
98
|
+
path: string,
|
|
99
|
+
ctx: ValidateCtx,
|
|
100
|
+
): void {
|
|
101
|
+
const allowed = model.options.some((option) => option.value === value);
|
|
102
|
+
if (!allowed) ctx.errors[path] = 'must be one of the allowed values';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function validateString(
|
|
106
|
+
model: ModelOf<'string'>,
|
|
107
|
+
value: unknown,
|
|
108
|
+
path: string,
|
|
109
|
+
ctx: ValidateCtx,
|
|
110
|
+
): void {
|
|
111
|
+
if (typeof value !== 'string') {
|
|
112
|
+
ctx.errors[path] = 'must be a string';
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// A media-annotated string carries a byte cap; enforce it on the value's
|
|
116
|
+
// DECODED size so a value that arrived by any route is caught here too. The
|
|
117
|
+
// precedence matches the renderer: field `contentMaxBytes` → host cap →
|
|
118
|
+
// shared default.
|
|
119
|
+
if (model.media !== undefined) {
|
|
120
|
+
const cap = effectiveMaxBytes(model.media.maxBytes, ctx.maxUploadBytes);
|
|
121
|
+
const size = decodedByteSize(value);
|
|
122
|
+
if (size > cap) {
|
|
123
|
+
ctx.errors[path] = overCapMessage('The value', size, cap);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (value.length > 0 && model.format !== undefined) {
|
|
128
|
+
const message = formatError(model.format, value);
|
|
129
|
+
if (message !== undefined) ctx.errors[path] = message;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function validateNumber(
|
|
134
|
+
model: ModelOf<'number'>,
|
|
63
135
|
value: unknown,
|
|
64
136
|
path: string,
|
|
65
|
-
|
|
66
|
-
errors: Record<string, string>,
|
|
67
|
-
maxUploadBytes: number | undefined,
|
|
137
|
+
ctx: ValidateCtx,
|
|
68
138
|
): void {
|
|
69
|
-
|
|
139
|
+
if (typeof value !== 'number' || Number.isNaN(value)) {
|
|
140
|
+
ctx.errors[path] = model.integer ? 'must be an integer' : 'must be a number';
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (model.integer && !Number.isInteger(value)) ctx.errors[path] = 'must be an integer';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function validateBoolean(value: unknown, path: string, ctx: ValidateCtx): void {
|
|
147
|
+
if (typeof value !== 'boolean') ctx.errors[path] = 'must be true or false';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function validateArray(
|
|
151
|
+
model: ModelOf<'array'>,
|
|
152
|
+
value: unknown,
|
|
153
|
+
path: string,
|
|
154
|
+
ctx: ValidateCtx,
|
|
155
|
+
): void {
|
|
156
|
+
if (!Array.isArray(value)) {
|
|
157
|
+
ctx.errors[path] = 'must be an array';
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
value.forEach((item, index) => {
|
|
161
|
+
walk(model.items, item, `${path}[${String(index)}]`, ctx);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function validateObject(
|
|
166
|
+
model: ModelOf<'object'>,
|
|
167
|
+
value: unknown,
|
|
168
|
+
path: string,
|
|
169
|
+
ctx: ValidateCtx,
|
|
170
|
+
): void {
|
|
171
|
+
if (!isRecord(value)) {
|
|
172
|
+
ctx.errors[path] = 'must be an object';
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const propMap = new Map(model.properties);
|
|
176
|
+
for (const [name, propSchema] of model.properties) {
|
|
177
|
+
const childPath = joinPath(path, name);
|
|
178
|
+
const childValue = value[name];
|
|
179
|
+
if (childValue === undefined) {
|
|
180
|
+
if (model.required.has(name)) ctx.errors[childPath] = `"${name}" is required`;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
walk(propSchema, childValue, childPath, ctx);
|
|
184
|
+
}
|
|
185
|
+
// Required keys naming a property that somehow isn't declared still count.
|
|
186
|
+
for (const name of model.required) {
|
|
187
|
+
if (!propMap.has(name) && value[name] === undefined) {
|
|
188
|
+
ctx.errors[joinPath(path, name)] = `"${name}" is required`;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function validateRecord(
|
|
194
|
+
model: ModelOf<'record'>,
|
|
195
|
+
value: unknown,
|
|
196
|
+
path: string,
|
|
197
|
+
ctx: ValidateCtx,
|
|
198
|
+
): void {
|
|
199
|
+
if (!isRecord(value)) {
|
|
200
|
+
ctx.errors[path] = 'must be an object';
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
for (const [key, entryValue] of Object.entries(value)) {
|
|
204
|
+
walk(model.values, entryValue, joinPath(path, key), ctx);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function validateUnion(
|
|
209
|
+
model: ModelOf<'union'>,
|
|
210
|
+
value: unknown,
|
|
211
|
+
path: string,
|
|
212
|
+
ctx: ValidateCtx,
|
|
213
|
+
): void {
|
|
214
|
+
const index = activeVariantIndex(value, model.variants, model.discriminator, ctx.root);
|
|
215
|
+
const variant = index === -1 ? undefined : model.variants[index];
|
|
216
|
+
if (variant === undefined) {
|
|
217
|
+
ctx.errors[path] = 'does not match any allowed variant';
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
walk(variant.schema, value, path, ctx);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function walk(schema: JsonSchema, value: unknown, path: string, ctx: ValidateCtx): void {
|
|
224
|
+
const classified = classifySchema(schema, ctx.root);
|
|
70
225
|
|
|
71
226
|
if (value === null) {
|
|
72
|
-
if (!classified.nullable) errors[path] = 'must not be null';
|
|
227
|
+
if (!classified.nullable) ctx.errors[path] = 'must not be null';
|
|
73
228
|
return;
|
|
74
229
|
}
|
|
75
230
|
|
|
76
231
|
const { model } = classified;
|
|
77
232
|
switch (model.kind) {
|
|
78
233
|
case 'json':
|
|
79
|
-
|
|
80
|
-
// container the schema commits to (mirrors the renderer's inline check).
|
|
81
|
-
if (model.jsonType === 'object' && !isPlainObject(value)) {
|
|
82
|
-
errors[path] = 'must be a JSON object';
|
|
83
|
-
} else if (model.jsonType === 'array' && !Array.isArray(value)) {
|
|
84
|
-
errors[path] = 'must be a JSON array';
|
|
85
|
-
}
|
|
234
|
+
validateJson(model, value, path, ctx);
|
|
86
235
|
return;
|
|
87
236
|
case 'const':
|
|
88
|
-
|
|
237
|
+
validateConst(model, value, path, ctx);
|
|
89
238
|
return;
|
|
90
|
-
case 'enum':
|
|
91
|
-
|
|
92
|
-
if (!allowed) errors[path] = 'must be one of the allowed values';
|
|
239
|
+
case 'enum':
|
|
240
|
+
validateEnum(model, value, path, ctx);
|
|
93
241
|
return;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (typeof value !== 'string') {
|
|
97
|
-
errors[path] = 'must be a string';
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
// A media-annotated string carries a byte cap; enforce it on the value's
|
|
101
|
-
// DECODED size so a value that arrived by any route is caught here too. The
|
|
102
|
-
// precedence matches the renderer: field `contentMaxBytes` → host cap →
|
|
103
|
-
// shared default.
|
|
104
|
-
if (model.media !== undefined) {
|
|
105
|
-
const cap = effectiveMaxBytes(model.media.maxBytes, maxUploadBytes);
|
|
106
|
-
const size = decodedByteSize(value);
|
|
107
|
-
if (size > cap) {
|
|
108
|
-
errors[path] = overCapMessage('The value', size, cap);
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
if (value.length > 0 && model.format !== undefined) {
|
|
113
|
-
const message = formatError(model.format, value);
|
|
114
|
-
if (message !== undefined) errors[path] = message;
|
|
115
|
-
}
|
|
242
|
+
case 'string':
|
|
243
|
+
validateString(model, value, path, ctx);
|
|
116
244
|
return;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (typeof value !== 'number' || Number.isNaN(value)) {
|
|
120
|
-
errors[path] = model.integer ? 'must be an integer' : 'must be a number';
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
if (model.integer && !Number.isInteger(value)) errors[path] = 'must be an integer';
|
|
245
|
+
case 'number':
|
|
246
|
+
validateNumber(model, value, path, ctx);
|
|
124
247
|
return;
|
|
125
|
-
}
|
|
126
248
|
case 'boolean':
|
|
127
|
-
|
|
249
|
+
validateBoolean(value, path, ctx);
|
|
128
250
|
return;
|
|
129
|
-
case 'array':
|
|
130
|
-
|
|
131
|
-
errors[path] = 'must be an array';
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
value.forEach((item, index) => {
|
|
135
|
-
walk(model.items, item, `${path}[${String(index)}]`, root, errors, maxUploadBytes);
|
|
136
|
-
});
|
|
251
|
+
case 'array':
|
|
252
|
+
validateArray(model, value, path, ctx);
|
|
137
253
|
return;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (!isPlainObject(value)) {
|
|
141
|
-
errors[path] = 'must be an object';
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
const propMap = new Map(model.properties);
|
|
145
|
-
for (const [name, propSchema] of model.properties) {
|
|
146
|
-
const childPath = joinPath(path, name);
|
|
147
|
-
const childValue = value[name];
|
|
148
|
-
if (childValue === undefined) {
|
|
149
|
-
if (model.required.has(name)) errors[childPath] = `"${name}" is required`;
|
|
150
|
-
continue;
|
|
151
|
-
}
|
|
152
|
-
walk(propSchema, childValue, childPath, root, errors, maxUploadBytes);
|
|
153
|
-
}
|
|
154
|
-
// Required keys naming a property that somehow isn't declared still count.
|
|
155
|
-
for (const name of model.required) {
|
|
156
|
-
if (!propMap.has(name) && value[name] === undefined) {
|
|
157
|
-
errors[joinPath(path, name)] = `"${name}" is required`;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
254
|
+
case 'object':
|
|
255
|
+
validateObject(model, value, path, ctx);
|
|
160
256
|
return;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
if (!isPlainObject(value)) {
|
|
164
|
-
errors[path] = 'must be an object';
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
for (const [key, entryValue] of Object.entries(value)) {
|
|
168
|
-
walk(model.values, entryValue, joinPath(path, key), root, errors, maxUploadBytes);
|
|
169
|
-
}
|
|
257
|
+
case 'record':
|
|
258
|
+
validateRecord(model, value, path, ctx);
|
|
170
259
|
return;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const index = activeVariantIndex(value, model.variants, model.discriminator, root);
|
|
174
|
-
const variant = index === -1 ? undefined : model.variants[index];
|
|
175
|
-
if (variant === undefined) {
|
|
176
|
-
errors[path] = 'does not match any allowed variant';
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
walk(variant.schema, value, path, root, errors, maxUploadBytes);
|
|
260
|
+
case 'union':
|
|
261
|
+
validateUnion(model, value, path, ctx);
|
|
180
262
|
return;
|
|
181
|
-
}
|
|
182
263
|
}
|
|
183
264
|
}
|
|
184
265
|
|
|
@@ -194,6 +275,6 @@ export function validateAgainstSchema(
|
|
|
194
275
|
options?: ValidateOptions,
|
|
195
276
|
): SchemaFormErrors {
|
|
196
277
|
const errors: Record<string, string> = {};
|
|
197
|
-
walk(schema, value, '', schema, errors, options?.maxUploadBytes);
|
|
278
|
+
walk(schema, value, '', { root: schema, errors, maxUploadBytes: options?.maxUploadBytes });
|
|
198
279
|
return errors;
|
|
199
280
|
}
|
|
Binary file
|