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.
Files changed (24) hide show
  1. package/package.json +1 -1
  2. package/templates/vite/.howone/skills/howone-sdk/01-architect/01-app-generation.md +180 -91
  3. package/templates/vite/.howone/skills/howone-sdk/01-architect/02-manifest-codegen.md +67 -4
  4. package/templates/vite/.howone/skills/howone-sdk/02-database/01-schema-design.md +463 -69
  5. package/templates/vite/.howone/skills/howone-sdk/02-database/02-schema-operations.md +366 -64
  6. package/templates/vite/.howone/skills/howone-sdk/02-database/03-data-access-patterns.md +204 -67
  7. package/templates/vite/.howone/skills/howone-sdk/02-database/04-query-dsl-and-responses.md +237 -0
  8. package/templates/vite/.howone/skills/howone-sdk/02-database/05-ai-persistence-patterns.md +372 -0
  9. package/templates/vite/.howone/skills/howone-sdk/03-sdk/01-client-setup.md +58 -36
  10. package/templates/vite/.howone/skills/howone-sdk/03-sdk/02-entity-operations.md +67 -0
  11. package/templates/vite/.howone/skills/howone-sdk/03-sdk/03-auth.md +267 -469
  12. package/templates/vite/.howone/skills/howone-sdk/03-sdk/04-react-integration.md +113 -320
  13. package/templates/vite/.howone/skills/howone-sdk/03-sdk/07-ai-action-calls.md +66 -16
  14. package/templates/vite/.howone/skills/howone-sdk/03-sdk/08-extension-boundaries.md +226 -0
  15. package/templates/vite/.howone/skills/howone-sdk/04-ai/01-ai-capability-architecture.md +159 -96
  16. package/templates/vite/.howone/skills/howone-sdk/04-ai/02-workflow-contract-rules.md +353 -96
  17. package/templates/vite/.howone/skills/howone-sdk/04-ai/03-ai-sdk-handoff.md +181 -42
  18. package/templates/vite/.howone/skills/howone-sdk/04-ai/04-service-capability-catalog.md +281 -0
  19. package/templates/vite/.howone/skills/howone-sdk/04-ai/05-workflow-operations.md +256 -0
  20. package/templates/vite/.howone/skills/howone-sdk/04-ai/06-ai-feature-playbooks.md +296 -0
  21. package/templates/vite/.howone/skills/howone-sdk/SKILL.md +29 -12
  22. package/templates/vite/.howone/skills/howone-sdk/agents/openai.yaml +2 -2
  23. package/templates/vite/package.json +1 -1
  24. package/templates/vite/.howone/skills/howone-sdk/04-ai/.gitkeep +0 -1
@@ -1,16 +1,19 @@
1
- # Database Architecture Design
1
+ # Database Schema Design
2
2
 
3
- Use this reference when designing or changing HowOne backend entity schemas. It summarizes
4
- the contract in `docs/dynamic-entity-architecture.zh.md` for app-building agents.
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 reference is for **backend entity design**. For schema tool flow, read
7
- `02-database/02-schema-operations.md`. For frontend read/write calls, read
8
- `02-database/03-data-access-patterns.md` and `03-sdk/02-entity-operations.md` after schema
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
- ## Core Model
11
+ ## Mental Model
12
12
 
13
- An entity is a versioned database contract, not a loose JSON form. Design all relevant parts:
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?: AuthenticatedAccess
25
- public?: PublicAccess
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
- Every schema change should preserve this mental split:
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
- - `properties` and `required` define payload validation.
38
- - `access` defines who can read/write and how ownership is derived.
39
- - `indexes` and `performance` define expected query paths.
40
- - `relations` defines valid `include` names.
41
- - `presentation` helps admin UI and SDK generation.
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
- ## Access Contract
145
+ Do not assume full JSON Schema enforcement for every nested constraint. Use Zod/frontend validation
146
+ for stronger UX validation:
45
147
 
46
- Every entity should explicitly define both authenticated and public access. Choose the data posture
47
- in `02-database/03-data-access-patterns.md`, then encode it in the `access` block here.
148
+ - `enum`
149
+ - `minimum` / `maximum`
150
+ - `minLength` / `maxLength`
151
+ - `pattern`
152
+ - nested `items` / `properties`
48
153
 
49
- ## Field Design Rules
154
+ ### Defaults and Generated Fields
50
155
 
51
- - Entity and field names must match `^[a-zA-Z_][a-zA-Z0-9_]*$`.
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
- ## Index and Performance Rules
158
+ ```json
159
+ { "completed": { "type": "boolean", "default": false } }
160
+ ```
62
161
 
63
- Add indexes for the query paths the UI will actually use:
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
- "indexes": [
68
- {
69
- "name": "owner_status_updated",
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
- Guidelines:
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
- - Private user lists usually need owner-scoped indexes.
86
- - Public list pages need `access.public.allowedFilters` and `access.public.allowedSorts`.
87
- - `performance.allowedSorts` informs SDK/admin generation; public sort enforcement comes from
88
- `access.public.allowedSorts`.
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
- ## Todo Example
272
+ ## Standard Patterns
91
273
 
92
- For a personal Todo app:
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
- "read": "own",
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": "owner_updated",
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
- Corresponding frontend read:
313
+ SDK list:
141
314
 
142
315
  ```ts
143
- const result = await howone.entities.Todo.query.mine({
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.