diff --git a/packages/server/src/api/controllers/workflow/index.js b/packages/server/src/api/controllers/workflow.js similarity index 82% rename from packages/server/src/api/controllers/workflow/index.js rename to packages/server/src/api/controllers/workflow.js index efb93ad1c1..04a0b7f1ba 100644 --- a/packages/server/src/api/controllers/workflow/index.js +++ b/packages/server/src/api/controllers/workflow.js @@ -1,7 +1,8 @@ -const CouchDB = require("../../../db") -const newid = require("../../../db/newid") -const blockDefinitions = require("./blockDefinitions") -const triggers = require("../../../workflows/triggers") +const CouchDB = require("../../db") +const newid = require("../../db/newid") +const actions = require("../../workflows/actions") +const logic = require("../../workflows/logic") +const triggers = require("../../workflows/triggers") /************************* * * @@ -67,22 +68,22 @@ exports.destroy = async function(ctx) { } exports.getActionList = async function(ctx) { - ctx.body = blockDefinitions.ACTION + ctx.body = actions.BUILTIN_DEFINITIONS } exports.getTriggerList = async function(ctx) { - ctx.body = blockDefinitions.TRIGGER + ctx.body = triggers.BUILTIN_DEFINITIONS } exports.getLogicList = async function(ctx) { - ctx.body = blockDefinitions.LOGIC + ctx.body = logic.BUILTIN_DEFINITIONS } module.exports.getDefinitionList = async function(ctx) { ctx.body = { - logic: blockDefinitions.LOGIC, - trigger: blockDefinitions.TRIGGER, - action: blockDefinitions.ACTION, + logic: logic.BUILTIN_DEFINITIONS, + trigger: triggers.BUILTIN_DEFINITIONS, + action: actions.BUILTIN_DEFINITIONS, } } diff --git a/packages/server/src/api/controllers/workflow/blockDefinitions.js b/packages/server/src/api/controllers/workflow/blockDefinitions.js deleted file mode 100644 index 5607ac8ee4..0000000000 --- a/packages/server/src/api/controllers/workflow/blockDefinitions.js +++ /dev/null @@ -1,330 +0,0 @@ -let accessLevels = require("../../../utilities/accessLevels") -let conditions = require("../../../workflows/logic").LogicConditions - -const ACTION = { - SAVE_RECORD: { - name: "Save Record", - tagline: "Save a {{inputs.enriched.model.name}} record", - icon: "ri-save-3-fill", - description: "Save a record to your database", - type: "ACTION", - inputs: {}, - schema: { - inputs: { - properties: { - record: { - type: "object", - properties: { - modelId: { - type: "string", - customType: "model", - title: "Table", - }, - }, - customType: "record", - title: "Table", - default: {}, - required: ["modelId"], - }, - }, - required: ["record"], - }, - outputs: { - properties: { - response: { - type: "object", - description: "The response from the table", - }, - success: { - type: "boolean", - description: "Whether the action was successful", - }, - id: { - type: "string", - description: "The identifier of the new record", - }, - revision: { - type: "string", - description: "The revision of the new record", - }, - }, - required: ["success", "id", "revision"], - }, - }, - }, - DELETE_RECORD: { - description: "Delete a record from your database", - icon: "ri-delete-bin-line", - name: "Delete Record", - tagline: "Delete a {{inputs.enriched.model.name}} record", - type: "ACTION", - inputs: {}, - schema: { - inputs: { - properties: { - modelId: { - type: "string", - customType: "model", - title: "Table", - }, - id: { - type: "string", - title: "Record ID", - }, - revision: { - type: "string", - title: "Record Revision", - }, - }, - required: ["modelId", "id", "revision"], - }, - outputs: { - properties: { - record: { - type: "object", - customType: "record", - description: "The deleted record", - }, - response: { - type: "object", - description: "The response from the table", - }, - success: { - type: "boolean", - description: "Whether the action was successful", - }, - }, - required: ["record", "success"], - }, - }, - }, - CREATE_USER: { - description: "Create a new user", - tagline: "Create user {{inputs.username}}", - icon: "ri-user-add-fill", - name: "Create User", - type: "ACTION", - inputs: {}, - schema: { - inputs: { - properties: { - username: { - type: "string", - title: "Username", - }, - password: { - type: "string", - customType: "password", - title: "Password", - }, - accessLevelId: { - type: "string", - title: "Access Level", - default: accessLevels.POWERUSER_LEVEL_ID, - enum: accessLevels.ACCESS_LEVELS, - }, - }, - required: ["username", "password", "accessLevelId"], - }, - outputs: { - properties: { - id: { - type: "string", - description: "The identifier of the new user", - }, - revision: { - type: "string", - description: "The revision of the new user", - }, - response: { - type: "object", - description: "The response from the user table", - }, - success: { - type: "boolean", - description: "Whether the action was successful", - }, - }, - required: ["id", "revision", "success"], - }, - }, - }, - SEND_EMAIL: { - description: "Send an email.", - tagline: "Send email to {{inputs.to}}", - icon: "ri-mail-open-fill", - name: "Send Email", - type: "ACTION", - inputs: {}, - schema: { - inputs: { - properties: { - to: { - type: "string", - title: "Send To", - }, - from: { - type: "string", - title: "Send From", - }, - subject: { - type: "string", - title: "Email Subject", - }, - contents: { - type: "string", - title: "Email Contents", - }, - }, - required: ["to", "from", "subject", "contents"], - }, - outputs: { - properties: { - success: { - type: "boolean", - description: "Whether the email was sent", - }, - response: { - type: "object", - description: - "A response from the email client, this may be an error", - }, - }, - required: ["success"], - }, - }, - }, -} - -const LOGIC = { - FILTER: { - name: "Filter", - tagline: "{{inputs.filter}} {{inputs.condition}} {{inputs.value}}", - icon: "ri-git-branch-line", - description: "Filter any workflows which do not meet certain conditions", - type: "LOGIC", - inputs: {}, - schema: { - inputs: { - properties: { - filter: { - type: "string", - title: "Reference Value", - }, - condition: { - type: "string", - title: "Condition", - enum: conditions, - default: "equals", - }, - value: { - type: "string", - title: "Comparison Value", - }, - }, - required: ["filter", "condition", "value"], - }, - outputs: { - properties: { - success: { - type: "boolean", - description: "Whether the logic block passed", - }, - }, - required: ["success"], - }, - }, - }, - DELAY: { - name: "Delay", - icon: "ri-time-fill", - tagline: "Delay for {{inputs.time}} milliseconds", - description: "Delay the workflow until an amount of time has passed", - inputs: {}, - schema: { - inputs: { - properties: { - time: { - type: "number", - title: "Delay in milliseconds", - }, - }, - required: ["time"], - }, - }, - type: "LOGIC", - }, -} - -const TRIGGER = { - RECORD_SAVED: { - name: "Record Saved", - event: "record:save", - icon: "ri-save-line", - tagline: "Record is added to {{inputs.enriched.model.name}}", - description: "Fired when a record is saved to your database", - inputs: {}, - schema: { - inputs: { - properties: { - modelId: { - type: "string", - customType: "model", - title: "Table", - }, - }, - required: ["modelId"], - }, - outputs: { - properties: { - record: { - type: "object", - customType: "record", - description: "The new record that was saved", - }, - }, - required: ["record"], - }, - }, - type: "TRIGGER", - }, - RECORD_DELETED: { - name: "Record Deleted", - event: "record:delete", - icon: "ri-delete-bin-line", - tagline: "Record is deleted from {{inputs.enriched.model.name}}", - description: "Fired when a record is deleted from your database", - inputs: {}, - schema: { - inputs: { - properties: { - modelId: { - type: "string", - customType: "model", - title: "Table", - }, - }, - required: ["modelId"], - }, - outputs: { - properties: { - record: { - type: "object", - customType: "record", - description: "The record that was deleted", - }, - }, - required: ["record"], - }, - }, - type: "TRIGGER", - }, -} - -// This contains the definitions for the steps and triggers that make up a workflow, a workflow comprises -// of many steps and a single trigger -module.exports = { - ACTION, - LOGIC, - TRIGGER, -} diff --git a/packages/server/src/workflows/actions.js b/packages/server/src/workflows/actions.js index 1f3d8481cd..3af75c2934 100644 --- a/packages/server/src/workflows/actions.js +++ b/packages/server/src/workflows/actions.js @@ -1,115 +1,20 @@ -const userController = require("../api/controllers/user") -const recordController = require("../api/controllers/record") -const sgMail = require("@sendgrid/mail") +const sendEmail = require("./steps/sendEmail") +const saveRecord = require("./steps/saveRecord") +const deleteRecord = require("./steps/deleteRecord") +const createUser = require("./steps/createUser") -sgMail.setApiKey(process.env.SENDGRID_API_KEY) +const BUILTIN_ACTIONS = { + SEND_EMAIL: sendEmail.run, + SAVE_RECORD: saveRecord.run, + DELETE_RECORD: deleteRecord.run, + CREATE_USER: createUser.run, +} -let BUILTIN_ACTIONS = { - CREATE_USER: async function(inputs) { - const { username, password, accessLevelId } = inputs - const ctx = { - user: { - instanceId: inputs.instanceId, - }, - request: { - body: { username, password, accessLevelId }, - }, - } - - try { - await userController.create(ctx) - return { - response: ctx.body, - id: ctx.body._id, - revision: ctx.body._rev, - success: ctx.status === 200, - } - } catch (err) { - console.error(err) - return { - success: false, - response: err, - } - } - }, - SAVE_RECORD: async function(inputs) { - const ctx = { - params: { - instanceId: inputs.instanceId, - modelId: inputs.model._id, - }, - request: { - body: inputs.record, - }, - user: { instanceId: inputs.instanceId }, - } - - try { - await recordController.save(ctx) - return { - response: ctx.body, - id: ctx.body._id, - revision: ctx.body._rev, - success: ctx.status === 200, - } - } catch (err) { - console.error(err) - return { - success: false, - response: err, - } - } - }, - SEND_EMAIL: async function(inputs) { - const msg = { - to: inputs.to, - from: inputs.from, - subject: inputs.subject, - text: inputs.text, - } - - try { - await sgMail.send(msg) - return { - success: true, - } - } catch (err) { - console.error(err) - return { - success: false, - response: err, - } - } - }, - DELETE_RECORD: async function(inputs) { - const { model, ...record } = inputs.record - // TODO: better logging of when actions are missed due to missing parameters - if (record.recordId == null || record.revId == null) { - return - } - let ctx = { - params: { - modelId: model._id, - recordId: record.recordId, - revId: record.revId, - }, - user: { instanceId: inputs.instanceId }, - } - - try { - await recordController.destroy(ctx) - return { - response: ctx.body, - success: ctx.status === 200, - } - } catch (err) { - console.error(err) - return { - success: false, - response: err, - } - } - }, +const BUILTIN_DEFINITIONS = { + SEND_EMAIL: sendEmail.definition, + SAVE_RECORD: saveRecord.definition, + DELETE_RECORD: deleteRecord.definition, + CREATE_USER: createUser.definition, } module.exports.getAction = async function(actionName) { @@ -118,3 +23,5 @@ module.exports.getAction = async function(actionName) { } // TODO: load async actions here } + +module.exports.BUILTIN_DEFINITIONS = BUILTIN_DEFINITIONS diff --git a/packages/server/src/workflows/logic.js b/packages/server/src/workflows/logic.js index 7a584f995d..b74c02bb56 100644 --- a/packages/server/src/workflows/logic.js +++ b/packages/server/src/workflows/logic.js @@ -1,31 +1,20 @@ -const wait = ms => new Promise(resolve => setTimeout(resolve, ms)) +let filter = require("./steps/filter") +let delay = require("./steps/delay") -let LOGIC = { - DELAY: async function delay(inputs) { - await wait(inputs.time) - }, +let BUILTIN_LOGIC = { + DELAY: delay.run, + FILTER: filter.run, +} - FILTER: async function filter(inputs) { - const { field, condition, value } = inputs - switch (condition) { - case "equals": - if (field !== value) return - break - default: - return - } - }, +let BUILTIN_DEFINITIONS = { + DELAY: delay.definition, + FILTER: filter.definition, } module.exports.getLogic = function(logicName) { - if (LOGIC[logicName] != null) { - return LOGIC[logicName] + if (BUILTIN_LOGIC[logicName] != null) { + return BUILTIN_LOGIC[logicName] } } -module.exports.LogicConditions = [ - "Equals", - "Not equals", - "Greater than", - "Less than", -] +module.exports.BUILTIN_DEFINITIONS = BUILTIN_DEFINITIONS diff --git a/packages/server/src/workflows/steps/createUser.js b/packages/server/src/workflows/steps/createUser.js new file mode 100644 index 0000000000..35aa9bd31c --- /dev/null +++ b/packages/server/src/workflows/steps/createUser.js @@ -0,0 +1,82 @@ +const userController = require("../../api/controllers/user") +let accessLevels = require("../../utilities/accessLevels") + +module.exports.definition = { + description: "Create a new user", + tagline: "Create user {{inputs.username}}", + icon: "ri-user-add-fill", + name: "Create User", + type: "ACTION", + inputs: {}, + schema: { + inputs: { + properties: { + username: { + type: "string", + title: "Username", + }, + password: { + type: "string", + customType: "password", + title: "Password", + }, + accessLevelId: { + type: "string", + title: "Access Level", + default: accessLevels.POWERUSER_LEVEL_ID, + enum: accessLevels.ACCESS_LEVELS, + }, + }, + required: ["username", "password", "accessLevelId"], + }, + outputs: { + properties: { + id: { + type: "string", + description: "The identifier of the new user", + }, + revision: { + type: "string", + description: "The revision of the new user", + }, + response: { + type: "object", + description: "The response from the user table", + }, + success: { + type: "boolean", + description: "Whether the action was successful", + }, + }, + required: ["id", "revision", "success"], + }, + }, +} + +module.exports.run = async function({ inputs, instanceId }) { + const { username, password, accessLevelId } = inputs + const ctx = { + user: { + instanceId: instanceId, + }, + request: { + body: { username, password, accessLevelId }, + }, + } + + try { + await userController.create(ctx) + return { + response: ctx.body, + id: ctx.body._id, + revision: ctx.body._rev, + success: ctx.status === 200, + } + } catch (err) { + console.error(err) + return { + success: false, + response: err, + } + } +} \ No newline at end of file diff --git a/packages/server/src/workflows/steps/delay.js b/packages/server/src/workflows/steps/delay.js new file mode 100644 index 0000000000..728d75d257 --- /dev/null +++ b/packages/server/src/workflows/steps/delay.js @@ -0,0 +1,25 @@ +const wait = ms => new Promise(resolve => setTimeout(resolve, ms)) + +module.exports.definition = { + name: "Delay", + icon: "ri-time-fill", + tagline: "Delay for {{inputs.time}} milliseconds", + description: "Delay the workflow until an amount of time has passed", + inputs: {}, + schema: { + inputs: { + properties: { + time: { + type: "number", + title: "Delay in milliseconds", + }, + }, + required: ["time"], + }, + }, + type: "LOGIC", +} + +module.exports.run = async function delay({ inputs }) { + await wait(inputs.time) +} diff --git a/packages/server/src/workflows/steps/deleteRecord.js b/packages/server/src/workflows/steps/deleteRecord.js new file mode 100644 index 0000000000..abf44b1c9b --- /dev/null +++ b/packages/server/src/workflows/steps/deleteRecord.js @@ -0,0 +1,77 @@ +const recordController = require("../../api/controllers/record") + +module.exports.definition = { + description: "Delete a record from your database", + icon: "ri-delete-bin-line", + name: "Delete Record", + tagline: "Delete a {{inputs.enriched.model.name}} record", + type: "ACTION", + inputs: {}, + schema: { + inputs: { + properties: { + modelId: { + type: "string", + customType: "model", + title: "Table", + }, + id: { + type: "string", + title: "Record ID", + }, + revision: { + type: "string", + title: "Record Revision", + }, + }, + required: ["modelId", "id", "revision"], + }, + outputs: { + properties: { + record: { + type: "object", + customType: "record", + description: "The deleted record", + }, + response: { + type: "object", + description: "The response from the table", + }, + success: { + type: "boolean", + description: "Whether the action was successful", + }, + }, + required: ["record", "success"], + }, + }, +} + +module.exports.run = async function({ inputs, instanceId }) { + // TODO: better logging of when actions are missed due to missing parameters + if (inputs.id == null || inputs.revision == null) { + return + } + let ctx = { + params: { + modelId: inputs.modelId, + recordId: inputs.id, + revId: inputs.revision, + }, + user: { instanceId }, + } + + try { + await recordController.destroy(ctx) + return { + response: ctx.body, + success: ctx.status === 200, + } + } catch (err) { + console.error(err) + return { + success: false, + response: err, + } + } +} diff --git a/packages/server/src/workflows/steps/filter.js b/packages/server/src/workflows/steps/filter.js new file mode 100644 index 0000000000..080af957e1 --- /dev/null +++ b/packages/server/src/workflows/steps/filter.js @@ -0,0 +1,71 @@ +const LogicConditions = { + EQUALS: "Equals", + NOT_EQUALS: "Not equals", + GREATER_THAN: "Greater than", + LESS_THAN: "Less than", +} + +module.exports.definition = { + name: "Filter", + tagline: "{{inputs.filter}} {{inputs.condition}} {{inputs.value}}", + icon: "ri-git-branch-line", + description: "Filter any workflows which do not meet certain conditions", + type: "LOGIC", + inputs: {}, + schema: { + inputs: { + properties: { + filter: { + type: "string", + title: "Reference Value", + }, + condition: { + type: "string", + title: "Condition", + enum: Object.values(LogicConditions), + default: "equals", + }, + value: { + type: "string", + title: "Comparison Value", + }, + }, + required: ["filter", "condition", "value"], + }, + outputs: { + properties: { + success: { + type: "boolean", + description: "Whether the logic block passed", + }, + }, + required: ["success"], + }, + }, +} + +module.exports.run = async function filter({ inputs }) { + const { field, condition, value } = inputs + let success + if (typeof field !== "object" && typeof value !== "object") { + switch (condition) { + case LogicConditions.EQUALS: + success = field === value + break + case LogicConditions.NOT_EQUALS: + success = field !== value + break + case LogicConditions.GREATER_THAN: + success = field > value + break + case LogicConditions.LESS_THAN: + success = field < value + break + default: + return + } + } else { + success = false + } + return { success } +} diff --git a/packages/server/src/workflows/steps/saveRecord.js b/packages/server/src/workflows/steps/saveRecord.js new file mode 100644 index 0000000000..2d88a74cde --- /dev/null +++ b/packages/server/src/workflows/steps/saveRecord.js @@ -0,0 +1,85 @@ +const recordController = require("../../api/controllers/record") + +module.exports.definition = { + name: "Save Record", + tagline: "Save a {{inputs.enriched.model.name}} record", + icon: "ri-save-3-fill", + description: "Save a record to your database", + type: "ACTION", + inputs: {}, + schema: { + inputs: { + properties: { + record: { + type: "object", + properties: { + modelId: { + type: "string", + customType: "model", + title: "Table", + }, + }, + customType: "record", + title: "The record to be written", + default: {}, + required: ["modelId"], + }, + }, + required: ["record"], + }, + outputs: { + properties: { + response: { + type: "object", + description: "The response from the table", + }, + success: { + type: "boolean", + description: "Whether the action was successful", + }, + id: { + type: "string", + description: "The identifier of the new record", + }, + revision: { + type: "string", + description: "The revision of the new record", + }, + }, + required: ["success", "id", "revision"], + }, + }, +} + +module.exports.run = async function({ inputs, instanceId }) { + // TODO: better logging of when actions are missed due to missing parameters + if (inputs.record == null || inputs.record.modelId == null) { + return + } + // have to clean up the record, remove the model from it + const ctx = { + params: { + modelId: inputs.record.modelId, + }, + request: { + body: inputs.record, + }, + user: { instanceId }, + } + + try { + await recordController.save(ctx) + return { + response: ctx.body, + id: ctx.body._id, + revision: ctx.body._rev, + success: ctx.status === 200, + } + } catch (err) { + console.error(err) + return { + success: false, + response: err, + } + } +} diff --git a/packages/server/src/workflows/steps/sendEmail.js b/packages/server/src/workflows/steps/sendEmail.js new file mode 100644 index 0000000000..c25e56e76e --- /dev/null +++ b/packages/server/src/workflows/steps/sendEmail.js @@ -0,0 +1,70 @@ +const sgMail = require("@sendgrid/mail") +sgMail.setApiKey(process.env.SENDGRID_API_KEY) + +module.exports.definition = { + description: "Send an email.", + tagline: "Send email to {{inputs.to}}", + icon: "ri-mail-open-fill", + name: "Send Email", + type: "ACTION", + inputs: {}, + schema: { + inputs: { + properties: { + to: { + type: "string", + title: "Send To", + }, + from: { + type: "string", + title: "Send From", + }, + subject: { + type: "string", + title: "Email Subject", + }, + contents: { + type: "string", + title: "Email Contents", + }, + }, + required: ["to", "from", "subject", "contents"], + }, + outputs: { + properties: { + success: { + type: "boolean", + description: "Whether the email was sent", + }, + response: { + type: "object", + description: "A response from the email client, this may be an error", + }, + }, + required: ["success"], + }, + }, +} + +module.exports.run = async function({ inputs }) { + const msg = { + to: inputs.to, + from: inputs.from, + subject: inputs.subject, + text: inputs.text, + } + + try { + let response = await sgMail.send(msg) + return { + success: true, + response, + } + } catch (err) { + console.error(err) + return { + success: false, + response: err, + } + } +} diff --git a/packages/server/src/workflows/thread.js b/packages/server/src/workflows/thread.js index 18eb6e2434..177e7cbe52 100644 --- a/packages/server/src/workflows/thread.js +++ b/packages/server/src/workflows/thread.js @@ -2,18 +2,34 @@ const mustache = require("mustache") const actions = require("./actions") const logic = require("./logic") +function recurseMustache(inputs, context) { + for (let key in Object.keys(inputs)) { + let val = inputs[key] + if (typeof val === "string") { + inputs[key] = mustache.render(val, { context }) + } + // this covers objects and arrays + else if (typeof val === "object") { + inputs[key] = recurseMustache(inputs[key], context) + } + } + return inputs +} + /** * The workflow orchestrator is a class responsible for executing workflows. * It handles the context of the workflow and makes sure each step gets the correct * inputs and handles any outputs. */ class Orchestrator { - constructor(workflow) { - this._context = {} + constructor(workflow, triggerOutput) { + this._instanceId = triggerOutput.instanceId + // block zero is never used as the mustache is zero indexed for customer facing + this._context = { blocks: [{}, triggerOutput] } this._workflow = workflow } - async getStep(type, stepId) { + async getStepFunctionality(type, stepId) { let step = null if (type === "ACTION") { step = await actions.getAction(stepId) @@ -26,28 +42,17 @@ class Orchestrator { return step } - async execute(context) { + async execute() { let workflow = this._workflow for (let block of workflow.definition.steps) { - let step = await this.getStep(block.type, block.stepId) - let args = { ...block.args } - // bind the workflow action args to the workflow context, if required - for (let arg of Object.keys(args)) { - const argValue = args[arg] - // We don't want to render mustache templates on non-strings - if (typeof argValue !== "string") continue - - args[arg] = mustache.render(argValue, { context: this._context }) - } - const response = await step({ - args, - context, + let stepFn = await this.getStepFunctionality(block.type, block.stepId) + block.inputs = recurseMustache(block.inputs, this._context) + // instanceId is always passed + const outputs = await stepFn({ + inputs: block.inputs, + instanceId: this._instanceId, }) - - this._context = { - ...this._context, - [block.id]: response, - } + this._context.blocks.push(outputs) } } } @@ -55,8 +60,11 @@ class Orchestrator { // callback is required for worker-farm to state that the worker thread has completed module.exports = async (job, cb = null) => { try { - const workflowOrchestrator = new Orchestrator(job.data.workflow) - await workflowOrchestrator.execute(job.data.event) + const workflowOrchestrator = new Orchestrator( + job.data.workflow, + job.data.event + ) + await workflowOrchestrator.execute() if (cb) { cb() } diff --git a/packages/server/src/workflows/triggers.js b/packages/server/src/workflows/triggers.js index f8bbf5b5e5..3149c52a65 100644 --- a/packages/server/src/workflows/triggers.js +++ b/packages/server/src/workflows/triggers.js @@ -4,6 +4,71 @@ const InMemoryQueue = require("./queue/inMemoryQueue") let workflowQueue = new InMemoryQueue() +const BUILTIN_DEFINITIONS = { + RECORD_SAVED: { + name: "Record Saved", + event: "record:save", + icon: "ri-save-line", + tagline: "Record is added to {{inputs.enriched.model.name}}", + description: "Fired when a record is saved to your database", + inputs: {}, + schema: { + inputs: { + properties: { + modelId: { + type: "string", + customType: "model", + title: "Table", + }, + }, + required: ["modelId"], + }, + outputs: { + properties: { + record: { + type: "object", + customType: "record", + description: "The new record that was saved", + }, + }, + required: ["record"], + }, + }, + type: "TRIGGER", + }, + RECORD_DELETED: { + name: "Record Deleted", + event: "record:delete", + icon: "ri-delete-bin-line", + tagline: "Record is deleted from {{inputs.enriched.model.name}}", + description: "Fired when a record is deleted from your database", + inputs: {}, + schema: { + inputs: { + properties: { + modelId: { + type: "string", + customType: "model", + title: "Table", + }, + }, + required: ["modelId"], + }, + outputs: { + properties: { + record: { + type: "object", + customType: "record", + description: "The record that was deleted", + }, + }, + required: ["record"], + }, + }, + type: "TRIGGER", + }, +} + async function queueRelevantWorkflows(event, eventType) { if (event.instanceId == null) { throw `No instanceId specified for ${eventType} - check event emitters.` @@ -36,3 +101,5 @@ module.exports.externalTrigger = async function(workflow, params) { } module.exports.workflowQueue = workflowQueue + +module.exports.BUILTIN_DEFINITIONS = BUILTIN_DEFINITIONS