@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.
@@ -1,129 +1,163 @@
1
- import { normalizeMappingEdges } from './mappingEdge.js';
2
- import type { MappingEdge, FieldRemapDocument } from '../types.js';
3
-
4
- export const FIELD_REMAP_DOCUMENT_VERSION = 1 as const;
5
-
6
- /** Thrown when parse/deserialize sees a document `version` other than the supported constant. */
7
- export class UnsupportedFieldRemapDocumentVersionError extends Error {
8
- readonly version: unknown;
9
- readonly expectedVersion: typeof FIELD_REMAP_DOCUMENT_VERSION;
10
-
11
- constructor(version: unknown) {
12
- super(
13
- `Unsupported field remap document version ${String(version)}; expected ${FIELD_REMAP_DOCUMENT_VERSION}.`,
14
- );
15
- this.name = 'UnsupportedFieldRemapDocumentVersionError';
16
- this.version = version;
17
- this.expectedVersion = FIELD_REMAP_DOCUMENT_VERSION;
18
- }
19
- }
20
-
21
- /** Thrown when parse/deserialize receives a value that is not a mapping document. */
22
- export class InvalidFieldRemapDocumentError extends Error {
23
- constructor(message: string) {
24
- super(message);
25
- this.name = 'InvalidFieldRemapDocumentError';
26
- }
27
- }
28
-
29
- /** Build a versioned, normalized mapping document for host persistence. */
30
- export function createFieldRemapDocument(edges: readonly MappingEdge[]): FieldRemapDocument {
31
- return {
32
- version: FIELD_REMAP_DOCUMENT_VERSION,
33
- edges: normalizeMappingEdges(edges),
34
- };
35
- }
36
-
37
- /** Normalize edges (including legacy transform id aliases) on a persisted document. */
38
- export function normalizeFieldRemapDocument(document: FieldRemapDocument): FieldRemapDocument {
39
- if (document.version !== FIELD_REMAP_DOCUMENT_VERSION) {
40
- throw new UnsupportedFieldRemapDocumentVersionError(document.version);
41
- }
42
- return {
43
- version: FIELD_REMAP_DOCUMENT_VERSION,
44
- edges: normalizeMappingEdges(document.edges),
45
- };
46
- }
47
-
48
- /**
49
- * Stable JSON serialization for persistence / clipboard.
50
- * Always emits the current document version with normalized edges.
51
- */
52
- export function serializeFieldRemapDocument(
53
- document: FieldRemapDocument | readonly MappingEdge[],
54
- ): string {
55
- const doc = Array.isArray(document)
56
- ? createFieldRemapDocument(document)
57
- : createFieldRemapDocument((document as FieldRemapDocument).edges);
58
- return JSON.stringify(doc);
59
- }
60
-
61
- /**
62
- * Parse an unknown JSON value into a normalized `FieldRemapDocument`.
63
- * Rejects unsupported versions and malformed shapes with typed errors.
64
- */
65
- export function parseFieldRemapDocument(input: unknown): FieldRemapDocument {
66
- if (!input || typeof input !== 'object' || Array.isArray(input)) {
67
- throw new InvalidFieldRemapDocumentError(
68
- 'Expected a field remap document object with version and edges.',
69
- );
70
- }
71
-
72
- const record = input as Record<string, unknown>;
73
- if (!('version' in record)) {
74
- throw new InvalidFieldRemapDocumentError('Field remap document is missing version.');
75
- }
76
- if (record.version !== FIELD_REMAP_DOCUMENT_VERSION) {
77
- throw new UnsupportedFieldRemapDocumentVersionError(record.version);
78
- }
79
- if (!Array.isArray(record.edges)) {
80
- throw new InvalidFieldRemapDocumentError('Field remap document edges must be an array.');
81
- }
82
-
83
- return normalizeFieldRemapDocument({
84
- version: FIELD_REMAP_DOCUMENT_VERSION,
85
- edges: record.edges as MappingEdge[],
86
- });
87
- }
88
-
89
- /** JSON.parse + {@link parseFieldRemapDocument}. */
90
- export function deserializeFieldRemapDocument(json: string): FieldRemapDocument {
91
- let parsed: unknown;
92
- try {
93
- parsed = JSON.parse(json) as unknown;
94
- } catch (error) {
95
- const detail = error instanceof Error ? error.message : String(error);
96
- throw new InvalidFieldRemapDocumentError(`Field remap document JSON is invalid: ${detail}`);
97
- }
98
- return parseFieldRemapDocument(parsed);
99
- }
100
-
101
- /**
102
- * Migrate an unknown persisted value to the current {@link FieldRemapDocument}.
103
- *
104
- * Hosts should call this (or `parseFieldRemapDocument`) at load time so future
105
- * document versions can be rewritten here without changing call sites.
106
- *
107
- * **v1:** passthrough normalize (legacy transform id aliases rewritten).
108
- * Future versions: add `case` branches that rewrite into v1 shape, then normalize.
109
- */
110
- export function migrateFieldRemapDocument(input: unknown): FieldRemapDocument {
111
- if (!input || typeof input !== 'object' || Array.isArray(input)) {
112
- throw new InvalidFieldRemapDocumentError(
113
- 'Expected a field remap document object with version and edges.',
114
- );
115
- }
116
-
117
- const record = input as Record<string, unknown>;
118
- if (!('version' in record)) {
119
- throw new InvalidFieldRemapDocumentError('Field remap document is missing version.');
120
- }
121
-
122
- switch (record.version) {
123
- case FIELD_REMAP_DOCUMENT_VERSION:
124
- return parseFieldRemapDocument(input);
125
- // Future: case 2: return parseFieldRemapDocument(migrateV2ToV1(record));
126
- default:
127
- throw new UnsupportedFieldRemapDocumentVersionError(record.version);
128
- }
129
- }
1
+ import { normalizeMappingEdges } from './mappingEdge.js';
2
+ import { normalizeMappingOperators } from '../mapping/mappingOperators.js';
3
+ import type { MappingEdge, MappingOperator, FieldRemapDocument } from '../types.js';
4
+
5
+ /** Current persistence version (edges + optional `operators[]`). */
6
+ export const FIELD_REMAP_DOCUMENT_VERSION = 2 as const;
7
+ /** Legacy edges-only documents. */
8
+ export const FIELD_REMAP_DOCUMENT_V1_VERSION = 1 as const;
9
+
10
+ type SupportedDocumentVersion =
11
+ typeof FIELD_REMAP_DOCUMENT_VERSION | typeof FIELD_REMAP_DOCUMENT_V1_VERSION;
12
+
13
+ /** Thrown when parse/deserialize sees a document `version` other than a supported constant. */
14
+ export class UnsupportedFieldRemapDocumentVersionError extends Error {
15
+ readonly version: unknown;
16
+ readonly expectedVersion: typeof FIELD_REMAP_DOCUMENT_VERSION;
17
+
18
+ constructor(version: unknown) {
19
+ super(
20
+ `Unsupported field remap document version ${String(version)}; expected ${FIELD_REMAP_DOCUMENT_V1_VERSION} or ${FIELD_REMAP_DOCUMENT_VERSION}.`,
21
+ );
22
+ this.name = 'UnsupportedFieldRemapDocumentVersionError';
23
+ this.version = version;
24
+ this.expectedVersion = FIELD_REMAP_DOCUMENT_VERSION;
25
+ }
26
+ }
27
+
28
+ /** Thrown when parse/deserialize receives a value that is not a mapping document. */
29
+ export class InvalidFieldRemapDocumentError extends Error {
30
+ constructor(message: string) {
31
+ super(message);
32
+ this.name = 'InvalidFieldRemapDocumentError';
33
+ }
34
+ }
35
+
36
+ export type CreateFieldRemapDocumentOptions = {
37
+ readonly operators?: readonly MappingOperator[];
38
+ };
39
+
40
+ /** Build a versioned, normalized mapping document for host persistence. */
41
+ export function createFieldRemapDocument(
42
+ edges: readonly MappingEdge[],
43
+ options?: CreateFieldRemapDocumentOptions,
44
+ ): FieldRemapDocument {
45
+ const operators = normalizeMappingOperators(options?.operators);
46
+ return {
47
+ version: FIELD_REMAP_DOCUMENT_VERSION,
48
+ edges: normalizeMappingEdges(edges),
49
+ ...(operators ? { operators } : {}),
50
+ };
51
+ }
52
+
53
+ function isSupportedVersion(version: unknown): version is SupportedDocumentVersion {
54
+ return version === FIELD_REMAP_DOCUMENT_V1_VERSION || version === FIELD_REMAP_DOCUMENT_VERSION;
55
+ }
56
+
57
+ /** Normalize edges / operators on a persisted document; always emits the current version. */
58
+ export function normalizeFieldRemapDocument(document: FieldRemapDocument): FieldRemapDocument {
59
+ if (!isSupportedVersion(document.version)) {
60
+ throw new UnsupportedFieldRemapDocumentVersionError(document.version);
61
+ }
62
+ const operators = normalizeMappingOperators(document.operators);
63
+ return {
64
+ version: FIELD_REMAP_DOCUMENT_VERSION,
65
+ edges: normalizeMappingEdges(document.edges),
66
+ ...(operators ? { operators } : {}),
67
+ };
68
+ }
69
+
70
+ /**
71
+ * Stable JSON serialization for persistence / clipboard.
72
+ * Always emits the current document version with normalized edges / operators.
73
+ */
74
+ export function serializeFieldRemapDocument(
75
+ document: FieldRemapDocument | readonly MappingEdge[],
76
+ ): string {
77
+ const doc = Array.isArray(document)
78
+ ? createFieldRemapDocument(document)
79
+ : createFieldRemapDocument((document as FieldRemapDocument).edges, {
80
+ operators: (document as FieldRemapDocument).operators,
81
+ });
82
+ return JSON.stringify(doc);
83
+ }
84
+
85
+ /**
86
+ * Parse an unknown JSON value into a normalized `FieldRemapDocument`.
87
+ * Accepts v1 (edges-only) and v2 (optional operators); always returns current version.
88
+ */
89
+ export function parseFieldRemapDocument(input: unknown): FieldRemapDocument {
90
+ if (!input || typeof input !== 'object' || Array.isArray(input)) {
91
+ throw new InvalidFieldRemapDocumentError(
92
+ 'Expected a field remap document object with version and edges.',
93
+ );
94
+ }
95
+
96
+ const record = input as Record<string, unknown>;
97
+ if (!('version' in record)) {
98
+ throw new InvalidFieldRemapDocumentError('Field remap document is missing version.');
99
+ }
100
+ if (!isSupportedVersion(record.version)) {
101
+ throw new UnsupportedFieldRemapDocumentVersionError(record.version);
102
+ }
103
+ if (!Array.isArray(record.edges)) {
104
+ throw new InvalidFieldRemapDocumentError('Field remap document edges must be an array.');
105
+ }
106
+ if (
107
+ record.operators !== undefined &&
108
+ record.operators !== null &&
109
+ !Array.isArray(record.operators)
110
+ ) {
111
+ throw new InvalidFieldRemapDocumentError(
112
+ 'Field remap document operators must be an array when present.',
113
+ );
114
+ }
115
+
116
+ return normalizeFieldRemapDocument({
117
+ version: record.version,
118
+ edges: record.edges as MappingEdge[],
119
+ operators: record.operators as MappingOperator[] | undefined,
120
+ });
121
+ }
122
+
123
+ /** JSON.parse + {@link parseFieldRemapDocument}. */
124
+ export function deserializeFieldRemapDocument(json: string): FieldRemapDocument {
125
+ let parsed: unknown;
126
+ try {
127
+ parsed = JSON.parse(json) as unknown;
128
+ } catch (error) {
129
+ const detail = error instanceof Error ? error.message : String(error);
130
+ throw new InvalidFieldRemapDocumentError(`Field remap document JSON is invalid: ${detail}`);
131
+ }
132
+ return parseFieldRemapDocument(parsed);
133
+ }
134
+
135
+ /**
136
+ * Migrate an unknown persisted value to the current {@link FieldRemapDocument}.
137
+ *
138
+ * Hosts should call this (or `parseFieldRemapDocument`) at load time so future
139
+ * document versions can be rewritten here without changing call sites.
140
+ *
141
+ * **v1:** edges-only → current version (operators omitted).
142
+ * **v2:** normalize edges + operators.
143
+ */
144
+ export function migrateFieldRemapDocument(input: unknown): FieldRemapDocument {
145
+ if (!input || typeof input !== 'object' || Array.isArray(input)) {
146
+ throw new InvalidFieldRemapDocumentError(
147
+ 'Expected a field remap document object with version and edges.',
148
+ );
149
+ }
150
+
151
+ const record = input as Record<string, unknown>;
152
+ if (!('version' in record)) {
153
+ throw new InvalidFieldRemapDocumentError('Field remap document is missing version.');
154
+ }
155
+
156
+ switch (record.version) {
157
+ case FIELD_REMAP_DOCUMENT_V1_VERSION:
158
+ case FIELD_REMAP_DOCUMENT_VERSION:
159
+ return parseFieldRemapDocument(input);
160
+ default:
161
+ throw new UnsupportedFieldRemapDocumentVersionError(record.version);
162
+ }
163
+ }
@@ -1,136 +1,136 @@
1
- import {
2
- canonicalizeTransformId,
3
- IDENTITY_TRANSFORM_ID,
4
- MAX_TRANSFORM_CHAIN,
5
- } from '../constants.js';
6
- import {
7
- sanitizeOptionRecord,
8
- sanitizeOptionSteps,
9
- sharedOptionsFromSteps,
10
- } from '../mapping/transformOptions.js';
11
- import type { MappingEdge } from '../types.js';
12
-
13
- export { MAX_TRANSFORM_CHAIN } from '../constants.js';
14
-
15
- function sanitizeTransformIds(ids: readonly string[] | undefined): string[] {
16
- const cleaned = ids
17
- ?.map((id) => canonicalizeTransformId(id))
18
- .filter((id) => id.length > 0 && id !== IDENTITY_TRANSFORM_ID);
19
- if (!cleaned || cleaned.length === 0) {
20
- return [];
21
- }
22
- return cleaned.slice(0, MAX_TRANSFORM_CHAIN);
23
- }
24
-
25
- /**
26
- * Resolve the effective transform chain for an edge.
27
- * Prefers `transformIds`; falls back to legacy `transformId`.
28
- * Identity / empty / null → `[]` (pass-through).
29
- */
30
- export function edgeTransformIds(edge: MappingEdge): string[] {
31
- const fromList = sanitizeTransformIds(edge.transformIds);
32
- if (fromList.length > 0) {
33
- return fromList;
34
- }
35
-
36
- const legacy = edge.transformId ? canonicalizeTransformId(edge.transformId) : '';
37
- if (legacy && legacy !== IDENTITY_TRANSFORM_ID) {
38
- return [legacy];
39
- }
40
-
41
- return [];
42
- }
43
-
44
- /** Per-item transform chain applied after optional `itemSourcePath` projection. */
45
- export function edgeItemTransformIds(edge: MappingEdge): string[] {
46
- return sanitizeTransformIds(edge.itemTransformIds);
47
- }
48
-
49
- /**
50
- * Normalize an edge to the `transformIds` model while keeping `transformId`
51
- * as the first chain step (or `null`) for older hosts.
52
- *
53
- * Option bags:
54
- * - Prefer `transformOptionSteps` when present (aligned to chain length).
55
- * - Keep `transformOptions` as the first non-empty step (or the legacy shared bag).
56
- * - Same rules for `itemTransformOptionSteps` / `itemTransformOptions`.
57
- */
58
- export function normalizeMappingEdge(edge: MappingEdge): MappingEdge {
59
- const ids = edgeTransformIds(edge);
60
- const itemIds = edgeItemTransformIds(edge);
61
- const itemSourcePath = edge.itemSourcePath?.trim() || undefined;
62
- const itemEdges =
63
- edge.itemEdges && edge.itemEdges.length > 0
64
- ? edge.itemEdges.map((child) => {
65
- // One collection level — drop nested list contexts on children.
66
- const { itemEdges: _nested, ...rest } = child;
67
- return normalizeMappingEdge(rest);
68
- })
69
- : undefined;
70
-
71
- const transformOptionSteps = sanitizeOptionSteps(edge.transformOptionSteps, ids.length);
72
- const transformOptions =
73
- sharedOptionsFromSteps(transformOptionSteps) ?? sanitizeOptionRecord(edge.transformOptions);
74
-
75
- const itemTransformOptionSteps = sanitizeOptionSteps(
76
- edge.itemTransformOptionSteps,
77
- itemIds.length,
78
- );
79
- const itemTransformOptions =
80
- sharedOptionsFromSteps(itemTransformOptionSteps) ??
81
- sanitizeOptionRecord(edge.itemTransformOptions);
82
-
83
- return {
84
- id: edge.id,
85
- sourceFieldId: edge.sourceFieldId,
86
- targetSlotId: edge.targetSlotId,
87
- transformIds: ids.length > 0 ? ids : undefined,
88
- transformId: ids[0] ?? null,
89
- ...(transformOptionSteps ? { transformOptionSteps } : {}),
90
- ...(transformOptions ? { transformOptions } : {}),
91
- ...(itemSourcePath && !itemEdges ? { itemSourcePath } : {}),
92
- ...(itemIds.length > 0 && !itemEdges ? { itemTransformIds: itemIds } : {}),
93
- ...(itemTransformOptionSteps && !itemEdges ? { itemTransformOptionSteps } : {}),
94
- ...(itemTransformOptions && !itemEdges ? { itemTransformOptions } : {}),
95
- ...(itemEdges ? { itemEdges } : {}),
96
- };
97
- }
98
-
99
- export function normalizeMappingEdges(edges: readonly MappingEdge[]): MappingEdge[] {
100
- return edges.map(normalizeMappingEdge);
101
- }
102
-
103
- /** Build a normalized edge from wire / assign actions. */
104
- export function createMappingEdge(input: {
105
- readonly id: string;
106
- readonly sourceFieldId: string;
107
- readonly targetSlotId: string;
108
- readonly transformIds?: readonly string[];
109
- /** Legacy single id; ignored when `transformIds` is provided. */
110
- readonly transformId?: string | null;
111
- readonly transformOptionSteps?: readonly (Readonly<Record<string, unknown>> | undefined)[];
112
- readonly transformOptions?: Readonly<Record<string, unknown>>;
113
- /** Optional per-item projection path for array mappings. */
114
- readonly itemSourcePath?: string;
115
- /** Optional per-item transform chain after projection. */
116
- readonly itemTransformIds?: readonly string[];
117
- readonly itemTransformOptionSteps?: readonly (Readonly<Record<string, unknown>> | undefined)[];
118
- readonly itemTransformOptions?: Readonly<Record<string, unknown>>;
119
- /** List-context child edges for array-of-object → array-of-object. */
120
- readonly itemEdges?: readonly MappingEdge[];
121
- }): MappingEdge {
122
- return normalizeMappingEdge({
123
- id: input.id,
124
- sourceFieldId: input.sourceFieldId,
125
- targetSlotId: input.targetSlotId,
126
- transformIds: input.transformIds,
127
- transformId: input.transformId,
128
- transformOptionSteps: input.transformOptionSteps,
129
- transformOptions: input.transformOptions,
130
- itemSourcePath: input.itemSourcePath,
131
- itemTransformIds: input.itemTransformIds,
132
- itemTransformOptionSteps: input.itemTransformOptionSteps,
133
- itemTransformOptions: input.itemTransformOptions,
134
- itemEdges: input.itemEdges,
135
- });
136
- }
1
+ import {
2
+ canonicalizeTransformId,
3
+ IDENTITY_TRANSFORM_ID,
4
+ MAX_TRANSFORM_CHAIN,
5
+ } from '../constants.js';
6
+ import {
7
+ sanitizeOptionRecord,
8
+ sanitizeOptionSteps,
9
+ sharedOptionsFromSteps,
10
+ } from '../mapping/transformOptions.js';
11
+ import type { MappingEdge } from '../types.js';
12
+
13
+ export { MAX_TRANSFORM_CHAIN } from '../constants.js';
14
+
15
+ function sanitizeTransformIds(ids: readonly string[] | undefined): string[] {
16
+ const cleaned = ids
17
+ ?.map((id) => canonicalizeTransformId(id))
18
+ .filter((id) => id.length > 0 && id !== IDENTITY_TRANSFORM_ID);
19
+ if (!cleaned || cleaned.length === 0) {
20
+ return [];
21
+ }
22
+ return cleaned.slice(0, MAX_TRANSFORM_CHAIN);
23
+ }
24
+
25
+ /**
26
+ * Resolve the effective transform chain for an edge.
27
+ * Prefers `transformIds`; falls back to legacy `transformId`.
28
+ * Identity / empty / null → `[]` (pass-through).
29
+ */
30
+ export function edgeTransformIds(edge: MappingEdge): string[] {
31
+ const fromList = sanitizeTransformIds(edge.transformIds);
32
+ if (fromList.length > 0) {
33
+ return fromList;
34
+ }
35
+
36
+ const legacy = edge.transformId ? canonicalizeTransformId(edge.transformId) : '';
37
+ if (legacy && legacy !== IDENTITY_TRANSFORM_ID) {
38
+ return [legacy];
39
+ }
40
+
41
+ return [];
42
+ }
43
+
44
+ /** Per-item transform chain applied after optional `itemSourcePath` projection. */
45
+ export function edgeItemTransformIds(edge: MappingEdge): string[] {
46
+ return sanitizeTransformIds(edge.itemTransformIds);
47
+ }
48
+
49
+ /**
50
+ * Normalize an edge to the `transformIds` model while keeping `transformId`
51
+ * as the first chain step (or `null`) for older hosts.
52
+ *
53
+ * Option bags:
54
+ * - Prefer `transformOptionSteps` when present (aligned to chain length).
55
+ * - Keep `transformOptions` as the first non-empty step (or the legacy shared bag).
56
+ * - Same rules for `itemTransformOptionSteps` / `itemTransformOptions`.
57
+ */
58
+ export function normalizeMappingEdge(edge: MappingEdge): MappingEdge {
59
+ const ids = edgeTransformIds(edge);
60
+ const itemIds = edgeItemTransformIds(edge);
61
+ const itemSourcePath = edge.itemSourcePath?.trim() || undefined;
62
+ const itemEdges =
63
+ edge.itemEdges && edge.itemEdges.length > 0
64
+ ? edge.itemEdges.map((child) => {
65
+ // One collection level — drop nested list contexts on children.
66
+ const { itemEdges: _nested, ...rest } = child;
67
+ return normalizeMappingEdge(rest);
68
+ })
69
+ : undefined;
70
+
71
+ const transformOptionSteps = sanitizeOptionSteps(edge.transformOptionSteps, ids.length);
72
+ const transformOptions =
73
+ sharedOptionsFromSteps(transformOptionSteps) ?? sanitizeOptionRecord(edge.transformOptions);
74
+
75
+ const itemTransformOptionSteps = sanitizeOptionSteps(
76
+ edge.itemTransformOptionSteps,
77
+ itemIds.length,
78
+ );
79
+ const itemTransformOptions =
80
+ sharedOptionsFromSteps(itemTransformOptionSteps) ??
81
+ sanitizeOptionRecord(edge.itemTransformOptions);
82
+
83
+ return {
84
+ id: edge.id,
85
+ sourceFieldId: edge.sourceFieldId,
86
+ targetSlotId: edge.targetSlotId,
87
+ transformIds: ids.length > 0 ? ids : undefined,
88
+ transformId: ids[0] ?? null,
89
+ ...(transformOptionSteps ? { transformOptionSteps } : {}),
90
+ ...(transformOptions ? { transformOptions } : {}),
91
+ ...(itemSourcePath && !itemEdges ? { itemSourcePath } : {}),
92
+ ...(itemIds.length > 0 && !itemEdges ? { itemTransformIds: itemIds } : {}),
93
+ ...(itemTransformOptionSteps && !itemEdges ? { itemTransformOptionSteps } : {}),
94
+ ...(itemTransformOptions && !itemEdges ? { itemTransformOptions } : {}),
95
+ ...(itemEdges ? { itemEdges } : {}),
96
+ };
97
+ }
98
+
99
+ export function normalizeMappingEdges(edges: readonly MappingEdge[]): MappingEdge[] {
100
+ return edges.map(normalizeMappingEdge);
101
+ }
102
+
103
+ /** Build a normalized edge from wire / assign actions. */
104
+ export function createMappingEdge(input: {
105
+ readonly id: string;
106
+ readonly sourceFieldId: string;
107
+ readonly targetSlotId: string;
108
+ readonly transformIds?: readonly string[];
109
+ /** Legacy single id; ignored when `transformIds` is provided. */
110
+ readonly transformId?: string | null;
111
+ readonly transformOptionSteps?: readonly (Readonly<Record<string, unknown>> | undefined)[];
112
+ readonly transformOptions?: Readonly<Record<string, unknown>>;
113
+ /** Optional per-item projection path for array mappings. */
114
+ readonly itemSourcePath?: string;
115
+ /** Optional per-item transform chain after projection. */
116
+ readonly itemTransformIds?: readonly string[];
117
+ readonly itemTransformOptionSteps?: readonly (Readonly<Record<string, unknown>> | undefined)[];
118
+ readonly itemTransformOptions?: Readonly<Record<string, unknown>>;
119
+ /** List-context child edges for array-of-object → array-of-object. */
120
+ readonly itemEdges?: readonly MappingEdge[];
121
+ }): MappingEdge {
122
+ return normalizeMappingEdge({
123
+ id: input.id,
124
+ sourceFieldId: input.sourceFieldId,
125
+ targetSlotId: input.targetSlotId,
126
+ transformIds: input.transformIds,
127
+ transformId: input.transformId,
128
+ transformOptionSteps: input.transformOptionSteps,
129
+ transformOptions: input.transformOptions,
130
+ itemSourcePath: input.itemSourcePath,
131
+ itemTransformIds: input.itemTransformIds,
132
+ itemTransformOptionSteps: input.itemTransformOptionSteps,
133
+ itemTransformOptions: input.itemTransformOptions,
134
+ itemEdges: input.itemEdges,
135
+ });
136
+ }