@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,296 @@
1
+ ---
2
+ name: live-change-design-actions-views-triggers
3
+ description: Design actions, views, triggers with indexes and batch processing patterns
4
+ ---
5
+
6
+ # Skill: live-change-design-actions-views-triggers (Claude Code)
7
+
8
+ Use this skill to design **actions, views, and triggers** in LiveChange services while making good use of indexes and avoiding full-table scans.
9
+
10
+ ## When to use
11
+
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).
15
+
16
+ ## Step 1 – Design an action
17
+
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.
31
+
32
+ Example:
33
+
34
+ ```js
35
+ definition.action({
36
+ name: 'someAction',
37
+ properties: {
38
+ someKey: { type: String }
39
+ },
40
+ async execute({ someKey }, { client, service }) {
41
+ const obj = await SomeModel.indexObjectGet('bySomeKey', { someKey })
42
+ if(!obj) throw new Error('notFound')
43
+
44
+ const id = app.generateUid()
45
+
46
+ await SomeOtherModel.create({
47
+ id
48
+ // ...
49
+ })
50
+
51
+ return { id }
52
+ }
53
+ })
54
+ ```
55
+
56
+ ## Step 2 – Design a view
57
+
58
+ 1. Decide what kind of data source you have, then pick the **view variant** (exactly one):
59
+
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`. |
65
+
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
75
+
76
+ ### Example: `daoPath` (preferred, DAO-backed)
77
+
78
+ ```js
79
+ definition.view({
80
+ name: 'costInvoice',
81
+ properties: {
82
+ costInvoice: {
83
+ type: String
84
+ }
85
+ },
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)
122
+ }
123
+ })
124
+ ```
125
+
126
+ ### Anti-pattern: `get` without `observable` (do not do this)
127
+
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
+ ```
140
+
141
+ ## Step 3 – Online/offline triggers
142
+
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.).
148
+
149
+ Example:
150
+
151
+ ```js
152
+ definition.trigger({
153
+ name: 'sessionConnectionOnline',
154
+ properties: {
155
+ connection: { type: String }
156
+ },
157
+ async execute({ connection }, { service }) {
158
+ await Connection.update(connection, {
159
+ status: 'online',
160
+ lastSeenAt: new Date()
161
+ })
162
+ }
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
+ })
176
+ ```
177
+
178
+ ## Step 4 – Batch triggers (avoid full scans)
179
+
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.
185
+
186
+ Example:
187
+
188
+ ```js
189
+ definition.trigger({
190
+ name: 'allOffline',
191
+ async execute({}, { service }) {
192
+ let last = ''
193
+ while(true) {
194
+ const items = await Connection.rangeGet({
195
+ gt: last,
196
+ limit: 32
197
+ })
198
+ if(items.length === 0) break
199
+
200
+ for(const item of items) {
201
+ await Connection.update(item.id, { status: 'offline' })
202
+ }
203
+
204
+ last = items[items.length - 1].id
205
+ }
206
+ }
207
+ })
208
+ ```
209
+
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
255
+
256
+ ## Step 6 – Pending + resolve pattern for async results
257
+
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.
259
+
260
+ ### Steps
261
+
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)`.
271
+
272
+ Helper sketch:
273
+
274
+ ```js
275
+ const pendingCommands = new Map()
276
+
277
+ export function waitForCommand(commandId, timeoutMs = 115000) {
278
+ return new Promise((resolve, reject) => {
279
+ const timer = setTimeout(() => {
280
+ pendingCommands.delete(commandId)
281
+ reject(new Error('timeout'))
282
+ }, timeoutMs)
283
+ pendingCommands.set(commandId, { resolve, reject, timer })
284
+ })
285
+ }
286
+
287
+ export function resolveCommand(commandId, result) {
288
+ const pending = pendingCommands.get(commandId)
289
+ if(pending) {
290
+ clearTimeout(pending.timer)
291
+ pendingCommands.delete(commandId)
292
+ pending.resolve(result)
293
+ }
294
+ }
295
+ ```
296
+
@@ -0,0 +1,230 @@
1
+ ---
2
+ name: live-change-design-models-relations
3
+ description: Design models with userItem, itemOf, propertyOf relations and access control
4
+ ---
5
+
6
+ # Skill: live-change-design-models-relations (Claude Code)
7
+
8
+ Use this skill when you design or refactor **models and relations** in a LiveChange service.
9
+
10
+ ## When to use
11
+
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.
15
+
16
+ ## Step 1 – Decide the relation type
17
+
18
+ For each new model, decide how it relates to the rest of the domain:
19
+
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.
24
+
25
+ Choose one main relation; other associations can be plain fields + indexes.
26
+
27
+ ## Step 2 – Define `properties` clearly
28
+
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:
32
+
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` |
40
+
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:
44
+
45
+ ```js
46
+ // ✅ Only define YOUR fields — 'device' is auto-added by itemOf
47
+ properties: {
48
+ name: {
49
+ type: String,
50
+ validation: ['nonEmpty']
51
+ },
52
+ status: {
53
+ type: String,
54
+ default: 'offline'
55
+ },
56
+ capabilities: {
57
+ type: Array,
58
+ of: {
59
+ type: String
60
+ }
61
+ }
62
+ }
63
+ ```
64
+
65
+ ## Step 3 – Configure the relation
66
+
67
+ ### `userItem`
68
+
69
+ 1. Add a `userItem` block inside the model definition.
70
+ 2. Set roles for read/write and list which fields can be written.
71
+
72
+ ```js
73
+ userItem: {
74
+ readAccessControl: { roles: ['owner', 'admin'] },
75
+ writeAccessControl: { roles: ['owner', 'admin'] },
76
+ writeableProperties: ['name']
77
+ }
78
+ ```
79
+
80
+ ### `itemOf`
81
+
82
+ 1. Decide the parent model.
83
+ 2. If the parent is in another service, declare it via `foreignModel` (see next step).
84
+
85
+ ```js
86
+ itemOf: {
87
+ what: Device,
88
+ readAccessControl: { roles: ['owner', 'admin'] },
89
+ writeAccessControl: { roles: ['owner', 'admin'] }
90
+ }
91
+ ```
92
+
93
+ ### `propertyOf`
94
+
95
+ 1. Use when the child should share the same id as the parent.
96
+ 2. This simplifies lookups and avoids extra indexes.
97
+
98
+ ```js
99
+ propertyOf: {
100
+ what: Device,
101
+ readAccessControl: { roles: ['owner', 'admin'] },
102
+ writeAccessControl: { roles: ['owner', 'admin'] }
103
+ }
104
+ ```
105
+
106
+ ### `propertyOf` with multiple parents (1:1 link to each)
107
+
108
+ Use this when a model should act as a dedicated 1:1 link between multiple entities (e.g. invoice ↔ contractor role links),
109
+ so the relations/CRUD generator can treat it as a relation rather than a plain `someId` property.
110
+
111
+ Notes:
112
+
113
+ - Usually you’ll have 1–2 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.
115
+
116
+ Example:
117
+
118
+ ```js
119
+ const CostInvoice = definition.foreignModel('invoice', 'CostInvoice')
120
+ const Contractor = definition.foreignModel('company', 'Contractor')
121
+
122
+ definition.model({
123
+ name: 'Supplier',
124
+ properties: {
125
+ // optional extra fields
126
+ },
127
+ propertyOf: [
128
+ { what: CostInvoice },
129
+ { what: Contractor }
130
+ ]
131
+ })
132
+ ```
133
+
134
+ ## Step 4 – Use `foreignModel` for cross-service relations
135
+
136
+ 1. At the top of the domain file, declare:
137
+
138
+ ```js
139
+ const Device = definition.foreignModel('deviceManager', 'Device')
140
+ ```
141
+
142
+ 2. Then use `Device` in `itemOf` or `propertyOf`:
143
+
144
+ ```js
145
+ itemOf: {
146
+ what: Device,
147
+ readAccessControl: { roles: ['owner', 'admin'] }
148
+ }
149
+ ```
150
+
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:
157
+
158
+ ```js
159
+ indexes: {
160
+ bySessionKey: {
161
+ property: ['sessionKey']
162
+ },
163
+ byDeviceAndStatus: {
164
+ property: ['device', 'status']
165
+ }
166
+ }
167
+ ```
168
+
169
+ 3. Use these indexes in views/actions, via `indexObjectGet` / `indexRangeGet`.
170
+
171
+ ## Step 6 – Set access control on relations
172
+
173
+ 1. For `userItem`, `itemOf`, and `propertyOf`, always define:
174
+ - `readAccessControl`,
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.
181
+
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
+ ```
219
+
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.
221
+
222
+ ## Step 8 – Check auto-generated views/actions
223
+
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.
230
+
@@ -0,0 +1,76 @@
1
+ ---
2
+ name: live-change-design-service
3
+ description: Create or restructure a LiveChange backend service with proper directory layout
4
+ ---
5
+
6
+ # Skill: live-change-design-service
7
+
8
+ Ten skill opisuje **krok po kroku**, jak zaprojektować nowy serwis w LiveChange / live-change-stack albo sensownie rozbudować istniejący.
9
+
10
+ ## Kiedy używać
11
+
12
+ Użyj tego skilla, gdy:
13
+
14
+ - dodajesz **nowy serwis domenowy** do projektu,
15
+ - przenosisz większy kawałek logiki z innego serwisu,
16
+ - potrzebujesz upewnić się, że struktura plików i rejestracja serwisu są zgodne z konwencją.
17
+
18
+ ## Kroki – nowy serwis
19
+
20
+ 1. **Nazwij serwis**
21
+ - Wybierz zwięzłą, domenową nazwę, np. `payments`, `notifications`, `deviceManager`.
22
+ - Nazwa będzie używana jako `name` w `createServiceDefinition` oraz w `app.config.js`.
23
+
24
+ 2. **Utwórz katalog serwisu**
25
+ - Ścieżka: `server/services/<serviceName>/`.
26
+ - W katalogu utwórz pliki:
27
+ - `definition.js`
28
+ - `index.js`
29
+ - opcjonalnie `config.js`
30
+ - pliki domenowe (np. `models.js`, `authenticator.js`, `actions.js`), jeśli potrzebne.
31
+
32
+ 3. **Zaimplementuj `definition.js`**
33
+ - Importuj `app` z `@live-change/framework`.
34
+ - Jeśli serwis korzysta z relacji lub access control:
35
+ - importuj `relationsPlugin` z `@live-change/relations-plugin`,
36
+ - importuj `accessControlService` z `@live-change/access-control-service`.
37
+ - Wywołaj `app.createServiceDefinition({ name, use })`.
38
+ - **Nie** deklaruj tu modeli, akcji ani widoków.
39
+
40
+ 4. **Zaimplementuj `index.js`**
41
+ - Importuj `definition` z `./definition.js`.
42
+ - Importuj wszystkie pliki domenowe (np. `./models.js`, `./authenticator.js`).
43
+ - Eksportuj `definition` jako `default`.
44
+ - Nie dodawaj innej logiki do `index.js`.
45
+
46
+ 5. **(Opcjonalnie) utwórz `config.js`**
47
+ - Importuj `definition`.
48
+ - Odczytaj `definition.config` i rozwiąż wartości domyślne.
49
+ - Eksportuj plain object z konfiguracją serwisu.
50
+
51
+ 6. **Dodaj serwis do `services.list.js`**
52
+ - Importuj z katalogu serwisu, nie z pojedynczego pliku.
53
+ - Dodaj serwis do eksportowanego obiektu.
54
+
55
+ 7. **Dodaj serwis do `app.config.js`**
56
+ - W sekcji `services` dodaj `{ name: '<serviceName>' }`.
57
+ - Upewnij się, że kolejność jest sensowna:
58
+ - serwisy bazowe/plugins (user, session, accessControl) na początku,
59
+ - serwisy domenowe zależne od nich – dalej.
60
+
61
+ 8. **Sprawdź zależności**
62
+ - Jeśli serwis korzysta z modeli w innych serwisach:
63
+ - użyj `definition.foreignModel` wewnątrz domenowych plików serwisu,
64
+ - nie importuj bezpośrednio ich plików modeli.
65
+ - Upewnij się, że serwisy, od których zależysz, są wcześniejsze w `app.config.js`.
66
+
67
+ ## Kroki – rozbudowa istniejącego serwisu
68
+
69
+ 1. Przejrzyj istniejący katalog `server/services/<serviceName>/`.
70
+ 2. Sprawdź, czy `definition.js` ma poprawne `use` (relacje, accessControl).
71
+ 3. Nowe modele/akcje/widoki/triggery dodaj do **osobnych plików domenowych**:
72
+ - jeśli logika jest powiązana z istniejącym modelem – do jego pliku,
73
+ - jeśli tworzysz większy nowy obszar – do nowego pliku (np. `notifications.js`).
74
+ 4. Upewnij się, że nowy plik jest importowany w `index.js`.
75
+ 5. Nie dodawaj ciężkiej logiki do `definition.js` ani `index.js`.
76
+