howone 0.1.23 → 0.1.25
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/package.json +1 -1
- package/templates/vite/.howone/skills/howone-sdk/01-architect/01-app-generation.md +180 -91
- package/templates/vite/.howone/skills/howone-sdk/01-architect/02-manifest-codegen.md +67 -4
- package/templates/vite/.howone/skills/howone-sdk/02-database/01-schema-design.md +463 -69
- package/templates/vite/.howone/skills/howone-sdk/02-database/02-schema-operations.md +366 -64
- package/templates/vite/.howone/skills/howone-sdk/02-database/03-data-access-patterns.md +204 -67
- package/templates/vite/.howone/skills/howone-sdk/02-database/04-query-dsl-and-responses.md +237 -0
- package/templates/vite/.howone/skills/howone-sdk/02-database/05-ai-persistence-patterns.md +372 -0
- package/templates/vite/.howone/skills/howone-sdk/03-sdk/01-client-setup.md +58 -36
- package/templates/vite/.howone/skills/howone-sdk/03-sdk/02-entity-operations.md +67 -0
- package/templates/vite/.howone/skills/howone-sdk/03-sdk/03-auth.md +267 -469
- package/templates/vite/.howone/skills/howone-sdk/03-sdk/04-react-integration.md +113 -320
- package/templates/vite/.howone/skills/howone-sdk/03-sdk/07-ai-action-calls.md +66 -16
- package/templates/vite/.howone/skills/howone-sdk/03-sdk/08-extension-boundaries.md +226 -0
- package/templates/vite/.howone/skills/howone-sdk/04-ai/01-ai-capability-architecture.md +159 -96
- package/templates/vite/.howone/skills/howone-sdk/04-ai/02-workflow-contract-rules.md +353 -96
- package/templates/vite/.howone/skills/howone-sdk/04-ai/03-ai-sdk-handoff.md +181 -42
- package/templates/vite/.howone/skills/howone-sdk/04-ai/04-service-capability-catalog.md +281 -0
- package/templates/vite/.howone/skills/howone-sdk/04-ai/05-workflow-operations.md +256 -0
- package/templates/vite/.howone/skills/howone-sdk/04-ai/06-ai-feature-playbooks.md +296 -0
- package/templates/vite/.howone/skills/howone-sdk/SKILL.md +29 -12
- package/templates/vite/.howone/skills/howone-sdk/agents/openai.yaml +2 -2
- package/templates/vite/package.json +1 -1
- package/templates/vite/.howone/skills/howone-sdk/04-ai/.gitkeep +0 -1
|
@@ -1,103 +1,103 @@
|
|
|
1
1
|
# Data Access Patterns
|
|
2
2
|
|
|
3
|
-
Use this reference to connect backend `access` design to frontend SDK calls.
|
|
3
|
+
Use this reference to connect backend `access` design to frontend SDK calls. It answers:
|
|
4
|
+
**which namespace should the app call, which filters are legal, and what must not be persisted?**
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
For schema design, read `01-schema-design.md`. For query syntax details, read
|
|
7
|
+
`04-query-dsl-and-responses.md`.
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
settings.
|
|
9
|
+
## Namespace Decision
|
|
9
10
|
|
|
10
|
-
Schema
|
|
11
|
+
| Schema / page need | SDK namespace | Auth header | Typical method |
|
|
12
|
+
|---|---|---|---|
|
|
13
|
+
| Current user's private records | `howone.entities.Entity` | yes | `query.mine`, `create`, `update`, `delete` |
|
|
14
|
+
| Logged-in shared records | `howone.entities.Entity` | yes | `query`, `get`, CRUD |
|
|
15
|
+
| Public list page | `howone.public.entities.Entity` | no | `query` |
|
|
16
|
+
| Public scoped share/detail | `howone.public.entities.Entity` | no | `queryScoped`, `get` with scope options |
|
|
17
|
+
| Schema tooling | `howone.schema` | yes | `previewPatch`, `applyPatch` |
|
|
18
|
+
| Low-level fallback | `howone.raw` / `howone.public.raw` | depends | only when typed method missing |
|
|
19
|
+
|
|
20
|
+
Do not mix authenticated and public namespaces for the same page without a clear reason.
|
|
21
|
+
|
|
22
|
+
## Pattern A: Private Per-User Data
|
|
23
|
+
|
|
24
|
+
Use for todos, notes, journals, saved generations, personal dashboards, private settings.
|
|
25
|
+
|
|
26
|
+
Schema:
|
|
11
27
|
|
|
12
28
|
```json
|
|
13
29
|
{
|
|
14
30
|
"visibility": "private",
|
|
15
31
|
"access": {
|
|
16
|
-
"authenticated": {
|
|
17
|
-
|
|
18
|
-
"create": "own",
|
|
19
|
-
"update": "own",
|
|
20
|
-
"delete": "own"
|
|
21
|
-
},
|
|
22
|
-
"public": {
|
|
23
|
-
"read": "none",
|
|
24
|
-
"create": "none",
|
|
25
|
-
"update": "none",
|
|
26
|
-
"delete": "none"
|
|
27
|
-
}
|
|
32
|
+
"authenticated": { "read": "own", "create": "own", "update": "own", "delete": "own" },
|
|
33
|
+
"public": { "read": "none", "create": "none", "update": "none", "delete": "none" }
|
|
28
34
|
}
|
|
29
35
|
}
|
|
30
36
|
```
|
|
31
37
|
|
|
32
|
-
Frontend
|
|
38
|
+
Frontend:
|
|
33
39
|
|
|
34
40
|
```ts
|
|
35
|
-
const
|
|
41
|
+
const list = await howone.entities.Todo.query.mine({
|
|
36
42
|
page: { number: 1, size: 50 },
|
|
37
43
|
orderBy: { updatedDate: 'desc' },
|
|
38
44
|
})
|
|
45
|
+
|
|
46
|
+
await howone.entities.Todo.create({
|
|
47
|
+
text,
|
|
48
|
+
completed: false,
|
|
49
|
+
})
|
|
39
50
|
```
|
|
40
51
|
|
|
41
52
|
Rules:
|
|
42
53
|
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
|
|
54
|
+
- Do not pass `ownerId`, `created_by_id`, `createdById`, `created_by_user_id`, or `puid`.
|
|
55
|
+
- Backend derives owner from JWT/session.
|
|
56
|
+
- Use `query.mine()` for owned lists.
|
|
57
|
+
- For first auth load, call `await howone.me()` or `await howone.requireMe()`.
|
|
47
58
|
|
|
48
|
-
## Authenticated Shared Data
|
|
59
|
+
## Pattern B: Authenticated Shared Data
|
|
49
60
|
|
|
50
|
-
Use when logged-in users
|
|
61
|
+
Use when logged-in app users can see shared records: team projects, CMS admin, internal catalogs.
|
|
51
62
|
|
|
52
|
-
Schema
|
|
63
|
+
Schema:
|
|
53
64
|
|
|
54
65
|
```json
|
|
55
66
|
{
|
|
56
67
|
"visibility": "private",
|
|
57
68
|
"access": {
|
|
58
|
-
"authenticated": {
|
|
59
|
-
|
|
60
|
-
"create": "all",
|
|
61
|
-
"update": "all",
|
|
62
|
-
"delete": "all"
|
|
63
|
-
},
|
|
64
|
-
"public": {
|
|
65
|
-
"read": "none",
|
|
66
|
-
"create": "none",
|
|
67
|
-
"update": "none",
|
|
68
|
-
"delete": "none"
|
|
69
|
-
}
|
|
69
|
+
"authenticated": { "read": "all", "create": "all", "update": "all", "delete": "all" },
|
|
70
|
+
"public": { "read": "none", "create": "none", "update": "none", "delete": "none" }
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
73
|
```
|
|
73
74
|
|
|
74
|
-
Frontend
|
|
75
|
+
Frontend:
|
|
75
76
|
|
|
76
77
|
```ts
|
|
77
|
-
const
|
|
78
|
+
const list = await howone.entities.Project.query({
|
|
78
79
|
page: { number: 1, size: 20 },
|
|
79
80
|
orderBy: { updatedDate: 'desc' },
|
|
80
81
|
})
|
|
81
82
|
```
|
|
82
83
|
|
|
83
|
-
|
|
84
|
+
Rules:
|
|
85
|
+
|
|
86
|
+
- Use `query()`, not `query.mine()`.
|
|
87
|
+
- Still require auth.
|
|
88
|
+
- Be conservative with `update: "all"` and `delete: "all"` unless the app has a real role model.
|
|
84
89
|
|
|
85
|
-
## Public Read-Only Content
|
|
90
|
+
## Pattern C: Public Read-Only Content
|
|
86
91
|
|
|
87
|
-
Use for public articles, templates,
|
|
92
|
+
Use for public articles, templates, products, profiles, published galleries.
|
|
88
93
|
|
|
89
|
-
Schema
|
|
94
|
+
Schema:
|
|
90
95
|
|
|
91
96
|
```json
|
|
92
97
|
{
|
|
93
98
|
"visibility": "public",
|
|
94
99
|
"access": {
|
|
95
|
-
"authenticated": {
|
|
96
|
-
"read": "all",
|
|
97
|
-
"create": "all",
|
|
98
|
-
"update": "all",
|
|
99
|
-
"delete": "all"
|
|
100
|
-
},
|
|
100
|
+
"authenticated": { "read": "all", "create": "all", "update": "all", "delete": "all" },
|
|
101
101
|
"public": {
|
|
102
102
|
"read": "list",
|
|
103
103
|
"create": "none",
|
|
@@ -112,34 +112,34 @@ Schema posture:
|
|
|
112
112
|
}
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
Frontend
|
|
115
|
+
Frontend:
|
|
116
116
|
|
|
117
117
|
```ts
|
|
118
|
-
const
|
|
119
|
-
where: { status: 'published' },
|
|
118
|
+
const list = await howone.public.entities.Article.query({
|
|
119
|
+
where: { status: 'published', category },
|
|
120
120
|
page: { number: 1, size: 20 },
|
|
121
121
|
orderBy: { publishedAt: 'desc' },
|
|
122
122
|
})
|
|
123
123
|
```
|
|
124
124
|
|
|
125
|
-
|
|
125
|
+
Rules:
|
|
126
|
+
|
|
127
|
+
- Public filters must be in `allowedFilters`.
|
|
128
|
+
- Public sorts must be in `allowedSorts`.
|
|
129
|
+
- Never pass tokens or use authenticated namespace for anonymous landing pages.
|
|
130
|
+
- Keep public result fields safe for anonymous users.
|
|
126
131
|
|
|
127
|
-
## Public Scoped Share Pages
|
|
132
|
+
## Pattern D: Public Scoped Share Pages
|
|
128
133
|
|
|
129
|
-
Use for public URLs
|
|
134
|
+
Use for public URLs exposing one scoped record: QR profile, public report, resume, invite page.
|
|
130
135
|
|
|
131
|
-
Schema
|
|
136
|
+
Schema:
|
|
132
137
|
|
|
133
138
|
```json
|
|
134
139
|
{
|
|
135
140
|
"visibility": "public",
|
|
136
141
|
"access": {
|
|
137
|
-
"authenticated": {
|
|
138
|
-
"read": "own",
|
|
139
|
-
"create": "own",
|
|
140
|
-
"update": "own",
|
|
141
|
-
"delete": "own"
|
|
142
|
-
},
|
|
142
|
+
"authenticated": { "read": "own", "create": "own", "update": "own", "delete": "own" },
|
|
143
143
|
"public": {
|
|
144
144
|
"read": "scoped",
|
|
145
145
|
"create": "none",
|
|
@@ -155,18 +155,155 @@ Schema posture:
|
|
|
155
155
|
}
|
|
156
156
|
```
|
|
157
157
|
|
|
158
|
-
Frontend
|
|
158
|
+
Frontend:
|
|
159
159
|
|
|
160
160
|
```ts
|
|
161
161
|
const result = await howone.public.entities.QrProfile.queryScoped({
|
|
162
162
|
where: { ownerId, slug, active: true },
|
|
163
163
|
page: { number: 1, size: 1 },
|
|
164
164
|
})
|
|
165
|
+
const profile = result.items[0] ?? null
|
|
165
166
|
```
|
|
166
167
|
|
|
167
168
|
Rules:
|
|
168
169
|
|
|
169
170
|
- Pass every `requiredScopes` field.
|
|
170
|
-
- Do not use current
|
|
171
|
-
|
|
172
|
-
-
|
|
171
|
+
- Do not use current JWT `puid` as `ownerId` unless schema explicitly stores that as public scope.
|
|
172
|
+
- Do not turn scoped pages into broad list pages.
|
|
173
|
+
- Keep `maxLimit` small.
|
|
174
|
+
|
|
175
|
+
## Pattern E: Public Create / Anonymous Submission
|
|
176
|
+
|
|
177
|
+
Use only for forms that must accept anonymous/public submissions: waitlist, contact, feedback,
|
|
178
|
+
public RSVP.
|
|
179
|
+
|
|
180
|
+
Schema:
|
|
181
|
+
|
|
182
|
+
```json
|
|
183
|
+
{
|
|
184
|
+
"visibility": "public",
|
|
185
|
+
"access": {
|
|
186
|
+
"authenticated": { "read": "all", "create": "all", "update": "all", "delete": "all" },
|
|
187
|
+
"public": {
|
|
188
|
+
"read": "none",
|
|
189
|
+
"create": "scoped",
|
|
190
|
+
"update": "none",
|
|
191
|
+
"delete": "none",
|
|
192
|
+
"requiredScopes": ["created_by_user_id"],
|
|
193
|
+
"allowedFilters": [],
|
|
194
|
+
"allowedSorts": [],
|
|
195
|
+
"defaultLimit": 1,
|
|
196
|
+
"maxLimit": 1
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Frontend:
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
await howone.public.entities.Feedback.create({
|
|
206
|
+
created_by_user_id: projectUserId,
|
|
207
|
+
message,
|
|
208
|
+
rating,
|
|
209
|
+
})
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Rules:
|
|
213
|
+
|
|
214
|
+
- Public create needs a clear `created_by_user_id` source when backend requires ownership mapping.
|
|
215
|
+
- Do not expose public read unless needed.
|
|
216
|
+
- Add anti-abuse UX/server constraints outside the dynamic schema when needed.
|
|
217
|
+
- Never persist UI-only fields from form components.
|
|
218
|
+
|
|
219
|
+
## Pattern F: AI Workflow Output Persistence
|
|
220
|
+
|
|
221
|
+
Use for generation/analyze/report products that need history and refresh resilience.
|
|
222
|
+
|
|
223
|
+
Recommended flow:
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
const pending = await howone.entities.Generation.create({
|
|
227
|
+
prompt,
|
|
228
|
+
status: 'pending',
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
const output = await howone.ai.generateImage.run({ prompt })
|
|
233
|
+
await howone.entities.Generation.update(pending.id, {
|
|
234
|
+
status: 'completed',
|
|
235
|
+
resultUrl: output.imageUrl,
|
|
236
|
+
completedAt: new Date().toISOString(),
|
|
237
|
+
})
|
|
238
|
+
} catch (error) {
|
|
239
|
+
await howone.entities.Generation.update(pending.id, {
|
|
240
|
+
status: 'failed',
|
|
241
|
+
errorMessage: error instanceof Error ? error.message : 'Generation failed',
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const history = await howone.entities.Generation.query.mine({
|
|
246
|
+
orderBy: { updatedDate: 'desc' },
|
|
247
|
+
page: { number: 1, size: 20 },
|
|
248
|
+
})
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Rules:
|
|
252
|
+
|
|
253
|
+
- Persist only fields declared in the entity schema.
|
|
254
|
+
- Do not persist raw workflow event streams unless schema defines an object/array field for them.
|
|
255
|
+
- Failure branch must persist failure if the product shows history.
|
|
256
|
+
- Latest result, history list, and detail pages should reload from data API, not only local state.
|
|
257
|
+
|
|
258
|
+
## Payload Whitelist Rule
|
|
259
|
+
|
|
260
|
+
Before every create/update, mentally compute:
|
|
261
|
+
|
|
262
|
+
```text
|
|
263
|
+
payload keys ⊆ entity.properties keys
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Allowed:
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
await howone.entities.Todo.create({
|
|
270
|
+
text,
|
|
271
|
+
completed: false,
|
|
272
|
+
})
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Forbidden:
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
await howone.entities.Todo.create({
|
|
279
|
+
text,
|
|
280
|
+
completed: false,
|
|
281
|
+
gradient_direction: 'to right', // UI-only
|
|
282
|
+
created_by_id: user.id, // system/owner field
|
|
283
|
+
workflowRawResult: output, // undeclared workflow envelope
|
|
284
|
+
})
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
If the app truly needs a new persisted field, update schema first.
|
|
288
|
+
|
|
289
|
+
## Access-to-SDK Mapping
|
|
290
|
+
|
|
291
|
+
| Access posture | Read | Create | Update/Delete |
|
|
292
|
+
|---|---|---|---|
|
|
293
|
+
| authenticated `own` | `entities.X.query.mine()` | `entities.X.create()` | `entities.X.update/delete()` |
|
|
294
|
+
| authenticated `all` | `entities.X.query()` | `entities.X.create()` | `entities.X.update/delete()` |
|
|
295
|
+
| public `list` | `public.entities.X.query()` | no | no |
|
|
296
|
+
| public `scoped` | `public.entities.X.queryScoped()` | only if `create: scoped/any` | only if `update: scoped/any` |
|
|
297
|
+
| public `none` | no public call | no public call | no public call |
|
|
298
|
+
|
|
299
|
+
## Common Mistakes
|
|
300
|
+
|
|
301
|
+
| Mistake | Fix |
|
|
302
|
+
|---|---|
|
|
303
|
+
| Passing `created_by_user_id` for normal private data | Omit owner fields; backend derives owner. |
|
|
304
|
+
| Using `entities.*` on public page | Use `public.entities.*`. |
|
|
305
|
+
| Public filter not in `allowedFilters` | Add filter to schema or remove query. |
|
|
306
|
+
| Public sort not in `allowedSorts` | Add sort to schema or change UI. |
|
|
307
|
+
| Saving workflow output object directly | Map only declared fields. |
|
|
308
|
+
| Rendering history only from local state | Reload via `query.mine()` / public query. |
|
|
309
|
+
| Treating `visibility: "public"` as enough | Always define `access.public`. |
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# Query DSL And Responses
|
|
2
|
+
|
|
3
|
+
Use this reference when implementing list/detail pages, filters, sorting, pagination, or response
|
|
4
|
+
normalization for HowOne dynamic entities.
|
|
5
|
+
|
|
6
|
+
## SDK Query Shape
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
await howone.entities.Todo.query({
|
|
10
|
+
where: {
|
|
11
|
+
completed: false,
|
|
12
|
+
priority: { in: ['medium', 'high'] },
|
|
13
|
+
updatedDate: { gte: '2026-01-01T00:00:00.000Z' },
|
|
14
|
+
},
|
|
15
|
+
search: 'invoice',
|
|
16
|
+
page: { number: 1, size: 20 },
|
|
17
|
+
orderBy: { updatedDate: 'desc' },
|
|
18
|
+
include: ['owner'],
|
|
19
|
+
exactCount: true,
|
|
20
|
+
})
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Private owner-scoped list:
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
await howone.entities.Todo.query.mine({
|
|
27
|
+
page: { number: 1, size: 50 },
|
|
28
|
+
orderBy: { updatedDate: 'desc' },
|
|
29
|
+
})
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Public list:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
await howone.public.entities.Article.query({
|
|
36
|
+
where: { status: 'published' },
|
|
37
|
+
orderBy: { publishedAt: 'desc' },
|
|
38
|
+
page: { number: 1, size: 20 },
|
|
39
|
+
})
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Public scoped:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
await howone.public.entities.QrProfile.queryScoped({
|
|
46
|
+
where: { ownerId, slug, active: true },
|
|
47
|
+
page: { number: 1, size: 1 },
|
|
48
|
+
})
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Operators
|
|
52
|
+
|
|
53
|
+
Supported field operators:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
type FieldOperator<T> = {
|
|
57
|
+
eq?: T
|
|
58
|
+
equals?: T
|
|
59
|
+
ne?: T
|
|
60
|
+
not?: T
|
|
61
|
+
gt?: T
|
|
62
|
+
gte?: T
|
|
63
|
+
lt?: T
|
|
64
|
+
lte?: T
|
|
65
|
+
contains?: string
|
|
66
|
+
like?: string
|
|
67
|
+
startsWith?: string
|
|
68
|
+
starts?: string
|
|
69
|
+
endsWith?: string
|
|
70
|
+
ends?: string
|
|
71
|
+
in?: T[]
|
|
72
|
+
notIn?: T[]
|
|
73
|
+
null?: boolean
|
|
74
|
+
empty?: boolean
|
|
75
|
+
exists?: boolean
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Examples:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
where: { status: 'published' }
|
|
83
|
+
where: { status: { eq: 'published' } }
|
|
84
|
+
where: { score: { gte: 80, lt: 100 } }
|
|
85
|
+
where: { category: { in: ['news', 'guide'] } }
|
|
86
|
+
where: { title: { contains: 'AI' } }
|
|
87
|
+
where: { deletedAt: { null: true } }
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Pagination
|
|
91
|
+
|
|
92
|
+
Use SDK page object:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
page: { number: 1, size: 20 }
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Response:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
type QueryResult<T> = {
|
|
102
|
+
items: T[]
|
|
103
|
+
page: {
|
|
104
|
+
number: number
|
|
105
|
+
size: number
|
|
106
|
+
total: number
|
|
107
|
+
totalPages: number
|
|
108
|
+
hasNext: boolean
|
|
109
|
+
hasPrev: boolean
|
|
110
|
+
}
|
|
111
|
+
traceId?: string | number
|
|
112
|
+
raw?: unknown
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Always render from a normalized array:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
const result = await howone.entities.Todo.query.mine(...)
|
|
120
|
+
const items = Array.isArray(result.items) ? result.items : []
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Sorting
|
|
124
|
+
|
|
125
|
+
SDK uses:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
orderBy: { updatedDate: 'desc' }
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Rules:
|
|
132
|
+
|
|
133
|
+
- Public sort fields must be in `access.public.allowedSorts`.
|
|
134
|
+
- Private sort fields should be in `performance.allowedSorts` and covered by indexes.
|
|
135
|
+
- Prefer `updatedDate` for recently changed lists and `createdDate` for creation history.
|
|
136
|
+
- Do not expose arbitrary public sort fields.
|
|
137
|
+
|
|
138
|
+
## Include / Relations
|
|
139
|
+
|
|
140
|
+
Use `include` only for relation names declared in schema `relations`.
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
await howone.entities.Article.query({
|
|
144
|
+
include: ['author'],
|
|
145
|
+
})
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Rules:
|
|
149
|
+
|
|
150
|
+
- Do not invent include names.
|
|
151
|
+
- Public includes must be safe for anonymous exposure.
|
|
152
|
+
- If a list needs a small display field frequently, consider denormalizing it instead of requiring include on every row.
|
|
153
|
+
|
|
154
|
+
## Detail Reads
|
|
155
|
+
|
|
156
|
+
Authenticated:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
const item = await howone.entities.Todo.get(id)
|
|
160
|
+
const item = await howone.entities.Todo.getOrThrow(id)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Public:
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
const item = await howone.public.entities.Article.get(id)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
For public scoped detail, prefer `queryScoped` when the route naturally has scope fields like
|
|
170
|
+
`ownerId + slug`.
|
|
171
|
+
|
|
172
|
+
## Response Field Names
|
|
173
|
+
|
|
174
|
+
SDK defaults to `caseStyle: 'camel'`, so responses are normalized toward camelCase where supported.
|
|
175
|
+
Still know the backend system field meanings:
|
|
176
|
+
|
|
177
|
+
| Backend concept | Common SDK field |
|
|
178
|
+
|---|---|
|
|
179
|
+
| record id | `id` |
|
|
180
|
+
| created date | `createdDate` or `created_date` depending case style |
|
|
181
|
+
| updated date | `updatedDate` or `updated_date` |
|
|
182
|
+
| owner | `createdById` or `created_by_id` |
|
|
183
|
+
| schema version id | `schemaVersionId` or `schema_version_id` |
|
|
184
|
+
| schema version number | `schemaVersionNumber` or `schema_version_number` |
|
|
185
|
+
|
|
186
|
+
Do not use `_id` in app code unless inspecting raw backend payloads.
|
|
187
|
+
|
|
188
|
+
## Public Guardrails
|
|
189
|
+
|
|
190
|
+
Before writing public query code, check schema:
|
|
191
|
+
|
|
192
|
+
```json
|
|
193
|
+
"public": {
|
|
194
|
+
"read": "list",
|
|
195
|
+
"allowedFilters": ["status", "slug"],
|
|
196
|
+
"allowedSorts": ["publishedAt"],
|
|
197
|
+
"defaultLimit": 20,
|
|
198
|
+
"maxLimit": 100
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Then only use allowed filters and sorts:
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
// OK
|
|
206
|
+
where: { status: 'published' }
|
|
207
|
+
orderBy: { publishedAt: 'desc' }
|
|
208
|
+
|
|
209
|
+
// Not OK unless listed in access.public
|
|
210
|
+
where: { internalReviewState: 'approved' }
|
|
211
|
+
orderBy: { revenue: 'desc' }
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Search
|
|
215
|
+
|
|
216
|
+
Use `search` for broad text search only when the backend/schema supports the desired behavior:
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
await howone.public.entities.Article.query({
|
|
220
|
+
search: query,
|
|
221
|
+
where: { status: 'published' },
|
|
222
|
+
})
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Do not use `search` as a substitute for required public scopes.
|
|
226
|
+
|
|
227
|
+
## Common Mistakes
|
|
228
|
+
|
|
229
|
+
| Mistake | Fix |
|
|
230
|
+
|---|---|
|
|
231
|
+
| Rendering `res.data.data` from SDK query | SDK returns `QueryResult.items`; render `result.items`. |
|
|
232
|
+
| Using `_id` as record id | Use `id`. |
|
|
233
|
+
| Public query with unlisted filter | Add to `allowedFilters` or remove it. |
|
|
234
|
+
| Public query with unlisted sort | Add to `allowedSorts` or change sorting. |
|
|
235
|
+
| Passing owner filters in authenticated `own` queries | Use `query.mine()` and omit owner fields. |
|
|
236
|
+
| Using include without schema relation | Add relation first or remove include. |
|
|
237
|
+
| No pagination on list pages | Always pass `page` and respect `maxLimit`. |
|