howone 0.2.3 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/templates/vite/.howone/skills/howone/01-architect/01-app-generation.md +8 -7
- package/templates/vite/.howone/skills/howone/01-architect/02-manifest-codegen.md +121 -436
- package/templates/vite/.howone/skills/howone/03-ai-capabilities/04-workflow-operations.md +13 -4
- package/templates/vite/.howone/skills/howone/04-app-sdk/01-client-setup.md +94 -261
- package/templates/vite/.howone/skills/howone/04-app-sdk/02-entity-operations.md +85 -465
- package/templates/vite/.howone/skills/howone/04-app-sdk/03-auth.md +11 -7
- package/templates/vite/.howone/skills/howone/04-app-sdk/04-react-integration.md +84 -137
- package/templates/vite/.howone/skills/howone/04-app-sdk/05-file-upload.md +66 -273
- package/templates/vite/.howone/skills/howone/04-app-sdk/06-raw-http.md +72 -249
- package/templates/vite/.howone/skills/howone/04-app-sdk/07-ai-action-calls.md +135 -499
- package/templates/vite/.howone/skills/howone/04-app-sdk/08-ai-manifest-handoff.md +49 -196
- package/templates/vite/.howone/skills/howone/04-app-sdk/09-extension-boundaries.md +4 -4
- package/templates/vite/.howone/skills/howone/04-app-sdk/10-workflow-execute-sse.md +94 -61
- package/templates/vite/.howone/skills/howone/04-app-sdk/11-entity-data-access-patterns.md +4 -3
- package/templates/vite/.howone/skills/howone/SKILL.md +48 -8
- package/templates/vite/.howone/skills/howone/references/common-errors.md +27 -0
- package/templates/vite/.howone/skills/howone/references/version-evidence.md +47 -0
- package/templates/vite/.howone/skills/howone/scripts/verify-project.mjs +151 -0
- package/templates/vite/package.json +1 -1
- package/templates/vite/src/App.tsx +9 -5
- package/templates/vite/src/lib/sdk.ts +7 -5
- package/templates/vite/bun.lock +0 -1478
|
@@ -108,6 +108,11 @@ Terminal result meaning:
|
|
|
108
108
|
| `completed` | workflow operation completed |
|
|
109
109
|
| `failed` / `canceled` / `timed_out` | report error and do not pretend workflow is ready |
|
|
110
110
|
|
|
111
|
+
A non-terminal submission is not a legal completion or pause boundary for the Coding Agent. Do not
|
|
112
|
+
end the run merely to wait for a host notification. Continue all implementation that does not need
|
|
113
|
+
the promoted workflow ID, including UI structure, persistence, loading/error states, and other app
|
|
114
|
+
logic. Defer only final workflow-ID binding and live execution verification until terminal success.
|
|
115
|
+
|
|
111
116
|
## Standard Flow
|
|
112
117
|
|
|
113
118
|
New AI feature:
|
|
@@ -116,7 +121,8 @@ New AI feature:
|
|
|
116
121
|
ai-capability-design apply_capability_patch
|
|
117
122
|
sync_ai_artifacts
|
|
118
123
|
external-ai-capability { cwd, capabilityNames }
|
|
119
|
-
|
|
124
|
+
continue independent app implementation; do not stop to wait
|
|
125
|
+
terminal result
|
|
120
126
|
sync_ai_artifacts
|
|
121
127
|
read .howone/ai/manifest.json
|
|
122
128
|
```
|
|
@@ -127,7 +133,8 @@ Schema-changing AI update:
|
|
|
127
133
|
ai-capability-design apply_capability_patch
|
|
128
134
|
sync_ai_artifacts
|
|
129
135
|
external-ai-capability { cwd, updates: [{ capabilityName, updatePrompt }] }
|
|
130
|
-
|
|
136
|
+
continue independent app implementation; do not stop to wait
|
|
137
|
+
terminal result
|
|
131
138
|
sync_ai_artifacts
|
|
132
139
|
read .howone/ai/manifest.json
|
|
133
140
|
```
|
|
@@ -136,13 +143,15 @@ Behavior-only update:
|
|
|
136
143
|
|
|
137
144
|
```text
|
|
138
145
|
external-ai-capability { cwd, updates: [{ capabilityName, updatePrompt }] }
|
|
139
|
-
|
|
146
|
+
continue independent app implementation; do not stop to wait
|
|
147
|
+
terminal result
|
|
140
148
|
sync_ai_artifacts
|
|
141
149
|
read .howone/ai/manifest.json
|
|
142
150
|
update SDK workflowId bindings from the newly synced manifest
|
|
143
151
|
```
|
|
144
152
|
|
|
145
|
-
SDK
|
|
153
|
+
Final workflow-ID SDK binding happens after terminal sync and belongs to the SDK track. Other app
|
|
154
|
+
source work proceeds while the external job is pending.
|
|
146
155
|
|
|
147
156
|
## Checklist
|
|
148
157
|
|
|
@@ -1,329 +1,162 @@
|
|
|
1
1
|
# Client Setup
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use this track after synced manifests exist and before writing app code that imports
|
|
4
|
+
`@howone/sdk`. Read [version-evidence.md](../../references/version-evidence.md) first when the installed
|
|
5
|
+
SDK version is uncertain.
|
|
4
6
|
|
|
5
|
-
##
|
|
7
|
+
## One client, one source of truth
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
### CreateClientOptions — full type
|
|
10
|
-
|
|
11
|
-
```ts
|
|
12
|
-
type Environment = 'local' | 'dev' | 'prod'
|
|
13
|
-
|
|
14
|
-
type CreateClientOptions = {
|
|
15
|
-
// ── Required ──────────────────────────────────────────────
|
|
16
|
-
projectId?: string // Your HowOne project ID (set via VITE_HOWONE_PROJECT_ID)
|
|
17
|
-
|
|
18
|
-
// ── Environment ───────────────────────────────────────────
|
|
19
|
-
env?: Environment | string // 'local' | 'dev' | 'prod' (set via VITE_HOWONE_ENV)
|
|
20
|
-
apiUrl?: string // Override the REST API base URL
|
|
21
|
-
aiUrl?: string // Override the AI/SSE base URL
|
|
22
|
-
|
|
23
|
-
// ── Behaviour ─────────────────────────────────────────────
|
|
24
|
-
caseStyle?: 'camel' | 'snake' // Default: 'camel'
|
|
25
|
-
mode?: 'auto' | 'standalone' | 'embedded'
|
|
26
|
-
|
|
27
|
-
// ── Auth (one parameter for custom login) ─────────────────
|
|
28
|
-
auth?: 'custom' | 'hosted' | 'headless' | 'none' | {
|
|
29
|
-
mode?: 'custom' | 'hosted' | 'headless' | 'none' | 'managed'
|
|
30
|
-
loginPath?: string // default '/login' when mode is 'custom'
|
|
31
|
-
logoutPath?: string
|
|
32
|
-
guard?: 'required' | 'optional' | 'none'
|
|
33
|
-
getToken?: () => Promise<string | null>
|
|
34
|
-
adapter?: AuthAdapter
|
|
35
|
-
tokenCacheMs?: number
|
|
36
|
-
}
|
|
37
|
-
loginPath?: string // shorthand when auth is 'custom'
|
|
38
|
-
logoutPath?: string
|
|
39
|
-
|
|
40
|
-
// ── Limit-exceeded callbacks ───────────────────────────────
|
|
41
|
-
limitExceeded?: {
|
|
42
|
-
onLimitExceeded?: (context: LimitExceededContext) => void
|
|
43
|
-
showUpgradeToast?: boolean
|
|
44
|
-
upgradeUrl?: string
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// ── Deprecated — do not use in new code ───────────────────
|
|
48
|
-
appId?: string // Use projectId
|
|
49
|
-
baseUrl?: string // Use apiUrl / aiUrl
|
|
50
|
-
apiBaseUrl?: string // Use apiUrl
|
|
51
|
-
aiBaseUrl?: string // Use aiUrl
|
|
52
|
-
authRequired?: boolean // Use auth.mode
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### What createClient returns
|
|
57
|
-
|
|
58
|
-
```ts
|
|
59
|
-
const client = createClient({ ... })
|
|
60
|
-
|
|
61
|
-
client.projectId // string — resolved project ID
|
|
62
|
-
client.appId // string — alias for projectId
|
|
63
|
-
client.caseStyle // 'camel' | 'snake'
|
|
64
|
-
|
|
65
|
-
// Entity factory
|
|
66
|
-
client.entity<TRecord, TCreate, TUpdate>(entityName: string): EntityClient
|
|
67
|
-
|
|
68
|
-
// Typed entity map (populated via withEntities)
|
|
69
|
-
client.entities: Record<string, EntityClient>
|
|
70
|
-
|
|
71
|
-
// Public entity namespace (never sends Authorization)
|
|
72
|
-
client.public.entity<TRecord, TPublicCreate, TPublicUpdate>(entityName: string): PublicEntityClient
|
|
73
|
-
client.public.entities: Record<string, PublicEntityClient>
|
|
74
|
-
client.public.raw: RawHttpClient
|
|
75
|
-
|
|
76
|
-
// Schema contract/version client
|
|
77
|
-
client.schema.listDefinitions()
|
|
78
|
-
client.schema.getDefinition(entityName)
|
|
79
|
-
client.schema.operate(operation)
|
|
80
|
-
client.schema.applyPatch(patch, { expectedVersionId, reason })
|
|
81
|
-
client.schema.getState()
|
|
82
|
-
client.schema.listVersions()
|
|
83
|
-
client.schema.getVersion(versionId)
|
|
84
|
-
client.schema.restore(versionId, reason?)
|
|
85
|
-
|
|
86
|
-
// AI action runner (low-level)
|
|
87
|
-
client.ai: AiClient
|
|
88
|
-
|
|
89
|
-
// HTTP utilities (low-level — see 04-app-sdk/06-raw-http.md)
|
|
90
|
-
client.raw: RawHttpClient
|
|
91
|
-
|
|
92
|
-
// File upload
|
|
93
|
-
client.upload.file(file, options?)
|
|
94
|
-
client.upload.image(file)
|
|
95
|
-
client.upload.batch(options)
|
|
96
|
-
|
|
97
|
-
// User profile
|
|
98
|
-
client.me(options?) // Promise<UserProfile | null>
|
|
99
|
-
client.requireMe(options?) // Promise<UserProfile> throws if unauthenticated
|
|
100
|
-
client.session.user() // alias for client.me()
|
|
101
|
-
|
|
102
|
-
// Auth helpers (behavior driven by createClient auth mode)
|
|
103
|
-
client.auth.mode // 'custom' | 'hosted' | 'headless' | 'none'
|
|
104
|
-
client.auth.loginPath // e.g. '/login'
|
|
105
|
-
client.auth.setToken(token: string | null)
|
|
106
|
-
client.auth.getToken(): string | null
|
|
107
|
-
client.auth.isAuthenticated(): boolean
|
|
108
|
-
client.auth.login(returnUrl?: string)
|
|
109
|
-
await client.auth.logout()
|
|
110
|
-
await client.auth.clearSession({ redirect?: false | string })
|
|
111
|
-
client.auth.subscribe((state) => { ... }) // auth state callback
|
|
112
|
-
|
|
113
|
-
// URL utilities
|
|
114
|
-
client.sanitizeUrl(opts?: { clearAll?: boolean; sensitiveParams?: string[] })
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
---
|
|
118
|
-
|
|
119
|
-
## Standard Vite Setup
|
|
9
|
+
Create the client once at module scope in `src/lib/sdk.ts`. Use the same composed instance for
|
|
10
|
+
entities, AI, uploads, auth, and raw requests. Do not create clients inside components or handlers.
|
|
120
11
|
|
|
121
12
|
```ts
|
|
122
13
|
// src/lib/sdk.ts
|
|
123
14
|
import {
|
|
124
15
|
createClient,
|
|
125
|
-
defineAiAction,
|
|
126
16
|
defineAiActions,
|
|
127
17
|
defineEntities,
|
|
128
|
-
pickEntityPayload,
|
|
129
|
-
runAiActionAndPersist,
|
|
130
|
-
type EntityRecord,
|
|
131
18
|
withAiActions,
|
|
132
19
|
withEntities,
|
|
133
20
|
} from '@howone/sdk'
|
|
134
|
-
import { z } from 'zod'
|
|
135
21
|
|
|
136
|
-
// ── 1. Create client ─────────────────────────────────────────
|
|
137
22
|
const client = createClient({
|
|
138
23
|
projectId: import.meta.env.VITE_HOWONE_PROJECT_ID,
|
|
139
24
|
env: import.meta.env.VITE_HOWONE_ENV,
|
|
140
25
|
})
|
|
141
26
|
|
|
142
|
-
// ── 2. Define entity types & bind ────────────────────────────
|
|
143
|
-
// (see 04-app-sdk/02-entity-operations.md for full details)
|
|
144
|
-
export type NoteRecord = EntityRecord & { title: string; body: string }
|
|
145
|
-
export type NoteCreate = { title: string; body: string }
|
|
146
|
-
export type NoteUpdate = Partial<NoteCreate>
|
|
147
|
-
|
|
148
27
|
export const entities = defineEntities({
|
|
149
|
-
|
|
28
|
+
// Generated bindings go here; read 01-architect/02-manifest-codegen.md.
|
|
150
29
|
})
|
|
30
|
+
export const ai = defineAiActions({})
|
|
151
31
|
|
|
152
|
-
// ── 3. Define AI actions ─────────────────────────────────────
|
|
153
|
-
// (see 04-app-sdk/07-ai-action-calls.md for full details)
|
|
154
|
-
export const summarizeInputSchema = z.object({ noteId: z.string() })
|
|
155
|
-
export type SummarizeInput = z.infer<typeof summarizeInputSchema>
|
|
156
|
-
|
|
157
|
-
export const ai = defineAiActions({
|
|
158
|
-
summarizeNote: defineAiAction('summarizeNote', {
|
|
159
|
-
inputSchema: summarizeInputSchema,
|
|
160
|
-
}),
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
// ── 4. Compose and export ────────────────────────────────────
|
|
164
32
|
const howone = withAiActions(withEntities(client, entities), ai)
|
|
165
33
|
export default howone
|
|
166
34
|
```
|
|
167
35
|
|
|
168
|
-
|
|
36
|
+
`projectId` is required. Missing or invalid Vite variables must fail loudly; do not add empty
|
|
37
|
+
fallbacks such as `?? ''` or silently default the environment to production.
|
|
169
38
|
|
|
170
|
-
|
|
171
|
-
|---|---|
|
|
172
|
-
| `pickEntityPayload(definition, payload)` | Keep only schema-declared business fields before create/update. |
|
|
173
|
-
| `validateEntityPayload(definition, payload)` | Return structured issues for unknown/system/ownership/missing required fields. |
|
|
174
|
-
| `assertEntityPayload(definition, payload)` | Throw structured `EntityPayloadValidationError` before unsafe writes. |
|
|
175
|
-
| `validatePublicEntityQuery(definition, options)` | Check public filters, sorts, scopes, and limits against `access.public`. |
|
|
176
|
-
| `assertPublicEntityQuery(definition, options)` | Throw before generating an invalid public query. |
|
|
177
|
-
| `runAiActionAndPersist(options)` | Standard pending-first AI execution + entity persistence helper. |
|
|
39
|
+
## Environment-owned routing
|
|
178
40
|
|
|
179
|
-
|
|
41
|
+
`env` selects the complete deployment target. The REST API, auth endpoints, uploads, and EAX
|
|
42
|
+
workflow endpoint must use the same environment. EAX URLs are intentionally not configurable per
|
|
43
|
+
request or through `createClient`:
|
|
180
44
|
|
|
181
|
-
|
|
45
|
+
| `env` | REST base | EAX workflow base |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `local` | `http://localhost:3002/api` | `https://eax-backend-orchestrator-dev.fly.dev` |
|
|
48
|
+
| `dev` | `https://api.howone.dev/api` | `https://eax-backend-orchestrator-dev.fly.dev` |
|
|
49
|
+
| `prod` | `https://api.howone.ai/api` | `https://eax-backend-orchestrator-prod.fly.dev` |
|
|
182
50
|
|
|
183
|
-
|
|
51
|
+
Never add `aiUrl`, `aiBaseUrl`, or an EAX URL environment variable. If a special deployment needs a
|
|
52
|
+
different REST API, `apiUrl` is the only supported URL override; it does not change EAX routing.
|
|
184
53
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
54
|
+
The selected values are inspectable without guessing:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
howone.runtime.environment // 'local' | 'dev' | 'prod'
|
|
58
|
+
howone.runtime.apiBaseUrl
|
|
59
|
+
howone.runtime.aiBaseUrl
|
|
60
|
+
howone.runtime.authRoot
|
|
61
|
+
howone.runtime.authCookieRoot
|
|
188
62
|
```
|
|
189
63
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
- **Do not** hardcode project IDs in source. Use the env var.
|
|
193
|
-
- `env` accepts `'local'`, `'dev'`, or `'prod'`. **Auth OTP/OAuth, entities, AI, and uploads all use this same env.**
|
|
194
|
-
- Import `src/lib/sdk.ts` before calling `loginWithEmailCode` / `unifiedAuth` so env is pinned (otherwise auth defaults to prod APIs).
|
|
64
|
+
`howone.runtime` is immutable. A second client created later cannot retarget an existing client's
|
|
65
|
+
requests. Prefer one client per app; if tests create several, assert each client's runtime.
|
|
195
66
|
|
|
196
|
-
|
|
197
|
-
|-------|----------|------------------|
|
|
198
|
-
| `local` | `http://localhost:3002/api` | `http://localhost:3002/api/auth/email/send-code` |
|
|
199
|
-
| `dev` | `https://api.howone.dev/api` | `https://api.howone.dev/api/auth/email/send-code` |
|
|
200
|
-
| `prod` | `https://api.howone.ai/api` | `https://api.howone.ai/api/auth/email/send-code` |
|
|
67
|
+
## Options that matter
|
|
201
68
|
|
|
202
|
-
|
|
69
|
+
```ts
|
|
70
|
+
type CreateClientOptions = {
|
|
71
|
+
projectId?: string
|
|
72
|
+
env?: 'local' | 'dev' | 'prod' | string
|
|
73
|
+
apiUrl?: string // REST-only exceptional override
|
|
74
|
+
apiBaseUrl?: string // deprecated alias for apiUrl
|
|
75
|
+
appId?: string // deprecated alias for projectId
|
|
76
|
+
caseStyle?: 'camel' | 'snake' // default 'camel'
|
|
77
|
+
caseDepth?: 'top-level' | 'deep' // default 'top-level'
|
|
78
|
+
auth?: 'hosted' | 'custom' | 'headless' | 'none' | {
|
|
79
|
+
mode?: 'hosted' | 'custom' | 'headless' | 'none'
|
|
80
|
+
guard?: 'required' | 'optional' | 'none'
|
|
81
|
+
loginPath?: string
|
|
82
|
+
logoutPath?: string
|
|
83
|
+
getToken?: () => Promise<string | null>
|
|
84
|
+
adapter?: AuthAdapter
|
|
85
|
+
tokenCacheMs?: number
|
|
86
|
+
}
|
|
87
|
+
loginPath?: string
|
|
88
|
+
logoutPath?: string
|
|
89
|
+
authRequired?: boolean // deprecated compatibility alias
|
|
90
|
+
limitExceeded?: LimitExceededHandlerOptions
|
|
91
|
+
}
|
|
92
|
+
```
|
|
203
93
|
|
|
204
|
-
|
|
94
|
+
Do not use the removed `mode: 'auto' | 'standalone' | 'embedded'` option. That was not a runtime
|
|
95
|
+
behavior switch. Authentication mode belongs under `auth.mode` and means hosted/custom/headless/none.
|
|
205
96
|
|
|
206
|
-
|
|
97
|
+
## Authentication posture
|
|
207
98
|
|
|
208
99
|
```ts
|
|
209
|
-
//
|
|
100
|
+
// Hosted login (default): Provider inherits a required guard.
|
|
210
101
|
createClient({ projectId, env })
|
|
211
102
|
|
|
212
|
-
//
|
|
103
|
+
// App-owned login page using HowOne auth APIs.
|
|
213
104
|
createClient({ projectId, env, auth: 'custom', loginPath: '/login' })
|
|
214
105
|
|
|
215
|
-
//
|
|
106
|
+
// External identity provider; adapter supplies a token.
|
|
216
107
|
createClient({
|
|
217
108
|
projectId,
|
|
218
109
|
env,
|
|
219
110
|
auth: {
|
|
220
111
|
mode: 'headless',
|
|
221
112
|
adapter: {
|
|
222
|
-
getToken:
|
|
113
|
+
getToken: () => externalAuth.getToken(),
|
|
223
114
|
setToken: (token) => externalAuth.setToken(token),
|
|
224
|
-
login: ({ returnUrl } = {}) => router.push(`/login?redirect=${encodeURIComponent(returnUrl ?? '/')}`),
|
|
225
|
-
logout: () => router.push('/'),
|
|
226
115
|
},
|
|
227
|
-
tokenCacheMs: 60_000,
|
|
228
116
|
},
|
|
229
117
|
})
|
|
230
118
|
|
|
231
|
-
//
|
|
119
|
+
// Deliberately public app.
|
|
232
120
|
createClient({ projectId, env, auth: 'none' })
|
|
233
121
|
```
|
|
234
122
|
|
|
235
|
-
|
|
123
|
+
`HowOneProvider` receives `client={howone}` and derives its default route guard from
|
|
124
|
+
`client.auth.guard`. Override `auth="required|optional|none"` only when the UI intentionally needs
|
|
125
|
+
a different guard. Do not configure auth once on the client and independently guess it on the
|
|
126
|
+
Provider.
|
|
236
127
|
|
|
237
|
-
##
|
|
128
|
+
## Compose manifest bindings
|
|
238
129
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const client = createClient({
|
|
242
|
-
projectId: import.meta.env.VITE_HOWONE_PROJECT_ID,
|
|
243
|
-
env: import.meta.env.VITE_HOWONE_ENV,
|
|
244
|
-
// Override URLs only for special deployments
|
|
245
|
-
// apiUrl: import.meta.env.VITE_HOWONE_API_URL,
|
|
246
|
-
// aiUrl: import.meta.env.VITE_HOWONE_AI_URL,
|
|
247
|
-
})
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
---
|
|
251
|
-
|
|
252
|
-
## UserProfile Type
|
|
130
|
+
Use contract wrappers for generated entities and actions. Public bindings are a separate namespace;
|
|
131
|
+
never call an authenticated entity client from a public page just because the entity name matches.
|
|
253
132
|
|
|
254
133
|
```ts
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
appId?: string
|
|
263
|
-
roles?: string[]
|
|
264
|
-
metadata?: Record<string, unknown>
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Usage
|
|
268
|
-
const me = await client.me() // null if not logged in
|
|
269
|
-
const me = await client.requireMe() // throws HowOneAuthError if not logged in
|
|
270
|
-
|
|
271
|
-
// Check auth state programmatically
|
|
272
|
-
const isLoggedIn = client.auth.isAuthenticated()
|
|
273
|
-
const token = client.auth.getToken()
|
|
274
|
-
|
|
275
|
-
// Manually set a token (e.g. after custom login flow)
|
|
276
|
-
client.auth.setToken(jwtToken)
|
|
277
|
-
|
|
278
|
-
// Trigger login redirect
|
|
279
|
-
client.auth.login('/dashboard') // optional return path
|
|
280
|
-
|
|
281
|
-
// Logout
|
|
282
|
-
client.auth.logout()
|
|
134
|
+
const howone = withAiActions(
|
|
135
|
+
withPublicEntities(
|
|
136
|
+
withEntities(client, entities),
|
|
137
|
+
publicEntities,
|
|
138
|
+
),
|
|
139
|
+
ai,
|
|
140
|
+
)
|
|
283
141
|
```
|
|
284
142
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
## Client Namespace Rules
|
|
288
|
-
|
|
289
|
-
- Use `client.entities.*` / `howone.entities.*` for authenticated app data. The backend derives owner from the JWT; do not pass owner filters or owner fields.
|
|
290
|
-
- Use `client.public.entities.*` / `howone.public.entities.*` for public landing pages, public article lists, scoped QR/profile pages, and public forms.
|
|
291
|
-
- Use `client.schema.*` only for backend contract management: definitions, schema operations, schema patch apply, versions, and restore.
|
|
292
|
-
- Use `client.raw.*` only for custom endpoints not covered by typed SDK methods.
|
|
293
|
-
|
|
294
|
-
---
|
|
295
|
-
|
|
296
|
-
## HowOneAuthError
|
|
143
|
+
The exact generated composition belongs in `01-architect/02-manifest-codegen.md`.
|
|
297
144
|
|
|
298
|
-
|
|
299
|
-
import { HowOneAuthError } from '@howone/sdk'
|
|
300
|
-
|
|
301
|
-
try {
|
|
302
|
-
const user = await client.requireMe()
|
|
303
|
-
} catch (err) {
|
|
304
|
-
if (err instanceof HowOneAuthError) {
|
|
305
|
-
// err.code === 'UNAUTHENTICATED'
|
|
306
|
-
client.auth.login()
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
```
|
|
145
|
+
## Runtime activation checklist
|
|
310
146
|
|
|
311
|
-
|
|
147
|
+
- Keep `src/lib/sdk.ts` imported before rendering `HowOneProvider`.
|
|
148
|
+
- Add a Provider whenever app code uses entities, AI, uploads, auth, session, or raw SDK requests.
|
|
149
|
+
- Use `auth: 'none'` only for a deliberately public app or when the app owns its route guard.
|
|
150
|
+
- Use real token acquisition for authenticated AI/entity/upload calls; do not substitute mock data.
|
|
151
|
+
- Run `node .howone/skills/howone/scripts/verify-project.mjs` before typecheck/build.
|
|
312
152
|
|
|
313
|
-
##
|
|
153
|
+
## Common mistakes
|
|
314
154
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
console.error('Limit exceeded:', context.source, context.message)
|
|
324
|
-
// context.source: 'axios-response' | 'workflow-executor-sse' | ...
|
|
325
|
-
// context.status: HTTP status code (if available)
|
|
326
|
-
},
|
|
327
|
-
},
|
|
328
|
-
})
|
|
329
|
-
```
|
|
155
|
+
| Mistake | Correct pattern |
|
|
156
|
+
|---|---|
|
|
157
|
+
| AI URL in `.env` or `createClient` | Set only `env`; EAX is environment-owned. |
|
|
158
|
+
| `HowOneProvider` without the composed client | `<HowOneProvider client={howone}>`. |
|
|
159
|
+
| Client created in a component | Module-level singleton in `src/lib/sdk.ts`. |
|
|
160
|
+
| Using `apiUrl` to point at EAX | `apiUrl` is REST-only; EAX remains the env mapping. |
|
|
161
|
+
| Passing a JSON Schema object to an AI action | Generate a Zod schema first. |
|
|
162
|
+
| Calling `client.entities.Unknown` after strict codegen | Use `client.entity<T...>('Unknown')` only for an explicit, reviewed escape hatch. |
|