en un clic
automation-slice
builds an automation slice from an event model
Menu
builds an automation slice from an event model
| name | automation-slice |
| description | builds an automation slice from an event model |
Make sure to read the Agents.md file before building anything.
If the processors-Array is not empty, it´s an automation slice.
Important - an automation slice is "just" a state-change slice with an additional automation that triggers the command. So also read the skill for 'state-change-slice'
restaurantId in their metadata (camelCase)locationId or location_id - these are outdated and forbiddenrestaurant_id column (snake_case)Automations are processes that happen in the background, based on a TODO List. TODO Lists are always tables (read models) and each row in the table is a TODO Item.
The automation is only responsible to:
┌─────────────────┐
│ TODO List │ (Read Model - Inbound Dependency)
│ "items to do" │ Example: "clerks_to_invite", "items_to_fetch"
└────────┬────────┘
│
│ reads
│
┌────────▼────────┐
│ processor.ts │ (CRON scheduled automation)
│ │ - Fetches TODO items
│ │ - Fires commands
└────────┬────────┘
│
│ invokes
│
┌────────▼────────┐
│ CommandHandler │ (State-change logic)
│ Command.ts │ - decide() function
│ routes.ts │ - evolve() function
└─────────────────┘
An automation slice consists of:
Two patterns exist in the codebase:
import {ProcessorConfig, ProcessorTodoItem, startProcessor} from "../../process/process";
import {handleYourCommand} from "./YourCommandCommand";
export type ItemToProcess = {
itemId: string,
// other fields from read model
}
const config: ProcessorConfig = {
schedule: "*/5 * * * * *", // Every 5 seconds (cron format)
endpoint: "your-todo-list-collection", // Read model endpoint
query: {
"status": "OPEN", // Filter criteria
"_limit": "1" // Process one at a time
}
}
const handler = async (item: ItemToProcess & ProcessorTodoItem) => {
console.log(`Processing item: ${item.itemId}`)
try {
await handleYourCommand(`aggregate-${item.itemId}`, {
type: "YourCommand",
data: {
itemId: item.itemId,
// map other fields
},
metadata: {}
})
console.log(`Successfully processed item: ${item.itemId}`)
} catch (error) {
console.error(`Error processing item ${item.itemId}:`, error)
}
}
export const processor = {
start: () => {
console.log("[YourProcessor] Starting processor...")
startProcessor<ItemToProcess>(config, handler)
}
}
import {ProcessorConfig} from "../../process/process";
import {YourCommand, handleYourCommand} from "./YourCommandCommand";
import cron from "node-cron";
import {createServiceClient} from "../../supabase/api";
const config: ProcessorConfig = {
schedule: '*/30 * * * * *', // Every 30 seconds
endpoint: "your_todo_table", // Supabase table name
}
export const processor = {
start: () => {
cron.schedule(config.schedule, async () => {
console.log("Running process")
let client = createServiceClient()
let result = await client.from(config.endpoint).select("*")
if (result.count == 0) {
console.log(`Nothing to do for ${config.endpoint}`)
return;
}
for (const item of result.data ?? []) {
const command: YourCommand = {
type: "YourCommand",
data: {
itemId: item.itemId!
},
metadata: {}
}
const id = item.itemId
if (!id) {
throw `Cannot process Command ${command.type}. No Id available.`
}
await handleYourCommand(id, command)
}
})
}
}
┌───────────── second (0-59)
│ ┌─────────── minute (0-59)
│ │ ┌───────── hour (0-23)
│ │ │ ┌─────── day of month (1-31)
│ │ │ │ ┌───── month (1-12)
│ │ │ │ │ ┌─── day of week (0-7)
│ │ │ │ │ │
* * * * * *
Common schedules:
*/5 * * * * * - Every 5 seconds*/30 * * * * * - Every 30 seconds0 */1 * * * * - Every minute0 0 * * * * - Every hourTODO List Read Model: clerks_to_invite table
Automation: src/slices/ConfirmInvitation/processor.ts
Command: ConfirmInvitationCommand
Event Emitted: ClerkInvitationConfirmed
// processor.ts
const config: ProcessorConfig = {
schedule: '*/30 * * * * *',
endpoint: "clerks_to_invite",
}
// Fetches clerks from TODO list and confirms their invitations
for (const clerk of result.data ?? []) {
const command: ConfirmInvitationCommand = {
type: "ConfirmInvitation",
data: {
clerkId: clerk.clerkId!
},
metadata: {}
}
await handleConfirmInvitation(clerk.clerkId, command)
}
in each slice folder, generate a file .slice.json
{
"id" : "<slice id>",
"slice": "<slice title>",
"context": "<contextx>",
"link": "https://miro.com/app/board/<board-id>=/?moveToWidget=<slice id>"
}
<things>_to_<action> (e.g., clerks_to_invite, items_to_fetch)/api/query/<name>-collection endpointstatus field ("OPEN", "PROCESSING", "DONE")_limit: "1" to process one item at a time, preventing concurrent issuescatalogue-${itemId})[Command]Command.ts with decide/evolve functionsroutes.ts for HTTP API endpointSample input: read 'templates/sample-input.json'
The input defines: