@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.
- package/.claude/rules/live-change-backend-actions-views-triggers.md +184 -0
- package/.claude/rules/live-change-backend-architecture.md +126 -0
- package/.claude/rules/live-change-backend-models-and-relations.md +188 -0
- package/.claude/rules/live-change-frontend-vue-primevue.md +291 -0
- package/.claude/rules/live-change-service-structure.md +89 -0
- package/.claude/skills/create-skills-and-rules.md +196 -0
- package/.claude/skills/live-change-design-actions-views-triggers.md +190 -0
- package/.claude/skills/live-change-design-models-relations.md +173 -0
- package/.claude/skills/live-change-design-service.md +132 -0
- package/.claude/skills/live-change-frontend-action-buttons.md +128 -0
- package/.claude/skills/live-change-frontend-action-form.md +143 -0
- package/.claude/skills/live-change-frontend-analytics.md +146 -0
- package/.claude/skills/live-change-frontend-command-forms.md +215 -0
- package/.claude/skills/live-change-frontend-data-views.md +182 -0
- package/.claude/skills/live-change-frontend-editor-form.md +177 -0
- package/.claude/skills/live-change-frontend-locale-time.md +171 -0
- package/.claude/skills/live-change-frontend-page-list-detail.md +200 -0
- package/.claude/skills/live-change-frontend-range-list.md +128 -0
- package/.claude/skills/live-change-frontend-ssr-setup.md +118 -0
- package/.cursor/rules/live-change-backend-actions-views-triggers.mdc +202 -0
- package/.cursor/rules/live-change-backend-architecture.mdc +131 -0
- package/.cursor/rules/live-change-backend-models-and-relations.mdc +194 -0
- package/.cursor/rules/live-change-frontend-vue-primevue.mdc +290 -0
- package/.cursor/rules/live-change-service-structure.mdc +107 -0
- package/.cursor/skills/live-change-design-actions-views-triggers.md +197 -0
- package/.cursor/skills/live-change-design-models-relations.md +168 -0
- package/.cursor/skills/live-change-design-service.md +75 -0
- package/.cursor/skills/live-change-frontend-action-buttons.md +128 -0
- package/.cursor/skills/live-change-frontend-action-form.md +143 -0
- package/.cursor/skills/live-change-frontend-analytics.md +146 -0
- package/.cursor/skills/live-change-frontend-command-forms.md +215 -0
- package/.cursor/skills/live-change-frontend-data-views.md +182 -0
- package/.cursor/skills/live-change-frontend-editor-form.md +177 -0
- package/.cursor/skills/live-change-frontend-locale-time.md +171 -0
- package/.cursor/skills/live-change-frontend-page-list-detail.md +200 -0
- package/.cursor/skills/live-change-frontend-range-list.md +128 -0
- package/.cursor/skills/live-change-frontend-ssr-setup.md +119 -0
- package/README.md +71 -0
- package/package.json +50 -50
- package/server/app.config.js +35 -0
- package/server/services.list.js +2 -0
- package/.nx/workspace-data/file-map.json +0 -195
- package/.nx/workspace-data/nx_files.nxt +0 -0
- package/.nx/workspace-data/project-graph.json +0 -8
- package/.nx/workspace-data/project-graph.lock +0 -0
- package/.nx/workspace-data/source-maps.json +0 -1
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Rules for implementing actions, views, and triggers in LiveChange services
|
|
3
|
+
globs: **/services/**/*.js
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# LiveChange backend – actions, views, triggers (Claude Code)
|
|
7
|
+
|
|
8
|
+
Use these rules when implementing actions, views, and triggers in LiveChange services.
|
|
9
|
+
|
|
10
|
+
## Actions
|
|
11
|
+
|
|
12
|
+
- Place actions in domain files, near the models they operate on.
|
|
13
|
+
- Each action should:
|
|
14
|
+
- declare clear input `properties`,
|
|
15
|
+
- perform minimal but necessary validation,
|
|
16
|
+
- use indexes instead of full scans,
|
|
17
|
+
- return a meaningful result (ids, data, etc.).
|
|
18
|
+
|
|
19
|
+
Example:
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
definition.action({
|
|
23
|
+
name: 'registerDeviceConnection',
|
|
24
|
+
properties: {
|
|
25
|
+
pairingKey: { type: String },
|
|
26
|
+
connectionType: { type: String },
|
|
27
|
+
capabilities: { type: Array }
|
|
28
|
+
},
|
|
29
|
+
async execute({ pairingKey, connectionType, capabilities }, { client, service }) {
|
|
30
|
+
const device = await Device.indexObjectGet('byPairingKey', { pairingKey })
|
|
31
|
+
if(!device) throw new Error('notFound')
|
|
32
|
+
|
|
33
|
+
const id = app.generateUid()
|
|
34
|
+
const sessionKey = app.generateUid() + app.generateUid()
|
|
35
|
+
|
|
36
|
+
await DeviceConnection.create({
|
|
37
|
+
id,
|
|
38
|
+
device: device.id,
|
|
39
|
+
connectionType,
|
|
40
|
+
capabilities,
|
|
41
|
+
sessionKey,
|
|
42
|
+
status: 'offline'
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
return { connectionId: id, sessionKey }
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Views
|
|
51
|
+
|
|
52
|
+
- Views should be simple query endpoints over models.
|
|
53
|
+
- Prefer `indexObjectGet` / `indexRangeGet` instead of scanning whole tables.
|
|
54
|
+
|
|
55
|
+
Example of a range view:
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
definition.view({
|
|
59
|
+
name: 'pendingCommands',
|
|
60
|
+
properties: {
|
|
61
|
+
connectionId: { type: String }
|
|
62
|
+
},
|
|
63
|
+
async get({ connectionId }, { client, service }) {
|
|
64
|
+
return BotCommand.indexRangeGet('byConnectionAndStatus', {
|
|
65
|
+
connection: connectionId,
|
|
66
|
+
status: 'pending'
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Triggers – online/offline and batch updates
|
|
73
|
+
|
|
74
|
+
- Use triggers for reacting to events (e.g. connection online/offline, server startup).
|
|
75
|
+
- There are two typical patterns:
|
|
76
|
+
- single-object triggers (update one record),
|
|
77
|
+
- batch triggers (update many records safely).
|
|
78
|
+
|
|
79
|
+
Online/offline example:
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
definition.trigger({
|
|
83
|
+
name: 'sessionDeviceConnectionOnline',
|
|
84
|
+
properties: {
|
|
85
|
+
connection: { type: String }
|
|
86
|
+
},
|
|
87
|
+
async execute({ connection }, { service }) {
|
|
88
|
+
await DeviceConnection.update(connection, {
|
|
89
|
+
status: 'online',
|
|
90
|
+
lastSeenAt: new Date()
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
definition.trigger({
|
|
96
|
+
name: 'sessionDeviceConnectionOffline',
|
|
97
|
+
properties: {
|
|
98
|
+
connection: { type: String }
|
|
99
|
+
},
|
|
100
|
+
async execute({ connection }, { service }) {
|
|
101
|
+
await DeviceConnection.update(connection, {
|
|
102
|
+
status: 'offline'
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Batch processing – always paginate
|
|
109
|
+
|
|
110
|
+
- Never load “all rows” in one go.
|
|
111
|
+
- Use a fixed `limit` (e.g. 32 or 128) and a cursor (`gt: lastId`) in a loop.
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
definition.trigger({
|
|
115
|
+
name: 'allOffline',
|
|
116
|
+
async execute({}, { service }) {
|
|
117
|
+
let last = ''
|
|
118
|
+
while(true) {
|
|
119
|
+
const connections = await DeviceConnection.rangeGet({
|
|
120
|
+
gt: last,
|
|
121
|
+
limit: 32
|
|
122
|
+
})
|
|
123
|
+
if(connections.length === 0) break
|
|
124
|
+
|
|
125
|
+
for(const conn of connections) {
|
|
126
|
+
await DeviceConnection.update(conn.id, { status: 'offline' })
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
last = connections[connections.length - 1].id
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Pending + resolve pattern (async result)
|
|
136
|
+
|
|
137
|
+
Use this pattern when an action must wait for a result from an external process (device, worker, etc.).
|
|
138
|
+
|
|
139
|
+
1. The main action creates a “pending” record and calls `waitForCommand(id, timeoutMs)`.
|
|
140
|
+
2. Another action or trigger updates the record and calls `resolveCommand(id, result)`.
|
|
141
|
+
|
|
142
|
+
In-memory helper:
|
|
143
|
+
|
|
144
|
+
```js
|
|
145
|
+
const pendingCommands = new Map()
|
|
146
|
+
|
|
147
|
+
export function waitForCommand(commandId, timeoutMs = 115000) {
|
|
148
|
+
return new Promise((resolve, reject) => {
|
|
149
|
+
const timer = setTimeout(() => {
|
|
150
|
+
pendingCommands.delete(commandId)
|
|
151
|
+
reject(new Error('timeout'))
|
|
152
|
+
}, timeoutMs)
|
|
153
|
+
pendingCommands.set(commandId, { resolve, reject, timer })
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function resolveCommand(commandId, result) {
|
|
158
|
+
const pending = pendingCommands.get(commandId)
|
|
159
|
+
if(pending) {
|
|
160
|
+
clearTimeout(pending.timer)
|
|
161
|
+
pendingCommands.delete(commandId)
|
|
162
|
+
pending.resolve(result)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Usage in an action:
|
|
168
|
+
|
|
169
|
+
```js
|
|
170
|
+
await SomeCommand.create({ id, status: 'pending', ... })
|
|
171
|
+
const result = await waitForCommand(id)
|
|
172
|
+
return result
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
And in a reporting action:
|
|
176
|
+
|
|
177
|
+
```js
|
|
178
|
+
await SomeCommand.update(id, {
|
|
179
|
+
status: 'completed',
|
|
180
|
+
result
|
|
181
|
+
})
|
|
182
|
+
resolveCommand(id, result)
|
|
183
|
+
```
|
|
184
|
+
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Rules for LiveChange backend service architecture and directory structure
|
|
3
|
+
globs: **/services/**/*.js
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# LiveChange backend – service architecture (Claude Code)
|
|
7
|
+
|
|
8
|
+
Use these rules whenever you work on backend services in this repo or other live-change-stack projects.
|
|
9
|
+
|
|
10
|
+
## Service must be a directory
|
|
11
|
+
|
|
12
|
+
- Each LiveChange service **must be a directory**, not a single file.
|
|
13
|
+
- Use a consistent structure:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
server/services/<serviceName>/
|
|
17
|
+
definition.js # createServiceDefinition + use – no models/actions here
|
|
18
|
+
index.js # imports definition + all domain files, exports definition
|
|
19
|
+
config.js # optional – reads definition.config and exports plain object
|
|
20
|
+
<domain>.js # domain files: models, views, actions, triggers
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## `definition.js`
|
|
24
|
+
|
|
25
|
+
- Import `app` from `@live-change/framework`.
|
|
26
|
+
- Create the service definition **without** registering models/actions/views there.
|
|
27
|
+
- If the service uses relations or access control, **always** add the plugins in `use`.
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
import { app } from '@live-change/framework'
|
|
31
|
+
import relationsPlugin from '@live-change/relations-plugin'
|
|
32
|
+
import accessControlService from '@live-change/access-control-service'
|
|
33
|
+
|
|
34
|
+
const definition = app.createServiceDefinition({
|
|
35
|
+
name: 'myService',
|
|
36
|
+
use: [relationsPlugin, accessControlService]
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
export default definition
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## `index.js`
|
|
43
|
+
|
|
44
|
+
- Import `definition`.
|
|
45
|
+
- Import all domain files (models/views/actions/triggers) as side-effect imports.
|
|
46
|
+
- Re-export `definition` as default.
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
import definition from './definition.js'
|
|
50
|
+
|
|
51
|
+
import './modelA.js'
|
|
52
|
+
import './modelB.js'
|
|
53
|
+
import './authenticator.js'
|
|
54
|
+
|
|
55
|
+
export default definition
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## `config.js` (optional)
|
|
59
|
+
|
|
60
|
+
- Read `definition.config` (set in `app.config.js`).
|
|
61
|
+
- Apply defaults and export a plain object.
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
import definition from './definition.js'
|
|
65
|
+
|
|
66
|
+
const {
|
|
67
|
+
someOption = 'default'
|
|
68
|
+
} = definition.config
|
|
69
|
+
|
|
70
|
+
export default { someOption }
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Registering the service in the app
|
|
74
|
+
|
|
75
|
+
### `services.list.js`
|
|
76
|
+
|
|
77
|
+
- Always import the service from its `index.js`:
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
import myService from './services/myService/index.js'
|
|
81
|
+
|
|
82
|
+
export default {
|
|
83
|
+
// ...
|
|
84
|
+
myService
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### `app.config.js`
|
|
89
|
+
|
|
90
|
+
- Make sure `services: [{ name }]` matches the name in `createServiceDefinition`.
|
|
91
|
+
- Keep a **sensible order** of services:
|
|
92
|
+
- core/common services and plugins first,
|
|
93
|
+
- application-specific services last (they can depend on earlier ones).
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
services: [
|
|
97
|
+
{ name: 'user' },
|
|
98
|
+
{ name: 'session' },
|
|
99
|
+
{ name: 'accessControl' },
|
|
100
|
+
// ...
|
|
101
|
+
{ name: 'myService' }
|
|
102
|
+
]
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Inspecting services with `describe`
|
|
106
|
+
|
|
107
|
+
Use the `describe` CLI command to see what the framework generated from your definitions (models, views, actions, triggers, indexes, events):
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# All services overview
|
|
111
|
+
node server/start.js describe
|
|
112
|
+
|
|
113
|
+
# One service in YAML (shows all generated code)
|
|
114
|
+
node server/start.js describe --service myService --output yaml
|
|
115
|
+
|
|
116
|
+
# Specific entity
|
|
117
|
+
node server/start.js describe --service myService --model MyModel --output yaml
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
This is especially useful after using relations (`userItem`, `itemOf`, `propertyOf`) — `describe` shows all the auto-generated views, actions, triggers, and indexes.
|
|
121
|
+
|
|
122
|
+
## When to create a new service
|
|
123
|
+
|
|
124
|
+
- When you have a clearly separate domain (payments, notifications, devices, etc.).
|
|
125
|
+
- When a group of models/actions/views has its own configuration and dependencies.
|
|
126
|
+
- When adding it to an existing service would mix unrelated responsibilities.
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Rules for defining models, relations, indexes and access control in LiveChange
|
|
3
|
+
globs: **/services/**/*.js
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# LiveChange backend – models and relations (Claude Code)
|
|
7
|
+
|
|
8
|
+
Use these rules when defining or editing models and relations in LiveChange services.
|
|
9
|
+
|
|
10
|
+
## General style for models
|
|
11
|
+
|
|
12
|
+
- Put models in domain files imported from the service `index.js`.
|
|
13
|
+
- Prefer **readable, multi-line** property definitions.
|
|
14
|
+
- Avoid squeezing `type`, `default`, `validation` into a single unreadable line.
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
properties: {
|
|
18
|
+
name: {
|
|
19
|
+
type: String,
|
|
20
|
+
validation: ['nonEmpty']
|
|
21
|
+
},
|
|
22
|
+
status: {
|
|
23
|
+
type: String,
|
|
24
|
+
default: 'offline'
|
|
25
|
+
},
|
|
26
|
+
capabilities: {
|
|
27
|
+
type: Array,
|
|
28
|
+
of: {
|
|
29
|
+
type: String
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## `userItem` – belongs to the signed-in user
|
|
36
|
+
|
|
37
|
+
Use when the model is owned by the currently signed-in user.
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
definition.model({
|
|
41
|
+
name: 'Device',
|
|
42
|
+
properties: {
|
|
43
|
+
name: {
|
|
44
|
+
type: String
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
userItem: {
|
|
48
|
+
readAccessControl: { roles: ['owner', 'admin'] },
|
|
49
|
+
writeAccessControl: { roles: ['owner', 'admin'] },
|
|
50
|
+
writeableProperties: ['name']
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This automatically generates:
|
|
56
|
+
|
|
57
|
+
- “my X” views (list + single),
|
|
58
|
+
- basic CRUD actions for the owner.
|
|
59
|
+
|
|
60
|
+
## `itemOf` – child belongs to a parent
|
|
61
|
+
|
|
62
|
+
Use for lists of items related to another model.
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
definition.model({
|
|
66
|
+
name: 'DeviceConnection',
|
|
67
|
+
properties: {
|
|
68
|
+
connectionType: {
|
|
69
|
+
type: String
|
|
70
|
+
},
|
|
71
|
+
status: {
|
|
72
|
+
type: String,
|
|
73
|
+
default: 'offline'
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
itemOf: {
|
|
77
|
+
what: Device,
|
|
78
|
+
readAccessControl: { roles: ['owner', 'admin'] },
|
|
79
|
+
writeAccessControl: { roles: ['owner', 'admin'] }
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Guidelines:
|
|
85
|
+
|
|
86
|
+
- `what` must point to a model defined earlier (in this or another service).
|
|
87
|
+
- The relation generates standard views/actions for listing and managing children.
|
|
88
|
+
|
|
89
|
+
## `propertyOf` – one-to-one property, id = parent id
|
|
90
|
+
|
|
91
|
+
Use when the model represents a single “state object” for a parent, with the same id.
|
|
92
|
+
|
|
93
|
+
```js
|
|
94
|
+
definition.model({
|
|
95
|
+
name: 'DeviceCursorState',
|
|
96
|
+
properties: {
|
|
97
|
+
x: { type: Number },
|
|
98
|
+
y: { type: Number }
|
|
99
|
+
},
|
|
100
|
+
propertyOf: {
|
|
101
|
+
what: Device,
|
|
102
|
+
readAccessControl: { roles: ['owner', 'admin'] },
|
|
103
|
+
writeAccessControl: { roles: ['owner', 'admin'] }
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Effects:
|
|
109
|
+
|
|
110
|
+
- You can fetch it directly as `DeviceCursorState.get(deviceId)`.
|
|
111
|
+
- No need for an extra `device` field or index for lookups by parent id.
|
|
112
|
+
|
|
113
|
+
## `propertyOf` with multiple parents (1:1 link to each)
|
|
114
|
+
|
|
115
|
+
Sometimes a model is a dedicated 1:1 link between entities (for example: invoice ↔ contractor in a specific role).
|
|
116
|
+
Most commonly this is 1–2 parents, but `propertyOf` can point to **any number** of parent models (including 3+), if that matches the domain semantics.
|
|
117
|
+
|
|
118
|
+
In that case:
|
|
119
|
+
|
|
120
|
+
- avoid storing the “other side id” as a plain `contractorId` / `someId` property
|
|
121
|
+
- avoid adding ad-hoc `...Id` fields in a relation model just to “join” entities — CRUD/relations generators won’t treat it as a relation
|
|
122
|
+
- instead, define the relation as `propertyOf` to **each** parent so the relations/CRUD generator understands the model is connecting entities.
|
|
123
|
+
|
|
124
|
+
Example (schematic):
|
|
125
|
+
|
|
126
|
+
```js
|
|
127
|
+
const CostInvoice = definition.foreignModel('invoice', 'CostInvoice')
|
|
128
|
+
const Contractor = definition.foreignModel('company', 'Contractor')
|
|
129
|
+
|
|
130
|
+
definition.model({
|
|
131
|
+
name: 'Supplier',
|
|
132
|
+
properties: {
|
|
133
|
+
// optional extra fields
|
|
134
|
+
},
|
|
135
|
+
propertyOf: [
|
|
136
|
+
{ what: CostInvoice },
|
|
137
|
+
{ what: Contractor }
|
|
138
|
+
]
|
|
139
|
+
})
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## `foreignModel` – parent from another service
|
|
143
|
+
|
|
144
|
+
Use when `itemOf` / `propertyOf` refers to a model from a different service.
|
|
145
|
+
|
|
146
|
+
```js
|
|
147
|
+
const Device = definition.foreignModel('deviceManager', 'Device')
|
|
148
|
+
|
|
149
|
+
definition.model({
|
|
150
|
+
name: 'BotSession',
|
|
151
|
+
properties: {
|
|
152
|
+
// ...
|
|
153
|
+
},
|
|
154
|
+
itemOf: {
|
|
155
|
+
what: Device,
|
|
156
|
+
readAccessControl: { roles: ['owner', 'admin'] }
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Notes:
|
|
162
|
+
|
|
163
|
+
- First argument is the service name.
|
|
164
|
+
- Second is the model name in that service.
|
|
165
|
+
|
|
166
|
+
## Indexes
|
|
167
|
+
|
|
168
|
+
- Declare indexes explicitly when you frequently query by a field or field combination.
|
|
169
|
+
- Use descriptive names like `bySessionKey`, `byDeviceAndStatus`, etc.
|
|
170
|
+
|
|
171
|
+
```js
|
|
172
|
+
indexes: {
|
|
173
|
+
bySessionKey: {
|
|
174
|
+
property: ['sessionKey']
|
|
175
|
+
},
|
|
176
|
+
byDeviceAndStatus: {
|
|
177
|
+
property: ['device', 'status']
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
In other services, the same index may be visible with a prefixed name such as `myService_Model_byDeviceAndStatus`.
|
|
183
|
+
|
|
184
|
+
## Access control on relations
|
|
185
|
+
|
|
186
|
+
- Always set `readAccessControl` and `writeAccessControl` on relations (`userItem`, `itemOf`, `propertyOf`).
|
|
187
|
+
- Treat access control as part of the model definition, not an afterthought.
|
|
188
|
+
|