@nebulit/embuilder 0.1.46 → 0.1.48
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/package.json +1 -1
- package/templates/.claude/skills/slice-automation/SKILL.md +237 -35
- package/templates/backend/AGENTS.md +533 -0
- package/templates/backend/prompt.md +460 -91
- package/templates/frontend/prompt.md +11 -7
- package/templates/frontend/setup-env.sh +0 -0
- package/templates/frontend/src/App.tsx +2 -1
- package/templates/frontend/src/contexts/AuthContext.tsx +6 -5
- package/templates/prompt.md +2 -1
- package/templates/server.mjs +2 -1
package/package.json
CHANGED
|
@@ -1,49 +1,251 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
3
|
-
description:
|
|
2
|
+
name: slice-automation
|
|
3
|
+
description: builds an automation slice from an event model
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
### Critical understanding
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Make sure to read the Agents.md file before building anything.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
If the processors-Array is not empty, it´s an automation slice.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Important - an automation slice is "just" a state-change slice with an additional automation that triggers the command.
|
|
13
|
+
So also read the skill for 'state-change-slice'
|
|
13
14
|
|
|
14
|
-
##
|
|
15
|
+
## Critical Requirements
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
### Restaurant ID Requirement
|
|
18
|
+
- **CRITICAL**: ALL events MUST have `restaurantId` in their metadata (camelCase)
|
|
19
|
+
- **NEVER** use `locationId` or `location_id` - these are outdated and forbidden
|
|
20
|
+
- **CRITICAL**: ALL database tables (including TODO lists) MUST have a `restaurant_id` column (snake_case)
|
|
21
|
+
- This ensures proper multi-tenancy and data isolation
|
|
19
22
|
|
|
20
|
-
|
|
21
|
-
- Look for slices with `"sliceType": "AUTOMATION"`
|
|
22
|
-
- Extract their IDs and titles
|
|
23
|
+
## Overview
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
- Or accept slice IDs from the user's original request
|
|
25
|
+
Automations are processes that happen in the background, based on a TODO List.
|
|
26
|
+
TODO Lists are always tables (read models) and each row in the table is a TODO Item.
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
The automation is only responsible to:
|
|
29
|
+
1. Fetch items from the TODO List (read model)
|
|
30
|
+
2. Fire the command for each item
|
|
31
|
+
3. Repeat on a schedule using CRON
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
## Architecture Pattern
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
┌─────────────────┐
|
|
37
|
+
│ TODO List │ (Read Model - Inbound Dependency)
|
|
38
|
+
│ "items to do" │ Example: "clerks_to_invite", "items_to_fetch"
|
|
39
|
+
└────────┬────────┘
|
|
40
|
+
│
|
|
41
|
+
│ reads
|
|
42
|
+
│
|
|
43
|
+
┌────────▼────────┐
|
|
44
|
+
│ processor.ts │ (CRON scheduled automation)
|
|
45
|
+
│ │ - Fetches TODO items
|
|
46
|
+
│ │ - Fires commands
|
|
47
|
+
└────────┬────────┘
|
|
48
|
+
│
|
|
49
|
+
│ invokes
|
|
50
|
+
│
|
|
51
|
+
┌────────▼────────┐
|
|
52
|
+
│ CommandHandler │ (State-change logic)
|
|
53
|
+
│ Command.ts │ - decide() function
|
|
54
|
+
│ routes.ts │ - evolve() function
|
|
55
|
+
└─────────────────┘
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Implementation Structure
|
|
59
|
+
|
|
60
|
+
An automation slice consists of:
|
|
61
|
+
1. **processor.ts** - CRON automation that fetches TODO items and fires commands
|
|
62
|
+
2. **[Command]Command.ts** - Command handler with decide/evolve logic
|
|
63
|
+
3. **routes.ts** - HTTP API endpoint for manual command invocation
|
|
64
|
+
|
|
65
|
+
## Processor Configuration
|
|
66
|
+
|
|
67
|
+
Two patterns exist in the codebase:
|
|
68
|
+
|
|
69
|
+
### Pattern 1: Using startProcessor helper (Recommended)
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import {ProcessorConfig, ProcessorTodoItem, startProcessor} from "../../process/process";
|
|
73
|
+
import {handleYourCommand} from "./YourCommandCommand";
|
|
74
|
+
|
|
75
|
+
export type ItemToProcess = {
|
|
76
|
+
itemId: string,
|
|
77
|
+
// other fields from read model
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const config: ProcessorConfig = {
|
|
81
|
+
schedule: "*/5 * * * * *", // Every 5 seconds (cron format)
|
|
82
|
+
endpoint: "your-todo-list-collection", // Read model endpoint
|
|
83
|
+
query: {
|
|
84
|
+
"status": "OPEN", // Filter criteria
|
|
85
|
+
"_limit": "1" // Process one at a time
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const handler = async (item: ItemToProcess & ProcessorTodoItem) => {
|
|
90
|
+
console.log(`Processing item: ${item.itemId}`)
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
await handleYourCommand(`aggregate-${item.itemId}`, {
|
|
94
|
+
type: "YourCommand",
|
|
95
|
+
data: {
|
|
96
|
+
itemId: item.itemId,
|
|
97
|
+
// map other fields
|
|
98
|
+
},
|
|
99
|
+
metadata: {}
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
console.log(`Successfully processed item: ${item.itemId}`)
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error(`Error processing item ${item.itemId}:`, error)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export const processor = {
|
|
109
|
+
start: () => {
|
|
110
|
+
console.log("[YourProcessor] Starting processor...")
|
|
111
|
+
startProcessor<ItemToProcess>(config, handler)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Pattern 2: Direct Supabase query (Legacy)
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
import {ProcessorConfig} from "../../process/process";
|
|
120
|
+
import {YourCommand, handleYourCommand} from "./YourCommandCommand";
|
|
121
|
+
import cron from "node-cron";
|
|
122
|
+
import {createServiceClient} from "../../supabase/api";
|
|
123
|
+
|
|
124
|
+
const config: ProcessorConfig = {
|
|
125
|
+
schedule: '*/30 * * * * *', // Every 30 seconds
|
|
126
|
+
endpoint: "your_todo_table", // Supabase table name
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export const processor = {
|
|
130
|
+
start: () => {
|
|
131
|
+
cron.schedule(config.schedule, async () => {
|
|
132
|
+
console.log("Running process")
|
|
133
|
+
let client = createServiceClient()
|
|
134
|
+
let result = await client.from(config.endpoint).select("*")
|
|
135
|
+
|
|
136
|
+
if (result.count == 0) {
|
|
137
|
+
console.log(`Nothing to do for ${config.endpoint}`)
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
for (const item of result.data ?? []) {
|
|
142
|
+
const command: YourCommand = {
|
|
143
|
+
type: "YourCommand",
|
|
144
|
+
data: {
|
|
145
|
+
itemId: item.itemId!
|
|
146
|
+
},
|
|
147
|
+
metadata: {}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const id = item.itemId
|
|
151
|
+
if (!id) {
|
|
152
|
+
throw `Cannot process Command ${command.type}. No Id available.`
|
|
153
|
+
}
|
|
154
|
+
await handleYourCommand(id, command)
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## CRON Schedule Format
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
┌───────────── second (0-59)
|
|
165
|
+
│ ┌─────────── minute (0-59)
|
|
166
|
+
│ │ ┌───────── hour (0-23)
|
|
167
|
+
│ │ │ ┌─────── day of month (1-31)
|
|
168
|
+
│ │ │ │ ┌───── month (1-12)
|
|
169
|
+
│ │ │ │ │ ┌─── day of week (0-7)
|
|
170
|
+
│ │ │ │ │ │
|
|
171
|
+
* * * * * *
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Common schedules:
|
|
175
|
+
- `*/5 * * * * *` - Every 5 seconds
|
|
176
|
+
- `*/30 * * * * *` - Every 30 seconds
|
|
177
|
+
- `0 */1 * * * *` - Every minute
|
|
178
|
+
- `0 0 * * * *` - Every hour
|
|
179
|
+
|
|
180
|
+
## Real Examples from Codebase
|
|
181
|
+
|
|
182
|
+
### Example 1: ConfirmInvitation (Clerk management)
|
|
183
|
+
|
|
184
|
+
**TODO List Read Model**: `clerks_to_invite` table
|
|
185
|
+
**Automation**: `src/slices/ConfirmInvitation/processor.ts`
|
|
186
|
+
**Command**: `ConfirmInvitationCommand`
|
|
187
|
+
**Event Emitted**: `ClerkInvitationConfirmed`
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
// processor.ts
|
|
191
|
+
const config: ProcessorConfig = {
|
|
192
|
+
schedule: '*/30 * * * * *',
|
|
193
|
+
endpoint: "clerks_to_invite",
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Fetches clerks from TODO list and confirms their invitations
|
|
197
|
+
for (const clerk of result.data ?? []) {
|
|
198
|
+
const command: ConfirmInvitationCommand = {
|
|
199
|
+
type: "ConfirmInvitation",
|
|
200
|
+
data: {
|
|
201
|
+
clerkId: clerk.clerkId!
|
|
202
|
+
},
|
|
203
|
+
metadata: {}
|
|
204
|
+
}
|
|
205
|
+
await handleConfirmInvitation(clerk.clerkId, command)
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
in each slice folder, generate a file .slice.json
|
|
210
|
+
```
|
|
211
|
+
{
|
|
212
|
+
"id" : "<slice id>",
|
|
213
|
+
"slice": "<slice title>",
|
|
214
|
+
"context": "<contextx>",
|
|
215
|
+
"link": "https://miro.com/app/board/<board-id>=/?moveToWidget=<slice id>"
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Key Points
|
|
220
|
+
|
|
221
|
+
1. **TODO List Naming**: Use format `<things>_to_<action>` (e.g., `clerks_to_invite`, `items_to_fetch`)
|
|
222
|
+
2. **Read Model Endpoint**: TODO list is accessed via `/api/query/<name>-collection` endpoint
|
|
223
|
+
3. **Processor Types**: Define TypeScript types for TODO items matching read model structure
|
|
224
|
+
4. **Error Handling**: Always wrap command execution in try-catch, log errors but continue processing
|
|
225
|
+
5. **Schedule Wisely**: Balance responsiveness vs system load (30 seconds is common for production)
|
|
226
|
+
6. **Status Management**: TODO list typically has a `status` field ("OPEN", "PROCESSING", "DONE")
|
|
227
|
+
7. **Limit Processing**: Use `_limit: "1"` to process one item at a time, preventing concurrent issues
|
|
228
|
+
8. **Stream Naming**: Use aggregate-based stream names (e.g., `catalogue-${itemId}`)
|
|
229
|
+
|
|
230
|
+
## Implementation Steps
|
|
231
|
+
|
|
232
|
+
1. **Read the input JSON** from templates/sample-input.json
|
|
233
|
+
2. **Create processor.ts** following one of the patterns above
|
|
234
|
+
3. **Implement state-change slice** using the 'state-change-slice' skill
|
|
235
|
+
- Creates `[Command]Command.ts` with decide/evolve functions
|
|
236
|
+
- No `routes.ts` for Automations.
|
|
237
|
+
4. **Register processor** in main application startup
|
|
238
|
+
5. **Ensure TODO List exists** as a read model (separate slice handles this)
|
|
239
|
+
|
|
240
|
+
## Sample Input Structure
|
|
241
|
+
|
|
242
|
+
Sample input: read 'templates/sample-input.json'
|
|
243
|
+
|
|
244
|
+
The input defines:
|
|
245
|
+
- **processors[]**: Array of automation definitions
|
|
246
|
+
- title: Automation name
|
|
247
|
+
- dependencies: Inbound (TODO list) and Outbound (Command) connections
|
|
248
|
+
- **commands[]**: Commands triggered by the automation
|
|
249
|
+
- **events[]**: Events emitted by the commands
|
|
38
250
|
|
|
39
|
-
## Important Notes
|
|
40
251
|
|
|
41
|
-
- The generator is located in `.claude/skills/gen-skeleton/generators/emmet-supabase`
|
|
42
|
-
- Multiple slices can be generated in one command (comma-separated)
|
|
43
|
-
- Slice IDs must exactly match those in config.json
|
|
44
|
-
- Generated files typically include:
|
|
45
|
-
- processor.ts (background automation logic)
|
|
46
|
-
- CRON configuration
|
|
47
|
-
- Tests
|
|
48
|
-
- Automation slices read from TODO lists (work queues) and fire commands on a schedule
|
|
49
|
-
- Common use cases: auto-confirm invitations, process checkouts, send notifications
|