@octaviaflow/flow-rules 0.14.0 → 0.15.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.
@@ -24,6 +24,19 @@ 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
+ /**
31
+ * One record per key (DEV-478): rows whose `key` resolves to the same value
32
+ * become one record (fields from the group's first row); the mappings marked
33
+ * `lineField` become a list of line objects under `into`, in row order.
34
+ */
35
+ export interface TransformGroupBy {
36
+ /** Source expression of the key, written like a sourceField: `{{orders[].number}}`. */
37
+ key: string;
38
+ /** Target field holding the list of lines: `line_items` (Shopify), `Line` (QuickBooks), `order_line` (Odoo). */
39
+ into: string;
27
40
  }
28
41
  export type TransformMode = 'visual' | 'expression';
29
42
  /**
@@ -65,6 +78,8 @@ export interface TransformConfigType {
65
78
  /** Explicit upsert/update match field (legacy externalid/pkey). Empty =
66
79
  * per-connector auto-detection (Mongo _id, SF Id, SQL primary key). */
67
80
  upsertMatchKey?: string;
81
+ /** One record per key, with the `lineField` mappings collected into a list (DEV-478). */
82
+ groupBy?: TransformGroupBy;
68
83
  /** Ordered column header names — the exact columns + header text written. */
69
84
  sheetHeaders?: string[];
70
85
  /** Create the target tab on first write if it doesn't exist (default true). */
@@ -117,6 +132,12 @@ export declare const TransformConfig: {
117
132
  mappings: {
118
133
  type: string;
119
134
  required: boolean;
135
+ description: string;
136
+ };
137
+ groupBy: {
138
+ type: string;
139
+ required: boolean;
140
+ description: string;
120
141
  };
121
142
  expression: {
122
143
  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: { type: "array", required: false },
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."
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) => {
@@ -3812,6 +3821,16 @@ var TransformConfig = {
3812
3821
  });
3813
3822
  }
3814
3823
  }
3824
+ const groupBy = config.groupBy;
3825
+ if (groupBy && (groupBy.key || groupBy.into)) {
3826
+ if (!groupBy.key)
3827
+ errors.push("One record per key: choose the key field (groupBy.key)");
3828
+ if (!groupBy.into)
3829
+ errors.push("One record per key: name the field that holds the lines (groupBy.into)");
3830
+ if (groupBy.key && groupBy.into && !(config.mappings ?? []).some((m) => m.lineField)) {
3831
+ errors.push("One record per key: mark at least one mapping as a line field (lineField: true)");
3832
+ }
3833
+ }
3815
3834
  return { valid: errors.length === 0, errors };
3816
3835
  },
3817
3836
  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 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: { type: "array", required: false },
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."
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) => {
@@ -3654,6 +3663,16 @@ var TransformConfig = {
3654
3663
  });
3655
3664
  }
3656
3665
  }
3666
+ const groupBy = config.groupBy;
3667
+ if (groupBy && (groupBy.key || groupBy.into)) {
3668
+ if (!groupBy.key)
3669
+ errors.push("One record per key: choose the key field (groupBy.key)");
3670
+ if (!groupBy.into)
3671
+ errors.push("One record per key: name the field that holds the lines (groupBy.into)");
3672
+ if (groupBy.key && groupBy.into && !(config.mappings ?? []).some((m) => m.lineField)) {
3673
+ errors.push("One record per key: mark at least one mapping as a line field (lineField: true)");
3674
+ }
3675
+ }
3657
3676
  return { valid: errors.length === 0, errors };
3658
3677
  },
3659
3678
  merge: (userConfig) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octaviaflow/flow-rules",
3
- "version": "0.14.0",
3
+ "version": "0.15.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",