@workbench-kit/field-remap 0.0.1-prototype.0 → 0.0.2-prototype.0.2.10

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.
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Project source/target shape trees for Flow / browse with optional `hidden` filtering.
3
+ */
4
+
5
+ import type { MappingEdge, SourceField, TargetSlot } from '../types.js';
6
+ import { pruneMappingEdgesForShapes } from './shapeEdit.js';
7
+
8
+ export type ProjectShapesOptions = {
9
+ /**
10
+ * When `false` (default), omit fields/slots with `hidden: true` (and prune empty
11
+ * child lists). When `true`, keep hidden nodes for browse / authoring overlays.
12
+ */
13
+ readonly includeHidden?: boolean;
14
+ };
15
+
16
+ function projectSourceNode(field: SourceField, includeHidden: boolean): SourceField | null {
17
+ if (!includeHidden && field.hidden === true) {
18
+ return null;
19
+ }
20
+ if (!field.children?.length) {
21
+ return field;
22
+ }
23
+ const children = projectSourceFields(field.children, { includeHidden });
24
+ if (children === field.children) {
25
+ return field;
26
+ }
27
+ return children.length > 0 ? { ...field, children } : { ...field, children: undefined };
28
+ }
29
+
30
+ function projectTargetNode(slot: TargetSlot, includeHidden: boolean): TargetSlot | null {
31
+ if (!includeHidden && slot.hidden === true) {
32
+ return null;
33
+ }
34
+ if (!slot.children?.length) {
35
+ return slot;
36
+ }
37
+ const children = projectTargetSlots(slot.children, { includeHidden });
38
+ if (children === slot.children) {
39
+ return slot;
40
+ }
41
+ return children.length > 0 ? { ...slot, children } : { ...slot, children: undefined };
42
+ }
43
+
44
+ /** Filter a source field tree by `hidden` (default: omit hidden leaves/branches). */
45
+ export function projectSourceFields(
46
+ fields: readonly SourceField[],
47
+ options?: ProjectShapesOptions,
48
+ ): readonly SourceField[] {
49
+ const includeHidden = options?.includeHidden === true;
50
+ const next: SourceField[] = [];
51
+ let changed = false;
52
+ for (const field of fields) {
53
+ const projected = projectSourceNode(field, includeHidden);
54
+ if (projected === null) {
55
+ changed = true;
56
+ continue;
57
+ }
58
+ if (projected !== field) {
59
+ changed = true;
60
+ }
61
+ next.push(projected);
62
+ }
63
+ return changed ? next : fields;
64
+ }
65
+
66
+ /** Filter a target slot tree by `hidden` (default: omit hidden leaves/branches). */
67
+ export function projectTargetSlots(
68
+ slots: readonly TargetSlot[],
69
+ options?: ProjectShapesOptions,
70
+ ): readonly TargetSlot[] {
71
+ const includeHidden = options?.includeHidden === true;
72
+ const next: TargetSlot[] = [];
73
+ let changed = false;
74
+ for (const slot of slots) {
75
+ const projected = projectTargetNode(slot, includeHidden);
76
+ if (projected === null) {
77
+ changed = true;
78
+ continue;
79
+ }
80
+ if (projected !== slot) {
81
+ changed = true;
82
+ }
83
+ next.push(projected);
84
+ }
85
+ return changed ? next : slots;
86
+ }
87
+
88
+ export type ProjectShapesInput = {
89
+ readonly sources: readonly SourceField[];
90
+ readonly targets: readonly TargetSlot[];
91
+ readonly edges?: readonly MappingEdge[];
92
+ readonly options?: ProjectShapesOptions;
93
+ };
94
+
95
+ export type ProjectShapesResult = {
96
+ readonly sources: readonly SourceField[];
97
+ readonly targets: readonly TargetSlot[];
98
+ readonly edges?: readonly MappingEdge[];
99
+ };
100
+
101
+ /**
102
+ * Project sources/targets with `includeHidden`, and optionally prune mapping edges
103
+ * whose endpoints disappeared from the projected id set.
104
+ */
105
+ export function projectShapes(input: ProjectShapesInput): ProjectShapesResult {
106
+ const sources = projectSourceFields(input.sources, input.options);
107
+ const targets = projectTargetSlots(input.targets, input.options);
108
+ if (input.edges === undefined) {
109
+ return { sources, targets };
110
+ }
111
+ return {
112
+ sources,
113
+ targets,
114
+ edges: pruneMappingEdgesForShapes(input.edges, sources, targets),
115
+ };
116
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Host-owned shape editing helpers: type patches and edge pruning when ids disappear.
3
+ */
4
+
5
+ import { flattenSourceFields, flattenTargetSlots } from '../mapping/treeUtils.js';
6
+ import type { FieldDataType, MappingEdge, SourceField, TargetSlot } from '../types.js';
7
+
8
+ export const FIELD_DATA_TYPES = [
9
+ 'string',
10
+ 'number',
11
+ 'boolean',
12
+ 'date',
13
+ 'time',
14
+ 'datetime',
15
+ 'object',
16
+ 'array',
17
+ 'unknown',
18
+ ] as const satisfies readonly FieldDataType[];
19
+
20
+ export function isFieldDataType(value: unknown): value is FieldDataType {
21
+ return typeof value === 'string' && (FIELD_DATA_TYPES as readonly string[]).includes(value);
22
+ }
23
+
24
+ export function collectSourceFieldIds(fields: readonly SourceField[]): ReadonlySet<string> {
25
+ return new Set(flattenSourceFields(fields).map((field) => field.id));
26
+ }
27
+
28
+ export function collectTargetSlotIds(slots: readonly TargetSlot[]): ReadonlySet<string> {
29
+ return new Set(flattenTargetSlots(slots).map((slot) => slot.id));
30
+ }
31
+
32
+ function pruneEdgeList(
33
+ edges: readonly MappingEdge[],
34
+ sourceIds: ReadonlySet<string>,
35
+ targetIds: ReadonlySet<string>,
36
+ ): MappingEdge[] {
37
+ const next: MappingEdge[] = [];
38
+ for (const edge of edges) {
39
+ if (!sourceIds.has(edge.sourceFieldId) || !targetIds.has(edge.targetSlotId)) {
40
+ continue;
41
+ }
42
+ if (!edge.itemEdges?.length) {
43
+ next.push(edge);
44
+ continue;
45
+ }
46
+ const itemEdges = pruneEdgeList(edge.itemEdges, sourceIds, targetIds);
47
+ next.push(itemEdges.length === edge.itemEdges.length ? edge : { ...edge, itemEdges });
48
+ }
49
+ return next;
50
+ }
51
+
52
+ /**
53
+ * Drop (or trim nested `itemEdges` of) bindings whose source/target ids are gone.
54
+ * Hosts should call this after shape ingest / structural edits.
55
+ */
56
+ export function pruneMappingEdgesForShapes(
57
+ edges: readonly MappingEdge[],
58
+ sources: readonly SourceField[],
59
+ targets: readonly TargetSlot[],
60
+ ): readonly MappingEdge[] {
61
+ return pruneEdgeList(edges, collectSourceFieldIds(sources), collectTargetSlotIds(targets));
62
+ }
63
+
64
+ function mapSourceTree(
65
+ fields: readonly SourceField[],
66
+ fieldId: string,
67
+ mapper: (field: SourceField) => SourceField,
68
+ ): SourceField[] {
69
+ return fields.map((field) => {
70
+ if (field.id === fieldId) {
71
+ return mapper(field);
72
+ }
73
+ if (!field.children?.length) {
74
+ return field;
75
+ }
76
+ const children = mapSourceTree(field.children, fieldId, mapper);
77
+ return children === field.children ? field : { ...field, children };
78
+ });
79
+ }
80
+
81
+ function mapTargetTree(
82
+ slots: readonly TargetSlot[],
83
+ slotId: string,
84
+ mapper: (slot: TargetSlot) => TargetSlot,
85
+ ): TargetSlot[] {
86
+ return slots.map((slot) => {
87
+ if (slot.id === slotId) {
88
+ return mapper(slot);
89
+ }
90
+ if (!slot.children?.length) {
91
+ return slot;
92
+ }
93
+ const children = mapTargetTree(slot.children, slotId, mapper);
94
+ return children === slot.children ? slot : { ...slot, children };
95
+ });
96
+ }
97
+
98
+ /** Patch `dataType` on a source field id (nested-aware). */
99
+ export function setSourceFieldDataType(
100
+ fields: readonly SourceField[],
101
+ fieldId: string,
102
+ dataType: FieldDataType,
103
+ ): readonly SourceField[] {
104
+ return mapSourceTree(fields, fieldId, (field) => ({ ...field, dataType }));
105
+ }
106
+
107
+ /** Patch `dataType` on a target slot id (nested-aware). */
108
+ export function setTargetSlotDataType(
109
+ slots: readonly TargetSlot[],
110
+ slotId: string,
111
+ dataType: FieldDataType,
112
+ ): readonly TargetSlot[] {
113
+ return mapTargetTree(slots, slotId, (slot) => ({ ...slot, dataType }));
114
+ }
@@ -1,182 +1,242 @@
1
- /**
2
- * Shared field / slot / edge types for field-remap UIs.
3
- */
4
-
5
- export type FieldDataType =
6
- | 'string'
7
- | 'number'
8
- | 'boolean'
9
- | 'date'
10
- | 'time'
11
- | 'datetime'
12
- /**
13
- * Plain object (record) — e.g. for `string:template` object → string.
14
- */
15
- | 'object'
16
- /**
17
- * Collection hint — badge + array→array wiring.
18
- * Optional per-item projection via `MappingEdge.itemSourcePath`.
19
- */
20
- | 'array'
21
- | 'unknown';
22
-
23
- export interface SourceField {
24
- readonly id: string;
25
- readonly label: string;
26
- /** Dot-path or capability path hint for hosts. */
27
- readonly path?: string;
28
- /**
29
- * Owning managed shape id when fields are merged from multiple source shapes.
30
- * Used by `convertToShape` to pick the correct named input bag.
31
- */
32
- readonly shapeId?: string;
33
- readonly dataType?: FieldDataType;
34
- /** Optional sample used by live preview / transform labels. */
35
- readonly sampleValue?: unknown;
36
- readonly group?: string;
37
- readonly children?: readonly SourceField[];
38
- }
39
-
40
- export interface TargetSlot {
41
- readonly id: string;
42
- readonly label: string;
43
- /**
44
- * Dot-path used when assembling nested JSON output via `convertToShape`.
45
- * When omitted, hosts may fall back to slot id / label.
46
- */
47
- readonly path?: string;
48
- readonly dataType?: FieldDataType;
49
- readonly required?: boolean;
50
- readonly description?: string;
51
- /** Nested slot groups (expand/collapse in the mapper tree). */
52
- readonly children?: readonly TargetSlot[];
53
- }
54
-
55
- export interface MappingEdge {
56
- readonly id: string;
57
- readonly sourceFieldId: string;
58
- readonly targetSlotId: string;
59
- /**
60
- * Ordered transform chain (max 3). Empty / omitted means identity.
61
- * Prefer this over `transformId` for new writers.
62
- * For arrays: use reduce builtins (`array:join`, `array:first`, …) here after
63
- * optional item projection / item transforms.
64
- */
65
- readonly transformIds?: readonly string[];
66
- /**
67
- * Legacy single transform. Prefer `transformIds`.
68
- * Readers should use `edgeTransformIds` / `normalizeMappingEdge`.
69
- * `null` / omitted (with no `transformIds`) means identity (pass-through).
70
- */
71
- readonly transformId?: string | null;
72
- /**
73
- * Per-step options aligned with `transformIds` (index N applies to step N).
74
- * Prefer this when steps need different bags (e.g. `showSeconds` then `maxLength`).
75
- */
76
- readonly transformOptionSteps?: readonly (Readonly<Record<string, unknown>> | undefined)[];
77
- /**
78
- * Shared options for all `transformIds` steps (legacy / apply-to-all).
79
- * Used when `transformOptionSteps` is omitted. Still written as a back-compat
80
- * summary of step 0 (or the first non-empty step) by `normalizeMappingEdge`.
81
- */
82
- readonly transformOptions?: Readonly<Record<string, unknown>>;
83
- /**
84
- * When the source is an array of objects, optional dotted path into each item
85
- * (e.g. `name` or `meta.label`) before the value is written to the target.
86
- * Omit / empty for whole-array pass-through (or reduce on the full array).
87
- */
88
- readonly itemSourcePath?: string;
89
- /**
90
- * Ordered per-item transform chain (max 3) applied after `itemSourcePath`
91
- * projection (or to each element when projecting is a no-op). Empty / omitted
92
- * means identity per item. Independent of `transformIds` (which run on the
93
- * whole collection afterward — e.g. reduce).
94
- */
95
- readonly itemTransformIds?: readonly string[];
96
- /**
97
- * Per-step options aligned with `itemTransformIds`.
98
- */
99
- readonly itemTransformOptionSteps?: readonly (Readonly<Record<string, unknown>> | undefined)[];
100
- /**
101
- * Shared options for all `itemTransformIds` steps (legacy / apply-to-all).
102
- * Independent of `transformOptions` / `transformOptionSteps`.
103
- */
104
- readonly itemTransformOptions?: Readonly<Record<string, unknown>>;
105
- /**
106
- * List-context child bindings (Stedi-style): when source is an array of objects,
107
- * each element is converted through these edges into a target item object.
108
- * Child `sourceFieldId` / `targetSlotId` should resolve to item-schema fields
109
- * (ingest ids like `a.tags.item.name`) whose `path` is item-relative.
110
- *
111
- * Takes precedence over `itemSourcePath` / `itemTransformIds` for the outer edge.
112
- * Nested `itemEdges` on children are ignored (one collection level per edge).
113
- */
114
- readonly itemEdges?: readonly MappingEdge[];
115
- }
116
-
117
- /**
118
- * Minimal JSON-serializable mapping document for host persistence.
119
- * Hosts own schema trees; this document stores the binding graph only.
120
- */
121
- export interface FieldRemapDocument {
122
- readonly version: 1;
123
- readonly edges: readonly MappingEdge[];
124
- }
125
-
126
- export interface TransformContext {
127
- readonly locale?: string;
128
- /** Reference instant for time/date presets; defaults to `new Date()`. */
129
- readonly now?: Date;
130
- readonly sampleValue?: unknown;
131
- /**
132
- * Optional plain object for `string:template` placeholder resolution when the
133
- * transform input is not itself an object (hosts / demos).
134
- */
135
- readonly record?: Readonly<Record<string, unknown>>;
136
- readonly options?: Readonly<Record<string, unknown>>;
137
- }
138
-
139
- /** Declares a host-editable option consumed via `context.options[key]`. */
140
- export interface TransformOptionField {
141
- readonly key: string;
142
- readonly label: string;
143
- /**
144
- * - `string` / `number` / `boolean` — single scalar inputs
145
- * - `stringMap` — key/value row editor for string→string maps (e.g. `codeLabels`),
146
- * with an optional List / JSON view toggle in `TransformOptionsEditor`
147
- * - `json` — validated JSON textarea for plain objects (advanced / free-form);
148
- * drafts commit when parseable and pretty-print on blur
149
- */
150
- readonly kind: 'string' | 'number' | 'boolean' | 'stringMap' | 'json';
151
- }
152
-
153
- export interface ValueTransformDefinition {
154
- readonly id: string;
155
- readonly label: string;
156
- readonly description?: string;
157
- readonly category?: string;
158
- readonly inputTypes?: readonly FieldDataType[];
159
- readonly outputType?: FieldDataType;
160
- readonly apply: (value: unknown, context: TransformContext) => unknown;
161
- /** Optional picker label that includes a live format sample. */
162
- readonly formatSampleLabel?: (context: TransformContext) => string;
163
- /**
164
- * Data-driven option editors (mapped rows / convert panel).
165
- * Values are stored per step (`transformOptionSteps` / `itemTransformOptionSteps`)
166
- * or as a shared bag (`transformOptions` / `itemTransformOptions`), or on host
167
- * `TransformContext.options`.
168
- */
169
- readonly optionFields?: readonly TransformOptionField[];
170
- }
171
-
172
- export interface ValueTransformListFilter {
173
- readonly inputType?: FieldDataType;
174
- readonly category?: string;
175
- }
176
-
177
- export interface ValueTransformRegistry {
178
- list(filter?: ValueTransformListFilter): ValueTransformDefinition[];
179
- get(id: string): ValueTransformDefinition | undefined;
180
- apply(id: string, value: unknown, context?: TransformContext): unknown;
181
- register(definition: ValueTransformDefinition): void;
182
- }
1
+ /**
2
+ * Shared field / slot / edge types for field-remap UIs.
3
+ */
4
+
5
+ export type FieldDataType =
6
+ | 'string'
7
+ | 'number'
8
+ | 'boolean'
9
+ | 'date'
10
+ | 'time'
11
+ | 'datetime'
12
+ /**
13
+ * Plain object (record) — e.g. for `string:template` object → string.
14
+ */
15
+ | 'object'
16
+ /**
17
+ * Collection hint — badge + array→array wiring.
18
+ * Optional per-item projection via `MappingEdge.itemSourcePath`.
19
+ */
20
+ | 'array'
21
+ | 'unknown';
22
+
23
+ /**
24
+ * Stable class identity for a port/field whose nested `children` describe that class.
25
+ * Hosts own the class registry vocabulary; kit only carries the ref for browse / Flow.
26
+ */
27
+ export type ClassRef = {
28
+ readonly id: string;
29
+ readonly version: number;
30
+ };
31
+
32
+ export interface SourceField {
33
+ readonly id: string;
34
+ readonly label: string;
35
+ /** Dot-path or capability path hint for hosts. */
36
+ readonly path?: string;
37
+ /**
38
+ * Owning managed shape id when fields are merged from multiple source shapes.
39
+ * Used by `convertToShape` to pick the correct named input bag.
40
+ */
41
+ readonly shapeId?: string;
42
+ readonly dataType?: FieldDataType;
43
+ /** Optional sample used by live preview / transform labels. */
44
+ readonly sampleValue?: unknown;
45
+ readonly group?: string;
46
+ /** When set with nested `children`, marks a class-typed object shape. */
47
+ readonly classRef?: ClassRef;
48
+ /**
49
+ * When `true`, omit from default Flow / mapper projections unless
50
+ * `projectShapes({ includeHidden: true })` (or browse with show-hidden).
51
+ */
52
+ readonly hidden?: boolean;
53
+ readonly children?: readonly SourceField[];
54
+ }
55
+
56
+ export interface TargetSlot {
57
+ readonly id: string;
58
+ readonly label: string;
59
+ /**
60
+ * Dot-path used when assembling nested JSON output via `convertToShape`.
61
+ * When omitted, hosts may fall back to slot id / label.
62
+ */
63
+ readonly path?: string;
64
+ readonly dataType?: FieldDataType;
65
+ readonly required?: boolean;
66
+ readonly description?: string;
67
+ /** When set with nested `children`, marks a class-typed object shape. */
68
+ readonly classRef?: ClassRef;
69
+ /**
70
+ * When `true`, omit from default Flow / mapper projections unless
71
+ * `projectShapes({ includeHidden: true })` (or browse with show-hidden).
72
+ */
73
+ readonly hidden?: boolean;
74
+ /** Nested slot groups (expand/collapse in the mapper tree). */
75
+ readonly children?: readonly TargetSlot[];
76
+ }
77
+
78
+ export interface MappingEdge {
79
+ readonly id: string;
80
+ readonly sourceFieldId: string;
81
+ readonly targetSlotId: string;
82
+ /**
83
+ * Ordered transform chain (max 3). Empty / omitted means identity.
84
+ * Prefer this over `transformId` for new writers.
85
+ * For arrays: use reduce builtins (`array:join`, `array:first`, …) here after
86
+ * optional item projection / item transforms.
87
+ */
88
+ readonly transformIds?: readonly string[];
89
+ /**
90
+ * Legacy single transform. Prefer `transformIds`.
91
+ * Readers should use `edgeTransformIds` / `normalizeMappingEdge`.
92
+ * `null` / omitted (with no `transformIds`) means identity (pass-through).
93
+ */
94
+ readonly transformId?: string | null;
95
+ /**
96
+ * Per-step options aligned with `transformIds` (index N applies to step N).
97
+ * Prefer this when steps need different bags (e.g. `showSeconds` then `maxLength`).
98
+ */
99
+ readonly transformOptionSteps?: readonly (Readonly<Record<string, unknown>> | undefined)[];
100
+ /**
101
+ * Shared options for all `transformIds` steps (legacy / apply-to-all).
102
+ * Used when `transformOptionSteps` is omitted. Still written as a back-compat
103
+ * summary of step 0 (or the first non-empty step) by `normalizeMappingEdge`.
104
+ */
105
+ readonly transformOptions?: Readonly<Record<string, unknown>>;
106
+ /**
107
+ * When the source is an array of objects, optional dotted path into each item
108
+ * (e.g. `name` or `meta.label`) before the value is written to the target.
109
+ * Omit / empty for whole-array pass-through (or reduce on the full array).
110
+ */
111
+ readonly itemSourcePath?: string;
112
+ /**
113
+ * Ordered per-item transform chain (max 3) applied after `itemSourcePath`
114
+ * projection (or to each element when projecting is a no-op). Empty / omitted
115
+ * means identity per item. Independent of `transformIds` (which run on the
116
+ * whole collection afterward — e.g. reduce).
117
+ */
118
+ readonly itemTransformIds?: readonly string[];
119
+ /**
120
+ * Per-step options aligned with `itemTransformIds`.
121
+ */
122
+ readonly itemTransformOptionSteps?: readonly (Readonly<Record<string, unknown>> | undefined)[];
123
+ /**
124
+ * Shared options for all `itemTransformIds` steps (legacy / apply-to-all).
125
+ * Independent of `transformOptions` / `transformOptionSteps`.
126
+ */
127
+ readonly itemTransformOptions?: Readonly<Record<string, unknown>>;
128
+ /**
129
+ * List-context child bindings (Stedi-style): when source is an array of objects,
130
+ * each element is converted through these edges into a target item object.
131
+ * Child `sourceFieldId` / `targetSlotId` should resolve to item-schema fields
132
+ * (ingest ids like `a.tags.item.name`) whose `path` is item-relative.
133
+ *
134
+ * Takes precedence over `itemSourcePath` / `itemTransformIds` for the outer edge.
135
+ * Nested `itemEdges` on children are ignored (one collection level per edge).
136
+ */
137
+ readonly itemEdges?: readonly MappingEdge[];
138
+ }
139
+
140
+ /** Fan-in: multiple source fields → one target slot. */
141
+ export interface CombineMappingOperator {
142
+ readonly kind: 'combine';
143
+ readonly id: string;
144
+ readonly inputFieldIds: readonly string[];
145
+ readonly outputSlotId: string;
146
+ /** Optional chain applied to the combined object bag (max 3 via registry). */
147
+ readonly transformIds?: readonly string[];
148
+ }
149
+
150
+ /** Fan-out: one source field multiple target slots. */
151
+ export interface SplitMappingOperator {
152
+ readonly kind: 'split';
153
+ readonly id: string;
154
+ readonly inputFieldId: string;
155
+ readonly outputSlotIds: readonly string[];
156
+ /** Optional chain applied to the source value before splitting an object. */
157
+ readonly transformIds?: readonly string[];
158
+ }
159
+
160
+ export type MappingOperator = CombineMappingOperator | SplitMappingOperator;
161
+
162
+ /**
163
+ * Minimal JSON-serializable mapping document for host persistence.
164
+ * Hosts own schema trees; this document stores the binding graph (and optional
165
+ * n→m operators from document v2 onward).
166
+ */
167
+ export interface FieldRemapDocument {
168
+ /** `1` = edges-only; `2` = edges + optional `operators[]`. */
169
+ readonly version: 1 | 2;
170
+ readonly edges: readonly MappingEdge[];
171
+ /**
172
+ * Optional n→m combine/split operators (document v2).
173
+ * Omitted / empty on v1 documents and on v2 hosts that only use 1→1 edges.
174
+ */
175
+ readonly operators?: readonly MappingOperator[];
176
+ }
177
+ export interface TransformContext {
178
+ readonly locale?: string;
179
+ /** Reference instant for time/date presets; defaults to `new Date()`. */
180
+ readonly now?: Date;
181
+ readonly sampleValue?: unknown;
182
+ /**
183
+ * Optional plain object for `string:template` placeholder resolution when the
184
+ * transform input is not itself an object (hosts / demos).
185
+ */
186
+ readonly record?: Readonly<Record<string, unknown>>;
187
+ readonly options?: Readonly<Record<string, unknown>>;
188
+ /**
189
+ * Optional cancellation signal. `applyTransformChain` / `convertToShape` check
190
+ * between steps and reject with `AbortError` when aborted.
191
+ */
192
+ readonly signal?: AbortSignal;
193
+ }
194
+
195
+ /** Declares a host-editable option consumed via `context.options[key]`. */
196
+ export interface TransformOptionField {
197
+ readonly key: string;
198
+ readonly label: string;
199
+ /**
200
+ * - `string` / `number` / `boolean` — single scalar inputs
201
+ * - `stringMap` — key/value row editor for string→string maps (e.g. `codeLabels`),
202
+ * with an optional List / JSON view toggle in `TransformOptionsEditor`
203
+ * - `json` — validated JSON textarea for plain objects (advanced / free-form);
204
+ * drafts commit when parseable and pretty-print on blur
205
+ */
206
+ readonly kind: 'string' | 'number' | 'boolean' | 'stringMap' | 'json';
207
+ }
208
+
209
+ export interface ValueTransformDefinition {
210
+ readonly id: string;
211
+ readonly label: string;
212
+ readonly description?: string;
213
+ readonly category?: string;
214
+ readonly inputTypes?: readonly FieldDataType[];
215
+ readonly outputType?: FieldDataType;
216
+ /**
217
+ * May return a Promise (e.g. host JSONata 2.x). Prefer `applyTransformChain` /
218
+ * `convertToShape`, which always await transform results.
219
+ */
220
+ readonly apply: (value: unknown, context: TransformContext) => unknown | PromiseLike<unknown>;
221
+ /** Optional picker label that includes a live format sample. */
222
+ readonly formatSampleLabel?: (context: TransformContext) => string;
223
+ /**
224
+ * Data-driven option editors (mapped rows / convert panel).
225
+ * Values are stored per step (`transformOptionSteps` / `itemTransformOptionSteps`)
226
+ * or as a shared bag (`transformOptions` / `itemTransformOptions`), or on host
227
+ * `TransformContext.options`.
228
+ */
229
+ readonly optionFields?: readonly TransformOptionField[];
230
+ }
231
+
232
+ export interface ValueTransformListFilter {
233
+ readonly inputType?: FieldDataType;
234
+ readonly category?: string;
235
+ }
236
+
237
+ export interface ValueTransformRegistry {
238
+ list(filter?: ValueTransformListFilter): ValueTransformDefinition[];
239
+ get(id: string): ValueTransformDefinition | undefined;
240
+ apply(id: string, value: unknown, context?: TransformContext): unknown | PromiseLike<unknown>;
241
+ register(definition: ValueTransformDefinition): void;
242
+ }