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,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
- ## Private Per-User Data
6
+ For schema design, read `01-schema-design.md`. For query syntax details, read
7
+ `04-query-dsl-and-responses.md`.
6
8
 
7
- Use for todos, notes, journals, saved generations, personal files, dashboards, and user-owned
8
- settings.
9
+ ## Namespace Decision
9
10
 
10
- Schema posture:
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
- "read": "own",
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 calls:
38
+ Frontend:
33
39
 
34
40
  ```ts
35
- const result = await howone.entities.Todo.query.mine({
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
- - Use `query.mine(...)` for list pages.
44
- - Use authenticated `create/update/delete`.
45
- - Do not pass owner fields such as `ownerId`, `created_by_id`, `createdById`,
46
- `created_by_user_id`, or `puid`.
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 in the app should see shared records.
61
+ Use when logged-in app users can see shared records: team projects, CMS admin, internal catalogs.
51
62
 
52
- Schema posture:
63
+ Schema:
53
64
 
54
65
  ```json
55
66
  {
56
67
  "visibility": "private",
57
68
  "access": {
58
- "authenticated": {
59
- "read": "all",
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 calls:
75
+ Frontend:
75
76
 
76
77
  ```ts
77
- const result = await howone.entities.Project.query({
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
- Use `query(...)`, not `query.mine(...)`.
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, catalogs, profiles, or landing-page content.
92
+ Use for public articles, templates, products, profiles, published galleries.
88
93
 
89
- Schema posture:
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 calls:
115
+ Frontend:
116
116
 
117
117
  ```ts
118
- const result = await howone.public.entities.Article.query({
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
- Public query types should expose only allowed filters and allowed sorts.
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 that expose one owner-scoped record, such as QR pages or profile pages.
134
+ Use for public URLs exposing one scoped record: QR profile, public report, resume, invite page.
130
135
 
131
- Schema posture:
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 calls:
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 user's `puid` as public owner scope unless schema explicitly stores that
171
- value as the public scope.
172
- - Public scoped reads must stay inside `howone.public.entities.*`.
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`. |