howone 0.1.11 → 0.1.13

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "howone",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "private": false,
5
5
  "description": "HowOne command line tools for creating app templates.",
6
6
  "type": "module",
@@ -1,30 +1,39 @@
1
1
  ---
2
2
  name: howone-sdk
3
- description: Build application code with @howone/sdk correctly. Use when implementing or reviewing HowOne app SDK wiring, src/lib/sdk.ts, dynamic entity bindings, AI action bindings, zod runtime schemas, Vite environment variables, calls like howone.entities.* and howone.ai.*.run, or code generated from .howone/database and .howone/ai manifests.
3
+ description: Build application code with @howone/sdk correctly. Use when implementing or reviewing HowOne app SDK wiring, src/lib/sdk.ts, dynamic entity bindings, AI action bindings, zod runtime schemas, Vite environment variables, calls like howone.entities.* and howone.ai.*.run, file uploads, auth flows, raw HTTP calls, or code generated from .howone/database and .howone/ai manifests.
4
4
  ---
5
5
 
6
- # HowOne SDK
6
+ # HowOne SDK Skill
7
7
 
8
- Use this skill when writing app code that consumes `@howone/sdk`. The goal is stable generated code: read backend-synced manifests, define runtime schemas, bind entities and AI actions in the app SDK entry, then call those bindings from UI code.
8
+ This skill is the authoritative source of truth for all `@howone/sdk` usage in generated apps. Every capability is documented in a dedicated reference file use those files to understand the API. Do not read `node_modules` to rediscover imports or signatures.
9
9
 
10
- This skill is the normal source of truth for HowOne SDK usage in generated apps. Do not inspect `node_modules/@howone/sdk` just to rediscover imports, hook names, entity methods, AI action call shapes, or Vite environment wiring. Use dependency source only for narrow missing details, signature drift proven by verification, or SDK-package development.
10
+ ## Module Reference Map
11
11
 
12
- ## Workflow
12
+ | Module | File | What It Covers |
13
+ |---|---|---|
14
+ | Client Setup | `references/01-client-setup.md` | `createClient`, env vars, `CreateClientOptions`, all client fields |
15
+ | Entity Operations | `references/02-entity-operations.md` | CRUD, `query`, `list`, `aggregate`, `bulkCreate`, typed records |
16
+ | AI Actions | `references/03-ai-actions.md` | `defineAiAction`, `run`/`stream`/`events`, zod schemas, SSE callbacks |
17
+ | Auth | `references/04-auth.md` | Email OTP, Phone OTP, OAuth (Google/GitHub), token management |
18
+ | File Upload | `references/05-file-upload.md` | `upload.file`, `upload.image`, `upload.batch`, progress, abort |
19
+ | React Integration | `references/06-react-integration.md` | `HowOneProvider`, `useHowoneContext`, `FloatingButton`, `Loading` |
20
+ | Raw HTTP | `references/07-raw-http.md` | `client.raw.*`, custom API calls, interceptors |
21
+ | Manifest Codegen | `references/08-manifest-codegen.md` | Reading `.howone/database` and `.howone/ai`, generating `src/lib/sdk.ts` |
13
22
 
14
- 1. Establish the app root. All `.howone/*` metadata and `src/lib/sdk.ts` work belongs inside that app root.
15
- 2. Read `node_modules/@howone/sdk/dist/index.d.ts` and `node_modules/@howone/sdk/dist/react.d.ts` to get the complete, always-up-to-date type signatures and public exports.
16
- 3. Read `references/usage-patterns.md` for generation recipes, usage examples, and known traps that aren't in the type declarations.
17
- 4. Read the existing app SDK entry, usually `src/lib/sdk.ts`, before editing. Preserve its imports and local style.
18
- 5. For Entity bindings, read `.howone/database/manifest.json`.
19
- 6. For AI action bindings, read `.howone/ai/manifest.json`.
20
- 7. Generate source code from the manifests, not from memory or tool-call summaries.
21
- 8. Keep sync tools and app-code edits separate. `sync_schema_artifacts` and `sync_ai_artifacts` write metadata only; the coding agent edits `src/lib/sdk.ts`.
23
+ ## Workflow
22
24
 
23
- ## Quick SDK Contract
25
+ 1. Identify which module(s) the task touches using the map above.
26
+ 2. Read the corresponding reference file(s) — they contain complete, runnable code examples.
27
+ 3. Read the existing `src/lib/sdk.ts` before editing. Preserve its import style and existing bindings.
28
+ 4. For Entity bindings, read `.howone/database/manifest.json`.
29
+ 5. For AI action bindings, read `.howone/ai/manifest.json`.
30
+ 6. Generate code from the manifests, not from memory.
31
+ 7. Keep sync tools (`sync_schema_artifacts`, `sync_ai_artifacts`) separate from app-code edits. Those tools write metadata only; the coding agent edits `src/lib/sdk.ts`.
24
32
 
25
- See `/sdk/dist/index.d.ts` and `/sdk/dist/react.d.ts` for the full type signatures. The minimal app wiring looks like:
33
+ ## Minimal App Wiring (Quick Reference)
26
34
 
27
35
  ```ts
36
+ // src/lib/sdk.ts
28
37
  import {
29
38
  createClient,
30
39
  defineAiAction,
@@ -34,70 +43,51 @@ import {
34
43
  withAiActions,
35
44
  withEntities,
36
45
  } from '@howone/sdk'
37
- import { HowOneProvider, useHowoneContext } from '@howone/sdk/react'
38
46
  import { z } from 'zod'
39
47
 
48
+ // Types
49
+ export type TodoRecord = EntityRecord & {
50
+ title: string
51
+ completed: boolean
52
+ }
53
+ export type TodoCreate = { title: string; completed: boolean }
54
+ export type TodoUpdate = Partial<TodoCreate>
55
+
56
+ // Schemas
57
+ export const summarizeTodoInputSchema = z.object({ title: z.string().min(1) })
58
+ export type SummarizeTodoInput = z.infer<typeof summarizeTodoInputSchema>
59
+
60
+ // Client
40
61
  const client = createClient({
41
62
  projectId: import.meta.env.VITE_HOWONE_PROJECT_ID,
42
63
  env: import.meta.env.VITE_HOWONE_ENV,
43
64
  })
44
65
 
66
+ // Entities
45
67
  export const entities = defineEntities({
46
- Todo: client.entity<Todo, TodoCreate, TodoUpdate>('Todo'),
68
+ Todo: client.entity<TodoRecord, TodoCreate, TodoUpdate>('Todo'),
47
69
  })
48
70
 
71
+ // AI Actions
49
72
  export const ai = defineAiActions({
50
73
  summarizeTodo: defineAiAction('summarizeTodo', {
51
- inputSchema: z.object({ title: z.string() }),
74
+ inputSchema: summarizeTodoInputSchema,
52
75
  }),
53
76
  })
54
77
 
78
+ // Composed client
55
79
  const howone = withAiActions(withEntities(client, entities), ai)
56
80
  export default howone
57
81
  ```
58
82
 
59
- Entity calls are plain async SDK calls:
60
-
61
- ```ts
62
- await howone.entities.Todo.list()
63
- await howone.entities.Todo.create({ title: 'Ship it', completed: false })
64
- await howone.entities.Todo.update(id, { completed: true })
65
- await howone.entities.Todo.delete(id)
66
- ```
67
-
68
- AI action calls are bound by action name:
69
-
70
- ```ts
71
- const result = await howone.ai.summarizeTodo.run({ title })
72
- const session = howone.ai.summarizeTodo.stream({ title })
73
- for await (const event of howone.ai.summarizeTodo.events({ title })) {
74
- // handle event
75
- }
76
- ```
77
-
78
- React integration provides provider/auth/loading UI only: `HowOneProvider`, `useHowoneContext`, `FloatingButton`, `Loading`, and `LoadingSpinner`. It does not provide entity/query/AI data hooks. Use `useEffect`/`useState` or a data-fetching library around plain async SDK calls.
79
-
80
- ## Source Order
81
-
82
- Use this order when deciding what to read:
83
-
84
- 1. `packages/sdk/dist/index.d.ts` and `packages/sdk/dist/react.d.ts` — complete public API.
85
- 2. `references/usage-patterns.md` — generation recipes, examples, traps.
86
- 3. Current app files: `src/lib/sdk.ts`, relevant UI files, and generated manifests.
87
-
88
- ## Rules
89
-
90
- - Use `createClient({ projectId: import.meta.env.VITE_HOWONE_PROJECT_ID, env: import.meta.env.VITE_HOWONE_ENV })` in Vite apps. Do not add `?? 'prod'` or hardcoded project IDs in generated app code.
91
- - Use zod for AI input/output runtime schemas.
92
- - Use `defineAiAction` inside `defineAiActions`.
93
- - Current action binding shape is `howone.ai.<actionName>.run(input)`, `stream(input)`, and `events(input)`. Do not generate `howone.ai.run.<actionName>(input)` unless the SDK implementation has been changed to support that API.
94
- - Do not name AI actions `run`, `stream`, or `events`; those are reserved.
95
- - Define entity create/update types explicitly. Avoid deriving create types from `Omit<EntityRecord & ...>` when it widens the payload.
96
- - React integration does not provide entity/query/AI hooks. Use plain async SDK calls from React state/effects or an app-level data-fetching library.
97
- - Never make generated SDK/type files under `.howone`. `.howone` stores manifests only.
98
- - If an AI-first feature persists generated results, create/update AI capability schema first, sync `.howone/ai`, then derive Entity fields from the AI `outputSchema`.
99
-
100
- ## References
101
-
102
- Read `packages/sdk/dist/index.d.ts` and `packages/sdk/dist/react.d.ts` for the full SDK type surface.
103
- Read `references/usage-patterns.md` for generation recipes, usage examples, and known traps.
83
+ ## Hard Rules
84
+
85
+ - **Vite env only**: use `import.meta.env.VITE_HOWONE_PROJECT_ID` and `import.meta.env.VITE_HOWONE_ENV`. Never hardcode project IDs or add `?? 'prod'` fallbacks.
86
+ - **Explicit types**: define `EntityRecord`, `Create`, and `Update` types explicitly. Never derive create types from `Omit<EntityRecord & ...>` — it widens the payload.
87
+ - **AI action naming**: do not name actions `run`, `stream`, or `events` — those are reserved method names.
88
+ - **Call shape**: `howone.ai.<actionName>.run(input)` / `.stream(input)` / `.events(input)`. Never `howone.ai.run.<actionName>(input)`.
89
+ - **No hooks**: `@howone/sdk/react` provides auth/theme/loading UI only — no entity, query, or AI data hooks.
90
+ - **No codegen under `.howone`**: `.howone/` stores manifests only. Never write generated source files there.
91
+ - **AI-first persistence**: define AI output schema first, sync `.howone/ai`, then derive Entity fields from `outputSchema`.
92
+ - **zod for schemas**: always use zod for AI `inputSchema` / `outputSchema`.
93
+ - **`outputSchema` caution**: `run()` validates the full `ExecutionResult` envelope, not just `finalResult`.
@@ -0,0 +1,278 @@
1
+ # Client Setup
2
+
3
+ ## createClient
4
+
5
+ `createClient(opts: CreateClientOptions)` is the single factory for everything in the HowOne SDK. Call it once at module level and export the result (or the composed `howone` client).
6
+
7
+ ### CreateClientOptions — full type
8
+
9
+ ```ts
10
+ type Environment = 'local' | 'dev' | 'prod'
11
+
12
+ type CreateClientOptions = {
13
+ // ── Required ──────────────────────────────────────────────
14
+ projectId?: string // Your HowOne project ID (set via VITE_HOWONE_PROJECT_ID)
15
+
16
+ // ── Environment ───────────────────────────────────────────
17
+ env?: Environment | string // 'local' | 'dev' | 'prod' (set via VITE_HOWONE_ENV)
18
+ apiUrl?: string // Override the REST API base URL
19
+ aiUrl?: string // Override the AI/SSE base URL
20
+
21
+ // ── Behaviour ─────────────────────────────────────────────
22
+ caseStyle?: 'camel' | 'snake' // Default: 'camel'
23
+ mode?: 'auto' | 'standalone' | 'embedded'
24
+
25
+ // ── Auth ──────────────────────────────────────────────────
26
+ auth?: {
27
+ mode?: 'none' | 'managed' | 'headless'
28
+ getToken?: () => Promise<string | null> // Custom token provider (headless)
29
+ tokenCacheMs?: number // How long to cache the token
30
+ tokenInjection?: {
31
+ allowedOrigins?: string[]
32
+ waitMs?: number
33
+ clearUrlParamsAfterInjectionMs?: number
34
+ clearAllUrlParams?: boolean
35
+ sensitiveParams?: string[]
36
+ }
37
+ }
38
+
39
+ // ── Limit-exceeded callbacks ───────────────────────────────
40
+ limitExceeded?: {
41
+ onLimitExceeded?: (context: LimitExceededContext) => void
42
+ showUpgradeToast?: boolean
43
+ upgradeUrl?: string
44
+ }
45
+
46
+ // ── Deprecated — do not use in new code ───────────────────
47
+ appId?: string // Use projectId
48
+ baseUrl?: string // Use apiUrl / aiUrl
49
+ apiBaseUrl?: string // Use apiUrl
50
+ aiBaseUrl?: string // Use aiUrl
51
+ authRequired?: boolean // Use auth.mode
52
+ }
53
+ ```
54
+
55
+ ### What createClient returns
56
+
57
+ ```ts
58
+ const client = createClient({ ... })
59
+
60
+ client.projectId // string — resolved project ID
61
+ client.appId // string — alias for projectId
62
+ client.caseStyle // 'camel' | 'snake'
63
+
64
+ // Entity factory
65
+ client.entity<TRecord, TCreate, TUpdate>(entityName: string): EntityClient
66
+
67
+ // Typed entity map (populated via withEntities)
68
+ client.entities: Record<string, EntityClient>
69
+
70
+ // AI action runner (low-level)
71
+ client.ai: AiClient
72
+
73
+ // HTTP utilities (low-level — see 07-raw-http.md)
74
+ client.raw: RawHttpClient
75
+
76
+ // File upload
77
+ client.upload.file(file, options?)
78
+ client.upload.image(file)
79
+ client.upload.batch(options)
80
+
81
+ // User profile
82
+ client.me(options?) // Promise<UserProfile | null>
83
+ client.requireMe(options?) // Promise<UserProfile> throws if unauthenticated
84
+ client.session.user() // alias for client.me()
85
+
86
+ // Auth helpers
87
+ client.auth.setToken(token: string | null)
88
+ client.auth.getToken(): string | null
89
+ client.auth.isAuthenticated(): boolean
90
+ client.auth.login(redirect?: string) // redirects to HowOne login page
91
+ client.auth.logout()
92
+
93
+ // URL utilities
94
+ client.sanitizeUrl(opts?: { clearAll?: boolean; sensitiveParams?: string[] })
95
+ ```
96
+
97
+ ---
98
+
99
+ ## Standard Vite Setup
100
+
101
+ ```ts
102
+ // src/lib/sdk.ts
103
+ import {
104
+ createClient,
105
+ defineAiAction,
106
+ defineAiActions,
107
+ defineEntities,
108
+ type EntityRecord,
109
+ withAiActions,
110
+ withEntities,
111
+ } from '@howone/sdk'
112
+ import { z } from 'zod'
113
+
114
+ // ── 1. Create client ─────────────────────────────────────────
115
+ const client = createClient({
116
+ projectId: import.meta.env.VITE_HOWONE_PROJECT_ID,
117
+ env: import.meta.env.VITE_HOWONE_ENV,
118
+ })
119
+
120
+ // ── 2. Define entity types & bind ────────────────────────────
121
+ // (see 02-entity-operations.md for full details)
122
+ export type NoteRecord = EntityRecord & { title: string; body: string }
123
+ export type NoteCreate = { title: string; body: string }
124
+ export type NoteUpdate = Partial<NoteCreate>
125
+
126
+ export const entities = defineEntities({
127
+ Note: client.entity<NoteRecord, NoteCreate, NoteUpdate>('Note'),
128
+ })
129
+
130
+ // ── 3. Define AI actions ─────────────────────────────────────
131
+ // (see 03-ai-actions.md for full details)
132
+ export const summarizeInputSchema = z.object({ noteId: z.string() })
133
+ export type SummarizeInput = z.infer<typeof summarizeInputSchema>
134
+
135
+ export const ai = defineAiActions({
136
+ summarizeNote: defineAiAction('summarizeNote', {
137
+ inputSchema: summarizeInputSchema,
138
+ }),
139
+ })
140
+
141
+ // ── 4. Compose and export ────────────────────────────────────
142
+ const howone = withAiActions(withEntities(client, entities), ai)
143
+ export default howone
144
+ ```
145
+
146
+ ---
147
+
148
+ ## Environment Variables
149
+
150
+ In Vite apps, these two env vars are mandatory:
151
+
152
+ ```
153
+ VITE_HOWONE_PROJECT_ID=proj_xxxxxxxxxxxxxxxx
154
+ VITE_HOWONE_ENV=prod
155
+ ```
156
+
157
+ Rules:
158
+ - **Do not** add `?? 'prod'` or `?? ''` fallbacks. Missing env vars should surface as misconfiguration errors.
159
+ - **Do not** hardcode project IDs in source. Use the env var.
160
+ - `env` accepts `'local'`, `'dev'`, or `'prod'`. SDK routes API calls to the correct endpoint automatically.
161
+
162
+ ---
163
+
164
+ ## Auth Modes
165
+
166
+ ```ts
167
+ // Managed (default) — SDK handles login redirect and token storage
168
+ const client = createClient({
169
+ projectId: import.meta.env.VITE_HOWONE_PROJECT_ID,
170
+ env: import.meta.env.VITE_HOWONE_ENV,
171
+ auth: { mode: 'managed' },
172
+ })
173
+
174
+ // Headless — provide your own token (e.g. Supabase, Clerk, etc.)
175
+ const client = createClient({
176
+ projectId: import.meta.env.VITE_HOWONE_PROJECT_ID,
177
+ env: import.meta.env.VITE_HOWONE_ENV,
178
+ auth: {
179
+ mode: 'headless',
180
+ getToken: async () => {
181
+ // return your JWT or null
182
+ return localStorage.getItem('my_token')
183
+ },
184
+ tokenCacheMs: 60_000, // cache for 1 minute
185
+ },
186
+ })
187
+
188
+ // None — no auth, all requests are unauthenticated
189
+ const client = createClient({
190
+ projectId: import.meta.env.VITE_HOWONE_PROJECT_ID,
191
+ env: import.meta.env.VITE_HOWONE_ENV,
192
+ auth: { mode: 'none' },
193
+ })
194
+ ```
195
+
196
+ ---
197
+
198
+ ## Multi-environment Setup
199
+
200
+ ```ts
201
+ // src/lib/sdk.ts
202
+ const client = createClient({
203
+ projectId: import.meta.env.VITE_HOWONE_PROJECT_ID,
204
+ env: import.meta.env.VITE_HOWONE_ENV,
205
+ // Override URLs only for special deployments
206
+ // apiUrl: import.meta.env.VITE_HOWONE_API_URL,
207
+ // aiUrl: import.meta.env.VITE_HOWONE_AI_URL,
208
+ })
209
+ ```
210
+
211
+ ---
212
+
213
+ ## UserProfile Type
214
+
215
+ ```ts
216
+ type UserProfile = {
217
+ id: string
218
+ email?: string
219
+ name?: string
220
+ avatarUrl?: string
221
+ roles?: string[]
222
+ metadata?: Record<string, unknown>
223
+ }
224
+
225
+ // Usage
226
+ const me = await client.me() // null if not logged in
227
+ const me = await client.requireMe() // throws HowOneAuthError if not logged in
228
+
229
+ // Check auth state programmatically
230
+ const isLoggedIn = client.auth.isAuthenticated()
231
+ const token = client.auth.getToken()
232
+
233
+ // Manually set a token (e.g. after custom login flow)
234
+ client.auth.setToken(jwtToken)
235
+
236
+ // Trigger login redirect
237
+ client.auth.login('/dashboard') // optional return path
238
+
239
+ // Logout
240
+ client.auth.logout()
241
+ ```
242
+
243
+ ---
244
+
245
+ ## HowOneAuthError
246
+
247
+ ```ts
248
+ import { HowOneAuthError } from '@howone/sdk'
249
+
250
+ try {
251
+ const user = await client.requireMe()
252
+ } catch (err) {
253
+ if (err instanceof HowOneAuthError) {
254
+ // err.code === 'UNAUTHENTICATED'
255
+ client.auth.login()
256
+ }
257
+ }
258
+ ```
259
+
260
+ ---
261
+
262
+ ## LimitExceeded Handling
263
+
264
+ ```ts
265
+ const client = createClient({
266
+ projectId: import.meta.env.VITE_HOWONE_PROJECT_ID,
267
+ env: import.meta.env.VITE_HOWONE_ENV,
268
+ limitExceeded: {
269
+ showUpgradeToast: true,
270
+ upgradeUrl: 'https://howone.app/upgrade',
271
+ onLimitExceeded: (context) => {
272
+ console.error('Limit exceeded:', context.source, context.message)
273
+ // context.source: 'axios-response' | 'workflow-executor-sse' | ...
274
+ // context.status: HTTP status code (if available)
275
+ },
276
+ },
277
+ })
278
+ ```