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

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,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
+ }
@@ -1,103 +1,103 @@
1
- import { isPlainObject } from '../mapping/pathUtils.js';
2
- import type { FieldDataType, SourceField } from '../types.js';
3
-
4
- export interface SourceFieldsFromPlainObjectOptions {
5
- /** Prefix for generated field ids (default `src`). */
6
- readonly idPrefix?: string;
7
- /** Max nesting depth for object children (default 4). */
8
- readonly maxDepth?: number;
9
- }
10
-
11
- function inferDataType(value: unknown): FieldDataType {
12
- if (value === null || value === undefined) {
13
- return 'unknown';
14
- }
15
- if (typeof value === 'string') {
16
- return 'string';
17
- }
18
- if (typeof value === 'number' && Number.isFinite(value)) {
19
- return 'number';
20
- }
21
- if (typeof value === 'boolean') {
22
- return 'boolean';
23
- }
24
- if (value instanceof Date && !Number.isNaN(value.getTime())) {
25
- return 'datetime';
26
- }
27
- if (Array.isArray(value)) {
28
- return 'array';
29
- }
30
- if (isPlainObject(value)) {
31
- return 'object';
32
- }
33
- return 'unknown';
34
- }
35
-
36
- function fieldFromEntry(
37
- key: string,
38
- value: unknown,
39
- path: string,
40
- idPrefix: string,
41
- depth: number,
42
- maxDepth: number,
43
- ): SourceField {
44
- const id = path ? `${idPrefix}.${path}` : `${idPrefix}.${key}`;
45
- const dataType = inferDataType(value);
46
- const base: SourceField = {
47
- id,
48
- label: key,
49
- path,
50
- dataType,
51
- sampleValue: value,
52
- };
53
-
54
- if (depth >= maxDepth) {
55
- return base;
56
- }
57
-
58
- if (isPlainObject(value)) {
59
- const children = Object.entries(value).map(([childKey, childValue]) =>
60
- fieldFromEntry(
61
- childKey,
62
- childValue,
63
- path ? `${path}.${childKey}` : childKey,
64
- idPrefix,
65
- depth + 1,
66
- maxDepth,
67
- ),
68
- );
69
- return children.length > 0 ? { ...base, children } : base;
70
- }
71
-
72
- if (Array.isArray(value) && value.length > 0 && isPlainObject(value[0])) {
73
- const sampleItem = value[0] as Record<string, unknown>;
74
- const children = Object.entries(sampleItem).map(([childKey, childValue]) =>
75
- fieldFromEntry(childKey, childValue, childKey, `${id}.item`, depth + 1, maxDepth),
76
- );
77
- return children.length > 0 ? { ...base, children } : base;
78
- }
79
-
80
- return base;
81
- }
82
-
83
- /**
84
- * Infer a shallow/nested `SourceField` tree from a plain sample object.
85
- * Useful for demos and hosts that have a sample payload but no formal schema.
86
- *
87
- * - Top-level keys become root fields.
88
- * - Nested plain objects become `children` (up to `maxDepth`).
89
- * - Arrays of objects expose item-key children for projection pickers.
90
- */
91
- export function sourceFieldsFromPlainObject(
92
- sample: unknown,
93
- options: SourceFieldsFromPlainObjectOptions = {},
94
- ): SourceField[] {
95
- if (!isPlainObject(sample)) {
96
- return [];
97
- }
98
- const idPrefix = options.idPrefix?.trim() || 'src';
99
- const maxDepth = Math.max(0, options.maxDepth ?? 4);
100
- return Object.entries(sample).map(([key, value]) =>
101
- fieldFromEntry(key, value, key, idPrefix, 0, maxDepth),
102
- );
103
- }
1
+ import { isPlainObject } from '../mapping/pathUtils.js';
2
+ import type { FieldDataType, SourceField } from '../types.js';
3
+
4
+ export interface SourceFieldsFromPlainObjectOptions {
5
+ /** Prefix for generated field ids (default `src`). */
6
+ readonly idPrefix?: string;
7
+ /** Max nesting depth for object children (default 4). */
8
+ readonly maxDepth?: number;
9
+ }
10
+
11
+ function inferDataType(value: unknown): FieldDataType {
12
+ if (value === null || value === undefined) {
13
+ return 'unknown';
14
+ }
15
+ if (typeof value === 'string') {
16
+ return 'string';
17
+ }
18
+ if (typeof value === 'number' && Number.isFinite(value)) {
19
+ return 'number';
20
+ }
21
+ if (typeof value === 'boolean') {
22
+ return 'boolean';
23
+ }
24
+ if (value instanceof Date && !Number.isNaN(value.getTime())) {
25
+ return 'datetime';
26
+ }
27
+ if (Array.isArray(value)) {
28
+ return 'array';
29
+ }
30
+ if (isPlainObject(value)) {
31
+ return 'object';
32
+ }
33
+ return 'unknown';
34
+ }
35
+
36
+ function fieldFromEntry(
37
+ key: string,
38
+ value: unknown,
39
+ path: string,
40
+ idPrefix: string,
41
+ depth: number,
42
+ maxDepth: number,
43
+ ): SourceField {
44
+ const id = path ? `${idPrefix}.${path}` : `${idPrefix}.${key}`;
45
+ const dataType = inferDataType(value);
46
+ const base: SourceField = {
47
+ id,
48
+ label: key,
49
+ path,
50
+ dataType,
51
+ sampleValue: value,
52
+ };
53
+
54
+ if (depth >= maxDepth) {
55
+ return base;
56
+ }
57
+
58
+ if (isPlainObject(value)) {
59
+ const children = Object.entries(value).map(([childKey, childValue]) =>
60
+ fieldFromEntry(
61
+ childKey,
62
+ childValue,
63
+ path ? `${path}.${childKey}` : childKey,
64
+ idPrefix,
65
+ depth + 1,
66
+ maxDepth,
67
+ ),
68
+ );
69
+ return children.length > 0 ? { ...base, children } : base;
70
+ }
71
+
72
+ if (Array.isArray(value) && value.length > 0 && isPlainObject(value[0])) {
73
+ const sampleItem = value[0] as Record<string, unknown>;
74
+ const children = Object.entries(sampleItem).map(([childKey, childValue]) =>
75
+ fieldFromEntry(childKey, childValue, childKey, `${id}.item`, depth + 1, maxDepth),
76
+ );
77
+ return children.length > 0 ? { ...base, children } : base;
78
+ }
79
+
80
+ return base;
81
+ }
82
+
83
+ /**
84
+ * Infer a shallow/nested `SourceField` tree from a plain sample object.
85
+ * Useful for demos and hosts that have a sample payload but no formal schema.
86
+ *
87
+ * - Top-level keys become root fields.
88
+ * - Nested plain objects become `children` (up to `maxDepth`).
89
+ * - Arrays of objects expose item-key children for projection pickers.
90
+ */
91
+ export function sourceFieldsFromPlainObject(
92
+ sample: unknown,
93
+ options: SourceFieldsFromPlainObjectOptions = {},
94
+ ): SourceField[] {
95
+ if (!isPlainObject(sample)) {
96
+ return [];
97
+ }
98
+ const idPrefix = options.idPrefix?.trim() || 'src';
99
+ const maxDepth = Math.max(0, options.maxDepth ?? 4);
100
+ return Object.entries(sample).map(([key, value]) =>
101
+ fieldFromEntry(key, value, key, idPrefix, 0, maxDepth),
102
+ );
103
+ }
@@ -1,41 +1,41 @@
1
- import { sourceFieldsFromPlainObject } from './sourceFieldsFromPlainObject.js';
2
- import type { SourceField, TargetSlot } from '../types.js';
3
-
4
- export interface TargetSlotsFromPlainObjectOptions {
5
- /** Prefix for generated slot ids (default `tgt`). */
6
- readonly idPrefix?: string;
7
- /** Max nesting depth for object children (default 4). */
8
- readonly maxDepth?: number;
9
- }
10
-
11
- function sourceFieldToTargetSlot(field: SourceField): TargetSlot {
12
- const slot: TargetSlot = {
13
- id: field.id,
14
- label: field.label,
15
- ...(field.path ? { path: field.path } : {}),
16
- ...(field.dataType ? { dataType: field.dataType } : {}),
17
- };
18
- if (field.children && field.children.length > 0) {
19
- return {
20
- ...slot,
21
- children: field.children.map(sourceFieldToTargetSlot),
22
- };
23
- }
24
- return slot;
25
- }
26
-
27
- /**
28
- * Infer a nested `TargetSlot` tree from a plain sample object (or target shape).
29
- * Mirrors {@link sourceFieldsFromPlainObject}: same nesting / array-of-object rules,
30
- * keeping `path` for `convertToShape` output assembly (no `sampleValue`).
31
- */
32
- export function targetSlotsFromPlainObject(
33
- sample: unknown,
34
- options: TargetSlotsFromPlainObjectOptions = {},
35
- ): TargetSlot[] {
36
- const idPrefix = options.idPrefix?.trim() || 'tgt';
37
- return sourceFieldsFromPlainObject(sample, {
38
- idPrefix,
39
- maxDepth: options.maxDepth,
40
- }).map(sourceFieldToTargetSlot);
41
- }
1
+ import { sourceFieldsFromPlainObject } from './sourceFieldsFromPlainObject.js';
2
+ import type { SourceField, TargetSlot } from '../types.js';
3
+
4
+ export interface TargetSlotsFromPlainObjectOptions {
5
+ /** Prefix for generated slot ids (default `tgt`). */
6
+ readonly idPrefix?: string;
7
+ /** Max nesting depth for object children (default 4). */
8
+ readonly maxDepth?: number;
9
+ }
10
+
11
+ function sourceFieldToTargetSlot(field: SourceField): TargetSlot {
12
+ const slot: TargetSlot = {
13
+ id: field.id,
14
+ label: field.label,
15
+ ...(field.path ? { path: field.path } : {}),
16
+ ...(field.dataType ? { dataType: field.dataType } : {}),
17
+ };
18
+ if (field.children && field.children.length > 0) {
19
+ return {
20
+ ...slot,
21
+ children: field.children.map(sourceFieldToTargetSlot),
22
+ };
23
+ }
24
+ return slot;
25
+ }
26
+
27
+ /**
28
+ * Infer a nested `TargetSlot` tree from a plain sample object (or target shape).
29
+ * Mirrors {@link sourceFieldsFromPlainObject}: same nesting / array-of-object rules,
30
+ * keeping `path` for `convertToShape` output assembly (no `sampleValue`).
31
+ */
32
+ export function targetSlotsFromPlainObject(
33
+ sample: unknown,
34
+ options: TargetSlotsFromPlainObjectOptions = {},
35
+ ): TargetSlot[] {
36
+ const idPrefix = options.idPrefix?.trim() || 'tgt';
37
+ return sourceFieldsFromPlainObject(sample, {
38
+ idPrefix,
39
+ maxDepth: options.maxDepth,
40
+ }).map(sourceFieldToTargetSlot);
41
+ }