@live-change/frontend-template 0.9.197 → 0.9.199

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 (46) hide show
  1. package/.claude/rules/live-change-backend-actions-views-triggers.md +184 -0
  2. package/.claude/rules/live-change-backend-architecture.md +126 -0
  3. package/.claude/rules/live-change-backend-models-and-relations.md +188 -0
  4. package/.claude/rules/live-change-frontend-vue-primevue.md +291 -0
  5. package/.claude/rules/live-change-service-structure.md +89 -0
  6. package/.claude/skills/create-skills-and-rules.md +196 -0
  7. package/.claude/skills/live-change-design-actions-views-triggers.md +190 -0
  8. package/.claude/skills/live-change-design-models-relations.md +173 -0
  9. package/.claude/skills/live-change-design-service.md +132 -0
  10. package/.claude/skills/live-change-frontend-action-buttons.md +128 -0
  11. package/.claude/skills/live-change-frontend-action-form.md +143 -0
  12. package/.claude/skills/live-change-frontend-analytics.md +146 -0
  13. package/.claude/skills/live-change-frontend-command-forms.md +215 -0
  14. package/.claude/skills/live-change-frontend-data-views.md +182 -0
  15. package/.claude/skills/live-change-frontend-editor-form.md +177 -0
  16. package/.claude/skills/live-change-frontend-locale-time.md +171 -0
  17. package/.claude/skills/live-change-frontend-page-list-detail.md +200 -0
  18. package/.claude/skills/live-change-frontend-range-list.md +128 -0
  19. package/.claude/skills/live-change-frontend-ssr-setup.md +118 -0
  20. package/.cursor/rules/live-change-backend-actions-views-triggers.mdc +202 -0
  21. package/.cursor/rules/live-change-backend-architecture.mdc +131 -0
  22. package/.cursor/rules/live-change-backend-models-and-relations.mdc +194 -0
  23. package/.cursor/rules/live-change-frontend-vue-primevue.mdc +290 -0
  24. package/.cursor/rules/live-change-service-structure.mdc +107 -0
  25. package/.cursor/skills/live-change-design-actions-views-triggers.md +197 -0
  26. package/.cursor/skills/live-change-design-models-relations.md +168 -0
  27. package/.cursor/skills/live-change-design-service.md +75 -0
  28. package/.cursor/skills/live-change-frontend-action-buttons.md +128 -0
  29. package/.cursor/skills/live-change-frontend-action-form.md +143 -0
  30. package/.cursor/skills/live-change-frontend-analytics.md +146 -0
  31. package/.cursor/skills/live-change-frontend-command-forms.md +215 -0
  32. package/.cursor/skills/live-change-frontend-data-views.md +182 -0
  33. package/.cursor/skills/live-change-frontend-editor-form.md +177 -0
  34. package/.cursor/skills/live-change-frontend-locale-time.md +171 -0
  35. package/.cursor/skills/live-change-frontend-page-list-detail.md +200 -0
  36. package/.cursor/skills/live-change-frontend-range-list.md +128 -0
  37. package/.cursor/skills/live-change-frontend-ssr-setup.md +119 -0
  38. package/README.md +71 -0
  39. package/package.json +50 -50
  40. package/server/app.config.js +35 -0
  41. package/server/services.list.js +2 -0
  42. package/.nx/workspace-data/file-map.json +0 -195
  43. package/.nx/workspace-data/nx_files.nxt +0 -0
  44. package/.nx/workspace-data/project-graph.json +0 -8
  45. package/.nx/workspace-data/project-graph.lock +0 -0
  46. package/.nx/workspace-data/source-maps.json +0 -1
@@ -0,0 +1,118 @@
1
+ ---
2
+ description: Set up SSR entry points, router, PrimeVue theme and Suspense data loading
3
+ ---
4
+
5
+ # Skill: live-change-frontend-ssr-setup (Claude Code)
6
+
7
+ Use this skill to set up or adjust a **LiveChange SSR frontend**:
8
+
9
+ - client/server entry points,
10
+ - router + `meta.signedIn`,
11
+ - PrimeVue theme configuration.
12
+
13
+ ## Step 1 – Client and server entry points
14
+
15
+ 1. Ensure the frontend has two entry files:
16
+ - `entry-client.js` (or `.ts`),
17
+ - `entry-server.js` (or `.ts`).
18
+
19
+ 2. Use helpers from `@live-change/frontend-base`:
20
+
21
+ ```js
22
+ // entry-client.js
23
+ import { clientEntry } from '@live-change/frontend-base/client-entry.js'
24
+ import App from './App.vue'
25
+ import { createRouter } from './router.js'
26
+ import { config } from './config.js'
27
+
28
+ export default clientEntry(App, createRouter, config)
29
+ ```
30
+
31
+ ```js
32
+ // entry-server.js
33
+ import { serverEntry, sitemapEntry } from '@live-change/frontend-base/server-entry.js'
34
+ import App from './App.vue'
35
+ import { createRouter, routerSitemap } from './router.js'
36
+ import { config } from './config.js'
37
+
38
+ export const render = serverEntry(App, createRouter, config)
39
+ export const sitemap = sitemapEntry(App, createRouter, routerSitemap, config)
40
+ ```
41
+
42
+ ## Step 2 – Router and `meta.signedIn`
43
+
44
+ 1. Use `vite-plugin-pages` to auto-generate routes from `src/pages/`.
45
+ 2. Add a `<route>` block to each page with basic meta:
46
+
47
+ ```vue
48
+ <route>
49
+ { "name": "devices", "meta": { "signedIn": true } }
50
+ </route>
51
+ ```
52
+
53
+ 3. Add a navigation guard for signed-in pages:
54
+
55
+ ```js
56
+ router.beforeEach((to) => {
57
+ if(to.meta.signedIn && !isLoggedIn()) {
58
+ localStorage.setItem('redirectAfterLogin', to.fullPath)
59
+ return { name: 'user:signIn' }
60
+ }
61
+ })
62
+ ```
63
+
64
+ Implement `isLoggedIn()` according to the project’s auth/session model.
65
+
66
+ ## Step 3 – PrimeVue theme configuration
67
+
68
+ 1. In `config.js`, configure the PrimeVue theme using `definePreset`:
69
+
70
+ ```js
71
+ import { definePreset } from '@primevue/themes'
72
+ import Aura from '@primevue/themes/aura'
73
+
74
+ const MyPreset = definePreset(Aura, {
75
+ semantic: {
76
+ primary: {
77
+ 50: '{indigo.50}',
78
+ 100: '{indigo.100}',
79
+ 200: '{indigo.200}',
80
+ 300: '{indigo.300}',
81
+ 400: '{indigo.400}',
82
+ 500: '{indigo.500}',
83
+ 600: '{indigo.600}',
84
+ 700: '{indigo.700}',
85
+ 800: '{indigo.800}',
86
+ 900: '{indigo.900}',
87
+ 950: '{indigo.950}'
88
+ }
89
+ }
90
+ })
91
+
92
+ export const config = {
93
+ theme: {
94
+ preset: MyPreset,
95
+ options: {
96
+ darkModeSelector: '.app-dark-mode'
97
+ }
98
+ }
99
+ }
100
+ ```
101
+
102
+ 2. Ensure the app uses this config when initializing PrimeVue (usually in `App.vue` / main entry).
103
+
104
+ ## Step 4 – Global components and forms
105
+
106
+ 1. In `App.vue` (or main setup), register global components used throughout the app:
107
+ - auto-form components,
108
+ - common layout components, etc.
109
+ 2. This allows pages to use components like `<command-form>` without local imports.
110
+
111
+ ## Step 5 – SSR-friendly data loading
112
+
113
+ 1. Ensure the root of the app (e.g. `ViewRoot`) wraps content in `<Suspense>`.
114
+ 2. In page components:
115
+ - use `await Promise.all([live(path()....)])` inside `script setup`,
116
+ - read from `.value` in templates,
117
+ - **do not** fetch main data in `onMounted`.
118
+
@@ -0,0 +1,202 @@
1
+ ---
2
+ description: Rules for implementing actions, views, and triggers in LiveChange services
3
+ globs: **/services/**/*.js
4
+ alwaysApply: false
5
+ ---
6
+
7
+ # LiveChange backend – akcje, widoki, triggery
8
+
9
+ ## Akcje – ogólne zasady
10
+
11
+ - Akcje umieszczaj w plikach domenowych, razem z modelami, których dotyczą.
12
+ - Każda akcja powinna:
13
+ - jasno określać wejście (`properties`),
14
+ - wykonywać minimalną potrzebną walidację,
15
+ - używać indeksów zamiast pełnych skanów,
16
+ - zwracać sensowny wynik (ID obiektu, fragment stanu itp.).
17
+
18
+ ### Wzorzec prostej akcji
19
+
20
+ ```js
21
+ definition.action({
22
+ name: 'registerDeviceConnection',
23
+ properties: {
24
+ pairingKey: {
25
+ type: String
26
+ },
27
+ connectionType: {
28
+ type: String
29
+ },
30
+ capabilities: {
31
+ type: Array
32
+ }
33
+ },
34
+ async execute({ pairingKey, connectionType, capabilities }, { client, service }) {
35
+ const device = await Device.indexObjectGet('byPairingKey', { pairingKey })
36
+ if(!device) throw new Error('notFound')
37
+
38
+ const id = app.generateUid()
39
+ const sessionKey = app.generateUid() + app.generateUid()
40
+
41
+ await DeviceConnection.create({
42
+ id,
43
+ device: device.id,
44
+ connectionType,
45
+ capabilities,
46
+ sessionKey,
47
+ status: 'offline'
48
+ })
49
+
50
+ return { connectionId: id, sessionKey }
51
+ }
52
+ })
53
+ ```
54
+
55
+ ## Widoki – ogólne zasady
56
+
57
+ - Widok ma być prostym, czytelnym wejściem do danych:
58
+ - korzystaj z indeksów (`indexObjectGet`, `indexRangeGet`),
59
+ - nie implementuj skomplikowanej logiki w widokach, jeśli można ją przenieść do akcji.
60
+
61
+ ### Wzorzec widoku zakresowego
62
+
63
+ ```js
64
+ definition.view({
65
+ name: 'pendingCommands',
66
+ properties: {
67
+ connectionId: {
68
+ type: String
69
+ }
70
+ },
71
+ async get({ connectionId }, { client, service }) {
72
+ return BotCommand.indexRangeGet('byConnectionAndStatus', {
73
+ connection: connectionId,
74
+ status: 'pending'
75
+ })
76
+ }
77
+ })
78
+ ```
79
+
80
+ ## Triggery – online/offline i batchowanie
81
+
82
+ - Triggery są do reakcji na zdarzenia (np. zmiana stanu sesji, start serwera).
83
+ - Dwie podstawowe kategorie:
84
+ - triggery dla pojedynczych obiektów (np. połączenie online/offline),
85
+ - triggery „hurtowe” (np. ustawienie wszystkich na offline przy starcie).
86
+
87
+ ### Wzorzec triggerów online/offline
88
+
89
+ ```js
90
+ definition.trigger({
91
+ name: 'sessionDeviceConnectionOnline',
92
+ properties: {
93
+ connection: {
94
+ type: String
95
+ }
96
+ },
97
+ async execute({ connection }, { service }) {
98
+ await DeviceConnection.update(connection, {
99
+ status: 'online',
100
+ lastSeenAt: new Date()
101
+ })
102
+ }
103
+ })
104
+
105
+ definition.trigger({
106
+ name: 'sessionDeviceConnectionOffline',
107
+ properties: {
108
+ connection: {
109
+ type: String
110
+ }
111
+ },
112
+ async execute({ connection }, { service }) {
113
+ await DeviceConnection.update(connection, {
114
+ status: 'offline'
115
+ })
116
+ }
117
+ })
118
+ ```
119
+
120
+ ### Wzorzec batchowania odczytów (unikaj pełnych skanów)
121
+
122
+ - Przy triggerach, które mają przejść po wielu rekordach, **zawsze** używaj paginacji:
123
+ - stały `limit` (np. 32 lub 128),
124
+ - zakres po kluczu (`gt: lastId`),
125
+ - pętla aż do pustego batcha.
126
+
127
+ ```js
128
+ definition.trigger({
129
+ name: 'allOffline',
130
+ async execute({}, { service }) {
131
+ let last = ''
132
+ while(true) {
133
+ const connections = await DeviceConnection.rangeGet({
134
+ gt: last,
135
+ limit: 32
136
+ })
137
+ if(connections.length === 0) break
138
+
139
+ for(const conn of connections) {
140
+ await DeviceConnection.update(conn.id, {
141
+ status: 'offline'
142
+ })
143
+ }
144
+
145
+ last = connections[connections.length - 1].id
146
+ }
147
+ }
148
+ })
149
+ ```
150
+
151
+ ## Wzorzec „pending + resolve” (asynchroniczny wynik)
152
+
153
+ - Używaj, gdy akcja w serwisie musi poczekać na wynik z zewnętrznego procesu (np. urządzenie, worker).
154
+ - Struktura:
155
+ 1. Akcja tworzy rekord „pending”.
156
+ 2. Akcja czeka na `Promise` powiązany z ID.
157
+ 3. Inna akcja/trigger aktualizuje rekord i rozwiązuje `Promise`.
158
+
159
+ Szkic:
160
+
161
+ ```js
162
+ // pendingCommands.js – singleton w pamięci
163
+ const pendingCommands = new Map()
164
+
165
+ export function waitForCommand(commandId, timeoutMs = 115000) {
166
+ return new Promise((resolve, reject) => {
167
+ const timer = setTimeout(() => {
168
+ pendingCommands.delete(commandId)
169
+ reject(new Error('timeout'))
170
+ }, timeoutMs)
171
+ pendingCommands.set(commandId, { resolve, reject, timer })
172
+ })
173
+ }
174
+
175
+ export function resolveCommand(commandId, result) {
176
+ const pending = pendingCommands.get(commandId)
177
+ if(pending) {
178
+ clearTimeout(pending.timer)
179
+ pendingCommands.delete(commandId)
180
+ pending.resolve(result)
181
+ }
182
+ }
183
+ ```
184
+
185
+ W akcji wywołującej:
186
+
187
+ ```js
188
+ await SomeCommand.create({ id, status: 'pending', ... })
189
+ const result = await waitForCommand(id)
190
+ return result
191
+ ```
192
+
193
+ W akcji raportującej:
194
+
195
+ ```js
196
+ await SomeCommand.update(id, {
197
+ status: 'completed',
198
+ result
199
+ })
200
+ resolveCommand(id, result)
201
+ ```
202
+
@@ -0,0 +1,131 @@
1
+ ---
2
+ description: Rules for LiveChange backend service architecture and directory structure
3
+ globs: **/services/**/*.js
4
+ alwaysApply: false
5
+ ---
6
+
7
+ # LiveChange backend – architektura serwisów
8
+
9
+ ## Główna zasada
10
+
11
+ - Każdy serwis LiveChange **musi być katalogiem**, nie pojedynczym plikiem.
12
+ - Serwis ma czytelną, powtarzalną strukturę plików.
13
+
14
+ ## Struktura katalogu serwisu
15
+
16
+ ```
17
+ server/services/<serviceName>/
18
+ definition.js # createServiceDefinition + use – żadnych modeli/akcji
19
+ index.js # import definition, import plików domenowych, export definition
20
+ config.js # opcjonalnie – rozwiązywanie definition.config do plain object
21
+ <domain>.js # pliki domenowe: modele, widoki, akcje, triggery
22
+ ```
23
+
24
+ ### `definition.js`
25
+
26
+ - Importuj `app` z `@live-change/framework`.
27
+ - Twórz definicję serwisu **bez** modeli/akcji/widoków w tym pliku.
28
+ - Jeśli serwis używa relacji lub kontroli dostępu, **od razu** podłącz pluginy w `use`.
29
+
30
+ Przykład:
31
+
32
+ ```js
33
+ import { app } from '@live-change/framework'
34
+ import relationsPlugin from '@live-change/relations-plugin'
35
+ import accessControlService from '@live-change/access-control-service'
36
+
37
+ const definition = app.createServiceDefinition({
38
+ name: 'myService',
39
+ use: [relationsPlugin, accessControlService]
40
+ })
41
+
42
+ export default definition
43
+ ```
44
+
45
+ ### `index.js`
46
+
47
+ - Importuj `definition`.
48
+ - Importuj wszystkie pliki domenowe tylko po to, żeby wykonały się side-effecty (rejestracja modeli/akcji/widoków/triggerów).
49
+ - Eksportuj `definition` jako default.
50
+
51
+ ```js
52
+ import definition from './definition.js'
53
+
54
+ import './modelA.js'
55
+ import './modelB.js'
56
+ import './authenticator.js'
57
+
58
+ export default definition
59
+ ```
60
+
61
+ ### `config.js` (opcjonalnie)
62
+
63
+ - Czyta `definition.config` (ustawiane w `app.config.js`).
64
+ - Nadaje wartości domyślne i eksportuje zwykły obiekt.
65
+
66
+ ```js
67
+ import definition from './definition.js'
68
+
69
+ const {
70
+ someOption = 'default'
71
+ } = definition.config
72
+
73
+ export default { someOption }
74
+ ```
75
+
76
+ ## Rejestracja serwisu w projekcie
77
+
78
+ ### `services.list.js`
79
+
80
+ - Importuj serwis z jego `index.js` w katalogu, nie z płaskiego pliku.
81
+
82
+ ```js
83
+ import myService from './services/myService/index.js'
84
+
85
+ export default {
86
+ // ...
87
+ myService
88
+ }
89
+ ```
90
+
91
+ ### `app.config.js`
92
+
93
+ - Upewnij się, że nazwa serwisu w configu odpowiada nazwie z `createServiceDefinition`.
94
+ - Zachowaj **sensowną kolejność** serwisów:
95
+ - na początku serwisy bazowe, wspólne i pluginy,
96
+ - na końcu serwisy właściwe dla aplikacji, które z nich korzystają.
97
+
98
+ ```js
99
+ services: [
100
+ // serwisy bazowe / wspólne
101
+ { name: 'user' },
102
+ { name: 'session' },
103
+ { name: 'accessControl' },
104
+ // ...
105
+ // serwisy własne aplikacji
106
+ { name: 'myService' }
107
+ ]
108
+ ```
109
+
110
+ ## Podgląd serwisów komendą `describe`
111
+
112
+ Komenda CLI `describe` pokazuje co framework wygenerował z twoich definicji (modele, widoki, akcje, triggery, indeksy, eventy):
113
+
114
+ ```bash
115
+ # Przegląd wszystkich serwisów
116
+ node server/start.js describe
117
+
118
+ # Jeden serwis w YAML (z wygenerowanym kodem)
119
+ node server/start.js describe --service myService --output yaml
120
+
121
+ # Konkretna encja
122
+ node server/start.js describe --service myService --model MyModel --output yaml
123
+ ```
124
+
125
+ Szczególnie przydatne po użyciu relacji (`userItem`, `itemOf`, `propertyOf`) — `describe` pokaże wszystkie automatycznie wygenerowane widoki, akcje, triggery i indeksy.
126
+
127
+ ## Kiedy tworzyć nowy serwis
128
+
129
+ - Gdy masz wyraźnie wydzieloną domenę (np. urządzenia, płatności, powiadomienia).
130
+ - Gdy zestaw modeli/akcji/widoków ma własną konfigurację i zależności.
131
+ - Gdy logika nie mieści się sensownie w istniejącym serwisie bez mieszania odpowiedzialności.
@@ -0,0 +1,194 @@
1
+ ---
2
+ description: Rules for defining models, relations, indexes and access control in LiveChange
3
+ globs: **/services/**/*.js
4
+ alwaysApply: false
5
+ ---
6
+
7
+ # LiveChange backend – modele i relacje
8
+
9
+ ## Ogólne zasady
10
+
11
+ - Modele zapisuj w plikach domenowych (`<domain>.js`) importowanych w `index.js` serwisu.
12
+ - Dbaj o czytelność definicji – **właściwości wieloliniowo**, z jasnymi polami `type`, `default`, `validation` itd.
13
+ - Tam, gdzie to możliwe, używaj relacji (`userItem`, `itemOf`, `propertyOf`, `foreignModel`) zamiast ręcznego klepania CRUD i widoków.
14
+
15
+ ## Styl definicji `properties`
16
+
17
+ - Każde pole opisane wieloliniowo, jedna rzecz na linię.
18
+ - Unikaj upychania typu i walidacji w jednej linii, jeśli zmniejsza to czytelność.
19
+
20
+ ```js
21
+ properties: {
22
+ name: {
23
+ type: String,
24
+ validation: ['nonEmpty']
25
+ },
26
+ status: {
27
+ type: String,
28
+ default: 'offline'
29
+ },
30
+ capabilities: {
31
+ type: Array,
32
+ of: {
33
+ type: String
34
+ }
35
+ }
36
+ }
37
+ ```
38
+
39
+ ## `userItem` – zasób należący do użytkownika
40
+
41
+ Używaj, gdy model należy do zalogowanego użytkownika.
42
+
43
+ ```js
44
+ definition.model({
45
+ name: 'Device',
46
+ properties: {
47
+ name: {
48
+ type: String
49
+ }
50
+ },
51
+ userItem: {
52
+ readAccessControl: { roles: ['owner', 'admin'] },
53
+ writeAccessControl: { roles: ['owner', 'admin'] },
54
+ writeableProperties: ['name']
55
+ }
56
+ })
57
+ ```
58
+
59
+ Skutek:
60
+
61
+ - automatyczne widoki typu „moje X”,
62
+ - automatyczne akcje create/update/delete dla właściciela.
63
+
64
+ ## `itemOf` – dziecko należy do rodzica
65
+
66
+ Używaj, gdy model jest listą elementów powiązanych z innym modelem.
67
+
68
+ ```js
69
+ definition.model({
70
+ name: 'DeviceConnection',
71
+ properties: {
72
+ connectionType: {
73
+ type: String
74
+ },
75
+ status: {
76
+ type: String,
77
+ default: 'offline'
78
+ }
79
+ },
80
+ itemOf: {
81
+ what: Device,
82
+ readAccessControl: { roles: ['owner', 'admin'] },
83
+ writeAccessControl: { roles: ['owner', 'admin'] }
84
+ }
85
+ })
86
+ ```
87
+
88
+ Zasady:
89
+
90
+ - rodzic `what` to model zadeklarowany wcześniej w tym lub innym serwisie,
91
+ - relacja generuje standardowe widoki/akcje dla listy i elementów.
92
+
93
+ ## `propertyOf` – właściwość z ID równym rodzicowi
94
+
95
+ Używaj, gdy model przechowuje „stan” jednego obiektu (ID dziecka = ID rodzica).
96
+
97
+ ```js
98
+ definition.model({
99
+ name: 'DeviceCursorState',
100
+ properties: {
101
+ x: { type: Number },
102
+ y: { type: Number }
103
+ },
104
+ propertyOf: {
105
+ what: Device,
106
+ readAccessControl: { roles: ['owner', 'admin'] },
107
+ writeAccessControl: { roles: ['owner', 'admin'] }
108
+ }
109
+ })
110
+ ```
111
+
112
+ Skutek:
113
+
114
+ - prosty dostęp: `DeviceCursorState.get(deviceId)`,
115
+ - bez potrzeby dodatkowych indeksów po polu `device`.
116
+
117
+ ## `propertyOf` dla wielu modeli (relacja 1:1 do każdego z nich)
118
+
119
+ W niektórych domenach model jest „łącznikiem 1:1” pomiędzy rekordami (np. faktura ↔ kontrahent w roli dostawcy/klienta).
120
+ Najczęściej są to 2 modele, ale `propertyOf` może wskazywać na **dowolnie wiele** modeli (np. relacja łącząca 3+ encje), jeśli taka jest semantyka domeny.
121
+
122
+ Wtedy:
123
+
124
+ - **nie** przechowuj „drugiej strony” jako zwykłego `contractorId` (albo ogólnie `someId`) w `properties`
125
+ - **nie** dodawaj ręcznie pól `...Id` w modelu relacyjnym, jeśli to ma być relacja – to utrudnia generatorowi CRUD/relacji poprawne wnioskowanie o powiązaniach
126
+ - zamiast tego zdefiniuj relacje jako `propertyOf` do **każdego** z modeli, które mają być rodzicami relacji
127
+
128
+ To jest istotne, bo generator CRUD/relacji rozumie wtedy, że encja jest powiązaniem pomiędzy dwiema encjami, a nie „zwykłym obiektem z polem id”.
129
+
130
+ Przykład (schematycznie):
131
+
132
+ ```js
133
+ const CostInvoice = definition.foreignModel('invoice', 'CostInvoice')
134
+ const Contractor = definition.foreignModel('company', 'Contractor')
135
+
136
+ definition.model({
137
+ name: 'Supplier',
138
+ properties: {
139
+ // dodatkowe pola relacji (opcjonalnie)
140
+ },
141
+ propertyOf: [
142
+ { what: CostInvoice },
143
+ { what: Contractor }
144
+ ]
145
+ })
146
+ ```
147
+
148
+ ## `foreignModel` – relacja do modelu z innego serwisu
149
+
150
+ Używaj, gdy `itemOf`/`propertyOf` ma wskazywać na model spoza aktualnego serwisu.
151
+
152
+ ```js
153
+ const Device = definition.foreignModel('deviceManager', 'Device')
154
+
155
+ definition.model({
156
+ name: 'BotSession',
157
+ properties: {
158
+ // ...
159
+ },
160
+ itemOf: {
161
+ what: Device,
162
+ readAccessControl: { roles: ['owner', 'admin'] }
163
+ }
164
+ })
165
+ ```
166
+
167
+ Zasady:
168
+
169
+ - pierwszy argument to nazwa serwisu,
170
+ - drugi to nazwa modelu w tamtym serwisie.
171
+
172
+ ## Indeksy
173
+
174
+ - Definiuj indeksy jawnie w modelu, gdy będziesz często wyszukiwać po danym polu lub kombinacji pól.
175
+ - Nazwy indeksów powinny być opisowe, bez skrótów trudnych do odczytania.
176
+
177
+ ```js
178
+ indexes: {
179
+ bySessionKey: {
180
+ property: ['sessionKey']
181
+ },
182
+ byDeviceAndStatus: {
183
+ property: ['device', 'status']
184
+ }
185
+ }
186
+ ```
187
+
188
+ Poza serwisem indeks bywa widoczny pod nazwą z prefiksem serwisu, np. `myService_Model_byDeviceAndStatus`.
189
+
190
+ ## Access control na relacjach
191
+
192
+ - Zawsze ustawiaj `readAccessControl` i `writeAccessControl` na relacjach (`userItem`, `itemOf`, `propertyOf`), zamiast polegać na domyślnym zachowaniu.
193
+ - Traktuj to jako część modelu, nie dodatek na końcu.
194
+