ts-procedures 5.2.0 → 5.3.0

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.
@@ -0,0 +1,142 @@
1
+ ---
2
+ name: guide
3
+ description: "ts-procedures framework reference — core API, schema validation, error classes, context shape, HTTP implementations, and decision framework. Auto-loaded when ts-procedures imports are detected."
4
+ invocable_by:
5
+ - model
6
+ ---
7
+
8
+ # ts-procedures Framework Reference
9
+
10
+ You are assisting a developer using **ts-procedures**, a TypeScript RPC framework that creates type-safe, schema-validated procedure calls with a single function definition. Always follow these rules when writing or reviewing code.
11
+
12
+ ## Core Flow
13
+
14
+ ```
15
+ Procedures<TContext, TExtendedConfig>(builder?)
16
+
17
+ Create(name, config, handler) → Standard async procedure
18
+ CreateStream(name, config, handler) → Streaming async generator
19
+
20
+ Returns: { [name]: handler, procedure: handler, info: metadata }
21
+ ```
22
+
23
+ ## Factory API
24
+
25
+ ```typescript
26
+ const { Create, CreateStream, getProcedures, getProcedure, removeProcedure, clear } =
27
+ Procedures<TContext, TExtendedConfig>({
28
+ onCreate: (procedure) => { /* called on each registration */ }
29
+ })
30
+ ```
31
+
32
+ | Method | Purpose |
33
+ |--------|---------|
34
+ | `Create(name, config, handler)` | Register standard async procedure |
35
+ | `CreateStream(name, config, handler)` | Register streaming procedure (async generator) |
36
+ | `getProcedures()` | Returns array of all registered procedures |
37
+ | `getProcedure(name)` | Get single procedure by name |
38
+ | `removeProcedure(name)` | Remove procedure by name |
39
+ | `clear()` | Remove all procedures |
40
+
41
+ ## Context
42
+
43
+ Handlers receive `(ctx, params)` where ctx includes:
44
+
45
+ | Property | Type | Availability |
46
+ |----------|------|-------------|
47
+ | Base context fields | `TContext` | Always |
48
+ | `error(message, meta?)` | `(string, object?) => ProcedureError` | Always |
49
+ | `signal` | `AbortSignal` | **Guaranteed** in CreateStream; optional in Create (present when HTTP impl provides it) |
50
+ | `isPrevalidated` | `boolean` | Set by HTTP impls to skip redundant validation |
51
+
52
+ ## Schema System
53
+
54
+ Uses **TypeBox** for schema definitions (`import { Type } from 'typebox'`):
55
+
56
+ | Library | Detection | Example |
57
+ |---------|-----------|---------|
58
+ | **TypeBox** | `~kind` symbol | `Type.Object({ name: Type.String() })` |
59
+
60
+ ### Validation Rules
61
+
62
+ - `schema.params` — **Validated at runtime** via AJV
63
+ - `schema.returnType` — **Documentation only**, never validated
64
+ - `schema.yieldType` — Validated only if `validateYields: true` in CreateStream config
65
+ - AJV config: `allErrors: true`, `coerceTypes: true`, `removeAdditional: true`
66
+
67
+ ## Error Classes
68
+
69
+ | Error Class | Trigger | Key Properties |
70
+ |-------------|---------|----------------|
71
+ | `ProcedureError` | `ctx.error()` in handlers, or unhandled handler exceptions | `procedureName`, `message`, `meta`, `cause`, `definedAt` |
72
+ | `ProcedureValidationError` | Schema params validation failure | `procedureName`, `errors[]` (AJV errors) |
73
+ | `ProcedureYieldValidationError` | Stream yield validation failure | `procedureName`, `errors[]` (AJV errors) |
74
+ | `ProcedureRegistrationError` | Invalid schema at registration time | `procedureName`, `message` |
75
+
76
+ All errors include `definedAt` (file:line:column) and enhanced stack traces pointing to the procedure definition location.
77
+
78
+ ## HTTP Implementations
79
+
80
+ | Builder | Import | Transport |
81
+ |---------|--------|-----------|
82
+ | `ExpressRPCAppBuilder` | `ts-procedures/express-rpc` | POST JSON |
83
+ | `HonoRPCAppBuilder` | `ts-procedures/hono-rpc` | POST JSON |
84
+ | `HonoStreamAppBuilder` | `ts-procedures/hono-stream` | SSE or newline-delimited JSON |
85
+
86
+ ### Route Path Format
87
+
88
+ ```
89
+ {pathPrefix}/{scope}/{kebab-case-name}/{version}
90
+ ```
91
+
92
+ Example: `Create('GetUser', { scope: 'users', version: 1 }, ...)` → `POST /users/get-user/1`
93
+
94
+ ### Builder Pattern
95
+
96
+ ```typescript
97
+ const app = new ExpressRPCAppBuilder({ pathPrefix: '/api' })
98
+ .register(factory, (req) => ({ /* context */ }))
99
+ .build()
100
+ ```
101
+
102
+ ### Lifecycle Hooks (HTTP builders)
103
+
104
+ | Hook | When |
105
+ |------|------|
106
+ | `onRequestStart` | Before context resolution |
107
+ | `onRequestEnd` | After response sent |
108
+ | `onSuccess` | After successful handler execution |
109
+ | `onError` | On handler error (return custom response) |
110
+ | `onStreamStart` | Before first yield (HonoStreamAppBuilder) |
111
+ | `onStreamEnd` | After stream closes (HonoStreamAppBuilder) |
112
+ | `onPreStreamError` | Validation/context error before streaming starts |
113
+ | `onMidStreamError` | Error during streaming (return data to yield as final event) |
114
+
115
+ ## Decision Framework
116
+
117
+ **Which procedure type?**
118
+ - Request → single response → **Create**
119
+ - Request → multiple values over time → **CreateStream**
120
+
121
+ **Which schema library?**
122
+ - Use **TypeBox** (`import { Type } from 'typebox'`)
123
+ - TypeBox schemas are valid JSON Schema directly
124
+ - Use `Type.Optional(...)` for optional fields
125
+
126
+ **Which HTTP implementation?**
127
+ - Express app → **ExpressRPCAppBuilder**
128
+ - Hono app (standard RPC) → **HonoRPCAppBuilder**
129
+ - Hono app (streaming) → **HonoStreamAppBuilder**
130
+ - SSE with browser EventSource → `streamMode: 'sse'`
131
+ - Simple text streaming → `streamMode: 'text'`
132
+
133
+ **How to group procedures?**
134
+ - By domain: `UserProcedures`, `OrderProcedures`
135
+ - By access level: `PublicRPC`, `AuthenticatedRPC`
136
+ - Each group = one `Procedures()` factory call
137
+
138
+ ## Supporting Files
139
+
140
+ For complete API details, see `api-reference.md` in this skill directory.
141
+ For prescribed patterns with code examples, see `patterns.md`.
142
+ For common mistakes to avoid, see `anti-patterns.md`.
@@ -0,0 +1,502 @@
1
+ # ts-procedures Anti-Patterns
2
+
3
+ These are common mistakes when using ts-procedures. Each anti-pattern includes a fix.
4
+
5
+ ---
6
+
7
+ ## 1. Throwing Raw Errors Instead of ctx.error()
8
+
9
+ **Problem:** Throwing plain `Error` loses procedure metadata and makes error handling inconsistent.
10
+
11
+ ```typescript
12
+ // BAD
13
+ async (ctx, params) => {
14
+ if (!params.id) {
15
+ throw new Error('ID is required')
16
+ }
17
+ }
18
+ ```
19
+
20
+ **Fix:** Use `ctx.error()` for business logic errors.
21
+
22
+ ```typescript
23
+ // GOOD
24
+ async (ctx, params) => {
25
+ if (!params.id) {
26
+ throw ctx.error('ID is required', { code: 'MISSING_ID' })
27
+ }
28
+ }
29
+ ```
30
+
31
+ **Why:** `ctx.error()` creates a `ProcedureError` with `procedureName`, `meta`, `definedAt`, and enhanced stack trace. Raw errors get wrapped but lose the `meta` field and intentional error semantics.
32
+
33
+ ---
34
+
35
+ ## 2. Putting Validation Logic in the Handler
36
+
37
+ **Problem:** Manually validating params when schema validation handles it automatically.
38
+
39
+ ```typescript
40
+ // BAD
41
+ Create('GetUser', {}, async (ctx, params) => {
42
+ if (!params.userId || typeof params.userId !== 'string') {
43
+ throw ctx.error('userId is required and must be a string')
44
+ }
45
+ return fetchUser(params.userId)
46
+ })
47
+ ```
48
+
49
+ **Fix:** Use `schema.params` — AJV validates automatically.
50
+
51
+ ```typescript
52
+ // GOOD
53
+ Create('GetUser', {
54
+ schema: {
55
+ params: Type.Object({ userId: Type.String() }),
56
+ },
57
+ }, async (ctx, params) => {
58
+ // params.userId is guaranteed to be a string
59
+ return fetchUser(params.userId)
60
+ })
61
+ ```
62
+
63
+ **Why:** AJV provides detailed error messages, collects all errors (`allErrors: true`), coerces types, and removes additional properties. Manual validation duplicates this and is less thorough.
64
+
65
+ ---
66
+
67
+ ## 3. Expecting returnType to Be Validated at Runtime
68
+
69
+ **Problem:** Assuming `schema.returnType` validates the handler's return value.
70
+
71
+ ```typescript
72
+ // BAD — developer expects runtime validation of return value
73
+ Create('GetUser', {
74
+ schema: {
75
+ params: Type.Object({ id: Type.String() }),
76
+ returnType: Type.Object({ id: Type.String(), name: Type.String() }),
77
+ },
78
+ }, async (ctx, params) => {
79
+ return { id: 123, wrong: 'field' } // No error thrown!
80
+ })
81
+ ```
82
+
83
+ **Fix:** Understand that `returnType` is documentation only. Rely on TypeScript for return type safety.
84
+
85
+ ```typescript
86
+ // GOOD — TypeScript enforces return type via generic inference
87
+ Create('GetUser', {
88
+ schema: {
89
+ params: Type.Object({ id: Type.String() }),
90
+ returnType: Type.Object({ id: Type.String(), name: Type.String() }),
91
+ },
92
+ }, async (ctx, params): Promise<{ id: string; name: string }> => {
93
+ return { id: params.id, name: 'John' } // TypeScript catches mismatches
94
+ })
95
+ ```
96
+
97
+ ---
98
+
99
+ ## 4. Ignoring ctx.signal in Async Operations
100
+
101
+ **Problem:** Not passing `signal` to downstream calls, preventing cancellation on client disconnect.
102
+
103
+ ```typescript
104
+ // BAD
105
+ async (ctx, params) => {
106
+ const data = await fetch('https://api.example.com/heavy-query')
107
+ const processed = await heavyComputation(data)
108
+ return processed
109
+ }
110
+ ```
111
+
112
+ **Fix:** Pass `ctx.signal` to all async calls.
113
+
114
+ ```typescript
115
+ // GOOD
116
+ async (ctx, params) => {
117
+ const data = await fetch('https://api.example.com/heavy-query', {
118
+ signal: ctx.signal,
119
+ })
120
+ const processed = await heavyComputation(data, { signal: ctx.signal })
121
+ return processed
122
+ }
123
+ ```
124
+
125
+ **Why:** When a client disconnects, the HTTP implementation aborts the signal. Without passing it, the handler continues wasting resources on a response nobody will receive.
126
+
127
+ ---
128
+
129
+ ## 5. Not Checking signal.aborted in Stream Loops
130
+
131
+ **Problem:** Stream continues producing values after client disconnects.
132
+
133
+ ```typescript
134
+ // BAD
135
+ async function* (ctx, params) {
136
+ while (true) {
137
+ const data = await pollForData()
138
+ yield data
139
+ await delay(1000)
140
+ }
141
+ }
142
+ ```
143
+
144
+ **Fix:** Check `ctx.signal.aborted` in the loop condition.
145
+
146
+ ```typescript
147
+ // GOOD
148
+ async function* (ctx, params) {
149
+ while (!ctx.signal.aborted) {
150
+ const data = await pollForData({ signal: ctx.signal })
151
+ yield data
152
+ await delay(1000)
153
+ }
154
+ }
155
+ ```
156
+
157
+ ---
158
+
159
+ ## 6. Duplicate Procedure Names
160
+
161
+ **Problem:** Registering two procedures with the same name in the same factory.
162
+
163
+ ```typescript
164
+ // BAD — throws ProcedureRegistrationError
165
+ Create('GetUser', { scope: 'users', version: 1 }, handler1)
166
+ Create('GetUser', { scope: 'admin', version: 1 }, handler2)
167
+ ```
168
+
169
+ **Fix:** Use unique names. Use scope/version to differentiate routes.
170
+
171
+ ```typescript
172
+ // GOOD
173
+ Create('GetUser', { scope: 'users', version: 1 }, handler1)
174
+ Create('GetUserAdmin', { scope: 'admin', version: 1 }, handler2)
175
+
176
+ // Or use separate factories
177
+ const UserRPC = Procedures<UserContext, RPCConfig>()
178
+ const AdminRPC = Procedures<AdminContext, RPCConfig>()
179
+ UserRPC.Create('GetUser', { scope: 'users', version: 1 }, handler1)
180
+ AdminRPC.Create('GetUser', { scope: 'admin', version: 1 }, handler2)
181
+ ```
182
+
183
+ ---
184
+
185
+ ## 7. Using validateYields Without yieldType Schema
186
+
187
+ **Problem:** Setting `validateYields: true` without providing a `yieldType` schema.
188
+
189
+ ```typescript
190
+ // BAD — validateYields has no effect without yieldType schema
191
+ CreateStream('Stream', {
192
+ validateYields: true,
193
+ schema: {
194
+ params: Type.Object({ id: Type.String() }),
195
+ },
196
+ }, async function* (ctx, params) {
197
+ yield { anything: 'goes' }
198
+ })
199
+ ```
200
+
201
+ **Fix:** Provide `yieldType` schema when using `validateYields`.
202
+
203
+ ```typescript
204
+ // GOOD
205
+ CreateStream('Stream', {
206
+ validateYields: true,
207
+ schema: {
208
+ params: Type.Object({ id: Type.String() }),
209
+ yieldType: Type.Object({ id: Type.String(), value: Type.Number() }),
210
+ },
211
+ }, async function* (ctx, params) {
212
+ yield { id: '1', value: 42 }
213
+ })
214
+ ```
215
+
216
+ ---
217
+
218
+ ## 8. Swallowing Errors in Handlers
219
+
220
+ **Problem:** Catching errors without re-throwing, hiding failures from the caller.
221
+
222
+ ```typescript
223
+ // BAD
224
+ async (ctx, params) => {
225
+ try {
226
+ return await riskyOperation(params)
227
+ } catch (err) {
228
+ console.error('Operation failed:', err)
229
+ return null // Caller thinks it succeeded
230
+ }
231
+ }
232
+ ```
233
+
234
+ **Fix:** Let errors propagate. The framework wraps them in `ProcedureError` with enhanced stack traces.
235
+
236
+ ```typescript
237
+ // GOOD — let the framework handle it
238
+ async (ctx, params) => {
239
+ return await riskyOperation(params)
240
+ }
241
+
242
+ // Or rethrow as a business error with context
243
+ async (ctx, params) => {
244
+ try {
245
+ return await riskyOperation(params)
246
+ } catch (err) {
247
+ throw ctx.error('Operation failed', { originalError: err.message, params })
248
+ }
249
+ }
250
+ ```
251
+
252
+ ---
253
+
254
+ ## 9. Not Using AJV's coerceTypes Behavior
255
+
256
+ **Problem:** Treating query string values as strings when AJV will coerce them.
257
+
258
+ ```typescript
259
+ // BAD — unnecessary manual parsing
260
+ async (ctx, params) => {
261
+ const page = parseInt(params.page, 10)
262
+ const limit = parseInt(params.limit, 10)
263
+ return fetchPage(page, limit)
264
+ }
265
+ ```
266
+
267
+ **Fix:** Let AJV coerce types via the schema.
268
+
269
+ ```typescript
270
+ // GOOD — AJV coerces "5" → 5 automatically
271
+ Create('ListUsers', {
272
+ schema: {
273
+ params: Type.Object({
274
+ page: Type.Number(),
275
+ limit: Type.Number(),
276
+ }),
277
+ },
278
+ }, async (ctx, params) => {
279
+ // params.page and params.limit are already numbers
280
+ return fetchPage(params.page, params.limit)
281
+ })
282
+ ```
283
+
284
+ **Why:** AJV is configured with `coerceTypes: true`. String `"5"` from query params becomes number `5` after validation.
285
+
286
+ ---
287
+
288
+ ## 10. Forgetting removeAdditional Strips Extra Fields
289
+
290
+ **Problem:** Passing extra fields in params and expecting them to survive validation.
291
+
292
+ ```typescript
293
+ // BAD — extra fields silently removed
294
+ const result = await GetUser(ctx, {
295
+ userId: '123',
296
+ debug: true, // Stripped by AJV
297
+ extraData: 'value', // Stripped by AJV
298
+ })
299
+ ```
300
+
301
+ **Fix:** Only pass fields defined in the schema. If you need additional fields, add them to the schema.
302
+
303
+ ```typescript
304
+ // GOOD
305
+ Create('GetUser', {
306
+ schema: {
307
+ params: Type.Object({
308
+ userId: Type.String(),
309
+ debug: Type.Optional(Type.Boolean()), // Include in schema if needed
310
+ }),
311
+ },
312
+ }, handler)
313
+ ```
314
+
315
+ **Why:** AJV is configured with `removeAdditional: true`. Any property not defined in the schema is silently removed before the handler receives params.
316
+
317
+ ---
318
+
319
+ ## 11. Creating Factory Without Context Type When Using HTTP Builders
320
+
321
+ **Problem:** Using `Procedures()` without a context type, then registering with an HTTP builder that injects context.
322
+
323
+ ```typescript
324
+ // BAD — handler ctx has no type information
325
+ const { Create } = Procedures()
326
+
327
+ Create('GetUser', {}, async (ctx, params) => {
328
+ ctx.userId // Type error: Property 'userId' does not exist
329
+ })
330
+ ```
331
+
332
+ **Fix:** Always specify the context type.
333
+
334
+ ```typescript
335
+ // GOOD
336
+ const { Create } = Procedures<{ userId: string }>()
337
+
338
+ Create('GetUser', {}, async (ctx, params) => {
339
+ ctx.userId // Typed correctly
340
+ })
341
+ ```
342
+
343
+ ---
344
+
345
+ ## 12. Registering Standard Procedures with HonoStreamAppBuilder
346
+
347
+ **Problem:** Using `Create` procedures with `HonoStreamAppBuilder` — it only handles `CreateStream` procedures.
348
+
349
+ ```typescript
350
+ // BAD — Create procedures are silently ignored
351
+ const { Create, CreateStream } = Procedures<AppContext, RPCConfig>()
352
+ Create('GetUser', { scope: 'users', version: 1 }, handler)
353
+ CreateStream('StreamEvents', { scope: 'events', version: 1 }, streamHandler)
354
+
355
+ new HonoStreamAppBuilder()
356
+ .register(factory, ctx) // Only StreamEvents is registered
357
+ .build()
358
+ ```
359
+
360
+ **Fix:** Use `HonoRPCAppBuilder` or `ExpressRPCAppBuilder` for standard procedures. Use `HonoStreamAppBuilder` only for stream procedures.
361
+
362
+ ```typescript
363
+ // GOOD
364
+ const rpcApp = new HonoRPCAppBuilder().register(RPC, ctx).build()
365
+ const streamApp = new HonoStreamAppBuilder().register(StreamRPC, ctx).build()
366
+ ```
367
+
368
+ ---
369
+
370
+ ## 13. Missing Schema at Registration Time
371
+
372
+ **Problem:** Providing malformed or incompatible schema objects.
373
+
374
+ ```typescript
375
+ // BAD — plain objects are not valid schemas
376
+ Create('GetUser', {
377
+ schema: {
378
+ params: { type: 'object', properties: { id: { type: 'string' } } },
379
+ },
380
+ }, handler)
381
+ ```
382
+
383
+ **Fix:** Use TypeBox schema builders.
384
+
385
+ ```typescript
386
+ // GOOD — TypeBox
387
+ Create('GetUser', {
388
+ schema: { params: Type.Object({ id: Type.String() }) },
389
+ }, handler)
390
+ ```
391
+
392
+ **Why:** ts-procedures detects schema type via TypeBox's `~kind` symbol. Plain JSON Schema objects are not recognized and will throw `ProcedureRegistrationError`.
393
+
394
+ ---
395
+
396
+ ## 14. Not Handling Pre-Stream vs Mid-Stream Errors Differently
397
+
398
+ **Problem:** Using a single error handler for both validation errors (before streaming) and runtime errors (during streaming).
399
+
400
+ ```typescript
401
+ // BAD — onError doesn't apply to streaming
402
+ new HonoStreamAppBuilder({
403
+ onError: (proc, c, err) => { /* This doesn't exist */ },
404
+ })
405
+ ```
406
+
407
+ **Fix:** Use `onPreStreamError` for validation errors and `onMidStreamError` for runtime errors.
408
+
409
+ ```typescript
410
+ // GOOD
411
+ new HonoStreamAppBuilder({
412
+ onPreStreamError: (proc, c, error) => {
413
+ // Validation/context error — return normal HTTP response
414
+ return c.json({ error: error.message }, 400)
415
+ },
416
+ onMidStreamError: (proc, c, error) => {
417
+ // Runtime error during streaming — yield error event, then close
418
+ return { data: { error: error.message }, closeStream: true }
419
+ },
420
+ })
421
+ ```
422
+
423
+ ---
424
+
425
+ ## 15. Not Using extendProcedureDoc for Documentation
426
+
427
+ **Problem:** Manually building documentation from procedure metadata instead of using the built-in extension point.
428
+
429
+ ```typescript
430
+ // BAD — manual doc building, fragile and incomplete
431
+ const docs = getProcedures().map(p => ({
432
+ name: p.name,
433
+ path: `/${p.config.scope}/${p.name}/${p.config.version}`,
434
+ }))
435
+ ```
436
+
437
+ **Fix:** Use `extendProcedureDoc` in the HTTP builder registration.
438
+
439
+ ```typescript
440
+ // GOOD — base doc auto-generated with correct paths and schemas
441
+ builder.register(factory, context, ({ base, procedure }) => ({
442
+ summary: procedure.config.description,
443
+ tags: [base.scope],
444
+ }))
445
+
446
+ // Access via builder.docs
447
+ console.log(builder.docs)
448
+ ```
449
+
450
+ **Why:** The builder generates correct kebab-case paths, includes JSON schemas for body/response, and merges your extensions. Manual building duplicates this logic and easily drifts.
451
+
452
+ ---
453
+
454
+ ## 16. Using Async Context Factory Without Error Handling
455
+
456
+ **Problem:** Async context factories that throw unhandled errors, crashing the request.
457
+
458
+ ```typescript
459
+ // BAD — if authenticate() throws, request crashes
460
+ builder.register(factory, async (req) => {
461
+ const user = await authenticate(req.headers.authorization)
462
+ return { userId: user.id }
463
+ })
464
+ ```
465
+
466
+ **Fix:** Use the builder's `onError` callback to handle context resolution failures gracefully.
467
+
468
+ ```typescript
469
+ // GOOD
470
+ new ExpressRPCAppBuilder({
471
+ onError: (procedure, req, res, error) => {
472
+ if (error.message.includes('Unauthorized')) {
473
+ res.status(401).json({ error: 'Unauthorized' })
474
+ } else {
475
+ res.status(500).json({ error: 'Internal server error' })
476
+ }
477
+ },
478
+ })
479
+ ```
480
+
481
+ ---
482
+
483
+ ## Summary Table
484
+
485
+ | # | Anti-Pattern | Risk | Severity |
486
+ |---|-------------|------|----------|
487
+ | 1 | Raw Error instead of ctx.error() | Lost metadata, inconsistent handling | CRITICAL |
488
+ | 2 | Manual validation in handler | Duplicated logic, less thorough | WARNING |
489
+ | 3 | Expecting returnType validation | Silent data contract violations | CRITICAL |
490
+ | 4 | Ignoring ctx.signal | Resource waste on cancelled requests | WARNING |
491
+ | 5 | No signal check in stream loops | Infinite resource consumption | CRITICAL |
492
+ | 6 | Duplicate procedure names | ProcedureRegistrationError at startup | CRITICAL |
493
+ | 7 | validateYields without yieldType | Silent no-op, false confidence | WARNING |
494
+ | 8 | Swallowing errors | Hidden failures, debugging difficulty | CRITICAL |
495
+ | 9 | Manual type coercion | Unnecessary code, coercion mismatch | SUGGESTION |
496
+ | 10 | Expecting extra fields to survive | Silent data loss | WARNING |
497
+ | 11 | Missing context type | No type safety in handlers | WARNING |
498
+ | 12 | Create with HonoStreamAppBuilder | Procedures silently ignored | CRITICAL |
499
+ | 13 | Plain JSON Schema objects instead of TypeBox | ProcedureRegistrationError | CRITICAL |
500
+ | 14 | Wrong error handler for streams | Unhandled errors or wrong response format | WARNING |
501
+ | 15 | Manual doc building | Fragile, incomplete documentation | SUGGESTION |
502
+ | 16 | Unhandled async context factory | Request crashes | WARNING |