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
package/package.json
CHANGED
|
@@ -18,6 +18,9 @@ export const entities = defineEntities({
|
|
|
18
18
|
|
|
19
19
|
export const ai = defineAiActions({
|
|
20
20
|
// Add generated AI action bindings here, for example:
|
|
21
|
+
// import { z } from "zod" and define Zod schemas before using defineAiAction.
|
|
22
|
+
// Do not paste JSON Schema objects from .howone/ai/manifest.json here directly.
|
|
23
|
+
// With outputSchema configured, howone.ai.<action>.run() returns the validated finalResult payload.
|
|
21
24
|
// generateImage: defineAiAction("generateImage", {
|
|
22
25
|
// inputSchema: generateImageInputSchema,
|
|
23
26
|
// outputSchema: generateImageOutputSchema,
|
|
@@ -1,101 +1,215 @@
|
|
|
1
1
|
# App Generation Architect
|
|
2
2
|
|
|
3
|
-
Use this file
|
|
4
|
-
|
|
3
|
+
Use this file before building or changing a HowOne generated app. It decides which platform tracks
|
|
4
|
+
must be used and what order to execute them in.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
This is the planning layer. It should prevent the agent from jumping straight into UI code while
|
|
7
|
+
missing schema, auth, AI, manifest, or SDK binding contracts.
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
## Platform Shape
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
manifests.
|
|
12
|
-
2. **SDK binding**: `src/lib/sdk.ts` converts manifests into typed entity and AI clients.
|
|
13
|
-
3. **Frontend experience**: React/UI code calls the SDK and owns app-specific interaction design.
|
|
11
|
+
HowOne generated apps have four product layers:
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
| Layer | Source of truth | App code should do |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| Backend database | synced `.howone/database/manifest.json` | generate entity types/bindings and call SDK |
|
|
16
|
+
| AI capabilities | synced `.howone/ai/manifest.json` + workflow status | generate AI action bindings and call SDK |
|
|
17
|
+
| SDK runtime | `@howone/sdk` + `src/lib/sdk.ts` | centralize env/auth/entities/AI/upload |
|
|
18
|
+
| Frontend app | user experience | own UI, state, feedback, forms, navigation |
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
The agent may propose schema/capability changes, but the validated/synced manifests drive code.
|
|
19
21
|
|
|
20
22
|
```text
|
|
21
|
-
|
|
22
|
-
validated backend manifest = source of truth
|
|
23
|
-
generated files = compiler/binding output
|
|
23
|
+
user request -> architecture decision -> backend/AI contracts -> sync manifests -> sdk binding -> UI
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
Do not skip the binding layer. UI code should import `howone` from `src/lib/sdk.ts`, not construct
|
|
27
|
+
raw URLs or guessed entity/action names.
|
|
28
28
|
|
|
29
|
-
##
|
|
29
|
+
## First Decision: What Surfaces Are Touched?
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
Classify the request before editing.
|
|
32
32
|
|
|
33
|
-
|
|
|
33
|
+
| User request says | Touched surfaces | Required references |
|
|
34
34
|
|---|---|---|
|
|
35
|
-
|
|
|
36
|
-
|
|
|
37
|
-
|
|
|
38
|
-
|
|
|
35
|
+
| "store/save/history/list/my data" | database + SDK + UI | `02-database/`, `03-sdk/02-entity-operations.md` |
|
|
36
|
+
| "login/account/my/private" | auth + database access | `03-sdk/03-auth.md`, `03-sdk/04-react-integration.md` |
|
|
37
|
+
| "public page/share/link/landing/catalog" | public access + SDK public namespace | `02-database/03-data-access-patterns.md` |
|
|
38
|
+
| "AI/generate/analyze/summarize/research/edit image/video/audio" | AI contract + workflow + SDK | `04-ai/` |
|
|
39
|
+
| "upload file/image/audio/pdf" | upload + maybe AI URL input | `03-sdk/05-file-upload.md`, `04-ai/02-workflow-contract-rules.md` |
|
|
40
|
+
| "change schema/add field/new table" | schema operations + manifest codegen | `02-database/02-schema-operations.md`, `01-architect/02-manifest-codegen.md` |
|
|
41
|
+
| "frontend only" | SDK usage + UI | `03-sdk/01-client-setup.md` and relevant SDK docs |
|
|
39
42
|
|
|
40
|
-
If
|
|
41
|
-
default to private user data.
|
|
43
|
+
If multiple surfaces are touched, read one reference from each surface before editing.
|
|
42
44
|
|
|
43
|
-
##
|
|
45
|
+
## Data Posture Decision
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
Choose data posture before schema and UI.
|
|
46
48
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
10. Read the needed SDK file, usually `03-sdk/02-entity-operations.md` and sometimes `03-sdk/03-auth.md`.
|
|
57
|
-
11. Update the UI.
|
|
58
|
-
12. Validate.
|
|
49
|
+
| Product need | Access contract | SDK read |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| per-user private data | authenticated own, public none | `howone.entities.X.query.mine()` |
|
|
52
|
+
| logged-in shared admin/team data | authenticated all, public none | `howone.entities.X.query()` |
|
|
53
|
+
| anonymous public catalog/feed | authenticated all, public list | `howone.public.entities.X.query()` |
|
|
54
|
+
| one public share/detail page | authenticated own/all, public scoped | `howone.public.entities.X.queryScoped()` |
|
|
55
|
+
| anonymous form submission | authenticated all, public create scoped/any | `howone.public.entities.X.create()` |
|
|
56
|
+
| AI generation history | authenticated own, public none | `runAiActionAndPersist()` + `query.mine()` |
|
|
57
|
+
| AI public share | private history + public scoped share entity | two entities |
|
|
59
58
|
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
Defaults:
|
|
60
|
+
|
|
61
|
+
- "my" / "per user" / "private" -> authenticated own.
|
|
62
|
+
- "landing page" / "blog" / "gallery" -> public list only if fields are safe.
|
|
63
|
+
- "share link" / "QR" / "public result" -> public scoped, small `maxLimit`.
|
|
64
|
+
- "AI history" -> private history entity; do not make it public just for sharing.
|
|
62
65
|
|
|
63
66
|
## Auth Decision
|
|
64
67
|
|
|
65
|
-
|
|
68
|
+
| Need | SDK config | Provider behavior |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| default HowOne login | `createClient({ projectId, env })` | hosted login |
|
|
71
|
+
| custom designed login page using HowOne auth APIs | `auth: 'custom'`, `HowOneProvider auth="none"` | app owns login UI |
|
|
72
|
+
| external identity provider/JWT | `auth: { mode: 'headless', adapter }` | adapter owns token/user |
|
|
73
|
+
| public-only app | `auth: 'none'` | no auth guard |
|
|
74
|
+
|
|
75
|
+
Rules:
|
|
66
76
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
77
|
+
- Keep the bottom-right HowOne `FloatingButton` by default unless explicitly hidden.
|
|
78
|
+
- SDK must not add toast/overlay/login-page UI.
|
|
79
|
+
- Use `client.me()` or `client.requireMe()` for first-load user resolution.
|
|
80
|
+
- Do not use `auth.isAuthenticated()` as the only initial truth when user data must be loaded.
|
|
81
|
+
|
|
82
|
+
## Backend Feature Workflow
|
|
83
|
+
|
|
84
|
+
Use when persistence or schema changes are needed:
|
|
85
|
+
|
|
86
|
+
1. Read `02-database/01-schema-design.md`.
|
|
87
|
+
2. Read `02-database/02-schema-operations.md`.
|
|
88
|
+
3. Inspect current schema state/manifest.
|
|
89
|
+
4. Design complete entity contract: fields, required, access, indexes, presentation.
|
|
90
|
+
5. Preview one complete schema patch.
|
|
91
|
+
6. Apply the exact previewed patch if risk is acceptable.
|
|
92
|
+
7. Sync schema artifacts from returned version.
|
|
93
|
+
8. Read `.howone/database/manifest.json`.
|
|
94
|
+
9. Update `src/lib/sdk.ts` from manifest using `01-architect/02-manifest-codegen.md`.
|
|
95
|
+
10. Implement UI with `howone.entities.*` or `howone.public.entities.*`.
|
|
96
|
+
11. Validate build/tests.
|
|
97
|
+
|
|
98
|
+
Risk stops:
|
|
99
|
+
|
|
100
|
+
- deleting entity/field;
|
|
101
|
+
- making required field without default;
|
|
102
|
+
- broadening public access;
|
|
103
|
+
- enabling public write;
|
|
104
|
+
- changing owner/public scope semantics.
|
|
105
|
+
|
|
106
|
+
## AI Feature Workflow
|
|
107
|
+
|
|
108
|
+
Use when AI capability/workflow is needed:
|
|
109
|
+
|
|
110
|
+
1. Read `04-ai/01-ai-capability-architecture.md`.
|
|
111
|
+
2. Read `04-ai/04-service-capability-catalog.md` to verify support.
|
|
112
|
+
3. Pick a playbook from `04-ai/06-ai-feature-playbooks.md` when applicable.
|
|
113
|
+
4. Design schemas with `04-ai/02-workflow-contract-rules.md`.
|
|
114
|
+
5. Preview/apply AI capability patch.
|
|
115
|
+
6. Sync `.howone/ai/manifest.json`.
|
|
116
|
+
7. Submit external workflow create/update using `04-ai/05-workflow-operations.md`.
|
|
117
|
+
8. Preserve returned `request_id`.
|
|
118
|
+
9. Poll status until terminal; preserve `workflowConfigID` on success.
|
|
119
|
+
10. Update `src/lib/sdk.ts` with `04-ai/03-ai-sdk-handoff.md`.
|
|
120
|
+
11. Implement UI through `howone.ai.*`.
|
|
121
|
+
12. If output persists, use `02-database/05-ai-persistence-patterns.md`.
|
|
122
|
+
|
|
123
|
+
Do not build fake AI. If the required capability is unsupported, report the exact gap.
|
|
124
|
+
|
|
125
|
+
## Manifest Codegen Workflow
|
|
126
|
+
|
|
127
|
+
Run after database or AI sync:
|
|
128
|
+
|
|
129
|
+
1. Read current `src/lib/sdk.ts`.
|
|
130
|
+
2. Read synced manifests.
|
|
131
|
+
3. Preserve existing exports and naming style.
|
|
132
|
+
4. Generate/update:
|
|
133
|
+
- entity `Record/Create/Update` types;
|
|
134
|
+
- optional exported `*EntityDefinition` for guards;
|
|
135
|
+
- `client.entity<...>()` bindings;
|
|
136
|
+
- AI Zod schemas and `defineAiAction(...)`;
|
|
137
|
+
- composed `howone` export.
|
|
138
|
+
5. Never write generated source under `.howone/`.
|
|
72
139
|
|
|
73
|
-
|
|
140
|
+
## Common User Situations
|
|
74
141
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
142
|
+
### User asks for "just a frontend"
|
|
143
|
+
|
|
144
|
+
Still check whether UI needs stored data, auth, upload, or AI. If it only renders static/local state,
|
|
145
|
+
do not invent schema or workflow. If it saves anything, use database flow.
|
|
146
|
+
|
|
147
|
+
### User asks for "AI app" but no persistence
|
|
148
|
+
|
|
149
|
+
Design AI capability and SDK binding only. Keep results in app state. Do not create entities unless
|
|
150
|
+
history, refresh resilience, user library, or share page is needed.
|
|
151
|
+
|
|
152
|
+
### User asks for "AI app with history"
|
|
153
|
+
|
|
154
|
+
Design AI first, then database:
|
|
155
|
+
|
|
156
|
+
```text
|
|
157
|
+
AI output contract -> Generation entity -> runAiActionAndPersist -> history query.mine()
|
|
79
158
|
```
|
|
80
159
|
|
|
81
|
-
|
|
82
|
-
|
|
160
|
+
Do not put history fields into workflow output unless the AI itself must generate them.
|
|
161
|
+
|
|
162
|
+
### User asks for "public AI result"
|
|
163
|
+
|
|
164
|
+
Use two entities:
|
|
165
|
+
|
|
166
|
+
- private `Generation` for owner history and retry;
|
|
167
|
+
- public scoped `SharedGeneration` for anonymous viewing.
|
|
168
|
+
|
|
169
|
+
Do not expose private prompt/history broadly.
|
|
170
|
+
|
|
171
|
+
### User asks for "latest/current/research"
|
|
172
|
+
|
|
173
|
+
Use AI workflow with web search/crawling capability. If app also lists saved briefings, add entity
|
|
174
|
+
persistence after output contract.
|
|
175
|
+
|
|
176
|
+
### User asks to modify existing AI behavior
|
|
177
|
+
|
|
178
|
+
If only behavior changes, use external workflow update with `workflowConfigID`.
|
|
179
|
+
If input/output changes, update AI capability contract first, sync manifest, then update workflow
|
|
180
|
+
and SDK bindings.
|
|
181
|
+
|
|
182
|
+
### User asks to change schema used by UI
|
|
183
|
+
|
|
184
|
+
Change backend schema first, sync, regenerate SDK, then update UI. Do not patch UI types from memory.
|
|
185
|
+
|
|
186
|
+
### User asks for custom auth
|
|
187
|
+
|
|
188
|
+
Use `auth: 'custom'` or headless `AuthAdapter`. App owns visible login UI. Keep SDK callbacks/data
|
|
189
|
+
only.
|
|
190
|
+
|
|
191
|
+
## Implementation Guardrails
|
|
83
192
|
|
|
84
|
-
|
|
193
|
+
- Use `@howone/sdk` typed clients before `raw`.
|
|
194
|
+
- Use `howone.raw` only when typed surface is missing.
|
|
195
|
+
- Do not hardcode HowOne API URLs.
|
|
196
|
+
- Do not pass owner fields for authenticated own records.
|
|
197
|
+
- Do not import or create SDK toast APIs.
|
|
198
|
+
- Do not remove HowOne floating logo unless explicitly requested.
|
|
199
|
+
- Do not call AI workflows from render.
|
|
200
|
+
- Do not persist workflow envelopes or UI-only fields.
|
|
201
|
+
- Do not assume unavailable AI capabilities.
|
|
85
202
|
|
|
86
|
-
|
|
87
|
-
- Preserve existing bindings unless the manifest changed.
|
|
88
|
-
- Export explicit `Record`, `Create`, and `Update` types.
|
|
89
|
-
- Do not include system fields in create/update payloads.
|
|
90
|
-
- Do not infer public access from `visibility` alone; inspect `access.public`.
|
|
203
|
+
## Final Architecture Checklist
|
|
91
204
|
|
|
92
|
-
|
|
205
|
+
Before writing final code:
|
|
93
206
|
|
|
94
|
-
-
|
|
95
|
-
|
|
96
|
-
-
|
|
97
|
-
|
|
98
|
-
-
|
|
99
|
-
|
|
100
|
-
-
|
|
101
|
-
|
|
207
|
+
- Data posture is explicit.
|
|
208
|
+
- Auth mode is explicit.
|
|
209
|
+
- Public access has filters/sorts/scopes/limits.
|
|
210
|
+
- AI capability is supported by service catalog.
|
|
211
|
+
- Workflow count follows one-feature rule or RAG exception.
|
|
212
|
+
- Persistence is separate from AI workflow.
|
|
213
|
+
- Manifests are synced before SDK codegen.
|
|
214
|
+
- `src/lib/sdk.ts` is the only app SDK entrypoint.
|
|
215
|
+
- UI owns visible states and feedback.
|
|
@@ -13,6 +13,11 @@ HowOne apps are driven by two backend-synced manifests:
|
|
|
13
13
|
|
|
14
14
|
Sync tools (`sync_schema_artifacts`, `sync_ai_artifacts`) write the manifests. The coding agent reads the manifests and writes `src/lib/sdk.ts`.
|
|
15
15
|
|
|
16
|
+
For AI capabilities, external workflow create/update is submitted by `external-ai-capability` from
|
|
17
|
+
the synced manifest. Do not duplicate AI schemas in app code beyond generated zod/type bindings.
|
|
18
|
+
For workflow edits, `workflowConfigID` belongs to the external workflow operation; it is not an SDK
|
|
19
|
+
binding field.
|
|
20
|
+
|
|
16
21
|
---
|
|
17
22
|
|
|
18
23
|
## Reading `.howone/database/manifest.json`
|
|
@@ -157,6 +162,7 @@ export type CommentUpdate = Partial<CommentCreate>
|
|
|
157
162
|
{
|
|
158
163
|
"id": "generateStory",
|
|
159
164
|
"name": "Generate Story",
|
|
165
|
+
"workflowId": "d69ab648-2c00-4d94-928e-01bd7b2a5bb2",
|
|
160
166
|
"inputSchema": {
|
|
161
167
|
"type": "object",
|
|
162
168
|
"properties": {
|
|
@@ -179,6 +185,7 @@ export type CommentUpdate = Partial<CommentCreate>
|
|
|
179
185
|
{
|
|
180
186
|
"id": "translateText",
|
|
181
187
|
"name": "Translate Text",
|
|
188
|
+
"workflowId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
|
182
189
|
"inputSchema": {
|
|
183
190
|
"type": "object",
|
|
184
191
|
"properties": {
|
|
@@ -219,12 +226,12 @@ export const generateStoryInputSchema = z.object({
|
|
|
219
226
|
})
|
|
220
227
|
export type GenerateStoryInput = z.infer<typeof generateStoryInputSchema>
|
|
221
228
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
229
|
+
export const generateStoryOutputSchema = z.object({
|
|
230
|
+
title: z.string(),
|
|
231
|
+
content: z.string(),
|
|
232
|
+
summary: z.string().optional(),
|
|
233
|
+
})
|
|
234
|
+
export type GenerateStoryOutput = z.infer<typeof generateStoryOutputSchema>
|
|
228
235
|
|
|
229
236
|
// ── translateText ─────────────────────────────────────────────
|
|
230
237
|
export const translateTextInputSchema = z.object({
|
|
@@ -235,6 +242,10 @@ export const translateTextInputSchema = z.object({
|
|
|
235
242
|
export type TranslateTextInput = z.infer<typeof translateTextInputSchema>
|
|
236
243
|
```
|
|
237
244
|
|
|
245
|
+
Do not make required output fields optional to silence validation failures. Do not add
|
|
246
|
+
`.passthrough()` as a workaround for EAX execution envelopes. `defineAiAction` validates the
|
|
247
|
+
workflow `finalResult` payload when an `outputSchema` is configured.
|
|
248
|
+
|
|
238
249
|
---
|
|
239
250
|
|
|
240
251
|
## Full Generated `src/lib/sdk.ts`
|
|
@@ -249,6 +260,8 @@ import {
|
|
|
249
260
|
defineAiAction,
|
|
250
261
|
defineAiActions,
|
|
251
262
|
defineEntities,
|
|
263
|
+
runAiActionAndPersist,
|
|
264
|
+
type EntityDefinition,
|
|
252
265
|
type EntityRecord,
|
|
253
266
|
withAiActions,
|
|
254
267
|
withEntities,
|
|
@@ -293,6 +306,41 @@ export type CommentCreate = {
|
|
|
293
306
|
}
|
|
294
307
|
export type CommentUpdate = Partial<CommentCreate>
|
|
295
308
|
|
|
309
|
+
export const storyEntityDefinition = {
|
|
310
|
+
name: 'Story',
|
|
311
|
+
type: 'object',
|
|
312
|
+
properties: {
|
|
313
|
+
title: { type: 'string' },
|
|
314
|
+
content: { type: 'string' },
|
|
315
|
+
authorId: { type: 'string' },
|
|
316
|
+
status: { type: 'string', enum: ['draft', 'published', 'archived'] },
|
|
317
|
+
wordCount: { type: 'integer' },
|
|
318
|
+
tags: { type: 'array', items: { type: 'string' } },
|
|
319
|
+
coverUrl: { type: 'string' },
|
|
320
|
+
},
|
|
321
|
+
required: ['title', 'content', 'authorId', 'status', 'wordCount'],
|
|
322
|
+
access: {
|
|
323
|
+
authenticated: { read: 'own', create: 'own', update: 'own', delete: 'own' },
|
|
324
|
+
public: { read: 'none', create: 'none', update: 'none', delete: 'none' },
|
|
325
|
+
},
|
|
326
|
+
} satisfies EntityDefinition
|
|
327
|
+
|
|
328
|
+
export const commentEntityDefinition = {
|
|
329
|
+
name: 'Comment',
|
|
330
|
+
type: 'object',
|
|
331
|
+
properties: {
|
|
332
|
+
storyId: { type: 'string' },
|
|
333
|
+
authorId: { type: 'string' },
|
|
334
|
+
body: { type: 'string' },
|
|
335
|
+
likes: { type: 'integer' },
|
|
336
|
+
},
|
|
337
|
+
required: ['storyId', 'authorId', 'body'],
|
|
338
|
+
access: {
|
|
339
|
+
authenticated: { read: 'own', create: 'own', update: 'own', delete: 'own' },
|
|
340
|
+
public: { read: 'none', create: 'none', update: 'none', delete: 'none' },
|
|
341
|
+
},
|
|
342
|
+
} satisfies EntityDefinition
|
|
343
|
+
|
|
296
344
|
// ═══════════════════════════════════════════════════════════════
|
|
297
345
|
// AI SCHEMAS & TYPES
|
|
298
346
|
// ═══════════════════════════════════════════════════════════════
|
|
@@ -303,11 +351,12 @@ export const generateStoryInputSchema = z.object({
|
|
|
303
351
|
language: z.string().optional(),
|
|
304
352
|
})
|
|
305
353
|
export type GenerateStoryInput = z.infer<typeof generateStoryInputSchema>
|
|
306
|
-
export
|
|
307
|
-
title: string
|
|
308
|
-
content: string
|
|
309
|
-
summary
|
|
310
|
-
}
|
|
354
|
+
export const generateStoryOutputSchema = z.object({
|
|
355
|
+
title: z.string(),
|
|
356
|
+
content: z.string(),
|
|
357
|
+
summary: z.string().optional(),
|
|
358
|
+
})
|
|
359
|
+
export type GenerateStoryOutput = z.infer<typeof generateStoryOutputSchema>
|
|
311
360
|
|
|
312
361
|
export const translateTextInputSchema = z.object({
|
|
313
362
|
text: z.string(),
|
|
@@ -315,10 +364,6 @@ export const translateTextInputSchema = z.object({
|
|
|
315
364
|
formality: z.enum(['formal', 'informal']).optional(),
|
|
316
365
|
})
|
|
317
366
|
export type TranslateTextInput = z.infer<typeof translateTextInputSchema>
|
|
318
|
-
export type TranslateTextOutput = {
|
|
319
|
-
translatedText: string
|
|
320
|
-
detectedLang?: string
|
|
321
|
-
}
|
|
322
367
|
|
|
323
368
|
// ═══════════════════════════════════════════════════════════════
|
|
324
369
|
// CLIENT
|
|
@@ -344,9 +389,12 @@ export const entities = defineEntities({
|
|
|
344
389
|
|
|
345
390
|
export const ai = defineAiActions({
|
|
346
391
|
generateStory: defineAiAction('generateStory', {
|
|
392
|
+
workflowId: 'd69ab648-2c00-4d94-928e-01bd7b2a5bb2',
|
|
347
393
|
inputSchema: generateStoryInputSchema,
|
|
394
|
+
outputSchema: generateStoryOutputSchema,
|
|
348
395
|
}),
|
|
349
396
|
translateText: defineAiAction('translateText', {
|
|
397
|
+
workflowId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
|
|
350
398
|
inputSchema: translateTextInputSchema,
|
|
351
399
|
}),
|
|
352
400
|
})
|
|
@@ -366,12 +414,17 @@ export default howone
|
|
|
366
414
|
Before finalising generated code, verify:
|
|
367
415
|
|
|
368
416
|
- [ ] Every entity from `.howone/database/manifest.json` has a `Record`, `Create`, and `Update` type
|
|
417
|
+
- [ ] Entity definitions are exported as `*EntityDefinition` when app code needs payload/query guards
|
|
369
418
|
- [ ] `Create` types are defined **explicitly** (not via `Omit`)
|
|
370
419
|
- [ ] Create optionality accounts for `required`, `default`, `defaultValue`, `autoGenerate`, and nullable types
|
|
371
420
|
- [ ] System fields are not present in create/update input types
|
|
372
421
|
- [ ] Public query/write types are generated only from `access.public.allowedFilters`, `allowedSorts`, `requiredScopes`, and write permissions
|
|
373
422
|
- [ ] Every AI action from `.howone/ai/manifest.json` has an `inputSchema` zod object
|
|
423
|
+
- [ ] Every AI action with manifest `outputSchema` has a matching zod `outputSchema`
|
|
424
|
+
- [ ] Every AI action binding includes the exact manifest `workflowId`
|
|
374
425
|
- [ ] Required input fields are not `.optional()` in zod
|
|
426
|
+
- [ ] Required output fields are not `.optional()` in zod
|
|
427
|
+
- [ ] AI output schemas do not use `.passthrough()` to hide execution-envelope mismatches
|
|
375
428
|
- [ ] AI action names match the manifest `id` exactly (case-sensitive)
|
|
376
429
|
- [ ] `createClient` uses `import.meta.env.*` only
|
|
377
430
|
- [ ] `withEntities` is applied before `withAiActions` in the composition chain
|
|
@@ -408,20 +461,21 @@ export const entities = defineEntities({
|
|
|
408
461
|
|
|
409
462
|
When the app generates data with AI and saves it to an entity:
|
|
410
463
|
|
|
411
|
-
1. Read `.howone/ai/manifest.json`
|
|
412
|
-
2.
|
|
413
|
-
3. Add
|
|
414
|
-
|
|
464
|
+
1. Read `.howone/ai/manifest.json` to know the typed AI output.
|
|
465
|
+
2. Decide which output fields are durable product fields.
|
|
466
|
+
3. Add app-specific persistence fields such as `status`, `errorMessage`, `requestedAt`,
|
|
467
|
+
`completedAt`, source URLs, prompt/options, or share state.
|
|
468
|
+
4. Define/update the entity schema from product persistence needs, not by blindly copying
|
|
469
|
+
`outputSchema`.
|
|
470
|
+
5. Generate entity types and AI action bindings from synced manifests.
|
|
471
|
+
6. Use `runAiActionAndPersist()` for history-style products.
|
|
415
472
|
|
|
416
473
|
```ts
|
|
417
474
|
// AI generates: { title: string, content: string, summary: string }
|
|
418
475
|
// Save it to Story entity which adds: authorId, status, wordCount
|
|
419
476
|
|
|
420
477
|
async function generateAndSave(input: GenerateStoryInput, authorId: string) {
|
|
421
|
-
const
|
|
422
|
-
if (!result.success) throw new Error(result.errors.join(', '))
|
|
423
|
-
|
|
424
|
-
const output = result.finalResult as GenerateStoryOutput
|
|
478
|
+
const output = await howone.ai.generateStory.run(input)
|
|
425
479
|
|
|
426
480
|
return howone.entities.Story.create({
|
|
427
481
|
title: output.title,
|
|
@@ -432,3 +486,24 @@ async function generateAndSave(input: GenerateStoryInput, authorId: string) {
|
|
|
432
486
|
})
|
|
433
487
|
}
|
|
434
488
|
```
|
|
489
|
+
|
|
490
|
+
History-style generation should create a pending record before running AI:
|
|
491
|
+
|
|
492
|
+
```ts
|
|
493
|
+
await runAiActionAndPersist({
|
|
494
|
+
entity: howone.entities.Generation,
|
|
495
|
+
input,
|
|
496
|
+
createPending: (input) => ({
|
|
497
|
+
prompt: input.topic,
|
|
498
|
+
status: 'pending',
|
|
499
|
+
requestedAt: new Date().toISOString(),
|
|
500
|
+
}),
|
|
501
|
+
run: (input) => howone.ai.generateStory.run(input),
|
|
502
|
+
mapCompleted: ({ output }) => ({
|
|
503
|
+
status: 'completed',
|
|
504
|
+
title: output.title,
|
|
505
|
+
content: output.content,
|
|
506
|
+
completedAt: new Date().toISOString(),
|
|
507
|
+
}),
|
|
508
|
+
})
|
|
509
|
+
```
|