@octaviaflow/flow-rules 0.14.0 → 0.16.0
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/dist/catalog/general/transform/config.d.ts +34 -0
- package/dist/index.cjs +24 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +24 -1
- package/package.json +1 -1
|
@@ -24,6 +24,32 @@ export interface FieldMapping {
|
|
|
24
24
|
parentId?: string;
|
|
25
25
|
/** Nesting depth (0 = root) */
|
|
26
26
|
depth?: number;
|
|
27
|
+
/** With `groupBy`: this field goes into each line of the record's list, not the record (DEV-478). Root level only. */
|
|
28
|
+
lineField?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* "Find by" (DEV-480): the mapped value names a record in the target app —
|
|
31
|
+
* the write uses the id of the `object` record whose `matchField` equals it.
|
|
32
|
+
* Root level only; on a line field it applies to each line. Odoo today.
|
|
33
|
+
*/
|
|
34
|
+
lookup?: TransformLookup;
|
|
35
|
+
}
|
|
36
|
+
/** A record in the target app, found by one of its fields (DEV-480). */
|
|
37
|
+
export interface TransformLookup {
|
|
38
|
+
/** The target app's object (Odoo model): res.partner, product.product. */
|
|
39
|
+
object: string;
|
|
40
|
+
/** The field the mapped value is matched against: email, default_code. Must be unique. */
|
|
41
|
+
matchField: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* One record per key (DEV-478): rows whose `key` resolves to the same value
|
|
45
|
+
* become one record (fields from the group's first row); the mappings marked
|
|
46
|
+
* `lineField` become a list of line objects under `into`, in row order.
|
|
47
|
+
*/
|
|
48
|
+
export interface TransformGroupBy {
|
|
49
|
+
/** Source expression of the key, written like a sourceField: `{{orders[].number}}`. */
|
|
50
|
+
key: string;
|
|
51
|
+
/** Target field holding the list of lines: `line_items` (Shopify), `Line` (QuickBooks), `order_line` (Odoo). */
|
|
52
|
+
into: string;
|
|
27
53
|
}
|
|
28
54
|
export type TransformMode = 'visual' | 'expression';
|
|
29
55
|
/**
|
|
@@ -65,6 +91,8 @@ export interface TransformConfigType {
|
|
|
65
91
|
/** Explicit upsert/update match field (legacy externalid/pkey). Empty =
|
|
66
92
|
* per-connector auto-detection (Mongo _id, SF Id, SQL primary key). */
|
|
67
93
|
upsertMatchKey?: string;
|
|
94
|
+
/** One record per key, with the `lineField` mappings collected into a list (DEV-478). */
|
|
95
|
+
groupBy?: TransformGroupBy;
|
|
68
96
|
/** Ordered column header names — the exact columns + header text written. */
|
|
69
97
|
sheetHeaders?: string[];
|
|
70
98
|
/** Create the target tab on first write if it doesn't exist (default true). */
|
|
@@ -117,6 +145,12 @@ export declare const TransformConfig: {
|
|
|
117
145
|
mappings: {
|
|
118
146
|
type: string;
|
|
119
147
|
required: boolean;
|
|
148
|
+
description: string;
|
|
149
|
+
};
|
|
150
|
+
groupBy: {
|
|
151
|
+
type: string;
|
|
152
|
+
required: boolean;
|
|
153
|
+
description: string;
|
|
120
154
|
};
|
|
121
155
|
expression: {
|
|
122
156
|
type: string;
|
package/dist/index.cjs
CHANGED
|
@@ -3771,7 +3771,16 @@ var TransformConfig = {
|
|
|
3771
3771
|
options: TRANSFORM_MODES,
|
|
3772
3772
|
description: "How the mappings are authored: visual (the Field Mapper — the only mode the editor offers) or expression (legacy JavaScript). This is not the write mode; that is `operation`."
|
|
3773
3773
|
},
|
|
3774
|
-
mappings: {
|
|
3774
|
+
mappings: {
|
|
3775
|
+
type: "array",
|
|
3776
|
+
required: false,
|
|
3777
|
+
description: "Field mappings: [{ id, sourceField, targetField, targetType, transform? }]. sourceField is a token like {{orders[].email}}; " + "an index picks one list element ({{products[].variants[0].price}}). With groupBy, set lineField: true on the mappings that make each line. " + 'When the target field holds a record id but you have a reference (an email, SHOP-123), add lookup: { "object": "product.product", "matchField": "default_code" } — ' + "the write uses the id of the one record whose matchField equals the value; no match, or more than one, fails that row. Odoo only for now."
|
|
3778
|
+
},
|
|
3779
|
+
groupBy: {
|
|
3780
|
+
type: "json",
|
|
3781
|
+
required: false,
|
|
3782
|
+
description: 'One record per key, with line items: { "key": "{{orders[].number}}", "into": "line_items" }. Rows whose key is the same become ONE record ' + "(its fields from the first row); mappings with lineField: true become a list of lines under `into` (Shopify line_items, QuickBooks Line, Odoo order_line). " + "Dotted line fields nest inside each line (SalesItemLineDetail.ItemRef.value)."
|
|
3783
|
+
},
|
|
3775
3784
|
expression: { type: "code", required: false }
|
|
3776
3785
|
},
|
|
3777
3786
|
generateName: (operation, connectorLabel, objectName) => {
|
|
@@ -3809,9 +3818,23 @@ var TransformConfig = {
|
|
|
3809
3818
|
if (!mapping.targetField) {
|
|
3810
3819
|
errors.push(`Mapping ${index + 1}: Target field is required`);
|
|
3811
3820
|
}
|
|
3821
|
+
const lookup = mapping.lookup;
|
|
3822
|
+
if (lookup && (!lookup.object || !lookup.matchField)) {
|
|
3823
|
+
errors.push(`Mapping ${index + 1}: Find by needs both the object and the field to match (lookup.object, lookup.matchField)`);
|
|
3824
|
+
}
|
|
3812
3825
|
});
|
|
3813
3826
|
}
|
|
3814
3827
|
}
|
|
3828
|
+
const groupBy = config.groupBy;
|
|
3829
|
+
if (groupBy && (groupBy.key || groupBy.into)) {
|
|
3830
|
+
if (!groupBy.key)
|
|
3831
|
+
errors.push("One record per key: choose the key field (groupBy.key)");
|
|
3832
|
+
if (!groupBy.into)
|
|
3833
|
+
errors.push("One record per key: name the field that holds the lines (groupBy.into)");
|
|
3834
|
+
if (groupBy.key && groupBy.into && !(config.mappings ?? []).some((m) => m.lineField)) {
|
|
3835
|
+
errors.push("One record per key: mark at least one mapping as a line field (lineField: true)");
|
|
3836
|
+
}
|
|
3837
|
+
}
|
|
3815
3838
|
return { valid: errors.length === 0, errors };
|
|
3816
3839
|
},
|
|
3817
3840
|
merge: (userConfig) => {
|
package/dist/index.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ export { EmailConfig, } from "./catalog/communication/email-send/config";
|
|
|
36
36
|
export { queryDefinition, } from "./catalog/general/query/index";
|
|
37
37
|
export { isQueryableConnector, } from "./catalog/general/query/config";
|
|
38
38
|
export { AggregateFunction, type BuildQueryConfig, type ColumnSchema, type ConnectorSchema, JoinType, LogicalOperator, type QueryExecutionResult, type QueryField, type QueryFilter, type QueryFilterGroup, type QueryJoin, type QuerySort, SortDirection, type VisualQueryConfig, } from "./catalog/general/query/types";
|
|
39
|
-
export { DATABASE_CONNECTOR_IDS, type FieldMapping, TRANSFORM_MODES, TRANSFORM_OPERATIONS, TransformConfig, type TransformConfigType, type TransformOperation, } from "./catalog/general/transform/config";
|
|
39
|
+
export { DATABASE_CONNECTOR_IDS, type FieldMapping, TRANSFORM_MODES, TRANSFORM_OPERATIONS, TransformConfig, type TransformConfigType, type TransformGroupBy, type TransformLookup, type TransformOperation, } from "./catalog/general/transform/config";
|
|
40
40
|
export { AGGREGATE_FUNCTIONS, AGGREGATE_FUNCTION_DESCRIPTIONS, AggregateConfig, type AggregateConfigType, type AggregateFn, type AggregateMetric, type AggregateSchemaNode, groupKeyName, } from "./catalog/general/aggregate/config";
|
|
41
41
|
export { detectRecordFields, resolveRecordSchema, type RecordSchemaLike, } from "./catalog/logic/for-each/recordSchema";
|
|
42
42
|
export { STORAGE_CONNECTOR_IDS, type StorageExtraField, type StoragePathField, getStorageProvider, storageConfigKeys, } from "./catalog/storage/storageCapabilities";
|
package/dist/index.js
CHANGED
|
@@ -3613,7 +3613,16 @@ var TransformConfig = {
|
|
|
3613
3613
|
options: TRANSFORM_MODES,
|
|
3614
3614
|
description: "How the mappings are authored: visual (the Field Mapper — the only mode the editor offers) or expression (legacy JavaScript). This is not the write mode; that is `operation`."
|
|
3615
3615
|
},
|
|
3616
|
-
mappings: {
|
|
3616
|
+
mappings: {
|
|
3617
|
+
type: "array",
|
|
3618
|
+
required: false,
|
|
3619
|
+
description: "Field mappings: [{ id, sourceField, targetField, targetType, transform? }]. sourceField is a token like {{orders[].email}}; " + "an index picks one list element ({{products[].variants[0].price}}). With groupBy, set lineField: true on the mappings that make each line. " + 'When the target field holds a record id but you have a reference (an email, SHOP-123), add lookup: { "object": "product.product", "matchField": "default_code" } — ' + "the write uses the id of the one record whose matchField equals the value; no match, or more than one, fails that row. Odoo only for now."
|
|
3620
|
+
},
|
|
3621
|
+
groupBy: {
|
|
3622
|
+
type: "json",
|
|
3623
|
+
required: false,
|
|
3624
|
+
description: 'One record per key, with line items: { "key": "{{orders[].number}}", "into": "line_items" }. Rows whose key is the same become ONE record ' + "(its fields from the first row); mappings with lineField: true become a list of lines under `into` (Shopify line_items, QuickBooks Line, Odoo order_line). " + "Dotted line fields nest inside each line (SalesItemLineDetail.ItemRef.value)."
|
|
3625
|
+
},
|
|
3617
3626
|
expression: { type: "code", required: false }
|
|
3618
3627
|
},
|
|
3619
3628
|
generateName: (operation, connectorLabel, objectName) => {
|
|
@@ -3651,9 +3660,23 @@ var TransformConfig = {
|
|
|
3651
3660
|
if (!mapping.targetField) {
|
|
3652
3661
|
errors.push(`Mapping ${index + 1}: Target field is required`);
|
|
3653
3662
|
}
|
|
3663
|
+
const lookup = mapping.lookup;
|
|
3664
|
+
if (lookup && (!lookup.object || !lookup.matchField)) {
|
|
3665
|
+
errors.push(`Mapping ${index + 1}: Find by needs both the object and the field to match (lookup.object, lookup.matchField)`);
|
|
3666
|
+
}
|
|
3654
3667
|
});
|
|
3655
3668
|
}
|
|
3656
3669
|
}
|
|
3670
|
+
const groupBy = config.groupBy;
|
|
3671
|
+
if (groupBy && (groupBy.key || groupBy.into)) {
|
|
3672
|
+
if (!groupBy.key)
|
|
3673
|
+
errors.push("One record per key: choose the key field (groupBy.key)");
|
|
3674
|
+
if (!groupBy.into)
|
|
3675
|
+
errors.push("One record per key: name the field that holds the lines (groupBy.into)");
|
|
3676
|
+
if (groupBy.key && groupBy.into && !(config.mappings ?? []).some((m) => m.lineField)) {
|
|
3677
|
+
errors.push("One record per key: mark at least one mapping as a line field (lineField: true)");
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3657
3680
|
return { valid: errors.length === 0, errors };
|
|
3658
3681
|
},
|
|
3659
3682
|
merge: (userConfig) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@octaviaflow/flow-rules",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "The flow action catalog and the rules engine behind Flow Doctor — one definition of what a step accepts and what makes a flow valid, shared by the editor and the server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|