Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/modules/creator/creator-profile.schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ export const CreatorPerkSchema = z.object({
*
* The shape is explicit now so future indexing-backed values can be dropped in
* without changing API contracts.
*
* @see ../creators/creators.serializers.ts — module docblock for null vs absent
* field rules across creator list and detail responses.
*/
export const CreatorProfileReadResponseSchema = z.object({
creatorId: z.string(),
Expand Down
50 changes: 50 additions & 0 deletions src/modules/creators/creators.serializers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,53 @@
/**
* ## Optional fields in public creator responses (null vs absent)
*
* Success bodies are sent with Express `res.json()`. In JSON output:
*
* - **`null`** — the key is present and the value is JSON `null`.
* - **Absent** — the key is omitted when the JavaScript value is `undefined`
* (standard `JSON.stringify` behavior).
*
* Clients should treat **`null` and absent differently** only where documented below;
* elsewhere, assume the key is always present when listed.
*
* ### Creator list (`GET /api/v1/creators`)
*
* Items use {@link CreatorListItem} from `creator-list-item.mapper.ts`
* (`serializeCreatorList` → `mapCreatorListItem`).
*
* | Field | When empty / unknown |
* |-------|----------------------|
* | `id`, `followers` | Always present (`followers` is a number, currently `0`). |
* | `name`, `avatar` | **Always present**; use JSON **`null`** when display name or avatar URL is missing (`?? null` in the mapper). |
*
* List envelope (`items`, `meta`) always includes both keys. Offset `meta` always
* includes `limit`, `offset`, `total`, and `hasMore`.
*
* **Deviations**
*
* - **Cursor list `meta.nextCursor`**: JSON **`null`** when there is no next page
* (`serializeCreatorListCursorMeta`), not omitted.
* - **`CreatorSummary` / `serializeCreatorSummary`**: not used by the active list
* route today; if reused, `avatarUrl` would be **omitted** when `undefined`
* (optional property), unlike `CreatorListItem.avatar` which uses **`null`**.
*
* ### Creator detail (`GET /api/v1/creators/:creatorId/profile`)
*
* Shape: `CreatorProfileReadResponse` in `creator-profile.schemas.ts`,
* built by `getCreatorProfile` in `creator-profile.service.ts`.
*
* | Field | When empty / unknown |
* |-------|----------------------|
* | `creatorId`, `metadata` | Always present. |
* | `displayName`, `bio`, `avatarUrl` | **Always present**; JSON **`null`** when unset (Zod `.nullable()`; placeholder fallback uses explicit `null`). |
* | `links` | **Always present** as an array; use **`[]`** when there are no links (never `null`, never omitted). |
* | `perks` | **Always present** as an array in current handlers (`[]` when none). The read schema marks `perks` optional, but the service always includes the key. |
*
* When adding new optional creator response fields, pick one strategy deliberately:
* nullable keys for “known empty”, omitted keys only when the field is truly
* inapplicable, and empty arrays for “none yet” collections.
*/

import { CreatorProfile } from '../../types/profile.types';
import type { CursorPaginationMeta } from '../../types/cursor.types';
import type { OffsetPaginationMeta } from '../../utils/pagination.utils';
Expand Down
Loading