@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.
@@ -1,184 +1,184 @@
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
- }
37
-
38
- /** Option fields for a single chain step. */
39
- export function optionFieldsForStep(
40
- registry: ValueTransformRegistry,
41
- transformId: string | undefined,
42
- ): TransformOptionField[] {
43
- if (!transformId) {
44
- return [];
45
- }
46
- return [...(registry.get(transformId)?.optionFields ?? [])];
47
- }
48
-
49
- /** Drop empty / undefined option bags when persisting edges. */
50
- export function sanitizeOptionRecord(
51
- options: Readonly<Record<string, unknown>> | undefined,
52
- ): Readonly<Record<string, unknown>> | undefined {
53
- if (!options) {
54
- return undefined;
55
- }
56
- const next: Record<string, unknown> = {};
57
- for (const [key, value] of Object.entries(options)) {
58
- if (value === undefined) {
59
- continue;
60
- }
61
- next[key] = value;
62
- }
63
- return Object.keys(next).length > 0 ? next : undefined;
64
- }
65
-
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
- /**
78
- * Align / sanitize per-step option bags to `length`.
79
- * Returns `undefined` when every step is empty.
80
- */
81
- export function sanitizeOptionSteps(
82
- steps: readonly (Readonly<Record<string, unknown>> | undefined)[] | undefined,
83
- length: number,
84
- ): readonly (Readonly<Record<string, unknown>> | undefined)[] | undefined {
85
- if (length <= 0) {
86
- return undefined;
87
- }
88
- const next: (Readonly<Record<string, unknown>> | undefined)[] = [];
89
- let any = false;
90
- for (let index = 0; index < length; index += 1) {
91
- const sanitized = sanitizeOptionRecord(steps?.[index]);
92
- next.push(sanitized);
93
- if (sanitized) {
94
- any = true;
95
- }
96
- }
97
- return any ? next : undefined;
98
- }
99
-
100
- /**
101
- * Resolve per-step options for a transform chain.
102
- * Prefers `steps`; otherwise expands shared `transformOptions` to every step (apply-to-all).
103
- */
104
- export function resolveOptionSteps(
105
- transformIds: readonly string[],
106
- steps: readonly (Readonly<Record<string, unknown>> | undefined)[] | undefined,
107
- shared: Readonly<Record<string, unknown>> | undefined,
108
- ): (Readonly<Record<string, unknown>> | undefined)[] {
109
- const length = transformIds.length;
110
- if (length === 0) {
111
- return [];
112
- }
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);
173
- }
174
-
175
- export function resizeOptionSteps(
176
- steps: readonly (Readonly<Record<string, unknown>> | undefined)[] | undefined,
177
- length: number,
178
- ): readonly (Readonly<Record<string, unknown>> | undefined)[] | undefined {
179
- if (length <= 0) {
180
- return undefined;
181
- }
182
- const next = Array.from({ length }, (_, index) => sanitizeOptionRecord(steps?.[index]));
183
- return sanitizeOptionSteps(next, length);
184
- }
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
+ }
37
+
38
+ /** Option fields for a single chain step. */
39
+ export function optionFieldsForStep(
40
+ registry: ValueTransformRegistry,
41
+ transformId: string | undefined,
42
+ ): TransformOptionField[] {
43
+ if (!transformId) {
44
+ return [];
45
+ }
46
+ return [...(registry.get(transformId)?.optionFields ?? [])];
47
+ }
48
+
49
+ /** Drop empty / undefined option bags when persisting edges. */
50
+ export function sanitizeOptionRecord(
51
+ options: Readonly<Record<string, unknown>> | undefined,
52
+ ): Readonly<Record<string, unknown>> | undefined {
53
+ if (!options) {
54
+ return undefined;
55
+ }
56
+ const next: Record<string, unknown> = {};
57
+ for (const [key, value] of Object.entries(options)) {
58
+ if (value === undefined) {
59
+ continue;
60
+ }
61
+ next[key] = value;
62
+ }
63
+ return Object.keys(next).length > 0 ? next : undefined;
64
+ }
65
+
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
+ /**
78
+ * Align / sanitize per-step option bags to `length`.
79
+ * Returns `undefined` when every step is empty.
80
+ */
81
+ export function sanitizeOptionSteps(
82
+ steps: readonly (Readonly<Record<string, unknown>> | undefined)[] | undefined,
83
+ length: number,
84
+ ): readonly (Readonly<Record<string, unknown>> | undefined)[] | undefined {
85
+ if (length <= 0) {
86
+ return undefined;
87
+ }
88
+ const next: (Readonly<Record<string, unknown>> | undefined)[] = [];
89
+ let any = false;
90
+ for (let index = 0; index < length; index += 1) {
91
+ const sanitized = sanitizeOptionRecord(steps?.[index]);
92
+ next.push(sanitized);
93
+ if (sanitized) {
94
+ any = true;
95
+ }
96
+ }
97
+ return any ? next : undefined;
98
+ }
99
+
100
+ /**
101
+ * Resolve per-step options for a transform chain.
102
+ * Prefers `steps`; otherwise expands shared `transformOptions` to every step (apply-to-all).
103
+ */
104
+ export function resolveOptionSteps(
105
+ transformIds: readonly string[],
106
+ steps: readonly (Readonly<Record<string, unknown>> | undefined)[] | undefined,
107
+ shared: Readonly<Record<string, unknown>> | undefined,
108
+ ): (Readonly<Record<string, unknown>> | undefined)[] {
109
+ const length = transformIds.length;
110
+ if (length === 0) {
111
+ return [];
112
+ }
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);
173
+ }
174
+
175
+ export function resizeOptionSteps(
176
+ steps: readonly (Readonly<Record<string, unknown>> | undefined)[] | undefined,
177
+ length: number,
178
+ ): readonly (Readonly<Record<string, unknown>> | undefined)[] | undefined {
179
+ if (length <= 0) {
180
+ return undefined;
181
+ }
182
+ const next = Array.from({ length }, (_, index) => sanitizeOptionRecord(steps?.[index]));
183
+ return sanitizeOptionSteps(next, length);
184
+ }
@@ -1,32 +1,32 @@
1
- import type { SourceField, TargetSlot } from '../types.js';
2
-
3
- /** Flatten nested source fields depth-first. */
4
- export function flattenSourceFields(fields: readonly SourceField[]): SourceField[] {
5
- const out: SourceField[] = [];
6
- for (const field of fields) {
7
- out.push(field);
8
- if (field.children?.length) {
9
- out.push(...flattenSourceFields(field.children));
10
- }
11
- }
12
- return out;
13
- }
14
-
15
- /** Flatten nested target slots depth-first. */
16
- export function flattenTargetSlots(slots: readonly TargetSlot[]): TargetSlot[] {
17
- const out: TargetSlot[] = [];
18
- for (const slot of slots) {
19
- out.push(slot);
20
- if (slot.children?.length) {
21
- out.push(...flattenTargetSlots(slot.children));
22
- }
23
- }
24
- return out;
25
- }
26
-
27
- export function findTargetSlot(
28
- slots: readonly TargetSlot[],
29
- slotId: string,
30
- ): TargetSlot | undefined {
31
- return flattenTargetSlots(slots).find((slot) => slot.id === slotId);
32
- }
1
+ import type { SourceField, TargetSlot } from '../types.js';
2
+
3
+ /** Flatten nested source fields depth-first. */
4
+ export function flattenSourceFields(fields: readonly SourceField[]): SourceField[] {
5
+ const out: SourceField[] = [];
6
+ for (const field of fields) {
7
+ out.push(field);
8
+ if (field.children?.length) {
9
+ out.push(...flattenSourceFields(field.children));
10
+ }
11
+ }
12
+ return out;
13
+ }
14
+
15
+ /** Flatten nested target slots depth-first. */
16
+ export function flattenTargetSlots(slots: readonly TargetSlot[]): TargetSlot[] {
17
+ const out: TargetSlot[] = [];
18
+ for (const slot of slots) {
19
+ out.push(slot);
20
+ if (slot.children?.length) {
21
+ out.push(...flattenTargetSlots(slot.children));
22
+ }
23
+ }
24
+ return out;
25
+ }
26
+
27
+ export function findTargetSlot(
28
+ slots: readonly TargetSlot[],
29
+ slotId: string,
30
+ ): TargetSlot | undefined {
31
+ return flattenTargetSlots(slots).find((slot) => slot.id === slotId);
32
+ }
@@ -1,95 +1,95 @@
1
- import {
2
- createFieldRemapDocument,
3
- normalizeFieldRemapDocument,
4
- } from '../document/fieldRemapDocument.js';
5
- import type { MappingEdge, FieldRemapDocument } from '../types.js';
6
-
7
- /**
8
- * Managed conversion between one or more source shapes and a target shape.
9
- * Edges live in `document`; runtime uses `convertToShape` (not a method on a shape).
10
- */
11
- export interface ConversionDefinition {
12
- readonly id: string;
13
- readonly label?: string;
14
- /** One or more managed source shape ids (multi-input). */
15
- readonly sourceShapeIds: readonly string[];
16
- readonly targetShapeId: string;
17
- readonly document: FieldRemapDocument;
18
- }
19
-
20
- export interface ConversionRegistry {
21
- register(conversion: ConversionDefinition): void;
22
- get(id: string): ConversionDefinition | undefined;
23
- list(): readonly ConversionDefinition[];
24
- }
25
-
26
- export interface DefineConversionInput {
27
- readonly id: string;
28
- readonly label?: string;
29
- readonly sourceShapeIds: readonly string[];
30
- readonly targetShapeId: string;
31
- readonly document?: FieldRemapDocument;
32
- readonly edges?: readonly MappingEdge[];
33
- }
34
-
35
- export function defineConversion(input: DefineConversionInput): ConversionDefinition {
36
- const id = input.id.trim();
37
- if (!id) {
38
- throw new Error('ConversionDefinition.id must be a non-empty string.');
39
- }
40
- const sourceShapeIds = input.sourceShapeIds.map((item) => item.trim()).filter(Boolean);
41
- if (sourceShapeIds.length === 0) {
42
- throw new Error('ConversionDefinition.sourceShapeIds must include at least one shape id.');
43
- }
44
- const targetShapeId = input.targetShapeId.trim();
45
- if (!targetShapeId) {
46
- throw new Error('ConversionDefinition.targetShapeId must be a non-empty string.');
47
- }
48
-
49
- const document = input.document
50
- ? normalizeFieldRemapDocument(input.document)
51
- : createFieldRemapDocument(input.edges ?? []);
52
-
53
- return {
54
- id,
55
- ...(input.label?.trim() ? { label: input.label.trim() } : {}),
56
- sourceShapeIds,
57
- targetShapeId,
58
- document,
59
- };
60
- }
61
-
62
- export function createConversionRegistry(
63
- initial: readonly ConversionDefinition[] = [],
64
- ): ConversionRegistry {
65
- const byId = new Map<string, ConversionDefinition>();
66
-
67
- const api: ConversionRegistry = {
68
- register(conversion) {
69
- const defined = defineConversion(conversion);
70
- byId.set(defined.id, defined);
71
- },
72
- get(id) {
73
- return byId.get(id.trim());
74
- },
75
- list() {
76
- return [...byId.values()];
77
- },
78
- };
79
-
80
- for (const conversion of initial) {
81
- api.register(conversion);
82
- }
83
- return api;
84
- }
85
-
86
- /** Replace edges on a conversion (immutable). */
87
- export function withConversionEdges(
88
- conversion: ConversionDefinition,
89
- edges: readonly MappingEdge[],
90
- ): ConversionDefinition {
91
- return {
92
- ...conversion,
93
- document: createFieldRemapDocument(edges),
94
- };
95
- }
1
+ import {
2
+ createFieldRemapDocument,
3
+ normalizeFieldRemapDocument,
4
+ } from '../document/fieldRemapDocument.js';
5
+ import type { MappingEdge, FieldRemapDocument } from '../types.js';
6
+
7
+ /**
8
+ * Managed conversion between one or more source shapes and a target shape.
9
+ * Edges live in `document`; runtime uses `convertToShape` (not a method on a shape).
10
+ */
11
+ export interface ConversionDefinition {
12
+ readonly id: string;
13
+ readonly label?: string;
14
+ /** One or more managed source shape ids (multi-input). */
15
+ readonly sourceShapeIds: readonly string[];
16
+ readonly targetShapeId: string;
17
+ readonly document: FieldRemapDocument;
18
+ }
19
+
20
+ export interface ConversionRegistry {
21
+ register(conversion: ConversionDefinition): void;
22
+ get(id: string): ConversionDefinition | undefined;
23
+ list(): readonly ConversionDefinition[];
24
+ }
25
+
26
+ export interface DefineConversionInput {
27
+ readonly id: string;
28
+ readonly label?: string;
29
+ readonly sourceShapeIds: readonly string[];
30
+ readonly targetShapeId: string;
31
+ readonly document?: FieldRemapDocument;
32
+ readonly edges?: readonly MappingEdge[];
33
+ }
34
+
35
+ export function defineConversion(input: DefineConversionInput): ConversionDefinition {
36
+ const id = input.id.trim();
37
+ if (!id) {
38
+ throw new Error('ConversionDefinition.id must be a non-empty string.');
39
+ }
40
+ const sourceShapeIds = input.sourceShapeIds.map((item) => item.trim()).filter(Boolean);
41
+ if (sourceShapeIds.length === 0) {
42
+ throw new Error('ConversionDefinition.sourceShapeIds must include at least one shape id.');
43
+ }
44
+ const targetShapeId = input.targetShapeId.trim();
45
+ if (!targetShapeId) {
46
+ throw new Error('ConversionDefinition.targetShapeId must be a non-empty string.');
47
+ }
48
+
49
+ const document = input.document
50
+ ? normalizeFieldRemapDocument(input.document)
51
+ : createFieldRemapDocument(input.edges ?? []);
52
+
53
+ return {
54
+ id,
55
+ ...(input.label?.trim() ? { label: input.label.trim() } : {}),
56
+ sourceShapeIds,
57
+ targetShapeId,
58
+ document,
59
+ };
60
+ }
61
+
62
+ export function createConversionRegistry(
63
+ initial: readonly ConversionDefinition[] = [],
64
+ ): ConversionRegistry {
65
+ const byId = new Map<string, ConversionDefinition>();
66
+
67
+ const api: ConversionRegistry = {
68
+ register(conversion) {
69
+ const defined = defineConversion(conversion);
70
+ byId.set(defined.id, defined);
71
+ },
72
+ get(id) {
73
+ return byId.get(id.trim());
74
+ },
75
+ list() {
76
+ return [...byId.values()];
77
+ },
78
+ };
79
+
80
+ for (const conversion of initial) {
81
+ api.register(conversion);
82
+ }
83
+ return api;
84
+ }
85
+
86
+ /** Replace edges on a conversion (immutable). */
87
+ export function withConversionEdges(
88
+ conversion: ConversionDefinition,
89
+ edges: readonly MappingEdge[],
90
+ ): ConversionDefinition {
91
+ return {
92
+ ...conversion,
93
+ document: createFieldRemapDocument(edges),
94
+ };
95
+ }