@live-change/frontend-template 0.9.199 → 0.9.200
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/.claude/rules/live-change-backend-actions-views-triggers.md +62 -0
- package/.claude/rules/live-change-backend-event-sourcing.md +186 -0
- package/.claude/rules/live-change-backend-models-and-relations.md +72 -0
- package/.claude/rules/live-change-frontend-vue-primevue.md +26 -0
- package/.claude/settings.json +32 -0
- package/.claude/skills/create-skills-and-rules/SKILL.md +248 -0
- package/.claude/skills/live-change-backend-change-triggers/SKILL.md +186 -0
- package/.claude/skills/live-change-design-actions-views-triggers/SKILL.md +462 -0
- package/.claude/skills/live-change-design-models-relations/SKILL.md +230 -0
- package/.claude/skills/live-change-design-service/SKILL.md +133 -0
- package/.claude/skills/live-change-frontend-accessible-objects/SKILL.md +384 -0
- package/.claude/skills/live-change-frontend-accessible-objects.md +383 -0
- package/.claude/skills/live-change-frontend-action-buttons/SKILL.md +129 -0
- package/.claude/skills/live-change-frontend-action-form/SKILL.md +149 -0
- package/.claude/skills/live-change-frontend-analytics/SKILL.md +147 -0
- package/.claude/skills/live-change-frontend-command-forms/SKILL.md +216 -0
- package/.claude/skills/live-change-frontend-data-views/SKILL.md +183 -0
- package/.claude/skills/live-change-frontend-editor-form/SKILL.md +240 -0
- package/.claude/skills/live-change-frontend-locale-time/SKILL.md +172 -0
- package/.claude/skills/live-change-frontend-page-list-detail/SKILL.md +201 -0
- package/.claude/skills/live-change-frontend-range-list/SKILL.md +129 -0
- package/.claude/skills/live-change-frontend-ssr-setup/SKILL.md +119 -0
- package/.cursor/rules/live-change-backend-actions-views-triggers.mdc +88 -0
- package/.cursor/rules/live-change-backend-event-sourcing.mdc +185 -0
- package/.cursor/rules/live-change-backend-models-and-relations.mdc +62 -0
- package/.cursor/skills/create-skills-and-rules.md +248 -0
- package/.cursor/skills/live-change-backend-change-triggers.md +186 -0
- package/.cursor/skills/live-change-design-actions-views-triggers.md +178 -79
- package/.cursor/skills/live-change-design-models-relations.md +112 -50
- package/.cursor/skills/live-change-design-service.md +1 -0
- package/.cursor/skills/live-change-frontend-accessible-objects.md +384 -0
- package/.cursor/skills/live-change-frontend-action-buttons.md +1 -0
- package/.cursor/skills/live-change-frontend-action-form.md +9 -3
- package/.cursor/skills/live-change-frontend-analytics.md +1 -0
- package/.cursor/skills/live-change-frontend-command-forms.md +1 -0
- package/.cursor/skills/live-change-frontend-data-views.md +1 -0
- package/.cursor/skills/live-change-frontend-editor-form.md +135 -72
- package/.cursor/skills/live-change-frontend-locale-time.md +1 -0
- package/.cursor/skills/live-change-frontend-page-list-detail.md +1 -0
- package/.cursor/skills/live-change-frontend-range-list.md +1 -0
- package/.cursor/skills/live-change-frontend-ssr-setup.md +1 -0
- package/front/src/router.js +2 -1
- package/opencode.json +10 -0
- package/package.json +52 -50
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: create-skills-and-rules
|
|
3
|
+
description: Create or update Claude Code skills and Cursor rules/skills with proper format and frontmatter, and configure OpenCode
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: create-skills-and-rules
|
|
7
|
+
|
|
8
|
+
Use this skill when you need to **create or update skills and rules** for Claude Code (`.claude/`), Cursor (`.cursor/`), and OpenCode (`opencode.json`).
|
|
9
|
+
|
|
10
|
+
## Directory structure
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
.claude/
|
|
14
|
+
rules/ *.md – always-loaded project instructions
|
|
15
|
+
skills/ <name>/SKILL.md – step-by-step guides, invocable or auto-matched by description
|
|
16
|
+
.cursor/
|
|
17
|
+
rules/ *.mdc – Cursor rules with description/globs/alwaysApply frontmatter
|
|
18
|
+
skills/ *.md – flat files, same content as .claude/skills/ (Cursor reads them as reference)
|
|
19
|
+
opencode.json – OpenCode config: instructions array points to .claude/rules/*.md
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Cross-tool compatibility:**
|
|
23
|
+
- **Claude Code** reads both flat files and directory-based skills from `.claude/skills/`
|
|
24
|
+
- **OpenCode** reads **only** directory-based skills (`.claude/skills/<name>/SKILL.md`) — flat files are ignored
|
|
25
|
+
- **Cursor** reads flat files from `.cursor/skills/*.md` as reference documents
|
|
26
|
+
- Rules are loaded by OpenCode via `opencode.json` `instructions` array
|
|
27
|
+
|
|
28
|
+
**Standard format:** Always use directory-based skills (`<name>/SKILL.md`) for compatibility with all tools.
|
|
29
|
+
|
|
30
|
+
## Step 1 – Decide: rule or skill
|
|
31
|
+
|
|
32
|
+
| Type | Purpose | When loaded |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| **Rule** | Constraints, conventions, best practices | Automatically, based on `globs` or `alwaysApply` |
|
|
35
|
+
| **Skill** | Step-by-step implementation guide | When description matches task, or invoked via `/skill-name` |
|
|
36
|
+
|
|
37
|
+
Rules say **what to do / not do**. Skills say **how to do it step by step**.
|
|
38
|
+
|
|
39
|
+
## Step 2 – Create a skill (`.claude/skills/<name>/SKILL.md`)
|
|
40
|
+
|
|
41
|
+
### Create the directory and file
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
mkdir -p .claude/skills/my-skill-name
|
|
45
|
+
# then create .claude/skills/my-skill-name/SKILL.md
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Required frontmatter
|
|
49
|
+
|
|
50
|
+
```yaml
|
|
51
|
+
---
|
|
52
|
+
name: my-skill-name
|
|
53
|
+
description: Short description of what this skill does and when to use it
|
|
54
|
+
---
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
| Field | Required by | Description |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `name` | OpenCode, Claude Code | Must match directory name exactly. Lowercase, hyphens only, regex `^[a-z0-9]+(-[a-z0-9]+)*$`, max 64 chars |
|
|
60
|
+
| `description` | All tools | When to use — Claude/OpenCode match this to decide auto-invocation |
|
|
61
|
+
|
|
62
|
+
### Optional frontmatter fields (Claude Code only)
|
|
63
|
+
|
|
64
|
+
```yaml
|
|
65
|
+
---
|
|
66
|
+
name: my-skill
|
|
67
|
+
description: What this skill does
|
|
68
|
+
user-invocable: true
|
|
69
|
+
disable-model-invocation: false
|
|
70
|
+
allowed-tools: Read, Grep, Bash
|
|
71
|
+
context: fork
|
|
72
|
+
agent: Explore
|
|
73
|
+
argument-hint: [filename]
|
|
74
|
+
model: sonnet
|
|
75
|
+
---
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
| Field | Default | Description |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| `user-invocable` | `true` | Show in `/` menu |
|
|
81
|
+
| `disable-model-invocation` | `false` | Prevent auto-loading by Claude |
|
|
82
|
+
| `allowed-tools` | – | Tools allowed without asking (e.g. `Read, Grep, Bash`) |
|
|
83
|
+
| `context` | – | Set to `fork` to run in isolated subagent |
|
|
84
|
+
| `agent` | – | Subagent type when `context: fork` (`Explore`, `Plan`, etc.) |
|
|
85
|
+
| `argument-hint` | – | Hint for autocomplete (e.g. `[issue-number]`) |
|
|
86
|
+
| `model` | inherited | Model override (`sonnet`, `opus`, `haiku`) |
|
|
87
|
+
|
|
88
|
+
OpenCode ignores unknown frontmatter fields, so these are safe to include.
|
|
89
|
+
|
|
90
|
+
### Dynamic substitutions in skill content
|
|
91
|
+
|
|
92
|
+
- `$ARGUMENTS` – all arguments passed when invoking
|
|
93
|
+
- `$ARGUMENTS[0]`, `$1` – specific argument by index
|
|
94
|
+
- `${CLAUDE_SESSION_ID}` – current session ID
|
|
95
|
+
- `${CLAUDE_SKILL_DIR}` – directory containing SKILL.md
|
|
96
|
+
|
|
97
|
+
### Body structure
|
|
98
|
+
|
|
99
|
+
```markdown
|
|
100
|
+
---
|
|
101
|
+
name: my-skill-name
|
|
102
|
+
description: Build X with Y and Z
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
# Skill: my-skill-name
|
|
106
|
+
|
|
107
|
+
Use this skill when you build **X** using Y and Z.
|
|
108
|
+
|
|
109
|
+
## When to use
|
|
110
|
+
|
|
111
|
+
- Bullet list of scenarios
|
|
112
|
+
|
|
113
|
+
## Step 1 – First step
|
|
114
|
+
|
|
115
|
+
Explanation + code example.
|
|
116
|
+
|
|
117
|
+
## Step 2 – Second step
|
|
118
|
+
|
|
119
|
+
Explanation + code example.
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Keep skills under 500 lines. Use clear step numbering. Include real code examples.
|
|
123
|
+
|
|
124
|
+
## Step 3 – Create a Claude Code rule (`.claude/rules/<name>.md`)
|
|
125
|
+
|
|
126
|
+
### Frontmatter
|
|
127
|
+
|
|
128
|
+
```yaml
|
|
129
|
+
---
|
|
130
|
+
description: Short description of this rule's purpose
|
|
131
|
+
globs: **/front/src/**/*.{vue,js,ts}
|
|
132
|
+
---
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
| Field | Description |
|
|
136
|
+
|---|---|
|
|
137
|
+
| `description` | What this rule covers (used for matching) |
|
|
138
|
+
| `globs` | File patterns that trigger this rule (e.g. `**/*.js`, `**/services/**/*.js`) |
|
|
139
|
+
|
|
140
|
+
### Body structure
|
|
141
|
+
|
|
142
|
+
```markdown
|
|
143
|
+
---
|
|
144
|
+
description: Rules for X development
|
|
145
|
+
globs: **/*.js
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
# Title
|
|
149
|
+
|
|
150
|
+
## Section 1
|
|
151
|
+
|
|
152
|
+
- Rule bullet points
|
|
153
|
+
- Code examples
|
|
154
|
+
|
|
155
|
+
## Section 2
|
|
156
|
+
|
|
157
|
+
- More rules
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Rules are concise. Lead with the constraint, then show a code example.
|
|
161
|
+
|
|
162
|
+
## Step 4 – Create Cursor rule (`.cursor/rules/<name>.mdc`)
|
|
163
|
+
|
|
164
|
+
Same content as the Claude rule, but with Cursor-specific frontmatter:
|
|
165
|
+
|
|
166
|
+
```yaml
|
|
167
|
+
---
|
|
168
|
+
description: Short description of this rule's purpose
|
|
169
|
+
globs: **/front/src/**/*.{vue,js,ts}
|
|
170
|
+
alwaysApply: false
|
|
171
|
+
---
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
| Field | Default | Description |
|
|
175
|
+
|---|---|---|
|
|
176
|
+
| `description` | – | What the rule does; Cursor uses this to decide when to apply |
|
|
177
|
+
| `globs` | – | Comma-separated file patterns (e.g. `**/*.tsx, src/**/*.js`) |
|
|
178
|
+
| `alwaysApply` | `false` | If `true`, applies to every session regardless of context |
|
|
179
|
+
|
|
180
|
+
**Notes:**
|
|
181
|
+
- Do NOT quote glob patterns in frontmatter
|
|
182
|
+
- Keep rules short (target 25 lines, max 50 lines for best Cursor performance)
|
|
183
|
+
- The `.mdc` extension is required for Cursor
|
|
184
|
+
|
|
185
|
+
## Step 5 – Register rules in OpenCode (`opencode.json`)
|
|
186
|
+
|
|
187
|
+
OpenCode reads `.claude/skills/<name>/SKILL.md` natively for skills (no extra step needed). But rules must be registered in `opencode.json`:
|
|
188
|
+
|
|
189
|
+
```json
|
|
190
|
+
{
|
|
191
|
+
"$schema": "https://opencode.ai/config.json",
|
|
192
|
+
"instructions": [
|
|
193
|
+
".claude/rules/live-change-backend-architecture.md",
|
|
194
|
+
".claude/rules/live-change-frontend-vue-primevue.md"
|
|
195
|
+
]
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
When you **create a new rule**, add its path to the `instructions` array in `opencode.json`.
|
|
200
|
+
|
|
201
|
+
When you **create a new skill**, no `opencode.json` change is needed — OpenCode discovers skills from `.claude/skills/<name>/SKILL.md` automatically.
|
|
202
|
+
|
|
203
|
+
**Important:** OpenCode ignores the `globs` frontmatter from Claude Code rules. All instructions listed in `opencode.json` are always loaded.
|
|
204
|
+
|
|
205
|
+
## Step 6 – Mirror Cursor skills (`.cursor/skills/<name>.md`)
|
|
206
|
+
|
|
207
|
+
Copy the skill content to `.cursor/skills/` as a **flat file** (Cursor reads flat `.md` files as reference):
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
cp .claude/skills/my-skill-name/SKILL.md .cursor/skills/my-skill-name.md
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
The content is the same (including `name` in frontmatter). Cursor ignores unknown frontmatter fields.
|
|
214
|
+
|
|
215
|
+
## Step 7 – Mirror to sub-projects
|
|
216
|
+
|
|
217
|
+
If the project has sub-projects with their own `.claude/` and `.cursor/` directories, copy the files there too:
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
for dir in automation auto-firma; do
|
|
221
|
+
# Skills: directory format for .claude, flat for .cursor
|
|
222
|
+
mkdir -p "$dir/.claude/skills/my-skill-name"
|
|
223
|
+
cp .claude/skills/my-skill-name/SKILL.md "$dir/.claude/skills/my-skill-name/SKILL.md"
|
|
224
|
+
cp .claude/skills/my-skill-name/SKILL.md "$dir/.cursor/skills/my-skill-name.md"
|
|
225
|
+
|
|
226
|
+
# Rules
|
|
227
|
+
cp .claude/rules/my-rule.md "$dir/.claude/rules/"
|
|
228
|
+
cp .cursor/rules/my-rule.mdc "$dir/.cursor/rules/"
|
|
229
|
+
done
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Naming conventions
|
|
233
|
+
|
|
234
|
+
- Use lowercase with hyphens: `live-change-frontend-editor-form`
|
|
235
|
+
- Must match regex `^[a-z0-9]+(-[a-z0-9]+)*$` (OpenCode requirement)
|
|
236
|
+
- Prefix with domain: `live-change-frontend-*`, `live-change-backend-*`
|
|
237
|
+
- Skills describe actions: `*-editor-form`, `*-range-list`, `*-action-buttons`
|
|
238
|
+
- Rules describe scope: `*-vue-primevue`, `*-models-and-relations`
|
|
239
|
+
|
|
240
|
+
## Checklist
|
|
241
|
+
|
|
242
|
+
- [ ] Directory created: `.claude/skills/<name>/SKILL.md`
|
|
243
|
+
- [ ] Frontmatter has both `name` (matching dir) and `description`
|
|
244
|
+
- [ ] `.cursor/skills/<name>.md` mirrored (flat file, same content)
|
|
245
|
+
- [ ] `.claude/rules/*.md` created (if rule)
|
|
246
|
+
- [ ] `.cursor/rules/*.mdc` created with `globs` + `alwaysApply` (if rule)
|
|
247
|
+
- [ ] `opencode.json` `instructions` array updated (if new rule)
|
|
248
|
+
- [ ] Sub-projects updated (automation, auto-firma)
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: live-change-backend-change-triggers
|
|
3
|
+
description: React to model changes with automatic change triggers from the relations plugin
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: live-change-backend-change-triggers (Claude Code)
|
|
7
|
+
|
|
8
|
+
Use this skill when you need to **react to model changes** — run logic when a record is created, updated, or deleted.
|
|
9
|
+
|
|
10
|
+
## When to use
|
|
11
|
+
|
|
12
|
+
- You need to keep derived data in sync when a model changes (e.g. create/cancel a timer when a schedule is created/updated/deleted).
|
|
13
|
+
- You want to initialize related resources when a model is created.
|
|
14
|
+
- You need cross-service reactions to model lifecycle events.
|
|
15
|
+
- You want custom cleanup logic on delete.
|
|
16
|
+
|
|
17
|
+
## How it works
|
|
18
|
+
|
|
19
|
+
The relations plugin automatically fires change triggers for every model that uses relations (`propertyOf`, `itemOf`, `userItem`, `propertyOfAny`, etc.). You just define a trigger with the matching name.
|
|
20
|
+
|
|
21
|
+
## Step 1 – Understand the naming convention
|
|
22
|
+
|
|
23
|
+
For a model `MyModel` in service `myService`, these triggers are fired automatically:
|
|
24
|
+
|
|
25
|
+
| Trigger name | When |
|
|
26
|
+
|---|---|
|
|
27
|
+
| `createMyService_MyModel` | On create |
|
|
28
|
+
| `updateMyService_MyModel` | On update |
|
|
29
|
+
| `deleteMyService_MyModel` | On delete |
|
|
30
|
+
| `changeMyService_MyModel` | On any change |
|
|
31
|
+
| `createObject` / `updateObject` / `deleteObject` / `changeObject` | Generic (all models) |
|
|
32
|
+
|
|
33
|
+
The pattern is: `{changeType}{ServiceName}_{ModelName}` where service name is capitalized.
|
|
34
|
+
|
|
35
|
+
## Step 2 – Define a change trigger (recommended: use `change*`)
|
|
36
|
+
|
|
37
|
+
The `change*` variant covers all cases. Check `data` and `oldData` to distinguish create/update/delete:
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
definition.trigger({
|
|
41
|
+
name: 'changeMyService_MyModel',
|
|
42
|
+
properties: {
|
|
43
|
+
object: {
|
|
44
|
+
type: MyModel,
|
|
45
|
+
validation: ['nonEmpty'],
|
|
46
|
+
},
|
|
47
|
+
data: {
|
|
48
|
+
type: Object,
|
|
49
|
+
},
|
|
50
|
+
oldData: {
|
|
51
|
+
type: Object,
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
async execute({ object, data, oldData }, { service, trigger, triggerService }, emit) {
|
|
55
|
+
if(oldData) {
|
|
56
|
+
// Updated or deleted — clean up old state
|
|
57
|
+
}
|
|
58
|
+
if(data) {
|
|
59
|
+
// Created or updated — set up new state
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
How to distinguish:
|
|
66
|
+
|
|
67
|
+
| `oldData` | `data` | Meaning |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| `null` | `{...}` | Created |
|
|
70
|
+
| `{...}` | `{...}` | Updated |
|
|
71
|
+
| `{...}` | `null` | Deleted |
|
|
72
|
+
|
|
73
|
+
## Step 3 – Real example: cron-service reacting to Schedule changes
|
|
74
|
+
|
|
75
|
+
The cron-service uses `changeCron_Schedule` to automatically manage timers when schedules are created, updated, or deleted:
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
// Source: live-change-stack/services/cron-service/schedule.js
|
|
79
|
+
|
|
80
|
+
definition.trigger({
|
|
81
|
+
name: 'changeCron_Schedule',
|
|
82
|
+
properties: {
|
|
83
|
+
object: {
|
|
84
|
+
type: Schedule,
|
|
85
|
+
validation: ['nonEmpty'],
|
|
86
|
+
},
|
|
87
|
+
data: {
|
|
88
|
+
type: Object,
|
|
89
|
+
},
|
|
90
|
+
oldData: {
|
|
91
|
+
type: Object,
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
execute: async ({ object, data, oldData }, { service, trigger, triggerService }, emit) => {
|
|
95
|
+
if(oldData) {
|
|
96
|
+
// Cancel old timer on update or delete
|
|
97
|
+
await triggerService({
|
|
98
|
+
service: 'timer',
|
|
99
|
+
type: 'cancelTimerIfExists',
|
|
100
|
+
}, {
|
|
101
|
+
timer: 'cron_Schedule_' + object
|
|
102
|
+
})
|
|
103
|
+
await ScheduleInfo.delete(object)
|
|
104
|
+
}
|
|
105
|
+
if(data) {
|
|
106
|
+
// Create new timer on create or update
|
|
107
|
+
await processSchedule({ id: object, ...data }, { triggerService })
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
This means: when a user creates a Schedule via the UI or API, the timer is automatically set up. When they update it, the old timer is canceled and a new one created. When they delete it, the timer is canceled.
|
|
114
|
+
|
|
115
|
+
## Step 4 – Specific lifecycle triggers (alternative)
|
|
116
|
+
|
|
117
|
+
If you only care about one lifecycle event, use the specific variant:
|
|
118
|
+
|
|
119
|
+
```javascript
|
|
120
|
+
// React only to creation
|
|
121
|
+
definition.trigger({
|
|
122
|
+
name: 'createBilling_Billing',
|
|
123
|
+
properties: {
|
|
124
|
+
object: { type: Billing }
|
|
125
|
+
},
|
|
126
|
+
async execute({ object }, { triggerService }, emit) {
|
|
127
|
+
// Initialize balance when billing is created
|
|
128
|
+
const existingBalance = await app.serviceViewGet('balance', 'balance', {
|
|
129
|
+
ownerType: 'billing_Billing', owner: object
|
|
130
|
+
})
|
|
131
|
+
if(!existingBalance) {
|
|
132
|
+
await triggerService({
|
|
133
|
+
service: 'balance',
|
|
134
|
+
type: 'balance_setOrUpdateBalance',
|
|
135
|
+
}, { ownerType: 'billing_Billing', owner: object })
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Step 5 – Full trigger parameters
|
|
142
|
+
|
|
143
|
+
All change triggers receive:
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
{
|
|
147
|
+
objectType, // e.g. 'cron_Schedule' (service_Model)
|
|
148
|
+
object, // record ID
|
|
149
|
+
identifiers, // parent identifiers from the model's relations
|
|
150
|
+
data, // new data (null on delete)
|
|
151
|
+
oldData, // old data (null on create)
|
|
152
|
+
changeType // 'create', 'update', or 'delete'
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The `identifiers` object contains the parent references defined in the model's relations (e.g. for `itemOf: { what: Device }`, identifiers would include `{ device: '...' }`).
|
|
157
|
+
|
|
158
|
+
## Step 6 – Cross-service triggers
|
|
159
|
+
|
|
160
|
+
Change triggers work across services. Define the trigger in any service — the framework routes it by name:
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
// In serviceA, react to changes in serviceB's Model
|
|
164
|
+
const SomeModel = definition.foreignModel('serviceB', 'SomeModel')
|
|
165
|
+
|
|
166
|
+
definition.trigger({
|
|
167
|
+
name: 'changeServiceB_SomeModel',
|
|
168
|
+
properties: {
|
|
169
|
+
object: { type: SomeModel },
|
|
170
|
+
data: { type: Object },
|
|
171
|
+
oldData: { type: Object }
|
|
172
|
+
},
|
|
173
|
+
async execute({ object, data, oldData }, { triggerService }) {
|
|
174
|
+
// React to changes in SomeModel from serviceB
|
|
175
|
+
}
|
|
176
|
+
})
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Common patterns
|
|
180
|
+
|
|
181
|
+
| Pattern | Trigger to use | Example |
|
|
182
|
+
|---|---|---|
|
|
183
|
+
| Keep derived data in sync | `changeSvc_Model` | Cron: cancel/create timers on schedule change |
|
|
184
|
+
| Initialize on creation | `createSvc_Model` | Billing: create balance when billing created |
|
|
185
|
+
| Custom cleanup on delete | `deleteSvc_Model` | Custom: archive or notify before deletion |
|
|
186
|
+
| React to any model change | `changeObject` | Audit: log all changes across all models |
|