founder-schema 0.19.0 → 0.20.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.
- package/README.md +46 -0
- package/dist/access/permissions.d.ts +3 -3
- package/dist/access/permissions.d.ts.map +1 -1
- package/dist/access/permissions.js.map +1 -1
- package/dist/core/common.d.ts +42 -15
- package/dist/core/common.d.ts.map +1 -1
- package/dist/core/common.js +58 -15
- package/dist/core/common.js.map +1 -1
- package/dist/core/deletion.d.ts +40 -0
- package/dist/core/deletion.d.ts.map +1 -0
- package/dist/core/deletion.js +39 -0
- package/dist/core/deletion.js.map +1 -0
- package/dist/domains/audit/audit.d.ts +36 -12
- package/dist/domains/audit/audit.d.ts.map +1 -1
- package/dist/domains/audit/audit.js +9 -10
- package/dist/domains/audit/audit.js.map +1 -1
- package/dist/domains/billing/billing.d.ts +29 -13
- package/dist/domains/billing/billing.d.ts.map +1 -1
- package/dist/domains/billing/billing.js +2 -3
- package/dist/domains/billing/billing.js.map +1 -1
- package/dist/domains/chat/chat.d.ts +19 -5
- package/dist/domains/chat/chat.d.ts.map +1 -1
- package/dist/domains/chat/chat.js +10 -10
- package/dist/domains/chat/chat.js.map +1 -1
- package/dist/domains/communities/communities.d.ts +95 -57
- package/dist/domains/communities/communities.d.ts.map +1 -1
- package/dist/domains/contributions/contributions.d.ts +28 -12
- package/dist/domains/contributions/contributions.d.ts.map +1 -1
- package/dist/domains/contributions/contributions.js +3 -0
- package/dist/domains/contributions/contributions.js.map +1 -1
- package/dist/domains/discover/discover.d.ts +2 -2
- package/dist/domains/feed/feed.d.ts +111 -63
- package/dist/domains/feed/feed.d.ts.map +1 -1
- package/dist/domains/feed/feed.js +3 -0
- package/dist/domains/feed/feed.js.map +1 -1
- package/dist/domains/location/areas.d.ts +7 -0
- package/dist/domains/location/areas.d.ts.map +1 -1
- package/dist/domains/location/areas.js +3 -0
- package/dist/domains/location/areas.js.map +1 -1
- package/dist/domains/media/media.d.ts +22 -0
- package/dist/domains/media/media.d.ts.map +1 -1
- package/dist/domains/media/media.js +20 -0
- package/dist/domains/media/media.js.map +1 -1
- package/dist/domains/members/members.d.ts +26 -9
- package/dist/domains/members/members.d.ts.map +1 -1
- package/dist/domains/members/members.js +9 -10
- package/dist/domains/members/members.js.map +1 -1
- package/dist/domains/membership/membership.d.ts +19 -0
- package/dist/domains/membership/membership.d.ts.map +1 -1
- package/dist/domains/membership/membership.js +10 -0
- package/dist/domains/membership/membership.js.map +1 -1
- package/dist/domains/moderation/moderation.d.ts +8 -2
- package/dist/domains/moderation/moderation.d.ts.map +1 -1
- package/dist/domains/moderation/moderation.js +3 -6
- package/dist/domains/moderation/moderation.js.map +1 -1
- package/dist/domains/notifications/notifications.d.ts +10 -6
- package/dist/domains/notifications/notifications.d.ts.map +1 -1
- package/dist/domains/onboarding/onboarding-answers.js +1 -1
- package/dist/domains/onboarding/onboarding-answers.js.map +1 -1
- package/dist/domains/onboarding/onboarding.d.ts +11 -10
- package/dist/domains/onboarding/onboarding.d.ts.map +1 -1
- package/dist/domains/onboarding/onboarding.js +16 -9
- package/dist/domains/onboarding/onboarding.js.map +1 -1
- package/dist/domains/people/person.d.ts +54 -0
- package/dist/domains/people/person.d.ts.map +1 -0
- package/dist/domains/people/person.js +73 -0
- package/dist/domains/people/person.js.map +1 -0
- package/dist/domains/products/products.d.ts +62 -33
- package/dist/domains/products/products.d.ts.map +1 -1
- package/dist/domains/products/products.js +3 -0
- package/dist/domains/products/products.js.map +1 -1
- package/dist/domains/profile/founder-profile.d.ts +36 -19
- package/dist/domains/profile/founder-profile.d.ts.map +1 -1
- package/dist/domains/profile/founder-profile.js +43 -21
- package/dist/domains/profile/founder-profile.js.map +1 -1
- package/dist/domains/reviews/reviews.d.ts +25 -15
- package/dist/domains/reviews/reviews.d.ts.map +1 -1
- package/dist/domains/support/support.d.ts +47 -10
- package/dist/domains/support/support.d.ts.map +1 -1
- package/dist/domains/support/support.js +4 -2
- package/dist/domains/support/support.js.map +1 -1
- package/dist/domains/team/team.d.ts +9 -3
- package/dist/domains/team/team.d.ts.map +1 -1
- package/dist/domains/team/team.js +3 -6
- package/dist/domains/team/team.js.map +1 -1
- package/dist/index.d.ts +11 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -9
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/dist/account.d.ts +0 -40
- package/dist/account.d.ts.map +0 -1
- package/dist/account.js +0 -39
- package/dist/account.js.map +0 -1
- package/dist/chat.d.ts +0 -187
- package/dist/chat.d.ts.map +0 -1
- package/dist/chat.js +0 -189
- package/dist/chat.js.map +0 -1
- package/dist/common.d.ts +0 -304
- package/dist/common.d.ts.map +0 -1
- package/dist/common.js +0 -340
- package/dist/common.js.map +0 -1
- package/dist/discover.d.ts +0 -245
- package/dist/discover.d.ts.map +0 -1
- package/dist/discover.js +0 -68
- package/dist/discover.js.map +0 -1
- package/dist/errors.d.ts +0 -85
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -89
- package/dist/errors.js.map +0 -1
- package/dist/feed.d.ts +0 -1658
- package/dist/feed.d.ts.map +0 -1
- package/dist/feed.js +0 -623
- package/dist/feed.js.map +0 -1
- package/dist/founder-profile.d.ts +0 -387
- package/dist/founder-profile.d.ts.map +0 -1
- package/dist/founder-profile.js +0 -180
- package/dist/founder-profile.js.map +0 -1
- package/dist/limits.d.ts +0 -76
- package/dist/limits.d.ts.map +0 -1
- package/dist/limits.js +0 -84
- package/dist/limits.js.map +0 -1
- package/dist/onboarding.d.ts +0 -1018
- package/dist/onboarding.d.ts.map +0 -1
- package/dist/onboarding.js +0 -717
- package/dist/onboarding.js.map +0 -1
- package/dist/responses/common.d.ts +0 -167
- package/dist/responses/common.d.ts.map +0 -1
- package/dist/responses/common.js +0 -67
- package/dist/responses/common.js.map +0 -1
- package/dist/responses/index.d.ts +0 -7
- package/dist/responses/index.d.ts.map +0 -1
- package/dist/responses/index.js +0 -7
- package/dist/responses/index.js.map +0 -1
- package/dist/uploads.d.ts +0 -113
- package/dist/uploads.d.ts.map +0 -1
- package/dist/uploads.js +0 -106
- package/dist/uploads.js.map +0 -1
- package/dist/validation.d.ts +0 -136
- package/dist/validation.d.ts.map +0 -1
- package/dist/validation.js +0 -142
- package/dist/validation.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feed.js","sourceRoot":"","sources":["../../../src/domains/feed/feed.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema — Feed wire shapes: post, poll, comment, reaction\n *\n * ADAPTED FROM `jansathi-community-schema`, NOT DEPENDENT ON IT.\n *\n * The user ruled on 2026-09-21 that Founder Circle is a standalone app, so\n * these shapes are copied and cut down rather than imported. The reason is\n * worth keeping: `jansathi-community-schema` encodes Jansathi's reach levels,\n * community designations, official-area kinds and crowdfunding audiences.\n * Depending on it would put all of that permanently inside our contract for a\n * feed that has none of those concepts, and would mean a Jansathi publish can\n * break this product.\n *\n * DELIBERATELY NOT PORTED, so nobody \"restores\" them:\n * - the five civic verticals (lost_found, suggestions_complaints,\n * donate_item, crowdfunding, event). `contentKind` here is post | poll\n * only; comment threads and bookmarks key off it, reactions off the same\n * union plus `comment`, and reports off that plus a product and a review\n * (`reportTargetKindSchema`, 0.6.0).\n * - official communities, designations, identity verification badges.\n * - social score, and the whole area-proximity ranking vocabulary\n * (local / district / state / national / global).\n * - `areaLineage` as a REQUIRED authorship snapshot. Jansathi gates its\n * composer on a declared residence resolving to Indian district level or\n * deeper; the user ruled location is never mandatory here, so nothing in\n * this file requires one.\n * - anonymous posting, which in Jansathi is gated on identity verification\n * we do not have. A founders' network is an identified network.\n *\n * @module founder-schema/domains/feed\n */\n\nimport { z } from 'zod';\nimport {\n eachOnce,\n emptyStringToUndefined,\n objectIdSchema,\n paginationQuerySchema,\n} from '../../core/common.js';\nimport { DAY_MS, MEDIA_ALT_MAX, REPORT_NOTE_MAX } from '../../core/limits.js';\nimport { paginatedSchema } from '../../core/responses.js';\nimport { VALIDATION_MESSAGE } from '../../core/validation.js';\nimport { communityChipSchema } from '../communities/communities.js';\nimport { mediaImageSchema } from '../media/media.js';\nimport { topicSchema } from '../onboarding/onboarding.js';\nimport { productAskSchema, productChipSchema } from '../products/products.js';\nimport { memberBylineSchema } from '../profile/founder-profile.js';\n\n// ─── Limits ──────────────────────────────────────────────────────────────────\n\n/**\n * Exported because the composer must count down to the SAME number the server\n * rejects at. A client-side limit that disagrees with the server is a message\n * a member cannot send and cannot see why.\n */\nexport const POST_MAX_BODY_CHARS = 5000;\n/**\n * Total attachments on one post, images and videos together.\n *\n * ONE cap over ONE ordered list, rather than Jansathi's separate image and\n * video arrays. A member who posts a screenshot, then a clip, then another\n * screenshot means that order, and two parallel arrays cannot express it: the\n * card would show both images and then the video, which is not what was\n * written.\n */\nexport const POST_MAX_MEDIA = 10;\n/**\n * Topics per item.\n *\n * Capped low on purpose: a post tagged with everything is about nothing, and\n * one tagged with ten matches every member, which is how a relevance feature\n * turns back into a chronological one.\n */\nexport const MAX_TOPICS = 3;\nexport const COMMENT_MAX_BODY_CHARS = 2000;\n\nexport const POLL_QUESTION_MAX_CHARS = 280;\nexport const POLL_OPTION_MAX_CHARS = 80;\nexport const POLL_MIN_OPTIONS = 2;\nexport const POLL_MAX_OPTIONS = 6;\n/** 5 minutes. Below this a poll closes before anyone sees it. */\nexport const POLL_MIN_DURATION_MS = 5 * 60 * 1000;\n/** 30 days. */\nexport const POLL_MAX_DURATION_MS = 30 * DAY_MS;\n\n/**\n * How long after writing it the author may still edit a post or a comment.\n *\n * A window rather than forever: an edit after people have reacted changes what\n * they reacted to. Deletion has no window, because a member must always be able\n * to remove their own words. The server is the gate (`EDIT_WINDOW_CLOSED`);\n * a client uses this to stop offering Edit at the moment the server would\n * start refusing it, instead of on the next reload.\n */\nexport const EDIT_WINDOW_MS = 15 * 60 * 1000;\n\n// ─── Enums ───────────────────────────────────────────────────────────────────\n\n/**\n * What can appear in the feed. Two kinds, not seven.\n *\n * Engagement is keyed by `(contentKind, contentId)` so one engagement bar\n * serves both, and comments hang off the same pair. Adding a kind later is\n * additive; the union is deliberately narrow now so the client never has to\n * handle a kind this product does not have.\n */\nexport const CONTENT_KIND_VALUES = ['post', 'poll'] as const;\nexport const contentKindSchema = z.enum(CONTENT_KIND_VALUES);\nexport type ContentKind = z.infer<typeof contentKindSchema>;\n\n/**\n * What a reaction or a report can point at: a feed item, or a comment on one.\n *\n * Wider than `ContentKind` on purpose. A comment carries reaction counts and a\n * viewer reaction, and a comment can be abusive, so both reach it. A bookmark\n * and a comment thread cannot: they take `ContentKind`. Until 0.2.0 the\n * reaction and report RESPONSES were typed `ContentKind` while the routes\n * accepted comments, so every answer about a comment broke this contract.\n */\nexport const ENGAGEMENT_TARGET_KIND_VALUES = [...CONTENT_KIND_VALUES, 'comment'] as const;\nexport const engagementTargetKindSchema = z.enum(ENGAGEMENT_TARGET_KIND_VALUES);\nexport type EngagementTargetKind = z.infer<typeof engagementTargetKindSchema>;\n\n/**\n * What a member can REPORT (0.6.0): everything a reaction can reach, plus a\n * product page and a review. Each has its own report route; the answer and the\n * moderation queue use this one list.\n */\nexport const REPORT_TARGET_KIND_VALUES = [\n ...ENGAGEMENT_TARGET_KIND_VALUES,\n 'product',\n 'review',\n] as const;\nexport const reportTargetKindSchema = z.enum(REPORT_TARGET_KIND_VALUES);\nexport type ReportTargetKind = z.infer<typeof reportTargetKindSchema>;\n\n/**\n * Every reaction a row can hold. The first four were Founder Circle's own;\n * `love`, `support` and `haha` came from Jansathi in 0.8.0 (the user's set,\n * 2026-10-07), so the feed feels like the community app members know.\n *\n * ⚠️ These values are stored per reaction row, so removing one orphans data.\n * Append only. A value that should no longer be OFFERED leaves\n * `REACTION_PICKER_VALUES` instead, and stays here so old rows still read.\n */\nexport const REACTION_TYPE_VALUES = [\n 'like',\n 'celebrate',\n 'insightful',\n 'curious',\n 'love',\n 'support',\n 'haha',\n] as const;\nexport const reactionTypeSchema = z.enum(REACTION_TYPE_VALUES);\nexport type ReactionType = z.infer<typeof reactionTypeSchema>;\n\n/**\n * What the picker offers, IN ORDER (the user's set, 2026-10-07). A member can\n * set only these; `curious` is no longer offered, but a member who chose it\n * keeps it, sees it as their reaction, and can clear it.\n *\n * The order is the schema's, not a client literal: the backend accepts only\n * these on a write (`setReactionBodySchema`), and two lists that must agree\n * belong in one place.\n */\nexport const REACTION_PICKER_VALUES = [\n 'like',\n 'love',\n 'celebrate',\n 'insightful',\n 'support',\n 'haha',\n] as const satisfies readonly ReactionType[];\nexport const pickableReactionSchema = z.enum(REACTION_PICKER_VALUES);\nexport type PickableReaction = z.infer<typeof pickableReactionSchema>;\n\n/**\n * Feed ordering.\n *\n * `new` is the default here, unlike Jansathi where `hot` leads. A hot ranking\n * needs volume to be meaningful, and a brand-new network ranking a handful of\n * posts by a blended score mostly hides the newest thing somebody just wrote.\n * Revisit when there is enough content for `hot` to mean anything.\n */\nexport const FEED_SORT_VALUES = ['new', 'top'] as const;\nexport const feedSortSchema = z.enum(FEED_SORT_VALUES);\nexport type FeedSort = z.infer<typeof feedSortSchema>;\n\n/**\n * Counts keyed by reaction type, e.g. `{ like: 4, celebrate: 1 }`. Only the\n * reactions an item actually has need appear.\n *\n * ⚠️ `partialRecord`, NOT `record`. In Zod 4 an enum-keyed `z.record` is\n * EXHAUSTIVE: it demands every key, so `{ like: 4 }` fails with three missing\n * keys and `{}` fails with four. That would force the server to ship a zero for\n * every reaction on every item, and the day a fifth reaction is added every\n * previously stored payload and every older client stops parsing.\n * `partialRecord` keeps the key validation without the exhaustiveness, so an\n * unknown key like Jansathi's `support` is still refused.\n */\nexport const reactionCountsSchema = z.partialRecord(\n reactionTypeSchema,\n z.number().int().nonnegative(),\n);\nexport type ReactionCounts = z.infer<typeof reactionCountsSchema>;\n\n// ─── Author byline ───────────────────────────────────────────────────────────\n\n/**\n * The author as a card renders them: the shared `memberBylineSchema` (0.6.0),\n * which products, reviews and notifications name members with too.\n *\n * Every field here is LIVE, joined against the profile at read time rather\n * than frozen onto the post at write time. So a renamed handle, a new avatar\n * or an edited headline updates on old posts, and the byline link always\n * targets the current username instead of 404ing on a handle that moved.\n *\n * Jansathi additionally snapshots `localName` (\"where the author was when\n * posting\"). Not ported: location is optional here, so a byline that depends\n * on one would be blank for anyone who never set it.\n */\nexport const feedAuthorSchema = memberBylineSchema;\nexport type FeedAuthor = z.infer<typeof feedAuthorSchema>;\n\n// ─── Media ───────────────────────────────────────────────────────────────────\n\n/**\n * A post's image: a processed `mediaImageSchema` (every stored size, ready for\n * `srcset`, plus a placeholder) with the post's own alt text.\n *\n * `width` and `height` are required so a card can reserve the right box before\n * the image loads. Without them every image arrival shifts the feed under the\n * reader's thumb.\n *\n * `thumbnailUrl` is the smallest stored size, for a grid or a notification.\n *\n * URLs here come from our own upload pipeline, so they take a plain `z.url()`\n * rather than `httpUrlSchema`. ⚠️ If a \"paste an image link\" path is ever\n * added, these become member-supplied and must move to `httpUrlSchema`.\n */\nexport const feedImageSchema = mediaImageSchema.extend({\n kind: z.literal('image'),\n thumbnailUrl: z.url().optional(),\n /** Author-supplied alt text. Optional, and worth asking for. */\n alt: z.string().max(MEDIA_ALT_MAX).optional(),\n});\nexport type FeedImage = z.infer<typeof feedImageSchema>;\n\n/**\n * What a post carries, in the order it was attached. IMAGES ONLY (0.18.0):\n * video is shared as a link in the post text (the user's decision,\n * 2026-09-21), and the uploaded-video shape this used to admit described a\n * server-made poster frame no server makes. `kind` stays, so a stored item\n * says what it is.\n */\nexport const feedMediaSchema = feedImageSchema;\nexport type FeedMedia = z.infer<typeof feedMediaSchema>;\n\n// ─── Viewer state ────────────────────────────────────────────────────────────\n\n/**\n * What THIS viewer has done to this item, computed per request.\n *\n * Optional as a block, because an unauthenticated or server-rendered read has\n * no viewer. ⚠️ A client must treat an absent `viewer` as \"no state known\",\n * never as \"has not reacted\": rendering an empty reaction button off a missing\n * block is how a member sees their own reaction disappear on reload.\n */\nexport const viewerStateSchema = z.object({\n reaction: reactionTypeSchema.nullable(),\n bookmarked: z.boolean(),\n isAuthor: z.boolean(),\n\n /**\n * What this viewer may DO, decided by the server and sent as plain answers.\n *\n * Not role data for the client to reason about. The moment a client derives\n * \"can I edit this\" from roles and ownership, that derivation is a second\n * implementation of the rule, it drifts from the server's, and the drift is\n * invisible until the two disagree: either a button that 403s or a button\n * that should not have been there. `isAuthor` stays because the byline reads\n * differently for your own post, not because the client should compute rights\n * from it.\n *\n * `canEdit` is false once the edit window closes, so it is not simply\n * `isAuthor`.\n */\n canEdit: z.boolean(),\n canDelete: z.boolean(),\n canReport: z.boolean(),\n});\nexport type ViewerState = z.infer<typeof viewerStateSchema>;\n\n/**\n * The viewer's state on a post or a poll: everything above, plus reposting\n * (0.8.0). Comments cannot be reposted, so they keep `viewerStateSchema`.\n */\nexport const contentViewerStateSchema = viewerStateSchema.extend({\n /**\n * The viewer's own ACTIVE repost of this item, or null. An id rather than a\n * boolean because undoing it is deleting that post (`DELETE\n * /api/v1/feed/:contentId`), and a client holding only \"true\" would have to\n * go and find it.\n */\n repostId: objectIdSchema.nullable(),\n /**\n * Whether a repost may be made now, decided by the server: false on your\n * own item, on a repost (repost the original instead), on an item hidden by\n * staff, while you already have one (undo it first), and for a member who\n * cannot write yet (no finished, active profile: `ONBOARDING_INCOMPLETE`).\n */\n canRepost: z.boolean(),\n /**\n * On content inside a community only (0.9.0): whether this viewer may take\n * part in it now (react, comment, vote, save, edit): an active member of a\n * community that is not archived. False for someone who can read it but has\n * not joined (a public community's reader, staff): offer Join instead of\n * the controls. `canEdit` is false too whenever this is.\n *\n * TAKING BACK needs only read access (0.18.0, the server's rule since\n * 0.9.0): someone who left, or a community since archived, may still remove\n * their reaction, their vote or their save, so a client gates only SETTING\n * one on this. Reporting is safety, open to any signed-in reader who did not\n * write it (`canReport`, 0.13.0), joined or not.\n */\n canTakePart: z.boolean().optional(),\n});\nexport type ContentViewerState = z.infer<typeof contentViewerStateSchema>;\n\n// ─── Repost ──────────────────────────────────────────────────────────────────\n\n/**\n * What a repost carries of the item it shares (0.8.0), computed when READ:\n * the byline is live, and an edit to the original shows in every repost of it.\n *\n * `unavailable` once the original is deleted, hidden by staff, or its\n * author's account is gone. The repost stays (it is its author's post, with\n * their words), and says the original is no longer there, rather than\n * vanishing from under the people who commented on it.\n */\nexport const repostedSourceSchema = z.discriminatedUnion('state', [\n z.object({\n state: z.literal('available'),\n contentKind: contentKindSchema,\n id: objectIdSchema,\n author: feedAuthorSchema,\n /** A post's whole text, or a poll's question. */\n text: z.string().max(POST_MAX_BODY_CHARS),\n /** The original's first attachment, for the card's thumbnail. */\n media: feedMediaSchema.optional(),\n /** The product the original is about, live, as on the original. */\n product: productChipSchema.optional(),\n createdAt: z.iso.datetime(),\n }),\n z.object({\n state: z.literal('unavailable'),\n contentKind: contentKindSchema,\n id: objectIdSchema,\n }),\n]);\nexport type RepostedSource = z.infer<typeof repostedSourceSchema>;\n\n// ─── Post ────────────────────────────────────────────────────────────────────\n\n/**\n * A post as the feed and the single-post page receive it.\n *\n * Counts are denormalised onto the row so one document serves a whole card.\n *\n * No `score` and no up/down vote. Jansathi's signed score exists to rank civic\n * content by community judgement, and a downvote arrow on a founder's first\n * post is a different product. Reactions and comments are the engagement axes\n * here. Adding a score later is additive; removing a visible downvote after\n * members have used it is not.\n */\nexport const feedPostSchema = z.object({\n id: objectIdSchema,\n contentKind: z.literal('post'),\n author: feedAuthorSchema,\n\n /**\n * The WHOLE body, never a server-side truncation.\n *\n * So there is no \"was this shortened\" flag and no second fetch to read the\n * rest: the card clamps visually and expands in place. At a 5,000 character\n * cap a page of twenty posts is small enough that trimming would cost a round\n * trip to save nothing. If that ever stops being true, a truncation flag is\n * an additive change; a client that has been guessing is not.\n */\n text: z.string().max(POST_MAX_BODY_CHARS),\n /** Ordered as the author attached them. */\n media: z.array(feedMediaSchema).max(POST_MAX_MEDIA),\n /**\n * What this post is ABOUT, from the same vocabulary a member picks as their\n * interests. This is the whole mechanism behind a relevant feed: matching is\n * an intersection of two lists drawn from one enum, not a guess from who\n * wrote it.\n *\n * ⚠️ An EMPTY array is allowed and means the post is untagged, so interest\n * matching cannot reach it. That is a real consequence, not a nuance: an\n * untagged post is only ever found chronologically.\n */\n topics: z.array(topicSchema).max(MAX_TOPICS),\n\n /**\n * The product this post is about, live-joined (0.6.0). Absent when it names\n * none, and when the product has since been deleted or hidden by staff: the\n * post stays, the chip goes. Every comment on a post with a product is that\n * product's feedback.\n */\n product: productChipSchema.optional(),\n /** What the post asks of the community about its product. Only with `product`. */\n ask: productAskSchema.optional(),\n\n /**\n * The community it was posted in (0.9.0): \"in AI Builders\". Absent on\n * general content. Never alongside `product`, `ask` or `repostOf`:\n * community content carries no product and is never reposted.\n */\n community: communityChipSchema.optional(),\n /** When a moderator pinned it as one of its community's announcements (0.9.0). */\n pinnedAt: z.iso.datetime().optional(),\n\n /**\n * Present only on a REPOST (0.8.0): the item this post shares, with `text`\n * as the reposter's own words above it (empty for a plain repost) and no\n * media of its own. A repost cannot be reposted; its own `repostCount` is\n * always 0.\n */\n repostOf: repostedSourceSchema.optional(),\n\n commentCount: z.number().int().nonnegative(),\n reactionCounts: reactionCountsSchema,\n /** Active reposts of this item (0.8.0); an undone or removed one no longer counts. */\n repostCount: z.number().int().nonnegative(),\n\n createdAt: z.iso.datetime(),\n /** Null when never edited. The edit window is the server's to enforce. */\n editedAt: z.iso.datetime().nullable(),\n\n viewer: contentViewerStateSchema.optional(),\n});\nexport type FeedPost = z.infer<typeof feedPostSchema>;\n\n// ─── Poll ────────────────────────────────────────────────────────────────────\n\n/**\n * One choice. `index` is the identifier a vote refers to, not the label:\n * sending indices avoids ambiguity between two options whose labels differ\n * only by case, spacing or an invisible character.\n */\nexport const pollOptionSchema = z.object({\n index: z\n .number()\n .int()\n .min(0)\n .max(POLL_MAX_OPTIONS - 1),\n label: z.string().min(1).max(POLL_OPTION_MAX_CHARS),\n voteCount: z.number().int().nonnegative(),\n});\nexport type PollOption = z.infer<typeof pollOptionSchema>;\n\n/**\n * A poll. Its own kind, not a post with extra fields, because votes are their\n * own records keyed by (poll, voter) and a post has nothing equivalent.\n *\n * Polls are not editable. Changing a question or an option after a vote has\n * been cast silently re-purposes that vote. A poll closes at its `closesAt`;\n * there is no closing it early.\n */\nexport const feedPollSchema = z.object({\n id: objectIdSchema,\n contentKind: z.literal('poll'),\n author: feedAuthorSchema,\n\n question: z.string().min(1).max(POLL_QUESTION_MAX_CHARS),\n options: z.array(pollOptionSchema).min(POLL_MIN_OPTIONS).max(POLL_MAX_OPTIONS),\n /** Unique voters, not votes: with multi-select the two differ. */\n totalVoters: z.number().int().nonnegative(),\n allowsMultipleChoices: z.boolean(),\n closesAt: z.iso.datetime(),\n /**\n * Whether the poll is closed, decided by the SERVER's clock.\n *\n * Derivable from `closesAt`, and deliberately sent anyway: a device with a\n * wrong clock would otherwise show a live poll as finished, or offer a vote\n * the server then rejects, and the member sees a control that lies. `closesAt`\n * stays for rendering \"closes in 2 days\"; this is what gates the control.\n */\n isClosed: z.boolean(),\n\n /** As on a post. A poll is feed content and is matched the same way. */\n topics: z.array(topicSchema).max(MAX_TOPICS),\n\n /** As on a post: \"which onboarding flow should we ship?\", about a product. */\n product: productChipSchema.optional(),\n ask: productAskSchema.optional(),\n /** As on a post (0.9.0). */\n community: communityChipSchema.optional(),\n pinnedAt: z.iso.datetime().optional(),\n\n commentCount: z.number().int().nonnegative(),\n reactionCounts: reactionCountsSchema,\n /** As on a post (0.8.0). */\n repostCount: z.number().int().nonnegative(),\n\n createdAt: z.iso.datetime(),\n /** Always null. Kept so one byline component renders both kinds. */\n editedAt: z.iso.datetime().nullable(),\n\n viewer: contentViewerStateSchema\n .extend({\n /** Indices this viewer chose. Empty means not voted yet, and it is\n * always populated for the viewer: a voter can always see their own\n * answer even where others' results are hidden. */\n votedOptionIndices: z.array(z.number().int().nonnegative()),\n })\n .optional(),\n});\nexport type FeedPoll = z.infer<typeof feedPollSchema>;\n\n// ─── The feed item union ─────────────────────────────────────────────────────\n\n/**\n * Discriminated on `contentKind`, so a client narrows with a switch and the\n * compiler catches an unhandled kind the day one is added. A plain union would\n * need every field checked by hand and would silently accept a hybrid.\n */\nexport const feedItemSchema = z.discriminatedUnion('contentKind', [feedPostSchema, feedPollSchema]);\nexport type FeedItem = z.infer<typeof feedItemSchema>;\n\n// ─── Comments ────────────────────────────────────────────────────────────────\n\n/**\n * A comment, one level of nesting only: a reply to a comment is allowed, a\n * reply to a reply is not. Two levels stay readable on a phone; three do not,\n * and the server is what enforces it.\n */\nexport const feedCommentSchema = z.object({\n id: objectIdSchema,\n contentKind: contentKindSchema,\n contentId: objectIdSchema,\n /** Null for a top-level comment. A non-null value may only name a top-level\n * comment, never a reply. */\n parentCommentId: objectIdSchema.nullable(),\n author: feedAuthorSchema,\n\n text: z.string().max(COMMENT_MAX_BODY_CHARS),\n\n /** Meaningful only on a top-level comment; always 0 on a reply. */\n replyCount: z.number().int().nonnegative(),\n reactionCounts: reactionCountsSchema,\n\n /**\n * When the product's team marked this comment as feedback that helped\n * (0.6.0), or null. Only a comment on a post tagged with a product, written\n * by someone outside that product's team, can carry it; it earns its writer\n * contribution points.\n */\n helpfulAt: z.iso.datetime().nullable(),\n\n createdAt: z.iso.datetime(),\n editedAt: z.iso.datetime().nullable(),\n\n /** No `bookmarked`: the post is the right thing to save, not a comment. */\n viewer: viewerStateSchema\n .omit({ bookmarked: true })\n .extend({\n /**\n * The control is theirs now (0.12.0). On a comment from outside the\n * team of the post's product: unmarked, marking it is acting for the\n * product (a member on its team, the product open); marked, taking the\n * mark back needs only being on the team, member or not, whatever the\n * product's state.\n */\n canMarkHelpful: z.boolean(),\n })\n .optional(),\n});\nexport type FeedComment = z.infer<typeof feedCommentSchema>;\n\n// ─── Queries ─────────────────────────────────────────────────────────────────\n\n/**\n * `GET /api/v1/feed`. Inherits `limit` and `cursor`, which already tolerate an\n * emptied control rather than answering 400.\n *\n * `interests=true` narrows to the viewer's topics (falling back to the plain\n * feed when they have none). `z.stringbool`, so `true`/`false`/`1`/`0` all\n * read as a person means them; until 0.2.0 the backend read this parameter\n * raw, outside the schema, and only the exact string `true` counted.\n *\n * ⚠ NO `authorId`. It was accepted here and then ignored by the server, so a\n * profile page asking for one founder's posts silently got everybody's. A\n * member's own posts are `GET /api/v1/feed/by-author/:userId`.\n */\nexport const feedQuerySchema = paginationQuerySchema.extend({\n sort: z.preprocess(emptyStringToUndefined, feedSortSchema.default('new')),\n interests: z.preprocess(emptyStringToUndefined, z.stringbool().default(false)),\n});\nexport type FeedQuery = z.infer<typeof feedQuerySchema>;\nexport type FeedQueryInput = z.input<typeof feedQuerySchema>;\n\n/** `GET /api/v1/feed/:contentKind/:contentId/comments`. */\nexport const commentsQuerySchema = paginationQuerySchema.extend({\n /** Omitted lists top-level comments; set, it lists that comment's replies. */\n parentCommentId: z.preprocess(emptyStringToUndefined, objectIdSchema.optional()),\n});\nexport type CommentsQuery = z.infer<typeof commentsQuerySchema>;\nexport type CommentsQueryInput = z.input<typeof commentsQuerySchema>;\n\n/**\n * `GET /api/v1/feed/:contentKind/:contentId/reactions` (0.8.0): who reacted,\n * newest first. `contentKind` is post, poll or comment, as for reacting.\n * `type` narrows to one reaction (a tab); absent lists every reaction (All).\n */\nexport const reactionsQuerySchema = paginationQuerySchema.extend({\n type: z.preprocess(emptyStringToUndefined, reactionTypeSchema.optional()),\n});\nexport type ReactionsQuery = z.infer<typeof reactionsQuerySchema>;\nexport type ReactionsQueryInput = z.input<typeof reactionsQuerySchema>;\n\n// ─── Route parameters ────────────────────────────────────────────────────────\n\n/**\n * Every id and kind in a feed URL, as a schema, so a bad one answers exactly\n * as a bad body field does: 400 `VALIDATION_ERROR`, the issue at\n * `location: 'params'`. Until 0.2.0 these were hand checks that answered\n * \"Invalid id.\" with no issues at all, and the comment routes never looked at\n * `:contentKind`.\n */\n\n/** `GET /feed/:id`, the permalink. */\nexport const feedItemParamsSchema = z.object({ id: objectIdSchema });\n\n/** `PATCH|DELETE /feed/:contentId`. */\nexport const contentParamsSchema = z.object({ contentId: objectIdSchema });\n\n/** A bookmark and a comment thread: a post or a poll. */\nexport const contentTargetParamsSchema = z.object({\n contentKind: contentKindSchema,\n contentId: objectIdSchema,\n});\n\n/** A reaction and a report: a post, a poll or a comment. */\nexport const engagementTargetParamsSchema = z.object({\n contentKind: engagementTargetKindSchema,\n contentId: objectIdSchema,\n});\n\n/** `POST|DELETE /feed/poll/:pollId/vote`. */\nexport const pollParamsSchema = z.object({ pollId: objectIdSchema });\n\n/** `DELETE /feed/comments/:commentId`. */\nexport const commentParamsSchema = z.object({ commentId: objectIdSchema });\n\n/** `GET /feed/by-author/:userId`. */\nexport const authorParamsSchema = z.object({ userId: objectIdSchema });\n\nexport type FeedItemParams = z.infer<typeof feedItemParamsSchema>;\nexport type ContentParams = z.infer<typeof contentParamsSchema>;\nexport type ContentTargetParams = z.infer<typeof contentTargetParamsSchema>;\nexport type EngagementTargetParams = z.infer<typeof engagementTargetParamsSchema>;\nexport type PollParams = z.infer<typeof pollParamsSchema>;\nexport type CommentParams = z.infer<typeof commentParamsSchema>;\nexport type AuthorParams = z.infer<typeof authorParamsSchema>;\n\n// ─── Pages ───────────────────────────────────────────────────────────────────\n\n/**\n * A page of the feed, and of one member's posts (`by-author`), which is the\n * same shape. `nextCursor` is null on the last page.\n */\nexport const feedPageSchema = paginatedSchema(feedItemSchema);\nexport type FeedPage = z.infer<typeof feedPageSchema>;\n\n/**\n * A community's pinned announcements (0.9.0), most recently pinned first, at\n * most `COMMUNITY_PINS_MAX` (domains/communities). Never paged.\n */\nexport const pinnedItemsSchema = z.object({ items: z.array(feedItemSchema) });\nexport type PinnedItems = z.infer<typeof pinnedItemsSchema>;\n\n/** A page of a thread: top-level comments, or one comment's replies. */\nexport const commentPageSchema = paginatedSchema(feedCommentSchema);\nexport type CommentPage = z.infer<typeof commentPageSchema>;\n\n/**\n * One row of \"who reacted\" (0.8.0).\n *\n * `reactedAt` is when they reacted, and the list's order: CHANGING a\n * reaction updates it in place, so the row keeps its position (and moves from\n * one tab to another) instead of jumping to the top under a reader's cursor.\n * Taking it back and reacting again is a new reaction, from then.\n *\n * Only members who can be shown are listed: an erased account's reactions are\n * withdrawn with it, and a paused, suspended or never-onboarded member is\n * left out of the list.\n * `reactionCounts` still counts every reaction, so a tab can list fewer\n * people than its number says. That is deliberate: dropping a suspended\n * member's reaction from the count would make it jump back on reinstatement.\n */\nexport const reactorSchema = z.object({\n member: memberBylineSchema,\n reaction: reactionTypeSchema,\n reactedAt: z.iso.datetime(),\n});\nexport type Reactor = z.infer<typeof reactorSchema>;\nexport const reactorPageSchema = paginatedSchema(reactorSchema);\nexport type ReactorPage = z.infer<typeof reactorPageSchema>;\n\n/**\n * A comment on a product's Feedback tab (`GET /products/:id/feedback`): the\n * comment as a thread shows it, plus whether its writer is already a\n * confirmed tester, so the team's \"Confirm as tester\" knows without paging\n * through `/testers`.\n */\nexport const productFeedbackItemSchema = feedCommentSchema.extend({\n authorIsTester: z.boolean(),\n});\nexport type ProductFeedbackItem = z.infer<typeof productFeedbackItemSchema>;\nexport const productFeedbackPageSchema = paginatedSchema(productFeedbackItemSchema);\nexport type ProductFeedbackPage = z.infer<typeof productFeedbackPageSchema>;\n\n// ─── Write bodies ────────────────────────────────────────────────────────────\n\n/**\n * ⚠️ Every write body below is its own object with NO defaults, and none of\n * them is built with `.pick().partial()`. `.partial()` does not switch off\n * `.default()`, so a patch body built that way rewrites defaulted fields on\n * every request. See `updateFounderProfileBodySchema` for the incident.\n */\n\n/**\n * An attachment on the way IN: the id of an image already uploaded and\n * completed (`media`), and deliberately not a URL.\n *\n * ⚠ The first draft of this took the full read shape, URL included. That is\n * the same hole that was closed on the profile: a URL in a write body is a\n * member-supplied URL, so a post could have carried `javascript:` in an image\n * source, and nothing downstream re-checks a value the server itself stored.\n * Taking an id instead removes the question rather than validating it.\n *\n * `width`, `height` and the URLs are NOT accepted either: the server measured\n * them when it processed the upload, and taking them from the client would let\n * a caller claim a 1x1 image and collapse the card.\n *\n * The server attaches each id only if it is the caller's, processed, uploaded\n * for a post, and not attached anywhere yet (`UPLOAD_NOT_ATTACHABLE`\n * otherwise), so one upload can never appear on two posts.\n */\nexport const attachedMediaSchema = z.object({\n mediaId: objectIdSchema,\n /** Author-supplied alt text, the one media field a member does write. */\n alt: z.string().trim().max(MEDIA_ALT_MAX).optional(),\n});\nexport type AttachedMedia = z.infer<typeof attachedMediaSchema>;\n\n/**\n * Which of the author's products a post or poll is about, and what it asks.\n * Set when it is written and NEVER EDITABLE afterwards: moving a post to\n * another product after people replied would move their feedback with it.\n * The server accepts only a product the author is on the team of\n * (`PRODUCT_NOT_YOURS`) and that is not archived (`PRODUCT_ARCHIVED`).\n */\nconst aboutAProduct = {\n productId: objectIdSchema.optional(),\n /** Only with `productId` (`askWithoutProduct`). */\n ask: productAskSchema.optional(),\n};\nconst askNeedsProduct = (b: { productId?: string; ask?: string }) =>\n b.ask === undefined || b.productId !== undefined;\n\n/**\n * Which community it is posted in (0.9.0), set when written and never moved.\n * The author must be an active member (`COMMUNITY_JOIN_REQUIRED`) and it must\n * not be archived (`COMMUNITY_ARCHIVED`). Community content carries no product\n * and no ask (`communityWithProduct`); the server checks that too.\n */\nconst inACommunity = { communityId: objectIdSchema.optional() };\nconst communityWithoutProduct = (b: { communityId?: string; productId?: string; ask?: string }) =>\n b.communityId === undefined || (b.productId === undefined && b.ask === undefined);\n\n/**\n * A post must carry something: text, media, or (a repost) the item it\n * reposts. ONE rule for writing and editing (0.18.0): an edit may clear the\n * text of a post that keeps its photos or its repost, and only a post that\n * would be left with nothing is refused (`postEmpty`, on `text`). The create\n * body checks it here; an edit can only be checked where the stored post is\n * known, so the server applies this same function there.\n */\nexport function postHasContent(post: {\n text?: string | null;\n mediaCount: number;\n isRepost: boolean;\n}): boolean {\n return Boolean(post.text?.trim().length) || post.mediaCount > 0 || post.isRepost;\n}\n\nexport const createPostBodySchema = z\n .object({\n text: z.string().trim().max(POST_MAX_BODY_CHARS).optional(),\n /** Already uploaded and completed. See `attachedMediaSchema`. Order is preserved. */\n media: z.array(attachedMediaSchema).max(POST_MAX_MEDIA).optional(),\n /** Absent means untagged, which means interest matching cannot find it. */\n topics: z.array(topicSchema).max(MAX_TOPICS).optional(),\n ...aboutAProduct,\n ...inACommunity,\n })\n .refine(\n (b) => postHasContent({ text: b.text, mediaCount: b.media?.length ?? 0, isRepost: false }),\n {\n error: VALIDATION_MESSAGE.postEmpty,\n path: ['text'],\n },\n )\n .refine((b) => eachOnce(b.media?.map((m) => m.mediaId) ?? []), {\n error: VALIDATION_MESSAGE.mediaDuplicate,\n path: ['media'],\n })\n .refine(askNeedsProduct, { error: VALIDATION_MESSAGE.askWithoutProduct, path: ['ask'] })\n .refine(communityWithoutProduct, {\n error: VALIDATION_MESSAGE.communityWithProduct,\n path: ['communityId'],\n });\nexport type CreatePostBody = z.infer<typeof createPostBodySchema>;\n\n/**\n * Only the text is editable. Changing media after the fact makes every\n * earlier reply look like a reply to something else. Empty text is allowed\n * here and refused by the server only when the post would carry nothing\n * (`postHasContent`): a photo's caption can be cleared, a text post's cannot.\n */\nexport const updatePostBodySchema = z.object({\n text: z.string().trim().max(POST_MAX_BODY_CHARS),\n});\nexport type UpdatePostBody = z.infer<typeof updatePostBodySchema>;\n\nexport const createPollBodySchema = z\n .object({\n question: z.string().trim().min(1).max(POLL_QUESTION_MAX_CHARS),\n options: z\n .array(z.object({ label: z.string().trim().min(1).max(POLL_OPTION_MAX_CHARS) }))\n .min(POLL_MIN_OPTIONS)\n .max(POLL_MAX_OPTIONS),\n /**\n * Duration from now, NOT an absolute closing time. A client whose clock is\n * wrong would otherwise open a poll that is already closed, and the member\n * would have no way to tell why.\n */\n durationMs: z.number().int().min(POLL_MIN_DURATION_MS).max(POLL_MAX_DURATION_MS),\n allowsMultipleChoices: z.boolean(),\n topics: z.array(topicSchema).max(MAX_TOPICS).optional(),\n ...aboutAProduct,\n ...inACommunity,\n })\n .refine(askNeedsProduct, { error: VALIDATION_MESSAGE.askWithoutProduct, path: ['ask'] })\n .refine(communityWithoutProduct, {\n error: VALIDATION_MESSAGE.communityWithProduct,\n path: ['communityId'],\n })\n .refine(\n (b) => {\n const seen = new Set<string>();\n for (const o of b.options) {\n const key = o.label.trim().toLowerCase();\n if (seen.has(key)) return false;\n seen.add(key);\n }\n return true;\n },\n { error: VALIDATION_MESSAGE.pollOptionsDuplicate, path: ['options'] },\n );\nexport type CreatePollBody = z.infer<typeof createPollBodySchema>;\n\n/**\n * Cast or change a vote. The server upserts on (poll, voter), so voting twice\n * replaces rather than doubles. Each option once (0.18.0: `[0, 0]` was taken\n * and silently cut to `[0]`).\n */\nexport const castVoteBodySchema = z.object({\n optionIndices: z\n .array(\n z\n .number()\n .int()\n .min(0)\n .max(POLL_MAX_OPTIONS - 1),\n )\n .min(1)\n .max(POLL_MAX_OPTIONS)\n .refine(eachOnce, { error: VALIDATION_MESSAGE.pollVoteDuplicate }),\n});\nexport type CastVoteBody = z.infer<typeof castVoteBodySchema>;\n\nexport const createCommentBodySchema = z.object({\n text: z.string().trim().min(1).max(COMMENT_MAX_BODY_CHARS),\n /** Null for a top-level comment. */\n parentCommentId: objectIdSchema.nullable(),\n});\nexport type CreateCommentBody = z.infer<typeof createCommentBodySchema>;\n\n/**\n * `PATCH /api/v1/feed/comments/:commentId` (route since 0.8.0): your own\n * comment, within `EDIT_WINDOW_MS` (`EDIT_WINDOW_CLOSED` after), answering\n * `IdResponse`. Sets `editedAt`.\n */\nexport const updateCommentBodySchema = z.object({\n text: z.string().trim().min(1).max(COMMENT_MAX_BODY_CHARS),\n});\nexport type UpdateCommentBody = z.infer<typeof updateCommentBodySchema>;\n\n/**\n * `POST /api/v1/feed/reposts` (0.8.0), answering `IdResponse`: the new repost,\n * which is a post of yours carrying `repostOf`. Undo it by deleting that post.\n *\n * One ACTIVE repost per member per item (`ALREADY_REPOSTED`). Refused on your\n * own item (`CANNOT_REPOST_OWN`), on a repost (`CANNOT_REPOST_A_REPOST`: share\n * the original), on anything deleted or hidden (404), and from a member who\n * cannot write yet (`ONBOARDING_INCOMPLETE`). Undo: `DELETE\n * /api/v1/feed/:contentId` on the repost.\n */\nexport const createRepostBodySchema = z.object({\n sourceKind: contentKindSchema,\n sourceId: objectIdSchema,\n /** Your own words above it. Absent or empty: a plain repost. */\n caption: z.string().trim().max(POST_MAX_BODY_CHARS).optional(),\n});\nexport type CreateRepostBody = z.infer<typeof createRepostBodySchema>;\n\n/**\n * Set or clear a reaction. `null` clears it; at most one per viewer per item.\n * Only an offered reaction can be SET (`REACTION_PICKER_VALUES`): a retired\n * one stays readable on old rows but is not chosen anew.\n */\nexport const setReactionBodySchema = z.object({\n reaction: pickableReactionSchema.nullable(),\n});\nexport type SetReactionBody = z.infer<typeof setReactionBodySchema>;\n\n// ─── Engagement responses ────────────────────────────────────────────────────\n\n/**\n * Returned by the reaction endpoint so the card can render the authoritative\n * result without a follow-up read. Without the fresh counts a client has to\n * guess the new total, and two quick taps leave it showing a number the server\n * never had.\n */\nexport const setReactionResponseSchema = z.object({\n contentKind: engagementTargetKindSchema,\n contentId: objectIdSchema,\n reaction: reactionTypeSchema.nullable(),\n reactionCounts: reactionCountsSchema,\n});\nexport type SetReactionResponse = z.infer<typeof setReactionResponseSchema>;\n\n// ─── Reporting ───────────────────────────────────────────────────────────────\n\n/**\n * Where a report stands (0.18.0: named here; the backend's model kept its own\n * list, and the permission rule that only an open report is decided checked a\n * plain string). Staff-only on the wire. Stored: append only.\n */\nexport const REPORT_STATUS_VALUES = ['open', 'actioned', 'dismissed'] as const;\nexport type ReportStatus = (typeof REPORT_STATUS_VALUES)[number];\n\n/**\n * Why a member reported something.\n *\n * ⚠️ Stored on every report row, so this is data, not a label: removing a value\n * orphans reports already filed under it, and the counts nobody can recover are\n * exactly the ones a moderation review needs. Append only.\n *\n * ⚠️ A REASON THAT IS MISSING DOES NOT DISAPPEAR, it lands in `other`. Whatever\n * is not named here becomes an undifferentiated pile that no aggregate can see\n * a pattern in, which is the one thing report categories exist for. Worth being\n * deliberate about the absences rather than discovering them later.\n */\nexport const REPORT_REASON_VALUES = [\n 'spam',\n 'harassment',\n 'misinformation',\n 'off_topic',\n 'impersonation',\n 'other',\n] as const;\nexport const reportReasonSchema = z.enum(REPORT_REASON_VALUES);\nexport type ReportReason = z.infer<typeof reportReasonSchema>;\n\n/**\n * What a member sends to report a post, poll or comment.\n *\n * The target is the URL's, not the body's: a report is filed against a specific\n * thing at a specific address, and taking the target from the body would let a\n * caller report one item while the route thinks it is another.\n *\n * `note` is free text and optional, which is the pressure valve for everything\n * the enum does not name. ⚠️ It is written by a member, so it is untrusted at\n * render: a moderation queue displays it, and a moderation queue is a screen\n * staff read quickly.\n */\nexport const createReportBodySchema = z.object({\n reason: reportReasonSchema,\n note: z.string().trim().max(REPORT_NOTE_MAX).optional(),\n});\nexport type CreateReportBody = z.infer<typeof createReportBodySchema>;\n\n/**\n * The answer to filing one.\n *\n * `alreadyReported` distinguishes \"we have taken this\" from \"you already told\n * us\", because one report per member per target means a second press is a\n * no-op, and a silent no-op reads as a failure to somebody who pressed twice\n * deliberately. It is NOT a count: how many OTHER people reported an item is\n * moderation's business and telling a reporter would turn the count into a\n * score anyone could farm.\n */\nexport const createReportResponseSchema = z.object({\n /** A post, poll or comment; since 0.6.0 also a product or a review. */\n contentKind: reportTargetKindSchema,\n contentId: objectIdSchema,\n alreadyReported: z.boolean(),\n});\nexport type CreateReportResponse = z.infer<typeof createReportResponseSchema>;\n\n/**\n * `PUT|DELETE /api/v1/feed/comments/:commentId/helpful`: the product's team\n * marks (or unmarks) a comment as feedback that helped. Answers the state it\n * settled on. `FEEDBACK_NOT_MARKABLE` when the comment is not feedback on a\n * product the caller is on the team of.\n */\nexport const commentHelpfulResponseSchema = z.object({\n commentId: objectIdSchema,\n helpfulAt: z.iso.datetime().nullable(),\n});\nexport type CommentHelpfulResponse = z.infer<typeof commentHelpfulResponseSchema>;\n\n/** The bookmark endpoint is a toggle and returns the state it settled on. */\nexport const toggleBookmarkResponseSchema = z.object({\n contentKind: contentKindSchema,\n contentId: objectIdSchema,\n bookmarked: z.boolean(),\n});\nexport type ToggleBookmarkResponse = z.infer<typeof toggleBookmarkResponseSchema>;\n\n/** A vote returns the whole poll: counts, totals and the viewer's own choices\n * all move together, and reassembling them client-side drifts. */\nexport const castVoteResponseSchema = z.object({\n poll: feedPollSchema,\n});\nexport type CastVoteResponse = z.infer<typeof castVoteResponseSchema>;\n"]}
|
|
1
|
+
{"version":3,"file":"feed.js","sourceRoot":"","sources":["../../../src/domains/feed/feed.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema — Feed wire shapes: post, poll, comment, reaction\n *\n * ADAPTED FROM `jansathi-community-schema`, NOT DEPENDENT ON IT.\n *\n * The user ruled on 2026-09-21 that Founder Circle is a standalone app, so\n * these shapes are copied and cut down rather than imported. The reason is\n * worth keeping: `jansathi-community-schema` encodes Jansathi's reach levels,\n * community designations, official-area kinds and crowdfunding audiences.\n * Depending on it would put all of that permanently inside our contract for a\n * feed that has none of those concepts, and would mean a Jansathi publish can\n * break this product.\n *\n * DELIBERATELY NOT PORTED, so nobody \"restores\" them:\n * - the five civic verticals (lost_found, suggestions_complaints,\n * donate_item, crowdfunding, event). `contentKind` here is post | poll\n * only; comment threads and bookmarks key off it, reactions off the same\n * union plus `comment`, and reports off that plus a product and a review\n * (`reportTargetKindSchema`, 0.6.0).\n * - official communities, designations, identity verification badges.\n * - social score, and the whole area-proximity ranking vocabulary\n * (local / district / state / national / global).\n * - `areaLineage` as a REQUIRED authorship snapshot. Jansathi gates its\n * composer on a declared residence resolving to Indian district level or\n * deeper; the user ruled location is never mandatory here, so nothing in\n * this file requires one.\n * - anonymous posting, which in Jansathi is gated on identity verification\n * we do not have. A founders' network is an identified network.\n *\n * @module founder-schema/domains/feed\n */\n\nimport { z } from 'zod';\nimport {\n eachOnce,\n emptyStringToUndefined,\n objectIdSchema,\n paginationQuerySchema,\n} from '../../core/common.js';\nimport { DAY_MS, MEDIA_ALT_MAX, REPORT_NOTE_MAX } from '../../core/limits.js';\nimport { paginatedSchema } from '../../core/responses.js';\nimport { VALIDATION_MESSAGE } from '../../core/validation.js';\nimport { communityChipSchema } from '../communities/communities.js';\nimport { mediaImageSchema } from '../media/media.js';\nimport { topicSchema } from '../onboarding/onboarding.js';\nimport { productAskSchema, productChipSchema } from '../products/products.js';\nimport { memberBylineSchema } from '../profile/founder-profile.js';\n\n// ─── Limits ──────────────────────────────────────────────────────────────────\n\n/**\n * Exported because the composer must count down to the SAME number the server\n * rejects at. A client-side limit that disagrees with the server is a message\n * a member cannot send and cannot see why.\n */\nexport const POST_MAX_BODY_CHARS = 5000;\n/**\n * Total attachments on one post, images and videos together.\n *\n * ONE cap over ONE ordered list, rather than Jansathi's separate image and\n * video arrays. A member who posts a screenshot, then a clip, then another\n * screenshot means that order, and two parallel arrays cannot express it: the\n * card would show both images and then the video, which is not what was\n * written.\n */\nexport const POST_MAX_MEDIA = 10;\n/**\n * Topics per item.\n *\n * Capped low on purpose: a post tagged with everything is about nothing, and\n * one tagged with ten matches every member, which is how a relevance feature\n * turns back into a chronological one.\n */\nexport const MAX_TOPICS = 3;\nexport const COMMENT_MAX_BODY_CHARS = 2000;\n\nexport const POLL_QUESTION_MAX_CHARS = 280;\nexport const POLL_OPTION_MAX_CHARS = 80;\nexport const POLL_MIN_OPTIONS = 2;\nexport const POLL_MAX_OPTIONS = 6;\n/** 5 minutes. Below this a poll closes before anyone sees it. */\nexport const POLL_MIN_DURATION_MS = 5 * 60 * 1000;\n/** 30 days. */\nexport const POLL_MAX_DURATION_MS = 30 * DAY_MS;\n\n/**\n * How long after writing it the author may still edit a post or a comment.\n *\n * A window rather than forever: an edit after people have reacted changes what\n * they reacted to. Deletion has no window, because a member must always be able\n * to remove their own words. The server is the gate (`EDIT_WINDOW_CLOSED`);\n * a client uses this to stop offering Edit at the moment the server would\n * start refusing it, instead of on the next reload.\n */\nexport const EDIT_WINDOW_MS = 15 * 60 * 1000;\n\n// ─── Enums ───────────────────────────────────────────────────────────────────\n\n/**\n * What can appear in the feed. Two kinds, not seven.\n *\n * Engagement is keyed by `(contentKind, contentId)` so one engagement bar\n * serves both, and comments hang off the same pair. Adding a kind later is\n * additive; the union is deliberately narrow now so the client never has to\n * handle a kind this product does not have.\n */\nexport const CONTENT_KIND_VALUES = ['post', 'poll'] as const;\nexport const contentKindSchema = z.enum(CONTENT_KIND_VALUES);\nexport type ContentKind = z.infer<typeof contentKindSchema>;\n\n/** What a post's media item is: images only today. The backend's storage's own values, from here like every other list (0.20.0). */\nexport const CONTENT_MEDIA_KIND_VALUES = ['image'] as const;\nexport const contentMediaKindSchema = z.enum(CONTENT_MEDIA_KIND_VALUES);\nexport type ContentMediaKind = z.infer<typeof contentMediaKindSchema>;\n\n/**\n * What a reaction or a report can point at: a feed item, or a comment on one.\n *\n * Wider than `ContentKind` on purpose. A comment carries reaction counts and a\n * viewer reaction, and a comment can be abusive, so both reach it. A bookmark\n * and a comment thread cannot: they take `ContentKind`. Until 0.2.0 the\n * reaction and report RESPONSES were typed `ContentKind` while the routes\n * accepted comments, so every answer about a comment broke this contract.\n */\nexport const ENGAGEMENT_TARGET_KIND_VALUES = [...CONTENT_KIND_VALUES, 'comment'] as const;\nexport const engagementTargetKindSchema = z.enum(ENGAGEMENT_TARGET_KIND_VALUES);\nexport type EngagementTargetKind = z.infer<typeof engagementTargetKindSchema>;\n\n/**\n * What a member can REPORT (0.6.0): everything a reaction can reach, plus a\n * product page and a review. Each has its own report route; the answer and the\n * moderation queue use this one list.\n */\nexport const REPORT_TARGET_KIND_VALUES = [\n ...ENGAGEMENT_TARGET_KIND_VALUES,\n 'product',\n 'review',\n] as const;\nexport const reportTargetKindSchema = z.enum(REPORT_TARGET_KIND_VALUES);\nexport type ReportTargetKind = z.infer<typeof reportTargetKindSchema>;\n\n/**\n * Every reaction a row can hold. The first four were Founder Circle's own;\n * `love`, `support` and `haha` came from Jansathi in 0.8.0 (the user's set,\n * 2026-10-07), so the feed feels like the community app members know.\n *\n * ⚠️ These values are stored per reaction row, so removing one orphans data.\n * Append only. A value that should no longer be OFFERED leaves\n * `REACTION_PICKER_VALUES` instead, and stays here so old rows still read.\n */\nexport const REACTION_TYPE_VALUES = [\n 'like',\n 'celebrate',\n 'insightful',\n 'curious',\n 'love',\n 'support',\n 'haha',\n] as const;\nexport const reactionTypeSchema = z.enum(REACTION_TYPE_VALUES);\nexport type ReactionType = z.infer<typeof reactionTypeSchema>;\n\n/**\n * What the picker offers, IN ORDER (the user's set, 2026-10-07). A member can\n * set only these; `curious` is no longer offered, but a member who chose it\n * keeps it, sees it as their reaction, and can clear it.\n *\n * The order is the schema's, not a client literal: the backend accepts only\n * these on a write (`setReactionBodySchema`), and two lists that must agree\n * belong in one place.\n */\nexport const REACTION_PICKER_VALUES = [\n 'like',\n 'love',\n 'celebrate',\n 'insightful',\n 'support',\n 'haha',\n] as const satisfies readonly ReactionType[];\nexport const pickableReactionSchema = z.enum(REACTION_PICKER_VALUES);\nexport type PickableReaction = z.infer<typeof pickableReactionSchema>;\n\n/**\n * Feed ordering.\n *\n * `new` is the default here, unlike Jansathi where `hot` leads. A hot ranking\n * needs volume to be meaningful, and a brand-new network ranking a handful of\n * posts by a blended score mostly hides the newest thing somebody just wrote.\n * Revisit when there is enough content for `hot` to mean anything.\n */\nexport const FEED_SORT_VALUES = ['new', 'top'] as const;\nexport const feedSortSchema = z.enum(FEED_SORT_VALUES);\nexport type FeedSort = z.infer<typeof feedSortSchema>;\n\n/**\n * Counts keyed by reaction type, e.g. `{ like: 4, celebrate: 1 }`. Only the\n * reactions an item actually has need appear.\n *\n * ⚠️ `partialRecord`, NOT `record`. In Zod 4 an enum-keyed `z.record` is\n * EXHAUSTIVE: it demands every key, so `{ like: 4 }` fails with three missing\n * keys and `{}` fails with four. That would force the server to ship a zero for\n * every reaction on every item, and the day a fifth reaction is added every\n * previously stored payload and every older client stops parsing.\n * `partialRecord` keeps the key validation without the exhaustiveness, so an\n * unknown key like Jansathi's `support` is still refused.\n */\nexport const reactionCountsSchema = z.partialRecord(\n reactionTypeSchema,\n z.number().int().nonnegative(),\n);\nexport type ReactionCounts = z.infer<typeof reactionCountsSchema>;\n\n// ─── Author byline ───────────────────────────────────────────────────────────\n\n/**\n * The author as a card renders them: the shared `memberBylineSchema` (0.6.0),\n * which products, reviews and notifications name members with too.\n *\n * Every field here is LIVE, joined against the profile at read time rather\n * than frozen onto the post at write time. So a renamed handle, a new avatar\n * or an edited headline updates on old posts, and the byline link always\n * targets the current username instead of 404ing on a handle that moved.\n *\n * Jansathi additionally snapshots `localName` (\"where the author was when\n * posting\"). Not ported: location is optional here, so a byline that depends\n * on one would be blank for anyone who never set it.\n */\nexport const feedAuthorSchema = memberBylineSchema;\nexport type FeedAuthor = z.infer<typeof feedAuthorSchema>;\n\n// ─── Media ───────────────────────────────────────────────────────────────────\n\n/**\n * A post's image: a processed `mediaImageSchema` (every stored size, ready for\n * `srcset`, plus a placeholder) with the post's own alt text.\n *\n * `width` and `height` are required so a card can reserve the right box before\n * the image loads. Without them every image arrival shifts the feed under the\n * reader's thumb.\n *\n * `thumbnailUrl` is the smallest stored size, for a grid or a notification.\n *\n * URLs here come from our own upload pipeline, so they take a plain `z.url()`\n * rather than `httpUrlSchema`. ⚠️ If a \"paste an image link\" path is ever\n * added, these become member-supplied and must move to `httpUrlSchema`.\n */\nexport const feedImageSchema = mediaImageSchema.extend({\n kind: z.literal('image'),\n thumbnailUrl: z.url().optional(),\n /** Author-supplied alt text. Optional, and worth asking for. */\n alt: z.string().max(MEDIA_ALT_MAX).optional(),\n});\nexport type FeedImage = z.infer<typeof feedImageSchema>;\n\n/**\n * What a post carries, in the order it was attached. IMAGES ONLY (0.18.0):\n * video is shared as a link in the post text (the user's decision,\n * 2026-09-21), and the uploaded-video shape this used to admit described a\n * server-made poster frame no server makes. `kind` stays, so a stored item\n * says what it is.\n */\nexport const feedMediaSchema = feedImageSchema;\nexport type FeedMedia = z.infer<typeof feedMediaSchema>;\n\n// ─── Viewer state ────────────────────────────────────────────────────────────\n\n/**\n * What THIS viewer has done to this item, computed per request.\n *\n * Optional as a block, because an unauthenticated or server-rendered read has\n * no viewer. ⚠️ A client must treat an absent `viewer` as \"no state known\",\n * never as \"has not reacted\": rendering an empty reaction button off a missing\n * block is how a member sees their own reaction disappear on reload.\n */\nexport const viewerStateSchema = z.object({\n reaction: reactionTypeSchema.nullable(),\n bookmarked: z.boolean(),\n isAuthor: z.boolean(),\n\n /**\n * What this viewer may DO, decided by the server and sent as plain answers.\n *\n * Not role data for the client to reason about. The moment a client derives\n * \"can I edit this\" from roles and ownership, that derivation is a second\n * implementation of the rule, it drifts from the server's, and the drift is\n * invisible until the two disagree: either a button that 403s or a button\n * that should not have been there. `isAuthor` stays because the byline reads\n * differently for your own post, not because the client should compute rights\n * from it.\n *\n * `canEdit` is false once the edit window closes, so it is not simply\n * `isAuthor`.\n */\n canEdit: z.boolean(),\n canDelete: z.boolean(),\n canReport: z.boolean(),\n});\nexport type ViewerState = z.infer<typeof viewerStateSchema>;\n\n/**\n * The viewer's state on a post or a poll: everything above, plus reposting\n * (0.8.0). Comments cannot be reposted, so they keep `viewerStateSchema`.\n */\nexport const contentViewerStateSchema = viewerStateSchema.extend({\n /**\n * The viewer's own ACTIVE repost of this item, or null. An id rather than a\n * boolean because undoing it is deleting that post (`DELETE\n * /api/v1/feed/:contentId`), and a client holding only \"true\" would have to\n * go and find it.\n */\n repostId: objectIdSchema.nullable(),\n /**\n * Whether a repost may be made now, decided by the server: false on your\n * own item, on a repost (repost the original instead), on an item hidden by\n * staff, while you already have one (undo it first), and for a member who\n * cannot write yet (no finished, active profile: `ONBOARDING_INCOMPLETE`).\n */\n canRepost: z.boolean(),\n /**\n * On content inside a community only (0.9.0): whether this viewer may take\n * part in it now (react, comment, vote, save, edit): an active member of a\n * community that is not archived. False for someone who can read it but has\n * not joined (a public community's reader, staff): offer Join instead of\n * the controls. `canEdit` is false too whenever this is.\n *\n * TAKING BACK needs only read access (0.18.0, the server's rule since\n * 0.9.0): someone who left, or a community since archived, may still remove\n * their reaction, their vote or their save, so a client gates only SETTING\n * one on this. Reporting is safety, open to any signed-in reader who did not\n * write it (`canReport`, 0.13.0), joined or not.\n */\n canTakePart: z.boolean().optional(),\n});\nexport type ContentViewerState = z.infer<typeof contentViewerStateSchema>;\n\n// ─── Repost ──────────────────────────────────────────────────────────────────\n\n/**\n * What a repost carries of the item it shares (0.8.0), computed when READ:\n * the byline is live, and an edit to the original shows in every repost of it.\n *\n * `unavailable` once the original is deleted, hidden by staff, or its\n * author's account is gone. The repost stays (it is its author's post, with\n * their words), and says the original is no longer there, rather than\n * vanishing from under the people who commented on it.\n */\nexport const repostedSourceSchema = z.discriminatedUnion('state', [\n z.object({\n state: z.literal('available'),\n contentKind: contentKindSchema,\n id: objectIdSchema,\n author: feedAuthorSchema,\n /** A post's whole text, or a poll's question. */\n text: z.string().max(POST_MAX_BODY_CHARS),\n /** The original's first attachment, for the card's thumbnail. */\n media: feedMediaSchema.optional(),\n /** The product the original is about, live, as on the original. */\n product: productChipSchema.optional(),\n createdAt: z.iso.datetime(),\n }),\n z.object({\n state: z.literal('unavailable'),\n contentKind: contentKindSchema,\n id: objectIdSchema,\n }),\n]);\nexport type RepostedSource = z.infer<typeof repostedSourceSchema>;\n\n// ─── Post ────────────────────────────────────────────────────────────────────\n\n/**\n * A post as the feed and the single-post page receive it.\n *\n * Counts are denormalised onto the row so one document serves a whole card.\n *\n * No `score` and no up/down vote. Jansathi's signed score exists to rank civic\n * content by community judgement, and a downvote arrow on a founder's first\n * post is a different product. Reactions and comments are the engagement axes\n * here. Adding a score later is additive; removing a visible downvote after\n * members have used it is not.\n */\nexport const feedPostSchema = z.object({\n id: objectIdSchema,\n contentKind: z.literal('post'),\n author: feedAuthorSchema,\n\n /**\n * The WHOLE body, never a server-side truncation.\n *\n * So there is no \"was this shortened\" flag and no second fetch to read the\n * rest: the card clamps visually and expands in place. At a 5,000 character\n * cap a page of twenty posts is small enough that trimming would cost a round\n * trip to save nothing. If that ever stops being true, a truncation flag is\n * an additive change; a client that has been guessing is not.\n */\n text: z.string().max(POST_MAX_BODY_CHARS),\n /** Ordered as the author attached them. */\n media: z.array(feedMediaSchema).max(POST_MAX_MEDIA),\n /**\n * What this post is ABOUT, from the same vocabulary a member picks as their\n * interests. This is the whole mechanism behind a relevant feed: matching is\n * an intersection of two lists drawn from one enum, not a guess from who\n * wrote it.\n *\n * ⚠️ An EMPTY array is allowed and means the post is untagged, so interest\n * matching cannot reach it. That is a real consequence, not a nuance: an\n * untagged post is only ever found chronologically.\n */\n topics: z.array(topicSchema).max(MAX_TOPICS),\n\n /**\n * The product this post is about, live-joined (0.6.0). Absent when it names\n * none, and when the product has since been deleted or hidden by staff: the\n * post stays, the chip goes. Every comment on a post with a product is that\n * product's feedback.\n */\n product: productChipSchema.optional(),\n /** What the post asks of the community about its product. Only with `product`. */\n ask: productAskSchema.optional(),\n\n /**\n * The community it was posted in (0.9.0): \"in AI Builders\". Absent on\n * general content. Never alongside `product`, `ask` or `repostOf`:\n * community content carries no product and is never reposted.\n */\n community: communityChipSchema.optional(),\n /** When a moderator pinned it as one of its community's announcements (0.9.0). */\n pinnedAt: z.iso.datetime().optional(),\n\n /**\n * Present only on a REPOST (0.8.0): the item this post shares, with `text`\n * as the reposter's own words above it (empty for a plain repost) and no\n * media of its own. A repost cannot be reposted; its own `repostCount` is\n * always 0.\n */\n repostOf: repostedSourceSchema.optional(),\n\n commentCount: z.number().int().nonnegative(),\n reactionCounts: reactionCountsSchema,\n /** Active reposts of this item (0.8.0); an undone or removed one no longer counts. */\n repostCount: z.number().int().nonnegative(),\n\n createdAt: z.iso.datetime(),\n /** Null when never edited. The edit window is the server's to enforce. */\n editedAt: z.iso.datetime().nullable(),\n\n viewer: contentViewerStateSchema.optional(),\n});\nexport type FeedPost = z.infer<typeof feedPostSchema>;\n\n// ─── Poll ────────────────────────────────────────────────────────────────────\n\n/**\n * One choice. `index` is the identifier a vote refers to, not the label:\n * sending indices avoids ambiguity between two options whose labels differ\n * only by case, spacing or an invisible character.\n */\nexport const pollOptionSchema = z.object({\n index: z\n .number()\n .int()\n .min(0)\n .max(POLL_MAX_OPTIONS - 1),\n label: z.string().min(1).max(POLL_OPTION_MAX_CHARS),\n voteCount: z.number().int().nonnegative(),\n});\nexport type PollOption = z.infer<typeof pollOptionSchema>;\n\n/**\n * A poll. Its own kind, not a post with extra fields, because votes are their\n * own records keyed by (poll, voter) and a post has nothing equivalent.\n *\n * Polls are not editable. Changing a question or an option after a vote has\n * been cast silently re-purposes that vote. A poll closes at its `closesAt`;\n * there is no closing it early.\n */\nexport const feedPollSchema = z.object({\n id: objectIdSchema,\n contentKind: z.literal('poll'),\n author: feedAuthorSchema,\n\n question: z.string().min(1).max(POLL_QUESTION_MAX_CHARS),\n options: z.array(pollOptionSchema).min(POLL_MIN_OPTIONS).max(POLL_MAX_OPTIONS),\n /** Unique voters, not votes: with multi-select the two differ. */\n totalVoters: z.number().int().nonnegative(),\n allowsMultipleChoices: z.boolean(),\n closesAt: z.iso.datetime(),\n /**\n * Whether the poll is closed, decided by the SERVER's clock.\n *\n * Derivable from `closesAt`, and deliberately sent anyway: a device with a\n * wrong clock would otherwise show a live poll as finished, or offer a vote\n * the server then rejects, and the member sees a control that lies. `closesAt`\n * stays for rendering \"closes in 2 days\"; this is what gates the control.\n */\n isClosed: z.boolean(),\n\n /** As on a post. A poll is feed content and is matched the same way. */\n topics: z.array(topicSchema).max(MAX_TOPICS),\n\n /** As on a post: \"which onboarding flow should we ship?\", about a product. */\n product: productChipSchema.optional(),\n ask: productAskSchema.optional(),\n /** As on a post (0.9.0). */\n community: communityChipSchema.optional(),\n pinnedAt: z.iso.datetime().optional(),\n\n commentCount: z.number().int().nonnegative(),\n reactionCounts: reactionCountsSchema,\n /** As on a post (0.8.0). */\n repostCount: z.number().int().nonnegative(),\n\n createdAt: z.iso.datetime(),\n /** Always null. Kept so one byline component renders both kinds. */\n editedAt: z.iso.datetime().nullable(),\n\n viewer: contentViewerStateSchema\n .extend({\n /** Indices this viewer chose. Empty means not voted yet, and it is\n * always populated for the viewer: a voter can always see their own\n * answer even where others' results are hidden. */\n votedOptionIndices: z.array(z.number().int().nonnegative()),\n })\n .optional(),\n});\nexport type FeedPoll = z.infer<typeof feedPollSchema>;\n\n// ─── The feed item union ─────────────────────────────────────────────────────\n\n/**\n * Discriminated on `contentKind`, so a client narrows with a switch and the\n * compiler catches an unhandled kind the day one is added. A plain union would\n * need every field checked by hand and would silently accept a hybrid.\n */\nexport const feedItemSchema = z.discriminatedUnion('contentKind', [feedPostSchema, feedPollSchema]);\nexport type FeedItem = z.infer<typeof feedItemSchema>;\n\n// ─── Comments ────────────────────────────────────────────────────────────────\n\n/**\n * A comment, one level of nesting only: a reply to a comment is allowed, a\n * reply to a reply is not. Two levels stay readable on a phone; three do not,\n * and the server is what enforces it.\n */\nexport const feedCommentSchema = z.object({\n id: objectIdSchema,\n contentKind: contentKindSchema,\n contentId: objectIdSchema,\n /** Null for a top-level comment. A non-null value may only name a top-level\n * comment, never a reply. */\n parentCommentId: objectIdSchema.nullable(),\n author: feedAuthorSchema,\n\n text: z.string().max(COMMENT_MAX_BODY_CHARS),\n\n /** Meaningful only on a top-level comment; always 0 on a reply. */\n replyCount: z.number().int().nonnegative(),\n reactionCounts: reactionCountsSchema,\n\n /**\n * When the product's team marked this comment as feedback that helped\n * (0.6.0), or null. Only a comment on a post tagged with a product, written\n * by someone outside that product's team, can carry it; it earns its writer\n * contribution points.\n */\n helpfulAt: z.iso.datetime().nullable(),\n\n createdAt: z.iso.datetime(),\n editedAt: z.iso.datetime().nullable(),\n\n /** No `bookmarked`: the post is the right thing to save, not a comment. */\n viewer: viewerStateSchema\n .omit({ bookmarked: true })\n .extend({\n /**\n * The control is theirs now (0.12.0). On a comment from outside the\n * team of the post's product: unmarked, marking it is acting for the\n * product (a member on its team, the product open); marked, taking the\n * mark back needs only being on the team, member or not, whatever the\n * product's state.\n */\n canMarkHelpful: z.boolean(),\n })\n .optional(),\n});\nexport type FeedComment = z.infer<typeof feedCommentSchema>;\n\n// ─── Queries ─────────────────────────────────────────────────────────────────\n\n/**\n * `GET /api/v1/feed`. Inherits `limit` and `cursor`, which already tolerate an\n * emptied control rather than answering 400.\n *\n * `interests=true` narrows to the viewer's topics (falling back to the plain\n * feed when they have none). `z.stringbool`, so `true`/`false`/`1`/`0` all\n * read as a person means them; until 0.2.0 the backend read this parameter\n * raw, outside the schema, and only the exact string `true` counted.\n *\n * ⚠ NO `authorId`. It was accepted here and then ignored by the server, so a\n * profile page asking for one founder's posts silently got everybody's. A\n * member's own posts are `GET /api/v1/feed/by-author/:userId`.\n */\nexport const feedQuerySchema = paginationQuerySchema.extend({\n sort: z.preprocess(emptyStringToUndefined, feedSortSchema.default('new')),\n interests: z.preprocess(emptyStringToUndefined, z.stringbool().default(false)),\n});\nexport type FeedQuery = z.infer<typeof feedQuerySchema>;\nexport type FeedQueryInput = z.input<typeof feedQuerySchema>;\n\n/** `GET /api/v1/feed/:contentKind/:contentId/comments`. */\nexport const commentsQuerySchema = paginationQuerySchema.extend({\n /** Omitted lists top-level comments; set, it lists that comment's replies. */\n parentCommentId: z.preprocess(emptyStringToUndefined, objectIdSchema.optional()),\n});\nexport type CommentsQuery = z.infer<typeof commentsQuerySchema>;\nexport type CommentsQueryInput = z.input<typeof commentsQuerySchema>;\n\n/**\n * `GET /api/v1/feed/:contentKind/:contentId/reactions` (0.8.0): who reacted,\n * newest first. `contentKind` is post, poll or comment, as for reacting.\n * `type` narrows to one reaction (a tab); absent lists every reaction (All).\n */\nexport const reactionsQuerySchema = paginationQuerySchema.extend({\n type: z.preprocess(emptyStringToUndefined, reactionTypeSchema.optional()),\n});\nexport type ReactionsQuery = z.infer<typeof reactionsQuerySchema>;\nexport type ReactionsQueryInput = z.input<typeof reactionsQuerySchema>;\n\n// ─── Route parameters ────────────────────────────────────────────────────────\n\n/**\n * Every id and kind in a feed URL, as a schema, so a bad one answers exactly\n * as a bad body field does: 400 `VALIDATION_ERROR`, the issue at\n * `location: 'params'`. Until 0.2.0 these were hand checks that answered\n * \"Invalid id.\" with no issues at all, and the comment routes never looked at\n * `:contentKind`.\n */\n\n/** `GET /feed/:id`, the permalink. */\nexport const feedItemParamsSchema = z.object({ id: objectIdSchema });\n\n/** `PATCH|DELETE /feed/:contentId`. */\nexport const contentParamsSchema = z.object({ contentId: objectIdSchema });\n\n/** A bookmark and a comment thread: a post or a poll. */\nexport const contentTargetParamsSchema = z.object({\n contentKind: contentKindSchema,\n contentId: objectIdSchema,\n});\n\n/** A reaction and a report: a post, a poll or a comment. */\nexport const engagementTargetParamsSchema = z.object({\n contentKind: engagementTargetKindSchema,\n contentId: objectIdSchema,\n});\n\n/** `POST|DELETE /feed/poll/:pollId/vote`. */\nexport const pollParamsSchema = z.object({ pollId: objectIdSchema });\n\n/** `DELETE /feed/comments/:commentId`. */\nexport const commentParamsSchema = z.object({ commentId: objectIdSchema });\n\n/** `GET /feed/by-author/:userId`. */\nexport const authorParamsSchema = z.object({ userId: objectIdSchema });\n\nexport type FeedItemParams = z.infer<typeof feedItemParamsSchema>;\nexport type ContentParams = z.infer<typeof contentParamsSchema>;\nexport type ContentTargetParams = z.infer<typeof contentTargetParamsSchema>;\nexport type EngagementTargetParams = z.infer<typeof engagementTargetParamsSchema>;\nexport type PollParams = z.infer<typeof pollParamsSchema>;\nexport type CommentParams = z.infer<typeof commentParamsSchema>;\nexport type AuthorParams = z.infer<typeof authorParamsSchema>;\n\n// ─── Pages ───────────────────────────────────────────────────────────────────\n\n/**\n * A page of the feed, and of one member's posts (`by-author`), which is the\n * same shape. `nextCursor` is null on the last page.\n */\nexport const feedPageSchema = paginatedSchema(feedItemSchema);\nexport type FeedPage = z.infer<typeof feedPageSchema>;\n\n/**\n * A community's pinned announcements (0.9.0), most recently pinned first, at\n * most `COMMUNITY_PINS_MAX` (domains/communities). Never paged.\n */\nexport const pinnedItemsSchema = z.object({ items: z.array(feedItemSchema) });\nexport type PinnedItems = z.infer<typeof pinnedItemsSchema>;\n\n/** A page of a thread: top-level comments, or one comment's replies. */\nexport const commentPageSchema = paginatedSchema(feedCommentSchema);\nexport type CommentPage = z.infer<typeof commentPageSchema>;\n\n/**\n * One row of \"who reacted\" (0.8.0).\n *\n * `reactedAt` is when they reacted, and the list's order: CHANGING a\n * reaction updates it in place, so the row keeps its position (and moves from\n * one tab to another) instead of jumping to the top under a reader's cursor.\n * Taking it back and reacting again is a new reaction, from then.\n *\n * Only members who can be shown are listed: an erased account's reactions are\n * withdrawn with it, and a paused, suspended or never-onboarded member is\n * left out of the list.\n * `reactionCounts` still counts every reaction, so a tab can list fewer\n * people than its number says. That is deliberate: dropping a suspended\n * member's reaction from the count would make it jump back on reinstatement.\n */\nexport const reactorSchema = z.object({\n member: memberBylineSchema,\n reaction: reactionTypeSchema,\n reactedAt: z.iso.datetime(),\n});\nexport type Reactor = z.infer<typeof reactorSchema>;\nexport const reactorPageSchema = paginatedSchema(reactorSchema);\nexport type ReactorPage = z.infer<typeof reactorPageSchema>;\n\n/**\n * A comment on a product's Feedback tab (`GET /products/:id/feedback`): the\n * comment as a thread shows it, plus whether its writer is already a\n * confirmed tester, so the team's \"Confirm as tester\" knows without paging\n * through `/testers`.\n */\nexport const productFeedbackItemSchema = feedCommentSchema.extend({\n authorIsTester: z.boolean(),\n});\nexport type ProductFeedbackItem = z.infer<typeof productFeedbackItemSchema>;\nexport const productFeedbackPageSchema = paginatedSchema(productFeedbackItemSchema);\nexport type ProductFeedbackPage = z.infer<typeof productFeedbackPageSchema>;\n\n// ─── Write bodies ────────────────────────────────────────────────────────────\n\n/**\n * ⚠️ Every write body below is its own object with NO defaults, and none of\n * them is built with `.pick().partial()`. `.partial()` does not switch off\n * `.default()`, so a patch body built that way rewrites defaulted fields on\n * every request. See `updateFounderProfileBodySchema` for the incident.\n */\n\n/**\n * An attachment on the way IN: the id of an image already uploaded and\n * completed (`media`), and deliberately not a URL.\n *\n * ⚠ The first draft of this took the full read shape, URL included. That is\n * the same hole that was closed on the profile: a URL in a write body is a\n * member-supplied URL, so a post could have carried `javascript:` in an image\n * source, and nothing downstream re-checks a value the server itself stored.\n * Taking an id instead removes the question rather than validating it.\n *\n * `width`, `height` and the URLs are NOT accepted either: the server measured\n * them when it processed the upload, and taking them from the client would let\n * a caller claim a 1x1 image and collapse the card.\n *\n * The server attaches each id only if it is the caller's, processed, uploaded\n * for a post, and not attached anywhere yet (`UPLOAD_NOT_ATTACHABLE`\n * otherwise), so one upload can never appear on two posts.\n */\nexport const attachedMediaSchema = z.object({\n mediaId: objectIdSchema,\n /** Author-supplied alt text, the one media field a member does write. */\n alt: z.string().trim().max(MEDIA_ALT_MAX).optional(),\n});\nexport type AttachedMedia = z.infer<typeof attachedMediaSchema>;\n\n/**\n * Which of the author's products a post or poll is about, and what it asks.\n * Set when it is written and NEVER EDITABLE afterwards: moving a post to\n * another product after people replied would move their feedback with it.\n * The server accepts only a product the author is on the team of\n * (`PRODUCT_NOT_YOURS`) and that is not archived (`PRODUCT_ARCHIVED`).\n */\nconst aboutAProduct = {\n productId: objectIdSchema.optional(),\n /** Only with `productId` (`askWithoutProduct`). */\n ask: productAskSchema.optional(),\n};\nconst askNeedsProduct = (b: { productId?: string; ask?: string }) =>\n b.ask === undefined || b.productId !== undefined;\n\n/**\n * Which community it is posted in (0.9.0), set when written and never moved.\n * The author must be an active member (`COMMUNITY_JOIN_REQUIRED`) and it must\n * not be archived (`COMMUNITY_ARCHIVED`). Community content carries no product\n * and no ask (`communityWithProduct`); the server checks that too.\n */\nconst inACommunity = { communityId: objectIdSchema.optional() };\nconst communityWithoutProduct = (b: { communityId?: string; productId?: string; ask?: string }) =>\n b.communityId === undefined || (b.productId === undefined && b.ask === undefined);\n\n/**\n * A post must carry something: text, media, or (a repost) the item it\n * reposts. ONE rule for writing and editing (0.18.0): an edit may clear the\n * text of a post that keeps its photos or its repost, and only a post that\n * would be left with nothing is refused (`postEmpty`, on `text`). The create\n * body checks it here; an edit can only be checked where the stored post is\n * known, so the server applies this same function there.\n */\nexport function postHasContent(post: {\n text?: string | null;\n mediaCount: number;\n isRepost: boolean;\n}): boolean {\n return Boolean(post.text?.trim().length) || post.mediaCount > 0 || post.isRepost;\n}\n\nexport const createPostBodySchema = z\n .object({\n text: z.string().trim().max(POST_MAX_BODY_CHARS).optional(),\n /** Already uploaded and completed. See `attachedMediaSchema`. Order is preserved. */\n media: z.array(attachedMediaSchema).max(POST_MAX_MEDIA).optional(),\n /** Absent means untagged, which means interest matching cannot find it. */\n topics: z.array(topicSchema).max(MAX_TOPICS).optional(),\n ...aboutAProduct,\n ...inACommunity,\n })\n .refine(\n (b) => postHasContent({ text: b.text, mediaCount: b.media?.length ?? 0, isRepost: false }),\n {\n error: VALIDATION_MESSAGE.postEmpty,\n path: ['text'],\n },\n )\n .refine((b) => eachOnce(b.media?.map((m) => m.mediaId) ?? []), {\n error: VALIDATION_MESSAGE.mediaDuplicate,\n path: ['media'],\n })\n .refine(askNeedsProduct, { error: VALIDATION_MESSAGE.askWithoutProduct, path: ['ask'] })\n .refine(communityWithoutProduct, {\n error: VALIDATION_MESSAGE.communityWithProduct,\n path: ['communityId'],\n });\nexport type CreatePostBody = z.infer<typeof createPostBodySchema>;\n\n/**\n * Only the text is editable. Changing media after the fact makes every\n * earlier reply look like a reply to something else. Empty text is allowed\n * here and refused by the server only when the post would carry nothing\n * (`postHasContent`): a photo's caption can be cleared, a text post's cannot.\n */\nexport const updatePostBodySchema = z.object({\n text: z.string().trim().max(POST_MAX_BODY_CHARS),\n});\nexport type UpdatePostBody = z.infer<typeof updatePostBodySchema>;\n\nexport const createPollBodySchema = z\n .object({\n question: z.string().trim().min(1).max(POLL_QUESTION_MAX_CHARS),\n options: z\n .array(z.object({ label: z.string().trim().min(1).max(POLL_OPTION_MAX_CHARS) }))\n .min(POLL_MIN_OPTIONS)\n .max(POLL_MAX_OPTIONS),\n /**\n * Duration from now, NOT an absolute closing time. A client whose clock is\n * wrong would otherwise open a poll that is already closed, and the member\n * would have no way to tell why.\n */\n durationMs: z.number().int().min(POLL_MIN_DURATION_MS).max(POLL_MAX_DURATION_MS),\n allowsMultipleChoices: z.boolean(),\n topics: z.array(topicSchema).max(MAX_TOPICS).optional(),\n ...aboutAProduct,\n ...inACommunity,\n })\n .refine(askNeedsProduct, { error: VALIDATION_MESSAGE.askWithoutProduct, path: ['ask'] })\n .refine(communityWithoutProduct, {\n error: VALIDATION_MESSAGE.communityWithProduct,\n path: ['communityId'],\n })\n .refine(\n (b) => {\n const seen = new Set<string>();\n for (const o of b.options) {\n const key = o.label.trim().toLowerCase();\n if (seen.has(key)) return false;\n seen.add(key);\n }\n return true;\n },\n { error: VALIDATION_MESSAGE.pollOptionsDuplicate, path: ['options'] },\n );\nexport type CreatePollBody = z.infer<typeof createPollBodySchema>;\n\n/**\n * Cast or change a vote. The server upserts on (poll, voter), so voting twice\n * replaces rather than doubles. Each option once (0.18.0: `[0, 0]` was taken\n * and silently cut to `[0]`).\n */\nexport const castVoteBodySchema = z.object({\n optionIndices: z\n .array(\n z\n .number()\n .int()\n .min(0)\n .max(POLL_MAX_OPTIONS - 1),\n )\n .min(1)\n .max(POLL_MAX_OPTIONS)\n .refine(eachOnce, { error: VALIDATION_MESSAGE.pollVoteDuplicate }),\n});\nexport type CastVoteBody = z.infer<typeof castVoteBodySchema>;\n\nexport const createCommentBodySchema = z.object({\n text: z.string().trim().min(1).max(COMMENT_MAX_BODY_CHARS),\n /** Null for a top-level comment. */\n parentCommentId: objectIdSchema.nullable(),\n});\nexport type CreateCommentBody = z.infer<typeof createCommentBodySchema>;\n\n/**\n * `PATCH /api/v1/feed/comments/:commentId` (route since 0.8.0): your own\n * comment, within `EDIT_WINDOW_MS` (`EDIT_WINDOW_CLOSED` after), answering\n * `IdResponse`. Sets `editedAt`.\n */\nexport const updateCommentBodySchema = z.object({\n text: z.string().trim().min(1).max(COMMENT_MAX_BODY_CHARS),\n});\nexport type UpdateCommentBody = z.infer<typeof updateCommentBodySchema>;\n\n/**\n * `POST /api/v1/feed/reposts` (0.8.0), answering `IdResponse`: the new repost,\n * which is a post of yours carrying `repostOf`. Undo it by deleting that post.\n *\n * One ACTIVE repost per member per item (`ALREADY_REPOSTED`). Refused on your\n * own item (`CANNOT_REPOST_OWN`), on a repost (`CANNOT_REPOST_A_REPOST`: share\n * the original), on anything deleted or hidden (404), and from a member who\n * cannot write yet (`ONBOARDING_INCOMPLETE`). Undo: `DELETE\n * /api/v1/feed/:contentId` on the repost.\n */\nexport const createRepostBodySchema = z.object({\n sourceKind: contentKindSchema,\n sourceId: objectIdSchema,\n /** Your own words above it. Absent or empty: a plain repost. */\n caption: z.string().trim().max(POST_MAX_BODY_CHARS).optional(),\n});\nexport type CreateRepostBody = z.infer<typeof createRepostBodySchema>;\n\n/**\n * Set or clear a reaction. `null` clears it; at most one per viewer per item.\n * Only an offered reaction can be SET (`REACTION_PICKER_VALUES`): a retired\n * one stays readable on old rows but is not chosen anew.\n */\nexport const setReactionBodySchema = z.object({\n reaction: pickableReactionSchema.nullable(),\n});\nexport type SetReactionBody = z.infer<typeof setReactionBodySchema>;\n\n// ─── Engagement responses ────────────────────────────────────────────────────\n\n/**\n * Returned by the reaction endpoint so the card can render the authoritative\n * result without a follow-up read. Without the fresh counts a client has to\n * guess the new total, and two quick taps leave it showing a number the server\n * never had.\n */\nexport const setReactionResponseSchema = z.object({\n contentKind: engagementTargetKindSchema,\n contentId: objectIdSchema,\n reaction: reactionTypeSchema.nullable(),\n reactionCounts: reactionCountsSchema,\n});\nexport type SetReactionResponse = z.infer<typeof setReactionResponseSchema>;\n\n// ─── Reporting ───────────────────────────────────────────────────────────────\n\n/**\n * Where a report stands (0.18.0: named here; the backend's model kept its own\n * list, and the permission rule that only an open report is decided checked a\n * plain string). Staff-only on the wire. Stored: append only.\n */\nexport const REPORT_STATUS_VALUES = ['open', 'actioned', 'dismissed'] as const;\nexport type ReportStatus = (typeof REPORT_STATUS_VALUES)[number];\n\n/**\n * Why a member reported something.\n *\n * ⚠️ Stored on every report row, so this is data, not a label: removing a value\n * orphans reports already filed under it, and the counts nobody can recover are\n * exactly the ones a moderation review needs. Append only.\n *\n * ⚠️ A REASON THAT IS MISSING DOES NOT DISAPPEAR, it lands in `other`. Whatever\n * is not named here becomes an undifferentiated pile that no aggregate can see\n * a pattern in, which is the one thing report categories exist for. Worth being\n * deliberate about the absences rather than discovering them later.\n */\nexport const REPORT_REASON_VALUES = [\n 'spam',\n 'harassment',\n 'misinformation',\n 'off_topic',\n 'impersonation',\n 'other',\n] as const;\nexport const reportReasonSchema = z.enum(REPORT_REASON_VALUES);\nexport type ReportReason = z.infer<typeof reportReasonSchema>;\n\n/**\n * What a member sends to report a post, poll or comment.\n *\n * The target is the URL's, not the body's: a report is filed against a specific\n * thing at a specific address, and taking the target from the body would let a\n * caller report one item while the route thinks it is another.\n *\n * `note` is free text and optional, which is the pressure valve for everything\n * the enum does not name. ⚠️ It is written by a member, so it is untrusted at\n * render: a moderation queue displays it, and a moderation queue is a screen\n * staff read quickly.\n */\nexport const createReportBodySchema = z.object({\n reason: reportReasonSchema,\n note: z.string().trim().max(REPORT_NOTE_MAX).optional(),\n});\nexport type CreateReportBody = z.infer<typeof createReportBodySchema>;\n\n/**\n * The answer to filing one.\n *\n * `alreadyReported` distinguishes \"we have taken this\" from \"you already told\n * us\", because one report per member per target means a second press is a\n * no-op, and a silent no-op reads as a failure to somebody who pressed twice\n * deliberately. It is NOT a count: how many OTHER people reported an item is\n * moderation's business and telling a reporter would turn the count into a\n * score anyone could farm.\n */\nexport const createReportResponseSchema = z.object({\n /** A post, poll or comment; since 0.6.0 also a product or a review. */\n contentKind: reportTargetKindSchema,\n contentId: objectIdSchema,\n alreadyReported: z.boolean(),\n});\nexport type CreateReportResponse = z.infer<typeof createReportResponseSchema>;\n\n/**\n * `PUT|DELETE /api/v1/feed/comments/:commentId/helpful`: the product's team\n * marks (or unmarks) a comment as feedback that helped. Answers the state it\n * settled on. `FEEDBACK_NOT_MARKABLE` when the comment is not feedback on a\n * product the caller is on the team of.\n */\nexport const commentHelpfulResponseSchema = z.object({\n commentId: objectIdSchema,\n helpfulAt: z.iso.datetime().nullable(),\n});\nexport type CommentHelpfulResponse = z.infer<typeof commentHelpfulResponseSchema>;\n\n/** The bookmark endpoint is a toggle and returns the state it settled on. */\nexport const toggleBookmarkResponseSchema = z.object({\n contentKind: contentKindSchema,\n contentId: objectIdSchema,\n bookmarked: z.boolean(),\n});\nexport type ToggleBookmarkResponse = z.infer<typeof toggleBookmarkResponseSchema>;\n\n/** A vote returns the whole poll: counts, totals and the viewer's own choices\n * all move together, and reassembling them client-side drifts. */\nexport const castVoteResponseSchema = z.object({\n poll: feedPollSchema,\n});\nexport type CastVoteResponse = z.infer<typeof castVoteResponseSchema>;\n"]}
|
|
@@ -296,6 +296,13 @@ export declare const geolocationSyncResultSchema: z.ZodObject<{
|
|
|
296
296
|
}, z.core.$strip>>;
|
|
297
297
|
}, z.core.$strip>;
|
|
298
298
|
export type GeolocationSyncResult = z.infer<typeof geolocationSyncResultSchema>;
|
|
299
|
+
/** A location history row: a raw GPS fix, or the area a fix or a pick named. The backend's storage's own values, from here like every other list (0.20.0). */
|
|
300
|
+
export declare const LOCATION_HISTORY_KIND_VALUES: readonly ["fix", "area"];
|
|
301
|
+
export declare const locationHistoryKindSchema: z.ZodEnum<{
|
|
302
|
+
fix: "fix";
|
|
303
|
+
area: "area";
|
|
304
|
+
}>;
|
|
305
|
+
export type LocationHistoryKind = z.infer<typeof locationHistoryKindSchema>;
|
|
299
306
|
/**
|
|
300
307
|
* Whether Founder Circle keeps the member's positions (the user, 2026-10-22:
|
|
301
308
|
* "Add stop + clear", India's DPDP right to withdraw consent). On by default,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"areas.d.ts","sourceRoot":"","sources":["../../../src/domains/location/areas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAYxB,oEAAoE;AACpE,eAAO,MAAM,gBAAgB,6HAUnB,CAAC;AACX,eAAO,MAAM,cAAc;;;;;;;;;;EAA2B,CAAC;AACvD,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAEtD;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,6FAOQ,CAAC;AACzC,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpE,+EAA+E;AAC/E,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAOhE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,wOAWzB,CAAC;AAEX,6EAA6E;AAC7E,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;iBAa3B,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,MAAM,CAIzE;AAED,qFAAqF;AACrF,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;iBAAiD,CAAC;AAC7E,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAEtD,yFAAyF;AACzF,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;iBAGzB,CAAC;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAEtD,+CAA+C;AAC/C,eAAO,MAAM,oBAAoB;;iBAAwC,CAAC;AAC1E,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB;;;;iBAIhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACpE,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAYzE;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB;;;;kBAYpB,CAAC;AACZ,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D;;;;;GAKG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAKtB,CAAC;AACH,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEhD,kDAAkD;AAClD,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,YAAY,CAMtD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAQ/C;AAID;;;;GAIG;AACH,eAAO,MAAM,oBAAoB;;;;kBAMtB,CAAC;AACZ,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAGtC,CAAC;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"areas.d.ts","sourceRoot":"","sources":["../../../src/domains/location/areas.ts"],"names":[],"mappings":"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"}
|
|
@@ -211,6 +211,9 @@ export const geolocationSyncResultSchema = z.object({
|
|
|
211
211
|
synced: z.literal(true),
|
|
212
212
|
match: placeSchema.nullable(),
|
|
213
213
|
});
|
|
214
|
+
/** A location history row: a raw GPS fix, or the area a fix or a pick named. The backend's storage's own values, from here like every other list (0.20.0). */
|
|
215
|
+
export const LOCATION_HISTORY_KIND_VALUES = ['fix', 'area'];
|
|
216
|
+
export const locationHistoryKindSchema = z.enum(LOCATION_HISTORY_KIND_VALUES);
|
|
214
217
|
// ─── The member's own control (0.19.0) ───────────────────────────────────────
|
|
215
218
|
/**
|
|
216
219
|
* Whether Founder Circle keeps the member's positions (the user, 2026-10-22:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"areas.js","sourceRoot":"","sources":["../../../src/domains/location/areas.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Where a member is and where they live: Jansathi's location model (0.19.0)\n * =========================================================================\n *\n * The user, 2026-10-21: Founder Circle takes Jansathi's location drawer\n * (state, then district, then locality, from `master_db.areas`) and its whole\n * location model (\"just copy paste things from jansathi\"). The plan, with the\n * Jansathi analysis it copies: founder-circle/LOCATION-PLAN-2026-10-21.md.\n *\n * Two axes, as in Jansathi, never mixed:\n * - the DECLARED location (where the member lives): picked in the drawer, or\n * filled from GPS by the member, then saved. A client sends AREA IDS only\n * (`locationPickSchema`); the server reads the names, the public city and\n * the coordinates from `master_db`, so no client can store a place the area\n * tree does not have.\n * - the GPS axis (where the member is): the daily sync and \"Use my location\"\n * send a fix (`geolocationFixSchema`); the server keeps it, the area it\n * falls in, and the last `LOCATION_HISTORY_MAX` of each. Nothing on this\n * axis is ever shown to another member.\n *\n * Area ids are master_db's ObjectIds: stable for the life of the area tree.\n *\n * @module domains/location/areas\n */\n\nimport { z } from 'zod';\nimport { emptyStringToUndefined, objectIdSchema, queryInt } from '../../core/common.js';\nimport {\n AREA_NAME_MAX,\n AREA_SEARCH_MAX,\n GEO_ACCURACY_MAX_M,\n LOCALITY_SKIP_MAX,\n} from '../../core/limits.js';\nimport { VALIDATION_MESSAGE } from '../../core/validation.js';\n\n// ─── The area tree ───────────────────────────────────────────────────────────\n\n/** The levels of `master_db.areas` a member's location can name. */\nexport const AREA_TYPE_VALUES = [\n 'COUNTRY',\n 'STATE',\n 'DISTRICT',\n 'SUB_DISTRICT',\n 'BLOCK',\n 'GRAM_PANCHAYAT',\n 'VILLAGE',\n 'URBAN_BODY',\n 'URBAN_WARD',\n] as const;\nexport const areaTypeSchema = z.enum(AREA_TYPE_VALUES);\nexport type AreaType = z.infer<typeof areaTypeSchema>;\n\n/**\n * The drawer's third step lists these, everything below a district that a\n * member can live in: Jansathi's `LOCALITY_TYPE_TO_ADMIN_KEY`, each one a\n * stored level. Jansathi's drawer also lists HAMLET and LOCALITY and then\n * refuses them on save (no stored level); here they are never offered.\n * RURAL_WARD is not offered either (Jansathi neither): it has no point.\n */\nexport const LOCALITY_AREA_TYPES = [\n 'SUB_DISTRICT',\n 'BLOCK',\n 'GRAM_PANCHAYAT',\n 'VILLAGE',\n 'URBAN_BODY',\n 'URBAN_WARD',\n] as const satisfies readonly AreaType[];\nexport type LocalityAreaType = (typeof LOCALITY_AREA_TYPES)[number];\n\n/** How a locality row names its level (Jansathi's `addressForm.areaTypes`). */\nexport const LOCALITY_TYPE_LABEL: Record<LocalityAreaType, string> = {\n SUB_DISTRICT: 'Tehsil',\n BLOCK: 'Block',\n GRAM_PANCHAYAT: 'Gram Panchayat',\n VILLAGE: 'Village',\n URBAN_BODY: 'Town or city',\n URBAN_WARD: 'Ward',\n};\n\n/**\n * An urban body's kind, as the area builder writes it (reform-backend's\n * `URBAN_BODY_TYPES`, LGD). Read leniently: a kind added there later shows as\n * \"Town or city\" rather than breaking the list.\n */\nexport const URBAN_BODY_TYPE_VALUES = [\n 'Municipal Corporation',\n 'Municipality',\n 'Notified Area Council',\n 'Town Panchayat',\n 'Nagar Panchayat',\n 'Municipal Council',\n 'NCT Municipal Council',\n 'City Municipal Council',\n 'Town Municipal Council',\n 'Cantonment Board',\n] as const;\n\n/** One area as a list row and as part of a place: what a client may show. */\nexport const areaOptionSchema = z.object({\n id: objectIdSchema,\n name: z.string().trim().min(1).max(AREA_NAME_MAX),\n type: areaTypeSchema,\n /** Only on an URBAN_BODY, when the tree knows its kind. */\n urbanBodyType: z.string().trim().min(1).max(AREA_NAME_MAX).optional(),\n /**\n * What a locality sits in, to tell same-named places apart (0.19.0, the\n * review: one district has 12 villages called \"Pali\", a town's wards are\n * named \"1\" to \"35\"): a ward's town, a village's or panchayat's tehsil or\n * block. Absent on states, districts and towns.\n */\n within: z.string().trim().min(1).max(AREA_NAME_MAX).optional(),\n});\nexport type AreaOption = z.infer<typeof areaOptionSchema>;\n\n/**\n * An area as a member reads it: a ward the tree names by its number alone\n * (\"25\") is \"Ward 25\"; anything else by its name.\n */\nexport function areaWords(area: Pick<AreaOption, 'name' | 'type'>): string {\n return area.type === 'URBAN_WARD' && /^\\d+$/.test(area.name.trim())\n ? `Ward ${area.name.trim()}`\n : area.name;\n}\n\n/** `GET /location/areas/states` and `/districts`: the whole list, sorted by name. */\nexport const areaListSchema = z.object({ items: z.array(areaOptionSchema) });\nexport type AreaList = z.infer<typeof areaListSchema>;\n\n/** `GET /location/areas/localities`: one page (`LOCALITY_PAGE_SIZE`), sorted by name. */\nexport const areaPageSchema = z.object({\n items: z.array(areaOptionSchema),\n hasMore: z.boolean(),\n});\nexport type AreaPage = z.infer<typeof areaPageSchema>;\n\n/** `GET /location/areas/districts?stateId=` */\nexport const districtsQuerySchema = z.object({ stateId: objectIdSchema });\nexport type DistrictsQuery = z.infer<typeof districtsQuerySchema>;\n\n/**\n * `GET /location/areas/localities?districtId=&q=&skip=`: every locality level\n * anywhere inside the district (a ward sits under its town, not under the\n * district). `q` matches anywhere in the name, as Jansathi's does; an emptied\n * box is \"no search\".\n */\nexport const localitiesQuerySchema = z.object({\n districtId: objectIdSchema,\n q: z.preprocess(emptyStringToUndefined, z.string().trim().max(AREA_SEARCH_MAX).optional()),\n skip: queryInt(0, LOCALITY_SKIP_MAX, 0),\n});\nexport type LocalitiesQuery = z.infer<typeof localitiesQuerySchema>;\nexport type LocalitiesQueryInput = z.input<typeof localitiesQuerySchema>;\n\n// ─── The declared location ───────────────────────────────────────────────────\n\n/** An area id a picker must have chosen: unchosen is worded as a choice. */\nconst chosenAreaId = (unchosen: string) =>\n z\n .string({ error: unchosen })\n .min(1, { error: unchosen, abort: true })\n .regex(/^[0-9a-f]{24}$/i, VALIDATION_MESSAGE.invalidId)\n .transform((v) => v.toLowerCase());\n\n/**\n * A location as it is WRITTEN (0.19.0): onboarding's finish and Me's personal\n * details. Ids only. `localityId: null` is the member's \"my locality is not\n * listed\": the whole district (the user's choice, 2026-10-21).\n *\n * The server refuses ids that are not one chain (a district outside the\n * state, a locality outside the district) and a state Founder Circle's list\n * does not name (`findIndiaState`).\n */\nexport const locationPickSchema = z\n .object({\n stateId: chosenAreaId(VALIDATION_MESSAGE.stateRequired),\n districtId: chosenAreaId(VALIDATION_MESSAGE.districtRequired),\n // Said explicitly: an id, or `null` for the whole district. Left out is a\n // choice not made (`localityRequired`), never Zod's own English.\n localityId: z\n .string({ error: VALIDATION_MESSAGE.localityRequired })\n .regex(/^[0-9a-f]{24}$/i, VALIDATION_MESSAGE.invalidId)\n .transform((v) => v.toLowerCase())\n .nullable(),\n })\n .strict();\nexport type LocationPick = z.infer<typeof locationPickSchema>;\n\n/**\n * A place with its names: the member's own location as Me reads it back, the\n * area GPS matched, and the onboarding draft. What the location field shows\n * (\"Locality, District, State\") and where the drawer reopens. Owner-only:\n * other members read `locationSchema` (city, state, country).\n */\nexport const placeSchema = z.object({\n state: areaOptionSchema,\n district: areaOptionSchema,\n /** `null`: the whole district. */\n locality: areaOptionSchema.nullable(),\n});\nexport type Place = z.infer<typeof placeSchema>;\n\n/** The pick a place is (what saving it sends). */\nexport function pickOfPlace(place: Place): LocationPick {\n return {\n stateId: place.state.id,\n districtId: place.district.id,\n localityId: place.locality?.id ?? null,\n };\n}\n\n/**\n * \"Locality, what it sits in, District, State\", the deepest first; a name\n * repeated is said once (\"Ward 25, Bassi, Jaipur, Rajasthan\"; \"Rajpur,\n * Dehradun, Uttarakhand\").\n */\nexport function placeWords(place: Place): string {\n const parts = [\n place.locality ? areaWords(place.locality) : undefined,\n place.locality?.within,\n place.district.name,\n place.state.name,\n ].filter((p): p is string => Boolean(p));\n return parts.filter((p, i) => parts.indexOf(p) === i).join(', ');\n}\n\n// ─── The GPS axis ────────────────────────────────────────────────────────────\n\n/**\n * `POST /me/geolocation`: one GPS fix, from the daily sync or a member's\n * \"Use my location\" (Jansathi's body). Plain degrees, accuracy in metres. A\n * fix coarser than `GEO_ACCURACY_MAX_M` is refused: it cannot name a place.\n */\nexport const geolocationFixSchema = z\n .object({\n latitude: z.number().min(-90).max(90),\n longitude: z.number().min(-180).max(180),\n accuracy: z.number().min(0).max(GEO_ACCURACY_MAX_M),\n })\n .strict();\nexport type GeolocationFix = z.infer<typeof geolocationFixSchema>;\n\n/**\n * The answer: the place the fix falls in, for \"Use my location\" to put in the\n * field (the member still saves it), or `null` when the area tree has no\n * confident match (outside India, or nothing within reach). The daily sync\n * ignores it. Never the coordinates themselves.\n */\nexport const geolocationSyncResultSchema = z.object({\n synced: z.literal(true),\n match: placeSchema.nullable(),\n});\nexport type GeolocationSyncResult = z.infer<typeof geolocationSyncResultSchema>;\n\n// ─── The member's own control (0.19.0) ───────────────────────────────────────\n\n/**\n * Whether Founder Circle keeps the member's positions (the user, 2026-10-22:\n * \"Add stop + clear\", India's DPDP right to withdraw consent). On by default,\n * as Jansathi; it only matters once the browser allows location at all.\n *\n * OFF: the server keeps NO position: a fix (the daily check or a tap on \"Use\n * my location\") is still answered with the place it falls in, so the field can\n * be filled, but nothing of it is stored, and no history row is written, not\n * even for a saved place. The web stops the daily check. The member's chosen\n * place is not touched: removing it is the location field's Remove.\n *\n * `GET /api/v1/me/location-settings`, and `PATCH` with\n * `updateLocationSettingsBodySchema`, both answer this.\n */\nexport const locationSettingsSchema = z.object({ dailyLocation: z.boolean() });\nexport type LocationSettings = z.infer<typeof locationSettingsSchema>;\n\n/** `PATCH /api/v1/me/location-settings` */\nexport const updateLocationSettingsBodySchema = z.object({ dailyLocation: z.boolean() }).strict();\nexport type UpdateLocationSettingsBody = z.infer<typeof updateLocationSettingsBodySchema>;\n\n/**\n * `DELETE /api/v1/me/location-history`: every position and area the member's\n * device sent, and the position kept with their saved place, deleted at once.\n * The chosen place itself (what others see as city and state) stays.\n */\nexport const clearLocationHistoryResultSchema = z.object({ cleared: z.literal(true) });\nexport type ClearLocationHistoryResult = z.infer<typeof clearLocationHistoryResultSchema>;\n"]}
|
|
1
|
+
{"version":3,"file":"areas.js","sourceRoot":"","sources":["../../../src/domains/location/areas.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Where a member is and where they live: Jansathi's location model (0.19.0)\n * =========================================================================\n *\n * The user, 2026-10-21: Founder Circle takes Jansathi's location drawer\n * (state, then district, then locality, from `master_db.areas`) and its whole\n * location model (\"just copy paste things from jansathi\"). The plan, with the\n * Jansathi analysis it copies: founder-circle/LOCATION-PLAN-2026-10-21.md.\n *\n * Two axes, as in Jansathi, never mixed:\n * - the DECLARED location (where the member lives): picked in the drawer, or\n * filled from GPS by the member, then saved. A client sends AREA IDS only\n * (`locationPickSchema`); the server reads the names, the public city and\n * the coordinates from `master_db`, so no client can store a place the area\n * tree does not have.\n * - the GPS axis (where the member is): the daily sync and \"Use my location\"\n * send a fix (`geolocationFixSchema`); the server keeps it, the area it\n * falls in, and the last `LOCATION_HISTORY_MAX` of each. Nothing on this\n * axis is ever shown to another member.\n *\n * Area ids are master_db's ObjectIds: stable for the life of the area tree.\n *\n * @module domains/location/areas\n */\n\nimport { z } from 'zod';\nimport { emptyStringToUndefined, objectIdSchema, queryInt } from '../../core/common.js';\nimport {\n AREA_NAME_MAX,\n AREA_SEARCH_MAX,\n GEO_ACCURACY_MAX_M,\n LOCALITY_SKIP_MAX,\n} from '../../core/limits.js';\nimport { VALIDATION_MESSAGE } from '../../core/validation.js';\n\n// ─── The area tree ───────────────────────────────────────────────────────────\n\n/** The levels of `master_db.areas` a member's location can name. */\nexport const AREA_TYPE_VALUES = [\n 'COUNTRY',\n 'STATE',\n 'DISTRICT',\n 'SUB_DISTRICT',\n 'BLOCK',\n 'GRAM_PANCHAYAT',\n 'VILLAGE',\n 'URBAN_BODY',\n 'URBAN_WARD',\n] as const;\nexport const areaTypeSchema = z.enum(AREA_TYPE_VALUES);\nexport type AreaType = z.infer<typeof areaTypeSchema>;\n\n/**\n * The drawer's third step lists these, everything below a district that a\n * member can live in: Jansathi's `LOCALITY_TYPE_TO_ADMIN_KEY`, each one a\n * stored level. Jansathi's drawer also lists HAMLET and LOCALITY and then\n * refuses them on save (no stored level); here they are never offered.\n * RURAL_WARD is not offered either (Jansathi neither): it has no point.\n */\nexport const LOCALITY_AREA_TYPES = [\n 'SUB_DISTRICT',\n 'BLOCK',\n 'GRAM_PANCHAYAT',\n 'VILLAGE',\n 'URBAN_BODY',\n 'URBAN_WARD',\n] as const satisfies readonly AreaType[];\nexport type LocalityAreaType = (typeof LOCALITY_AREA_TYPES)[number];\n\n/** How a locality row names its level (Jansathi's `addressForm.areaTypes`). */\nexport const LOCALITY_TYPE_LABEL: Record<LocalityAreaType, string> = {\n SUB_DISTRICT: 'Tehsil',\n BLOCK: 'Block',\n GRAM_PANCHAYAT: 'Gram Panchayat',\n VILLAGE: 'Village',\n URBAN_BODY: 'Town or city',\n URBAN_WARD: 'Ward',\n};\n\n/**\n * An urban body's kind, as the area builder writes it (reform-backend's\n * `URBAN_BODY_TYPES`, LGD). Read leniently: a kind added there later shows as\n * \"Town or city\" rather than breaking the list.\n */\nexport const URBAN_BODY_TYPE_VALUES = [\n 'Municipal Corporation',\n 'Municipality',\n 'Notified Area Council',\n 'Town Panchayat',\n 'Nagar Panchayat',\n 'Municipal Council',\n 'NCT Municipal Council',\n 'City Municipal Council',\n 'Town Municipal Council',\n 'Cantonment Board',\n] as const;\n\n/** One area as a list row and as part of a place: what a client may show. */\nexport const areaOptionSchema = z.object({\n id: objectIdSchema,\n name: z.string().trim().min(1).max(AREA_NAME_MAX),\n type: areaTypeSchema,\n /** Only on an URBAN_BODY, when the tree knows its kind. */\n urbanBodyType: z.string().trim().min(1).max(AREA_NAME_MAX).optional(),\n /**\n * What a locality sits in, to tell same-named places apart (0.19.0, the\n * review: one district has 12 villages called \"Pali\", a town's wards are\n * named \"1\" to \"35\"): a ward's town, a village's or panchayat's tehsil or\n * block. Absent on states, districts and towns.\n */\n within: z.string().trim().min(1).max(AREA_NAME_MAX).optional(),\n});\nexport type AreaOption = z.infer<typeof areaOptionSchema>;\n\n/**\n * An area as a member reads it: a ward the tree names by its number alone\n * (\"25\") is \"Ward 25\"; anything else by its name.\n */\nexport function areaWords(area: Pick<AreaOption, 'name' | 'type'>): string {\n return area.type === 'URBAN_WARD' && /^\\d+$/.test(area.name.trim())\n ? `Ward ${area.name.trim()}`\n : area.name;\n}\n\n/** `GET /location/areas/states` and `/districts`: the whole list, sorted by name. */\nexport const areaListSchema = z.object({ items: z.array(areaOptionSchema) });\nexport type AreaList = z.infer<typeof areaListSchema>;\n\n/** `GET /location/areas/localities`: one page (`LOCALITY_PAGE_SIZE`), sorted by name. */\nexport const areaPageSchema = z.object({\n items: z.array(areaOptionSchema),\n hasMore: z.boolean(),\n});\nexport type AreaPage = z.infer<typeof areaPageSchema>;\n\n/** `GET /location/areas/districts?stateId=` */\nexport const districtsQuerySchema = z.object({ stateId: objectIdSchema });\nexport type DistrictsQuery = z.infer<typeof districtsQuerySchema>;\n\n/**\n * `GET /location/areas/localities?districtId=&q=&skip=`: every locality level\n * anywhere inside the district (a ward sits under its town, not under the\n * district). `q` matches anywhere in the name, as Jansathi's does; an emptied\n * box is \"no search\".\n */\nexport const localitiesQuerySchema = z.object({\n districtId: objectIdSchema,\n q: z.preprocess(emptyStringToUndefined, z.string().trim().max(AREA_SEARCH_MAX).optional()),\n skip: queryInt(0, LOCALITY_SKIP_MAX, 0),\n});\nexport type LocalitiesQuery = z.infer<typeof localitiesQuerySchema>;\nexport type LocalitiesQueryInput = z.input<typeof localitiesQuerySchema>;\n\n// ─── The declared location ───────────────────────────────────────────────────\n\n/** An area id a picker must have chosen: unchosen is worded as a choice. */\nconst chosenAreaId = (unchosen: string) =>\n z\n .string({ error: unchosen })\n .min(1, { error: unchosen, abort: true })\n .regex(/^[0-9a-f]{24}$/i, VALIDATION_MESSAGE.invalidId)\n .transform((v) => v.toLowerCase());\n\n/**\n * A location as it is WRITTEN (0.19.0): onboarding's finish and Me's personal\n * details. Ids only. `localityId: null` is the member's \"my locality is not\n * listed\": the whole district (the user's choice, 2026-10-21).\n *\n * The server refuses ids that are not one chain (a district outside the\n * state, a locality outside the district) and a state Founder Circle's list\n * does not name (`findIndiaState`).\n */\nexport const locationPickSchema = z\n .object({\n stateId: chosenAreaId(VALIDATION_MESSAGE.stateRequired),\n districtId: chosenAreaId(VALIDATION_MESSAGE.districtRequired),\n // Said explicitly: an id, or `null` for the whole district. Left out is a\n // choice not made (`localityRequired`), never Zod's own English.\n localityId: z\n .string({ error: VALIDATION_MESSAGE.localityRequired })\n .regex(/^[0-9a-f]{24}$/i, VALIDATION_MESSAGE.invalidId)\n .transform((v) => v.toLowerCase())\n .nullable(),\n })\n .strict();\nexport type LocationPick = z.infer<typeof locationPickSchema>;\n\n/**\n * A place with its names: the member's own location as Me reads it back, the\n * area GPS matched, and the onboarding draft. What the location field shows\n * (\"Locality, District, State\") and where the drawer reopens. Owner-only:\n * other members read `locationSchema` (city, state, country).\n */\nexport const placeSchema = z.object({\n state: areaOptionSchema,\n district: areaOptionSchema,\n /** `null`: the whole district. */\n locality: areaOptionSchema.nullable(),\n});\nexport type Place = z.infer<typeof placeSchema>;\n\n/** The pick a place is (what saving it sends). */\nexport function pickOfPlace(place: Place): LocationPick {\n return {\n stateId: place.state.id,\n districtId: place.district.id,\n localityId: place.locality?.id ?? null,\n };\n}\n\n/**\n * \"Locality, what it sits in, District, State\", the deepest first; a name\n * repeated is said once (\"Ward 25, Bassi, Jaipur, Rajasthan\"; \"Rajpur,\n * Dehradun, Uttarakhand\").\n */\nexport function placeWords(place: Place): string {\n const parts = [\n place.locality ? areaWords(place.locality) : undefined,\n place.locality?.within,\n place.district.name,\n place.state.name,\n ].filter((p): p is string => Boolean(p));\n return parts.filter((p, i) => parts.indexOf(p) === i).join(', ');\n}\n\n// ─── The GPS axis ────────────────────────────────────────────────────────────\n\n/**\n * `POST /me/geolocation`: one GPS fix, from the daily sync or a member's\n * \"Use my location\" (Jansathi's body). Plain degrees, accuracy in metres. A\n * fix coarser than `GEO_ACCURACY_MAX_M` is refused: it cannot name a place.\n */\nexport const geolocationFixSchema = z\n .object({\n latitude: z.number().min(-90).max(90),\n longitude: z.number().min(-180).max(180),\n accuracy: z.number().min(0).max(GEO_ACCURACY_MAX_M),\n })\n .strict();\nexport type GeolocationFix = z.infer<typeof geolocationFixSchema>;\n\n/**\n * The answer: the place the fix falls in, for \"Use my location\" to put in the\n * field (the member still saves it), or `null` when the area tree has no\n * confident match (outside India, or nothing within reach). The daily sync\n * ignores it. Never the coordinates themselves.\n */\nexport const geolocationSyncResultSchema = z.object({\n synced: z.literal(true),\n match: placeSchema.nullable(),\n});\nexport type GeolocationSyncResult = z.infer<typeof geolocationSyncResultSchema>;\n\n/** A location history row: a raw GPS fix, or the area a fix or a pick named. The backend's storage's own values, from here like every other list (0.20.0). */\nexport const LOCATION_HISTORY_KIND_VALUES = ['fix', 'area'] as const;\nexport const locationHistoryKindSchema = z.enum(LOCATION_HISTORY_KIND_VALUES);\nexport type LocationHistoryKind = z.infer<typeof locationHistoryKindSchema>;\n\n// ─── The member's own control (0.19.0) ───────────────────────────────────────\n\n/**\n * Whether Founder Circle keeps the member's positions (the user, 2026-10-22:\n * \"Add stop + clear\", India's DPDP right to withdraw consent). On by default,\n * as Jansathi; it only matters once the browser allows location at all.\n *\n * OFF: the server keeps NO position: a fix (the daily check or a tap on \"Use\n * my location\") is still answered with the place it falls in, so the field can\n * be filled, but nothing of it is stored, and no history row is written, not\n * even for a saved place. The web stops the daily check. The member's chosen\n * place is not touched: removing it is the location field's Remove.\n *\n * `GET /api/v1/me/location-settings`, and `PATCH` with\n * `updateLocationSettingsBodySchema`, both answer this.\n */\nexport const locationSettingsSchema = z.object({ dailyLocation: z.boolean() });\nexport type LocationSettings = z.infer<typeof locationSettingsSchema>;\n\n/** `PATCH /api/v1/me/location-settings` */\nexport const updateLocationSettingsBodySchema = z.object({ dailyLocation: z.boolean() }).strict();\nexport type UpdateLocationSettingsBody = z.infer<typeof updateLocationSettingsBodySchema>;\n\n/**\n * `DELETE /api/v1/me/location-history`: every position and area the member's\n * device sent, and the position kept with their saved place, deleted at once.\n * The chosen place itself (what others see as city and state) stays.\n */\nexport const clearLocationHistoryResultSchema = z.object({ cleared: z.literal(true) });\nexport type ClearLocationHistoryResult = z.infer<typeof clearLocationHistoryResultSchema>;\n"]}
|
|
@@ -48,6 +48,28 @@ export declare const mediaPurposeSchema: z.ZodEnum<{
|
|
|
48
48
|
community_icon: "community_icon";
|
|
49
49
|
}>;
|
|
50
50
|
export type MediaPurpose = z.infer<typeof mediaPurposeSchema>;
|
|
51
|
+
/**
|
|
52
|
+
* An upload's life (pending, processing, ready, attached, detached) and what
|
|
53
|
+
* it is attached to. The backend's storage's own values, from here like every other list (0.20.0).
|
|
54
|
+
*/
|
|
55
|
+
export declare const MEDIA_STATUS_VALUES: readonly ["pending", "processing", "ready", "attached", "detached"];
|
|
56
|
+
export declare const mediaStatusSchema: z.ZodEnum<{
|
|
57
|
+
pending: "pending";
|
|
58
|
+
processing: "processing";
|
|
59
|
+
ready: "ready";
|
|
60
|
+
attached: "attached";
|
|
61
|
+
detached: "detached";
|
|
62
|
+
}>;
|
|
63
|
+
export type MediaStatus = z.infer<typeof mediaStatusSchema>;
|
|
64
|
+
export declare const MEDIA_ATTACHMENT_KIND_VALUES: readonly ["post", "avatar", "product", "community", "onboarding"];
|
|
65
|
+
export declare const mediaAttachmentKindSchema: z.ZodEnum<{
|
|
66
|
+
onboarding: "onboarding";
|
|
67
|
+
product: "product";
|
|
68
|
+
community: "community";
|
|
69
|
+
post: "post";
|
|
70
|
+
avatar: "avatar";
|
|
71
|
+
}>;
|
|
72
|
+
export type MediaAttachmentKind = z.infer<typeof mediaAttachmentKindSchema>;
|
|
51
73
|
/**
|
|
52
74
|
* The content types a member may upload. Every one is decoded and re-encoded
|
|
53
75
|
* to WebP before anyone sees it, so this list is about what the server can
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../../../src/domains/media/media.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;GAGG;AACH,eAAO,MAAM,oBAAoB,qFAMvB,CAAC;AACX,eAAO,MAAM,kBAAkB;;;;;;EAA+B,CAAC;AAC/D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D;;;;;;;;GAQG;AACH,eAAO,MAAM,0BAA0B,iEAK7B,CAAC;AACX,eAAO,MAAM,4BAA4B;;;;;EAAqC,CAAC;AAC/E,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAElF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,WAAW;;;;;;CAMoC,CAAC;AAE7D,kEAAkE;AAClE,eAAO,MAAM,kBAAkB,QAE9B,CAAC;AAEF,mCAAmC;AACnC,eAAO,MAAM,kBAAkB;;;;iBAI7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;iBAO3B,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;iBAG9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../../../src/domains/media/media.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;GAGG;AACH,eAAO,MAAM,oBAAoB,qFAMvB,CAAC;AACX,eAAO,MAAM,kBAAkB;;;;;;EAA+B,CAAC;AAC/D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D;;;GAGG;AACH,eAAO,MAAM,mBAAmB,qEAMtB,CAAC;AACX,eAAO,MAAM,iBAAiB;;;;;;EAA8B,CAAC;AAC7D,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC5D,eAAO,MAAM,4BAA4B,mEAM/B,CAAC;AACX,eAAO,MAAM,yBAAyB;;;;;;EAAuC,CAAC;AAC9E,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E;;;;;;;;GAQG;AACH,eAAO,MAAM,0BAA0B,iEAK7B,CAAC;AACX,eAAO,MAAM,4BAA4B;;;;;EAAqC,CAAC;AAC/E,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAElF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,WAAW;;;;;;CAMoC,CAAC;AAE7D,kEAAkE;AAClE,eAAO,MAAM,kBAAkB,QAE9B,CAAC;AAEF,mCAAmC;AACnC,eAAO,MAAM,kBAAkB;;;;iBAI7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;iBAO3B,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;iBAG9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC"}
|
|
@@ -48,6 +48,26 @@ export const MEDIA_PURPOSE_VALUES = [
|
|
|
48
48
|
'community_icon',
|
|
49
49
|
];
|
|
50
50
|
export const mediaPurposeSchema = z.enum(MEDIA_PURPOSE_VALUES);
|
|
51
|
+
/**
|
|
52
|
+
* An upload's life (pending, processing, ready, attached, detached) and what
|
|
53
|
+
* it is attached to. The backend's storage's own values, from here like every other list (0.20.0).
|
|
54
|
+
*/
|
|
55
|
+
export const MEDIA_STATUS_VALUES = [
|
|
56
|
+
'pending',
|
|
57
|
+
'processing',
|
|
58
|
+
'ready',
|
|
59
|
+
'attached',
|
|
60
|
+
'detached',
|
|
61
|
+
];
|
|
62
|
+
export const mediaStatusSchema = z.enum(MEDIA_STATUS_VALUES);
|
|
63
|
+
export const MEDIA_ATTACHMENT_KIND_VALUES = [
|
|
64
|
+
'post',
|
|
65
|
+
'avatar',
|
|
66
|
+
'product',
|
|
67
|
+
'community',
|
|
68
|
+
'onboarding',
|
|
69
|
+
];
|
|
70
|
+
export const mediaAttachmentKindSchema = z.enum(MEDIA_ATTACHMENT_KIND_VALUES);
|
|
51
71
|
/**
|
|
52
72
|
* The content types a member may upload. Every one is decoded and re-encoded
|
|
53
73
|
* to WebP before anyone sees it, so this list is about what the server can
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media.js","sourceRoot":"","sources":["../../../src/domains/media/media.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE7D;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,MAAM;IACN,QAAQ;IACR,cAAc;IACd,oBAAoB;IACpB,gBAAgB;CACR,CAAC;AACX,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;AAG/D;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG;IACxC,YAAY;IACZ,WAAW;IACX,YAAY;IACZ,WAAW;CACH,CAAC;AACX,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;AAG/E;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,IAAI,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC;IACvB,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC;IAClB,YAAY,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC;IAC5B,kBAAkB,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC;IACrC,cAAc,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC;CAC4B,CAAC;AAE7D,kEAAkE;AAClE,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC,GAAG,CACxC,GAAG,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAC3D,CAAC;AAEF,mCAAmC;AACnC,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IAClC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACnC,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE;CACb,CAAC,CAAC;AAGH;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IACd,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE;IACZ,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IAClC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACnC,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC;IACpE,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC,QAAQ,EAAE;CAC9D,CAAC,CAAC;AAGH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,gBAAgB,CAAC,MAAM,CAAC;IACzD,OAAO,EAAE,kBAAkB;IAC3B,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE;CAC5B,CAAC,CAAC","sourcesContent":["/**\n * founder-schema — Media: what an uploaded image becomes\n *\n * THE PIPELINE, end to end (0.5.0):\n *\n * 1. The client shrinks the photo (long edge at most the largest size in\n * `MEDIA_SIZES`, WebP or JPEG) and asks for an upload slot:\n * `POST /api/v1/uploads/presign` with a purpose, a content type and the\n * exact byte count (`uploadPresignBodySchema`).\n * 2. It PUTs the bytes straight to storage, with the headers it was given.\n * Nothing passes through the API server.\n * 3. It calls `POST /api/v1/uploads/:mediaId/complete`. The server checks the\n * file, decodes it fully, applies the photo's rotation, REMOVES EVERY\n * PIECE OF METADATA (GPS position, camera, timestamps), re-encodes it as\n * WebP at each size in `MEDIA_SIZES`, deletes the raw upload, and answers\n * with `uploadedMediaSchema`.\n * 4. A post (`attachedMediaSchema`), the avatar (`setAvatarBodySchema`,\n * onboarding's `avatarMediaId`), or a product's logo and screenshots\n * (`setProductLogoBodySchema`, `setProductScreenshotsBodySchema`, 0.6.0)\n * takes the media id. One upload is used once, by its owner, for the\n * purpose it was uploaded for.\n *\n * A product's images belong to the PRODUCT, not to whoever on the team uploaded\n * them: they stay when that person leaves or deletes their account, and go when\n * the product is deleted.\n *\n * What nobody sees: the raw upload. It sits in a folder the CDN cannot reach\n * and is deleted when processing finishes, or by storage itself after a day.\n * Only the re-encoded sizes are ever served.\n *\n * An image never attached is deleted after `MEDIA_UNATTACHED_TTL_HOURS`; a\n * deleted post's images, a replaced avatar, and an erased account's images are\n * deleted from storage by the server, so a copied link stops working.\n *\n * @module founder-schema/domains/media\n */\n\nimport { z } from 'zod';\nimport { MEDIA_PLACEHOLDER_MAX } from '../../core/limits.js';\n\n/**\n * What an upload is for. It decides the sizes made and where it may be attached.\n * Append only: every stored upload names one.\n */\nexport const MEDIA_PURPOSE_VALUES = [\n 'post',\n 'avatar',\n 'product_logo',\n 'product_screenshot',\n 'community_icon',\n] as const;\nexport const mediaPurposeSchema = z.enum(MEDIA_PURPOSE_VALUES);\nexport type MediaPurpose = z.infer<typeof mediaPurposeSchema>;\n\n/**\n * The content types a member may upload. Every one is decoded and re-encoded\n * to WebP before anyone sees it, so this list is about what the server can\n * DECODE safely, not what it serves.\n *\n * Not HEIC: an iPhone's photo picker converts HEIC to JPEG for the web, and a\n * client that shrinks the photo first re-encodes it anyway. SVG never: it is a\n * document that can carry script, not an image.\n */\nexport const MEDIA_UPLOAD_CONTENT_TYPES = [\n 'image/jpeg',\n 'image/png',\n 'image/webp',\n 'image/gif',\n] as const;\nexport const mediaUploadContentTypeSchema = z.enum(MEDIA_UPLOAD_CONTENT_TYPES);\nexport type MediaUploadContentType = z.infer<typeof mediaUploadContentTypeSchema>;\n\n/**\n * The sizes each purpose is stored at, smallest first.\n *\n * - post: the LONG edge, aspect kept. 540 for a feed card on a plain screen,\n * 1080 for the same card on a phone's dense screen, 2048 for the full-screen\n * view. An image smaller than a size is never enlarged, so a small image\n * has fewer sizes.\n * - avatar: a SQUARE, cropped around the most interesting region (usually a\n * face). 192 for every small circle (bylines, lists, chat) up to 64 CSS\n * pixels on a 3x screen, 512 for a profile header.\n * - product_logo: a SQUARE, but NEVER CROPPED: the whole logo is fitted\n * inside it and the rest is transparent, because cropping a wordmark the\n * way an avatar is cropped cuts letters off. Transparency is kept. 96 for\n * the product chip on a post (32 CSS pixels at 3x), 256 for a Discover card,\n * 512 for the product page.\n * - product_screenshot: as a post's image (long edge, aspect kept); a phone\n * screenshot is tall, a desktop one wide. An animated GIF (a short demo)\n * stays animated, within the same limits as a post's.\n * - community_icon (0.9.0): as a product logo, a SQUARE fitted, never\n * cropped. 96 for the \"in <community>\" chip, 256 for a card, 512 for the\n * community page. It belongs to the community, not its uploader.\n *\n * A client shrinks a photo to the largest size here before uploading: anything\n * bigger is thrown away by the server anyway, and costs the member data.\n */\nexport const MEDIA_SIZES = {\n post: [540, 1080, 2048],\n avatar: [192, 512],\n product_logo: [96, 256, 512],\n product_screenshot: [540, 1080, 2048],\n community_icon: [96, 256, 512],\n} as const satisfies Record<MediaPurpose, readonly number[]>;\n\n/** The most sizes any purpose stores: the bound on `variants`. */\nexport const MEDIA_VARIANTS_MAX = Math.max(\n ...Object.values(MEDIA_SIZES).map((sizes) => sizes.length),\n);\n\n/** One stored size of an image. */\nexport const mediaVariantSchema = z.object({\n width: z.number().int().positive(),\n height: z.number().int().positive(),\n url: z.url(),\n});\nexport type MediaVariant = z.infer<typeof mediaVariantSchema>;\n\n/**\n * A processed image, as every read shows it.\n *\n * `variants` is a ready-made `srcset`: smallest first, each with its own pixel\n * width. `url`, `width` and `height` repeat the largest one, so a client that\n * ignores `variants` still renders correctly and can reserve the image's box\n * from the aspect ratio before it loads (no layout shift).\n *\n * `placeholder` is a ThumbHash (base64): a ~30 character blurred preview a\n * client can paint instantly while the image loads.\n *\n * URLs point at our media CDN. They come from our own pipeline, never from a\n * member, so a plain `z.url()`.\n */\nexport const mediaImageSchema = z.object({\n id: z.string(),\n url: z.url(),\n width: z.number().int().positive(),\n height: z.number().int().positive(),\n variants: z.array(mediaVariantSchema).min(1).max(MEDIA_VARIANTS_MAX),\n placeholder: z.string().max(MEDIA_PLACEHOLDER_MAX).optional(),\n});\nexport type MediaImage = z.infer<typeof mediaImageSchema>;\n\n/**\n * `POST /api/v1/uploads/:mediaId/complete` answers with this: the image as it\n * will appear, not yet attached to anything.\n *\n * `expiresAt` is when the server deletes it if it is still unattached\n * (`MEDIA_UNATTACHED_TTL_HOURS` after the slot was issued). A composer left\n * open longer than that must upload again.\n */\nexport const uploadedMediaSchema = mediaImageSchema.extend({\n purpose: mediaPurposeSchema,\n expiresAt: z.iso.datetime(),\n});\nexport type UploadedMedia = z.infer<typeof uploadedMediaSchema>;\n"]}
|
|
1
|
+
{"version":3,"file":"media.js","sourceRoot":"","sources":["../../../src/domains/media/media.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema — Media: what an uploaded image becomes\n *\n * THE PIPELINE, end to end (0.5.0):\n *\n * 1. The client shrinks the photo (long edge at most the largest size in\n * `MEDIA_SIZES`, WebP or JPEG) and asks for an upload slot:\n * `POST /api/v1/uploads/presign` with a purpose, a content type and the\n * exact byte count (`uploadPresignBodySchema`).\n * 2. It PUTs the bytes straight to storage, with the headers it was given.\n * Nothing passes through the API server.\n * 3. It calls `POST /api/v1/uploads/:mediaId/complete`. The server checks the\n * file, decodes it fully, applies the photo's rotation, REMOVES EVERY\n * PIECE OF METADATA (GPS position, camera, timestamps), re-encodes it as\n * WebP at each size in `MEDIA_SIZES`, deletes the raw upload, and answers\n * with `uploadedMediaSchema`.\n * 4. A post (`attachedMediaSchema`), the avatar (`setAvatarBodySchema`,\n * onboarding's `avatarMediaId`), or a product's logo and screenshots\n * (`setProductLogoBodySchema`, `setProductScreenshotsBodySchema`, 0.6.0)\n * takes the media id. One upload is used once, by its owner, for the\n * purpose it was uploaded for.\n *\n * A product's images belong to the PRODUCT, not to whoever on the team uploaded\n * them: they stay when that person leaves or deletes their account, and go when\n * the product is deleted.\n *\n * What nobody sees: the raw upload. It sits in a folder the CDN cannot reach\n * and is deleted when processing finishes, or by storage itself after a day.\n * Only the re-encoded sizes are ever served.\n *\n * An image never attached is deleted after `MEDIA_UNATTACHED_TTL_HOURS`; a\n * deleted post's images, a replaced avatar, and an erased account's images are\n * deleted from storage by the server, so a copied link stops working.\n *\n * @module founder-schema/domains/media\n */\n\nimport { z } from 'zod';\nimport { MEDIA_PLACEHOLDER_MAX } from '../../core/limits.js';\n\n/**\n * What an upload is for. It decides the sizes made and where it may be attached.\n * Append only: every stored upload names one.\n */\nexport const MEDIA_PURPOSE_VALUES = [\n 'post',\n 'avatar',\n 'product_logo',\n 'product_screenshot',\n 'community_icon',\n] as const;\nexport const mediaPurposeSchema = z.enum(MEDIA_PURPOSE_VALUES);\nexport type MediaPurpose = z.infer<typeof mediaPurposeSchema>;\n\n/**\n * An upload's life (pending, processing, ready, attached, detached) and what\n * it is attached to. The backend's storage's own values, from here like every other list (0.20.0).\n */\nexport const MEDIA_STATUS_VALUES = [\n 'pending',\n 'processing',\n 'ready',\n 'attached',\n 'detached',\n] as const;\nexport const mediaStatusSchema = z.enum(MEDIA_STATUS_VALUES);\nexport type MediaStatus = z.infer<typeof mediaStatusSchema>;\nexport const MEDIA_ATTACHMENT_KIND_VALUES = [\n 'post',\n 'avatar',\n 'product',\n 'community',\n 'onboarding',\n] as const;\nexport const mediaAttachmentKindSchema = z.enum(MEDIA_ATTACHMENT_KIND_VALUES);\nexport type MediaAttachmentKind = z.infer<typeof mediaAttachmentKindSchema>;\n\n/**\n * The content types a member may upload. Every one is decoded and re-encoded\n * to WebP before anyone sees it, so this list is about what the server can\n * DECODE safely, not what it serves.\n *\n * Not HEIC: an iPhone's photo picker converts HEIC to JPEG for the web, and a\n * client that shrinks the photo first re-encodes it anyway. SVG never: it is a\n * document that can carry script, not an image.\n */\nexport const MEDIA_UPLOAD_CONTENT_TYPES = [\n 'image/jpeg',\n 'image/png',\n 'image/webp',\n 'image/gif',\n] as const;\nexport const mediaUploadContentTypeSchema = z.enum(MEDIA_UPLOAD_CONTENT_TYPES);\nexport type MediaUploadContentType = z.infer<typeof mediaUploadContentTypeSchema>;\n\n/**\n * The sizes each purpose is stored at, smallest first.\n *\n * - post: the LONG edge, aspect kept. 540 for a feed card on a plain screen,\n * 1080 for the same card on a phone's dense screen, 2048 for the full-screen\n * view. An image smaller than a size is never enlarged, so a small image\n * has fewer sizes.\n * - avatar: a SQUARE, cropped around the most interesting region (usually a\n * face). 192 for every small circle (bylines, lists, chat) up to 64 CSS\n * pixels on a 3x screen, 512 for a profile header.\n * - product_logo: a SQUARE, but NEVER CROPPED: the whole logo is fitted\n * inside it and the rest is transparent, because cropping a wordmark the\n * way an avatar is cropped cuts letters off. Transparency is kept. 96 for\n * the product chip on a post (32 CSS pixels at 3x), 256 for a Discover card,\n * 512 for the product page.\n * - product_screenshot: as a post's image (long edge, aspect kept); a phone\n * screenshot is tall, a desktop one wide. An animated GIF (a short demo)\n * stays animated, within the same limits as a post's.\n * - community_icon (0.9.0): as a product logo, a SQUARE fitted, never\n * cropped. 96 for the \"in <community>\" chip, 256 for a card, 512 for the\n * community page. It belongs to the community, not its uploader.\n *\n * A client shrinks a photo to the largest size here before uploading: anything\n * bigger is thrown away by the server anyway, and costs the member data.\n */\nexport const MEDIA_SIZES = {\n post: [540, 1080, 2048],\n avatar: [192, 512],\n product_logo: [96, 256, 512],\n product_screenshot: [540, 1080, 2048],\n community_icon: [96, 256, 512],\n} as const satisfies Record<MediaPurpose, readonly number[]>;\n\n/** The most sizes any purpose stores: the bound on `variants`. */\nexport const MEDIA_VARIANTS_MAX = Math.max(\n ...Object.values(MEDIA_SIZES).map((sizes) => sizes.length),\n);\n\n/** One stored size of an image. */\nexport const mediaVariantSchema = z.object({\n width: z.number().int().positive(),\n height: z.number().int().positive(),\n url: z.url(),\n});\nexport type MediaVariant = z.infer<typeof mediaVariantSchema>;\n\n/**\n * A processed image, as every read shows it.\n *\n * `variants` is a ready-made `srcset`: smallest first, each with its own pixel\n * width. `url`, `width` and `height` repeat the largest one, so a client that\n * ignores `variants` still renders correctly and can reserve the image's box\n * from the aspect ratio before it loads (no layout shift).\n *\n * `placeholder` is a ThumbHash (base64): a ~30 character blurred preview a\n * client can paint instantly while the image loads.\n *\n * URLs point at our media CDN. They come from our own pipeline, never from a\n * member, so a plain `z.url()`.\n */\nexport const mediaImageSchema = z.object({\n id: z.string(),\n url: z.url(),\n width: z.number().int().positive(),\n height: z.number().int().positive(),\n variants: z.array(mediaVariantSchema).min(1).max(MEDIA_VARIANTS_MAX),\n placeholder: z.string().max(MEDIA_PLACEHOLDER_MAX).optional(),\n});\nexport type MediaImage = z.infer<typeof mediaImageSchema>;\n\n/**\n * `POST /api/v1/uploads/:mediaId/complete` answers with this: the image as it\n * will appear, not yet attached to anything.\n *\n * `expiresAt` is when the server deletes it if it is still unattached\n * (`MEDIA_UNATTACHED_TTL_HOURS` after the slot was issued). A composer left\n * open longer than that must upload again.\n */\nexport const uploadedMediaSchema = mediaImageSchema.extend({\n purpose: mediaPurposeSchema,\n expiresAt: z.iso.datetime(),\n});\nexport type UploadedMedia = z.infer<typeof uploadedMediaSchema>;\n"]}
|