@workbench-kit/field-remap 0.0.2-prototype.0.2.26 → 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 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` (v1) stores **edges only**. Hosts own input/output shapes
64
- (`SourceField[]` / `TargetSlot[]`, or `defineDataShape` + ingest helpers) and pass
65
- them into `convertToShape` / the shell `FieldRemapPanel` / `FieldRemapFlowMapper`.
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 edges-only — no free graph. You can also add steps
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` **v1** is edges-only (1→1 bindings). **v2** (current) adds an
139
- optional `operators[]` list for fan-in / fan-out. Call `applyMappingOperators` with
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
- `migrateFieldRemapDocument` / `parseFieldRemapDocument` accept v1 and v2 and always
143
- emit the current version. Shell Flow renders combine/split as multi-port nodes and
144
- supports authoring (create / wire ports / delete) when hosts pass `operators` +
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 (`timeoutMs`,
273
- `maxExpressionLength`, `onError: 'throw'`). Use `createJsonataValueTransform()` to override.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workbench-kit/field-remap",
3
- "version": "0.0.2-prototype.0.2.26",
3
+ "version": "0.0.2-prototype.0.2.28",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -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
- 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. */
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 ${FIELD_REMAP_DOCUMENT_V1_VERSION} or ${FIELD_REMAP_DOCUMENT_VERSION}.`,
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
- 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. */
48
+ /** Normalize edges / operators on a current persisted document. */
58
49
  export function normalizeFieldRemapDocument(document: FieldRemapDocument): FieldRemapDocument {
59
- if (!isSupportedVersion(document.version)) {
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 (!isSupportedVersion(record.version)) {
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: record.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
- 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';
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) => canonicalizeTransformId(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 [];
@@ -22,23 +14,9 @@ function sanitizeTransformIds(ids: readonly string[] | undefined): string[] {
22
14
  return cleaned.slice(0, MAX_TRANSFORM_CHAIN);
23
15
  }
24
16
 
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
- */
17
+ /** Resolve the effective transform chain for an edge. Identity / empty → pass-through. */
30
18
  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 [];
19
+ return sanitizeTransformIds(edge.transformIds);
42
20
  }
43
21
 
44
22
  /** Per-item transform chain applied after optional `itemSourcePath` projection. */
@@ -46,15 +24,7 @@ export function edgeItemTransformIds(edge: MappingEdge): string[] {
46
24
  return sanitizeTransformIds(edge.itemTransformIds);
47
25
  }
48
26
 
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
- */
27
+ /** Normalize transform chains and their aligned per-step option bags. */
58
28
  export function normalizeMappingEdge(edge: MappingEdge): MappingEdge {
59
29
  const ids = edgeTransformIds(edge);
60
30
  const itemIds = edgeItemTransformIds(edge);
@@ -69,29 +39,20 @@ export function normalizeMappingEdge(edge: MappingEdge): MappingEdge {
69
39
  : undefined;
70
40
 
71
41
  const transformOptionSteps = sanitizeOptionSteps(edge.transformOptionSteps, ids.length);
72
- const transformOptions =
73
- sharedOptionsFromSteps(transformOptionSteps) ?? sanitizeOptionRecord(edge.transformOptions);
74
-
75
42
  const itemTransformOptionSteps = sanitizeOptionSteps(
76
43
  edge.itemTransformOptionSteps,
77
44
  itemIds.length,
78
45
  );
79
- const itemTransformOptions =
80
- sharedOptionsFromSteps(itemTransformOptionSteps) ??
81
- sanitizeOptionRecord(edge.itemTransformOptions);
82
46
 
83
47
  return {
84
48
  id: edge.id,
85
49
  sourceFieldId: edge.sourceFieldId,
86
50
  targetSlotId: edge.targetSlotId,
87
51
  transformIds: ids.length > 0 ? ids : undefined,
88
- transformId: ids[0] ?? null,
89
52
  ...(transformOptionSteps ? { transformOptionSteps } : {}),
90
- ...(transformOptions ? { transformOptions } : {}),
91
53
  ...(itemSourcePath && !itemEdges ? { itemSourcePath } : {}),
92
54
  ...(itemIds.length > 0 && !itemEdges ? { itemTransformIds: itemIds } : {}),
93
55
  ...(itemTransformOptionSteps && !itemEdges ? { itemTransformOptionSteps } : {}),
94
- ...(itemTransformOptions && !itemEdges ? { itemTransformOptions } : {}),
95
56
  ...(itemEdges ? { itemEdges } : {}),
96
57
  };
97
58
  }
@@ -106,16 +67,12 @@ export function createMappingEdge(input: {
106
67
  readonly sourceFieldId: string;
107
68
  readonly targetSlotId: string;
108
69
  readonly transformIds?: readonly string[];
109
- /** Legacy single id; ignored when `transformIds` is provided. */
110
- readonly transformId?: string | null;
111
70
  readonly transformOptionSteps?: readonly (Readonly<Record<string, unknown>> | undefined)[];
112
- readonly transformOptions?: Readonly<Record<string, unknown>>;
113
71
  /** Optional per-item projection path for array mappings. */
114
72
  readonly itemSourcePath?: string;
115
73
  /** Optional per-item transform chain after projection. */
116
74
  readonly itemTransformIds?: readonly string[];
117
75
  readonly itemTransformOptionSteps?: readonly (Readonly<Record<string, unknown>> | undefined)[];
118
- readonly itemTransformOptions?: Readonly<Record<string, unknown>>;
119
76
  /** List-context child edges for array-of-object → array-of-object. */
120
77
  readonly itemEdges?: readonly MappingEdge[];
121
78
  }): MappingEdge {
@@ -124,13 +81,10 @@ export function createMappingEdge(input: {
124
81
  sourceFieldId: input.sourceFieldId,
125
82
  targetSlotId: input.targetSlotId,
126
83
  transformIds: input.transformIds,
127
- transformId: input.transformId,
128
84
  transformOptionSteps: input.transformOptionSteps,
129
- transformOptions: input.transformOptions,
130
85
  itemSourcePath: input.itemSourcePath,
131
86
  itemTransformIds: input.itemTransformIds,
132
87
  itemTransformOptionSteps: input.itemTransformOptionSteps,
133
- itemTransformOptions: input.itemTransformOptions,
134
88
  itemEdges: input.itemEdges,
135
89
  });
136
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, edge.transformOptions);
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) => canonicalizeTransformId(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;
@@ -103,21 +103,6 @@ export function parseObjectPath(path: string): ObjectPathSegment[] {
103
103
  });
104
104
  }
105
105
 
106
- /**
107
- * Parse dotted path segments as plain property names (no `[index]` / `[*]`).
108
- * Rejects unsafe segments when any parts exist.
109
- */
110
- export function requireObjectPathParts(path: string): string[] {
111
- const segments = parseObjectPath(path);
112
- if (segments.some((segment) => segment.kind !== 'property')) {
113
- throw new InvalidObjectPathError(
114
- path,
115
- 'index/wildcard segments are not allowed in this context',
116
- );
117
- }
118
- return segments.map((segment) => segment.name);
119
- }
120
-
121
106
  export function isSafeObjectPath(path: string): boolean {
122
107
  const trimmed = path.trim();
123
108
  if (!SAFE_PATH_RE.test(trimmed)) {
@@ -28,10 +28,9 @@ export function findSourceField(
28
28
  * Apply order:
29
29
  * 1. Optional `itemSourcePath` projection (array of objects → projected array)
30
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)
31
+ * 3. `transformIds` on the whole value (including array reduces)
32
32
  *
33
- * Per-step options (`transformOptionSteps` / `itemTransformOptionSteps`) win when
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, edge.transformOptions);
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 { TransformContext, TransformOptionField, ValueTransformRegistry } from '../types.js';
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
- if (steps && steps.length > 0) {
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(
@@ -81,28 +81,15 @@ export interface MappingEdge {
81
81
  readonly targetSlotId: string;
82
82
  /**
83
83
  * Ordered transform chain (max 3). Empty / omitted means identity.
84
- * Prefer this over `transformId` for new writers.
85
84
  * For arrays: use reduce builtins (`array:join`, `array:first`, …) here after
86
85
  * optional item projection / item transforms.
87
86
  */
88
87
  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
88
  /**
96
89
  * 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`).
90
+ * Steps may use different bags (e.g. `showSeconds` then `maxLength`).
98
91
  */
99
92
  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
93
  /**
107
94
  * When the source is an array of objects, optional dotted path into each item
108
95
  * (e.g. `name` or `meta.label`) before the value is written to the target.
@@ -120,11 +107,6 @@ export interface MappingEdge {
120
107
  * Per-step options aligned with `itemTransformIds`.
121
108
  */
122
109
  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
110
  /**
129
111
  * List-context child bindings (Stedi-style): when source is an array of objects,
130
112
  * each element is converted through these edges into a target item object.
@@ -161,16 +143,14 @@ export type MappingOperator = CombineMappingOperator | SplitMappingOperator;
161
143
 
162
144
  /**
163
145
  * 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).
146
+ * Hosts own schema trees; this document stores the binding graph and optional n→m operators.
166
147
  */
167
148
  export interface FieldRemapDocument {
168
- /** `1` = edges-only; `2` = edges + optional `operators[]`. */
169
- readonly version: 1 | 2;
149
+ readonly version: 2;
170
150
  readonly edges: readonly MappingEdge[];
171
151
  /**
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.
152
+ * Optional n→m combine/split operators.
153
+ * Omitted / empty on hosts that only use 1→1 edges.
174
154
  */
175
155
  readonly operators?: readonly MappingOperator[];
176
156
  }
@@ -223,8 +203,7 @@ export interface ValueTransformDefinition {
223
203
  /**
224
204
  * Data-driven option editors (mapped rows / convert panel).
225
205
  * Values are stored per step (`transformOptionSteps` / `itemTransformOptionSteps`)
226
- * or as a shared bag (`transformOptions` / `itemTransformOptions`), or on host
227
- * `TransformContext.options`.
206
+ * or on host `TransformContext.options`.
228
207
  */
229
208
  readonly optionFields?: readonly TransformOptionField[];
230
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 { canonicalizeTransformId, MAX_TRANSFORM_CHAIN } from '../domain/constants.js';
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
- const canonical = canonicalizeTransformId(id);
22
- return byId.get(canonical) ?? byId.get(id);
21
+ return byId.get(id.trim()) ?? byId.get(id);
23
22
  }
24
23
 
25
24
  return {