@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,194 +1,194 @@
|
|
|
1
|
-
import { canonicalizeTransformId, MAX_TRANSFORM_CHAIN } from '../domain/constants.js';
|
|
2
|
-
import type {
|
|
3
|
-
FieldDataType,
|
|
4
|
-
TransformContext,
|
|
5
|
-
ValueTransformDefinition,
|
|
6
|
-
ValueTransformListFilter,
|
|
7
|
-
ValueTransformRegistry,
|
|
8
|
-
} from '../domain/types.js';
|
|
9
|
-
|
|
10
|
-
export function createValueTransformRegistry(
|
|
11
|
-
initial: readonly ValueTransformDefinition[] = [],
|
|
12
|
-
): ValueTransformRegistry {
|
|
13
|
-
const byId = new Map<string, ValueTransformDefinition>();
|
|
14
|
-
|
|
15
|
-
for (const definition of initial) {
|
|
16
|
-
byId.set(definition.id, definition);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function resolve(id: string): ValueTransformDefinition | undefined {
|
|
20
|
-
const canonical = canonicalizeTransformId(id);
|
|
21
|
-
return byId.get(canonical) ?? byId.get(id);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return {
|
|
25
|
-
list(filter) {
|
|
26
|
-
return [...byId.values()].filter((definition) => matchesFilter(definition, filter));
|
|
27
|
-
},
|
|
28
|
-
get(id) {
|
|
29
|
-
return resolve(id);
|
|
30
|
-
},
|
|
31
|
-
apply(id, value, context = {}) {
|
|
32
|
-
const definition = resolve(id);
|
|
33
|
-
if (!definition) {
|
|
34
|
-
throw new Error(`Unknown value transform: ${id}`);
|
|
35
|
-
}
|
|
36
|
-
return definition.apply(value, context);
|
|
37
|
-
},
|
|
38
|
-
register(definition) {
|
|
39
|
-
byId.set(definition.id, definition);
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Apply an ordered transform chain (empty = identity / unchanged).
|
|
46
|
-
* Optional `optionSteps[i]` merges over `context.options` for step `i` only.
|
|
47
|
-
*/
|
|
48
|
-
export function applyTransformChain(
|
|
49
|
-
registry: ValueTransformRegistry,
|
|
50
|
-
transformIds: readonly string[],
|
|
51
|
-
value: unknown,
|
|
52
|
-
context: TransformContext = {},
|
|
53
|
-
optionSteps?: readonly (Readonly<Record<string, unknown>> | undefined)[],
|
|
54
|
-
): unknown {
|
|
55
|
-
let current = value;
|
|
56
|
-
const ids = transformIds.slice(0, MAX_TRANSFORM_CHAIN);
|
|
57
|
-
for (let index = 0; index < ids.length; index += 1) {
|
|
58
|
-
const stepOptions = optionSteps?.[index];
|
|
59
|
-
const stepContext =
|
|
60
|
-
stepOptions && Object.keys(stepOptions).length > 0
|
|
61
|
-
? {
|
|
62
|
-
...context,
|
|
63
|
-
options: {
|
|
64
|
-
...context.options,
|
|
65
|
-
...stepOptions,
|
|
66
|
-
},
|
|
67
|
-
}
|
|
68
|
-
: context;
|
|
69
|
-
current = registry.apply(ids[index]!, current, stepContext);
|
|
70
|
-
}
|
|
71
|
-
return current;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function matchesFilter(
|
|
75
|
-
definition: ValueTransformDefinition,
|
|
76
|
-
filter: ValueTransformListFilter | undefined,
|
|
77
|
-
): boolean {
|
|
78
|
-
if (!filter) {
|
|
79
|
-
return true;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (filter.category && definition.category !== filter.category) {
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (filter.inputType) {
|
|
87
|
-
const inputTypes = definition.inputTypes;
|
|
88
|
-
if (inputTypes && inputTypes.length > 0 && !inputTypes.includes(filter.inputType)) {
|
|
89
|
-
return false;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return true;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/** Whether a transform may sit between a typed source field and a typed target slot. */
|
|
97
|
-
export function isTransformCompatible(
|
|
98
|
-
definition: ValueTransformDefinition,
|
|
99
|
-
sourceType: FieldDataType | undefined,
|
|
100
|
-
targetType: FieldDataType | undefined,
|
|
101
|
-
): boolean {
|
|
102
|
-
if (
|
|
103
|
-
sourceType &&
|
|
104
|
-
sourceType !== 'unknown' &&
|
|
105
|
-
definition.inputTypes &&
|
|
106
|
-
definition.inputTypes.length > 0 &&
|
|
107
|
-
!definition.inputTypes.includes(sourceType)
|
|
108
|
-
) {
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Collection sources only accept transforms that declare array input (or untyped).
|
|
113
|
-
if (sourceType === 'array') {
|
|
114
|
-
const acceptsArray =
|
|
115
|
-
!definition.inputTypes ||
|
|
116
|
-
definition.inputTypes.length === 0 ||
|
|
117
|
-
definition.inputTypes.includes('array');
|
|
118
|
-
if (!acceptsArray) {
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Array → array is pass-through only; reduce / typed outputs are not collection-preserving.
|
|
124
|
-
if (
|
|
125
|
-
sourceType === 'array' &&
|
|
126
|
-
targetType === 'array' &&
|
|
127
|
-
definition.outputType &&
|
|
128
|
-
definition.outputType !== 'array'
|
|
129
|
-
) {
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (
|
|
134
|
-
targetType &&
|
|
135
|
-
targetType !== 'unknown' &&
|
|
136
|
-
definition.outputType &&
|
|
137
|
-
definition.outputType !== 'unknown' &&
|
|
138
|
-
definition.outputType !== targetType
|
|
139
|
-
) {
|
|
140
|
-
// Formatted outputs are strings; string sinks accept them.
|
|
141
|
-
return targetType === 'string';
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Pass-through / undeclared output: known source and target types must match.
|
|
145
|
-
if (
|
|
146
|
-
!definition.outputType &&
|
|
147
|
-
sourceType &&
|
|
148
|
-
sourceType !== 'unknown' &&
|
|
149
|
-
targetType &&
|
|
150
|
-
targetType !== 'unknown' &&
|
|
151
|
-
sourceType !== targetType
|
|
152
|
-
) {
|
|
153
|
-
return false;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return true;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Whether an ordered transform chain can sit between source and target types.
|
|
161
|
-
* Each step must accept the previous output type (or the original source for step 0).
|
|
162
|
-
*/
|
|
163
|
-
export function isTransformChainCompatible(
|
|
164
|
-
registry: ValueTransformRegistry,
|
|
165
|
-
transformIds: readonly string[],
|
|
166
|
-
sourceType: FieldDataType | undefined,
|
|
167
|
-
targetType: FieldDataType | undefined,
|
|
168
|
-
): boolean {
|
|
169
|
-
if (transformIds.length === 0) {
|
|
170
|
-
return true;
|
|
171
|
-
}
|
|
172
|
-
if (transformIds.length > MAX_TRANSFORM_CHAIN) {
|
|
173
|
-
return false;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
let currentType = sourceType;
|
|
177
|
-
for (let index = 0; index < transformIds.length; index += 1) {
|
|
178
|
-
const definition = registry.get(transformIds[index]!);
|
|
179
|
-
if (!definition) {
|
|
180
|
-
return false;
|
|
181
|
-
}
|
|
182
|
-
const isLast = index === transformIds.length - 1;
|
|
183
|
-
if (!isTransformCompatible(definition, currentType, isLast ? targetType : undefined)) {
|
|
184
|
-
return false;
|
|
185
|
-
}
|
|
186
|
-
if (definition.outputType) {
|
|
187
|
-
currentType = definition.outputType;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return true;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
export type { TransformContext };
|
|
1
|
+
import { canonicalizeTransformId, MAX_TRANSFORM_CHAIN } from '../domain/constants.js';
|
|
2
|
+
import type {
|
|
3
|
+
FieldDataType,
|
|
4
|
+
TransformContext,
|
|
5
|
+
ValueTransformDefinition,
|
|
6
|
+
ValueTransformListFilter,
|
|
7
|
+
ValueTransformRegistry,
|
|
8
|
+
} from '../domain/types.js';
|
|
9
|
+
|
|
10
|
+
export function createValueTransformRegistry(
|
|
11
|
+
initial: readonly ValueTransformDefinition[] = [],
|
|
12
|
+
): ValueTransformRegistry {
|
|
13
|
+
const byId = new Map<string, ValueTransformDefinition>();
|
|
14
|
+
|
|
15
|
+
for (const definition of initial) {
|
|
16
|
+
byId.set(definition.id, definition);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function resolve(id: string): ValueTransformDefinition | undefined {
|
|
20
|
+
const canonical = canonicalizeTransformId(id);
|
|
21
|
+
return byId.get(canonical) ?? byId.get(id);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
list(filter) {
|
|
26
|
+
return [...byId.values()].filter((definition) => matchesFilter(definition, filter));
|
|
27
|
+
},
|
|
28
|
+
get(id) {
|
|
29
|
+
return resolve(id);
|
|
30
|
+
},
|
|
31
|
+
apply(id, value, context = {}) {
|
|
32
|
+
const definition = resolve(id);
|
|
33
|
+
if (!definition) {
|
|
34
|
+
throw new Error(`Unknown value transform: ${id}`);
|
|
35
|
+
}
|
|
36
|
+
return definition.apply(value, context);
|
|
37
|
+
},
|
|
38
|
+
register(definition) {
|
|
39
|
+
byId.set(definition.id, definition);
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Apply an ordered transform chain (empty = identity / unchanged).
|
|
46
|
+
* Optional `optionSteps[i]` merges over `context.options` for step `i` only.
|
|
47
|
+
*/
|
|
48
|
+
export function applyTransformChain(
|
|
49
|
+
registry: ValueTransformRegistry,
|
|
50
|
+
transformIds: readonly string[],
|
|
51
|
+
value: unknown,
|
|
52
|
+
context: TransformContext = {},
|
|
53
|
+
optionSteps?: readonly (Readonly<Record<string, unknown>> | undefined)[],
|
|
54
|
+
): unknown {
|
|
55
|
+
let current = value;
|
|
56
|
+
const ids = transformIds.slice(0, MAX_TRANSFORM_CHAIN);
|
|
57
|
+
for (let index = 0; index < ids.length; index += 1) {
|
|
58
|
+
const stepOptions = optionSteps?.[index];
|
|
59
|
+
const stepContext =
|
|
60
|
+
stepOptions && Object.keys(stepOptions).length > 0
|
|
61
|
+
? {
|
|
62
|
+
...context,
|
|
63
|
+
options: {
|
|
64
|
+
...context.options,
|
|
65
|
+
...stepOptions,
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
: context;
|
|
69
|
+
current = registry.apply(ids[index]!, current, stepContext);
|
|
70
|
+
}
|
|
71
|
+
return current;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function matchesFilter(
|
|
75
|
+
definition: ValueTransformDefinition,
|
|
76
|
+
filter: ValueTransformListFilter | undefined,
|
|
77
|
+
): boolean {
|
|
78
|
+
if (!filter) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (filter.category && definition.category !== filter.category) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (filter.inputType) {
|
|
87
|
+
const inputTypes = definition.inputTypes;
|
|
88
|
+
if (inputTypes && inputTypes.length > 0 && !inputTypes.includes(filter.inputType)) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Whether a transform may sit between a typed source field and a typed target slot. */
|
|
97
|
+
export function isTransformCompatible(
|
|
98
|
+
definition: ValueTransformDefinition,
|
|
99
|
+
sourceType: FieldDataType | undefined,
|
|
100
|
+
targetType: FieldDataType | undefined,
|
|
101
|
+
): boolean {
|
|
102
|
+
if (
|
|
103
|
+
sourceType &&
|
|
104
|
+
sourceType !== 'unknown' &&
|
|
105
|
+
definition.inputTypes &&
|
|
106
|
+
definition.inputTypes.length > 0 &&
|
|
107
|
+
!definition.inputTypes.includes(sourceType)
|
|
108
|
+
) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Collection sources only accept transforms that declare array input (or untyped).
|
|
113
|
+
if (sourceType === 'array') {
|
|
114
|
+
const acceptsArray =
|
|
115
|
+
!definition.inputTypes ||
|
|
116
|
+
definition.inputTypes.length === 0 ||
|
|
117
|
+
definition.inputTypes.includes('array');
|
|
118
|
+
if (!acceptsArray) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Array → array is pass-through only; reduce / typed outputs are not collection-preserving.
|
|
124
|
+
if (
|
|
125
|
+
sourceType === 'array' &&
|
|
126
|
+
targetType === 'array' &&
|
|
127
|
+
definition.outputType &&
|
|
128
|
+
definition.outputType !== 'array'
|
|
129
|
+
) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (
|
|
134
|
+
targetType &&
|
|
135
|
+
targetType !== 'unknown' &&
|
|
136
|
+
definition.outputType &&
|
|
137
|
+
definition.outputType !== 'unknown' &&
|
|
138
|
+
definition.outputType !== targetType
|
|
139
|
+
) {
|
|
140
|
+
// Formatted outputs are strings; string sinks accept them.
|
|
141
|
+
return targetType === 'string';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Pass-through / undeclared output: known source and target types must match.
|
|
145
|
+
if (
|
|
146
|
+
!definition.outputType &&
|
|
147
|
+
sourceType &&
|
|
148
|
+
sourceType !== 'unknown' &&
|
|
149
|
+
targetType &&
|
|
150
|
+
targetType !== 'unknown' &&
|
|
151
|
+
sourceType !== targetType
|
|
152
|
+
) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Whether an ordered transform chain can sit between source and target types.
|
|
161
|
+
* Each step must accept the previous output type (or the original source for step 0).
|
|
162
|
+
*/
|
|
163
|
+
export function isTransformChainCompatible(
|
|
164
|
+
registry: ValueTransformRegistry,
|
|
165
|
+
transformIds: readonly string[],
|
|
166
|
+
sourceType: FieldDataType | undefined,
|
|
167
|
+
targetType: FieldDataType | undefined,
|
|
168
|
+
): boolean {
|
|
169
|
+
if (transformIds.length === 0) {
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
if (transformIds.length > MAX_TRANSFORM_CHAIN) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
let currentType = sourceType;
|
|
177
|
+
for (let index = 0; index < transformIds.length; index += 1) {
|
|
178
|
+
const definition = registry.get(transformIds[index]!);
|
|
179
|
+
if (!definition) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
const isLast = index === transformIds.length - 1;
|
|
183
|
+
if (!isTransformCompatible(definition, currentType, isLast ? targetType : undefined)) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
if (definition.outputType) {
|
|
187
|
+
currentType = definition.outputType;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export type { TransformContext };
|