@live-change/frontend-template 0.9.198 → 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.
Files changed (71) hide show
  1. package/.claude/rules/live-change-backend-actions-views-triggers.md +246 -0
  2. package/.claude/rules/live-change-backend-architecture.md +126 -0
  3. package/.claude/rules/live-change-backend-event-sourcing.md +186 -0
  4. package/.claude/rules/live-change-backend-models-and-relations.md +260 -0
  5. package/.claude/rules/live-change-frontend-vue-primevue.md +317 -0
  6. package/.claude/rules/live-change-service-structure.md +89 -0
  7. package/.claude/settings.json +32 -0
  8. package/.claude/skills/create-skills-and-rules/SKILL.md +248 -0
  9. package/.claude/skills/create-skills-and-rules.md +196 -0
  10. package/.claude/skills/live-change-backend-change-triggers/SKILL.md +186 -0
  11. package/.claude/skills/live-change-design-actions-views-triggers/SKILL.md +462 -0
  12. package/.claude/skills/live-change-design-actions-views-triggers.md +190 -0
  13. package/.claude/skills/live-change-design-models-relations/SKILL.md +230 -0
  14. package/.claude/skills/live-change-design-models-relations.md +173 -0
  15. package/.claude/skills/live-change-design-service/SKILL.md +133 -0
  16. package/.claude/skills/live-change-design-service.md +132 -0
  17. package/.claude/skills/live-change-frontend-accessible-objects/SKILL.md +384 -0
  18. package/.claude/skills/live-change-frontend-accessible-objects.md +383 -0
  19. package/.claude/skills/live-change-frontend-action-buttons/SKILL.md +129 -0
  20. package/.claude/skills/live-change-frontend-action-buttons.md +128 -0
  21. package/.claude/skills/live-change-frontend-action-form/SKILL.md +149 -0
  22. package/.claude/skills/live-change-frontend-action-form.md +143 -0
  23. package/.claude/skills/live-change-frontend-analytics/SKILL.md +147 -0
  24. package/.claude/skills/live-change-frontend-analytics.md +146 -0
  25. package/.claude/skills/live-change-frontend-command-forms/SKILL.md +216 -0
  26. package/.claude/skills/live-change-frontend-command-forms.md +215 -0
  27. package/.claude/skills/live-change-frontend-data-views/SKILL.md +183 -0
  28. package/.claude/skills/live-change-frontend-data-views.md +182 -0
  29. package/.claude/skills/live-change-frontend-editor-form/SKILL.md +240 -0
  30. package/.claude/skills/live-change-frontend-editor-form.md +177 -0
  31. package/.claude/skills/live-change-frontend-locale-time/SKILL.md +172 -0
  32. package/.claude/skills/live-change-frontend-locale-time.md +171 -0
  33. package/.claude/skills/live-change-frontend-page-list-detail/SKILL.md +201 -0
  34. package/.claude/skills/live-change-frontend-page-list-detail.md +200 -0
  35. package/.claude/skills/live-change-frontend-range-list/SKILL.md +129 -0
  36. package/.claude/skills/live-change-frontend-range-list.md +128 -0
  37. package/.claude/skills/live-change-frontend-ssr-setup/SKILL.md +119 -0
  38. package/.claude/skills/live-change-frontend-ssr-setup.md +118 -0
  39. package/.cursor/rules/live-change-backend-actions-views-triggers.mdc +290 -0
  40. package/.cursor/rules/live-change-backend-architecture.mdc +131 -0
  41. package/.cursor/rules/live-change-backend-event-sourcing.mdc +185 -0
  42. package/.cursor/rules/live-change-backend-models-and-relations.mdc +256 -0
  43. package/.cursor/rules/live-change-frontend-vue-primevue.mdc +290 -0
  44. package/.cursor/rules/live-change-service-structure.mdc +107 -0
  45. package/.cursor/skills/create-skills-and-rules.md +248 -0
  46. package/.cursor/skills/live-change-backend-change-triggers.md +186 -0
  47. package/.cursor/skills/live-change-design-actions-views-triggers.md +296 -0
  48. package/.cursor/skills/live-change-design-models-relations.md +230 -0
  49. package/.cursor/skills/live-change-design-service.md +76 -0
  50. package/.cursor/skills/live-change-frontend-accessible-objects.md +384 -0
  51. package/.cursor/skills/live-change-frontend-action-buttons.md +129 -0
  52. package/.cursor/skills/live-change-frontend-action-form.md +149 -0
  53. package/.cursor/skills/live-change-frontend-analytics.md +147 -0
  54. package/.cursor/skills/live-change-frontend-command-forms.md +216 -0
  55. package/.cursor/skills/live-change-frontend-data-views.md +183 -0
  56. package/.cursor/skills/live-change-frontend-editor-form.md +240 -0
  57. package/.cursor/skills/live-change-frontend-locale-time.md +172 -0
  58. package/.cursor/skills/live-change-frontend-page-list-detail.md +201 -0
  59. package/.cursor/skills/live-change-frontend-range-list.md +129 -0
  60. package/.cursor/skills/live-change-frontend-ssr-setup.md +120 -0
  61. package/README.md +71 -0
  62. package/front/src/router.js +2 -1
  63. package/opencode.json +10 -0
  64. package/package.json +52 -50
  65. package/server/app.config.js +35 -0
  66. package/server/services.list.js +2 -0
  67. package/.nx/workspace-data/file-map.json +0 -195
  68. package/.nx/workspace-data/nx_files.nxt +0 -0
  69. package/.nx/workspace-data/project-graph.json +0 -8
  70. package/.nx/workspace-data/project-graph.lock +0 -0
  71. package/.nx/workspace-data/source-maps.json +0 -1
@@ -0,0 +1,246 @@
1
+ ---
2
+ description: Rules for implementing actions, views, and triggers in LiveChange services
3
+ globs: **/services/**/*.js
4
+ ---
5
+
6
+ # LiveChange backend – actions, views, triggers (Claude Code)
7
+
8
+ Use these rules when implementing actions, views, and triggers in LiveChange services.
9
+
10
+ ## Actions
11
+
12
+ - Place actions in domain files, near the models they operate on.
13
+ - Each action should:
14
+ - declare clear input `properties`,
15
+ - perform minimal but necessary validation,
16
+ - use indexes instead of full scans,
17
+ - return a meaningful result (ids, data, etc.).
18
+
19
+ Example:
20
+
21
+ ```js
22
+ definition.action({
23
+ name: 'registerDeviceConnection',
24
+ properties: {
25
+ pairingKey: { type: String },
26
+ connectionType: { type: String },
27
+ capabilities: { type: Array }
28
+ },
29
+ async execute({ pairingKey, connectionType, capabilities }, { client, service }) {
30
+ const device = await Device.indexObjectGet('byPairingKey', { pairingKey })
31
+ if(!device) throw new Error('notFound')
32
+
33
+ const id = app.generateUid()
34
+ const sessionKey = app.generateUid() + app.generateUid()
35
+
36
+ await DeviceConnection.create({
37
+ id,
38
+ device: device.id,
39
+ connectionType,
40
+ capabilities,
41
+ sessionKey,
42
+ status: 'offline'
43
+ })
44
+
45
+ return { connectionId: id, sessionKey }
46
+ }
47
+ })
48
+ ```
49
+
50
+ ## Views
51
+
52
+ - Views should be simple query endpoints over models.
53
+ - Prefer `indexObjectGet` / `indexRangeGet` instead of scanning whole tables.
54
+
55
+ Example of a range view:
56
+
57
+ ```js
58
+ definition.view({
59
+ name: 'pendingCommands',
60
+ properties: {
61
+ connectionId: { type: String }
62
+ },
63
+ async get({ connectionId }, { client, service }) {
64
+ return BotCommand.indexRangeGet('byConnectionAndStatus', {
65
+ connection: connectionId,
66
+ status: 'pending'
67
+ })
68
+ }
69
+ })
70
+ ```
71
+
72
+ ## Triggers – online/offline and batch updates
73
+
74
+ - Use triggers for reacting to events (e.g. connection online/offline, server startup).
75
+ - There are two typical patterns:
76
+ - single-object triggers (update one record),
77
+ - batch triggers (update many records safely).
78
+
79
+ Online/offline example:
80
+
81
+ ```js
82
+ definition.trigger({
83
+ name: 'sessionDeviceConnectionOnline',
84
+ properties: {
85
+ connection: { type: String }
86
+ },
87
+ async execute({ connection }, { service }) {
88
+ await DeviceConnection.update(connection, {
89
+ status: 'online',
90
+ lastSeenAt: new Date()
91
+ })
92
+ }
93
+ })
94
+
95
+ definition.trigger({
96
+ name: 'sessionDeviceConnectionOffline',
97
+ properties: {
98
+ connection: { type: String }
99
+ },
100
+ async execute({ connection }, { service }) {
101
+ await DeviceConnection.update(connection, {
102
+ status: 'offline'
103
+ })
104
+ }
105
+ })
106
+ ```
107
+
108
+ ### Batch processing – always paginate
109
+
110
+ - Never load “all rows” in one go.
111
+ - Use a fixed `limit` (e.g. 32 or 128) and a cursor (`gt: lastId`) in a loop.
112
+
113
+ ```js
114
+ definition.trigger({
115
+ name: 'allOffline',
116
+ async execute({}, { service }) {
117
+ let last = ''
118
+ while(true) {
119
+ const connections = await DeviceConnection.rangeGet({
120
+ gt: last,
121
+ limit: 32
122
+ })
123
+ if(connections.length === 0) break
124
+
125
+ for(const conn of connections) {
126
+ await DeviceConnection.update(conn.id, { status: 'offline' })
127
+ }
128
+
129
+ last = connections[connections.length - 1].id
130
+ }
131
+ }
132
+ })
133
+ ```
134
+
135
+ ## Change triggers – reacting to model changes
136
+
137
+ Models with relations (`propertyOf`, `itemOf`, `userItem`, etc.) automatically fire change triggers on every create/update/delete. The naming convention is `{changeType}{ServiceName}_{ModelName}`:
138
+
139
+ - `changeSvc_Model` — fires on any change (recommended, covers all cases)
140
+ - `createSvc_Model` / `updateSvc_Model` / `deleteSvc_Model` — specific lifecycle
141
+
142
+ Parameters: `{ objectType, object, identifiers, data, oldData, changeType }`.
143
+
144
+ ```js
145
+ // React to any change in Schedule model from cron service
146
+ definition.trigger({
147
+ name: 'changeCron_Schedule',
148
+ properties: {
149
+ object: { type: Schedule, validation: ['nonEmpty'] },
150
+ data: { type: Object },
151
+ oldData: { type: Object }
152
+ },
153
+ async execute({ object, data, oldData }, { triggerService }) {
154
+ if(oldData) { /* cleanup old state */ }
155
+ if(data) { /* setup new state */ }
156
+ }
157
+ })
158
+ ```
159
+
160
+ Check `data`/`oldData`: both present = update, only `data` = create, only `oldData` = delete.
161
+
162
+ ## Granting access on object creation
163
+
164
+ When a model uses `entity` with `writeAccessControl` / `readAccessControl`, the auto-generated CRUD checks roles but does **not** grant them automatically. The creator must be explicitly granted roles — typically `'owner'` — otherwise they cannot access their own object.
165
+
166
+ Use a change trigger that fires on creation (`data` exists, `oldData` does not):
167
+
168
+ ```js
169
+ definition.trigger({
170
+ name: 'changeMyService_MyModel',
171
+ properties: {
172
+ object: { type: MyModel, validation: ['nonEmpty'] },
173
+ data: { type: Object },
174
+ oldData: { type: Object }
175
+ },
176
+ async execute({ object, data, oldData }, { client, triggerService }) {
177
+ if (!data || oldData) return // only on create
178
+ if (!client?.user) return
179
+
180
+ await triggerService({ service: 'accessControl', type: 'accessControl_setAccess' }, {
181
+ objectType: 'myService_MyModel',
182
+ object,
183
+ roles: ['owner'],
184
+ sessionOrUserType: 'user_User',
185
+ sessionOrUser: client.user,
186
+ lastUpdate: new Date()
187
+ })
188
+ }
189
+ })
190
+ ```
191
+
192
+ Key details:
193
+ - `objectType` format: `serviceName_ModelName` (e.g. `company_Company`)
194
+ - For public access (readable by everyone), use `accessControl_setPublicAccess` with `userRoles` / `sessionRoles`
195
+ - For anonymous users, use `sessionOrUserType: 'session_Session'` and `sessionOrUser: client.session`
196
+
197
+ ## Pending + resolve pattern (async result)
198
+
199
+ Use this pattern when an action must wait for a result from an external process (device, worker, etc.).
200
+
201
+ 1. The main action creates a “pending” record and calls `waitForCommand(id, timeoutMs)`.
202
+ 2. Another action or trigger updates the record and calls `resolveCommand(id, result)`.
203
+
204
+ In-memory helper:
205
+
206
+ ```js
207
+ const pendingCommands = new Map()
208
+
209
+ export function waitForCommand(commandId, timeoutMs = 115000) {
210
+ return new Promise((resolve, reject) => {
211
+ const timer = setTimeout(() => {
212
+ pendingCommands.delete(commandId)
213
+ reject(new Error('timeout'))
214
+ }, timeoutMs)
215
+ pendingCommands.set(commandId, { resolve, reject, timer })
216
+ })
217
+ }
218
+
219
+ export function resolveCommand(commandId, result) {
220
+ const pending = pendingCommands.get(commandId)
221
+ if(pending) {
222
+ clearTimeout(pending.timer)
223
+ pendingCommands.delete(commandId)
224
+ pending.resolve(result)
225
+ }
226
+ }
227
+ ```
228
+
229
+ Usage in an action:
230
+
231
+ ```js
232
+ await SomeCommand.create({ id, status: 'pending', ... })
233
+ const result = await waitForCommand(id)
234
+ return result
235
+ ```
236
+
237
+ And in a reporting action:
238
+
239
+ ```js
240
+ await SomeCommand.update(id, {
241
+ status: 'completed',
242
+ result
243
+ })
244
+ resolveCommand(id, result)
245
+ ```
246
+
@@ -0,0 +1,126 @@
1
+ ---
2
+ description: Rules for LiveChange backend service architecture and directory structure
3
+ globs: **/services/**/*.js
4
+ ---
5
+
6
+ # LiveChange backend – service architecture (Claude Code)
7
+
8
+ Use these rules whenever you work on backend services in this repo or other live-change-stack projects.
9
+
10
+ ## Service must be a directory
11
+
12
+ - Each LiveChange service **must be a directory**, not a single file.
13
+ - Use a consistent structure:
14
+
15
+ ```
16
+ server/services/<serviceName>/
17
+ definition.js # createServiceDefinition + use – no models/actions here
18
+ index.js # imports definition + all domain files, exports definition
19
+ config.js # optional – reads definition.config and exports plain object
20
+ <domain>.js # domain files: models, views, actions, triggers
21
+ ```
22
+
23
+ ## `definition.js`
24
+
25
+ - Import `app` from `@live-change/framework`.
26
+ - Create the service definition **without** registering models/actions/views there.
27
+ - If the service uses relations or access control, **always** add the plugins in `use`.
28
+
29
+ ```js
30
+ import { app } from '@live-change/framework'
31
+ import relationsPlugin from '@live-change/relations-plugin'
32
+ import accessControlService from '@live-change/access-control-service'
33
+
34
+ const definition = app.createServiceDefinition({
35
+ name: 'myService',
36
+ use: [relationsPlugin, accessControlService]
37
+ })
38
+
39
+ export default definition
40
+ ```
41
+
42
+ ## `index.js`
43
+
44
+ - Import `definition`.
45
+ - Import all domain files (models/views/actions/triggers) as side-effect imports.
46
+ - Re-export `definition` as default.
47
+
48
+ ```js
49
+ import definition from './definition.js'
50
+
51
+ import './modelA.js'
52
+ import './modelB.js'
53
+ import './authenticator.js'
54
+
55
+ export default definition
56
+ ```
57
+
58
+ ## `config.js` (optional)
59
+
60
+ - Read `definition.config` (set in `app.config.js`).
61
+ - Apply defaults and export a plain object.
62
+
63
+ ```js
64
+ import definition from './definition.js'
65
+
66
+ const {
67
+ someOption = 'default'
68
+ } = definition.config
69
+
70
+ export default { someOption }
71
+ ```
72
+
73
+ ## Registering the service in the app
74
+
75
+ ### `services.list.js`
76
+
77
+ - Always import the service from its `index.js`:
78
+
79
+ ```js
80
+ import myService from './services/myService/index.js'
81
+
82
+ export default {
83
+ // ...
84
+ myService
85
+ }
86
+ ```
87
+
88
+ ### `app.config.js`
89
+
90
+ - Make sure `services: [{ name }]` matches the name in `createServiceDefinition`.
91
+ - Keep a **sensible order** of services:
92
+ - core/common services and plugins first,
93
+ - application-specific services last (they can depend on earlier ones).
94
+
95
+ ```js
96
+ services: [
97
+ { name: 'user' },
98
+ { name: 'session' },
99
+ { name: 'accessControl' },
100
+ // ...
101
+ { name: 'myService' }
102
+ ]
103
+ ```
104
+
105
+ ## Inspecting services with `describe`
106
+
107
+ Use the `describe` CLI command to see what the framework generated from your definitions (models, views, actions, triggers, indexes, events):
108
+
109
+ ```bash
110
+ # All services overview
111
+ node server/start.js describe
112
+
113
+ # One service in YAML (shows all generated code)
114
+ node server/start.js describe --service myService --output yaml
115
+
116
+ # Specific entity
117
+ node server/start.js describe --service myService --model MyModel --output yaml
118
+ ```
119
+
120
+ This is especially useful after using relations (`userItem`, `itemOf`, `propertyOf`) — `describe` shows all the auto-generated views, actions, triggers, and indexes.
121
+
122
+ ## When to create a new service
123
+
124
+ - When you have a clearly separate domain (payments, notifications, devices, etc.).
125
+ - When a group of models/actions/views has its own configuration and dependencies.
126
+ - When adding it to an existing service would mix unrelated responsibilities.
@@ -0,0 +1,186 @@
1
+ ---
2
+ description: Event-sourcing data flow rules — emit events for DB writes, use triggerService for cross-service writes
3
+ globs: **/services/**/*.js
4
+ ---
5
+
6
+ # LiveChange backend – event-sourcing data flow (Claude Code)
7
+
8
+ Use these rules when implementing actions, triggers, and events in LiveChange services.
9
+
10
+ ## How data flows in LiveChange
11
+
12
+ LiveChange uses an event-sourcing pattern:
13
+
14
+ 1. **Actions and triggers** validate input and publish events via `emit()`.
15
+ 2. **Events** (`definition.event()`) execute and perform actual database writes (`Model.create`, `Model.update`, `Model.delete`).
16
+ 3. For models with **relations** (`userItem`, `itemOf`, `propertyOf`, etc.), the relations plugin auto-generates CRUD events and triggers — use those via `triggerService()`.
17
+ 4. For **cross-service writes**, always use `triggerService()` — `foreignModel` is read-only.
18
+
19
+ ```
20
+ Action/Trigger ──emit()──▶ Event handler ──▶ Model.create/update/delete
21
+
22
+ └──triggerService()──▶ Other service's trigger ──emit()──▶ ...
23
+ ```
24
+
25
+ ## Rule 1: No direct Model.create/update/delete in actions or triggers
26
+
27
+ Actions and triggers must **not** call `Model.create()`, `Model.update()`, or `Model.delete()` directly. Instead:
28
+
29
+ - **If the model has relations** (auto-generated CRUD) → use `triggerService()` to call the relation-declared trigger (e.g. `serviceName_createModelName`, `serviceName_updateModelName`, `serviceName_setModelName`).
30
+ - **If no relation trigger exists** → use `emit()` to publish an event, then define a `definition.event()` handler that performs the DB write.
31
+
32
+ ### Correct: emit an event from an action
33
+
34
+ ```js
35
+ definition.action({
36
+ name: 'createImage',
37
+ properties: { /* ... */ },
38
+ waitForEvents: true,
39
+ async execute({ image, name, width, height }, { client, service }, emit) {
40
+ const id = image || app.generateUid()
41
+ // validation, business logic...
42
+ emit({
43
+ type: 'ImageCreated',
44
+ image: id,
45
+ data: { name, width, height }
46
+ })
47
+ return id
48
+ }
49
+ })
50
+
51
+ definition.event({
52
+ name: 'ImageCreated',
53
+ async execute({ image, data }) {
54
+ await Image.create({ ...data, id: image })
55
+ }
56
+ })
57
+ ```
58
+
59
+ ### Correct: use triggerService for relation-declared triggers
60
+
61
+ ```js
62
+ definition.action({
63
+ name: 'giveCard',
64
+ properties: { /* ... */ },
65
+ async execute({ receiverType, receiver }, { client, triggerService }, emit) {
66
+ // Use the auto-generated trigger from the relations plugin
67
+ await triggerService({
68
+ service: definition.name,
69
+ type: 'businessCard_setReceivedCard',
70
+ }, {
71
+ sessionOrUserType: receiverType,
72
+ sessionOrUser: receiver,
73
+ // ...
74
+ })
75
+ }
76
+ })
77
+ ```
78
+
79
+ ### Wrong: direct DB write in an action
80
+
81
+ ```js
82
+ // ❌ DON'T DO THIS
83
+ definition.action({
84
+ name: 'createSomething',
85
+ async execute({ name }, { client }, emit) {
86
+ const id = app.generateUid()
87
+ await Something.create({ id, name }) // ❌ direct write in action
88
+ return id
89
+ }
90
+ })
91
+ ```
92
+
93
+ ## Rule 2: Events can only modify same-service models
94
+
95
+ Event handlers (`definition.event()`) must only write to models defined in the **same service**. Never attempt to write to a `foreignModel` — it is read-only (supports `.get()`, `.indexObjectGet()`, `.indexRangeGet()` but not `.create()`, `.update()`, `.delete()`).
96
+
97
+ For cross-service writes, use `triggerService()` from the action or trigger (before emitting):
98
+
99
+ ```js
100
+ // ✅ Correct: cross-service write via triggerService
101
+ definition.trigger({
102
+ name: 'chargeCollected_billing_TopUp',
103
+ async execute(props, { triggerService }, emit) {
104
+ // Write to another service via its declared trigger
105
+ await triggerService({
106
+ service: 'balance',
107
+ type: 'balance_setOrUpdateBalance',
108
+ }, { ownerType: 'billing_Billing', owner: props.cause })
109
+
110
+ // Write to own service via triggerService (relation-declared trigger)
111
+ await triggerService({
112
+ service: definition.name,
113
+ type: 'billing_updateTopUp'
114
+ }, { topUp: props.cause, state: 'paid' })
115
+ }
116
+ })
117
+ ```
118
+
119
+ ```js
120
+ // ❌ DON'T DO THIS — foreignModel is read-only
121
+ const ExternalModel = definition.foreignModel('otherService', 'SomeModel')
122
+
123
+ definition.event({
124
+ name: 'SomethingHappened',
125
+ async execute({ id }) {
126
+ await ExternalModel.update(id, { status: 'done' }) // ❌ will fail
127
+ }
128
+ })
129
+ ```
130
+
131
+ ## `waitForEvents: true`
132
+
133
+ When an action or trigger emits events and needs them processed before returning a result, set `waitForEvents: true`:
134
+
135
+ ```js
136
+ definition.action({
137
+ name: 'createNotification',
138
+ waitForEvents: true,
139
+ async execute({ message }, { client }, emit) {
140
+ const id = app.generateUid()
141
+ emit({
142
+ type: 'created',
143
+ notification: id,
144
+ data: { message, sessionOrUserType: 'user_User', sessionOrUser: client.user }
145
+ })
146
+ return id // event is processed before this returns
147
+ }
148
+ })
149
+ ```
150
+
151
+ Without `waitForEvents: true`, the action returns immediately and events are processed asynchronously.
152
+
153
+ ## Relation-declared triggers
154
+
155
+ When a model uses relations (`userItem`, `itemOf`, `propertyOf`, etc.), the relations plugin auto-generates CRUD triggers. Use `describe` to discover them:
156
+
157
+ ```bash
158
+ node server/start.js describe --service myService --output yaml
159
+ ```
160
+
161
+ Common auto-generated trigger patterns:
162
+ - `serviceName_createModelName` — create a new record
163
+ - `serviceName_updateModelName` — update an existing record
164
+ - `serviceName_deleteModelName` — delete a record
165
+ - `serviceName_setModelName` — create or update (upsert)
166
+ - `serviceName_setOrUpdateModelName` — set if not exists, update if exists
167
+
168
+ Invoke them via `triggerService()`:
169
+
170
+ ```js
171
+ await triggerService({
172
+ service: 'myService',
173
+ type: 'myService_createMyModel'
174
+ }, {
175
+ // properties matching the model's writeable fields
176
+ })
177
+ ```
178
+
179
+ ## Summary
180
+
181
+ | Where | Can call Model.create/update/delete? | How to change data |
182
+ |---|---|---|
183
+ | `definition.event()` | ✅ Yes (same-service models only) | Direct DB writes |
184
+ | `definition.action()` | ❌ No | `emit()` or `triggerService()` |
185
+ | `definition.trigger()` | ❌ No | `emit()` or `triggerService()` |
186
+ | Cross-service | ❌ Never via foreignModel | `triggerService()` to target service |