@workbench-kit/field-remap 0.0.1-prototype.0 → 0.0.2-prototype.0.2.5
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/README.md +115 -115
- package/package.json +1 -1
- package/src/domain/constants.ts +14 -14
- package/src/domain/document/fieldRemapDocument.ts +129 -129
- package/src/domain/document/mappingEdge.ts +136 -136
- package/src/domain/ingest/sourceFieldsFromPlainObject.ts +103 -103
- package/src/domain/ingest/targetSlotsFromPlainObject.ts +41 -41
- package/src/domain/mapping/convertItemEdges.ts +93 -93
- package/src/domain/mapping/dateFormat.ts +102 -102
- package/src/domain/mapping/mappingConflicts.ts +90 -90
- package/src/domain/mapping/pathUtils.ts +156 -156
- package/src/domain/mapping/resolveMappedValue.ts +96 -96
- package/src/domain/mapping/transformOptions.ts +184 -184
- package/src/domain/mapping/treeUtils.ts +32 -32
- package/src/domain/shapes/conversionDefinition.ts +95 -95
- package/src/domain/shapes/convertToShape.ts +187 -187
- package/src/domain/shapes/dataShape.ts +109 -109
- package/src/domain/types.ts +182 -182
- package/src/index.ts +137 -137
- package/src/registry/builtinTransforms.ts +243 -243
- package/src/registry/createValueTransformRegistry.ts +194 -194
|
@@ -1,156 +1,156 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Lightweight path helpers for collection item projection and safe templates.
|
|
3
|
-
* Supports simple dotted paths (`name`, `meta.label`) on plain objects.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/** Identifier or dotted path: `city`, `a.b` (no expressions / eval). */
|
|
7
|
-
const SAFE_PATH_RE = /^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/;
|
|
8
|
-
|
|
9
|
-
/** Placeholder matcher: `{city}`, `{a.b}` — rejects expressions / spaces. */
|
|
10
|
-
const TEMPLATE_PLACEHOLDER_RE = /\{([A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*)\}/g;
|
|
11
|
-
|
|
12
|
-
export function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
13
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function isSafeObjectPath(path: string): boolean {
|
|
17
|
-
return SAFE_PATH_RE.test(path.trim());
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Fill `{path}` placeholders from a plain object using safe dotted paths only.
|
|
22
|
-
* Unknown / unsafe placeholders become empty strings. No JS eval.
|
|
23
|
-
*/
|
|
24
|
-
export function applyStringTemplate(
|
|
25
|
-
template: string,
|
|
26
|
-
record: Readonly<Record<string, unknown>> | null | undefined,
|
|
27
|
-
): string {
|
|
28
|
-
return template.replace(TEMPLATE_PLACEHOLDER_RE, (_match, path: string) => {
|
|
29
|
-
if (!record || !isSafeObjectPath(path)) {
|
|
30
|
-
return '';
|
|
31
|
-
}
|
|
32
|
-
const resolved = readObjectPath(record, path);
|
|
33
|
-
if (resolved === null || resolved === undefined) {
|
|
34
|
-
return '';
|
|
35
|
-
}
|
|
36
|
-
if (
|
|
37
|
-
typeof resolved === 'string' ||
|
|
38
|
-
typeof resolved === 'number' ||
|
|
39
|
-
typeof resolved === 'boolean'
|
|
40
|
-
) {
|
|
41
|
-
return String(resolved);
|
|
42
|
-
}
|
|
43
|
-
return '';
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function readObjectPath(value: unknown, path: string): unknown {
|
|
48
|
-
const parts = path
|
|
49
|
-
.split('.')
|
|
50
|
-
.map((part) => part.trim())
|
|
51
|
-
.filter((part) => part.length > 0);
|
|
52
|
-
if (parts.length === 0) {
|
|
53
|
-
return value;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
let current: unknown = value;
|
|
57
|
-
for (const part of parts) {
|
|
58
|
-
if (current === null || current === undefined || typeof current !== 'object') {
|
|
59
|
-
return undefined;
|
|
60
|
-
}
|
|
61
|
-
current = (current as Record<string, unknown>)[part];
|
|
62
|
-
}
|
|
63
|
-
return current;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Write `value` at a dotted path, creating plain-object parents as needed.
|
|
68
|
-
* Returns a new root object (does not mutate `root`).
|
|
69
|
-
*/
|
|
70
|
-
export function writeObjectPath(
|
|
71
|
-
root: Readonly<Record<string, unknown>> | null | undefined,
|
|
72
|
-
path: string,
|
|
73
|
-
value: unknown,
|
|
74
|
-
): Record<string, unknown> {
|
|
75
|
-
const parts = path
|
|
76
|
-
.split('.')
|
|
77
|
-
.map((part) => part.trim())
|
|
78
|
-
.filter((part) => part.length > 0);
|
|
79
|
-
if (parts.length === 0) {
|
|
80
|
-
return isPlainObject(root) ? { ...root } : {};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const result: Record<string, unknown> = isPlainObject(root) ? { ...root } : {};
|
|
84
|
-
let cursor: Record<string, unknown> = result;
|
|
85
|
-
|
|
86
|
-
for (let index = 0; index < parts.length - 1; index += 1) {
|
|
87
|
-
const part = parts[index]!;
|
|
88
|
-
const existing = cursor[part];
|
|
89
|
-
const nextChild: Record<string, unknown> = isPlainObject(existing) ? { ...existing } : {};
|
|
90
|
-
cursor[part] = nextChild;
|
|
91
|
-
cursor = nextChild;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
cursor[parts[parts.length - 1]!] = value;
|
|
95
|
-
return result;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Project each array element through `itemSourcePath`.
|
|
100
|
-
* Non-arrays are returned unchanged (callers decide whether that is valid).
|
|
101
|
-
*/
|
|
102
|
-
export function projectCollectionItems(value: unknown, itemSourcePath: string): unknown {
|
|
103
|
-
const path = itemSourcePath.trim();
|
|
104
|
-
if (!path || !Array.isArray(value)) {
|
|
105
|
-
return value;
|
|
106
|
-
}
|
|
107
|
-
return value.map((item) => readObjectPath(item, path));
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export interface ArrayItemProjectionOption {
|
|
111
|
-
readonly path: string;
|
|
112
|
-
readonly label: string;
|
|
113
|
-
readonly dataType?: string;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Projection candidates for an array source:
|
|
118
|
-
* 1) explicit item-schema `children` (path / label)
|
|
119
|
-
* 2) else keys of the first object in `sampleValue`
|
|
120
|
-
*/
|
|
121
|
-
export function listArrayItemProjectionOptions(source: {
|
|
122
|
-
readonly children?: readonly {
|
|
123
|
-
readonly label: string;
|
|
124
|
-
readonly path?: string;
|
|
125
|
-
readonly dataType?: string;
|
|
126
|
-
}[];
|
|
127
|
-
readonly sampleValue?: unknown;
|
|
128
|
-
}): ArrayItemProjectionOption[] {
|
|
129
|
-
if (source.children?.length) {
|
|
130
|
-
const options: ArrayItemProjectionOption[] = [];
|
|
131
|
-
for (const child of source.children) {
|
|
132
|
-
const path = (child.path ?? child.label).trim();
|
|
133
|
-
if (!path) {
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
options.push({
|
|
137
|
-
path,
|
|
138
|
-
label: child.label,
|
|
139
|
-
dataType: child.dataType,
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
return options;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (!Array.isArray(source.sampleValue) || source.sampleValue.length === 0) {
|
|
146
|
-
return [];
|
|
147
|
-
}
|
|
148
|
-
const first = source.sampleValue[0];
|
|
149
|
-
if (first === null || first === undefined || typeof first !== 'object' || Array.isArray(first)) {
|
|
150
|
-
return [];
|
|
151
|
-
}
|
|
152
|
-
return Object.keys(first as Record<string, unknown>).map((key) => ({
|
|
153
|
-
path: key,
|
|
154
|
-
label: key,
|
|
155
|
-
}));
|
|
156
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight path helpers for collection item projection and safe templates.
|
|
3
|
+
* Supports simple dotted paths (`name`, `meta.label`) on plain objects.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Identifier or dotted path: `city`, `a.b` (no expressions / eval). */
|
|
7
|
+
const SAFE_PATH_RE = /^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/;
|
|
8
|
+
|
|
9
|
+
/** Placeholder matcher: `{city}`, `{a.b}` — rejects expressions / spaces. */
|
|
10
|
+
const TEMPLATE_PLACEHOLDER_RE = /\{([A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*)\}/g;
|
|
11
|
+
|
|
12
|
+
export function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
13
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function isSafeObjectPath(path: string): boolean {
|
|
17
|
+
return SAFE_PATH_RE.test(path.trim());
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Fill `{path}` placeholders from a plain object using safe dotted paths only.
|
|
22
|
+
* Unknown / unsafe placeholders become empty strings. No JS eval.
|
|
23
|
+
*/
|
|
24
|
+
export function applyStringTemplate(
|
|
25
|
+
template: string,
|
|
26
|
+
record: Readonly<Record<string, unknown>> | null | undefined,
|
|
27
|
+
): string {
|
|
28
|
+
return template.replace(TEMPLATE_PLACEHOLDER_RE, (_match, path: string) => {
|
|
29
|
+
if (!record || !isSafeObjectPath(path)) {
|
|
30
|
+
return '';
|
|
31
|
+
}
|
|
32
|
+
const resolved = readObjectPath(record, path);
|
|
33
|
+
if (resolved === null || resolved === undefined) {
|
|
34
|
+
return '';
|
|
35
|
+
}
|
|
36
|
+
if (
|
|
37
|
+
typeof resolved === 'string' ||
|
|
38
|
+
typeof resolved === 'number' ||
|
|
39
|
+
typeof resolved === 'boolean'
|
|
40
|
+
) {
|
|
41
|
+
return String(resolved);
|
|
42
|
+
}
|
|
43
|
+
return '';
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function readObjectPath(value: unknown, path: string): unknown {
|
|
48
|
+
const parts = path
|
|
49
|
+
.split('.')
|
|
50
|
+
.map((part) => part.trim())
|
|
51
|
+
.filter((part) => part.length > 0);
|
|
52
|
+
if (parts.length === 0) {
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let current: unknown = value;
|
|
57
|
+
for (const part of parts) {
|
|
58
|
+
if (current === null || current === undefined || typeof current !== 'object') {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
current = (current as Record<string, unknown>)[part];
|
|
62
|
+
}
|
|
63
|
+
return current;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Write `value` at a dotted path, creating plain-object parents as needed.
|
|
68
|
+
* Returns a new root object (does not mutate `root`).
|
|
69
|
+
*/
|
|
70
|
+
export function writeObjectPath(
|
|
71
|
+
root: Readonly<Record<string, unknown>> | null | undefined,
|
|
72
|
+
path: string,
|
|
73
|
+
value: unknown,
|
|
74
|
+
): Record<string, unknown> {
|
|
75
|
+
const parts = path
|
|
76
|
+
.split('.')
|
|
77
|
+
.map((part) => part.trim())
|
|
78
|
+
.filter((part) => part.length > 0);
|
|
79
|
+
if (parts.length === 0) {
|
|
80
|
+
return isPlainObject(root) ? { ...root } : {};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const result: Record<string, unknown> = isPlainObject(root) ? { ...root } : {};
|
|
84
|
+
let cursor: Record<string, unknown> = result;
|
|
85
|
+
|
|
86
|
+
for (let index = 0; index < parts.length - 1; index += 1) {
|
|
87
|
+
const part = parts[index]!;
|
|
88
|
+
const existing = cursor[part];
|
|
89
|
+
const nextChild: Record<string, unknown> = isPlainObject(existing) ? { ...existing } : {};
|
|
90
|
+
cursor[part] = nextChild;
|
|
91
|
+
cursor = nextChild;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
cursor[parts[parts.length - 1]!] = value;
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Project each array element through `itemSourcePath`.
|
|
100
|
+
* Non-arrays are returned unchanged (callers decide whether that is valid).
|
|
101
|
+
*/
|
|
102
|
+
export function projectCollectionItems(value: unknown, itemSourcePath: string): unknown {
|
|
103
|
+
const path = itemSourcePath.trim();
|
|
104
|
+
if (!path || !Array.isArray(value)) {
|
|
105
|
+
return value;
|
|
106
|
+
}
|
|
107
|
+
return value.map((item) => readObjectPath(item, path));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface ArrayItemProjectionOption {
|
|
111
|
+
readonly path: string;
|
|
112
|
+
readonly label: string;
|
|
113
|
+
readonly dataType?: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Projection candidates for an array source:
|
|
118
|
+
* 1) explicit item-schema `children` (path / label)
|
|
119
|
+
* 2) else keys of the first object in `sampleValue`
|
|
120
|
+
*/
|
|
121
|
+
export function listArrayItemProjectionOptions(source: {
|
|
122
|
+
readonly children?: readonly {
|
|
123
|
+
readonly label: string;
|
|
124
|
+
readonly path?: string;
|
|
125
|
+
readonly dataType?: string;
|
|
126
|
+
}[];
|
|
127
|
+
readonly sampleValue?: unknown;
|
|
128
|
+
}): ArrayItemProjectionOption[] {
|
|
129
|
+
if (source.children?.length) {
|
|
130
|
+
const options: ArrayItemProjectionOption[] = [];
|
|
131
|
+
for (const child of source.children) {
|
|
132
|
+
const path = (child.path ?? child.label).trim();
|
|
133
|
+
if (!path) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
options.push({
|
|
137
|
+
path,
|
|
138
|
+
label: child.label,
|
|
139
|
+
dataType: child.dataType,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return options;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!Array.isArray(source.sampleValue) || source.sampleValue.length === 0) {
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
const first = source.sampleValue[0];
|
|
149
|
+
if (first === null || first === undefined || typeof first !== 'object' || Array.isArray(first)) {
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
return Object.keys(first as Record<string, unknown>).map((key) => ({
|
|
153
|
+
path: key,
|
|
154
|
+
label: key,
|
|
155
|
+
}));
|
|
156
|
+
}
|
|
@@ -1,96 +1,96 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
MappingEdge,
|
|
3
|
-
SourceField,
|
|
4
|
-
TransformContext,
|
|
5
|
-
ValueTransformRegistry,
|
|
6
|
-
} from '../types.js';
|
|
7
|
-
import { edgeItemTransformIds, edgeTransformIds } from '../document/mappingEdge.js';
|
|
8
|
-
import { projectCollectionItems } from './pathUtils.js';
|
|
9
|
-
import { applyTransformChain } from '../../registry/createValueTransformRegistry.js';
|
|
10
|
-
import { BUILTIN_TRANSFORM_IDS } from '../../registry/builtinTransforms.js';
|
|
11
|
-
import { resolveOptionSteps } from './transformOptions.js';
|
|
12
|
-
import { flattenSourceFields } from './treeUtils.js';
|
|
13
|
-
|
|
14
|
-
export { findTargetSlot, flattenSourceFields, flattenTargetSlots } from './treeUtils.js';
|
|
15
|
-
|
|
16
|
-
export { projectCollectionItems, readObjectPath } from './pathUtils.js';
|
|
17
|
-
|
|
18
|
-
export function findSourceField(
|
|
19
|
-
fields: readonly SourceField[],
|
|
20
|
-
fieldId: string,
|
|
21
|
-
): SourceField | undefined {
|
|
22
|
-
return flattenSourceFields(fields).find((field) => field.id === fieldId);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Resolve an edge against a live / sample source value.
|
|
27
|
-
*
|
|
28
|
-
* Apply order:
|
|
29
|
-
* 1. Optional `itemSourcePath` projection (array of objects → projected array)
|
|
30
|
-
* 2. Optional `itemTransformIds` per element (when the value is still an array)
|
|
31
|
-
* 3. `transformIds` / legacy `transformId` on the whole value (including array reduces)
|
|
32
|
-
*
|
|
33
|
-
* Per-step options (`transformOptionSteps` / `itemTransformOptionSteps`) win when
|
|
34
|
-
* present; otherwise shared `transformOptions` / `itemTransformOptions` apply to all steps.
|
|
35
|
-
*/
|
|
36
|
-
export function resolveMappedValue(
|
|
37
|
-
edge: MappingEdge,
|
|
38
|
-
sourceValue: unknown,
|
|
39
|
-
registry: ValueTransformRegistry,
|
|
40
|
-
context: TransformContext = {},
|
|
41
|
-
): unknown {
|
|
42
|
-
let current = edge.itemSourcePath
|
|
43
|
-
? projectCollectionItems(sourceValue, edge.itemSourcePath)
|
|
44
|
-
: sourceValue;
|
|
45
|
-
|
|
46
|
-
const itemChain = edgeItemTransformIds(edge);
|
|
47
|
-
const itemSteps = resolveOptionSteps(
|
|
48
|
-
itemChain,
|
|
49
|
-
edge.itemTransformOptionSteps,
|
|
50
|
-
edge.itemTransformOptions,
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
if (itemChain.length > 0 && Array.isArray(current)) {
|
|
54
|
-
current = current.map((item) =>
|
|
55
|
-
applyTransformChain(registry, itemChain, item, context, itemSteps),
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const chain = edgeTransformIds(edge);
|
|
60
|
-
if (chain.length === 0) {
|
|
61
|
-
const identity = registry.get(BUILTIN_TRANSFORM_IDS.identity);
|
|
62
|
-
return identity ? identity.apply(current, context) : current;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const valueSteps = resolveOptionSteps(chain, edge.transformOptionSteps, edge.transformOptions);
|
|
66
|
-
return applyTransformChain(registry, chain, current, context, valueSteps);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function resolveEdgePreview(
|
|
70
|
-
edge: MappingEdge,
|
|
71
|
-
sources: readonly SourceField[],
|
|
72
|
-
registry: ValueTransformRegistry,
|
|
73
|
-
context: TransformContext = {},
|
|
74
|
-
): unknown {
|
|
75
|
-
const field = findSourceField(sources, edge.sourceFieldId);
|
|
76
|
-
const sample =
|
|
77
|
-
context.sampleValue !== undefined ? context.sampleValue : (field?.sampleValue ?? context.now);
|
|
78
|
-
return resolveMappedValue(edge, sample, registry, {
|
|
79
|
-
...context,
|
|
80
|
-
sampleValue: sample,
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/** Resolve every edge into `{ targetSlotId, value }` for live preview panels. */
|
|
85
|
-
export function resolveAllEdgePreviews(
|
|
86
|
-
edges: readonly MappingEdge[],
|
|
87
|
-
sources: readonly SourceField[],
|
|
88
|
-
registry: ValueTransformRegistry,
|
|
89
|
-
context: TransformContext = {},
|
|
90
|
-
): ReadonlyArray<{ edgeId: string; targetSlotId: string; value: unknown }> {
|
|
91
|
-
return edges.map((edge) => ({
|
|
92
|
-
edgeId: edge.id,
|
|
93
|
-
targetSlotId: edge.targetSlotId,
|
|
94
|
-
value: resolveEdgePreview(edge, sources, registry, context),
|
|
95
|
-
}));
|
|
96
|
-
}
|
|
1
|
+
import type {
|
|
2
|
+
MappingEdge,
|
|
3
|
+
SourceField,
|
|
4
|
+
TransformContext,
|
|
5
|
+
ValueTransformRegistry,
|
|
6
|
+
} from '../types.js';
|
|
7
|
+
import { edgeItemTransformIds, edgeTransformIds } from '../document/mappingEdge.js';
|
|
8
|
+
import { projectCollectionItems } from './pathUtils.js';
|
|
9
|
+
import { applyTransformChain } from '../../registry/createValueTransformRegistry.js';
|
|
10
|
+
import { BUILTIN_TRANSFORM_IDS } from '../../registry/builtinTransforms.js';
|
|
11
|
+
import { resolveOptionSteps } from './transformOptions.js';
|
|
12
|
+
import { flattenSourceFields } from './treeUtils.js';
|
|
13
|
+
|
|
14
|
+
export { findTargetSlot, flattenSourceFields, flattenTargetSlots } from './treeUtils.js';
|
|
15
|
+
|
|
16
|
+
export { projectCollectionItems, readObjectPath } from './pathUtils.js';
|
|
17
|
+
|
|
18
|
+
export function findSourceField(
|
|
19
|
+
fields: readonly SourceField[],
|
|
20
|
+
fieldId: string,
|
|
21
|
+
): SourceField | undefined {
|
|
22
|
+
return flattenSourceFields(fields).find((field) => field.id === fieldId);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Resolve an edge against a live / sample source value.
|
|
27
|
+
*
|
|
28
|
+
* Apply order:
|
|
29
|
+
* 1. Optional `itemSourcePath` projection (array of objects → projected array)
|
|
30
|
+
* 2. Optional `itemTransformIds` per element (when the value is still an array)
|
|
31
|
+
* 3. `transformIds` / legacy `transformId` on the whole value (including array reduces)
|
|
32
|
+
*
|
|
33
|
+
* Per-step options (`transformOptionSteps` / `itemTransformOptionSteps`) win when
|
|
34
|
+
* present; otherwise shared `transformOptions` / `itemTransformOptions` apply to all steps.
|
|
35
|
+
*/
|
|
36
|
+
export function resolveMappedValue(
|
|
37
|
+
edge: MappingEdge,
|
|
38
|
+
sourceValue: unknown,
|
|
39
|
+
registry: ValueTransformRegistry,
|
|
40
|
+
context: TransformContext = {},
|
|
41
|
+
): unknown {
|
|
42
|
+
let current = edge.itemSourcePath
|
|
43
|
+
? projectCollectionItems(sourceValue, edge.itemSourcePath)
|
|
44
|
+
: sourceValue;
|
|
45
|
+
|
|
46
|
+
const itemChain = edgeItemTransformIds(edge);
|
|
47
|
+
const itemSteps = resolveOptionSteps(
|
|
48
|
+
itemChain,
|
|
49
|
+
edge.itemTransformOptionSteps,
|
|
50
|
+
edge.itemTransformOptions,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
if (itemChain.length > 0 && Array.isArray(current)) {
|
|
54
|
+
current = current.map((item) =>
|
|
55
|
+
applyTransformChain(registry, itemChain, item, context, itemSteps),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const chain = edgeTransformIds(edge);
|
|
60
|
+
if (chain.length === 0) {
|
|
61
|
+
const identity = registry.get(BUILTIN_TRANSFORM_IDS.identity);
|
|
62
|
+
return identity ? identity.apply(current, context) : current;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const valueSteps = resolveOptionSteps(chain, edge.transformOptionSteps, edge.transformOptions);
|
|
66
|
+
return applyTransformChain(registry, chain, current, context, valueSteps);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function resolveEdgePreview(
|
|
70
|
+
edge: MappingEdge,
|
|
71
|
+
sources: readonly SourceField[],
|
|
72
|
+
registry: ValueTransformRegistry,
|
|
73
|
+
context: TransformContext = {},
|
|
74
|
+
): unknown {
|
|
75
|
+
const field = findSourceField(sources, edge.sourceFieldId);
|
|
76
|
+
const sample =
|
|
77
|
+
context.sampleValue !== undefined ? context.sampleValue : (field?.sampleValue ?? context.now);
|
|
78
|
+
return resolveMappedValue(edge, sample, registry, {
|
|
79
|
+
...context,
|
|
80
|
+
sampleValue: sample,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Resolve every edge into `{ targetSlotId, value }` for live preview panels. */
|
|
85
|
+
export function resolveAllEdgePreviews(
|
|
86
|
+
edges: readonly MappingEdge[],
|
|
87
|
+
sources: readonly SourceField[],
|
|
88
|
+
registry: ValueTransformRegistry,
|
|
89
|
+
context: TransformContext = {},
|
|
90
|
+
): ReadonlyArray<{ edgeId: string; targetSlotId: string; value: unknown }> {
|
|
91
|
+
return edges.map((edge) => ({
|
|
92
|
+
edgeId: edge.id,
|
|
93
|
+
targetSlotId: edge.targetSlotId,
|
|
94
|
+
value: resolveEdgePreview(edge, sources, registry, context),
|
|
95
|
+
}));
|
|
96
|
+
}
|