@workbench-kit/field-remap 0.0.2-prototype.0.2.27 → 0.0.2-prototype.0.2.28
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 +12 -11
- package/package.json +1 -1
- package/src/domain/constants.ts +0 -9
- package/src/domain/document/fieldRemapDocument.ts +7 -47
- package/src/domain/document/mappingEdge.ts +4 -29
- package/src/domain/mapping/convertItemEdges.ts +1 -1
- package/src/domain/mapping/mappingOperators.ts +2 -6
- package/src/domain/mapping/resolveMappedValue.ts +3 -8
- package/src/domain/mapping/transformOptions.ts +3 -112
- package/src/domain/types.ts +6 -20
- package/src/index.ts +1 -14
- package/src/registry/createValueTransformRegistry.ts +2 -3
package/README.md
CHANGED
|
@@ -60,9 +60,10 @@ a lighter mapping detail rail (chain overview, palette, list context).
|
|
|
60
60
|
|
|
61
61
|
### Shape ownership
|
|
62
62
|
|
|
63
|
-
`FieldRemapDocument`
|
|
64
|
-
(`SourceField[]` / `TargetSlot[]`, or `defineDataShape` + ingest
|
|
65
|
-
them into `convertToShape` / the shell `FieldRemapPanel` /
|
|
63
|
+
`FieldRemapDocument` stores edges plus optional combine/split operators. Hosts own
|
|
64
|
+
input/output shapes (`SourceField[]` / `TargetSlot[]`, or `defineDataShape` + ingest
|
|
65
|
+
helpers) and pass them into `convertToShape` / the shell `FieldRemapPanel` /
|
|
66
|
+
`FieldRemapFlowMapper`.
|
|
66
67
|
Optional `classRef` / `hidden` on fields and slots are additive; use
|
|
67
68
|
`projectShapes` / `projectSourceFields` / `projectTargetSlots` with
|
|
68
69
|
`includeHidden` (default omit hidden) before wiring Flow columns, and
|
|
@@ -119,7 +120,7 @@ build `defineConversion` / `defineDataShape` registries yourself.
|
|
|
119
120
|
Place-then-wire uses **ephemeral draft nodes** in the shell Flow UI: place a
|
|
120
121
|
transform, wire source then target (or the reverse), and the draft finalizes into
|
|
121
122
|
a `MappingEdge` with `transformIds: [id]`. Escape discards unfinished drafts.
|
|
122
|
-
The persisted document stays
|
|
123
|
+
The persisted document stays mapping-only — no free graph nodes. You can also add steps
|
|
123
124
|
via the detail palette / `+ node` onto an existing binding (max 3). List context
|
|
124
125
|
uses `itemEdges` on array→array bindings.
|
|
125
126
|
|
|
@@ -135,13 +136,13 @@ uses `itemEdges` on array→array bindings.
|
|
|
135
136
|
|
|
136
137
|
### n→m operators (combine / split)
|
|
137
138
|
|
|
138
|
-
`FieldRemapDocument`
|
|
139
|
-
|
|
139
|
+
`FieldRemapDocument` v2 includes an optional `operators[]` list for fan-in / fan-out.
|
|
140
|
+
Call `applyMappingOperators` with
|
|
140
141
|
`combine` / `split` operators (limits: `MAX_MAPPING_FAN_IN` / `MAX_MAPPING_FAN_OUT`
|
|
141
142
|
= 8). Hosts may merge the result with `convertToShape` output.
|
|
142
|
-
`
|
|
143
|
-
|
|
144
|
-
|
|
143
|
+
`parseFieldRemapDocument` accepts the current version and normalizes edges and operators.
|
|
144
|
+
Shell Flow renders combine/split as multi-port nodes and supports authoring
|
|
145
|
+
(create / wire ports / delete) when hosts pass `operators` +
|
|
145
146
|
`onOperatorsChange` into `FieldRemapFlowMapper` (sample `nm-combine-split`).
|
|
146
147
|
|
|
147
148
|
```ts
|
|
@@ -269,8 +270,8 @@ Pass `signal` on `convertToShape` (or `TransformContext.signal`) to cancel stale
|
|
|
269
270
|
Aborted runs reject with `AbortError` and stop further edges / chain steps. The shell Field Remap
|
|
270
271
|
panel wires an `AbortController` to effect cleanup.
|
|
271
272
|
|
|
272
|
-
Host JSONata transforms in `@workbench-kit/shell-react` are bounded by default
|
|
273
|
-
`
|
|
273
|
+
Host JSONata transforms in `@workbench-kit/shell-react` are fail-closed and bounded by default
|
|
274
|
+
(`timeoutMs`, `maxExpressionLength`). Use `createJsonataValueTransform()` to override the bounds.
|
|
274
275
|
|
|
275
276
|
## Layout
|
|
276
277
|
|
package/package.json
CHANGED
package/src/domain/constants.ts
CHANGED
|
@@ -3,12 +3,3 @@ export const MAX_TRANSFORM_CHAIN = 3;
|
|
|
3
3
|
|
|
4
4
|
/** Built-in pass-through transform id (kept here to avoid import cycles). */
|
|
5
5
|
export const IDENTITY_TRANSFORM_ID = 'identity';
|
|
6
|
-
|
|
7
|
-
/** Legacy id aliases → canonical ids (empty until hosts need migration). */
|
|
8
|
-
export const TRANSFORM_ID_ALIASES: Readonly<Record<string, string>> = {};
|
|
9
|
-
|
|
10
|
-
/** Map a legacy or canonical transform id to its canonical form. */
|
|
11
|
-
export function canonicalizeTransformId(id: string): string {
|
|
12
|
-
const trimmed = id.trim();
|
|
13
|
-
return TRANSFORM_ID_ALIASES[trimmed] ?? trimmed;
|
|
14
|
-
}
|
|
@@ -4,20 +4,15 @@ import type { MappingEdge, MappingOperator, FieldRemapDocument } from '../types.
|
|
|
4
4
|
|
|
5
5
|
/** Current persistence version (edges + optional `operators[]`). */
|
|
6
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
7
|
|
|
10
|
-
|
|
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. */
|
|
8
|
+
/** Thrown when parse/deserialize sees a document `version` other than the current constant. */
|
|
14
9
|
export class UnsupportedFieldRemapDocumentVersionError extends Error {
|
|
15
10
|
readonly version: unknown;
|
|
16
11
|
readonly expectedVersion: typeof FIELD_REMAP_DOCUMENT_VERSION;
|
|
17
12
|
|
|
18
13
|
constructor(version: unknown) {
|
|
19
14
|
super(
|
|
20
|
-
`Unsupported field remap document version ${String(version)}; expected ${
|
|
15
|
+
`Unsupported field remap document version ${String(version)}; expected ${FIELD_REMAP_DOCUMENT_VERSION}.`,
|
|
21
16
|
);
|
|
22
17
|
this.name = 'UnsupportedFieldRemapDocumentVersionError';
|
|
23
18
|
this.version = version;
|
|
@@ -50,13 +45,9 @@ export function createFieldRemapDocument(
|
|
|
50
45
|
};
|
|
51
46
|
}
|
|
52
47
|
|
|
53
|
-
|
|
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. */
|
|
48
|
+
/** Normalize edges / operators on a current persisted document. */
|
|
58
49
|
export function normalizeFieldRemapDocument(document: FieldRemapDocument): FieldRemapDocument {
|
|
59
|
-
if (
|
|
50
|
+
if (document.version !== FIELD_REMAP_DOCUMENT_VERSION) {
|
|
60
51
|
throw new UnsupportedFieldRemapDocumentVersionError(document.version);
|
|
61
52
|
}
|
|
62
53
|
const operators = normalizeMappingOperators(document.operators);
|
|
@@ -83,8 +74,7 @@ export function serializeFieldRemapDocument(
|
|
|
83
74
|
}
|
|
84
75
|
|
|
85
76
|
/**
|
|
86
|
-
* Parse an unknown JSON value into a normalized `FieldRemapDocument`.
|
|
87
|
-
* Accepts v1 (edges-only) and v2 (optional operators); always returns current version.
|
|
77
|
+
* Parse an unknown JSON value into a normalized current `FieldRemapDocument`.
|
|
88
78
|
*/
|
|
89
79
|
export function parseFieldRemapDocument(input: unknown): FieldRemapDocument {
|
|
90
80
|
if (!input || typeof input !== 'object' || Array.isArray(input)) {
|
|
@@ -97,7 +87,7 @@ export function parseFieldRemapDocument(input: unknown): FieldRemapDocument {
|
|
|
97
87
|
if (!('version' in record)) {
|
|
98
88
|
throw new InvalidFieldRemapDocumentError('Field remap document is missing version.');
|
|
99
89
|
}
|
|
100
|
-
if (
|
|
90
|
+
if (record.version !== FIELD_REMAP_DOCUMENT_VERSION) {
|
|
101
91
|
throw new UnsupportedFieldRemapDocumentVersionError(record.version);
|
|
102
92
|
}
|
|
103
93
|
if (!Array.isArray(record.edges)) {
|
|
@@ -114,7 +104,7 @@ export function parseFieldRemapDocument(input: unknown): FieldRemapDocument {
|
|
|
114
104
|
}
|
|
115
105
|
|
|
116
106
|
return normalizeFieldRemapDocument({
|
|
117
|
-
version:
|
|
107
|
+
version: FIELD_REMAP_DOCUMENT_VERSION,
|
|
118
108
|
edges: record.edges as MappingEdge[],
|
|
119
109
|
operators: record.operators as MappingOperator[] | undefined,
|
|
120
110
|
});
|
|
@@ -131,33 +121,3 @@ export function deserializeFieldRemapDocument(json: string): FieldRemapDocument
|
|
|
131
121
|
}
|
|
132
122
|
return parseFieldRemapDocument(parsed);
|
|
133
123
|
}
|
|
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,20 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
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';
|
|
1
|
+
import { IDENTITY_TRANSFORM_ID, MAX_TRANSFORM_CHAIN } from '../constants.js';
|
|
2
|
+
import { sanitizeOptionSteps } from '../mapping/transformOptions.js';
|
|
11
3
|
import type { MappingEdge } from '../types.js';
|
|
12
4
|
|
|
13
5
|
export { MAX_TRANSFORM_CHAIN } from '../constants.js';
|
|
14
6
|
|
|
15
7
|
function sanitizeTransformIds(ids: readonly string[] | undefined): string[] {
|
|
16
8
|
const cleaned = ids
|
|
17
|
-
?.map((id) =>
|
|
9
|
+
?.map((id) => id.trim())
|
|
18
10
|
.filter((id) => id.length > 0 && id !== IDENTITY_TRANSFORM_ID);
|
|
19
11
|
if (!cleaned || cleaned.length === 0) {
|
|
20
12
|
return [];
|
|
@@ -32,12 +24,7 @@ export function edgeItemTransformIds(edge: MappingEdge): string[] {
|
|
|
32
24
|
return sanitizeTransformIds(edge.itemTransformIds);
|
|
33
25
|
}
|
|
34
26
|
|
|
35
|
-
/**
|
|
36
|
-
* Option bags:
|
|
37
|
-
* - Prefer `transformOptionSteps` when present (aligned to chain length).
|
|
38
|
-
* - Keep `transformOptions` as the first non-empty step (or the legacy shared bag).
|
|
39
|
-
* - Same rules for `itemTransformOptionSteps` / `itemTransformOptions`.
|
|
40
|
-
*/
|
|
27
|
+
/** Normalize transform chains and their aligned per-step option bags. */
|
|
41
28
|
export function normalizeMappingEdge(edge: MappingEdge): MappingEdge {
|
|
42
29
|
const ids = edgeTransformIds(edge);
|
|
43
30
|
const itemIds = edgeItemTransformIds(edge);
|
|
@@ -52,16 +39,10 @@ export function normalizeMappingEdge(edge: MappingEdge): MappingEdge {
|
|
|
52
39
|
: undefined;
|
|
53
40
|
|
|
54
41
|
const transformOptionSteps = sanitizeOptionSteps(edge.transformOptionSteps, ids.length);
|
|
55
|
-
const transformOptions =
|
|
56
|
-
sharedOptionsFromSteps(transformOptionSteps) ?? sanitizeOptionRecord(edge.transformOptions);
|
|
57
|
-
|
|
58
42
|
const itemTransformOptionSteps = sanitizeOptionSteps(
|
|
59
43
|
edge.itemTransformOptionSteps,
|
|
60
44
|
itemIds.length,
|
|
61
45
|
);
|
|
62
|
-
const itemTransformOptions =
|
|
63
|
-
sharedOptionsFromSteps(itemTransformOptionSteps) ??
|
|
64
|
-
sanitizeOptionRecord(edge.itemTransformOptions);
|
|
65
46
|
|
|
66
47
|
return {
|
|
67
48
|
id: edge.id,
|
|
@@ -69,11 +50,9 @@ export function normalizeMappingEdge(edge: MappingEdge): MappingEdge {
|
|
|
69
50
|
targetSlotId: edge.targetSlotId,
|
|
70
51
|
transformIds: ids.length > 0 ? ids : undefined,
|
|
71
52
|
...(transformOptionSteps ? { transformOptionSteps } : {}),
|
|
72
|
-
...(transformOptions ? { transformOptions } : {}),
|
|
73
53
|
...(itemSourcePath && !itemEdges ? { itemSourcePath } : {}),
|
|
74
54
|
...(itemIds.length > 0 && !itemEdges ? { itemTransformIds: itemIds } : {}),
|
|
75
55
|
...(itemTransformOptionSteps && !itemEdges ? { itemTransformOptionSteps } : {}),
|
|
76
|
-
...(itemTransformOptions && !itemEdges ? { itemTransformOptions } : {}),
|
|
77
56
|
...(itemEdges ? { itemEdges } : {}),
|
|
78
57
|
};
|
|
79
58
|
}
|
|
@@ -89,13 +68,11 @@ export function createMappingEdge(input: {
|
|
|
89
68
|
readonly targetSlotId: string;
|
|
90
69
|
readonly transformIds?: readonly string[];
|
|
91
70
|
readonly transformOptionSteps?: readonly (Readonly<Record<string, unknown>> | undefined)[];
|
|
92
|
-
readonly transformOptions?: Readonly<Record<string, unknown>>;
|
|
93
71
|
/** Optional per-item projection path for array mappings. */
|
|
94
72
|
readonly itemSourcePath?: string;
|
|
95
73
|
/** Optional per-item transform chain after projection. */
|
|
96
74
|
readonly itemTransformIds?: readonly string[];
|
|
97
75
|
readonly itemTransformOptionSteps?: readonly (Readonly<Record<string, unknown>> | undefined)[];
|
|
98
|
-
readonly itemTransformOptions?: Readonly<Record<string, unknown>>;
|
|
99
76
|
/** List-context child edges for array-of-object → array-of-object. */
|
|
100
77
|
readonly itemEdges?: readonly MappingEdge[];
|
|
101
78
|
}): MappingEdge {
|
|
@@ -105,11 +82,9 @@ export function createMappingEdge(input: {
|
|
|
105
82
|
targetSlotId: input.targetSlotId,
|
|
106
83
|
transformIds: input.transformIds,
|
|
107
84
|
transformOptionSteps: input.transformOptionSteps,
|
|
108
|
-
transformOptions: input.transformOptions,
|
|
109
85
|
itemSourcePath: input.itemSourcePath,
|
|
110
86
|
itemTransformIds: input.itemTransformIds,
|
|
111
87
|
itemTransformOptionSteps: input.itemTransformOptionSteps,
|
|
112
|
-
itemTransformOptions: input.itemTransformOptions,
|
|
113
88
|
itemEdges: input.itemEdges,
|
|
114
89
|
});
|
|
115
90
|
}
|
|
@@ -75,7 +75,7 @@ export async function convertArrayWithItemEdges(input: {
|
|
|
75
75
|
? await identity.apply(sourceValue, { ...input.context, sampleValue: sourceValue })
|
|
76
76
|
: sourceValue;
|
|
77
77
|
} else {
|
|
78
|
-
const steps = resolveOptionSteps(chain, edge.transformOptionSteps
|
|
78
|
+
const steps = resolveOptionSteps(chain, edge.transformOptionSteps);
|
|
79
79
|
value = await applyTransformChain(
|
|
80
80
|
input.transforms,
|
|
81
81
|
chain,
|
|
@@ -4,11 +4,7 @@
|
|
|
4
4
|
* {@link applyMappingOperators} explicitly without persisting.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
canonicalizeTransformId,
|
|
9
|
-
IDENTITY_TRANSFORM_ID,
|
|
10
|
-
MAX_TRANSFORM_CHAIN,
|
|
11
|
-
} from '../constants.js';
|
|
7
|
+
import { IDENTITY_TRANSFORM_ID, MAX_TRANSFORM_CHAIN } from '../constants.js';
|
|
12
8
|
import { throwIfAborted } from '../abort.js';
|
|
13
9
|
import type {
|
|
14
10
|
CombineMappingOperator,
|
|
@@ -113,7 +109,7 @@ function readFieldValue(field: SourceField, inputs: Readonly<Record<string, unkn
|
|
|
113
109
|
|
|
114
110
|
function sanitizeOperatorTransformIds(ids: readonly string[] | undefined): string[] | undefined {
|
|
115
111
|
const cleaned = ids
|
|
116
|
-
?.map((id) =>
|
|
112
|
+
?.map((id) => id.trim())
|
|
117
113
|
.filter((id) => id.length > 0 && id !== IDENTITY_TRANSFORM_ID);
|
|
118
114
|
if (!cleaned || cleaned.length === 0) {
|
|
119
115
|
return undefined;
|
|
@@ -30,8 +30,7 @@ export function findSourceField(
|
|
|
30
30
|
* 2. Optional `itemTransformIds` per element (when the value is still an array)
|
|
31
31
|
* 3. `transformIds` on the whole value (including array reduces)
|
|
32
32
|
*
|
|
33
|
-
* Per-step options
|
|
34
|
-
* present; otherwise shared `transformOptions` / `itemTransformOptions` apply to all steps.
|
|
33
|
+
* Per-step options are aligned through `transformOptionSteps` / `itemTransformOptionSteps`.
|
|
35
34
|
*/
|
|
36
35
|
export async function resolveMappedValue(
|
|
37
36
|
edge: MappingEdge,
|
|
@@ -44,11 +43,7 @@ export async function resolveMappedValue(
|
|
|
44
43
|
: sourceValue;
|
|
45
44
|
|
|
46
45
|
const itemChain = edgeItemTransformIds(edge);
|
|
47
|
-
const itemSteps = resolveOptionSteps(
|
|
48
|
-
itemChain,
|
|
49
|
-
edge.itemTransformOptionSteps,
|
|
50
|
-
edge.itemTransformOptions,
|
|
51
|
-
);
|
|
46
|
+
const itemSteps = resolveOptionSteps(itemChain, edge.itemTransformOptionSteps);
|
|
52
47
|
|
|
53
48
|
if (itemChain.length > 0 && Array.isArray(current)) {
|
|
54
49
|
current = await Promise.all(
|
|
@@ -62,7 +57,7 @@ export async function resolveMappedValue(
|
|
|
62
57
|
return identity ? await identity.apply(current, context) : current;
|
|
63
58
|
}
|
|
64
59
|
|
|
65
|
-
const valueSteps = resolveOptionSteps(chain, edge.transformOptionSteps
|
|
60
|
+
const valueSteps = resolveOptionSteps(chain, edge.transformOptionSteps);
|
|
66
61
|
return applyTransformChain(registry, chain, current, context, valueSteps);
|
|
67
62
|
}
|
|
68
63
|
|
|
@@ -1,39 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
/** Merge edge-local options over host `context.options` (edge wins). */
|
|
4
|
-
export function contextWithEdgeOptions(
|
|
5
|
-
context: TransformContext,
|
|
6
|
-
edgeOptions: Readonly<Record<string, unknown>> | undefined,
|
|
7
|
-
): TransformContext {
|
|
8
|
-
if (!edgeOptions || Object.keys(edgeOptions).length === 0) {
|
|
9
|
-
return context;
|
|
10
|
-
}
|
|
11
|
-
return {
|
|
12
|
-
...context,
|
|
13
|
-
options: {
|
|
14
|
-
...context.options,
|
|
15
|
-
...edgeOptions,
|
|
16
|
-
},
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** Collect unique `optionFields` declared by transforms in a chain (later ids win per key). */
|
|
21
|
-
export function collectOptionFields(
|
|
22
|
-
registry: ValueTransformRegistry,
|
|
23
|
-
transformIds: readonly string[],
|
|
24
|
-
): TransformOptionField[] {
|
|
25
|
-
const byKey = new Map<string, TransformOptionField>();
|
|
26
|
-
for (const id of transformIds) {
|
|
27
|
-
const fields = registry.get(id)?.optionFields;
|
|
28
|
-
if (!fields) {
|
|
29
|
-
continue;
|
|
30
|
-
}
|
|
31
|
-
for (const field of fields) {
|
|
32
|
-
byKey.set(field.key, field);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return [...byKey.values()];
|
|
36
|
-
}
|
|
1
|
+
import type { TransformOptionField, ValueTransformRegistry } from '../types.js';
|
|
37
2
|
|
|
38
3
|
/** Option fields for a single chain step. */
|
|
39
4
|
export function optionFieldsForStep(
|
|
@@ -63,17 +28,6 @@ export function sanitizeOptionRecord(
|
|
|
63
28
|
return Object.keys(next).length > 0 ? next : undefined;
|
|
64
29
|
}
|
|
65
30
|
|
|
66
|
-
export function patchOptionRecord(
|
|
67
|
-
previous: Readonly<Record<string, unknown>> | undefined,
|
|
68
|
-
key: string,
|
|
69
|
-
value: unknown,
|
|
70
|
-
): Readonly<Record<string, unknown>> | undefined {
|
|
71
|
-
return sanitizeOptionRecord({
|
|
72
|
-
...previous,
|
|
73
|
-
[key]: value,
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
31
|
/**
|
|
78
32
|
* Align / sanitize per-step option bags to `length`.
|
|
79
33
|
* Returns `undefined` when every step is empty.
|
|
@@ -97,79 +51,16 @@ export function sanitizeOptionSteps(
|
|
|
97
51
|
return any ? next : undefined;
|
|
98
52
|
}
|
|
99
53
|
|
|
100
|
-
/**
|
|
101
|
-
* Resolve per-step options for a transform chain.
|
|
102
|
-
* Prefers `steps`; otherwise expands shared `transformOptions` to every step (apply-to-all).
|
|
103
|
-
*/
|
|
54
|
+
/** Resolve sanitized per-step options aligned to a transform chain. */
|
|
104
55
|
export function resolveOptionSteps(
|
|
105
56
|
transformIds: readonly string[],
|
|
106
57
|
steps: readonly (Readonly<Record<string, unknown>> | undefined)[] | undefined,
|
|
107
|
-
shared: Readonly<Record<string, unknown>> | undefined,
|
|
108
58
|
): (Readonly<Record<string, unknown>> | undefined)[] {
|
|
109
59
|
const length = transformIds.length;
|
|
110
60
|
if (length === 0) {
|
|
111
61
|
return [];
|
|
112
62
|
}
|
|
113
|
-
|
|
114
|
-
return Array.from({ length }, (_, index) => sanitizeOptionRecord(steps[index]));
|
|
115
|
-
}
|
|
116
|
-
const bag = sanitizeOptionRecord(shared);
|
|
117
|
-
if (!bag) {
|
|
118
|
-
return Array.from({ length }, () => undefined);
|
|
119
|
-
}
|
|
120
|
-
return Array.from({ length }, () => bag);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/** Back-compat summary: first non-empty step bag (else undefined). */
|
|
124
|
-
export function sharedOptionsFromSteps(
|
|
125
|
-
steps: readonly (Readonly<Record<string, unknown>> | undefined)[] | undefined,
|
|
126
|
-
): Readonly<Record<string, unknown>> | undefined {
|
|
127
|
-
if (!steps) {
|
|
128
|
-
return undefined;
|
|
129
|
-
}
|
|
130
|
-
for (const step of steps) {
|
|
131
|
-
const sanitized = sanitizeOptionRecord(step);
|
|
132
|
-
if (sanitized) {
|
|
133
|
-
return sanitized;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
return undefined;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/** Merge all step bags (later steps win) — useful for live format-sample chips. */
|
|
140
|
-
export function mergeOptionSteps(
|
|
141
|
-
steps: readonly (Readonly<Record<string, unknown>> | undefined)[] | undefined,
|
|
142
|
-
): Readonly<Record<string, unknown>> | undefined {
|
|
143
|
-
if (!steps || steps.length === 0) {
|
|
144
|
-
return undefined;
|
|
145
|
-
}
|
|
146
|
-
const merged: Record<string, unknown> = {};
|
|
147
|
-
for (const step of steps) {
|
|
148
|
-
if (!step) {
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
Object.assign(merged, step);
|
|
152
|
-
}
|
|
153
|
-
return sanitizeOptionRecord(merged);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export function patchOptionStep(
|
|
157
|
-
steps: readonly (Readonly<Record<string, unknown>> | undefined)[] | undefined,
|
|
158
|
-
length: number,
|
|
159
|
-
index: number,
|
|
160
|
-
key: string,
|
|
161
|
-
value: unknown,
|
|
162
|
-
sharedFallback?: Readonly<Record<string, unknown>>,
|
|
163
|
-
): readonly (Readonly<Record<string, unknown>> | undefined)[] | undefined {
|
|
164
|
-
const base = resolveOptionSteps(
|
|
165
|
-
Array.from({ length }, () => ''),
|
|
166
|
-
steps,
|
|
167
|
-
sharedFallback,
|
|
168
|
-
);
|
|
169
|
-
const next = base.map((step, stepIndex) =>
|
|
170
|
-
stepIndex === index ? patchOptionRecord(step, key, value) : step,
|
|
171
|
-
);
|
|
172
|
-
return sanitizeOptionSteps(next, length);
|
|
63
|
+
return Array.from({ length }, (_, index) => sanitizeOptionRecord(steps?.[index]));
|
|
173
64
|
}
|
|
174
65
|
|
|
175
66
|
export function resizeOptionSteps(
|
package/src/domain/types.ts
CHANGED
|
@@ -87,15 +87,9 @@ export interface MappingEdge {
|
|
|
87
87
|
readonly transformIds?: readonly string[];
|
|
88
88
|
/**
|
|
89
89
|
* Per-step options aligned with `transformIds` (index N applies to step N).
|
|
90
|
-
*
|
|
90
|
+
* Steps may use different bags (e.g. `showSeconds` then `maxLength`).
|
|
91
91
|
*/
|
|
92
92
|
readonly transformOptionSteps?: readonly (Readonly<Record<string, unknown>> | undefined)[];
|
|
93
|
-
/**
|
|
94
|
-
* Shared options for all `transformIds` steps (legacy / apply-to-all).
|
|
95
|
-
* Used when `transformOptionSteps` is omitted. Still written as a back-compat
|
|
96
|
-
* summary of step 0 (or the first non-empty step) by `normalizeMappingEdge`.
|
|
97
|
-
*/
|
|
98
|
-
readonly transformOptions?: Readonly<Record<string, unknown>>;
|
|
99
93
|
/**
|
|
100
94
|
* When the source is an array of objects, optional dotted path into each item
|
|
101
95
|
* (e.g. `name` or `meta.label`) before the value is written to the target.
|
|
@@ -113,11 +107,6 @@ export interface MappingEdge {
|
|
|
113
107
|
* Per-step options aligned with `itemTransformIds`.
|
|
114
108
|
*/
|
|
115
109
|
readonly itemTransformOptionSteps?: readonly (Readonly<Record<string, unknown>> | undefined)[];
|
|
116
|
-
/**
|
|
117
|
-
* Shared options for all `itemTransformIds` steps (legacy / apply-to-all).
|
|
118
|
-
* Independent of `transformOptions` / `transformOptionSteps`.
|
|
119
|
-
*/
|
|
120
|
-
readonly itemTransformOptions?: Readonly<Record<string, unknown>>;
|
|
121
110
|
/**
|
|
122
111
|
* List-context child bindings (Stedi-style): when source is an array of objects,
|
|
123
112
|
* each element is converted through these edges into a target item object.
|
|
@@ -154,16 +143,14 @@ export type MappingOperator = CombineMappingOperator | SplitMappingOperator;
|
|
|
154
143
|
|
|
155
144
|
/**
|
|
156
145
|
* Minimal JSON-serializable mapping document for host persistence.
|
|
157
|
-
* Hosts own schema trees; this document stores the binding graph
|
|
158
|
-
* n→m operators from document v2 onward).
|
|
146
|
+
* Hosts own schema trees; this document stores the binding graph and optional n→m operators.
|
|
159
147
|
*/
|
|
160
148
|
export interface FieldRemapDocument {
|
|
161
|
-
|
|
162
|
-
readonly version: 1 | 2;
|
|
149
|
+
readonly version: 2;
|
|
163
150
|
readonly edges: readonly MappingEdge[];
|
|
164
151
|
/**
|
|
165
|
-
* Optional n→m combine/split operators
|
|
166
|
-
* Omitted / empty on
|
|
152
|
+
* Optional n→m combine/split operators.
|
|
153
|
+
* Omitted / empty on hosts that only use 1→1 edges.
|
|
167
154
|
*/
|
|
168
155
|
readonly operators?: readonly MappingOperator[];
|
|
169
156
|
}
|
|
@@ -216,8 +203,7 @@ export interface ValueTransformDefinition {
|
|
|
216
203
|
/**
|
|
217
204
|
* Data-driven option editors (mapped rows / convert panel).
|
|
218
205
|
* Values are stored per step (`transformOptionSteps` / `itemTransformOptionSteps`)
|
|
219
|
-
* or
|
|
220
|
-
* `TransformContext.options`.
|
|
206
|
+
* or on host `TransformContext.options`.
|
|
221
207
|
*/
|
|
222
208
|
readonly optionFields?: readonly TransformOptionField[];
|
|
223
209
|
}
|
package/src/index.ts
CHANGED
|
@@ -15,12 +15,7 @@ export type {
|
|
|
15
15
|
ValueTransformRegistry,
|
|
16
16
|
} from './domain/types.js';
|
|
17
17
|
|
|
18
|
-
export {
|
|
19
|
-
canonicalizeTransformId,
|
|
20
|
-
IDENTITY_TRANSFORM_ID,
|
|
21
|
-
MAX_TRANSFORM_CHAIN,
|
|
22
|
-
TRANSFORM_ID_ALIASES,
|
|
23
|
-
} from './domain/constants.js';
|
|
18
|
+
export { IDENTITY_TRANSFORM_ID, MAX_TRANSFORM_CHAIN } from './domain/constants.js';
|
|
24
19
|
|
|
25
20
|
export {
|
|
26
21
|
createMappingEdge,
|
|
@@ -34,11 +29,9 @@ export {
|
|
|
34
29
|
createFieldRemapDocument,
|
|
35
30
|
deserializeFieldRemapDocument,
|
|
36
31
|
InvalidFieldRemapDocumentError,
|
|
37
|
-
migrateFieldRemapDocument,
|
|
38
32
|
normalizeFieldRemapDocument,
|
|
39
33
|
parseFieldRemapDocument,
|
|
40
34
|
FIELD_REMAP_DOCUMENT_VERSION,
|
|
41
|
-
FIELD_REMAP_DOCUMENT_V1_VERSION,
|
|
42
35
|
serializeFieldRemapDocument,
|
|
43
36
|
UnsupportedFieldRemapDocumentVersionError,
|
|
44
37
|
} from './domain/document/fieldRemapDocument.js';
|
|
@@ -93,17 +86,11 @@ export type {
|
|
|
93
86
|
} from './domain/mapping/mappingOperators.js';
|
|
94
87
|
|
|
95
88
|
export {
|
|
96
|
-
collectOptionFields,
|
|
97
|
-
contextWithEdgeOptions,
|
|
98
|
-
mergeOptionSteps,
|
|
99
89
|
optionFieldsForStep,
|
|
100
|
-
patchOptionRecord,
|
|
101
|
-
patchOptionStep,
|
|
102
90
|
resolveOptionSteps,
|
|
103
91
|
resizeOptionSteps,
|
|
104
92
|
sanitizeOptionRecord,
|
|
105
93
|
sanitizeOptionSteps,
|
|
106
|
-
sharedOptionsFromSteps,
|
|
107
94
|
} from './domain/mapping/transformOptions.js';
|
|
108
95
|
|
|
109
96
|
export {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { throwIfAborted } from '../domain/abort.js';
|
|
2
|
-
import {
|
|
2
|
+
import { MAX_TRANSFORM_CHAIN } from '../domain/constants.js';
|
|
3
3
|
import type {
|
|
4
4
|
FieldDataType,
|
|
5
5
|
TransformContext,
|
|
@@ -18,8 +18,7 @@ export function createValueTransformRegistry(
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
function resolve(id: string): ValueTransformDefinition | undefined {
|
|
21
|
-
|
|
22
|
-
return byId.get(canonical) ?? byId.get(id);
|
|
21
|
+
return byId.get(id.trim()) ?? byId.get(id);
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
return {
|