From 7c352d9bfee53efaeb13e9c42c2535b2f34752f2 Mon Sep 17 00:00:00 2001 From: Mitch Spano Date: Fri, 13 Feb 2026 16:31:16 -0600 Subject: [PATCH 1/2] Delineate between FlowRecordCreate and FlowRecordUpdate - Refactor isRecordCreate and isRecordUpdate functions to include additional validation for required properties. --- src/main/flow_parser.ts | 18 +- src/main/flow_types.ts | 4 +- src/test/flow_parser_test.ts | 200 ++++++++++++++---- .../goldens/multiple_elements.flow-meta.xml | 6 +- ...cord_create_vs_record_update.flow-meta.xml | 65 ++++++ ...eate_without_input_reference.flow-meta.xml | 68 ++++++ .../goldens/single_elements.flow-meta.xml | 2 + 7 files changed, 322 insertions(+), 41 deletions(-) create mode 100644 src/test/goldens/record_create_vs_record_update.flow-meta.xml create mode 100644 src/test/goldens/record_create_without_input_reference.flow-meta.xml diff --git a/src/main/flow_parser.ts b/src/main/flow_parser.ts index 456fc6a..783cb37 100644 --- a/src/main/flow_parser.ts +++ b/src/main/flow_parser.ts @@ -687,7 +687,16 @@ function isLoop(node: flowTypes.FlowNode): node is flowTypes.FlowLoop { function isRecordCreate( node: flowTypes.FlowNode, ): node is flowTypes.FlowRecordCreate { - return (node as flowTypes.FlowRecordCreate).inputReference !== undefined; + const recordCreate = node as flowTypes.FlowRecordCreate; + // Check for required FlowRecordCreate properties + const hasInputAssignments = recordCreate.inputAssignments !== undefined && + Array.isArray(recordCreate.inputAssignments); + const hasObject = recordCreate.object !== undefined; + // FlowRecordUpdate has filters, FlowRecordCreate does not + const recordUpdate = node as flowTypes.FlowRecordUpdate; + const hasNoFilters = recordUpdate.filters === undefined; + + return hasInputAssignments && hasObject && hasNoFilters; } function isRecordDelete( @@ -705,7 +714,12 @@ function isRecordLookup( function isRecordUpdate( node: flowTypes.FlowNode, ): node is flowTypes.FlowRecordUpdate { - return (node as flowTypes.FlowRecordUpdate).inputReference !== undefined; + const recordUpdate = node as flowTypes.FlowRecordUpdate; + return ( + recordUpdate.inputAssignments !== undefined && + recordUpdate.filters !== undefined && + recordUpdate.object !== undefined + ); } function isRecordRollback( diff --git a/src/main/flow_types.ts b/src/main/flow_types.ts index cfe5d62..fae5ca7 100644 --- a/src/main/flow_types.ts +++ b/src/main/flow_types.ts @@ -554,7 +554,7 @@ export interface FlowRecordCreate extends FlowNode { connector?: FlowConnector; faultConnector?: FlowConnector; inputAssignments: FlowInputFieldAssignment[]; - inputReference: string; + inputReference?: string; object: string; storeOutputAutomatically?: boolean; // default: false, Available in API version 48.0 and later } @@ -598,7 +598,7 @@ export interface FlowRecordUpdate extends FlowNode { faultConnector?: FlowConnector; filters: FlowRecordFilter[]; inputAssignments: FlowInputFieldAssignment[]; // Assuming FlowInputFieldAssignment is a pre-existing interface - inputReference: string; + inputReference?: string; object: string; } diff --git a/src/test/flow_parser_test.ts b/src/test/flow_parser_test.ts index e91e1fc..61d7c67 100644 --- a/src/test/flow_parser_test.ts +++ b/src/test/flow_parser_test.ts @@ -56,6 +56,14 @@ const TEST_FILES = { GOLDENS_PATH, "non_async_scheduled_path.flow-meta.xml", ), + recordCreateWithoutInputReference: join( + GOLDENS_PATH, + "record_create_without_input_reference.flow-meta.xml", + ), + recordCreateVsRecordUpdate: join( + GOLDENS_PATH, + "record_create_vs_record_update.flow-meta.xml", + ), }; const NODE_NAMES = { @@ -534,11 +542,73 @@ Deno.test("FlowParser", async (t) => { }, ); + await t.step("should handle single async path correctly", async () => { + systemUnderTest = new FlowParser( + Deno.readTextFileSync(TEST_FILES.asyncPathTest), + ); + + parsedFlow = await systemUnderTest.generateFlowDefinition(); + + assert(parsedFlow.transitions); + assertEquals(parsedFlow.transitions.length, 2); + + // Main flow transition (no label) + assertEquals(parsedFlow.transitions[0], { + from: START_NODE_NAME, + to: "main_update", + fault: false, + label: undefined, + }); + + // Async path transition (with path type as label) + assertEquals(parsedFlow.transitions[1], { + from: START_NODE_NAME, + to: "async_update", + fault: false, + label: "AsyncAfterCommit", + }); + }); + + await t.step("should handle multiple async paths correctly", async () => { + systemUnderTest = new FlowParser( + Deno.readTextFileSync(TEST_FILES.multipleAsyncPaths), + ); + + parsedFlow = await systemUnderTest.generateFlowDefinition(); + + assert(parsedFlow.transitions); + assertEquals(parsedFlow.transitions.length, 3); + + // Main flow transition (no label) + assertEquals(parsedFlow.transitions[0], { + from: START_NODE_NAME, + to: "main_update", + fault: false, + label: undefined, + }); + + // First async path transition + assertEquals(parsedFlow.transitions[1], { + from: START_NODE_NAME, + to: "async_update_1", + fault: false, + label: "AsyncAfterCommit", + }); + + // Second async path transition + assertEquals(parsedFlow.transitions[2], { + from: START_NODE_NAME, + to: "async_update_2", + fault: false, + label: "AsyncAfterCommit", + }); + }); + await t.step( - "should handle single async path correctly", + "should not label non-async scheduled paths as asynchronous", async () => { systemUnderTest = new FlowParser( - Deno.readTextFileSync(TEST_FILES.asyncPathTest), + Deno.readTextFileSync(TEST_FILES.nonAsyncScheduledPath), ); parsedFlow = await systemUnderTest.generateFlowDefinition(); @@ -554,81 +624,139 @@ Deno.test("FlowParser", async (t) => { label: undefined, }); - // Async path transition (with path type as label) + // Non-async scheduled path transition (with path type as label) assertEquals(parsedFlow.transitions[1], { from: START_NODE_NAME, - to: "async_update", + to: "scheduled_update", fault: false, - label: "AsyncAfterCommit", + label: "RecordField", }); }, ); await t.step( - "should handle multiple async paths correctly", + "should include fault connectors for recordCreates nodes without inputReference", async () => { systemUnderTest = new FlowParser( - Deno.readTextFileSync(TEST_FILES.multipleAsyncPaths), + Deno.readTextFileSync(TEST_FILES.recordCreateWithoutInputReference), ); parsedFlow = await systemUnderTest.generateFlowDefinition(); + assert(parsedFlow); + assert(parsedFlow.recordCreates); + assertEquals(parsedFlow.recordCreates.length, 1); + + const recordCreate = parsedFlow.recordCreates[0]; + assertEquals(recordCreate.name, "create_property"); + assertEquals(recordCreate.object, "Property__c"); + // Verify that inputAssignments is present (required for identification) + assert(recordCreate.inputAssignments); + assertEquals(recordCreate.inputAssignments.length, 2); + // Verify that inputReference is optional and can be undefined + assertEquals(recordCreate.inputReference, undefined); + // Verify that fault connector is present and parsed correctly + assert(recordCreate.faultConnector); + assertEquals( + recordCreate.faultConnector.targetReference, + "error_creating_records", + ); + // Verify that normal connector is also present + assert(recordCreate.connector); + assertEquals(recordCreate.connector.targetReference, "success_screen"); + + // Verify that transitions include both normal and fault paths assert(parsedFlow.transitions); assertEquals(parsedFlow.transitions.length, 3); - // Main flow transition (no label) + // Start node to create_property assertEquals(parsedFlow.transitions[0], { from: START_NODE_NAME, - to: "main_update", + to: "create_property", fault: false, label: undefined, }); - // First async path transition + // Normal path: create_property to success_screen assertEquals(parsedFlow.transitions[1], { - from: START_NODE_NAME, - to: "async_update_1", + from: "create_property", + to: "success_screen", fault: false, - label: "AsyncAfterCommit", + label: undefined, }); - // Second async path transition + // Fault path: create_property to error_creating_records assertEquals(parsedFlow.transitions[2], { - from: START_NODE_NAME, - to: "async_update_2", - fault: false, - label: "AsyncAfterCommit", + from: "create_property", + to: "error_creating_records", + fault: true, + label: "Fault", }); }, ); await t.step( - "should not label non-async scheduled paths as asynchronous", + "should still work correctly for recordCreates nodes with inputReference (backward compatibility)", async () => { systemUnderTest = new FlowParser( - Deno.readTextFileSync(TEST_FILES.nonAsyncScheduledPath), + Deno.readTextFileSync(TEST_FILES.sample), ); parsedFlow = await systemUnderTest.generateFlowDefinition(); - assert(parsedFlow.transitions); - assertEquals(parsedFlow.transitions.length, 2); + assert(parsedFlow); + assert(parsedFlow.recordCreates); + assertEquals(parsedFlow.recordCreates.length, 1); - // Main flow transition (no label) - assertEquals(parsedFlow.transitions[0], { - from: START_NODE_NAME, - to: "main_update", - fault: false, - label: undefined, - }); + const recordCreate = parsedFlow.recordCreates[0]; + assertEquals(recordCreate.name, "Insert_Tag"); + // Verify that inputReference is present (traditional approach) + assertEquals(recordCreate.inputReference, "tagToInsert"); + // Verify that fault connector is still correctly parsed and included + assert(recordCreate.faultConnector); + assertEquals( + recordCreate.faultConnector.targetReference, + "Add_Issue_Inserting_Tag_Record_Error", + ); - // Non-async scheduled path transition (with path type as label) - assertEquals(parsedFlow.transitions[1], { - from: START_NODE_NAME, - to: "scheduled_update", - fault: false, - label: "RecordField", - }); + // Verify that the fault transition is included in the transitions array + const faultTransition = parsedFlow.transitions?.find( + (t) => t.from === "Insert_Tag" && t.fault === true, + ); + assert(faultTransition); + assertEquals(faultTransition.to, "Add_Issue_Inserting_Tag_Record_Error"); + assertEquals(faultTransition.label, "Fault"); + }, + ); + + await t.step( + "should correctly distinguish recordCreates from recordUpdates", + async () => { + systemUnderTest = new FlowParser( + Deno.readTextFileSync(TEST_FILES.recordCreateVsRecordUpdate), + ); + parsedFlow = await systemUnderTest.generateFlowDefinition(); + + assert(parsedFlow); + + // Verify recordCreates is identified correctly (has inputAssignments but no filters) + assert(parsedFlow.recordCreates); + assertEquals(parsedFlow.recordCreates.length, 1); + assertEquals(parsedFlow.recordCreates[0].name, "create_record"); + assert(parsedFlow.recordCreates[0].inputAssignments); + // recordCreates should not have filters + assertEquals( + (parsedFlow.recordCreates[0] as flowTypes.FlowRecordUpdate).filters, + undefined, + ); + + // Verify recordUpdates is identified correctly (has both inputAssignments and filters) + assert(parsedFlow.recordUpdates); + assertEquals(parsedFlow.recordUpdates.length, 1); + assertEquals(parsedFlow.recordUpdates[0].name, "update_record"); + assert(parsedFlow.recordUpdates[0].inputAssignments); + assert(parsedFlow.recordUpdates[0].filters); + assertEquals(parsedFlow.recordUpdates[0].filters.length, 1); }, ); }); diff --git a/src/test/goldens/multiple_elements.flow-meta.xml b/src/test/goldens/multiple_elements.flow-meta.xml index 7ed6600..b66476e 100644 --- a/src/test/goldens/multiple_elements.flow-meta.xml +++ b/src/test/goldens/multiple_elements.flow-meta.xml @@ -1,6 +1,6 @@ + + + 64.0 + + Flow + + 0 + 0 + + create_record + + + + create_record + + 100 + 100 + + update_record + + + Name + + Test + + + Account + + + update_record + + 200 + 200 + + Id + EqualTo + + create_record + + + + Status__c + + Active + + + Account + + diff --git a/src/test/goldens/record_create_without_input_reference.flow-meta.xml b/src/test/goldens/record_create_without_input_reference.flow-meta.xml new file mode 100644 index 0000000..7e67fe9 --- /dev/null +++ b/src/test/goldens/record_create_without_input_reference.flow-meta.xml @@ -0,0 +1,68 @@ + + + + + 64.0 + + Flow + + 0 + 0 + + create_property + + + + create_property + + 666 + 638 + + success_screen + + + error_creating_records + + + Name + + Test Property + + + + Status__c + + Available + + + + Property__c + true + + + success_screen + + 666 + 758 + + + error_creating_records + + 1458 + 758 + + diff --git a/src/test/goldens/single_elements.flow-meta.xml b/src/test/goldens/single_elements.flow-meta.xml index 0a93a7b..65548e7 100644 --- a/src/test/goldens/single_elements.flow-meta.xml +++ b/src/test/goldens/single_elements.flow-meta.xml @@ -43,6 +43,8 @@ myRecordCreate + Account + myRecordDelete From ce6270325274f11876cc4a7dbbd5fa88cef9980f Mon Sep 17 00:00:00 2001 From: Mitch Spano Date: Fri, 13 Feb 2026 16:34:12 -0600 Subject: [PATCH 2/2] Bump package version --- deno.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deno.json b/deno.json index 98cdd89..e89e484 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,6 @@ { "name": "@goog/flow-lens", - "version": "0.1.15", + "version": "0.1.16", "license": "Apache", "exports": "./src/main/main.ts", "imports": {