@synap-core/workspace-templates 0.9.1 → 0.9.3
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/index.d.ts +170 -8
- package/dist/index.js +643 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/business-developer.yaml +24 -6
- package/src/content-os.yaml +19 -0
- package/src/crm.yaml +28 -2
- package/src/define.primary-surface.test.ts +183 -0
- package/src/define.ts +142 -19
- package/src/index.ts +26 -3
- package/src/marketing.yaml +19 -0
- package/src/templates.ts +126 -8
- package/src/types.ts +74 -5
- package/src/validate-flow.drift.test.ts +83 -0
- package/src/validate-flow.ts +555 -0
- package/src/validate.test.ts +121 -0
- package/src/validate.ts +133 -10
package/dist/index.js
CHANGED
|
@@ -6841,14 +6841,13 @@ var DATA = {
|
|
|
6841
6841
|
"valueType": "string",
|
|
6842
6842
|
"inputType": "select",
|
|
6843
6843
|
"enumValues": [
|
|
6844
|
-
"
|
|
6845
|
-
"referral",
|
|
6844
|
+
"cold-outreach",
|
|
6846
6845
|
"linkedin",
|
|
6847
|
-
"
|
|
6846
|
+
"twitter",
|
|
6847
|
+
"landing-page",
|
|
6848
|
+
"inbound-call",
|
|
6849
|
+
"referral",
|
|
6848
6850
|
"event",
|
|
6849
|
-
"inbound",
|
|
6850
|
-
"partner",
|
|
6851
|
-
"import",
|
|
6852
6851
|
"other"
|
|
6853
6852
|
]
|
|
6854
6853
|
},
|
|
@@ -7218,6 +7217,37 @@ var DATA = {
|
|
|
7218
7217
|
"label": "Notes",
|
|
7219
7218
|
"valueType": "string",
|
|
7220
7219
|
"inputType": "textarea"
|
|
7220
|
+
},
|
|
7221
|
+
{
|
|
7222
|
+
"slug": "campaign-type",
|
|
7223
|
+
"label": "Type",
|
|
7224
|
+
"valueType": "string",
|
|
7225
|
+
"inputType": "select",
|
|
7226
|
+
"enumValues": [
|
|
7227
|
+
"scoping",
|
|
7228
|
+
"outreach",
|
|
7229
|
+
"live",
|
|
7230
|
+
"partnership",
|
|
7231
|
+
"event",
|
|
7232
|
+
"other"
|
|
7233
|
+
],
|
|
7234
|
+
"constraints": {
|
|
7235
|
+
"defaultValue": "outreach"
|
|
7236
|
+
}
|
|
7237
|
+
},
|
|
7238
|
+
{
|
|
7239
|
+
"slug": "campaign-color",
|
|
7240
|
+
"label": "Color",
|
|
7241
|
+
"valueType": "string",
|
|
7242
|
+
"inputType": "select",
|
|
7243
|
+
"enumValues": [
|
|
7244
|
+
"indigo",
|
|
7245
|
+
"violet",
|
|
7246
|
+
"amber",
|
|
7247
|
+
"cyan",
|
|
7248
|
+
"pink",
|
|
7249
|
+
"teal"
|
|
7250
|
+
]
|
|
7221
7251
|
}
|
|
7222
7252
|
]
|
|
7223
7253
|
}
|
|
@@ -8922,6 +8952,37 @@ var DATA = {
|
|
|
8922
8952
|
"label": "Notes",
|
|
8923
8953
|
"valueType": "string",
|
|
8924
8954
|
"inputType": "textarea"
|
|
8955
|
+
},
|
|
8956
|
+
{
|
|
8957
|
+
"slug": "campaign-type",
|
|
8958
|
+
"label": "Type",
|
|
8959
|
+
"valueType": "string",
|
|
8960
|
+
"inputType": "select",
|
|
8961
|
+
"enumValues": [
|
|
8962
|
+
"scoping",
|
|
8963
|
+
"outreach",
|
|
8964
|
+
"live",
|
|
8965
|
+
"partnership",
|
|
8966
|
+
"event",
|
|
8967
|
+
"other"
|
|
8968
|
+
],
|
|
8969
|
+
"constraints": {
|
|
8970
|
+
"defaultValue": "outreach"
|
|
8971
|
+
}
|
|
8972
|
+
},
|
|
8973
|
+
{
|
|
8974
|
+
"slug": "campaign-color",
|
|
8975
|
+
"label": "Color",
|
|
8976
|
+
"valueType": "string",
|
|
8977
|
+
"inputType": "select",
|
|
8978
|
+
"enumValues": [
|
|
8979
|
+
"indigo",
|
|
8980
|
+
"violet",
|
|
8981
|
+
"amber",
|
|
8982
|
+
"cyan",
|
|
8983
|
+
"pink",
|
|
8984
|
+
"teal"
|
|
8985
|
+
]
|
|
8925
8986
|
}
|
|
8926
8987
|
]
|
|
8927
8988
|
},
|
|
@@ -10024,6 +10085,12 @@ var DATA = {
|
|
|
10024
10085
|
],
|
|
10025
10086
|
"isPublic": true
|
|
10026
10087
|
},
|
|
10088
|
+
"app": {
|
|
10089
|
+
"kind": "url",
|
|
10090
|
+
"appId": "crm",
|
|
10091
|
+
"url": "https://crm.synap.live",
|
|
10092
|
+
"title": "CRM"
|
|
10093
|
+
},
|
|
10027
10094
|
"dependencies": [
|
|
10028
10095
|
{
|
|
10029
10096
|
"slug": "foundation",
|
|
@@ -10094,7 +10161,17 @@ var DATA = {
|
|
|
10094
10161
|
"slug": "source",
|
|
10095
10162
|
"label": "Source",
|
|
10096
10163
|
"valueType": "string",
|
|
10097
|
-
"inputType": "
|
|
10164
|
+
"inputType": "select",
|
|
10165
|
+
"enumValues": [
|
|
10166
|
+
"cold-outreach",
|
|
10167
|
+
"linkedin",
|
|
10168
|
+
"twitter",
|
|
10169
|
+
"landing-page",
|
|
10170
|
+
"inbound-call",
|
|
10171
|
+
"referral",
|
|
10172
|
+
"event",
|
|
10173
|
+
"other"
|
|
10174
|
+
]
|
|
10098
10175
|
},
|
|
10099
10176
|
{
|
|
10100
10177
|
"slug": "icpScore",
|
|
@@ -10183,7 +10260,17 @@ var DATA = {
|
|
|
10183
10260
|
"slug": "source",
|
|
10184
10261
|
"label": "Source",
|
|
10185
10262
|
"valueType": "string",
|
|
10186
|
-
"inputType": "
|
|
10263
|
+
"inputType": "select",
|
|
10264
|
+
"enumValues": [
|
|
10265
|
+
"cold-outreach",
|
|
10266
|
+
"linkedin",
|
|
10267
|
+
"twitter",
|
|
10268
|
+
"landing-page",
|
|
10269
|
+
"inbound-call",
|
|
10270
|
+
"referral",
|
|
10271
|
+
"event",
|
|
10272
|
+
"other"
|
|
10273
|
+
]
|
|
10187
10274
|
},
|
|
10188
10275
|
{
|
|
10189
10276
|
"slug": "icpScore",
|
|
@@ -22039,6 +22126,37 @@ var DATA = {
|
|
|
22039
22126
|
"label": "Notes",
|
|
22040
22127
|
"valueType": "string",
|
|
22041
22128
|
"inputType": "textarea"
|
|
22129
|
+
},
|
|
22130
|
+
{
|
|
22131
|
+
"slug": "campaign-type",
|
|
22132
|
+
"label": "Type",
|
|
22133
|
+
"valueType": "string",
|
|
22134
|
+
"inputType": "select",
|
|
22135
|
+
"enumValues": [
|
|
22136
|
+
"scoping",
|
|
22137
|
+
"outreach",
|
|
22138
|
+
"live",
|
|
22139
|
+
"partnership",
|
|
22140
|
+
"event",
|
|
22141
|
+
"other"
|
|
22142
|
+
],
|
|
22143
|
+
"constraints": {
|
|
22144
|
+
"defaultValue": "outreach"
|
|
22145
|
+
}
|
|
22146
|
+
},
|
|
22147
|
+
{
|
|
22148
|
+
"slug": "campaign-color",
|
|
22149
|
+
"label": "Color",
|
|
22150
|
+
"valueType": "string",
|
|
22151
|
+
"inputType": "select",
|
|
22152
|
+
"enumValues": [
|
|
22153
|
+
"indigo",
|
|
22154
|
+
"violet",
|
|
22155
|
+
"amber",
|
|
22156
|
+
"cyan",
|
|
22157
|
+
"pink",
|
|
22158
|
+
"teal"
|
|
22159
|
+
]
|
|
22042
22160
|
}
|
|
22043
22161
|
]
|
|
22044
22162
|
},
|
|
@@ -28861,6 +28979,339 @@ function groupByDomain(items, domainOf) {
|
|
|
28861
28979
|
}));
|
|
28862
28980
|
}
|
|
28863
28981
|
|
|
28982
|
+
// src/validate-flow.ts
|
|
28983
|
+
var FLOW_NODE_TYPES = [
|
|
28984
|
+
"trigger",
|
|
28985
|
+
"command",
|
|
28986
|
+
"condition",
|
|
28987
|
+
"delay",
|
|
28988
|
+
"output",
|
|
28989
|
+
"loop",
|
|
28990
|
+
"transform",
|
|
28991
|
+
"fetch",
|
|
28992
|
+
"query",
|
|
28993
|
+
"entity_read",
|
|
28994
|
+
"related_entities",
|
|
28995
|
+
"guard",
|
|
28996
|
+
"compute",
|
|
28997
|
+
"select",
|
|
28998
|
+
"claim",
|
|
28999
|
+
"messages_query",
|
|
29000
|
+
"switch",
|
|
29001
|
+
"skill",
|
|
29002
|
+
"capability",
|
|
29003
|
+
"sub_automation",
|
|
29004
|
+
"playbook_run"
|
|
29005
|
+
];
|
|
29006
|
+
var FLOW_OUTPUT_TYPES = [
|
|
29007
|
+
"notification",
|
|
29008
|
+
"entity_create",
|
|
29009
|
+
"entity_update",
|
|
29010
|
+
"facet_attach",
|
|
29011
|
+
"facet_update",
|
|
29012
|
+
"facet_detach",
|
|
29013
|
+
"relation_create",
|
|
29014
|
+
"webhook",
|
|
29015
|
+
"channel_message",
|
|
29016
|
+
"session_update",
|
|
29017
|
+
"set_state"
|
|
29018
|
+
];
|
|
29019
|
+
var CHANNEL_MESSAGE_CHANNEL_TYPES = [
|
|
29020
|
+
"personal_thread",
|
|
29021
|
+
"proactive",
|
|
29022
|
+
"subjectEntity"
|
|
29023
|
+
];
|
|
29024
|
+
var NODE_TYPE_SET = new Set(FLOW_NODE_TYPES);
|
|
29025
|
+
var OUTPUT_TYPE_SET = new Set(FLOW_OUTPUT_TYPES);
|
|
29026
|
+
var CHANNEL_TYPE_SET = new Set(CHANNEL_MESSAGE_CHANNEL_TYPES);
|
|
29027
|
+
function isRecord(v) {
|
|
29028
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
29029
|
+
}
|
|
29030
|
+
function nonEmptyString(v) {
|
|
29031
|
+
return typeof v === "string" && v.trim().length > 0;
|
|
29032
|
+
}
|
|
29033
|
+
function validateFlowDefinition(flow, resolvers) {
|
|
29034
|
+
const errors = [];
|
|
29035
|
+
if (!isRecord(flow)) {
|
|
29036
|
+
return {
|
|
29037
|
+
valid: false,
|
|
29038
|
+
errors: [
|
|
29039
|
+
{
|
|
29040
|
+
code: "flow_not_object",
|
|
29041
|
+
message: "flowDefinition must be an object with `nodes` and `edges` arrays."
|
|
29042
|
+
}
|
|
29043
|
+
]
|
|
29044
|
+
};
|
|
29045
|
+
}
|
|
29046
|
+
const f = flow;
|
|
29047
|
+
if (!Array.isArray(f.nodes)) {
|
|
29048
|
+
errors.push({
|
|
29049
|
+
code: "nodes_not_array",
|
|
29050
|
+
message: "flowDefinition.nodes must be an array."
|
|
29051
|
+
});
|
|
29052
|
+
}
|
|
29053
|
+
if (!Array.isArray(f.edges)) {
|
|
29054
|
+
errors.push({
|
|
29055
|
+
code: "edges_not_array",
|
|
29056
|
+
message: "flowDefinition.edges must be an array."
|
|
29057
|
+
});
|
|
29058
|
+
}
|
|
29059
|
+
if (errors.length > 0) return { valid: false, errors };
|
|
29060
|
+
const nodes = f.nodes.map(
|
|
29061
|
+
(n) => isRecord(n) ? n : {}
|
|
29062
|
+
);
|
|
29063
|
+
const edges = f.edges.map(
|
|
29064
|
+
(e) => isRecord(e) ? e : {}
|
|
29065
|
+
);
|
|
29066
|
+
const nodeIds = /* @__PURE__ */ new Set();
|
|
29067
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
29068
|
+
nodes.forEach((node, idx) => {
|
|
29069
|
+
if (!nonEmptyString(node.id)) {
|
|
29070
|
+
errors.push({
|
|
29071
|
+
code: "node_missing_id",
|
|
29072
|
+
message: `Node at index ${idx} is missing a non-empty string \`id\`.`
|
|
29073
|
+
});
|
|
29074
|
+
return;
|
|
29075
|
+
}
|
|
29076
|
+
if (seenIds.has(node.id)) {
|
|
29077
|
+
errors.push({
|
|
29078
|
+
nodeId: node.id,
|
|
29079
|
+
code: "duplicate_node_id",
|
|
29080
|
+
message: `Duplicate node id "${node.id}" \u2014 every node id must be unique.`
|
|
29081
|
+
});
|
|
29082
|
+
return;
|
|
29083
|
+
}
|
|
29084
|
+
seenIds.add(node.id);
|
|
29085
|
+
nodeIds.add(node.id);
|
|
29086
|
+
});
|
|
29087
|
+
const nodesById = /* @__PURE__ */ new Map();
|
|
29088
|
+
for (const node of nodes) {
|
|
29089
|
+
if (nonEmptyString(node.id)) nodesById.set(node.id, node);
|
|
29090
|
+
const nodeId = nonEmptyString(node.id) ? node.id : void 0;
|
|
29091
|
+
if (!nonEmptyString(node.type) || !NODE_TYPE_SET.has(node.type)) {
|
|
29092
|
+
errors.push({
|
|
29093
|
+
nodeId,
|
|
29094
|
+
code: "unknown_node_type",
|
|
29095
|
+
message: `Node ${nodeId ? `"${nodeId}"` : `at index ?`} has ${nonEmptyString(node.type) ? `unknown type "${node.type}"` : "no type"}. Must be one of: ${FLOW_NODE_TYPES.join(", ")}.`
|
|
29096
|
+
});
|
|
29097
|
+
continue;
|
|
29098
|
+
}
|
|
29099
|
+
const type = node.type;
|
|
29100
|
+
const data = isRecord(node.data) ? node.data : {};
|
|
29101
|
+
switch (type) {
|
|
29102
|
+
case "capability": {
|
|
29103
|
+
if (!nonEmptyString(data.verbId)) {
|
|
29104
|
+
errors.push({
|
|
29105
|
+
nodeId,
|
|
29106
|
+
code: "capability_missing_verbId",
|
|
29107
|
+
message: `Capability node "${nodeId}" has no \`verbId\`. Pick a verb on the selected tool.`
|
|
29108
|
+
});
|
|
29109
|
+
} else if (resolvers?.verbExists && !resolvers.verbExists(data.verbId)) {
|
|
29110
|
+
errors.push({
|
|
29111
|
+
nodeId,
|
|
29112
|
+
code: "capability_unknown_verbId",
|
|
29113
|
+
message: `Capability node "${nodeId}" references verb "${data.verbId}", which is not in the capability catalog.`
|
|
29114
|
+
});
|
|
29115
|
+
}
|
|
29116
|
+
break;
|
|
29117
|
+
}
|
|
29118
|
+
case "skill": {
|
|
29119
|
+
const skillId = nonEmptyString(data.skillId) ? data.skillId : void 0;
|
|
29120
|
+
const skillName = nonEmptyString(data.skillName) ? data.skillName : void 0;
|
|
29121
|
+
if (!skillId && !skillName) {
|
|
29122
|
+
errors.push({
|
|
29123
|
+
nodeId,
|
|
29124
|
+
code: "skill_missing_ref",
|
|
29125
|
+
message: `Skill node "${nodeId}" has neither \`skillId\` nor \`skillName\`. One is required.`
|
|
29126
|
+
});
|
|
29127
|
+
} else if (resolvers?.skillExists && !resolvers.skillExists({ skillId, skillName })) {
|
|
29128
|
+
errors.push({
|
|
29129
|
+
nodeId,
|
|
29130
|
+
code: "skill_unknown_ref",
|
|
29131
|
+
message: `Skill node "${nodeId}" references a skill (${skillId ? `id "${skillId}"` : `name "${skillName}"`}) that does not exist.`
|
|
29132
|
+
});
|
|
29133
|
+
}
|
|
29134
|
+
break;
|
|
29135
|
+
}
|
|
29136
|
+
case "playbook_run": {
|
|
29137
|
+
const playbookId = nonEmptyString(data.playbookId) ? data.playbookId : void 0;
|
|
29138
|
+
const playbookName = nonEmptyString(data.playbookName) ? data.playbookName : void 0;
|
|
29139
|
+
if (!playbookId && !playbookName) {
|
|
29140
|
+
errors.push({
|
|
29141
|
+
nodeId,
|
|
29142
|
+
code: "playbook_run_missing_ref",
|
|
29143
|
+
message: `Playbook-run node "${nodeId}" has neither \`playbookId\` nor \`playbookName\`. One is required.`
|
|
29144
|
+
});
|
|
29145
|
+
} else if (resolvers?.playbookExists && !resolvers.playbookExists({ playbookId, playbookName })) {
|
|
29146
|
+
errors.push({
|
|
29147
|
+
nodeId,
|
|
29148
|
+
code: "playbook_run_unknown_ref",
|
|
29149
|
+
message: `Playbook-run node "${nodeId}" references a playbook (${playbookId ? `id "${playbookId}"` : `name "${playbookName}"`}) that does not exist.`
|
|
29150
|
+
});
|
|
29151
|
+
}
|
|
29152
|
+
break;
|
|
29153
|
+
}
|
|
29154
|
+
case "output": {
|
|
29155
|
+
const outputType = data.outputType;
|
|
29156
|
+
if (!nonEmptyString(outputType) || !OUTPUT_TYPE_SET.has(outputType)) {
|
|
29157
|
+
errors.push({
|
|
29158
|
+
nodeId,
|
|
29159
|
+
code: "output_bad_outputType",
|
|
29160
|
+
message: `Output node "${nodeId}" has ${nonEmptyString(outputType) ? `unknown outputType "${outputType}"` : "no outputType"}. Must be one of: ${FLOW_OUTPUT_TYPES.join(", ")}.`
|
|
29161
|
+
});
|
|
29162
|
+
break;
|
|
29163
|
+
}
|
|
29164
|
+
if (outputType === "channel_message") {
|
|
29165
|
+
const config = isRecord(data.config) ? data.config : {};
|
|
29166
|
+
const channelType = config.channelType;
|
|
29167
|
+
if (channelType !== void 0 && channelType !== null) {
|
|
29168
|
+
if (!nonEmptyString(channelType) || !CHANNEL_TYPE_SET.has(channelType)) {
|
|
29169
|
+
errors.push({
|
|
29170
|
+
nodeId,
|
|
29171
|
+
code: "channel_message_unknown_channelType",
|
|
29172
|
+
message: `Channel-message output "${nodeId}" has unknown channelType ${nonEmptyString(channelType) ? `"${channelType}"` : JSON.stringify(channelType)}. Use one of: ${CHANNEL_MESSAGE_CHANNEL_TYPES.join(
|
|
29173
|
+
", "
|
|
29174
|
+
)}, or set \`config.channelEntityRef\`/\`config.channelId\`, or omit the target to post to the automation's own run channel.`
|
|
29175
|
+
});
|
|
29176
|
+
}
|
|
29177
|
+
}
|
|
29178
|
+
}
|
|
29179
|
+
break;
|
|
29180
|
+
}
|
|
29181
|
+
case "loop": {
|
|
29182
|
+
if (!nonEmptyString(data.iteratorExpression)) {
|
|
29183
|
+
errors.push({
|
|
29184
|
+
nodeId,
|
|
29185
|
+
code: "loop_missing_iterator",
|
|
29186
|
+
message: `Loop node "${nodeId}" has no \`iteratorExpression\` (the items to iterate, e.g. "steps.query.output.entities").`
|
|
29187
|
+
});
|
|
29188
|
+
}
|
|
29189
|
+
break;
|
|
29190
|
+
}
|
|
29191
|
+
case "condition": {
|
|
29192
|
+
if (!nonEmptyString(data.expression)) {
|
|
29193
|
+
errors.push({
|
|
29194
|
+
nodeId,
|
|
29195
|
+
code: "condition_missing_expression",
|
|
29196
|
+
message: `Condition node "${nodeId}" has no \`expression\` (the predicate to evaluate).`
|
|
29197
|
+
});
|
|
29198
|
+
}
|
|
29199
|
+
break;
|
|
29200
|
+
}
|
|
29201
|
+
case "switch": {
|
|
29202
|
+
if (!nonEmptyString(data.expression)) {
|
|
29203
|
+
errors.push({
|
|
29204
|
+
nodeId,
|
|
29205
|
+
code: "switch_missing_expression",
|
|
29206
|
+
message: `Switch node "${nodeId}" has no \`expression\` (the value to match against cases).`
|
|
29207
|
+
});
|
|
29208
|
+
}
|
|
29209
|
+
if (!Array.isArray(data.cases) || data.cases.length === 0) {
|
|
29210
|
+
errors.push({
|
|
29211
|
+
nodeId,
|
|
29212
|
+
code: "switch_missing_cases",
|
|
29213
|
+
message: `Switch node "${nodeId}" has no \`cases\`. Add at least one case with a \`value\`.`
|
|
29214
|
+
});
|
|
29215
|
+
}
|
|
29216
|
+
break;
|
|
29217
|
+
}
|
|
29218
|
+
}
|
|
29219
|
+
}
|
|
29220
|
+
edges.forEach((edge, idx) => {
|
|
29221
|
+
const edgeId = nonEmptyString(edge.id) ? edge.id : void 0;
|
|
29222
|
+
const label = edgeId ? `"${edgeId}"` : `at index ${idx}`;
|
|
29223
|
+
if (!nonEmptyString(edge.source) || !nodeIds.has(edge.source)) {
|
|
29224
|
+
errors.push({
|
|
29225
|
+
edgeId,
|
|
29226
|
+
code: "edge_bad_source",
|
|
29227
|
+
message: `Edge ${label} has ${nonEmptyString(edge.source) ? `source "${edge.source}" which is not a node in this flow` : "no source node"}.`
|
|
29228
|
+
});
|
|
29229
|
+
}
|
|
29230
|
+
if (!nonEmptyString(edge.target) || !nodeIds.has(edge.target)) {
|
|
29231
|
+
errors.push({
|
|
29232
|
+
edgeId,
|
|
29233
|
+
code: "edge_bad_target",
|
|
29234
|
+
message: `Edge ${label} has ${nonEmptyString(edge.target) ? `target "${edge.target}" which is not a node in this flow` : "no target node"}.`
|
|
29235
|
+
});
|
|
29236
|
+
}
|
|
29237
|
+
});
|
|
29238
|
+
for (const edge of edges) {
|
|
29239
|
+
if (!nonEmptyString(edge.source) || !nodesById.has(edge.source)) continue;
|
|
29240
|
+
if (edge.sourceHandle === void 0 || edge.sourceHandle === null) continue;
|
|
29241
|
+
if (!nonEmptyString(edge.sourceHandle)) continue;
|
|
29242
|
+
const src = nodesById.get(edge.source);
|
|
29243
|
+
const edgeId = nonEmptyString(edge.id) ? edge.id : void 0;
|
|
29244
|
+
if (src.type === "condition") {
|
|
29245
|
+
if (edge.sourceHandle !== "yes" && edge.sourceHandle !== "no") {
|
|
29246
|
+
errors.push({
|
|
29247
|
+
edgeId,
|
|
29248
|
+
nodeId: edge.source,
|
|
29249
|
+
code: "condition_bad_handle",
|
|
29250
|
+
message: `Edge from condition "${edge.source}" has sourceHandle "${edge.sourceHandle}" \u2014 must be "yes" or "no".`
|
|
29251
|
+
});
|
|
29252
|
+
}
|
|
29253
|
+
} else if (src.type === "switch") {
|
|
29254
|
+
const data = isRecord(src.data) ? src.data : {};
|
|
29255
|
+
const caseValues = Array.isArray(data.cases) ? data.cases.map((c) => isRecord(c) ? c.value : void 0).filter((v) => typeof v === "string") : [];
|
|
29256
|
+
if (!caseValues.includes(edge.sourceHandle)) {
|
|
29257
|
+
errors.push({
|
|
29258
|
+
edgeId,
|
|
29259
|
+
nodeId: edge.source,
|
|
29260
|
+
code: "switch_bad_handle",
|
|
29261
|
+
message: `Edge from switch "${edge.source}" has sourceHandle "${edge.sourceHandle}", which matches no declared case value (${caseValues.length ? caseValues.join(", ") : "none declared"}).`
|
|
29262
|
+
});
|
|
29263
|
+
}
|
|
29264
|
+
}
|
|
29265
|
+
}
|
|
29266
|
+
const cyclic = detectCycle(nodeIds, edges);
|
|
29267
|
+
if (cyclic.length > 0) {
|
|
29268
|
+
errors.push({
|
|
29269
|
+
code: "flow_has_cycle",
|
|
29270
|
+
message: `Flow has a cycle \u2014 these nodes cannot be ordered and would never run: ${cyclic.join(
|
|
29271
|
+
", "
|
|
29272
|
+
)}. Remove the back-edge(s) so the graph is acyclic.`
|
|
29273
|
+
});
|
|
29274
|
+
}
|
|
29275
|
+
return { valid: errors.length === 0, errors };
|
|
29276
|
+
}
|
|
29277
|
+
function detectCycle(nodeIds, edges) {
|
|
29278
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
29279
|
+
const adjacency = /* @__PURE__ */ new Map();
|
|
29280
|
+
for (const id of nodeIds) {
|
|
29281
|
+
inDegree.set(id, 0);
|
|
29282
|
+
adjacency.set(id, []);
|
|
29283
|
+
}
|
|
29284
|
+
for (const edge of edges) {
|
|
29285
|
+
const source = nonEmptyString(edge.source) ? edge.source : void 0;
|
|
29286
|
+
const target = nonEmptyString(edge.target) ? edge.target : void 0;
|
|
29287
|
+
if (!source || !target) continue;
|
|
29288
|
+
if (!nodeIds.has(source) || !nodeIds.has(target)) continue;
|
|
29289
|
+
adjacency.get(source).push(target);
|
|
29290
|
+
inDegree.set(target, (inDegree.get(target) ?? 0) + 1);
|
|
29291
|
+
}
|
|
29292
|
+
const queue = [];
|
|
29293
|
+
for (const [id, degree] of inDegree) if (degree === 0) queue.push(id);
|
|
29294
|
+
let visited = 0;
|
|
29295
|
+
while (queue.length > 0) {
|
|
29296
|
+
const id = queue.shift();
|
|
29297
|
+
visited++;
|
|
29298
|
+
for (const target of adjacency.get(id) ?? []) {
|
|
29299
|
+
const next = (inDegree.get(target) ?? 1) - 1;
|
|
29300
|
+
inDegree.set(target, next);
|
|
29301
|
+
if (next === 0) queue.push(target);
|
|
29302
|
+
}
|
|
29303
|
+
}
|
|
29304
|
+
if (visited === nodeIds.size) return [];
|
|
29305
|
+
const cyclic = [];
|
|
29306
|
+
for (const [id, degree] of inDegree) if (degree > 0) cyclic.push(id);
|
|
29307
|
+
return cyclic;
|
|
29308
|
+
}
|
|
29309
|
+
function flowValidationErrorMessage(flow, resolvers) {
|
|
29310
|
+
const result = validateFlowDefinition(flow, resolvers);
|
|
29311
|
+
if (result.valid) return null;
|
|
29312
|
+
return "Invalid automation flow: " + result.errors.map((e) => e.nodeId ? `[${e.nodeId}] ${e.message}` : e.message).join(" ");
|
|
29313
|
+
}
|
|
29314
|
+
|
|
28864
29315
|
// src/validate.ts
|
|
28865
29316
|
var REQUIRED_META_FIELDS = [
|
|
28866
29317
|
"slug",
|
|
@@ -28955,12 +29406,80 @@ function validateTemplate(tpl, opts = {}) {
|
|
|
28955
29406
|
else {
|
|
28956
29407
|
for (const f of REQUIRED_WORKSPACE_FIELDS) {
|
|
28957
29408
|
if (!tpl.workspace[f])
|
|
28958
|
-
add(
|
|
29409
|
+
add(
|
|
29410
|
+
"workspace-field-required",
|
|
29411
|
+
`workspace.${f}`,
|
|
29412
|
+
`workspace.${f} is required`
|
|
29413
|
+
);
|
|
28959
29414
|
}
|
|
28960
29415
|
}
|
|
28961
29416
|
if (!tpl.profiles || tpl.profiles.length === 0) {
|
|
28962
29417
|
add("profiles-required", "profiles", "At least one profile is required");
|
|
28963
29418
|
}
|
|
29419
|
+
if (tpl.app && tpl.layout && Object.prototype.hasOwnProperty.call(tpl.layout, "primarySurface")) {
|
|
29420
|
+
add(
|
|
29421
|
+
"primary-surface-authoring-conflict",
|
|
29422
|
+
"layout.primarySurface",
|
|
29423
|
+
"Declare either top-level app or layout.primarySurface, not both"
|
|
29424
|
+
);
|
|
29425
|
+
}
|
|
29426
|
+
if (tpl.app?.kind === "native" && !tpl.app.appId) {
|
|
29427
|
+
add(
|
|
29428
|
+
"app-native-id-required",
|
|
29429
|
+
"app.appId",
|
|
29430
|
+
"A native workspace app requires appId"
|
|
29431
|
+
);
|
|
29432
|
+
}
|
|
29433
|
+
if (tpl.app?.kind === "url" && (!tpl.app.appId || !tpl.app.url)) {
|
|
29434
|
+
add(
|
|
29435
|
+
"app-url-identity-required",
|
|
29436
|
+
"app",
|
|
29437
|
+
"A hosted workspace app requires both appId and url"
|
|
29438
|
+
);
|
|
29439
|
+
}
|
|
29440
|
+
if (tpl.app?.kind === "stored" && !tpl.app.cellKey) {
|
|
29441
|
+
add(
|
|
29442
|
+
"app-cell-key-required",
|
|
29443
|
+
"app.cellKey",
|
|
29444
|
+
"A stored workspace app requires cellKey"
|
|
29445
|
+
);
|
|
29446
|
+
}
|
|
29447
|
+
const authoredPrimary = tpl.layout?.primarySurface;
|
|
29448
|
+
if (authoredPrimary?.kind === "view" && !("viewId" in authoredPrimary)) {
|
|
29449
|
+
const viewName = authoredPrimary.viewName;
|
|
29450
|
+
const viewSlug = authoredPrimary.viewSlug;
|
|
29451
|
+
if (!viewName && !viewSlug) {
|
|
29452
|
+
add(
|
|
29453
|
+
"primary-view-reference-required",
|
|
29454
|
+
"layout.primarySurface",
|
|
29455
|
+
"A view primary surface requires viewName or viewSlug"
|
|
29456
|
+
);
|
|
29457
|
+
} else {
|
|
29458
|
+
const declaredViews = [
|
|
29459
|
+
...(tpl.views ?? []).map((view) => ({
|
|
29460
|
+
name: view.name,
|
|
29461
|
+
slug: view.slug
|
|
29462
|
+
})),
|
|
29463
|
+
...tpl.bento?.viewName ? [{ name: tpl.bento.viewName, slug: void 0 }] : []
|
|
29464
|
+
];
|
|
29465
|
+
const matches = declaredViews.filter(
|
|
29466
|
+
(view) => (!viewName || view.name === viewName) && (!viewSlug || view.slug === viewSlug)
|
|
29467
|
+
);
|
|
29468
|
+
if (matches.length === 0) {
|
|
29469
|
+
add(
|
|
29470
|
+
"primary-view-reference-unknown",
|
|
29471
|
+
"layout.primarySurface",
|
|
29472
|
+
`Primary surface references an unknown view (${viewName ?? viewSlug})`
|
|
29473
|
+
);
|
|
29474
|
+
} else if (matches.length > 1) {
|
|
29475
|
+
add(
|
|
29476
|
+
"primary-view-reference-ambiguous",
|
|
29477
|
+
"layout.primarySurface",
|
|
29478
|
+
`Primary surface view reference matches ${matches.length} authored views; add a unique viewSlug`
|
|
29479
|
+
);
|
|
29480
|
+
}
|
|
29481
|
+
}
|
|
29482
|
+
}
|
|
28964
29483
|
const baseProfileSlugs = collectBaseProfileSlugs(tpl, resolveDepFrom(opts));
|
|
28965
29484
|
const slugs = new Set((tpl.profiles || []).map((p) => p.slug));
|
|
28966
29485
|
const known = (s) => slugs.has(s) || SYSTEM_PROFILES.has(s) || baseProfileSlugs.has(s);
|
|
@@ -29038,11 +29557,29 @@ function validateTemplate(tpl, opts = {}) {
|
|
|
29038
29557
|
}
|
|
29039
29558
|
for (const a of tpl.automations || []) {
|
|
29040
29559
|
const refs = [];
|
|
29041
|
-
collectProfileSlugRefs(
|
|
29560
|
+
collectProfileSlugRefs(
|
|
29561
|
+
a.triggerConfig,
|
|
29562
|
+
`automation "${a.name}".triggerConfig`,
|
|
29563
|
+
refs
|
|
29564
|
+
);
|
|
29042
29565
|
collectProfileSlugRefs(a.flow, `automation "${a.name}".flow`, refs);
|
|
29043
29566
|
for (const r of refs)
|
|
29044
29567
|
if (!known(r.slug))
|
|
29045
|
-
add(
|
|
29568
|
+
add(
|
|
29569
|
+
"automation-ref-unknown",
|
|
29570
|
+
r.where,
|
|
29571
|
+
`${r.where} references unknown profile "${r.slug}"`
|
|
29572
|
+
);
|
|
29573
|
+
const flow = a.flow ?? a.flowDefinition;
|
|
29574
|
+
if (flow != null) {
|
|
29575
|
+
const {
|
|
29576
|
+
errors: flowErrors
|
|
29577
|
+
} = validateFlowDefinition(flow);
|
|
29578
|
+
for (const fe of flowErrors) {
|
|
29579
|
+
const loc = fe.nodeId ? `automation "${a.name}".flow.node "${fe.nodeId}"` : fe.edgeId ? `automation "${a.name}".flow.edge "${fe.edgeId}"` : `automation "${a.name}".flow`;
|
|
29580
|
+
add(`automation-flow-${fe.code}`, loc, `${loc}: ${fe.message}`);
|
|
29581
|
+
}
|
|
29582
|
+
}
|
|
29046
29583
|
}
|
|
29047
29584
|
for (const pb of tpl.playbooks || []) {
|
|
29048
29585
|
const ps = pb.subjectProfile?.profileSlug;
|
|
@@ -29088,7 +29625,9 @@ function validateTemplate(tpl, opts = {}) {
|
|
|
29088
29625
|
`profileEntityBentoTemplates has an entry for unknown profile "${key}"`
|
|
29089
29626
|
);
|
|
29090
29627
|
}
|
|
29091
|
-
const composeDeps = (tpl.dependencies || []).filter(
|
|
29628
|
+
const composeDeps = (tpl.dependencies || []).filter(
|
|
29629
|
+
(d) => d.relation === "compose"
|
|
29630
|
+
);
|
|
29092
29631
|
for (const dep of composeDeps) {
|
|
29093
29632
|
if (tpl.workspace?.visibility !== void 0) {
|
|
29094
29633
|
add(
|
|
@@ -29108,7 +29647,9 @@ function validateTemplate(tpl, opts = {}) {
|
|
|
29108
29647
|
const blocks = tpl.bento?.blocks ?? [];
|
|
29109
29648
|
const authoredWidgets = blocks.filter((b) => b.kind === "widget");
|
|
29110
29649
|
const authoredViews = blocks.filter((b) => b.kind === "view");
|
|
29111
|
-
const unknownKind = blocks.filter(
|
|
29650
|
+
const unknownKind = blocks.filter(
|
|
29651
|
+
(b) => b.kind !== "widget" && b.kind !== "view"
|
|
29652
|
+
);
|
|
29112
29653
|
if (unknownKind.length) {
|
|
29113
29654
|
add(
|
|
29114
29655
|
"bento-unknown-kind",
|
|
@@ -29140,7 +29681,11 @@ function validateTemplate(tpl, opts = {}) {
|
|
|
29140
29681
|
const expectW = authoredWidgets.length - badWidgets.length;
|
|
29141
29682
|
const expectV = authoredViews.length - badViews.length;
|
|
29142
29683
|
const doors = [
|
|
29143
|
-
[
|
|
29684
|
+
[
|
|
29685
|
+
"tRPC createFromDefinition",
|
|
29686
|
+
definition.bentoLayout ?? [],
|
|
29687
|
+
definition.bentoViewBlocks ?? []
|
|
29688
|
+
],
|
|
29144
29689
|
["CP packages/apply", pkg.bentoLayout ?? [], pkg.bentoViewBlocks ?? []]
|
|
29145
29690
|
];
|
|
29146
29691
|
for (const [door, layout, viewBlocks] of doors) {
|
|
@@ -29151,7 +29696,9 @@ function validateTemplate(tpl, opts = {}) {
|
|
|
29151
29696
|
`[${door}]: authored ${expectW} widget/${expectV} view \u2192 got ${layout.length} bentoLayout/${viewBlocks.length} bentoViewBlocks`
|
|
29152
29697
|
);
|
|
29153
29698
|
}
|
|
29154
|
-
const layoutBad = layout.filter(
|
|
29699
|
+
const layoutBad = layout.filter(
|
|
29700
|
+
(b) => typeof b.widgetType !== "string"
|
|
29701
|
+
);
|
|
29155
29702
|
const viewsBad = viewBlocks.filter(
|
|
29156
29703
|
(b) => typeof b.viewName !== "string" || b.kind !== "view"
|
|
29157
29704
|
);
|
|
@@ -29221,6 +29768,79 @@ function toAutomationWire(automations) {
|
|
|
29221
29768
|
status: a.status
|
|
29222
29769
|
}));
|
|
29223
29770
|
}
|
|
29771
|
+
var hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
|
|
29772
|
+
function appRefToPrimarySurface(app) {
|
|
29773
|
+
if (!app) return void 0;
|
|
29774
|
+
if (app.kind === "native") {
|
|
29775
|
+
if (!app.appId) {
|
|
29776
|
+
throw new Error("Workspace template native app is missing appId");
|
|
29777
|
+
}
|
|
29778
|
+
return {
|
|
29779
|
+
kind: "app",
|
|
29780
|
+
appId: app.appId,
|
|
29781
|
+
rendererType: "native",
|
|
29782
|
+
...app.title ? { title: app.title } : {},
|
|
29783
|
+
...app.props ? { props: app.props } : {}
|
|
29784
|
+
};
|
|
29785
|
+
}
|
|
29786
|
+
if (app.kind === "url") {
|
|
29787
|
+
if (!app.appId || !app.url) {
|
|
29788
|
+
throw new Error(
|
|
29789
|
+
"Workspace template URL app requires both appId and url"
|
|
29790
|
+
);
|
|
29791
|
+
}
|
|
29792
|
+
return {
|
|
29793
|
+
kind: "app",
|
|
29794
|
+
appId: app.appId,
|
|
29795
|
+
rendererType: "external",
|
|
29796
|
+
url: app.url,
|
|
29797
|
+
...app.title ? { title: app.title } : {},
|
|
29798
|
+
...app.props ? { props: app.props } : {}
|
|
29799
|
+
};
|
|
29800
|
+
}
|
|
29801
|
+
if (app.kind === "stored") {
|
|
29802
|
+
if (!app.cellKey) {
|
|
29803
|
+
throw new Error("Workspace template stored app is missing cellKey");
|
|
29804
|
+
}
|
|
29805
|
+
if (app.cellKey === "entity-detail" && !app.props?.entityId) {
|
|
29806
|
+
return void 0;
|
|
29807
|
+
}
|
|
29808
|
+
return {
|
|
29809
|
+
kind: "cell",
|
|
29810
|
+
cellKey: app.cellKey,
|
|
29811
|
+
...app.title ? { title: app.title } : {},
|
|
29812
|
+
...app.props ? { props: app.props } : {}
|
|
29813
|
+
};
|
|
29814
|
+
}
|
|
29815
|
+
return void 0;
|
|
29816
|
+
}
|
|
29817
|
+
function normalizeTemplateLayout(layout, app) {
|
|
29818
|
+
if (app && layout && hasOwn(layout, "primarySurface")) {
|
|
29819
|
+
throw new Error(
|
|
29820
|
+
"Workspace template declares both top-level app and layout.primarySurface; choose one authoring source"
|
|
29821
|
+
);
|
|
29822
|
+
}
|
|
29823
|
+
const primarySurface = (() => {
|
|
29824
|
+
if (layout && hasOwn(layout, "primarySurface")) {
|
|
29825
|
+
return layout.primarySurface;
|
|
29826
|
+
}
|
|
29827
|
+
const fromApp = appRefToPrimarySurface(app);
|
|
29828
|
+
if (fromApp) return fromApp;
|
|
29829
|
+
if (layout && hasOwn(layout, "defaultApp")) {
|
|
29830
|
+
return layout.defaultApp ? {
|
|
29831
|
+
kind: "app",
|
|
29832
|
+
appId: layout.defaultApp,
|
|
29833
|
+
rendererType: "native"
|
|
29834
|
+
} : null;
|
|
29835
|
+
}
|
|
29836
|
+
return void 0;
|
|
29837
|
+
})();
|
|
29838
|
+
if (!layout && primarySurface === void 0) return void 0;
|
|
29839
|
+
return {
|
|
29840
|
+
...layout,
|
|
29841
|
+
...primarySurface !== void 0 || layout && hasOwn(layout, "primarySurface") ? { primarySurface } : {}
|
|
29842
|
+
};
|
|
29843
|
+
}
|
|
29224
29844
|
function resolveBlockKind(block) {
|
|
29225
29845
|
if (block.kind === "widget" || block.kind === "view") return block.kind;
|
|
29226
29846
|
if (block.widgetType) return "widget";
|
|
@@ -29281,7 +29901,7 @@ function toWorkspaceDefinition(slugOrYaml, nameOverride) {
|
|
|
29281
29901
|
seedEntities: tpl.seedEntities ?? [],
|
|
29282
29902
|
suggestedRelations: tpl.suggestedRelations,
|
|
29283
29903
|
displayTemplates: tpl.displayTemplates,
|
|
29284
|
-
layoutConfig: tpl.layout,
|
|
29904
|
+
layoutConfig: normalizeTemplateLayout(tpl.layout, tpl.app),
|
|
29285
29905
|
bentoViewName: tpl.bento?.viewName,
|
|
29286
29906
|
bentoLayout: bento.bentoLayout,
|
|
29287
29907
|
bentoViewBlocks: bento.bentoViewBlocks,
|
|
@@ -29355,7 +29975,7 @@ function toPackageDefinition(slugOrYaml) {
|
|
|
29355
29975
|
bentoViewBlocks: bento.bentoViewBlocks,
|
|
29356
29976
|
bentoViewName: tpl.bento?.viewName,
|
|
29357
29977
|
profileEntityBentoTemplates: tpl.profileEntityBentoTemplates,
|
|
29358
|
-
layoutConfig: tpl.layout,
|
|
29978
|
+
layoutConfig: normalizeTemplateLayout(tpl.layout, tpl.app),
|
|
29359
29979
|
capabilities: tpl.integrations,
|
|
29360
29980
|
playbooks: tpl.playbooks,
|
|
29361
29981
|
automations: toAutomationWire(tpl.automations),
|
|
@@ -29365,7 +29985,10 @@ function toPackageDefinition(slugOrYaml) {
|
|
|
29365
29985
|
};
|
|
29366
29986
|
}
|
|
29367
29987
|
export {
|
|
29988
|
+
CHANNEL_MESSAGE_CHANNEL_TYPES,
|
|
29368
29989
|
DECLARED_DIVERGENCES,
|
|
29990
|
+
FLOW_NODE_TYPES,
|
|
29991
|
+
FLOW_OUTPUT_TYPES,
|
|
29369
29992
|
OTHER_DOMAIN_ID,
|
|
29370
29993
|
OTHER_DOMAIN_LABEL,
|
|
29371
29994
|
PACKAGE_DEPENDENCY_KINDS,
|
|
@@ -29381,6 +30004,7 @@ export {
|
|
|
29381
30004
|
conceptTerm,
|
|
29382
30005
|
domainLabel,
|
|
29383
30006
|
findConceptViolations,
|
|
30007
|
+
flowValidationErrorMessage,
|
|
29384
30008
|
formatConceptViolations,
|
|
29385
30009
|
getWorkspaceTemplate,
|
|
29386
30010
|
groupByDomain,
|
|
@@ -29390,10 +30014,12 @@ export {
|
|
|
29390
30014
|
listPublicTemplates,
|
|
29391
30015
|
listWorkspaceTemplates,
|
|
29392
30016
|
mergeCatalog,
|
|
30017
|
+
normalizeTemplateLayout,
|
|
29393
30018
|
rowToRemoteEntry,
|
|
29394
30019
|
splitBentoBlocks,
|
|
29395
30020
|
toPackageDefinition,
|
|
29396
30021
|
toWorkspaceDefinition,
|
|
30022
|
+
validateFlowDefinition,
|
|
29397
30023
|
validateTemplate
|
|
29398
30024
|
};
|
|
29399
30025
|
//# sourceMappingURL=index.js.map
|