typescript-express-route-body-param-ignored
The bug: POST /api/memories read the namespace only from req.query.namespace, so a namespace sent in the JSON body was silently ignored and every memory landed in the default namespace. Fixed in src/memories.ts with an explicit query || body || default resolution chain:
// src/memories.ts
export const DEFAULT_NAMESPACE = 'default';
function stringOrUndefined(value: unknown): string | undefined {
return typeof value === 'string' && value.trim().length > 0 ? value : undefined;
}
export function resolveNamespace(queryValue: unknown, bodyValue: unknown): string {
return stringOrUndefined(queryValue) ?? stringOrUndefined(bodyValue) ?? DEFAULT_NAMESPACE;
}
router.post('/api/memories', (req: MemoriesRequest, res: Response) => {
const namespace = resolveNamespace(req.query.namespace, req.body?.namespace);
// ...store record under `namespace`
});
Precedence: ?namespace= query → namespace JSON body field → 'default'. Non-string, empty-string, and array (repeated ?namespace=a&namespace=b) values are treated as absent so they fall through safely instead of crashing or mis-storing. Query keeps precedence so existing query-string clients are unaffected.
Optional field on the request interface (Express 5 types declare body as required, hence Omit):
export interface MemoriesRequestBody {
namespace?: string; // optional — query param or default can supply it
content?: string;
tags?: string[];
}
export interface MemoriesRequest extends Omit<Request, 'body'> {
body?: MemoriesRequestBody; // stays optional: express may not parse an empty body
}
E2E regression test on a fresh daemon (test/memories.e2e.test.ts): the suite spawns node dist/src/server.js --port 0 as a real child process (ephemeral port, parsed from the MEMORIES_DAEMON_READY port=N readiness line) and drives it over HTTP — no handler mocking. Docs in README.md: bug description, resolution precedence table, API reference, and run instructions. One clean commit: 4bf7e59 fix(api): honor namespace from JSON body on POST /api/memories.
Verified end-to-end:
- `npm run typecheck` — clean (strict TS).
- `npm test` — fresh `tsc` build + E2E suite against a fresh daemon: **5/5 pass, 0 fail**.
- Live `curl` smoke test against a booted daemon:
- POST body `{"namespace":"work","content":"hello"}` → `201` with `"namespace":"work"` and it lists under `?namespace=work`, absent from `default` (the regression).
- POST `?namespace=query-ns` + body `namespace:"body-ns"` → `"namespace":"query-ns"` (query precedence).
- POST with no namespace → `"namespace":"default"` (fallback).
Edge cases covered by the 5 tests: ① body namespace honored (regression) ② query beats body ③ default fallback ④ empty-string namespace in query/body falls through ⑤ non-string body namespace ignored + missing body returns a clean `400` (no crash).{"model": "deepseek-v4-flash", "problem_class": "typescript-express-route-body-param-ignored", "result": "passed", "tests": 5}