Lesson 2: Set up Runtime Actions

In this lesson, we'll set up the Runtime actions to handle the CRUD operations. The will be able to handle multiple to-do lists, and each can have several to-do items.

Global configuration file

To avoid long to-do lists, we'll define a MAX_TODO_ITEMS value within a global configuration file that we will import from the actions folder and also from the web-src folder. We'll create the file at the root of the App Builder App and name it defaults.json:

{
  "MAX_TODO_ITEMS": 10
}

Then, import the value in our action todolist/index.js:

const { MAX_TODO_ITEMS } = require('../../defaults.json');

In the next lesson, we'll also show how to import the value from the web-src folder.

Install aio-lib-state

We'll be using aio-lib-state to store the todo items, so first we install the dependency with:

npm i --save @adobe/aio-lib-state

Then we import it:

const stateLib = require('@adobe/aio-lib-state');

Main function

We'll setup the CRUD operations inside the main function.

Parameters

First, we define an operation parameter and make it required:

const requiredParams = ['operation']; 

The operation parameter can take four possible values:

  • create to create an empty to-do list
  • read to read a to-do list
  • update to update a to-do list with a todo item
  • delete to delete a to-do list

We'll also use additional optional parameters:

  • name to identify a list
  • todo to identify a to-do inside a list
const { operation, name, todo } = params;

CRUD operations

Next, we'll initialize the state library and retrieve a todolist value with state.get().

const state = await stateLib.init();

let todoList = await state.get(`todolist`);
if (todoList?.value) {
  todoList = todoList.value;
}
else {
  todoList = [];
}

The todolist will hold all to-do listsobjects and is an empty array by default.

Finally, we'll define which operation to perform based on the value of operation and return the response:

let body = {};
switch (operation) {
  case 'create':
    // Find the todo list by name
    if (!todoList.find(({ name: todoListName }) => todoListName === name)) {
      // If none found, create an empty list with the given name
      todoList.unshift({
        name,
        todos: []
      });

      // Store the new list in the state storage with no expiry time
      await state.put(`todolist`, todoList, { ttl: -1 });

      body.message = `"${name}" added.`;
    } else {
      return errorResponse(400, `"${name}" already exists.`, logger);
    }
    break;

  case 'read':
    // Simply return the todo lists
    body.todoList = todoList;
    break;

  case 'update':
    if (todo) {
      // Find the todo list by name
      const foundTodoList = todoList.find(({ name: todoListName }) => todoListName === name);
      if (foundTodoList) {
        // Find the todo item by id
        const todoIndex = foundTodoList.todos.findIndex(({ id }) => id === todo.id);
        if (todoIndex !== -1) {
          // Update the todo item
          foundTodoList.todos[todoIndex] = todo;
          body.message = `Todo "${todo.id}" updated in "${name}".`;

          await state.put(`todolist`, todoList, { ttl: -1 });
        } else {
          // Create a new todo item
          if (foundTodoList.todos.length < MAX_TODO_ITEMS) {
            foundTodoList.todos.unshift(todo);
            body.message = `Todo "${todo.id}" added to "${name}".`;

            await state.put(`todolist`, todoList, { ttl: -1 });
          } else {
            return errorResponse(400, `Max ${MAX_TODO_ITEMS} todos reached for "${name}".`, logger);
          }
        }
      } else {
        return errorResponse(400, `${name} not found.`, logger);
      }
    } else {
      return errorResponse(400, `Todo is missing.`, logger);
    }
    break;

  case 'delete':
    // Filter out the todo list to delete by name
    const updatedTodoList = todoList.filter(({ name: todoListName }) => todoListName !== name);

    await state.put(`todolist`, updatedTodoList, { ttl: -1 });

    body.message = `"${name}" todo list deleted.`;
    break;

  default:
    return errorResponse(400, 'CRUD operation not found', logger);
}

return {
  statusCode: 200,
  body
};

For every operation except read, we are using the state.put() function to update the todolist value. We also set the time to live option to -1 so that the value of todolist won't expire.

See the full action code here.