Appearance
EF: trade-planner-save
A worked exemplar of a Type A (authenticated) write edge function.
At a glance
| Name | trade-planner-save |
| Type | Type A (JWT required) |
| Path | supabase/functions/trade-planner-save/ |
| Registered in | EDGE_FN.SAVE in tiers/users/features/edge-builder/config/edgeBuilderConfig.js |
| Tests | supabase/functions/trade-planner-save/tests/ |
Purpose
Server-side gateway for all trade_planner_plans mutations. It is an EF (not an RPC) because it writes and enforces ownership/validation with the service role.
Security model
- JWT verification via
requireAuth()— unauthenticated requests rejected. user_idis always sourced from the verified JWT, never from the client body.- Plan ownership is verified before any UPDATE.
- All incoming fields are validated and sanitised before DB writes.
Trigger
Called from the Trade Planner service via supabase.functions.invoke(EDGE_FN.SAVE, { body }) (wrapped in a TanStack useMutation).
Request
json
{
"operation": "save_plan",
"plan_date": "YYYY-MM-DD",
"plan_type": "daily | weekly | monthly",
"title": "…",
"market_bias": "bullish | bearish | sideways | uncertain"
}operation: 'save_plan' upserts a row (INSERT on first save for a date; UPDATE on subsequent saves for the same date + type). Delete is not supported at any level — plans are permanent; no delete UI is rendered.
Response
ok({ … }) on success; typed errors map to HTTP status via err() (ValidationError → 400, AuthError → 401, ForbiddenError → 403, unhandled → safe 500).
Dependencies
_shared:handleCors,requireAuth,ok/err,logger.- Table:
trade_planner_plans(+ append-onlytrade_plan_updates). See table index. - Complex validation/sanitisation in the function's
helpers.ts.
Related files
- Lifecycle counterpart:
trade-planner-lifecycle-sweep(Type B cron). - Architecture context:
TRADE_PLANNER_ECOSYSTEM_PLAN.md.