howone 0.1.22 → 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/nextjs/lib/sdk.ts +3 -0
- package/templates/vite/.howone/skills/howone-sdk/01-architect/01-app-generation.md +183 -69
- package/templates/vite/.howone/skills/howone-sdk/01-architect/02-manifest-codegen.md +98 -23
- 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 -322
- package/templates/vite/.howone/skills/howone-sdk/03-sdk/07-ai-action-calls.md +95 -48
- 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 +205 -0
- package/templates/vite/.howone/skills/howone-sdk/04-ai/02-workflow-contract-rules.md +426 -0
- package/templates/vite/.howone/skills/howone-sdk/04-ai/03-ai-sdk-handoff.md +219 -0
- 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 +83 -15
- package/templates/vite/.howone/skills/howone-sdk/agents/openai.yaml +2 -2
- package/templates/vite/package.json +1 -1
- package/templates/vite/src/lib/sdk.ts +3 -0
- package/templates/vite/.howone/skills/howone-sdk/04-ai/.gitkeep +0 -1
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
# Database
|
|
1
|
+
# Database Schema Design
|
|
2
2
|
|
|
3
|
-
Use this reference when designing or changing HowOne backend entity schemas. It
|
|
4
|
-
|
|
3
|
+
Use this reference when designing or changing HowOne backend entity schemas. It condenses the
|
|
4
|
+
runtime contract from `docs/dynamic-entity-architecture.zh.md` into instructions an AI agent can
|
|
5
|
+
actually apply.
|
|
5
6
|
|
|
6
|
-
This
|
|
7
|
-
`02-
|
|
8
|
-
`
|
|
9
|
-
artifacts have been synced.
|
|
7
|
+
This file answers: **what should the schema be?** For how to apply changes, read
|
|
8
|
+
`02-schema-operations.md`. For frontend calls, read `03-data-access-patterns.md` and
|
|
9
|
+
`03-sdk/02-entity-operations.md`.
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Mental Model
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
A HowOne Entity is a versioned app-level database contract. It is not a MongoDB collection exposed
|
|
14
|
+
directly and not a loose JSON form.
|
|
15
|
+
|
|
16
|
+
Design the whole contract:
|
|
14
17
|
|
|
15
18
|
```ts
|
|
16
19
|
type EntityContract = {
|
|
@@ -21,8 +24,8 @@ type EntityContract = {
|
|
|
21
24
|
properties: Record<string, EntityField>
|
|
22
25
|
required?: string[]
|
|
23
26
|
access: {
|
|
24
|
-
authenticated
|
|
25
|
-
public
|
|
27
|
+
authenticated: AuthenticatedAccess
|
|
28
|
+
public: PublicAccess
|
|
26
29
|
}
|
|
27
30
|
indexes?: EntityIndex[]
|
|
28
31
|
relations?: Record<string, EntityRelation>
|
|
@@ -32,70 +35,250 @@ type EntityContract = {
|
|
|
32
35
|
}
|
|
33
36
|
```
|
|
34
37
|
|
|
35
|
-
|
|
38
|
+
Each section has a different job:
|
|
39
|
+
|
|
40
|
+
| Section | Purpose | AI design question |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| `properties` | Business fields and primitive validation | What data is persisted? |
|
|
43
|
+
| `required` | Create-time required fields | What must exist before first save? |
|
|
44
|
+
| `access.authenticated` | Logged-in/private API access | Who can read/write after auth? |
|
|
45
|
+
| `access.public` | Anonymous/public API access | Can a public page read or write it? |
|
|
46
|
+
| `indexes` | Query performance and uniqueness | What lists/details will be queried often? |
|
|
47
|
+
| `relations` | Valid include names | What can be joined/expanded? |
|
|
48
|
+
| `presentation` | Admin/generator hints | What fields identify the record in UI? |
|
|
49
|
+
| `lifecycle` | Audit/delete policy hints | Is this append-only, soft-deletable, audited? |
|
|
50
|
+
| `performance` | SDK/admin pagination/sort hints | What limits and sorts are safe? |
|
|
51
|
+
|
|
52
|
+
## Storage Reality
|
|
53
|
+
|
|
54
|
+
HowOne uses shared runtime collections:
|
|
55
|
+
|
|
56
|
+
| Collection | Meaning |
|
|
57
|
+
|---|---|
|
|
58
|
+
| `entityshares` | Current active entity definitions. |
|
|
59
|
+
| `entitydatashares` | Real business records. |
|
|
60
|
+
| `entityschemaversions` | Historical schema snapshots. |
|
|
61
|
+
| `entityschemastates` | Current schema version pointer per app. |
|
|
62
|
+
| `usershares` | App user mapping used for ownership. |
|
|
63
|
+
|
|
64
|
+
Schema restore changes definitions only. It does **not** roll back existing business records.
|
|
65
|
+
|
|
66
|
+
Every data row may carry:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
{
|
|
70
|
+
id: string
|
|
71
|
+
created_date: string
|
|
72
|
+
updated_date: string
|
|
73
|
+
created_by_id: string
|
|
74
|
+
schema_version_id?: string
|
|
75
|
+
schema_version_number?: number
|
|
76
|
+
is_sample?: boolean
|
|
77
|
+
...businessFields
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Do not create business fields that collide with system fields.
|
|
82
|
+
|
|
83
|
+
## Field Design
|
|
84
|
+
|
|
85
|
+
### Naming
|
|
86
|
+
|
|
87
|
+
Entity and field names must match:
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
^[a-zA-Z_][a-zA-Z0-9_]*$
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Conventions:
|
|
94
|
+
|
|
95
|
+
- Entity names: PascalCase, singular, domain noun: `Todo`, `Article`, `QrProfile`.
|
|
96
|
+
- Field names: camelCase: `qrImageUrl`, `publishedAt`, `moodScore`.
|
|
97
|
+
- Avoid ambiguous names like `data`, `info`, `value`, `result` unless the product really stores opaque blobs.
|
|
98
|
+
|
|
99
|
+
Forbidden business field names:
|
|
100
|
+
|
|
101
|
+
```text
|
|
102
|
+
id
|
|
103
|
+
_id
|
|
104
|
+
created_date
|
|
105
|
+
updated_date
|
|
106
|
+
created_by_id
|
|
107
|
+
createdById
|
|
108
|
+
ownerId
|
|
109
|
+
is_sample
|
|
110
|
+
schema_version_id
|
|
111
|
+
schema_version_number
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`created_by_user_id` is special: use it only when a public write/share flow explicitly needs a
|
|
115
|
+
project-user identifier. Do not use it as the owner field for normal authenticated private data;
|
|
116
|
+
the backend derives owner from JWT.
|
|
117
|
+
|
|
118
|
+
### Supported Types
|
|
119
|
+
|
|
120
|
+
```text
|
|
121
|
+
string
|
|
122
|
+
number
|
|
123
|
+
boolean
|
|
124
|
+
date
|
|
125
|
+
array
|
|
126
|
+
object
|
|
127
|
+
integer
|
|
128
|
+
null
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Nullable:
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{ "type": ["string", "null"], "default": null }
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Backend runtime currently enforces:
|
|
36
138
|
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
- schema versions track the contract applied when future records are written.
|
|
139
|
+
- unknown field rejection;
|
|
140
|
+
- create required fields;
|
|
141
|
+
- basic primitive type checks;
|
|
142
|
+
- non-null required fields unless the type includes `null`;
|
|
143
|
+
- defaults and `autoGenerate`.
|
|
43
144
|
|
|
44
|
-
|
|
145
|
+
Do not assume full JSON Schema enforcement for every nested constraint. Use Zod/frontend validation
|
|
146
|
+
for stronger UX validation:
|
|
45
147
|
|
|
46
|
-
|
|
47
|
-
|
|
148
|
+
- `enum`
|
|
149
|
+
- `minimum` / `maximum`
|
|
150
|
+
- `minLength` / `maxLength`
|
|
151
|
+
- `pattern`
|
|
152
|
+
- nested `items` / `properties`
|
|
48
153
|
|
|
49
|
-
|
|
154
|
+
### Defaults and Generated Fields
|
|
50
155
|
|
|
51
|
-
|
|
52
|
-
- Do not define system fields as business fields: `id`, `_id`, `created_date`, `updated_date`,
|
|
53
|
-
`created_by_id`, `schema_version_id`, `schema_version_number`, `is_sample`.
|
|
54
|
-
- Required fields must be supplied on create unless the field has `default`, `defaultValue`, or
|
|
55
|
-
`autoGenerate`.
|
|
56
|
-
- Use explicit enum fields for UI status values instead of free-form strings when choices are known.
|
|
57
|
-
- Use ISO strings for date/datetime fields at the SDK boundary.
|
|
58
|
-
- Complex JSON Schema validation may need frontend validation too; do not assume every nested
|
|
59
|
-
constraint is fully enforced by backend runtime.
|
|
156
|
+
Use `default` when a field has an obvious value at creation:
|
|
60
157
|
|
|
61
|
-
|
|
158
|
+
```json
|
|
159
|
+
{ "completed": { "type": "boolean", "default": false } }
|
|
160
|
+
```
|
|
62
161
|
|
|
63
|
-
|
|
162
|
+
Use nullable defaults for optional dates:
|
|
163
|
+
|
|
164
|
+
```json
|
|
165
|
+
{ "publishedAt": { "type": ["date", "null"], "default": null } }
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Use server generation for public IDs:
|
|
64
169
|
|
|
65
170
|
```json
|
|
66
171
|
{
|
|
67
|
-
"
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
"fields": ["status", "updatedDate"],
|
|
71
|
-
"scope": "owner",
|
|
72
|
-
"order": { "updatedDate": "desc" }
|
|
73
|
-
}
|
|
74
|
-
],
|
|
75
|
-
"performance": {
|
|
76
|
-
"defaultLimit": 20,
|
|
77
|
-
"maxLimit": 100,
|
|
78
|
-
"allowedSorts": ["createdDate", "updatedDate"]
|
|
172
|
+
"publicId": {
|
|
173
|
+
"type": "string",
|
|
174
|
+
"autoGenerate": { "strategy": "uuid" }
|
|
79
175
|
}
|
|
80
176
|
}
|
|
81
177
|
```
|
|
82
178
|
|
|
83
|
-
|
|
179
|
+
Current `autoGenerate.strategy` support:
|
|
180
|
+
|
|
181
|
+
```text
|
|
182
|
+
uuid
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
AI rule: Create input may omit fields with `default` or `autoGenerate`; response types should include
|
|
186
|
+
them as present/possible.
|
|
187
|
+
|
|
188
|
+
## Access Design
|
|
189
|
+
|
|
190
|
+
Always write both `authenticated` and `public`. Do not rely on `visibility` defaults for new schema.
|
|
191
|
+
|
|
192
|
+
Authenticated channel:
|
|
193
|
+
|
|
194
|
+
```text
|
|
195
|
+
/api/entities/apps/:appId/data/:entityName
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Public channel:
|
|
199
|
+
|
|
200
|
+
```text
|
|
201
|
+
/api/entities/public/apps/:appId/data/:entityName
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
These channels are independent.
|
|
205
|
+
|
|
206
|
+
### Authenticated Access
|
|
207
|
+
|
|
208
|
+
Each action accepts `own`, `all`, or `none`:
|
|
209
|
+
|
|
210
|
+
```json
|
|
211
|
+
"authenticated": {
|
|
212
|
+
"read": "own",
|
|
213
|
+
"create": "own",
|
|
214
|
+
"update": "own",
|
|
215
|
+
"delete": "own"
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
| Value | Meaning |
|
|
220
|
+
|---|---|
|
|
221
|
+
| `own` | Backend scopes to current `usershares._id`; create assigns current owner. |
|
|
222
|
+
| `all` | Logged-in users can access all records for that operation. |
|
|
223
|
+
| `none` | Operation forbidden. |
|
|
224
|
+
|
|
225
|
+
Rules:
|
|
226
|
+
|
|
227
|
+
- For private user data, use all `own`.
|
|
228
|
+
- For authenticated shared dashboards/CMS, use `read: "all"` and be conservative on update/delete.
|
|
229
|
+
- Do not pass owner fields in authenticated payloads or filters. Backend derives owner from auth.
|
|
230
|
+
- `query.mine()` is the SDK shorthand for authenticated own lists.
|
|
231
|
+
|
|
232
|
+
### Public Access
|
|
233
|
+
|
|
234
|
+
Public read values:
|
|
235
|
+
|
|
236
|
+
| Value | Use for |
|
|
237
|
+
|---|---|
|
|
238
|
+
| `none` | Not visible without login. |
|
|
239
|
+
| `list` | Public feeds/catalogs/lists. |
|
|
240
|
+
| `scoped` | Public share/detail pages that require scope keys. |
|
|
241
|
+
|
|
242
|
+
Public write values:
|
|
243
|
+
|
|
244
|
+
| Value | Use for |
|
|
245
|
+
|---|---|
|
|
246
|
+
| `none` | No anonymous write. |
|
|
247
|
+
| `scoped` | Anonymous write only with required scope values. |
|
|
248
|
+
| `any` | Fully public write; use rarely. |
|
|
249
|
+
|
|
250
|
+
Guardrail fields:
|
|
251
|
+
|
|
252
|
+
```json
|
|
253
|
+
"public": {
|
|
254
|
+
"read": "scoped",
|
|
255
|
+
"create": "none",
|
|
256
|
+
"update": "none",
|
|
257
|
+
"requiredScopes": ["ownerId", "slug"],
|
|
258
|
+
"allowedFilters": ["slug", "active"],
|
|
259
|
+
"allowedSorts": ["updatedDate"],
|
|
260
|
+
"defaultLimit": 1,
|
|
261
|
+
"maxLimit": 10
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Rules:
|
|
84
266
|
|
|
85
|
-
-
|
|
86
|
-
-
|
|
87
|
-
-
|
|
88
|
-
|
|
267
|
+
- `scoped` requires every `requiredScopes` value in query/body.
|
|
268
|
+
- `list` must define `allowedFilters`, `allowedSorts`, `defaultLimit`, and `maxLimit`.
|
|
269
|
+
- Public create requires a clear ownership/scoping story. If it needs `created_by_user_id`, document where that value comes from.
|
|
270
|
+
- Never expose broad public write unless the product explicitly needs anonymous submissions.
|
|
89
271
|
|
|
90
|
-
##
|
|
272
|
+
## Standard Patterns
|
|
91
273
|
|
|
92
|
-
|
|
274
|
+
### A. User Private Data
|
|
275
|
+
|
|
276
|
+
Use for todos, notes, journals, saved generations, personal settings.
|
|
93
277
|
|
|
94
278
|
```json
|
|
95
279
|
{
|
|
96
280
|
"name": "Todo",
|
|
97
281
|
"type": "object",
|
|
98
|
-
"description": "User-owned todo item",
|
|
99
282
|
"visibility": "private",
|
|
100
283
|
"properties": {
|
|
101
284
|
"text": { "type": "string" },
|
|
@@ -103,24 +286,14 @@ For a personal Todo app:
|
|
|
103
286
|
},
|
|
104
287
|
"required": ["text"],
|
|
105
288
|
"access": {
|
|
106
|
-
"authenticated": {
|
|
107
|
-
|
|
108
|
-
"create": "own",
|
|
109
|
-
"update": "own",
|
|
110
|
-
"delete": "own"
|
|
111
|
-
},
|
|
112
|
-
"public": {
|
|
113
|
-
"read": "none",
|
|
114
|
-
"create": "none",
|
|
115
|
-
"update": "none",
|
|
116
|
-
"delete": "none"
|
|
117
|
-
}
|
|
289
|
+
"authenticated": { "read": "own", "create": "own", "update": "own", "delete": "own" },
|
|
290
|
+
"public": { "read": "none", "create": "none", "update": "none", "delete": "none" }
|
|
118
291
|
},
|
|
119
292
|
"indexes": [
|
|
120
293
|
{
|
|
121
|
-
"name": "
|
|
122
|
-
"fields": ["updatedDate"],
|
|
294
|
+
"name": "owner_completed_updated",
|
|
123
295
|
"scope": "owner",
|
|
296
|
+
"fields": ["completed", "updatedDate"],
|
|
124
297
|
"order": { "updatedDate": "desc" }
|
|
125
298
|
}
|
|
126
299
|
],
|
|
@@ -137,11 +310,232 @@ For a personal Todo app:
|
|
|
137
310
|
}
|
|
138
311
|
```
|
|
139
312
|
|
|
140
|
-
|
|
313
|
+
SDK list:
|
|
141
314
|
|
|
142
315
|
```ts
|
|
143
|
-
|
|
316
|
+
await howone.entities.Todo.query.mine({
|
|
144
317
|
page: { number: 1, size: 50 },
|
|
145
318
|
orderBy: { updatedDate: 'desc' },
|
|
146
319
|
})
|
|
147
320
|
```
|
|
321
|
+
|
|
322
|
+
### B. Public Read-Only Catalog
|
|
323
|
+
|
|
324
|
+
Use for articles, templates, listings, published galleries.
|
|
325
|
+
|
|
326
|
+
```json
|
|
327
|
+
{
|
|
328
|
+
"name": "Article",
|
|
329
|
+
"type": "object",
|
|
330
|
+
"visibility": "public",
|
|
331
|
+
"properties": {
|
|
332
|
+
"title": { "type": "string" },
|
|
333
|
+
"slug": { "type": "string" },
|
|
334
|
+
"status": { "type": "string", "enum": ["draft", "published"], "default": "draft" },
|
|
335
|
+
"publishedAt": { "type": ["date", "null"], "default": null }
|
|
336
|
+
},
|
|
337
|
+
"required": ["title", "slug"],
|
|
338
|
+
"access": {
|
|
339
|
+
"authenticated": { "read": "all", "create": "all", "update": "all", "delete": "all" },
|
|
340
|
+
"public": {
|
|
341
|
+
"read": "list",
|
|
342
|
+
"create": "none",
|
|
343
|
+
"update": "none",
|
|
344
|
+
"delete": "none",
|
|
345
|
+
"allowedFilters": ["slug", "status"],
|
|
346
|
+
"allowedSorts": ["publishedAt", "updatedDate"],
|
|
347
|
+
"defaultLimit": 20,
|
|
348
|
+
"maxLimit": 100
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
"indexes": [
|
|
352
|
+
{ "name": "slug_unique", "scope": "global", "fields": ["slug"], "unique": true },
|
|
353
|
+
{
|
|
354
|
+
"name": "status_published",
|
|
355
|
+
"scope": "global",
|
|
356
|
+
"fields": ["status", "publishedAt"],
|
|
357
|
+
"order": { "publishedAt": "desc" }
|
|
358
|
+
}
|
|
359
|
+
]
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
SDK public list:
|
|
364
|
+
|
|
365
|
+
```ts
|
|
366
|
+
await howone.public.entities.Article.query({
|
|
367
|
+
where: { status: 'published' },
|
|
368
|
+
orderBy: { publishedAt: 'desc' },
|
|
369
|
+
page: { number: 1, size: 20 },
|
|
370
|
+
})
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### C. Public Scoped Share Page
|
|
374
|
+
|
|
375
|
+
Use for QR profile, public invoice, public resume, shared report.
|
|
376
|
+
|
|
377
|
+
```json
|
|
378
|
+
{
|
|
379
|
+
"name": "QrProfile",
|
|
380
|
+
"type": "object",
|
|
381
|
+
"visibility": "public",
|
|
382
|
+
"properties": {
|
|
383
|
+
"slug": { "type": "string" },
|
|
384
|
+
"title": { "type": "string" },
|
|
385
|
+
"qrImageUrl": { "type": "string" },
|
|
386
|
+
"active": { "type": "boolean", "default": true }
|
|
387
|
+
},
|
|
388
|
+
"required": ["slug", "title", "qrImageUrl"],
|
|
389
|
+
"access": {
|
|
390
|
+
"authenticated": { "read": "own", "create": "own", "update": "own", "delete": "own" },
|
|
391
|
+
"public": {
|
|
392
|
+
"read": "scoped",
|
|
393
|
+
"create": "none",
|
|
394
|
+
"update": "none",
|
|
395
|
+
"delete": "none",
|
|
396
|
+
"requiredScopes": ["ownerId", "slug"],
|
|
397
|
+
"allowedFilters": ["slug", "active"],
|
|
398
|
+
"allowedSorts": ["updatedDate"],
|
|
399
|
+
"defaultLimit": 1,
|
|
400
|
+
"maxLimit": 10
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
"indexes": [
|
|
404
|
+
{ "name": "owner_slug_unique", "scope": "owner", "fields": ["slug"], "unique": true }
|
|
405
|
+
]
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
SDK public scoped read:
|
|
410
|
+
|
|
411
|
+
```ts
|
|
412
|
+
await howone.public.entities.QrProfile.queryScoped({
|
|
413
|
+
where: { ownerId, slug, active: true },
|
|
414
|
+
page: { number: 1, size: 1 },
|
|
415
|
+
})
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### D. Workflow Output History
|
|
419
|
+
|
|
420
|
+
Use for AI generation/analyze/report flows that need persisted history.
|
|
421
|
+
|
|
422
|
+
```json
|
|
423
|
+
{
|
|
424
|
+
"name": "Generation",
|
|
425
|
+
"type": "object",
|
|
426
|
+
"visibility": "private",
|
|
427
|
+
"properties": {
|
|
428
|
+
"prompt": { "type": "string" },
|
|
429
|
+
"status": { "type": "string", "enum": ["pending", "completed", "failed"], "default": "pending" },
|
|
430
|
+
"resultUrl": { "type": ["string", "null"], "default": null },
|
|
431
|
+
"errorMessage": { "type": ["string", "null"], "default": null },
|
|
432
|
+
"completedAt": { "type": ["date", "null"], "default": null }
|
|
433
|
+
},
|
|
434
|
+
"required": ["prompt", "status"],
|
|
435
|
+
"access": {
|
|
436
|
+
"authenticated": { "read": "own", "create": "own", "update": "own", "delete": "own" },
|
|
437
|
+
"public": { "read": "none", "create": "none", "update": "none", "delete": "none" }
|
|
438
|
+
},
|
|
439
|
+
"indexes": [
|
|
440
|
+
{
|
|
441
|
+
"name": "owner_status_updated",
|
|
442
|
+
"scope": "owner",
|
|
443
|
+
"fields": ["status", "updatedDate"],
|
|
444
|
+
"order": { "updatedDate": "desc" }
|
|
445
|
+
}
|
|
446
|
+
]
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
Rules:
|
|
451
|
+
|
|
452
|
+
- Persist pending before long-running workflow when product needs history/resume.
|
|
453
|
+
- Persist completed output only into fields declared here.
|
|
454
|
+
- Persist failed state and error message if history must show failures.
|
|
455
|
+
- Do not persist raw workflow envelopes unless schema declares an object field for them.
|
|
456
|
+
|
|
457
|
+
## Index Design
|
|
458
|
+
|
|
459
|
+
Every list/detail path should map to an index.
|
|
460
|
+
|
|
461
|
+
| Query shape | Index recommendation |
|
|
462
|
+
|---|---|
|
|
463
|
+
| private user list by updated time | `scope: "owner"`, `fields: ["updatedDate"]` |
|
|
464
|
+
| private user list filtered by status | `scope: "owner"`, `fields: ["status", "updatedDate"]` |
|
|
465
|
+
| owner unique slug | `scope: "owner"`, `fields: ["slug"]`, `unique: true` |
|
|
466
|
+
| public slug detail | `scope: "global"`, `fields: ["slug"]`, `unique: true` |
|
|
467
|
+
| public feed by status/date | `scope: "global"`, `fields: ["status", "publishedAt"]` |
|
|
468
|
+
|
|
469
|
+
Index rules:
|
|
470
|
+
|
|
471
|
+
- Index fields should match real UI queries, not every field.
|
|
472
|
+
- Owner-scoped unique means unique per owner, not globally unique.
|
|
473
|
+
- Public filters/sorts must also be listed in `access.public.allowedFilters/allowedSorts`.
|
|
474
|
+
- Avoid designing public queries that require unbounded scans.
|
|
475
|
+
|
|
476
|
+
## Relations
|
|
477
|
+
|
|
478
|
+
Use `relations` only when frontend/admin needs `include`.
|
|
479
|
+
|
|
480
|
+
```json
|
|
481
|
+
"relations": {
|
|
482
|
+
"author": {
|
|
483
|
+
"type": "entity",
|
|
484
|
+
"entity": "Author",
|
|
485
|
+
"localField": "authorId",
|
|
486
|
+
"foreignField": "id",
|
|
487
|
+
"as": "author"
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
Rules:
|
|
493
|
+
|
|
494
|
+
- Keep relation names stable; SDK/UI may use them in `include`.
|
|
495
|
+
- Do not use relations to hide missing denormalized fields required by list pages.
|
|
496
|
+
- Public include should be conservative; ensure related data is safe to expose.
|
|
497
|
+
|
|
498
|
+
## Presentation and Lifecycle
|
|
499
|
+
|
|
500
|
+
`presentation` is not API validation. It teaches admin UI, codegen, and agents how to display a record:
|
|
501
|
+
|
|
502
|
+
```json
|
|
503
|
+
"presentation": {
|
|
504
|
+
"titleField": "title",
|
|
505
|
+
"imageField": "coverImageUrl",
|
|
506
|
+
"defaultSort": { "field": "updatedDate", "order": "desc" },
|
|
507
|
+
"listFields": ["title", "status", "updatedDate"]
|
|
508
|
+
}
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
`lifecycle` is policy metadata:
|
|
512
|
+
|
|
513
|
+
```json
|
|
514
|
+
"lifecycle": {
|
|
515
|
+
"audit": true,
|
|
516
|
+
"softDelete": false
|
|
517
|
+
}
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
Use it to document intent, but do not assume every lifecycle policy is fully enforced unless backend
|
|
521
|
+
source confirms it.
|
|
522
|
+
|
|
523
|
+
## Schema Review Checklist
|
|
524
|
+
|
|
525
|
+
Before applying a schema:
|
|
526
|
+
|
|
527
|
+
- Entity name and field names pass naming rules.
|
|
528
|
+
- No business field collides with system fields.
|
|
529
|
+
- Every required field exists in `properties`.
|
|
530
|
+
- Every optional nullable field has a deliberate `null` type/default.
|
|
531
|
+
- Defaults exist for fields that should not block create.
|
|
532
|
+
- Access has both `authenticated` and `public`.
|
|
533
|
+
- Public list/scoped flows have filters, sorts, scopes, and limits.
|
|
534
|
+
- Private owned data does not require app code to pass owner fields.
|
|
535
|
+
- Indexes match real list/detail queries.
|
|
536
|
+
- Presentation tells UI/admin which fields to show.
|
|
537
|
+
- Workflow output fields are explicitly declared before persistence.
|
|
538
|
+
- Dangerous public write is avoided or explicitly justified.
|
|
539
|
+
|
|
540
|
+
If any item cannot be answered from requirements, stop and ask for the missing contract instead of
|
|
541
|
+
inventing hidden fields or public exposure.
|