@live-change/frontend-template 0.9.199 → 0.9.201

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 (44) hide show
  1. package/.claude/rules/live-change-backend-actions-views-triggers.md +62 -0
  2. package/.claude/rules/live-change-backend-event-sourcing.md +186 -0
  3. package/.claude/rules/live-change-backend-models-and-relations.md +72 -0
  4. package/.claude/rules/live-change-frontend-vue-primevue.md +26 -0
  5. package/.claude/settings.json +32 -0
  6. package/.claude/skills/create-skills-and-rules/SKILL.md +248 -0
  7. package/.claude/skills/live-change-backend-change-triggers/SKILL.md +186 -0
  8. package/.claude/skills/live-change-design-actions-views-triggers/SKILL.md +462 -0
  9. package/.claude/skills/live-change-design-models-relations/SKILL.md +230 -0
  10. package/.claude/skills/live-change-design-service/SKILL.md +133 -0
  11. package/.claude/skills/live-change-frontend-accessible-objects/SKILL.md +384 -0
  12. package/.claude/skills/live-change-frontend-accessible-objects.md +383 -0
  13. package/.claude/skills/live-change-frontend-action-buttons/SKILL.md +129 -0
  14. package/.claude/skills/live-change-frontend-action-form/SKILL.md +149 -0
  15. package/.claude/skills/live-change-frontend-analytics/SKILL.md +147 -0
  16. package/.claude/skills/live-change-frontend-command-forms/SKILL.md +216 -0
  17. package/.claude/skills/live-change-frontend-data-views/SKILL.md +183 -0
  18. package/.claude/skills/live-change-frontend-editor-form/SKILL.md +240 -0
  19. package/.claude/skills/live-change-frontend-locale-time/SKILL.md +172 -0
  20. package/.claude/skills/live-change-frontend-page-list-detail/SKILL.md +201 -0
  21. package/.claude/skills/live-change-frontend-range-list/SKILL.md +129 -0
  22. package/.claude/skills/live-change-frontend-ssr-setup/SKILL.md +119 -0
  23. package/.cursor/rules/live-change-backend-actions-views-triggers.mdc +88 -0
  24. package/.cursor/rules/live-change-backend-event-sourcing.mdc +185 -0
  25. package/.cursor/rules/live-change-backend-models-and-relations.mdc +62 -0
  26. package/.cursor/skills/create-skills-and-rules.md +248 -0
  27. package/.cursor/skills/live-change-backend-change-triggers.md +186 -0
  28. package/.cursor/skills/live-change-design-actions-views-triggers.md +178 -79
  29. package/.cursor/skills/live-change-design-models-relations.md +112 -50
  30. package/.cursor/skills/live-change-design-service.md +1 -0
  31. package/.cursor/skills/live-change-frontend-accessible-objects.md +384 -0
  32. package/.cursor/skills/live-change-frontend-action-buttons.md +1 -0
  33. package/.cursor/skills/live-change-frontend-action-form.md +9 -3
  34. package/.cursor/skills/live-change-frontend-analytics.md +1 -0
  35. package/.cursor/skills/live-change-frontend-command-forms.md +1 -0
  36. package/.cursor/skills/live-change-frontend-data-views.md +1 -0
  37. package/.cursor/skills/live-change-frontend-editor-form.md +135 -72
  38. package/.cursor/skills/live-change-frontend-locale-time.md +1 -0
  39. package/.cursor/skills/live-change-frontend-page-list-detail.md +1 -0
  40. package/.cursor/skills/live-change-frontend-range-list.md +1 -0
  41. package/.cursor/skills/live-change-frontend-ssr-setup.md +1 -0
  42. package/front/src/router.js +2 -1
  43. package/opencode.json +10 -0
  44. package/package.json +52 -50
@@ -1,46 +1,41 @@
1
1
  ---
2
+ name: live-change-design-actions-views-triggers
2
3
  description: Design actions, views, triggers with indexes and batch processing patterns
3
4
  ---
4
5
 
5
- # Skill: live-change-design-actions-views-triggers
6
+ # Skill: live-change-design-actions-views-triggers (Claude Code)
6
7
 
7
- Ten skill opisuje **krok po kroku**, jak projektować akcje, widoki i triggery w serwisach LiveChange, korzystając z indeksów i unikając pełnych skanów.
8
+ Use this skill to design **actions, views, and triggers** in LiveChange services while making good use of indexes and avoiding full-table scans.
8
9
 
9
- ## Kiedy używać
10
+ ## When to use
10
11
 
11
- Użyj tego skilla, gdy:
12
+ - You add or change actions on existing models.
13
+ - You define new views (especially list/range views).
14
+ - You implement triggers (online/offline, batch processing, async result flows).
12
15
 
13
- - dodajesz nowe akcje do istniejących modeli,
14
- - tworzysz widoki (szczególnie zakresowe) dla list,
15
- - implementujesz triggery (online/offline, batchowe przetwarzanie, asynchroniczne wyniki).
16
+ ## Step 1 Design an action
16
17
 
17
- ## 1. Projekt akcji
18
+ 1. **Clarify the goal**:
19
+ - create / update / delete a record,
20
+ - or create a “command” that will be completed later.
21
+ 2. **Define `properties`** clearly:
22
+ - only include what the client must provide,
23
+ - fetch the rest from the database via indexes.
24
+ 3. **Use indexes**, not full scans:
25
+ - `indexObjectGet('bySomething', { ... })` for single-object lookups,
26
+ - `indexRangeGet('bySomething', { ... })` for lists.
27
+ 4. **Return a useful result**:
28
+ - new object id,
29
+ - session keys,
30
+ - any data needed for the next step.
18
31
 
19
- 1. **Określ cel akcji**
20
- - Czy tworzy/aktualizuje/usunie obiekt?
21
- - Czy ma czekać na zewnętrzny wynik (np. urządzenie)?
22
-
23
- 2. **Zdefiniuj `properties`**
24
- - Każde pole opisane wieloliniowo,
25
- - tylko to, co faktycznie potrzebne – resztę pobieraj z bazy na podstawie kluczy.
26
-
27
- 3. **Użyj indeksów do wyszukiwania**
28
- - Zamiast pełnych skanów, korzystaj z `indexObjectGet` / `indexRangeGet`.
29
-
30
- 4. **Zwróć sensowny wynik**
31
- - ID utworzonego obiektu,
32
- - klucze sesyjne, jeśli trzeba je przechować po stronie klienta,
33
- - dane potrzebne do dalszych kroków.
34
-
35
- ### Szkic wzorca
32
+ Example:
36
33
 
37
34
  ```js
38
35
  definition.action({
39
36
  name: 'someAction',
40
37
  properties: {
41
- someKey: {
42
- type: String
43
- }
38
+ someKey: { type: String }
44
39
  },
45
40
  async execute({ someKey }, { client, service }) {
46
41
  const obj = await SomeModel.indexObjectGet('bySomeKey', { someKey })
@@ -49,7 +44,7 @@ definition.action({
49
44
  const id = app.generateUid()
50
45
 
51
46
  await SomeOtherModel.create({
52
- id,
47
+ id
53
48
  // ...
54
49
  })
55
50
 
@@ -58,56 +53,106 @@ definition.action({
58
53
  })
59
54
  ```
60
55
 
61
- ## 2. Projekt widoku
56
+ ## Step 2 Design a view
62
57
 
63
- 1. **Zdecyduj, czy to pojedynczy obiekt czy lista**
64
- - pojedynczy: użyj `get` lub `indexObjectGet`,
65
- - lista: użyj `indexRangeGet` z indeksem.
58
+ 1. Decide what kind of data source you have, then pick the **view variant** (exactly one):
66
59
 
67
- 2. **Zdefiniuj `properties` widoku**
68
- - tylko parametry potrzebne do wyszukiwania,
69
- - typy zgodne z modelem (String/Number/itp.).
60
+ | Variant | When to use |
61
+ | --- | --- |
62
+ | `daoPath` | Data is stored in the framework DAO (preferred). The framework auto-generates both `get` and `observable` from `daoPath`. |
63
+ | `get` + `observable` | External or custom reactive data source (eg. WebSocket client, RPC stream). **Both are required together.** |
64
+ | `fetch` | Remote, non-reactive request/response data (eg. GeoIP). Often paired with `remote: true`. |
70
65
 
71
- 3. **Użyj indeksów**
66
+ 2. Decide if you need:
67
+ - a **single** object view, or
68
+ - a **list/range** view.
69
+ 3. Define `properties` for the view:
70
+ - only parameters needed for filtering,
71
+ - types consistent with model fields.
72
+ 4. Prefer `daoPath` when you are reading from the DAO:
73
+ - use model paths (`Model.path`, `Model.rangePath`, `Model.sortedIndexRangePath`, `Model.indexObjectPath`)
74
+ - use `...App.rangeProperties` + `App.extractRange(props)` for range views
72
75
 
73
- Przykład widoku zakresowego:
76
+ ### Example: `daoPath` (preferred, DAO-backed)
74
77
 
75
78
  ```js
76
79
  definition.view({
77
- name: 'myItemsByStatus',
80
+ name: 'costInvoice',
78
81
  properties: {
79
- status: {
82
+ costInvoice: {
80
83
  type: String
81
84
  }
82
85
  },
83
- async get({ status }, { client, service }) {
84
- return MyModel.indexRangeGet('byStatus', {
85
- status
86
- })
86
+ returns: { type: Object },
87
+ async daoPath({ costInvoice }) {
88
+ return CostInvoice.path(costInvoice)
89
+ }
90
+ })
91
+ ```
92
+
93
+ ### Example: `get` + `observable` together (external / reactive)
94
+
95
+ ```js
96
+ definition.view({
97
+ name: 'session',
98
+ properties: {},
99
+ returns: { type: Number },
100
+ async get(params, { client }) {
101
+ return onlineClient.get(['online', 'session', { ...params, session: client.session }])
102
+ },
103
+ async observable(params, { client }) {
104
+ return onlineClient.observable(
105
+ ['online', 'session', { ...params, session: client.session }],
106
+ ReactiveDao.ObservableValue
107
+ )
108
+ }
109
+ })
110
+ ```
111
+
112
+ ### Example: `fetch` (remote / non-reactive)
113
+
114
+ ```js
115
+ definition.view({
116
+ name: 'myCountry',
117
+ properties: {},
118
+ returns: { type: String },
119
+ remote: true,
120
+ async fetch(props, { client }) {
121
+ return await getGeoIp(client.ip)
87
122
  }
88
123
  })
89
124
  ```
90
125
 
91
- ## 3. Triggery online/offline
126
+ ### Anti-pattern: `get` without `observable` (do not do this)
92
127
 
93
- 1. **Zidentyfikuj zdarzenie**
94
- - np. „połączenie online/offline”, „sesja utworzona”, „serwer się uruchomił”.
128
+ ```js
129
+ definition.view({
130
+ name: 'brokenView',
131
+ properties: {
132
+ id: { type: String }
133
+ },
134
+ returns: { type: Object },
135
+ async get({ id }) {
136
+ return await SomeModel.get(id)
137
+ }
138
+ })
139
+ ```
95
140
 
96
- 2. **Zdefiniuj trigger z `properties`**
97
- - triggery online/offline zwykle potrzebują tylko ID obiektu.
141
+ ## Step 3 Online/offline triggers
98
142
 
99
- 3. **Aktualizuj minimalny zestaw pól**
100
- - np. `status`, `lastSeenAt`.
143
+ 1. Identify events:
144
+ - session or connection goes online,
145
+ - session or connection goes offline.
146
+ 2. Define triggers with minimal `properties` (usually just an id).
147
+ 3. Update only the necessary fields (`status`, `lastSeenAt`, etc.).
101
148
 
102
- Przykład:
149
+ Example:
103
150
 
104
151
  ```js
105
152
  definition.trigger({
106
153
  name: 'sessionConnectionOnline',
107
154
  properties: {
108
- connection: {
109
- type: String
110
- }
155
+ connection: { type: String }
111
156
  },
112
157
  async execute({ connection }, { service }) {
113
158
  await Connection.update(connection, {
@@ -116,17 +161,29 @@ definition.trigger({
116
161
  })
117
162
  }
118
163
  })
164
+
165
+ definition.trigger({
166
+ name: 'sessionConnectionOffline',
167
+ properties: {
168
+ connection: { type: String }
169
+ },
170
+ async execute({ connection }, { service }) {
171
+ await Connection.update(connection, {
172
+ status: 'offline'
173
+ })
174
+ }
175
+ })
119
176
  ```
120
177
 
121
- ## 4. Triggery batchowe unikaj pełnych skanów
178
+ ## Step 4 Batch triggers (avoid full scans)
122
179
 
123
- 1. **Ustal limit batcha** (np. 32 lub 128 rekordów).
124
- 2. **Wykorzystaj `rangeGet` z `gt: lastId`**
125
- - inicjalnie `last = ''`,
126
- - po każdym batchu ustaw `last` na ID ostatniego rekordu.
127
- 3. **Kończ, gdy batch jest pusty**.
180
+ 1. Pick a **batch size** (e.g. 32 or 128).
181
+ 2. Use `rangeGet` with `gt: lastId` in a loop:
182
+ - start with `last = ''`,
183
+ - after each batch, set `last` to the last record’s id,
184
+ - stop when the batch is empty.
128
185
 
129
- Przykład:
186
+ Example:
130
187
 
131
188
  ```js
132
189
  definition.trigger({
@@ -141,9 +198,7 @@ definition.trigger({
141
198
  if(items.length === 0) break
142
199
 
143
200
  for(const item of items) {
144
- await Connection.update(item.id, {
145
- status: 'offline'
146
- })
201
+ await Connection.update(item.id, { status: 'offline' })
147
202
  }
148
203
 
149
204
  last = items[items.length - 1].id
@@ -152,25 +207,69 @@ definition.trigger({
152
207
  })
153
208
  ```
154
209
 
155
- ## 5. Wzorzec „pending + resolve” dla asynchronicznych wyników
210
+ ## Step 5 Grant access on entity creation
211
+
212
+ When a model uses `entity` with `writeAccessControl` / `readAccessControl`, the auto-generated CRUD checks roles but does **not** grant them. Add a change trigger to grant the creator `'owner'` after creation:
213
+
214
+ ```js
215
+ definition.trigger({
216
+ name: 'changeMyService_MyModel',
217
+ properties: {
218
+ object: { type: MyModel, validation: ['nonEmpty'] },
219
+ data: { type: Object },
220
+ oldData: { type: Object }
221
+ },
222
+ async execute({ object, data, oldData }, { client, triggerService }) {
223
+ if (!data || oldData) return // only on create (data present, no oldData)
224
+ if (!client?.user) return
225
+
226
+ await triggerService({ service: 'accessControl', type: 'accessControl_setAccess' }, {
227
+ objectType: 'myService_MyModel', // format: serviceName_ModelName
228
+ object,
229
+ roles: ['owner'],
230
+ sessionOrUserType: 'user_User',
231
+ sessionOrUser: client.user,
232
+ lastUpdate: new Date()
233
+ })
234
+ }
235
+ })
236
+ ```
237
+
238
+ For publicly accessible objects, also call `accessControl_setPublicAccess`:
239
+
240
+ ```js
241
+ await triggerService({ service: 'accessControl', type: 'accessControl_setPublicAccess' }, {
242
+ objectType: 'myService_MyModel',
243
+ object,
244
+ userRoles: ['reader'], // roles for all logged-in users
245
+ sessionRoles: ['reader'], // roles for all sessions (including anonymous)
246
+ lastUpdate: new Date()
247
+ })
248
+ ```
249
+
250
+ Key points:
251
+ - `objectType` format: `serviceName_ModelName` (e.g. `company_Company`, `uploadedFiles_File`)
252
+ - `sessionOrUserType`: `'user_User'` for logged-in users, `'session_Session'` for anonymous
253
+ - For anonymous users: `sessionOrUser: client.session`
254
+ - Use `Promise.all([...])` when setting both public and per-user access
156
255
 
157
- Użyj tego wzorca, gdy:
256
+ ## Step 6 – Pending + resolve pattern for async results
158
257
 
159
- - akcja tworzy zlecenie/komendę,
160
- - wynik przychodzi później z innego procesu (urządzenie, worker, itp.),
161
- - chcesz, żeby akcja czekała na wynik z timeoutem.
258
+ Use this pattern when an action initiates a command that will be completed by an external process (device, worker, etc.) and you want the action to wait with a timeout.
162
259
 
163
- ### Kroki
260
+ ### Steps
164
261
 
165
- 1. Utwórz helper `pendingCommands` (Map) w osobnym module.
166
- 2. W akcji tworzącej:
167
- - utwórz rekord z `status: 'pending'`,
168
- - wywołaj `waitForCommand(id, timeoutMs)`.
169
- 3. W akcji raportującej:
170
- - zaktualizuj rekord (`status: 'completed'`, `result`),
171
- - wywołaj `resolveCommand(id, result)`.
262
+ 1. Implement a helper module with an in-memory `Map`:
263
+ - `waitForCommand(id, timeoutMs)` – returns a Promise,
264
+ - `resolveCommand(id, result)` resolves and clears timeout.
265
+ 2. In the main action:
266
+ - create a record with `status: 'pending'`,
267
+ - call `waitForCommand(id, timeoutMs)` and `return` the result.
268
+ 3. In the reporting action:
269
+ - update the record (`status: 'completed'`, `result`),
270
+ - call `resolveCommand(id, result)`.
172
271
 
173
- ### Szkic helpera
272
+ Helper sketch:
174
273
 
175
274
  ```js
176
275
  const pendingCommands = new Map()
@@ -1,39 +1,49 @@
1
1
  ---
2
+ name: live-change-design-models-relations
2
3
  description: Design models with userItem, itemOf, propertyOf relations and access control
3
4
  ---
4
5
 
5
- # Skill: live-change-design-models-relations
6
+ # Skill: live-change-design-models-relations (Claude Code)
6
7
 
7
- Ten skill opisuje **krok po kroku**, jak projektować modele i relacje (`userItem`, `itemOf`, `propertyOf`, `foreignModel`) w serwisach LiveChange.
8
+ Use this skill when you design or refactor **models and relations** in a LiveChange service.
8
9
 
9
- ## Kiedy używać
10
+ ## When to use
10
11
 
11
- Użyj tego skilla, gdy:
12
+ - You are adding a new model to a service.
13
+ - You want to switch from manual CRUD/views to proper relations.
14
+ - You need consistent access control and index usage.
12
15
 
13
- - dodajesz nowy model do serwisu,
14
- - przenosisz dane z ręcznego CRUD na relacje,
15
- - potrzebujesz spójnego wzorca dla access control i indeksów.
16
+ ## Step 1 Decide the relation type
16
17
 
17
- ## 1. Dobierz typ relacji
18
+ For each new model, decide how it relates to the rest of the domain:
18
19
 
19
- Zastanów się, jak model jest powiązany z resztą domeny:
20
+ - **`userItem`** the object belongs to the signed-in user (e.g. user’s device).
21
+ - **`itemOf`** – a list of children belonging to a parent model (e.g. device connections).
22
+ - **`propertyOf`** – a single state object with the same id as the parent (e.g. cursor state).
23
+ - **no relation** – for global data or other special cases.
20
24
 
21
- - **`userItem`** obiekt należy do zalogowanego użytkownika (np. konto, urządzenie użytkownika).
22
- - **`itemOf`** – lista elementów należy do innego modelu (np. połączenia urządzenia, pozycje zamówienia).
23
- - **`propertyOf`** – pojedyncza właściwość/model ze stanem o ID równym rodzicowi (np. stan kursora, ustawienia).
24
- - **bez relacji** – gdy obiekt jest globalny lub ma inną strukturę (np. globalny config).
25
+ Choose one main relation; other associations can be plain fields + indexes.
25
26
 
26
- Wybierz jedną główną relację na model dodatkowe powiązania możesz odzwierciedlić polami i indeksami.
27
+ ## Step 2Define `properties` clearly
27
28
 
28
- ## 2. Zdefiniuj `properties` w czytelny sposób
29
+ 1. Use a **multi-line** style for properties, with clear `type`, `default`, `validation`, etc.
30
+ 2. Avoid unreadable one-liners combining everything.
31
+ 3. **Do NOT re-declare fields that are auto-added by relations.** Each relation automatically adds identifier fields and indexes:
29
32
 
30
- 1. Utwórz sekcję `properties`:
31
- - każdą właściwość zapisuj wieloliniowo,
32
- - jasno określ typ, domyślne wartości, walidację.
33
+ | Relation | Auto-added field(s) | Auto-added index(es) |
34
+ |---|---|---|
35
+ | `itemOf: { what: Device }` | `device` | `byDevice` |
36
+ | `propertyOf: { what: Device }` | `device` | `byDevice` |
37
+ | `userItem` | `user` | `byUser` |
38
+ | `sessionOrUserProperty` | `sessionOrUserType`, `sessionOrUser` | `bySessionOrUser` |
39
+ | `propertyOfAny: { to: ['owner'] }` | `ownerType`, `owner` | `byOwner` |
33
40
 
34
- 2. Przykład:
41
+ Naming convention: parent model name with first letter lowercased (`Device` → `device`, `CostInvoice` → `costInvoice`). Polymorphic relations add a `Type` + value pair (e.g. `ownerType` + `owner`).
42
+
43
+ Example:
35
44
 
36
45
  ```js
46
+ // ✅ Only define YOUR fields — 'device' is auto-added by itemOf
37
47
  properties: {
38
48
  name: {
39
49
  type: String,
@@ -52,13 +62,12 @@ properties: {
52
62
  }
53
63
  ```
54
64
 
55
- ## 3. Skonfiguruj relację
65
+ ## Step 3 Configure the relation
56
66
 
57
67
  ### `userItem`
58
68
 
59
- 1. Dodaj blok `userItem` w definicji modelu.
60
- 2. Ustaw role dla odczytu i zapisu.
61
- 3. Ogranicz `writeableProperties` do pól, które użytkownik może zmieniać.
69
+ 1. Add a `userItem` block inside the model definition.
70
+ 2. Set roles for read/write and list which fields can be written.
62
71
 
63
72
  ```js
64
73
  userItem: {
@@ -70,8 +79,8 @@ userItem: {
70
79
 
71
80
  ### `itemOf`
72
81
 
73
- 1. Ustal model rodzica (`what`).
74
- 2. W razie potrzeby użyj `definition.foreignModel`, jeśli rodzic jest w innym serwisie.
82
+ 1. Decide the parent model.
83
+ 2. If the parent is in another service, declare it via `foreignModel` (see next step).
75
84
 
76
85
  ```js
77
86
  itemOf: {
@@ -83,8 +92,8 @@ itemOf: {
83
92
 
84
93
  ### `propertyOf`
85
94
 
86
- 1. Użyj, gdy chcesz, żeby ID modelu = ID rodzica.
87
- 2. To ułatwia odczyt (`Model.get(parentId)`).
95
+ 1. Use when the child should share the same id as the parent.
96
+ 2. This simplifies lookups and avoids extra indexes.
88
97
 
89
98
  ```js
90
99
  propertyOf: {
@@ -94,17 +103,17 @@ propertyOf: {
94
103
  }
95
104
  ```
96
105
 
97
- ### `propertyOf` z wieloma rodzicami (1:1 relacja do wielu encji)
106
+ ### `propertyOf` with multiple parents (1:1 link to each)
98
107
 
99
- Użyj, gdy model ma być „łącznikiem 1:1 pomiędzy kilkoma encjami (np. fakturakontrahent w konkretnej roli),
100
- żeby generator relacji/CRUD rozumiał, że to jest relacja, a nie pole `someId` w `properties`.
108
+ Use this when a model should act as a dedicated 1:1 link between multiple entities (e.g. invoicecontractor role links),
109
+ so the relations/CRUD generator can treat it as a relation rather than a plain `someId` property.
101
110
 
102
- Uwagi:
111
+ Notes:
103
112
 
104
- - Najczęściej jest to 1 lub 2 rodziców, ale lista `propertyOf` może zawierać **dowolnie wiele** modeli (np. relacja łącząca 3+ encje).
105
- - Jeśli encja jest relacją, **nie dodawaj ręcznie** pól typu `...Id` w `properties` tylko po to, żeby “zrobić join” generator CRUD nie potraktuje tego jako relacji.
113
+ - Usually you’ll have 12 parents, but the `propertyOf` list may contain **any number** of parent models (including 3+).
114
+ - If the entity is a relation, avoid adding manual `...Id` fields in `properties` just to represent the link CRUD generators won’t treat it as a relation.
106
115
 
107
- Przykład:
116
+ Example:
108
117
 
109
118
  ```js
110
119
  const CostInvoice = definition.foreignModel('invoice', 'CostInvoice')
@@ -113,7 +122,7 @@ const Contractor = definition.foreignModel('company', 'Contractor')
113
122
  definition.model({
114
123
  name: 'Supplier',
115
124
  properties: {
116
- // opcjonalne pola dodatkowe
125
+ // optional extra fields
117
126
  },
118
127
  propertyOf: [
119
128
  { what: CostInvoice },
@@ -122,20 +131,29 @@ definition.model({
122
131
  })
123
132
  ```
124
133
 
125
- ### `foreignModel`
134
+ ## Step 4 – Use `foreignModel` for cross-service relations
126
135
 
127
- 1. Na początku pliku z modelem zadeklaruj:
136
+ 1. At the top of the domain file, declare:
128
137
 
129
138
  ```js
130
139
  const Device = definition.foreignModel('deviceManager', 'Device')
131
140
  ```
132
141
 
133
- 2. Potem używaj `Device` w `itemOf`/`propertyOf`.
142
+ 2. Then use `Device` in `itemOf` or `propertyOf`:
134
143
 
135
- ## 4. Dodaj indeksy
144
+ ```js
145
+ itemOf: {
146
+ what: Device,
147
+ readAccessControl: { roles: ['owner', 'admin'] }
148
+ }
149
+ ```
136
150
 
137
- 1. Zidentyfikuj typowe zapytania (np. po `sessionKey`, po `(device, status)`).
138
- 2. Dodaj sekcję `indexes`:
151
+ ## Step 5 Add indexes
152
+
153
+ 1. Identify frequent queries:
154
+ - by a single field (e.g. `sessionKey`),
155
+ - by combinations (e.g. `(device, status)`).
156
+ 2. Declare indexes in the model:
139
157
 
140
158
  ```js
141
159
  indexes: {
@@ -148,21 +166,65 @@ indexes: {
148
166
  }
149
167
  ```
150
168
 
151
- 3. Używaj tych indeksów w widokach i akcjach (`indexObjectGet`, `indexRangeGet`).
169
+ 3. Use these indexes in views/actions, via `indexObjectGet` / `indexRangeGet`.
152
170
 
153
- ## 5. Ustal access control na poziomie modelu/relacji
171
+ ## Step 6 – Set access control on relations
154
172
 
155
- 1. Dla `userItem` / `itemOf` / `propertyOf` ustaw zawsze:
173
+ 1. For `userItem`, `itemOf`, and `propertyOf`, always define:
156
174
  - `readAccessControl`,
157
175
  - `writeAccessControl`.
176
+ 2. Don’t rely on unspecified defaults; access rules should be explicit in the model.
177
+
178
+ ## Step 7 – Grant access on `entity` model creation
179
+
180
+ Models with `entity` and `writeAccessControl` / `readAccessControl` check roles on every CRUD operation, but do **not** auto-grant roles to the creator. Without granting roles, the creator cannot even read their own object.
158
181
 
159
- 2. Nie zakładaj domyślnych reguł wpisz je wprost w definicji modelu.
182
+ Add a change trigger that grants `'owner'` on creation:
183
+
184
+ ```js
185
+ definition.trigger({
186
+ name: 'changeMyService_MyModel',
187
+ properties: {
188
+ object: { type: MyModel, validation: ['nonEmpty'] },
189
+ data: { type: Object },
190
+ oldData: { type: Object }
191
+ },
192
+ async execute({ object, data, oldData }, { client, triggerService }) {
193
+ if (!data || oldData) return // only on create
194
+ if (!client?.user) return
195
+
196
+ await triggerService({ service: 'accessControl', type: 'accessControl_setAccess' }, {
197
+ objectType: 'myService_MyModel', // format: serviceName_ModelName
198
+ object,
199
+ roles: ['owner'],
200
+ sessionOrUserType: 'user_User',
201
+ sessionOrUser: client.user,
202
+ lastUpdate: new Date()
203
+ })
204
+ }
205
+ })
206
+ ```
207
+
208
+ For objects that should also be publicly readable, add `accessControl_setPublicAccess`:
209
+
210
+ ```js
211
+ await triggerService({ service: 'accessControl', type: 'accessControl_setPublicAccess' }, {
212
+ objectType: 'myService_MyModel',
213
+ object,
214
+ userRoles: ['reader'], // roles for all logged-in users
215
+ sessionRoles: ['reader'], // roles for all sessions (including anonymous)
216
+ lastUpdate: new Date()
217
+ })
218
+ ```
160
219
 
161
- ## 6. Sprawdź wygenerowane widoki/akcje
220
+ **Note:** `userItem` and `itemOf`/`propertyOf` relations automatically handle access through the parent — you typically don't need manual `triggerService` calls for those. This step applies primarily to `entity` models.
162
221
 
163
- Po dodaniu relacji:
222
+ ## Step 8 – Check auto-generated views/actions
164
223
 
165
- 1. Sprawdź, jakie widoki/akcje generuje plugin (np. `myUserDevices`, `createMyUserDevice`, itp.).
166
- 2. Zastanów się, czy potrzebujesz dodatkowych, niestandardowych widoków/akcji:
167
- - jeśli tak, dodaj je, ale **nie duplikuj** tego, co generuje relacja.
224
+ 1. After adding relations, review the auto-generated views/actions:
225
+ - “my X” views and CRUD for `userItem`,
226
+ - parent-scoped lists for `itemOf`/`propertyOf`.
227
+ 2. Only add custom views/actions when:
228
+ - you need special filters,
229
+ - or custom logic not covered by the generated ones.
168
230
 
@@ -1,4 +1,5 @@
1
1
  ---
2
+ name: live-change-design-service
2
3
  description: Create or restructure a LiveChange backend service with proper directory layout
3
4
  ---
4
5