@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
@@ -0,0 +1,129 @@
1
+ ---
2
+ name: live-change-frontend-range-list
3
+ description: Build paginated scrollable lists with RangeViewer, rangeBuckets and .with()
4
+ ---
5
+
6
+ # Skill: live-change-frontend-range-list (Claude Code)
7
+
8
+ Use this skill when you build **paginated, scrollable lists** backed by DAO ranges in a LiveChange frontend.
9
+
10
+ ## When to use
11
+
12
+ - You need a list that loads items in pages (infinite scroll).
13
+ - The list is backed by a DAO range view (e.g. `articlesByCreatedAt`).
14
+ - You want to attach related objects to each item via `.with()`.
15
+
16
+ ## Step 1 – Define the path function
17
+
18
+ The path function receives a `range` object (with `gt`, `gte`, `lt`, `lte`, `limit`, `reverse`) and returns a DAO path.
19
+
20
+ Use `reverseRange()` to display items newest-first:
21
+
22
+ ```javascript
23
+ import { reverseRange } from '@live-change/vue3-ssr'
24
+
25
+ function articlesPathRange(range) {
26
+ return path.blog.articlesByCreatedAt({ ...reverseRange(range) })
27
+ }
28
+ ```
29
+
30
+ ## Step 2 – Attach related objects with `.with()`
31
+
32
+ Chain `.with()` calls to load related data for each item:
33
+
34
+ ```javascript
35
+ function articlesPathRange(range) {
36
+ return path.blog.articlesByCreatedAt({ ...reverseRange(range) })
37
+ .with(article => path.userIdentification.identification({
38
+ sessionOrUserType: article.authorType,
39
+ sessionOrUser: article.author
40
+ }).bind('authorProfile'))
41
+ .with(article => path.blog.articleStats({ article: article.id }).bind('stats'))
42
+ }
43
+ ```
44
+
45
+ Each `.with()` call:
46
+ - receives a proxy of the item,
47
+ - builds a path to the related data,
48
+ - calls `.bind('fieldName')` to attach the result under that field name.
49
+
50
+ Nested `.with()` is also supported:
51
+
52
+ ```javascript
53
+ function eventsPathRange(range) {
54
+ return path.myService.allEvents({ ...reverseRange(range) })
55
+ .with(event => path.myService.eventState({ event: event.id }).bind('state')
56
+ .with(state => path.myService.roundPairs({ event: event.id, round: state.round }).bind('roundPairs'))
57
+ )
58
+ }
59
+ ```
60
+
61
+ ## Step 3 – Use `<RangeViewer>` in the template
62
+
63
+ ```vue
64
+ <template>
65
+ <RangeViewer
66
+ :pathFunction="articlesPathRange"
67
+ :canLoadTop="false"
68
+ canDropBottom
69
+ loadBottomSensorSize="3000px"
70
+ dropBottomSensorSize="5000px"
71
+ >
72
+ <template #empty>
73
+ <p class="text-center text-surface-500 my-4">No articles yet.</p>
74
+ </template>
75
+ <template #default="{ item: article }">
76
+ <Card class="mb-2">
77
+ <template #content>
78
+ <h3>{{ article.title }}</h3>
79
+ <p class="text-sm text-surface-500">By {{ article.authorProfile?.firstName }}</p>
80
+ </template>
81
+ </Card>
82
+ </template>
83
+ </RangeViewer>
84
+ </template>
85
+ ```
86
+
87
+ Key props:
88
+
89
+ | Prop | Default | Description |
90
+ |---|---|---|
91
+ | `pathFunction` | required | `(range) => path` – builds the DAO path for a given range |
92
+ | `bucketSize` | `20` | Items per page |
93
+ | `canLoadTop` / `canLoadBottom` | `true` | Whether loading in each direction is allowed |
94
+ | `canDropTop` / `canDropBottom` | `false` | Whether to drop pages that scrolled far out of view |
95
+ | `loadBottomSensorSize` | `'500px'` | How far before the bottom to trigger loading (increase for smoother UX) |
96
+ | `dropBottomSensorSize` | `'5000px'` | How far to keep before dropping |
97
+ | `frozen` | `false` | Pause live updates |
98
+
99
+ Slots:
100
+
101
+ | Slot | Props | Description |
102
+ |---|---|---|
103
+ | `default` | `{ item, bucket, itemIndex, bucketIndex }` | Render each item |
104
+ | `empty` | – | Shown when there are no items |
105
+ | `loadingTop` / `loadingBottom` | – | Loading spinners |
106
+ | `changedTop` / `changedBottom` | – | Indicators when data changed while frozen |
107
+
108
+ ## Step 4 – Low-level `rangeBuckets` (optional)
109
+
110
+ For advanced control, use `rangeBuckets` directly:
111
+
112
+ ```javascript
113
+ import { rangeBuckets, reverseRange } from '@live-change/vue3-ssr'
114
+
115
+ const { buckets, loadBottom, dropTop, freeze, unfreeze } = await rangeBuckets(
116
+ (range) => path.blog.articlesByCreatedAt({ ...reverseRange(range) }),
117
+ { bucketSize: 20 }
118
+ )
119
+ ```
120
+
121
+ Iterate in the template:
122
+
123
+ ```vue
124
+ <template v-for="(bucket, bi) in buckets.value" :key="bi">
125
+ <div v-for="(item, ii) in bucket.data.value" :key="item.id">
126
+ <!-- render item -->
127
+ </div>
128
+ </template>
129
+ ```
@@ -0,0 +1,119 @@
1
+ ---
2
+ name: live-change-frontend-ssr-setup
3
+ description: Set up SSR entry points, router, PrimeVue theme and Suspense data loading
4
+ ---
5
+
6
+ # Skill: live-change-frontend-ssr-setup (Claude Code)
7
+
8
+ Use this skill to set up or adjust a **LiveChange SSR frontend**:
9
+
10
+ - client/server entry points,
11
+ - router + `meta.signedIn`,
12
+ - PrimeVue theme configuration.
13
+
14
+ ## Step 1 – Client and server entry points
15
+
16
+ 1. Ensure the frontend has two entry files:
17
+ - `entry-client.js` (or `.ts`),
18
+ - `entry-server.js` (or `.ts`).
19
+
20
+ 2. Use helpers from `@live-change/frontend-base`:
21
+
22
+ ```js
23
+ // entry-client.js
24
+ import { clientEntry } from '@live-change/frontend-base/client-entry.js'
25
+ import App from './App.vue'
26
+ import { createRouter } from './router.js'
27
+ import { config } from './config.js'
28
+
29
+ export default clientEntry(App, createRouter, config)
30
+ ```
31
+
32
+ ```js
33
+ // entry-server.js
34
+ import { serverEntry, sitemapEntry } from '@live-change/frontend-base/server-entry.js'
35
+ import App from './App.vue'
36
+ import { createRouter, routerSitemap } from './router.js'
37
+ import { config } from './config.js'
38
+
39
+ export const render = serverEntry(App, createRouter, config)
40
+ export const sitemap = sitemapEntry(App, createRouter, routerSitemap, config)
41
+ ```
42
+
43
+ ## Step 2 – Router and `meta.signedIn`
44
+
45
+ 1. Use `vite-plugin-pages` to auto-generate routes from `src/pages/`.
46
+ 2. Add a `<route>` block to each page with basic meta:
47
+
48
+ ```vue
49
+ <route>
50
+ { "name": "devices", "meta": { "signedIn": true } }
51
+ </route>
52
+ ```
53
+
54
+ 3. Add a navigation guard for signed-in pages:
55
+
56
+ ```js
57
+ router.beforeEach((to) => {
58
+ if(to.meta.signedIn && !isLoggedIn()) {
59
+ localStorage.setItem('redirectAfterLogin', to.fullPath)
60
+ return { name: 'user:signIn' }
61
+ }
62
+ })
63
+ ```
64
+
65
+ Implement `isLoggedIn()` according to the project’s auth/session model.
66
+
67
+ ## Step 3 – PrimeVue theme configuration
68
+
69
+ 1. In `config.js`, configure the PrimeVue theme using `definePreset`:
70
+
71
+ ```js
72
+ import { definePreset } from '@primevue/themes'
73
+ import Aura from '@primevue/themes/aura'
74
+
75
+ const MyPreset = definePreset(Aura, {
76
+ semantic: {
77
+ primary: {
78
+ 50: '{indigo.50}',
79
+ 100: '{indigo.100}',
80
+ 200: '{indigo.200}',
81
+ 300: '{indigo.300}',
82
+ 400: '{indigo.400}',
83
+ 500: '{indigo.500}',
84
+ 600: '{indigo.600}',
85
+ 700: '{indigo.700}',
86
+ 800: '{indigo.800}',
87
+ 900: '{indigo.900}',
88
+ 950: '{indigo.950}'
89
+ }
90
+ }
91
+ })
92
+
93
+ export const config = {
94
+ theme: {
95
+ preset: MyPreset,
96
+ options: {
97
+ darkModeSelector: '.app-dark-mode'
98
+ }
99
+ }
100
+ }
101
+ ```
102
+
103
+ 2. Ensure the app uses this config when initializing PrimeVue (usually in `App.vue` / main entry).
104
+
105
+ ## Step 4 – Global components and forms
106
+
107
+ 1. In `App.vue` (or main setup), register global components used throughout the app:
108
+ - auto-form components,
109
+ - common layout components, etc.
110
+ 2. This allows pages to use components like `<command-form>` without local imports.
111
+
112
+ ## Step 5 – SSR-friendly data loading
113
+
114
+ 1. Ensure the root of the app (e.g. `ViewRoot`) wraps content in `<Suspense>`.
115
+ 2. In page components:
116
+ - use `await Promise.all([live(path()....)])` inside `script setup`,
117
+ - read from `.value` in templates,
118
+ - **do not** fetch main data in `onMounted`.
119
+
@@ -58,6 +58,67 @@ definition.action({
58
58
  - korzystaj z indeksów (`indexObjectGet`, `indexRangeGet`),
59
59
  - nie implementuj skomplikowanej logiki w widokach, jeśli można ją przenieść do akcji.
60
60
 
61
+ ## Widoki – reguła: `get` + `observable` zawsze razem
62
+
63
+ Widok musi być dokładnie jednym z trzech wariantów:
64
+
65
+ - `daoPath` (preferowane) — framework wygeneruje zarówno `get`, jak i `observable`
66
+ - `get` + `observable` — oba wymagane, jeśli źródło danych jest zewnętrzne lub custom-reactive
67
+ - `fetch` — jednorazowy request/response (często `remote: true`), bez reaktywnego strumienia
68
+
69
+ Nigdy nie definiuj samego `get` bez `observable` ani samego `observable` bez `get`. To psuje użycie reaktywne i może kończyć się błędami w processorach, które owijają oba callbacki (np. access control).
70
+
71
+ ### Poprawnie: `daoPath` zamiast ręcznego `get`/`observable`
72
+
73
+ ```js
74
+ definition.view({
75
+ name: 'costInvoice',
76
+ properties: {
77
+ costInvoice: {
78
+ type: String
79
+ }
80
+ },
81
+ returns: { type: Object },
82
+ async daoPath({ costInvoice }) {
83
+ return CostInvoice.path(costInvoice)
84
+ }
85
+ })
86
+ ```
87
+
88
+ ### Poprawnie: `get` + `observable` razem (zewnętrzne źródło)
89
+
90
+ ```js
91
+ definition.view({
92
+ name: 'session',
93
+ properties: {},
94
+ returns: { type: Number },
95
+ async get(params, { client }) {
96
+ return onlineClient.get(['online', 'session', { ...params, session: client.session }])
97
+ },
98
+ async observable(params, { client }) {
99
+ return onlineClient.observable(
100
+ ['online', 'session', { ...params, session: client.session }],
101
+ ReactiveDao.ObservableValue
102
+ )
103
+ }
104
+ })
105
+ ```
106
+
107
+ ### Źle: samo `get` bez `observable`
108
+
109
+ ```js
110
+ definition.view({
111
+ name: 'brokenView',
112
+ properties: {
113
+ id: { type: String }
114
+ },
115
+ returns: { type: Object },
116
+ async get({ id }) {
117
+ return await SomeModel.get(id)
118
+ }
119
+ })
120
+ ```
121
+
61
122
  ### Wzorzec widoku zakresowego
62
123
 
63
124
  ```js
@@ -148,6 +209,33 @@ definition.trigger({
148
209
  })
149
210
  ```
150
211
 
212
+ ## Change triggers – reakcja na zmiany modeli
213
+
214
+ Modele z relacjami (`propertyOf`, `itemOf`, `userItem`, itp.) automatycznie odpalają change triggery przy każdym create/update/delete. Konwencja nazw: `{changeType}{ServiceName}_{ModelName}`:
215
+
216
+ - `changeSvc_Model` — odpala się przy każdej zmianie (rekomendowane, obsługuje wszystkie przypadki)
217
+ - `createSvc_Model` / `updateSvc_Model` / `deleteSvc_Model` — konkretne zdarzenia cyklu życia
218
+
219
+ Parametry: `{ objectType, object, identifiers, data, oldData, changeType }`.
220
+
221
+ ```js
222
+ // Reaguj na dowolną zmianę modelu Schedule z serwisu cron
223
+ definition.trigger({
224
+ name: 'changeCron_Schedule',
225
+ properties: {
226
+ object: { type: Schedule, validation: ['nonEmpty'] },
227
+ data: { type: Object },
228
+ oldData: { type: Object }
229
+ },
230
+ async execute({ object, data, oldData }, { triggerService }) {
231
+ if(oldData) { /* wyczyść stary stan */ }
232
+ if(data) { /* ustaw nowy stan */ }
233
+ }
234
+ })
235
+ ```
236
+
237
+ Sprawdzaj `data`/`oldData`: oba obecne = update, tylko `data` = create, tylko `oldData` = delete.
238
+
151
239
  ## Wzorzec „pending + resolve” (asynchroniczny wynik)
152
240
 
153
241
  - Używaj, gdy akcja w serwisie musi poczekać na wynik z zewnętrznego procesu (np. urządzenie, worker).
@@ -0,0 +1,185 @@
1
+ ---
2
+ description: Event-sourcing data flow rules — emit events for DB writes, use triggerService for cross-service writes
3
+ globs: **/services/**/*.js
4
+ alwaysApply: false
5
+ ---
6
+
7
+ # LiveChange backend – event-sourcing i przepływ danych
8
+
9
+ ## Jak płyną dane w LiveChange
10
+
11
+ LiveChange stosuje wzorzec event-sourcing:
12
+
13
+ 1. **Akcje i triggery** walidują dane i publikują eventy przez `emit()`.
14
+ 2. **Eventy** (`definition.event()`) wykonują faktyczne zapisy do bazy (`Model.create`, `Model.update`, `Model.delete`).
15
+ 3. Dla modeli z **relacjami** (`userItem`, `itemOf`, `propertyOf`, itp.) relations plugin auto-generuje eventy i triggery CRUD — używaj ich przez `triggerService()`.
16
+ 4. Dla **zapisów między serwisami** zawsze używaj `triggerService()` — `foreignModel` jest tylko do odczytu.
17
+
18
+ ```
19
+ Akcja/Trigger ──emit()──▶ Event handler ──▶ Model.create/update/delete
20
+
21
+ └──triggerService()──▶ Trigger innego serwisu ──emit()──▶ ...
22
+ ```
23
+
24
+ ## Reguła 1: Nie używaj Model.create/update/delete bezpośrednio w akcjach i triggerach
25
+
26
+ Akcje i triggery **nie powinny** wywoływać `Model.create()`, `Model.update()` ani `Model.delete()` bezpośrednio. Zamiast tego:
27
+
28
+ - **Jeśli model ma relacje** (auto-generowane CRUD) → użyj `triggerService()` do wywołania triggera relacji (np. `serviceName_createModelName`, `serviceName_updateModelName`, `serviceName_setModelName`).
29
+ - **Jeśli nie ma triggera relacji** → użyj `emit()` do opublikowania eventu, a potem zdefiniuj `definition.event()`, który wykona zapis.
30
+
31
+ ### Poprawnie: emit z akcji
32
+
33
+ ```js
34
+ definition.action({
35
+ name: 'createImage',
36
+ properties: { /* ... */ },
37
+ waitForEvents: true,
38
+ async execute({ image, name, width, height }, { client, service }, emit) {
39
+ const id = image || app.generateUid()
40
+ // walidacja, logika biznesowa...
41
+ emit({
42
+ type: 'ImageCreated',
43
+ image: id,
44
+ data: { name, width, height }
45
+ })
46
+ return id
47
+ }
48
+ })
49
+
50
+ definition.event({
51
+ name: 'ImageCreated',
52
+ async execute({ image, data }) {
53
+ await Image.create({ ...data, id: image })
54
+ }
55
+ })
56
+ ```
57
+
58
+ ### Poprawnie: triggerService dla triggerów relacji
59
+
60
+ ```js
61
+ definition.action({
62
+ name: 'giveCard',
63
+ properties: { /* ... */ },
64
+ async execute({ receiverType, receiver }, { client, triggerService }, emit) {
65
+ // Użyj auto-generowanego triggera z relations plugin
66
+ await triggerService({
67
+ service: definition.name,
68
+ type: 'businessCard_setReceivedCard',
69
+ }, {
70
+ sessionOrUserType: receiverType,
71
+ sessionOrUser: receiver,
72
+ // ...
73
+ })
74
+ }
75
+ })
76
+ ```
77
+
78
+ ### Źle: bezpośredni zapis w akcji
79
+
80
+ ```js
81
+ // ❌ NIE RÓB TEGO
82
+ definition.action({
83
+ name: 'createSomething',
84
+ async execute({ name }, { client }, emit) {
85
+ const id = app.generateUid()
86
+ await Something.create({ id, name }) // ❌ bezpośredni zapis w akcji
87
+ return id
88
+ }
89
+ })
90
+ ```
91
+
92
+ ## Reguła 2: Eventy mogą modyfikować tylko modele tego samego serwisu
93
+
94
+ Handlery eventów (`definition.event()`) mogą zapisywać tylko do modeli zdefiniowanych w **tym samym serwisie**. Nie próbuj zapisywać do `foreignModel` — jest tylko do odczytu (`.get()`, `.indexObjectGet()`, `.indexRangeGet()`, ale nie `.create()`, `.update()`, `.delete()`).
95
+
96
+ Do zapisów między serwisami używaj `triggerService()` z akcji lub triggera:
97
+
98
+ ```js
99
+ // ✅ Poprawnie: zapis między serwisami przez triggerService
100
+ definition.trigger({
101
+ name: 'chargeCollected_billing_TopUp',
102
+ async execute(props, { triggerService }, emit) {
103
+ // Zapis do innego serwisu przez jego zadeklarowany trigger
104
+ await triggerService({
105
+ service: 'balance',
106
+ type: 'balance_setOrUpdateBalance',
107
+ }, { ownerType: 'billing_Billing', owner: props.cause })
108
+
109
+ // Zapis do własnego serwisu przez triggerService (trigger relacji)
110
+ await triggerService({
111
+ service: definition.name,
112
+ type: 'billing_updateTopUp'
113
+ }, { topUp: props.cause, state: 'paid' })
114
+ }
115
+ })
116
+ ```
117
+
118
+ ```js
119
+ // ❌ NIE RÓB TEGO — foreignModel jest tylko do odczytu
120
+ const ExternalModel = definition.foreignModel('otherService', 'SomeModel')
121
+
122
+ definition.event({
123
+ name: 'SomethingHappened',
124
+ async execute({ id }) {
125
+ await ExternalModel.update(id, { status: 'done' }) // ❌ nie zadziała
126
+ }
127
+ })
128
+ ```
129
+
130
+ ## `waitForEvents: true`
131
+
132
+ Gdy akcja lub trigger emituje eventy i musi poczekać na ich przetworzenie przed zwróceniem wyniku, ustaw `waitForEvents: true`:
133
+
134
+ ```js
135
+ definition.action({
136
+ name: 'createNotification',
137
+ waitForEvents: true,
138
+ async execute({ message }, { client }, emit) {
139
+ const id = app.generateUid()
140
+ emit({
141
+ type: 'created',
142
+ notification: id,
143
+ data: { message, sessionOrUserType: 'user_User', sessionOrUser: client.user }
144
+ })
145
+ return id // event jest przetworzony zanim to się zwróci
146
+ }
147
+ })
148
+ ```
149
+
150
+ Bez `waitForEvents: true` akcja zwraca wynik natychmiast, a eventy są przetwarzane asynchronicznie.
151
+
152
+ ## Triggery relacji (auto-generowane)
153
+
154
+ Gdy model ma relacje (`userItem`, `itemOf`, `propertyOf`, itp.), relations plugin auto-generuje triggery CRUD. Użyj `describe`, żeby je odkryć:
155
+
156
+ ```bash
157
+ node server/start.js describe --service myService --output yaml
158
+ ```
159
+
160
+ Typowe auto-generowane triggery:
161
+ - `serviceName_createModelName` — tworzenie rekordu
162
+ - `serviceName_updateModelName` — aktualizacja rekordu
163
+ - `serviceName_deleteModelName` — usuwanie rekordu
164
+ - `serviceName_setModelName` — upsert (utwórz lub nadpisz)
165
+ - `serviceName_setOrUpdateModelName` — ustaw jeśli nie istnieje, zaktualizuj jeśli istnieje
166
+
167
+ Wywołuj je przez `triggerService()`:
168
+
169
+ ```js
170
+ await triggerService({
171
+ service: 'myService',
172
+ type: 'myService_createMyModel'
173
+ }, {
174
+ // właściwości pasujące do pól modelu
175
+ })
176
+ ```
177
+
178
+ ## Podsumowanie
179
+
180
+ | Gdzie | Może wywoływać Model.create/update/delete? | Jak zmieniać dane |
181
+ |---|---|---|
182
+ | `definition.event()` | ✅ Tak (tylko modele tego samego serwisu) | Bezpośredni zapis |
183
+ | `definition.action()` | ❌ Nie | `emit()` lub `triggerService()` |
184
+ | `definition.trigger()` | ❌ Nie | `emit()` lub `triggerService()` |
185
+ | Między serwisami | ❌ Nigdy przez foreignModel | `triggerService()` do docelowego serwisu |
@@ -169,6 +169,68 @@ Zasady:
169
169
  - pierwszy argument to nazwa serwisu,
170
170
  - drugi to nazwa modelu w tamtym serwisie.
171
171
 
172
+ ## Automatycznie dodawane pola z relacji
173
+
174
+ Relacje automatycznie dodają **pola identyfikatorów** i **indeksy** do modelu. **Nie definiuj ich ponownie** w `properties`.
175
+
176
+ **Konwencja nazw:** nazwa pola = nazwa modelu rodzica z małą pierwszą literą (`Device` → `device`, `CostInvoice` → `costInvoice`).
177
+
178
+ | Relacja | Dodane pole/pola | Dodane indeksy |
179
+ |---|---|---|
180
+ | `itemOf: { what: Device }` | `device` | `byDevice` |
181
+ | `propertyOf: { what: Device }` | `device` | `byDevice` |
182
+ | `userItem` | `user` | `byUser` |
183
+ | `userProperty` | `user` | `byUser` |
184
+ | `sessionOrUserProperty` | `sessionOrUserType`, `sessionOrUser` | `bySessionOrUser` (hash) |
185
+ | `sessionOrUserProperty: { extendedWith: ['object'] }` | + `objectType`, `object` | indeksy złożone |
186
+ | `propertyOfAny: { ownerTypes: [...] }` | `ownerType`, `owner` | `byOwner` (hash) |
187
+ | `boundTo: { what: Device }` | `device` | `byDevice` (hash) |
188
+
189
+ Dla relacji z wieloma rodzicami (np. `propertyOf: [{ what: A }, { what: B }]`) tworzone są wszystkie kombinacje indeksów (`byA`, `byB`, `byAAndB`).
190
+
191
+ ## `propertyOfAny` — typy rodzica (`{name}Types`)
192
+
193
+ `propertyOfAny` jest relacją polimorficzną. Lista dozwolonych typów jest podawana w polach `{name}Types`, gdzie `{name}` pochodzi z `to`.
194
+
195
+ - Gdy `to` nie jest podane, domyślnie jest `['owner']`, więc użyj `ownerTypes`.
196
+ - Gdy `to: ['invoice']`, użyj `invoiceTypes`.
197
+
198
+ ```js
199
+ // domyślnie to: ['owner']
200
+ propertyOfAny: {
201
+ ownerTypes: ['invoice_CostInvoice', 'invoice_IncomeInvoice']
202
+ }
203
+
204
+ // jawnie
205
+ propertyOfAny: {
206
+ to: ['invoice'],
207
+ invoiceTypes: ['invoice_CostInvoice', 'invoice_IncomeInvoice']
208
+ }
209
+ ```
210
+
211
+ ```js
212
+ // ✅ Poprawnie — definiuj tylko SWOJE pola
213
+ definition.model({
214
+ name: 'Connection',
215
+ properties: {
216
+ status: { type: String } // 'device' NIE jest tutaj — dodaje go itemOf
217
+ },
218
+ itemOf: { what: Device } // automatycznie dodaje pole 'device' + indeks 'byDevice'
219
+ })
220
+
221
+ // ❌ Źle — redundantne pole
222
+ definition.model({
223
+ name: 'Connection',
224
+ properties: {
225
+ device: { type: String }, // ❌ już dodane przez itemOf
226
+ status: { type: String }
227
+ },
228
+ itemOf: { what: Device }
229
+ })
230
+ ```
231
+
232
+ Użyj `node server/start.js describe --service myService --model MyModel --output yaml` żeby zobaczyć wszystkie pola łącznie z automatycznie dodanymi.
233
+
172
234
  ## Indeksy
173
235
 
174
236
  - Definiuj indeksy jawnie w modelu, gdy będziesz często wyszukiwać po danym polu lub kombinacji pól.