@octaviaflow/flow-rules 0.8.1 → 0.10.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/triggers/connector-trigger/events.d.ts +46 -0
- package/dist/index.cjs +126 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +126 -3
- package/dist/rules/configContracts.d.ts +8 -7
- package/package.json +2 -2
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a Connector Trigger can subscribe to, per connector.
|
|
3
|
+
*
|
|
4
|
+
* ONE vocabulary for the editor's panel and for an agent authoring over MCP.
|
|
5
|
+
* The trigger's `events` — `[{ entity, operations }]` — is what registers the
|
|
6
|
+
* flow's connector subscription, the record every inbound provider event is
|
|
7
|
+
* matched against: an entity name outside this list (or in the wrong case —
|
|
8
|
+
* HubSpot is lowercase, Salesforce is capitalised) registers a subscription
|
|
9
|
+
* that never fires, with no error anywhere. An agent wrote `lead` for a
|
|
10
|
+
* HubSpot contact trigger on 2026-09-26 and spent an hour on it.
|
|
11
|
+
*
|
|
12
|
+
* Copied as-is from octaviaflow-ui's ConnectorTriggerPanel (the panel's tables
|
|
13
|
+
* were the only copy); the panel is to import from here in its next flow-rules
|
|
14
|
+
* upgrade.
|
|
15
|
+
*/
|
|
16
|
+
export interface ConnectorEventEntity {
|
|
17
|
+
entity: string;
|
|
18
|
+
supportedOperations: string[];
|
|
19
|
+
}
|
|
20
|
+
/** One subscribed pair, as stored in a trigger step's `events`. */
|
|
21
|
+
export interface ConnectorTriggerEvent {
|
|
22
|
+
entity: string;
|
|
23
|
+
operations: string[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Connector ids that emit events a flow trigger can subscribe to. Database /
|
|
27
|
+
* http / openapi / ftp / etc. connectors are intentionally excluded: they
|
|
28
|
+
* cannot push events.
|
|
29
|
+
*/
|
|
30
|
+
export declare const EVENT_CAPABLE_CONNECTOR_IDS: ReadonlySet<string>;
|
|
31
|
+
/** Every subscribable entity, by connector id. */
|
|
32
|
+
export declare const CONNECTOR_EVENT_ENTITIES: Readonly<Record<string, ConnectorEventEntity[]>>;
|
|
33
|
+
export declare function getEntitiesForConnector(connectorId: string): ConnectorEventEntity[];
|
|
34
|
+
/** True when this connector type can drive an event trigger. */
|
|
35
|
+
export declare function isEventCapableConnector(connectorId: string): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* The one-line summary the canvas shows under a Connector Trigger
|
|
38
|
+
* (`Hubspot · 4 events`) — the same string the panel writes.
|
|
39
|
+
*/
|
|
40
|
+
export declare function summarizeConnectorTriggerEvents(connectorName: string, events: ConnectorTriggerEvent[]): string;
|
|
41
|
+
/**
|
|
42
|
+
* Why `events` cannot register a trigger on this connector — or `null` when
|
|
43
|
+
* it can. Names are matched EXACTLY, because the inbound matcher matches them
|
|
44
|
+
* exactly; a case-only miss says what was meant.
|
|
45
|
+
*/
|
|
46
|
+
export declare function validateConnectorTriggerEvents(connectorId: string, events: unknown): string | null;
|
package/dist/index.cjs
CHANGED
|
@@ -45,9 +45,11 @@ __export(exports_src, {
|
|
|
45
45
|
AggregateFunction: () => AggregateFunction,
|
|
46
46
|
CONDITION_OPERATORS: () => CONDITION_OPERATORS,
|
|
47
47
|
CONFIG_CONTRACTS: () => CONFIG_CONTRACTS,
|
|
48
|
+
CONNECTOR_EVENT_ENTITIES: () => CONNECTOR_EVENT_ENTITIES,
|
|
48
49
|
ConditionConfig: () => ConditionConfig,
|
|
49
50
|
DATABASE_CONNECTOR_IDS: () => DATABASE_CONNECTOR_IDS,
|
|
50
51
|
DebugConfig: () => DebugConfig,
|
|
52
|
+
EVENT_CAPABLE_CONNECTOR_IDS: () => EVENT_CAPABLE_CONNECTOR_IDS,
|
|
51
53
|
EdgeType: () => EdgeType,
|
|
52
54
|
EmailConfig: () => EmailConfig,
|
|
53
55
|
FieldType: () => FieldType,
|
|
@@ -89,20 +91,24 @@ __export(exports_src, {
|
|
|
89
91
|
getActionsByCategory: () => getActionsByCategory,
|
|
90
92
|
getAllActions: () => getAllActions,
|
|
91
93
|
getEdgeValidationStatus: () => getEdgeValidationStatus,
|
|
94
|
+
getEntitiesForConnector: () => getEntitiesForConnector,
|
|
92
95
|
getNodeConfigStatus: () => getNodeConfigStatus,
|
|
93
96
|
getNodeValidationStatus: () => getNodeValidationStatus,
|
|
94
97
|
getStorageProvider: () => getStorageProvider,
|
|
95
98
|
inferSchemaFromSample: () => inferSchemaFromSample,
|
|
96
99
|
isEditorPrivateKey: () => isEditorPrivateKey,
|
|
100
|
+
isEventCapableConnector: () => isEventCapableConnector,
|
|
97
101
|
isMappingFunction: () => isMappingFunction,
|
|
98
102
|
isQueryableConnector: () => isQueryableConnector,
|
|
99
103
|
mappingTransformFunctionName: () => mappingTransformFunctionName,
|
|
100
104
|
migrateDelayConfig: () => migrateDelayConfig,
|
|
101
105
|
queryDefinition: () => queryDefinition,
|
|
102
106
|
storageConfigKeys: () => storageConfigKeys,
|
|
107
|
+
summarizeConnectorTriggerEvents: () => summarizeConnectorTriggerEvents,
|
|
103
108
|
transformConnectionToEdge: () => transformConnectionToEdge,
|
|
104
109
|
transformFlowToWorkflow: () => transformFlowToWorkflow,
|
|
105
110
|
transformStepToNode: () => transformStepToNode,
|
|
111
|
+
validateConnectorTriggerEvents: () => validateConnectorTriggerEvents,
|
|
106
112
|
validateEdge: () => validateEdge,
|
|
107
113
|
validateFlowData: () => validateFlowData,
|
|
108
114
|
validateNode: () => validateNode,
|
|
@@ -555,7 +561,7 @@ var connectorTriggerDefinition = {
|
|
|
555
561
|
key: "events",
|
|
556
562
|
label: "Events",
|
|
557
563
|
type: "json" /* JSON */,
|
|
558
|
-
description:
|
|
564
|
+
description: 'Entity/operation pairs to subscribe to, as [{ "entity": "contact", "operations": ["Create", "Update"] }]. ' + "This is what registers the trigger — the platform derives the flow's connector subscription from it. " + "Entity and operation names are per connector (describe_action_type lists them); a name outside that list never fires.",
|
|
559
565
|
validation: { required: true }
|
|
560
566
|
}
|
|
561
567
|
]
|
|
@@ -3974,6 +3980,124 @@ function storageConfigKeys() {
|
|
|
3974
3980
|
}
|
|
3975
3981
|
return [...keys];
|
|
3976
3982
|
}
|
|
3983
|
+
// src/catalog/triggers/connector-trigger/events.ts
|
|
3984
|
+
var EVENT_CAPABLE_CONNECTOR_IDS = new Set([
|
|
3985
|
+
"quickbooks_online",
|
|
3986
|
+
"shopify",
|
|
3987
|
+
"hubspot",
|
|
3988
|
+
"salesforce",
|
|
3989
|
+
"google_sheets",
|
|
3990
|
+
"whatsapp"
|
|
3991
|
+
]);
|
|
3992
|
+
var QBO_ENTITIES = [
|
|
3993
|
+
{ entity: "Invoice", supportedOperations: ["Create", "Update", "Delete", "Void", "Emailed"] },
|
|
3994
|
+
{ entity: "Customer", supportedOperations: ["Create", "Update", "Delete", "Merge"] },
|
|
3995
|
+
{ entity: "Payment", supportedOperations: ["Create", "Update", "Delete", "Void"] },
|
|
3996
|
+
{ entity: "Bill", supportedOperations: ["Create", "Update", "Delete", "Void"] },
|
|
3997
|
+
{ entity: "Estimate", supportedOperations: ["Create", "Update", "Delete"] },
|
|
3998
|
+
{ entity: "SalesReceipt", supportedOperations: ["Create", "Update", "Delete", "Void", "Emailed"] },
|
|
3999
|
+
{ entity: "CreditMemo", supportedOperations: ["Create", "Update", "Delete", "Void", "Emailed"] },
|
|
4000
|
+
{ entity: "Vendor", supportedOperations: ["Create", "Update", "Delete", "Merge"] },
|
|
4001
|
+
{ entity: "Item", supportedOperations: ["Create", "Update", "Delete"] },
|
|
4002
|
+
{ entity: "Account", supportedOperations: ["Create", "Update", "Delete", "Merge"] },
|
|
4003
|
+
{ entity: "JournalEntry", supportedOperations: ["Create", "Update", "Delete", "Void"] },
|
|
4004
|
+
{ entity: "PurchaseOrder", supportedOperations: ["Create", "Update", "Delete"] }
|
|
4005
|
+
];
|
|
4006
|
+
var SALESFORCE_ENTITIES = [
|
|
4007
|
+
{ entity: "Lead", supportedOperations: ["Create", "Update", "Delete", "Undelete"] },
|
|
4008
|
+
{ entity: "Account", supportedOperations: ["Create", "Update", "Delete", "Undelete"] },
|
|
4009
|
+
{ entity: "Contact", supportedOperations: ["Create", "Update", "Delete", "Undelete"] },
|
|
4010
|
+
{ entity: "Opportunity", supportedOperations: ["Create", "Update", "Delete", "Undelete"] },
|
|
4011
|
+
{ entity: "Case", supportedOperations: ["Create", "Update", "Delete", "Undelete"] }
|
|
4012
|
+
];
|
|
4013
|
+
var HUBSPOT_ENTITIES = [
|
|
4014
|
+
{ entity: "contact", supportedOperations: ["Create", "Update", "Delete", "Merge", "Restore", "AssociationChange"] },
|
|
4015
|
+
{ entity: "company", supportedOperations: ["Create", "Update", "Delete", "Merge", "Restore", "AssociationChange"] },
|
|
4016
|
+
{ entity: "deal", supportedOperations: ["Create", "Update", "Delete", "Merge", "Restore", "AssociationChange"] },
|
|
4017
|
+
{ entity: "ticket", supportedOperations: ["Create", "Update", "Delete", "Merge", "Restore", "AssociationChange"] },
|
|
4018
|
+
{ entity: "product", supportedOperations: ["Create", "Update", "Delete", "Restore"] },
|
|
4019
|
+
{ entity: "line_item", supportedOperations: ["Create", "Update", "Delete", "Restore"] },
|
|
4020
|
+
{ entity: "lead", supportedOperations: ["Create", "Update", "Delete", "Restore"] },
|
|
4021
|
+
{ entity: "quote", supportedOperations: ["Create", "Update", "Delete", "Restore"] },
|
|
4022
|
+
{ entity: "call", supportedOperations: ["Create", "Update", "Delete"] },
|
|
4023
|
+
{ entity: "email", supportedOperations: ["Create", "Update", "Delete"] },
|
|
4024
|
+
{ entity: "meeting_event", supportedOperations: ["Create", "Update", "Delete"] },
|
|
4025
|
+
{ entity: "task", supportedOperations: ["Create", "Update", "Delete"] },
|
|
4026
|
+
{ entity: "note", supportedOperations: ["Create", "Update", "Delete"] },
|
|
4027
|
+
{ entity: "order", supportedOperations: ["Create", "Update", "Delete"] },
|
|
4028
|
+
{ entity: "cart", supportedOperations: ["Create", "Update", "Delete"] },
|
|
4029
|
+
{ entity: "invoice", supportedOperations: ["Create", "Update", "Delete"] }
|
|
4030
|
+
];
|
|
4031
|
+
var SHOPIFY_ENTITIES = [
|
|
4032
|
+
{ entity: "Order", supportedOperations: ["Create", "Update", "Paid", "Fulfilled", "Cancelled"] },
|
|
4033
|
+
{ entity: "Product", supportedOperations: ["Create", "Update"] },
|
|
4034
|
+
{ entity: "Customer", supportedOperations: ["Create", "Update"] }
|
|
4035
|
+
];
|
|
4036
|
+
var GOOGLE_SHEETS_ENTITIES = [
|
|
4037
|
+
{ entity: "Row", supportedOperations: ["Create", "Update", "Delete"] },
|
|
4038
|
+
{ entity: "Sheet", supportedOperations: ["Create", "Update"] }
|
|
4039
|
+
];
|
|
4040
|
+
var WHATSAPP_ENTITIES = [
|
|
4041
|
+
{ entity: "Message", supportedOperations: ["Received"] }
|
|
4042
|
+
];
|
|
4043
|
+
var CONNECTOR_EVENT_ENTITIES = {
|
|
4044
|
+
quickbooks_online: QBO_ENTITIES,
|
|
4045
|
+
salesforce: SALESFORCE_ENTITIES,
|
|
4046
|
+
hubspot: HUBSPOT_ENTITIES,
|
|
4047
|
+
shopify: SHOPIFY_ENTITIES,
|
|
4048
|
+
google_sheets: GOOGLE_SHEETS_ENTITIES,
|
|
4049
|
+
whatsapp: WHATSAPP_ENTITIES
|
|
4050
|
+
};
|
|
4051
|
+
function getEntitiesForConnector(connectorId) {
|
|
4052
|
+
return CONNECTOR_EVENT_ENTITIES[connectorId] ?? [];
|
|
4053
|
+
}
|
|
4054
|
+
function isEventCapableConnector(connectorId) {
|
|
4055
|
+
return EVENT_CAPABLE_CONNECTOR_IDS.has(connectorId) && getEntitiesForConnector(connectorId).length > 0;
|
|
4056
|
+
}
|
|
4057
|
+
function summarizeConnectorTriggerEvents(connectorName, events) {
|
|
4058
|
+
const eventCount = events.reduce((acc, ev) => acc + ev.operations.length, 0);
|
|
4059
|
+
if (eventCount === 0)
|
|
4060
|
+
return connectorName;
|
|
4061
|
+
const noun = eventCount === 1 ? "event" : "events";
|
|
4062
|
+
return `${connectorName} · ${eventCount} ${noun}`;
|
|
4063
|
+
}
|
|
4064
|
+
function validateConnectorTriggerEvents(connectorId, events) {
|
|
4065
|
+
const entities = getEntitiesForConnector(connectorId);
|
|
4066
|
+
if (!isEventCapableConnector(connectorId)) {
|
|
4067
|
+
return `"${connectorId}" does not emit events a trigger can subscribe to. ` + `Event-capable connectors: ${[...EVENT_CAPABLE_CONNECTOR_IDS].join(", ")}.`;
|
|
4068
|
+
}
|
|
4069
|
+
if (!Array.isArray(events) || events.length === 0) {
|
|
4070
|
+
return 'events must be a non-empty array of { "entity": string, "operations": string[] }.';
|
|
4071
|
+
}
|
|
4072
|
+
const seen = new Set;
|
|
4073
|
+
for (const ev of events) {
|
|
4074
|
+
const entity = ev?.entity;
|
|
4075
|
+
const operations = ev?.operations;
|
|
4076
|
+
if (typeof entity !== "string" || !entity) {
|
|
4077
|
+
return 'Each event needs an "entity" (string).';
|
|
4078
|
+
}
|
|
4079
|
+
const known = entities.find((e) => e.entity === entity);
|
|
4080
|
+
if (!known) {
|
|
4081
|
+
const caseHit = entities.find((e) => e.entity.toLowerCase() === entity.toLowerCase());
|
|
4082
|
+
const hint = caseHit ? ` Did you mean "${caseHit.entity}"? Names are case-sensitive.` : "";
|
|
4083
|
+
return `Unknown entity "${entity}" for ${connectorId}.${hint} Entities: ${entities.map((e) => e.entity).join(", ")}.`;
|
|
4084
|
+
}
|
|
4085
|
+
if (seen.has(entity))
|
|
4086
|
+
return `Entity "${entity}" is listed twice — give it one entry with all its operations.`;
|
|
4087
|
+
seen.add(entity);
|
|
4088
|
+
if (!Array.isArray(operations) || operations.length === 0) {
|
|
4089
|
+
return `Event "${entity}" needs a non-empty "operations" array. Operations: ${known.supportedOperations.join(", ")}.`;
|
|
4090
|
+
}
|
|
4091
|
+
for (const op of operations) {
|
|
4092
|
+
if (typeof op !== "string" || !known.supportedOperations.includes(op)) {
|
|
4093
|
+
const caseHit = typeof op === "string" ? known.supportedOperations.find((o) => o.toLowerCase() === op.toLowerCase()) : undefined;
|
|
4094
|
+
const hint = caseHit ? ` Did you mean "${caseHit}"?` : "";
|
|
4095
|
+
return `Unknown operation "${String(op)}" for ${connectorId} ${entity}.${hint} Operations: ${known.supportedOperations.join(", ")}.`;
|
|
4096
|
+
}
|
|
4097
|
+
}
|
|
4098
|
+
}
|
|
4099
|
+
return null;
|
|
4100
|
+
}
|
|
3977
4101
|
// src/catalog/workflow.ts
|
|
3978
4102
|
var NodeStatus;
|
|
3979
4103
|
((NodeStatus) => {
|
|
@@ -5046,7 +5170,6 @@ function validateStepConfig(step) {
|
|
|
5046
5170
|
if (!definition)
|
|
5047
5171
|
return issues;
|
|
5048
5172
|
const declared = new Map(contractFieldsFor(definition).map((f) => [f.key, f]));
|
|
5049
|
-
const derived = contractDerivedKeys(definition);
|
|
5050
5173
|
const entries = Object.entries(config);
|
|
5051
5174
|
for (const [key, value] of entries) {
|
|
5052
5175
|
const field = declared.get(key);
|
|
@@ -5062,7 +5185,7 @@ function validateStepConfig(step) {
|
|
|
5062
5185
|
}
|
|
5063
5186
|
if (!typeMatches(field, value)) {
|
|
5064
5187
|
issues.push({
|
|
5065
|
-
severity:
|
|
5188
|
+
severity: "error",
|
|
5066
5189
|
code: "FIELD_WRONG_TYPE",
|
|
5067
5190
|
stepId,
|
|
5068
5191
|
field: key,
|
package/dist/index.d.ts
CHANGED
|
@@ -40,6 +40,7 @@ export { DATABASE_CONNECTOR_IDS, type FieldMapping, TRANSFORM_OPERATIONS, Transf
|
|
|
40
40
|
export { STORAGE_CONNECTOR_IDS, type StorageExtraField, type StoragePathField, getStorageProvider, storageConfigKeys, } from "./catalog/storage/storageCapabilities";
|
|
41
41
|
export { ManualTriggerConfig } from "./catalog/triggers/manual-trigger/index";
|
|
42
42
|
export { type ManualTriggerConfigType } from "./catalog/triggers/manual-trigger/config";
|
|
43
|
+
export { CONNECTOR_EVENT_ENTITIES, EVENT_CAPABLE_CONNECTOR_IDS, getEntitiesForConnector, isEventCapableConnector, summarizeConnectorTriggerEvents, validateConnectorTriggerEvents, type ConnectorEventEntity, type ConnectorTriggerEvent, } from "./catalog/triggers/connector-trigger/events";
|
|
43
44
|
export * from "./catalog/workflow";
|
|
44
45
|
export { type FlowConnection, type FlowData, type FlowStep, generateConnectionsFromSteps, transformConnectionToEdge, transformFlowToWorkflow, transformStepToNode, validateFlowData, } from "./rules/flowShape";
|
|
45
46
|
export { flattenNestedWorkflow } from "./rules/flattenNestedWorkflow";
|
package/dist/index.js
CHANGED
|
@@ -441,7 +441,7 @@ var connectorTriggerDefinition = {
|
|
|
441
441
|
key: "events",
|
|
442
442
|
label: "Events",
|
|
443
443
|
type: "json" /* JSON */,
|
|
444
|
-
description:
|
|
444
|
+
description: 'Entity/operation pairs to subscribe to, as [{ "entity": "contact", "operations": ["Create", "Update"] }]. ' + "This is what registers the trigger — the platform derives the flow's connector subscription from it. " + "Entity and operation names are per connector (describe_action_type lists them); a name outside that list never fires.",
|
|
445
445
|
validation: { required: true }
|
|
446
446
|
}
|
|
447
447
|
]
|
|
@@ -3860,6 +3860,124 @@ function storageConfigKeys() {
|
|
|
3860
3860
|
}
|
|
3861
3861
|
return [...keys];
|
|
3862
3862
|
}
|
|
3863
|
+
// src/catalog/triggers/connector-trigger/events.ts
|
|
3864
|
+
var EVENT_CAPABLE_CONNECTOR_IDS = new Set([
|
|
3865
|
+
"quickbooks_online",
|
|
3866
|
+
"shopify",
|
|
3867
|
+
"hubspot",
|
|
3868
|
+
"salesforce",
|
|
3869
|
+
"google_sheets",
|
|
3870
|
+
"whatsapp"
|
|
3871
|
+
]);
|
|
3872
|
+
var QBO_ENTITIES = [
|
|
3873
|
+
{ entity: "Invoice", supportedOperations: ["Create", "Update", "Delete", "Void", "Emailed"] },
|
|
3874
|
+
{ entity: "Customer", supportedOperations: ["Create", "Update", "Delete", "Merge"] },
|
|
3875
|
+
{ entity: "Payment", supportedOperations: ["Create", "Update", "Delete", "Void"] },
|
|
3876
|
+
{ entity: "Bill", supportedOperations: ["Create", "Update", "Delete", "Void"] },
|
|
3877
|
+
{ entity: "Estimate", supportedOperations: ["Create", "Update", "Delete"] },
|
|
3878
|
+
{ entity: "SalesReceipt", supportedOperations: ["Create", "Update", "Delete", "Void", "Emailed"] },
|
|
3879
|
+
{ entity: "CreditMemo", supportedOperations: ["Create", "Update", "Delete", "Void", "Emailed"] },
|
|
3880
|
+
{ entity: "Vendor", supportedOperations: ["Create", "Update", "Delete", "Merge"] },
|
|
3881
|
+
{ entity: "Item", supportedOperations: ["Create", "Update", "Delete"] },
|
|
3882
|
+
{ entity: "Account", supportedOperations: ["Create", "Update", "Delete", "Merge"] },
|
|
3883
|
+
{ entity: "JournalEntry", supportedOperations: ["Create", "Update", "Delete", "Void"] },
|
|
3884
|
+
{ entity: "PurchaseOrder", supportedOperations: ["Create", "Update", "Delete"] }
|
|
3885
|
+
];
|
|
3886
|
+
var SALESFORCE_ENTITIES = [
|
|
3887
|
+
{ entity: "Lead", supportedOperations: ["Create", "Update", "Delete", "Undelete"] },
|
|
3888
|
+
{ entity: "Account", supportedOperations: ["Create", "Update", "Delete", "Undelete"] },
|
|
3889
|
+
{ entity: "Contact", supportedOperations: ["Create", "Update", "Delete", "Undelete"] },
|
|
3890
|
+
{ entity: "Opportunity", supportedOperations: ["Create", "Update", "Delete", "Undelete"] },
|
|
3891
|
+
{ entity: "Case", supportedOperations: ["Create", "Update", "Delete", "Undelete"] }
|
|
3892
|
+
];
|
|
3893
|
+
var HUBSPOT_ENTITIES = [
|
|
3894
|
+
{ entity: "contact", supportedOperations: ["Create", "Update", "Delete", "Merge", "Restore", "AssociationChange"] },
|
|
3895
|
+
{ entity: "company", supportedOperations: ["Create", "Update", "Delete", "Merge", "Restore", "AssociationChange"] },
|
|
3896
|
+
{ entity: "deal", supportedOperations: ["Create", "Update", "Delete", "Merge", "Restore", "AssociationChange"] },
|
|
3897
|
+
{ entity: "ticket", supportedOperations: ["Create", "Update", "Delete", "Merge", "Restore", "AssociationChange"] },
|
|
3898
|
+
{ entity: "product", supportedOperations: ["Create", "Update", "Delete", "Restore"] },
|
|
3899
|
+
{ entity: "line_item", supportedOperations: ["Create", "Update", "Delete", "Restore"] },
|
|
3900
|
+
{ entity: "lead", supportedOperations: ["Create", "Update", "Delete", "Restore"] },
|
|
3901
|
+
{ entity: "quote", supportedOperations: ["Create", "Update", "Delete", "Restore"] },
|
|
3902
|
+
{ entity: "call", supportedOperations: ["Create", "Update", "Delete"] },
|
|
3903
|
+
{ entity: "email", supportedOperations: ["Create", "Update", "Delete"] },
|
|
3904
|
+
{ entity: "meeting_event", supportedOperations: ["Create", "Update", "Delete"] },
|
|
3905
|
+
{ entity: "task", supportedOperations: ["Create", "Update", "Delete"] },
|
|
3906
|
+
{ entity: "note", supportedOperations: ["Create", "Update", "Delete"] },
|
|
3907
|
+
{ entity: "order", supportedOperations: ["Create", "Update", "Delete"] },
|
|
3908
|
+
{ entity: "cart", supportedOperations: ["Create", "Update", "Delete"] },
|
|
3909
|
+
{ entity: "invoice", supportedOperations: ["Create", "Update", "Delete"] }
|
|
3910
|
+
];
|
|
3911
|
+
var SHOPIFY_ENTITIES = [
|
|
3912
|
+
{ entity: "Order", supportedOperations: ["Create", "Update", "Paid", "Fulfilled", "Cancelled"] },
|
|
3913
|
+
{ entity: "Product", supportedOperations: ["Create", "Update"] },
|
|
3914
|
+
{ entity: "Customer", supportedOperations: ["Create", "Update"] }
|
|
3915
|
+
];
|
|
3916
|
+
var GOOGLE_SHEETS_ENTITIES = [
|
|
3917
|
+
{ entity: "Row", supportedOperations: ["Create", "Update", "Delete"] },
|
|
3918
|
+
{ entity: "Sheet", supportedOperations: ["Create", "Update"] }
|
|
3919
|
+
];
|
|
3920
|
+
var WHATSAPP_ENTITIES = [
|
|
3921
|
+
{ entity: "Message", supportedOperations: ["Received"] }
|
|
3922
|
+
];
|
|
3923
|
+
var CONNECTOR_EVENT_ENTITIES = {
|
|
3924
|
+
quickbooks_online: QBO_ENTITIES,
|
|
3925
|
+
salesforce: SALESFORCE_ENTITIES,
|
|
3926
|
+
hubspot: HUBSPOT_ENTITIES,
|
|
3927
|
+
shopify: SHOPIFY_ENTITIES,
|
|
3928
|
+
google_sheets: GOOGLE_SHEETS_ENTITIES,
|
|
3929
|
+
whatsapp: WHATSAPP_ENTITIES
|
|
3930
|
+
};
|
|
3931
|
+
function getEntitiesForConnector(connectorId) {
|
|
3932
|
+
return CONNECTOR_EVENT_ENTITIES[connectorId] ?? [];
|
|
3933
|
+
}
|
|
3934
|
+
function isEventCapableConnector(connectorId) {
|
|
3935
|
+
return EVENT_CAPABLE_CONNECTOR_IDS.has(connectorId) && getEntitiesForConnector(connectorId).length > 0;
|
|
3936
|
+
}
|
|
3937
|
+
function summarizeConnectorTriggerEvents(connectorName, events) {
|
|
3938
|
+
const eventCount = events.reduce((acc, ev) => acc + ev.operations.length, 0);
|
|
3939
|
+
if (eventCount === 0)
|
|
3940
|
+
return connectorName;
|
|
3941
|
+
const noun = eventCount === 1 ? "event" : "events";
|
|
3942
|
+
return `${connectorName} · ${eventCount} ${noun}`;
|
|
3943
|
+
}
|
|
3944
|
+
function validateConnectorTriggerEvents(connectorId, events) {
|
|
3945
|
+
const entities = getEntitiesForConnector(connectorId);
|
|
3946
|
+
if (!isEventCapableConnector(connectorId)) {
|
|
3947
|
+
return `"${connectorId}" does not emit events a trigger can subscribe to. ` + `Event-capable connectors: ${[...EVENT_CAPABLE_CONNECTOR_IDS].join(", ")}.`;
|
|
3948
|
+
}
|
|
3949
|
+
if (!Array.isArray(events) || events.length === 0) {
|
|
3950
|
+
return 'events must be a non-empty array of { "entity": string, "operations": string[] }.';
|
|
3951
|
+
}
|
|
3952
|
+
const seen = new Set;
|
|
3953
|
+
for (const ev of events) {
|
|
3954
|
+
const entity = ev?.entity;
|
|
3955
|
+
const operations = ev?.operations;
|
|
3956
|
+
if (typeof entity !== "string" || !entity) {
|
|
3957
|
+
return 'Each event needs an "entity" (string).';
|
|
3958
|
+
}
|
|
3959
|
+
const known = entities.find((e) => e.entity === entity);
|
|
3960
|
+
if (!known) {
|
|
3961
|
+
const caseHit = entities.find((e) => e.entity.toLowerCase() === entity.toLowerCase());
|
|
3962
|
+
const hint = caseHit ? ` Did you mean "${caseHit.entity}"? Names are case-sensitive.` : "";
|
|
3963
|
+
return `Unknown entity "${entity}" for ${connectorId}.${hint} Entities: ${entities.map((e) => e.entity).join(", ")}.`;
|
|
3964
|
+
}
|
|
3965
|
+
if (seen.has(entity))
|
|
3966
|
+
return `Entity "${entity}" is listed twice — give it one entry with all its operations.`;
|
|
3967
|
+
seen.add(entity);
|
|
3968
|
+
if (!Array.isArray(operations) || operations.length === 0) {
|
|
3969
|
+
return `Event "${entity}" needs a non-empty "operations" array. Operations: ${known.supportedOperations.join(", ")}.`;
|
|
3970
|
+
}
|
|
3971
|
+
for (const op of operations) {
|
|
3972
|
+
if (typeof op !== "string" || !known.supportedOperations.includes(op)) {
|
|
3973
|
+
const caseHit = typeof op === "string" ? known.supportedOperations.find((o) => o.toLowerCase() === op.toLowerCase()) : undefined;
|
|
3974
|
+
const hint = caseHit ? ` Did you mean "${caseHit}"?` : "";
|
|
3975
|
+
return `Unknown operation "${String(op)}" for ${connectorId} ${entity}.${hint} Operations: ${known.supportedOperations.join(", ")}.`;
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
}
|
|
3979
|
+
return null;
|
|
3980
|
+
}
|
|
3863
3981
|
// src/catalog/workflow.ts
|
|
3864
3982
|
var NodeStatus;
|
|
3865
3983
|
((NodeStatus) => {
|
|
@@ -4932,7 +5050,6 @@ function validateStepConfig(step) {
|
|
|
4932
5050
|
if (!definition)
|
|
4933
5051
|
return issues;
|
|
4934
5052
|
const declared = new Map(contractFieldsFor(definition).map((f) => [f.key, f]));
|
|
4935
|
-
const derived = contractDerivedKeys(definition);
|
|
4936
5053
|
const entries = Object.entries(config);
|
|
4937
5054
|
for (const [key, value] of entries) {
|
|
4938
5055
|
const field = declared.get(key);
|
|
@@ -4948,7 +5065,7 @@ function validateStepConfig(step) {
|
|
|
4948
5065
|
}
|
|
4949
5066
|
if (!typeMatches(field, value)) {
|
|
4950
5067
|
issues.push({
|
|
4951
|
-
severity:
|
|
5068
|
+
severity: "error",
|
|
4952
5069
|
code: "FIELD_WRONG_TYPE",
|
|
4953
5070
|
stepId,
|
|
4954
5071
|
field: key,
|
|
@@ -5068,9 +5185,11 @@ export {
|
|
|
5068
5185
|
AggregateFunction,
|
|
5069
5186
|
CONDITION_OPERATORS,
|
|
5070
5187
|
CONFIG_CONTRACTS,
|
|
5188
|
+
CONNECTOR_EVENT_ENTITIES,
|
|
5071
5189
|
ConditionConfig,
|
|
5072
5190
|
DATABASE_CONNECTOR_IDS,
|
|
5073
5191
|
DebugConfig,
|
|
5192
|
+
EVENT_CAPABLE_CONNECTOR_IDS,
|
|
5074
5193
|
EdgeType,
|
|
5075
5194
|
EmailConfig,
|
|
5076
5195
|
FieldType,
|
|
@@ -5112,20 +5231,24 @@ export {
|
|
|
5112
5231
|
getActionsByCategory,
|
|
5113
5232
|
getAllActions,
|
|
5114
5233
|
getEdgeValidationStatus,
|
|
5234
|
+
getEntitiesForConnector,
|
|
5115
5235
|
getNodeConfigStatus,
|
|
5116
5236
|
getNodeValidationStatus,
|
|
5117
5237
|
getStorageProvider,
|
|
5118
5238
|
inferSchemaFromSample,
|
|
5119
5239
|
isEditorPrivateKey,
|
|
5240
|
+
isEventCapableConnector,
|
|
5120
5241
|
isMappingFunction,
|
|
5121
5242
|
isQueryableConnector,
|
|
5122
5243
|
mappingTransformFunctionName,
|
|
5123
5244
|
migrateDelayConfig,
|
|
5124
5245
|
queryDefinition,
|
|
5125
5246
|
storageConfigKeys,
|
|
5247
|
+
summarizeConnectorTriggerEvents,
|
|
5126
5248
|
transformConnectionToEdge,
|
|
5127
5249
|
transformFlowToWorkflow,
|
|
5128
5250
|
transformStepToNode,
|
|
5251
|
+
validateConnectorTriggerEvents,
|
|
5129
5252
|
validateEdge,
|
|
5130
5253
|
validateFlowData,
|
|
5131
5254
|
validateNode,
|
|
@@ -93,12 +93,13 @@ export declare function contractVocabularyDrift(actionId: string): string[];
|
|
|
93
93
|
/**
|
|
94
94
|
* Keys `contractFieldsFor` ADDED — declared by config.ts but not by inputSchema.
|
|
95
95
|
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
96
|
+
* In 0.8.1 the fence treated a type mismatch on one of these as a WARNING,
|
|
97
|
+
* pending a measurement: the moment http_request's contract declared
|
|
98
|
+
* `queryParams: array`, a fixture storing it as an object stopped saving, and
|
|
99
|
+
* with 71 of 131 real keys previously undeclared any might have been stored in
|
|
100
|
+
* an unexpected shape. Measured 2026-09-26 against every flow on beta — 79
|
|
101
|
+
* flows, 302 steps — zero mismatches. 0.9.0 promotes them to errors, the same
|
|
102
|
+
* as any other declared key. This helper remains for tooling that wants to
|
|
103
|
+
* know which keys the contract added.
|
|
103
104
|
*/
|
|
104
105
|
export declare function contractDerivedKeys(definition: ActionDefinition): Set<string>;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@octaviaflow/flow-rules",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "The flow action catalog and the rules engine behind Flow Doctor
|
|
3
|
+
"version": "0.10.0",
|
|
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",
|
|
7
7
|
"module": "./dist/index.js",
|