howone 0.1.10 → 0.1.12

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.
@@ -1,443 +0,0 @@
1
- # HowOne SDK Usage Patterns
2
-
3
- Working reference for `@howone/sdk` and `@howone/sdk/react`. It covers the SDK surface that generated HowOne apps normally need: client setup, entity bindings, AI action bindings, React provider usage, and the known traps that cause repeated source inspection.
4
-
5
- ## Reading Strategy
6
-
7
- Use this file as the working SDK manual for normal app generation. It intentionally includes the public imports, client shape, entity methods, AI action bindings, React exports, app SDK entry shape, and known validation caveats.
8
-
9
- Do not inspect `node_modules/@howone/sdk` for the same information unless:
10
-
11
- - This file does not cover the exact API needed.
12
- - TypeScript or runtime verification contradicts this file.
13
- - You are intentionally updating the SDK package itself, not an app that consumes it.
14
-
15
- When source fallback is necessary, keep it narrow: inspect the exact exported symbol or declaration that is missing, then return to app code. Do not glob or read the whole SDK package to relearn the basic usage below.
16
-
17
- ## SDK Mental Model
18
-
19
- - `createClient(...)` creates the base HowOne client with auth, entity, AI, upload, and user helpers.
20
- - `client.entity<TRecord, TCreate, TUpdate>('EntityName')` creates one typed entity client.
21
- - `defineEntities(...)` plus `withEntities(client, entities)` makes `howone.entities.<EntityName>` typed in app code.
22
- - `defineAiAction(...)` plus `defineAiActions(...)` plus `withAiActions(...)` makes `howone.ai.<actionName>.run|stream|events` typed in app code.
23
- - `@howone/sdk/react` provides auth/theme/loading UI primitives only. It does not provide entity, query, or AI data hooks.
24
- - `.howone/database/manifest.json` and `.howone/ai/manifest.json` are the generated contracts. App source should be generated from those manifests, not from memory.
25
-
26
- ## Import Reference
27
-
28
- ```ts
29
- // @howone/sdk — all public exports
30
- import {
31
- createClient,
32
- defineAiAction,
33
- defineAiActions,
34
- defineEntities,
35
- withAiActions,
36
- withEntities,
37
- type CreateClientOptions,
38
- type EntityRecord,
39
- type EntityClient,
40
- type EntityBindings,
41
- type QueryOptions,
42
- type QueryResult,
43
- type PageInfo,
44
- type DeleteResult,
45
- type AiClient,
46
- type AiActionDefinition,
47
- type AiActionClient,
48
- type AiActionConfig,
49
- type AiResult, // = ExecutionResult
50
- type AiSession, // { result: Promise<AiResult>, cancel(): void, signal: AbortSignal }
51
- type AiEvent, // SSE event payload
52
- type AiOptions,
53
- type UserProfile,
54
- AiSchemaValidationError,
55
- } from '@howone/sdk'
56
-
57
- // @howone/sdk/react — React components and hooks
58
- import {
59
- HowOneProvider,
60
- useHowoneContext,
61
- FloatingButton,
62
- Loading,
63
- LoadingSpinner,
64
- type HowOneProviderProps,
65
- type HowOneAuthMode,
66
- type HowOneThemeMode,
67
- type HowOneBrandMode,
68
- } from '@howone/sdk/react'
69
-
70
- import { z } from 'zod'
71
- ```
72
-
73
- ---
74
-
75
- ## createClient
76
-
77
- ```ts
78
- const client = createClient({
79
- projectId: import.meta.env.VITE_HOWONE_PROJECT_ID, // required in Vite apps
80
- env: import.meta.env.VITE_HOWONE_ENV, // 'local' | 'dev' | 'prod'
81
- // Optional advanced options (rarely needed):
82
- // apiUrl, aiUrl, caseStyle ('camel'|'snake'), auth: { mode, getToken }
83
- })
84
-
85
- // client shape:
86
- client.entity<TRecord, TCreate, TUpdate>(entityName: string): EntityClient<...>
87
- client.entities: Record<string, EntityClient>
88
- client.ai: AiClient // low-level; prefer withAiActions bindings
89
- client.me(): Promise<UserProfile | null>
90
- client.requireMe(): Promise<UserProfile>
91
- client.auth.setToken(t: string | null): void
92
- client.auth.getToken(): string | null
93
- client.auth.isAuthenticated(): boolean
94
- client.auth.login(redirect?: string): void
95
- client.auth.logout(): void
96
- client.upload.file(file, options?): Promise<{ url, thumbnailUrl?, id?, size?, mimeType? }>
97
- client.upload.image(file): Promise<{ url }>
98
- client.upload.batch({ files, concurrent?, onProgress?, onFileComplete? }): Promise<BatchUploadResponse>
99
- ```
100
-
101
- ---
102
-
103
- ## EntityClient
104
-
105
- ```ts
106
- type EntityClient<TRecord, TCreate, TUpdate> = {
107
- name: string
108
- // Basic CRUD
109
- list(options?: { page?, limit?, sort?, order?, ...filters }): Promise<TRecord[]>
110
- get(id: string): Promise<TRecord | null>
111
- getOrThrow(id: string): Promise<TRecord>
112
- create(data: TCreate): Promise<TRecord>
113
- update(id: string, data: TUpdate): Promise<TRecord>
114
- delete(id: string): Promise<{ deleted: boolean; id: string; message? }>
115
- // Advanced query with filters/pagination
116
- query(options?: QueryOptions<TRecord>): Promise<QueryResult<TRecord>>
117
- query.mine(options?: QueryOptions<TRecord>): Promise<QueryResult<TRecord>>
118
- bulkCreate(records: TCreate[], options?: { sample?: boolean }): Promise<TRecord[]>
119
- aggregate(pipeline: unknown[]): Promise<unknown[]>
120
- }
121
-
122
- // QueryOptions
123
- type QueryOptions<TRecord> = {
124
- where?: { [K in keyof TRecord]?: TRecord[K] | FieldOperator }
125
- // FieldOperator: { eq, ne, gt, gte, lt, lte, contains, like, startsWith, endsWith, in, notIn }
126
- search?: string
127
- page?: { number?: number; size?: number }
128
- orderBy?: Partial<Record<keyof TRecord | string, 'asc' | 'desc'>>
129
- }
130
-
131
- // QueryResult
132
- type QueryResult<T> = {
133
- items: T[]
134
- page: { number, size, total, totalPages, hasNext, hasPrev }
135
- }
136
-
137
- // EntityRecord base
138
- type EntityRecord = {
139
- id: string
140
- createdDate?: string
141
- updatedDate?: string
142
- createdById?: string
143
- isSample?: boolean
144
- [key: string]: unknown
145
- }
146
- ```
147
-
148
- ---
149
-
150
- ## AI Action
151
-
152
- ```ts
153
- // Define a typed action
154
- defineAiAction(id: string, config?: {
155
- inputSchema?: z.ZodType<TInput>
156
- outputSchema?: z.ZodType<TOutput>
157
- mode?: 'run' | 'stream' | 'events'
158
- }): AiActionDefinition<TInput, TOutput>
159
-
160
- // Group actions
161
- defineAiActions({ [name]: AiActionDefinition, ... }): actions
162
-
163
- // Bind to client
164
- withAiActions(client, actions): client & { ai: { [name]: AiActionClient } }
165
-
166
- // AiActionClient methods
167
- howone.ai.<actionName>.run(inputs?, options?): Promise<TOutput>
168
- howone.ai.<actionName>.stream(inputs?, options?): AiSession
169
- howone.ai.<actionName>.events(inputs?, options?): AsyncIterable<AiEvent>
170
-
171
- // AiSession
172
- type AiSession = {
173
- result: Promise<AiResult>
174
- cancel(): void
175
- signal: AbortSignal
176
- }
177
-
178
- // Reserved names — do NOT use as action names: run, stream, events
179
- ```
180
-
181
- ---
182
-
183
- ## React: HowOneProvider & hooks
184
-
185
- `@howone/sdk/react` exports exactly: `HowOneProvider`, `useHowoneContext`, `FloatingButton`, `Loading`, `LoadingSpinner`.
186
-
187
- **There are no `useEntity`, `useQuery`, `useAi`, or other data-fetching hooks.** Entity and AI calls are plain async functions — call them directly in components with `useEffect`/`useState` or any data-fetching library (SWR, React Query, etc.).
188
-
189
- ```tsx
190
- // Wrap app root — handles auth, theme, toasts, floating button
191
- <HowOneProvider
192
- projectId={...} // optional if client is already configured
193
- auth="optional" // 'required' | 'optional' | 'none'
194
- brand="visible" // 'visible' | 'hidden'
195
- theme="inherit" // 'dark' | 'light' | 'system' | 'inherit'
196
- showBrandButton={true}
197
- >
198
- <App />
199
- </HowOneProvider>
200
-
201
- // Auth context hook — the only hook in the package
202
- const { user, token, isAuthenticated, logout } = useHowoneContext()
203
- // user shape: { id, email, name, avatar } | null
204
-
205
- // Loading components
206
- <Loading size="md" tone="brand" label="..." description="..." fullScreen />
207
- <LoadingSpinner size="sm" tone="neutral" />
208
- // size: 'sm' | 'md' | 'lg' tone: 'brand' | 'neutral' | 'inverse'
209
- ```
210
-
211
- ## App SDK Entry
212
-
213
- Preferred Vite app entry shape:
214
-
215
- ```ts
216
- import {
217
- createClient,
218
- defineAiAction,
219
- defineAiActions,
220
- defineEntities,
221
- type EntityRecord,
222
- withAiActions,
223
- withEntities,
224
- } from '@howone/sdk'
225
- import { z } from 'zod'
226
-
227
- const client = createClient({
228
- projectId: import.meta.env.VITE_HOWONE_PROJECT_ID,
229
- env: import.meta.env.VITE_HOWONE_ENV,
230
- })
231
-
232
- export const entities = defineEntities({
233
- // Todo: client.entity<Todo, TodoCreate, TodoUpdate>('Todo'),
234
- })
235
-
236
- export const ai = defineAiActions({
237
- // generateImage: defineAiAction('generateImage', {
238
- // inputSchema: generateImageInputSchema,
239
- // }),
240
- })
241
-
242
- const howone = withAiActions(withEntities(client, entities), ai)
243
-
244
- export default howone
245
- ```
246
-
247
- ## Manifest-To-Code Recipe
248
-
249
- When backend metadata has been synced, generate app SDK code in this order:
250
-
251
- 1. Read `src/lib/sdk.ts` to preserve the template's import style and existing bindings.
252
- 2. Read `.howone/database/manifest.json` for entity names and fields. Generate `Record`, `Create`, and `Update` types, then bind each entity with `client.entity<Record, Create, Update>('EntityName')`.
253
- 3. Read `.howone/ai/manifest.json` for AI capability names and JSON schemas. Generate zod input schemas and, only when safe, output schemas.
254
- 4. Export a single default `howone` client built by composing `withEntities` and `withAiActions`.
255
- 5. In UI code, import the default client and call `howone.entities.<Entity>.*` or `howone.ai.<action>.run(...)`.
256
-
257
- Basic JSON schema mapping for generated zod:
258
-
259
- ```ts
260
- string -> z.string()
261
- number -> z.number()
262
- integer -> z.number().int()
263
- boolean -> z.boolean()
264
- array -> z.array(...)
265
- object -> z.object({ ... })
266
- enum -> z.enum([...])
267
- missing from required[] -> .optional()
268
- ```
269
-
270
- ## Entity Types
271
-
272
- Prefer explicit payload types:
273
-
274
- ```ts
275
- export type StoryRecord = EntityRecord & {
276
- title: string
277
- content: string
278
- prompt: string
279
- }
280
-
281
- export type StoryCreate = {
282
- title: string
283
- content: string
284
- prompt: string
285
- }
286
-
287
- export type StoryUpdate = Partial<StoryCreate>
288
-
289
- export const entities = defineEntities({
290
- Story: client.entity<StoryRecord, StoryCreate, StoryUpdate>('Story'),
291
- })
292
- ```
293
-
294
- Generated entity app code usually needs only these calls:
295
-
296
- ```ts
297
- const result = await howone.entities.Story.query({
298
- search,
299
- page: { number: 1, size: 20 },
300
- orderBy: { createdDate: 'desc' },
301
- })
302
-
303
- await howone.entities.Story.create({ title, content, prompt })
304
- await howone.entities.Story.update(id, { title })
305
- await howone.entities.Story.delete(id)
306
- ```
307
-
308
- Prefer `query()` for list UIs that need paging/search/sort metadata. Use `list()` only for simple array reads.
309
-
310
- Avoid this for generated create payloads:
311
-
312
- ```ts
313
- type StoryCreate = Omit<StoryRecord, 'id' | 'createdDate' | 'updatedDate'>
314
- ```
315
-
316
- `EntityRecord` has an index signature, so `Omit` can make generated create types looser than intended.
317
-
318
- ## AI Action Types
319
-
320
- Generate zod schemas from `.howone/ai/manifest.json`:
321
-
322
- ```ts
323
- export const generateStoryInputSchema = z.object({
324
- topic: z.string().min(1),
325
- ageRange: z.enum(['3-5', '6-8', '9-12']),
326
- })
327
-
328
- export const generateStoryOutputSchema = z.object({
329
- title: z.string(),
330
- content: z.string(),
331
- })
332
-
333
- export type GenerateStoryInput = z.infer<typeof generateStoryInputSchema>
334
- export type GenerateStoryOutput = z.infer<typeof generateStoryOutputSchema>
335
-
336
- export const ai = defineAiActions({
337
- generateStory: defineAiAction('generateStory', {
338
- inputSchema: generateStoryInputSchema,
339
- mode: 'run',
340
- }),
341
- })
342
- ```
343
-
344
- Call from app code:
345
-
346
- ```ts
347
- const result = await howone.ai.generateStory.run({
348
- topic,
349
- ageRange,
350
- })
351
- const story = result.finalResult as GenerateStoryOutput
352
- ```
353
-
354
- For streaming:
355
-
356
- ```ts
357
- const session = howone.ai.generateStory.stream(input)
358
- session.cancel()
359
- ```
360
-
361
- For event iteration:
362
-
363
- ```ts
364
- for await (const event of howone.ai.generateStory.events(input)) {
365
- console.log(event)
366
- }
367
- ```
368
-
369
- ## AI Output Validation
370
-
371
- `run()` validates input with `inputSchema`, calls the workflow, then validates the **raw `ExecutionResult`** (the full SSE envelope) with `outputSchema`. It does **not** automatically unwrap `result.finalResult`.
372
-
373
- If you want to type the final result only, omit `outputSchema` and unwrap manually:
374
-
375
- ```ts
376
- const result = await howone.ai.generateStory.run(input) // returns AiResult
377
- const finalResult = result.finalResult as GenerateStoryOutput
378
- ```
379
-
380
- Only define `outputSchema` when it matches the full `ExecutionResult` envelope or after verifying the SDK has changed to validate `finalResult` directly.
381
-
382
- ## AI Result Persistence
383
-
384
- For AI-generated data that should be saved:
385
-
386
- 1. Define AI input/output schemas from `.howone/ai/manifest.json`.
387
- 2. Define entity fields from the AI output schema.
388
- 3. Add only needed request metadata fields, such as prompt, selected options, language, status, or timestamps.
389
- 4. Save with `howone.entities.<Entity>.create(...)`.
390
-
391
- Example:
392
-
393
- ```ts
394
- const story = await howone.ai.generateStory.run(input)
395
- const finalResult = story.finalResult as GenerateStoryOutput
396
-
397
- await howone.entities.Story.create({
398
- title: finalResult.title,
399
- content: finalResult.content,
400
- prompt: input.topic,
401
- })
402
- ```
403
-
404
- ## Do Not Generate
405
-
406
- Avoid these patterns unless the SDK has explicitly added them:
407
-
408
- ```ts
409
- // No data hooks in @howone/sdk/react
410
- const todos = useEntity('Todo')
411
- const query = useQuery(...)
412
- const result = useAi(...)
413
-
414
- // Wrong AI binding shape
415
- await howone.ai.run.generateStory(input)
416
-
417
- // Too-loose create payload
418
- type StoryCreate = Omit<StoryRecord, 'id' | 'createdDate' | 'updatedDate'>
419
-
420
- // Generated files do not belong under .howone
421
- // .howone stores synced manifests only.
422
- ```
423
-
424
- ## When To Read SDK Source
425
-
426
- Read SDK source or declaration files only for details not covered above, for example:
427
-
428
- - A newly added public method not listed here.
429
- - A TypeScript error proving a signature drifted.
430
- - A runtime error that points to a specific SDK helper.
431
- - Work that intentionally modifies `packages/sdk` itself.
432
-
433
- For app generation, do not inspect SDK source to rediscover `createClient`, entity CRUD, AI action binding shape, React exports, or Vite env setup.
434
-
435
- ## Generated Code Checklist
436
-
437
- - `src/lib/sdk.ts` imports only APIs it uses.
438
- - `createClient` uses `import.meta.env.VITE_HOWONE_PROJECT_ID` and `import.meta.env.VITE_HOWONE_ENV`.
439
- - Entity names match `.howone/database/manifest.json`.
440
- - AI action names and schemas match `.howone/ai/manifest.json`.
441
- - zod schemas are strict enough for generated UI inputs and backend contracts.
442
- - UI code calls `howone.ai.<action>.run(input)` and `howone.entities.<Entity>.*`.
443
- - No generated source file is written under `.howone`.