- packages/shared: PlanningView/PlanningItemView, exported.
- apps/api: planning module (service + route), mounted at /planning.
GET /planning/current returns the authenticated user's household's
planning covering today, or null (no error) when there isn't one yet —
the expected state until planning creation exists.
- Tests: Mocha (apps/api/test/planning.test.ts) + Cucumber
(features/planning.feature), same conventions as auth.
- packages/express-tools: fixed AsyncRequestHandler/wrapAsyncHandler's
Locals generic constraint (Record<string, unknown> -> Record<string,
any>, matching Express's own Response<ResBody, LocalsObj>) — the first
endpoint combining requireAuth/AuthLocals with an async handler exposed
that the stricter constraint rejected plain interfaces Response itself
accepts fine.
- Docs: README.md ("Planning" section) + specs/backend-architecture.md.
First commit of the home-page-after-login feature (see plan discussed in
chat) — frontend layout/routing/HomePage follow in subsequent commits on
this same branch/PR.
44 lines
2.2 KiB
TypeScript
44 lines
2.2 KiB
TypeScript
import type { NextFunction, Request, RequestHandler, Response } from "express";
|
|
|
|
/**
|
|
* An Express route handler whose body is `async` (returns a `Promise`).
|
|
* Only `ResBody`/`Locals` are made generic (what this codebase actually
|
|
* varies per-route) — params/request-body/query stay at Express's own
|
|
* internal defaults, same as an unparameterized `Request`.
|
|
*
|
|
* `Locals` is constrained to `Record<string, any>`, matching Express's own
|
|
* `Response<ResBody, LocalsObj>` exactly (see `@types/express-serve-static-
|
|
* core`) rather than the stricter `Record<string, unknown>`: a plain
|
|
* `interface` (e.g. `AuthLocals` in `require-auth.ts`) has no index
|
|
* signature, so under `unknown` it fails this generic's constraint even
|
|
* though it's assignable to `Response`'s own `Locals` param directly —
|
|
* `any` is what lets that structural gap close.
|
|
*/
|
|
export type AsyncRequestHandler<
|
|
ResBody = unknown,
|
|
// biome-ignore lint/suspicious/noExplicitAny: mirrors Express's own Response<ResBody, LocalsObj extends Record<string, any>> constraint (see comment above) — `unknown` here would reject plain interfaces like AuthLocals that Response itself accepts fine.
|
|
Locals extends Record<string, any> = Record<string, any>,
|
|
> = (req: Request, res: Response<ResBody, Locals>, next: NextFunction) => Promise<void>;
|
|
|
|
/**
|
|
* Wraps an async Express handler so a thrown error or rejected promise is
|
|
* forwarded to `next(err)` automatically. Without this, an unhandled
|
|
* rejection inside an `async` route handler never reaches Express's error
|
|
* middleware — every route ends up needing its own `try { ... } catch (err)
|
|
* { next(err); }` boilerplate, which this removes.
|
|
*
|
|
* @example
|
|
* router.post("/signup", wrapAsyncHandler(async (req, res) => {
|
|
* const profile = await signup(req.body);
|
|
* res.status(201).json(profile);
|
|
* }));
|
|
*/
|
|
export function wrapAsyncHandler<
|
|
ResBody = unknown,
|
|
// biome-ignore lint/suspicious/noExplicitAny: same constraint as AsyncRequestHandler above, for the same reason.
|
|
Locals extends Record<string, any> = Record<string, any>,
|
|
>(handler: AsyncRequestHandler<ResBody, Locals>): RequestHandler {
|
|
return (req, res, next) => {
|
|
handler(req, res as Response<ResBody, Locals>, next).catch(next);
|
|
};
|
|
}
|