founder-schema 0.19.0 → 0.21.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 +27 -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 +42 -1
- package/dist/domains/membership/membership.d.ts.map +1 -1
- package/dist/domains/membership/membership.js +42 -1
- 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
package/dist/onboarding.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"onboarding.js","sourceRoot":"","sources":["../src/onboarding.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema — Onboarding: steps, enums, and the two shapes of the answers\n *\n * ONE LIST DEFINES THE FLOW, AND IT IS DERIVED. Each step's fields are a pick\n * of the finish body (`ONBOARDING_STEP_SCHEMAS`), and `FOUNDER_ONBOARDING_STEPS`\n * is built FROM those picks: its `fields`, and even whether a step may be\n * skipped, come from the schema rather than from a second hand-written list.\n * GymMonk arrived at one list after keeping two in sync by hand, where a step\n * could exist with no title or, worse, no validation gate at all; until 0.2.0\n * this file still wrote the field lists twice.\n *\n * TWO SHAPES, ONE SET OF RULES. A half-filled form legitimately holds `null`\n * for a choice nobody has made yet; the finalize body only ever sees completed\n * answers. So the rules are written once and expressed over both shapes:\n * `onboardingFormSchema` for work in progress and `completeOnboardingBodySchema`\n * for the end. Letting those two drift is how a wizard says a step is optional\n * and then answers 422 at Finish.\n *\n * NO COPY IN HERE. Step titles, help lines and topic category names are\n * translation KEYS (`titleKey`, `helpKey`, `labelKey`), Jansathi's rule for\n * every config string: an English sentence in a shared package is a sentence\n * every other language has to match by wording.\n *\n * @module founder-schema/onboarding\n */\n\nimport { z } from 'zod';\nimport {\n GENDER_VALUES,\n GOAL_VALUES,\n genderSchema,\n goalSchema,\n INDUSTRY_VALUES,\n industrySchema,\n locationSchema,\n optionalTrimmedString,\n phoneSchema,\n SKILL_VALUES,\n STARTUP_STAGE_VALUES,\n skillSchema,\n startupStageSchema,\n} from './common.js';\nimport { founderLinksSchema, ventureSchema } from './founder-profile.js';\nimport {\n ABOUT_MAX,\n CITY_MAX,\n COUNTRY_MAX,\n DISPLAY_NAME_MAX,\n GOALS_MIN,\n HEADLINE_MAX,\n LINK_URL_MAX,\n SKILLS_MAX,\n STATE_MAX,\n TEAM_SIZE_MAX,\n VENTURE_NAME_MAX,\n} from './limits.js';\nimport { uploadKeySchema } from './uploads.js';\nimport { VALIDATION_MESSAGE } from './validation.js';\n\n// ─── Age ─────────────────────────────────────────────────────────────────────\n\n/**\n * 18, ruled by the user 2026-09-21. Under-18 would pull in parental-consent\n * duties under the DPDP Act, which is a different and larger problem than a\n * validation rule.\n *\n * ⚠️ The SERVER refuses at finalize. This schema exists so the member sees a\n * useful message; it is not the gate.\n */\nexport const MIN_AGE_YEARS = 18;\n/** A sanity ceiling, not a product rule. Catches a mistyped year. */\nexport const MAX_AGE_YEARS = 120;\n\n/**\n * Whole years completed, by the calendar.\n *\n * ⚠️ NOT `elapsedMs / (365.25 * day)`, which is what the sibling product uses.\n * That approximation reads 17.9986 at the start of somebody's eighteenth\n * birthday and only crosses 18 about halfway through the day, so it refuses\n * them for the half of their birthday they are most likely to be signing up on.\n * Measured: it disagrees with the calendar on all 400 of the next 400 days.\n * An age gate is exactly the place an approximation is not good enough.\n */\nexport function ageInYears(isoDate: string, now: Date = new Date()): number {\n // ⚠️ A STRING, `YYYY-MM-DD`, not a `Date`. This is a wire contract, so it\n // takes the wire type. The backend is the obvious caller and the backend\n // holds a `Date`, so it must format first; passing the Date straight in used\n // to fail with `isoDate.split is not a function`, which says nothing useful.\n // Naming the fix costs one line and saves the next person the detour.\n if (typeof isoDate !== 'string') {\n throw new TypeError(\n 'ageInYears expects a YYYY-MM-DD string, not a Date. Format it first, e.g. date.toISOString().slice(0, 10).',\n );\n }\n const [y, m, d] = isoDate.split('-').map(Number) as [number, number, number];\n let age = now.getUTCFullYear() - y;\n const monthNow = now.getUTCMonth() + 1;\n if (monthNow < m || (monthNow === m && now.getUTCDate() < d)) age -= 1;\n return age;\n}\n\n/** A calendar date with no time component, `YYYY-MM-DD`. */\nexport const isoDateSchema = z\n .string()\n .trim()\n .regex(/^\\d{4}-\\d{2}-\\d{2}$/, VALIDATION_MESSAGE.dateInvalid);\nexport type IsoDate = z.infer<typeof isoDateSchema>;\n\n/** True only for a date that exists. Rejects 2026-02-30 and 2026-13-01. */\nfunction isRealDate(iso: string): boolean {\n const [y, m, d] = iso.split('-').map(Number) as [number, number, number];\n if (m < 1 || m > 12 || d < 1) return false;\n const asDate = new Date(Date.UTC(y, m - 1, d));\n return (\n asDate.getUTCFullYear() === y && asDate.getUTCMonth() === m - 1 && asDate.getUTCDate() === d\n );\n}\n\n/**\n * Date of birth, with the age gate applied per parse.\n *\n * ⚠️ THE CLIENT IS NOT THE GATE. This schema exists so the member sees a useful\n * message, and the server must refuse the same dates at finalize regardless.\n * The sibling dating product validates the identical rule on its client and\n * still shipped a defect where an under-age date of birth produced a live,\n * discoverable profile, because the client was the only thing checking.\n */\nexport const dateOfBirthSchema = isoDateSchema\n .refine(isRealDate, { error: VALIDATION_MESSAGE.dobInvalid })\n .refine((v) => ageInYears(v) >= MIN_AGE_YEARS, { error: VALIDATION_MESSAGE.dobUnderAge })\n .refine((v) => ageInYears(v) <= MAX_AGE_YEARS, { error: VALIDATION_MESSAGE.dobTooOld });\nexport type DateOfBirth = z.infer<typeof dateOfBirthSchema>;\n\n// ─── Business ────────────────────────────────────────────────────────────────\n\nexport const BUSINESS_TYPE_VALUES = ['startup', 'sme', 'enterprise'] as const;\nexport const businessTypeSchema = z.enum(BUSINESS_TYPE_VALUES);\nexport type BusinessType = z.infer<typeof businessTypeSchema>;\n\n// ─── Topics: the shared vocabulary ───────────────────────────────────────────────────────────────\n\n/**\n * ONE vocabulary, used TWICE: a member picks these as their `interests`, and a\n * post carries them as its `topics`. That is what makes relevance an index\n * lookup rather than a guess, and it is why this list is not called \"interests\"\n * - naming it for one of its two jobs would have invited a second list for the\n * other, which is the mistake this package has already made once with goals.\n * (User's ruling via hary, 2026-09-21.)\n *\n * Deliberately DISTINCT from two neighbours that will otherwise collect the\n * same words:\n *\n * industry what your company does\n * skills what you can do\n * interests what you want to read about\n *\n * If the copy does not keep them apart, members fill all three identically and\n * every one of them stops telling us anything.\n *\n * A closed list, consistent with industry, skill and stage, for the reason\n * already written beside those: free text means two people typing \"growth\" and\n * \"Growth Marketing\" never match, and matching is the entire point. The cost is\n * that adding an interest needs a publish.\n *\n * Tuned down from the dating product's 246, which is mostly dating material.\n * Values reuse its exact strings wherever one existed, so its Hindi\n * translations can be lifted across.\n *\n * ⚠️ Stored per member. Append only; removing a value orphans data.\n */\nexport const TOPIC_VALUES = [\n // Career growth\n 'mentoring',\n 'leadership',\n 'networking',\n 'public_speaking',\n 'teaching',\n 'hiring_talent',\n 'personal_branding',\n 'career_transition',\n 'remote_work',\n // Innovation and tech\n 'ai_ml',\n 'blockchain',\n 'crypto_web3',\n 'robotics',\n 'data_science',\n 'cybersecurity',\n 'game_dev',\n 'ux_design',\n 'app_development',\n 'open_source',\n 'electronics',\n 'space_exploration',\n 'green_tech',\n 'deeptech',\n 'no_code',\n // Business\n 'entrepreneurship',\n 'investing',\n 'stock_trading',\n 'personal_finance',\n 'fundraising',\n 'marketing',\n 'sales',\n 'consulting',\n 'ecommerce',\n 'retail',\n 'real_estate',\n 'supply_chain',\n 'product_management',\n 'growth',\n 'b2b_saas',\n 'd2c',\n // Personal development\n 'meditation',\n 'journaling',\n 'self_improvement',\n 'productivity',\n 'reading',\n 'online_courses',\n 'philosophy',\n 'psychology',\n 'therapy',\n 'sleep_optimization',\n 'gym',\n 'yoga',\n // Creative\n 'photography',\n 'writing',\n 'graphic_design',\n 'filmmaking',\n 'digital_art',\n 'content_creation',\n 'blogging',\n 'vlogging',\n 'podcasts',\n 'animation',\n 'music_production',\n // Impact and community\n 'volunteering',\n 'ngo_work',\n 'environmental',\n 'women_empowerment',\n 'community_service',\n 'social_enterprise',\n 'education_access',\n] as const;\nexport const topicSchema = z.enum(TOPIC_VALUES);\nexport type Topic = z.infer<typeof topicSchema>;\n\n/**\n * The grouping the picker renders.\n *\n * ⚠️ `TOPIC_VALUES` above is the single source of truth and this is typed\n * against it, so a typo here is a compile error rather than a chip that renders\n * and can never be selected. The dating product keeps a categorised list and a\n * flat list side by side and they have already drifted apart by two values;\n * a test in this package asserts these two agree exactly, in both directions.\n */\nexport const TOPIC_CATEGORY_KEYS = [\n 'career',\n 'tech',\n 'business',\n 'growth',\n 'creative',\n 'impact',\n] as const;\nexport type TopicCategoryKey = (typeof TOPIC_CATEGORY_KEYS)[number];\n\n/**\n * ⚠ TRANSLATION KEY SHAPE, for whoever builds the picker: `topics.category.<key>`\n * for a category and `topics.value.<topic>` for a topic. The dating product's\n * interests JSON is keyed by CATEGORY NAME instead, so all five of its label\n * lookups miss and every chip renders its raw English in every locale, with a\n * fully translated Hindi file sitting unreachable.\n */\nexport interface TopicCategory {\n key: TopicCategoryKey;\n /** A translation key. The English lives in the client's message files, not here. */\n labelKey: `topics.category.${TopicCategoryKey}`;\n values: readonly Topic[];\n}\n\nconst topicCategory = (key: TopicCategoryKey, values: readonly Topic[]): TopicCategory => ({\n key,\n labelKey: `topics.category.${key}`,\n values,\n});\n\nexport const TOPIC_CATEGORIES: readonly TopicCategory[] = [\n topicCategory('career', [\n 'mentoring',\n 'leadership',\n 'networking',\n 'public_speaking',\n 'teaching',\n 'hiring_talent',\n 'personal_branding',\n 'career_transition',\n 'remote_work',\n ]),\n topicCategory('tech', [\n 'ai_ml',\n 'blockchain',\n 'crypto_web3',\n 'robotics',\n 'data_science',\n 'cybersecurity',\n 'game_dev',\n 'ux_design',\n 'app_development',\n 'open_source',\n 'electronics',\n 'space_exploration',\n 'green_tech',\n 'deeptech',\n 'no_code',\n ]),\n topicCategory('business', [\n 'entrepreneurship',\n 'investing',\n 'stock_trading',\n 'personal_finance',\n 'fundraising',\n 'marketing',\n 'sales',\n 'consulting',\n 'ecommerce',\n 'retail',\n 'real_estate',\n 'supply_chain',\n 'product_management',\n 'growth',\n 'b2b_saas',\n 'd2c',\n ]),\n topicCategory('growth', [\n 'meditation',\n 'journaling',\n 'self_improvement',\n 'productivity',\n 'reading',\n 'online_courses',\n 'philosophy',\n 'psychology',\n 'therapy',\n 'sleep_optimization',\n 'gym',\n 'yoga',\n ]),\n topicCategory('creative', [\n 'photography',\n 'writing',\n 'graphic_design',\n 'filmmaking',\n 'digital_art',\n 'content_creation',\n 'blogging',\n 'vlogging',\n 'podcasts',\n 'animation',\n 'music_production',\n ]),\n topicCategory('impact', [\n 'volunteering',\n 'ngo_work',\n 'environmental',\n 'women_empowerment',\n 'community_service',\n 'social_enterprise',\n 'education_access',\n ]),\n];\n\n/**\n * How many a member must pick. A minimum exists because these drive feed\n * relevance and zero of them means no relevance at all.\n *\n * ⚠️ If 3 turns out to be hard to reach against this list, that is a signal\n * about the LIST, not about the minimum. The sibling product enforces no\n * minimum at all, and its own field config says \"pick up to 10\" while every\n * screen and schema enforces 20.\n */\nexport const INTERESTS_MIN = 3;\nexport const INTERESTS_MAX = 10;\n\n// ─── The step keys ───────────────────────────────────────────────────────────\n\n/**\n * The step keys, in order, as a literal union, so a consumer can key a table by\n * step and have TypeScript insist on every one. Before 0.2.0 `key` was a plain\n * string and the web client kept its own copy of this list to get the same\n * check; a test there held the two together. Now there is one list.\n */\nexport const ONBOARDING_STEP_KEYS = [\n 'identity',\n 'contact',\n 'location',\n 'goals',\n 'interests',\n 'business',\n 'guidelines',\n] as const;\nexport type OnboardingStepKey = (typeof ONBOARDING_STEP_KEYS)[number];\n\n// ─── Guidelines ──────────────────────────────────────────────────────────────\n\n/**\n * The community guidelines a member agrees to at the last step, by version.\n *\n * The finish body accepts THIS version and no other: an old tab still showing\n * last month's text cannot record agreement to text it never displayed, and\n * `'whatever'` is not a version. Until 0.2.0 any non-empty string passed, and\n * the web client held this constant on its own.\n *\n * ⚠ Changing the guidelines text means bumping this, in the same publish.\n * Every member who agreed to an earlier version keeps that version on their\n * profile; that record is the point.\n */\nexport const GUIDELINES_VERSION = '2026-10-03' as const;\nexport const guidelinesVersionSchema = z.literal(GUIDELINES_VERSION, {\n error: VALIDATION_MESSAGE.guidelinesOutdated,\n});\n\n// ─── Contact ─────────────────────────────────────────────────────────────────\n\n/**\n * The links the contact step collects, picked from the profile's own links so\n * the two cannot disagree on what a link is. The phone is `phoneSchema`: ten\n * digits, stored raw with no country code, on the account rather than here.\n */\nexport const contactLinksSchema = founderLinksSchema.pick({\n linkedin: true,\n x: true,\n instagram: true,\n});\nexport type ContactLinks = z.infer<typeof contactLinksSchema>;\n\n/** A team size has at most this many digits: `TEAM_SIZE_MAX` written out. */\nexport const TEAM_SIZE_DIGITS_MAX = String(TEAM_SIZE_MAX).length;\n\n// ─── The form, mid-flight: the draft each step saves ──────────────────────────\n\n/**\n * The answers so far, keyed by the finish body's own field names: what each\n * step SAVES (`PATCH /onboarding`, merged per field), what `GET` hands back\n * to resume, and so the only keys a draft may hold\n * (`ONBOARDING_DRAFT_KEYS`).\n *\n * RAW, on purpose: a draft is what the member TYPED, not what Finish will send.\n * A half-typed link is ordinary text, a team size is digits as typed, and every\n * choice is `null` until made (\"not answered\" is not \"answered with nothing\").\n * Nothing here is required; whether a member may FINISH is\n * `completeOnboardingBodySchema`'s decision, and the client builds that body\n * from these answers.\n *\n * Screens may hold a different shape on top of this one (the web draws first\n * and last name as two fields and joins them into `displayName`). Derive that\n * type from `OnboardingForm` rather than writing it out, so a field added here\n * reaches every screen.\n *\n * A stored draft is UNTRUSTED when it comes back: an older client, or another\n * one, wrote it, and the server stores values opaquely. Read it with\n * `readOnboardingDraft`, never by casting.\n */\nexport const onboardingFormSchema = z.object({\n displayName: z.string().max(DISPLAY_NAME_MAX),\n gender: genderSchema.nullable(),\n /** `YYYY-MM-DD` once complete; whatever has been typed until then. */\n dateOfBirth: z.string(),\n headline: z.string().max(HEADLINE_MAX),\n about: z.string().max(ABOUT_MAX),\n /**\n * Allowed as a key, and NEVER restored by `readOnboardingDraft`: an upload\n * not attached within a day is deleted by the storage lifecycle rule, so a\n * key from an older draft would fail at Finish, on the last screen, about a\n * photo picked days ago. Clients hold it for the visit and do not save it.\n */\n avatarUploadKey: uploadKeySchema.nullable(),\n skills: z.array(skillSchema).max(SKILLS_MAX),\n\n /** As typed; the finish body takes `phoneSchema`'s ten digits. */\n phone: z.string(),\n /** As typed; the finish body takes full `https://` URLs. */\n links: z.object({\n linkedin: z.string().max(LINK_URL_MAX),\n x: z.string().max(LINK_URL_MAX),\n instagram: z.string().max(LINK_URL_MAX),\n }),\n\n /** `null` until the member opens the location step, and when they skip it. */\n location: z\n .object({\n city: z.string().max(CITY_MAX),\n state: z.string().max(STATE_MAX),\n country: z.string().max(COUNTRY_MAX),\n })\n .nullable(),\n\n goals: z.array(goalSchema).max(GOAL_VALUES.length),\n interests: z.array(topicSchema).max(INTERESTS_MAX),\n\n /**\n * Always present; empty until started. Onboarding does not ask for a\n * tagline (the profile does, later), so the draft has none.\n */\n venture: z.object({\n name: z.string().max(VENTURE_NAME_MAX),\n website: z.string().max(LINK_URL_MAX),\n industry: industrySchema.nullable(),\n stage: startupStageSchema.nullable(),\n /** Digits as typed, at most `TEAM_SIZE_DIGITS_MAX`; the body sends a number. */\n teamSize: z.string().max(TEAM_SIZE_DIGITS_MAX),\n }),\n businessType: businessTypeSchema.nullable(),\n\n /** The guidelines text the member agreed to, or `null`. */\n guidelinesVersion: z.string().nullable(),\n});\nexport type OnboardingForm = z.infer<typeof onboardingFormSchema>;\n\n// ─── Reading a stored draft back ─────────────────────────────────────────────\n\n/** Longest date of birth worth keeping from a draft: `YYYY-MM-DD`. */\nconst DATE_INPUT_MAX = 10;\n\n/** Longest phone number worth keeping from a draft, as typed with spaces. */\nconst PHONE_INPUT_MAX = 20;\n\n/** Text, cut to its limit; anything that is not text is \"not answered\". */\nconst draftText = (max: number) =>\n z\n .string()\n .catch('')\n .transform((v) => v.slice(0, max));\n\n/** One of a vocabulary, or `null` (\"not answered\"). */\nconst draftChoice = <T extends readonly [string, ...string[]]>(values: T) =>\n z.enum(values).nullable().catch(null);\n\n/**\n * Items of a vocabulary, deduplicated and capped. Unknown items (a value since\n * retired, or junk) are dropped one by one, so the rest of the answer survives.\n */\nconst draftList = <T extends readonly [string, ...string[]]>(values: T, max: number) =>\n z\n .array(z.unknown())\n .catch([])\n .transform((items) => {\n const allowed = new Set<string>(values);\n const kept = items.filter((v): v is T[number] => typeof v === 'string' && allowed.has(v));\n return [...new Set(kept)].slice(0, max);\n });\n\n/** An object of the given fields, or `null` when the draft holds none. */\nconst draftObject = <S extends z.ZodRawShape>(shape: S) =>\n z\n .record(z.string(), z.unknown())\n .nullable()\n .catch(null)\n .pipe(z.union([z.null(), z.object(shape)]));\n\nconst draftReaderSchema = z.object({\n displayName: draftText(DISPLAY_NAME_MAX),\n gender: draftChoice(GENDER_VALUES),\n dateOfBirth: draftText(DATE_INPUT_MAX),\n headline: draftText(HEADLINE_MAX),\n about: draftText(ABOUT_MAX),\n avatarUploadKey: z\n .unknown()\n .optional()\n .transform(() => null),\n skills: draftList(SKILL_VALUES, SKILLS_MAX),\n phone: draftText(PHONE_INPUT_MAX),\n links: draftObject({\n linkedin: draftText(LINK_URL_MAX),\n x: draftText(LINK_URL_MAX),\n instagram: draftText(LINK_URL_MAX),\n }).transform((links) => links ?? { linkedin: '', x: '', instagram: '' }),\n location: draftObject({\n city: draftText(CITY_MAX),\n state: draftText(STATE_MAX),\n country: draftText(COUNTRY_MAX),\n }),\n goals: draftList(GOAL_VALUES, GOAL_VALUES.length),\n interests: draftList(TOPIC_VALUES, INTERESTS_MAX),\n venture: draftObject({\n name: draftText(VENTURE_NAME_MAX),\n website: draftText(LINK_URL_MAX),\n industry: draftChoice(INDUSTRY_VALUES),\n stage: draftChoice(STARTUP_STAGE_VALUES),\n teamSize: z\n .string()\n .catch('')\n .transform((v) => v.replace(/\\D/g, '').slice(0, TEAM_SIZE_DIGITS_MAX)),\n }).transform((venture) => venture ?? { ...EMPTY_ONBOARDING_VENTURE }),\n businessType: draftChoice(BUSINESS_TYPE_VALUES),\n /** Agreement to an OLDER text is not agreement to this one. */\n guidelinesVersion: z\n .unknown()\n .optional()\n .transform((v) => (v === GUIDELINES_VERSION ? GUIDELINES_VERSION : null)),\n});\n\n/** The business step before anything is chosen. */\nexport const EMPTY_ONBOARDING_VENTURE: OnboardingForm['venture'] = {\n name: '',\n website: '',\n industry: null,\n stage: null,\n teamSize: '',\n};\n\n/**\n * A stored draft as an `OnboardingForm`, FIELD BY FIELD.\n *\n * Every field is checked against its own vocabulary and cut to its own limit;\n * one that does not fit becomes \"not answered\" (`''`, `null`, `[]`) and the\n * rest of the draft survives. Never coerced into a different answer: a value\n * outside a vocabulary is dropped, not mapped to the nearest one. A photo key\n * is never restored (see `avatarUploadKey`), and agreement only counts for the\n * CURRENT guidelines.\n *\n * It fills in no answer the member did not give: a blank country stays blank.\n * Whatever a screen shows by default is that screen's choice.\n */\nexport function readOnboardingDraft(draft: unknown): OnboardingForm {\n const source = draft && typeof draft === 'object' && !Array.isArray(draft) ? draft : {};\n return draftReaderSchema.parse(source);\n}\n\n// ─── The finalize body ───────────────────────────────────────────────────────\n\n/**\n * What the client sends to finish. Completed answers only.\n *\n * ⚠️ EVERY OPTIONAL FIELD HERE MUST BE ABSENT WHEN UNANSWERED, never a default\n * standing in for one. The sibling gym product sent `bloodGroup ?? 'O+'`, so\n * every member who skipped the question was recorded as O-positive: a real\n * answer, indistinguishable from one somebody gave, sitting on a screen staff\n * would read in an emergency. Absent has to stay absent.\n *\n * ⚠️ AND THIS IS NOT THE GATE EITHER. The server re-checks the age and refuses\n * at finalize. `isOnboarded` may be set here and nowhere else: four of the\n * profile's partial indexes are gated on it, so a half-built profile carrying\n * it true appears in Discover half-built.\n *\n * Location, venture and links are the profile's own shapes, not copies: the\n * copies are how the profile model came to lack Instagram.\n */\nexport const completeOnboardingBodySchema = z.object({\n // Plain bounds, no custom message: `too_small` with minimum 1 already says\n // \"required\", and the client words it in the member's language.\n displayName: z.string().trim().min(1).max(DISPLAY_NAME_MAX),\n /**\n * ⚠️ NO `username` HERE, and its absence is deliberate.\n *\n * It used to be required, and that was a defect: no onboarding step collects\n * one, the design draws no screen for one, and it was not even on the\n * in-progress form, so a finalize could never have succeeded. Found by\n * checking every finalize-required field against the step list; a test now\n * asserts that relationship in both directions.\n *\n * The handle is DERIVED SERVER-SIDE at finalize and edited later from the Me\n * tab, which is what the product describes. That also makes the collision\n * case kinder: a server deriving a handle can suffix it and move on, whereas\n * a member who picked one six steps ago can only be told, at the end, that\n * step one failed.\n */\n gender: genderSchema,\n dateOfBirth: dateOfBirthSchema,\n headline: optionalTrimmedString(HEADLINE_MAX),\n about: optionalTrimmedString(ABOUT_MAX),\n /**\n * The key the presign handed out, in the minted format. It was a bare\n * string until 0.2.0, so `'../../etc/passwd'` passed this schema and only\n * the server's own commit check stood in the way.\n */\n avatarUploadKey: uploadKeySchema.optional(),\n\n phone: phoneSchema,\n links: contactLinksSchema.optional(),\n\n /** Absent when the member skipped the step or refused the permission. */\n location: locationSchema.optional(),\n\n goals: z.array(goalSchema).min(GOALS_MIN).max(GOAL_VALUES.length),\n interests: z.array(topicSchema).min(INTERESTS_MIN).max(INTERESTS_MAX),\n skills: z.array(skillSchema).max(SKILLS_MAX).optional(),\n\n /**\n * Absent when the member skipped the business step. The profile's venture\n * without `foundedYear`, which onboarding does not ask; the Me tab does.\n */\n venture: ventureSchema.omit({ foundedYear: true }).optional(),\n businessType: businessTypeSchema.optional(),\n\n /**\n * The version of the guidelines this member read: `GUIDELINES_VERSION`,\n * exactly.\n *\n * A version and not a boolean, and the server stamps the time. A boolean says\n * somebody agreed; a version and a timestamp say WHAT they agreed to and\n * WHEN, which is the only form worth anything the day the text changes or a\n * moderation decision is disputed.\n */\n guidelinesVersion: guidelinesVersionSchema,\n});\nexport type CompleteOnboardingBody = z.infer<typeof completeOnboardingBodySchema>;\n/** What a form may hand to the finish body before parsing. */\nexport type CompleteOnboardingBodyInput = z.input<typeof completeOnboardingBodySchema>;\n\n// ─── The steps ───────────────────────────────────────────────────────────────\n\n/**\n * Each step's slice of the finish body, picked FROM it, so a client validating\n * \"may I leave this step\" and the server validating the finish apply the very\n * same rules. A copy would drift, and the drift shows up as a wizard that lets\n * a step pass and a finish that refuses it, on the last screen.\n *\n * THIS IS THE ONE PLACE A STEP'S FIELDS ARE WRITTEN. `FOUNDER_ONBOARDING_STEPS`\n * below reads them from here. A test asserts that every finish field belongs to\n * exactly one step.\n *\n * These validate the FINISH shape (skipped = absent), not the in-progress form:\n * a client maps its form state to the slice first. The server does not run them\n * on a draft save, because a half-answered step must be storable.\n */\nexport const ONBOARDING_STEP_SCHEMAS = {\n identity: completeOnboardingBodySchema.pick({\n displayName: true,\n gender: true,\n dateOfBirth: true,\n headline: true,\n about: true,\n avatarUploadKey: true,\n // Here because ruling D8 folded the design's orphan \"title, achievements,\n // skills\" screen into step one. It was on no step until 0.2.0, which made\n // it the one finish field no step owned.\n skills: true,\n }),\n contact: completeOnboardingBodySchema.pick({ phone: true, links: true }),\n location: completeOnboardingBodySchema.pick({ location: true }),\n goals: completeOnboardingBodySchema.pick({ goals: true }),\n interests: completeOnboardingBodySchema.pick({ interests: true }),\n business: completeOnboardingBodySchema.pick({ venture: true, businessType: true }),\n guidelines: completeOnboardingBodySchema.pick({ guidelinesVersion: true }),\n} as const satisfies Record<OnboardingStepKey, z.ZodObject>;\n\n/** One step's slice, parsed: `OnboardingStepBody<'contact'>` is `{ phone, links? }`. */\nexport type OnboardingStepBody<K extends OnboardingStepKey> = z.infer<\n (typeof ONBOARDING_STEP_SCHEMAS)[K]\n>;\n\n/** A finish-body field name. */\nexport type OnboardingField = keyof CompleteOnboardingBody;\n\n/**\n * One step of the wizard, as a client renders it.\n *\n * `fields` is what gates leaving this step: a field that appears in no step is\n * unreachable twice over, because Continue will not surface its error and the\n * final submit's jump-to-the-bad-step finds no step to jump to and leaves the\n * last screen dead.\n */\nexport interface OnboardingStepConfig {\n /** Stable identifier. Safe for analytics and for a resume cursor, unlike an index. */\n key: OnboardingStepKey;\n /** Translation keys. The words live in the client's message files. */\n titleKey: `onboarding.steps.${OnboardingStepKey}.title`;\n helpKey: `onboarding.steps.${OnboardingStepKey}.help`;\n /** The step's slice of the finish body, in schema order. */\n fields: readonly OnboardingField[];\n /**\n * False when the whole step may be passed without answering anything.\n * DERIVED: a step is optional exactly when its slice accepts an empty\n * object, so \"skippable\" can never disagree with what the finish accepts.\n */\n required: boolean;\n}\n\nexport const FOUNDER_ONBOARDING_STEPS: readonly OnboardingStepConfig[] = ONBOARDING_STEP_KEYS.map(\n (key): OnboardingStepConfig => {\n const slice = ONBOARDING_STEP_SCHEMAS[key];\n return {\n key,\n titleKey: `onboarding.steps.${key}.title`,\n helpKey: `onboarding.steps.${key}.help`,\n fields: Object.keys(slice.shape) as OnboardingField[],\n required: !slice.safeParse({}).success,\n };\n },\n);\n\nexport const ONBOARDING_TOTAL_STEPS = FOUNDER_ONBOARDING_STEPS.length;\n\n/** Fields gating each step, in step order. Read from the same picks. */\nexport const ONBOARDING_STEP_FIELDS: readonly (readonly OnboardingField[])[] =\n FOUNDER_ONBOARDING_STEPS.map((s) => s.fields);\n\n/** What finishing answers: the new profile's id and the handle derived for it. */\nexport const completeOnboardingResponseSchema = z.object({\n id: z.string(),\n username: z.string(),\n});\nexport type CompleteOnboardingResponse = z.infer<typeof completeOnboardingResponseSchema>;\n\n// ─── The resume cursor ───────────────────────────────────────────────────────\n\n/**\n * The keys a draft may hold: exactly the in-progress form's fields.\n *\n * ⚠ The server writes each key as a dot path (`draft.<key>`). Unchecked, a key\n * holding a dot writes somewhere nested the form never asked for, and the\n * collection fills with whatever a client invents. Until 0.2.0 the backend\n * kept its own copy of this list and DROPPED unknown keys without a word; now a\n * draft carrying one is refused, with the key in the issue's path.\n */\nexport const onboardingDraftKeySchema = onboardingFormSchema.keyof();\nexport const ONBOARDING_DRAFT_KEYS = onboardingDraftKeySchema.options;\nexport type OnboardingDraftKey = z.infer<typeof onboardingDraftKeySchema>;\n\n/**\n * Where the member got to, kept in its own collection, separate from the\n * profile.\n *\n * `step` is a flat index into `FOUNDER_ONBOARDING_STEPS`, not a `(step, sub)`\n * pair: one number is what lets a member start on the web and resume elsewhere,\n * provided both ends agree on the encoding.\n *\n * The draft's KEYS come from the form; its VALUES are opaque on purpose. A\n * half-answered step must be storable even though it would fail the finish,\n * and everything in it is re-validated strictly at finalize.\n *\n * `partialRecord`, not `record`: in Zod 4 an enum-keyed `z.record` demands\n * every key, and a draft holds whichever steps the member has reached.\n */\nexport const onboardingDraftSchema = z.partialRecord(onboardingDraftKeySchema, z.unknown());\nexport type OnboardingDraft = z.infer<typeof onboardingDraftSchema>;\n\nexport const onboardingProgressSchema = z.object({\n step: z.number().int().min(0).max(ONBOARDING_TOTAL_STEPS),\n draft: onboardingDraftSchema.optional(),\n});\nexport type OnboardingProgress = z.infer<typeof onboardingProgressSchema>;\n\n/**\n * `PATCH` body: any subset of the cursor, merged server-side. No defaults.\n * `step` may equal `ONBOARDING_TOTAL_STEPS`: \"past the last step\", which the\n * client sends on its way to finish.\n */\nexport const onboardingProgressUpdateSchema = z.object({\n step: z.number().int().min(0).max(ONBOARDING_TOTAL_STEPS).optional(),\n draft: onboardingDraftSchema.optional(),\n});\nexport type OnboardingProgressUpdate = z.infer<typeof onboardingProgressUpdateSchema>;\n\n/**\n * What the client reads on return, from `GET` and from every `PATCH`.\n *\n * ⚠️ `onboarded` is the gate and it comes from the profile, not from the\n * cursor: a finished member on a new device must be recognised even when the\n * cursor row is gone, and a cursor that failed to load must never make an\n * unfinished member look finished.\n */\nexport const onboardingStatusSchema = z.object({\n onboarded: z.boolean(),\n step: z.number().int().min(0).max(ONBOARDING_TOTAL_STEPS),\n draft: onboardingDraftSchema.optional(),\n});\nexport type OnboardingStatus = z.infer<typeof onboardingStatusSchema>;\n"]}
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* founder-schema — API response envelope
|
|
3
|
-
*
|
|
4
|
-
* Every Founder Circle endpoint answers in this shape. The clients read the
|
|
5
|
-
* failure reason at `error.message`, so a handler must never return a bare
|
|
6
|
-
* string body.
|
|
7
|
-
*
|
|
8
|
-
* @module founder-schema/responses/common
|
|
9
|
-
*/
|
|
10
|
-
import { z } from 'zod';
|
|
11
|
-
/**
|
|
12
|
-
* Where the per-field problems of a 400 go.
|
|
13
|
-
*
|
|
14
|
-
* `issues` is EVERY problem, each with its path, built on the server by
|
|
15
|
-
* `toValidationIssues`. Until 0.2.0 this was typed as a record of strings that
|
|
16
|
-
* nothing ever sent, while the real 400s carried only the first issue's
|
|
17
|
-
* message, so a client could not tell which field to mark.
|
|
18
|
-
*/
|
|
19
|
-
export declare const apiErrorDetailsSchema: z.ZodObject<{
|
|
20
|
-
issues: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
21
|
-
location: z.ZodOptional<z.ZodEnum<{
|
|
22
|
-
body: "body";
|
|
23
|
-
query: "query";
|
|
24
|
-
params: "params";
|
|
25
|
-
}>>;
|
|
26
|
-
path: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
27
|
-
code: z.ZodString;
|
|
28
|
-
message: z.ZodString;
|
|
29
|
-
minimum: z.ZodOptional<z.ZodNumber>;
|
|
30
|
-
maximum: z.ZodOptional<z.ZodNumber>;
|
|
31
|
-
origin: z.ZodOptional<z.ZodString>;
|
|
32
|
-
format: z.ZodOptional<z.ZodString>;
|
|
33
|
-
}, z.core.$strip>>>;
|
|
34
|
-
}, z.core.$strip>;
|
|
35
|
-
export type ApiErrorDetails = z.infer<typeof apiErrorDetailsSchema>;
|
|
36
|
-
/**
|
|
37
|
-
* `code` is one of `API_ERROR_CODE_VALUES`, and it is what a client acts on.
|
|
38
|
-
* `message` is English, for logs and for a client that predates the code.
|
|
39
|
-
*/
|
|
40
|
-
export declare const apiErrorSchema: z.ZodObject<{
|
|
41
|
-
code: z.ZodEnum<{
|
|
42
|
-
INTERNAL_ERROR: "INTERNAL_ERROR";
|
|
43
|
-
VALIDATION_ERROR: "VALIDATION_ERROR";
|
|
44
|
-
NOT_FOUND: "NOT_FOUND";
|
|
45
|
-
CONFLICT: "CONFLICT";
|
|
46
|
-
RATE_LIMITED: "RATE_LIMITED";
|
|
47
|
-
SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE";
|
|
48
|
-
UNAUTHORIZED: "UNAUTHORIZED";
|
|
49
|
-
INVALID_TOKEN: "INVALID_TOKEN";
|
|
50
|
-
TOKEN_EXPIRED: "TOKEN_EXPIRED";
|
|
51
|
-
SESSION_INVALIDATED: "SESSION_INVALIDATED";
|
|
52
|
-
FORBIDDEN: "FORBIDDEN";
|
|
53
|
-
ONBOARDING_INCOMPLETE: "ONBOARDING_INCOMPLETE";
|
|
54
|
-
ALREADY_ONBOARDED: "ALREADY_ONBOARDED";
|
|
55
|
-
UNDER_AGE: "UNDER_AGE";
|
|
56
|
-
UPLOAD_NOT_ATTACHABLE: "UPLOAD_NOT_ATTACHABLE";
|
|
57
|
-
UPLOAD_TOO_LARGE: "UPLOAD_TOO_LARGE";
|
|
58
|
-
UPLOAD_WRONG_TYPE: "UPLOAD_WRONG_TYPE";
|
|
59
|
-
EDIT_WINDOW_CLOSED: "EDIT_WINDOW_CLOSED";
|
|
60
|
-
POLL_NOT_EDITABLE: "POLL_NOT_EDITABLE";
|
|
61
|
-
POLL_CLOSED: "POLL_CLOSED";
|
|
62
|
-
POLL_CHOICE_INVALID: "POLL_CHOICE_INVALID";
|
|
63
|
-
REPLY_TOO_DEEP: "REPLY_TOO_DEEP";
|
|
64
|
-
CANNOT_REPORT_OWN: "CANNOT_REPORT_OWN";
|
|
65
|
-
CHAT_WITH_SELF: "CHAT_WITH_SELF";
|
|
66
|
-
}>;
|
|
67
|
-
message: z.ZodString;
|
|
68
|
-
details: z.ZodOptional<z.ZodObject<{
|
|
69
|
-
issues: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
70
|
-
location: z.ZodOptional<z.ZodEnum<{
|
|
71
|
-
body: "body";
|
|
72
|
-
query: "query";
|
|
73
|
-
params: "params";
|
|
74
|
-
}>>;
|
|
75
|
-
path: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
76
|
-
code: z.ZodString;
|
|
77
|
-
message: z.ZodString;
|
|
78
|
-
minimum: z.ZodOptional<z.ZodNumber>;
|
|
79
|
-
maximum: z.ZodOptional<z.ZodNumber>;
|
|
80
|
-
origin: z.ZodOptional<z.ZodString>;
|
|
81
|
-
format: z.ZodOptional<z.ZodString>;
|
|
82
|
-
}, z.core.$strip>>>;
|
|
83
|
-
}, z.core.$strip>>;
|
|
84
|
-
}, z.core.$strip>;
|
|
85
|
-
export type ApiError = z.infer<typeof apiErrorSchema>;
|
|
86
|
-
/** Success envelope. `T` is the payload schema. */
|
|
87
|
-
export declare const successResponseSchema: <T extends z.ZodType>(data: T) => z.ZodObject<{
|
|
88
|
-
success: z.ZodLiteral<true>;
|
|
89
|
-
data: T;
|
|
90
|
-
message: z.ZodOptional<z.ZodString>;
|
|
91
|
-
}, z.core.$strip>;
|
|
92
|
-
export declare const errorResponseSchema: z.ZodObject<{
|
|
93
|
-
success: z.ZodLiteral<false>;
|
|
94
|
-
error: z.ZodObject<{
|
|
95
|
-
code: z.ZodEnum<{
|
|
96
|
-
INTERNAL_ERROR: "INTERNAL_ERROR";
|
|
97
|
-
VALIDATION_ERROR: "VALIDATION_ERROR";
|
|
98
|
-
NOT_FOUND: "NOT_FOUND";
|
|
99
|
-
CONFLICT: "CONFLICT";
|
|
100
|
-
RATE_LIMITED: "RATE_LIMITED";
|
|
101
|
-
SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE";
|
|
102
|
-
UNAUTHORIZED: "UNAUTHORIZED";
|
|
103
|
-
INVALID_TOKEN: "INVALID_TOKEN";
|
|
104
|
-
TOKEN_EXPIRED: "TOKEN_EXPIRED";
|
|
105
|
-
SESSION_INVALIDATED: "SESSION_INVALIDATED";
|
|
106
|
-
FORBIDDEN: "FORBIDDEN";
|
|
107
|
-
ONBOARDING_INCOMPLETE: "ONBOARDING_INCOMPLETE";
|
|
108
|
-
ALREADY_ONBOARDED: "ALREADY_ONBOARDED";
|
|
109
|
-
UNDER_AGE: "UNDER_AGE";
|
|
110
|
-
UPLOAD_NOT_ATTACHABLE: "UPLOAD_NOT_ATTACHABLE";
|
|
111
|
-
UPLOAD_TOO_LARGE: "UPLOAD_TOO_LARGE";
|
|
112
|
-
UPLOAD_WRONG_TYPE: "UPLOAD_WRONG_TYPE";
|
|
113
|
-
EDIT_WINDOW_CLOSED: "EDIT_WINDOW_CLOSED";
|
|
114
|
-
POLL_NOT_EDITABLE: "POLL_NOT_EDITABLE";
|
|
115
|
-
POLL_CLOSED: "POLL_CLOSED";
|
|
116
|
-
POLL_CHOICE_INVALID: "POLL_CHOICE_INVALID";
|
|
117
|
-
REPLY_TOO_DEEP: "REPLY_TOO_DEEP";
|
|
118
|
-
CANNOT_REPORT_OWN: "CANNOT_REPORT_OWN";
|
|
119
|
-
CHAT_WITH_SELF: "CHAT_WITH_SELF";
|
|
120
|
-
}>;
|
|
121
|
-
message: z.ZodString;
|
|
122
|
-
details: z.ZodOptional<z.ZodObject<{
|
|
123
|
-
issues: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
124
|
-
location: z.ZodOptional<z.ZodEnum<{
|
|
125
|
-
body: "body";
|
|
126
|
-
query: "query";
|
|
127
|
-
params: "params";
|
|
128
|
-
}>>;
|
|
129
|
-
path: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
130
|
-
code: z.ZodString;
|
|
131
|
-
message: z.ZodString;
|
|
132
|
-
minimum: z.ZodOptional<z.ZodNumber>;
|
|
133
|
-
maximum: z.ZodOptional<z.ZodNumber>;
|
|
134
|
-
origin: z.ZodOptional<z.ZodString>;
|
|
135
|
-
format: z.ZodOptional<z.ZodString>;
|
|
136
|
-
}, z.core.$strip>>>;
|
|
137
|
-
}, z.core.$strip>>;
|
|
138
|
-
}, z.core.$strip>;
|
|
139
|
-
}, z.core.$strip>;
|
|
140
|
-
export type ErrorResponse = z.infer<typeof errorResponseSchema>;
|
|
141
|
-
/**
|
|
142
|
-
* Cursor-paginated list envelope.
|
|
143
|
-
*
|
|
144
|
-
* `nextCursor` is REQUIRED and is `null` at the end of the list. Omitting the
|
|
145
|
-
* key is not the same thing and does not validate: a handler must decide
|
|
146
|
-
* whether there is another page and say so, because "the key is missing" and
|
|
147
|
-
* "there are no more results" are indistinguishable to a client that has to
|
|
148
|
-
* choose between showing a Load more button and not showing one.
|
|
149
|
-
*
|
|
150
|
-
* An earlier version of this comment said absence and null were equivalent.
|
|
151
|
-
* They never were; the schema has always required the key. Corrected
|
|
152
|
-
* 2026-09-21, and pinned by a test.
|
|
153
|
-
*/
|
|
154
|
-
export declare const paginatedSchema: <T extends z.ZodType>(item: T) => z.ZodObject<{
|
|
155
|
-
items: z.ZodArray<T>;
|
|
156
|
-
nextCursor: z.ZodNullable<z.ZodString>;
|
|
157
|
-
}, z.core.$strip>;
|
|
158
|
-
/**
|
|
159
|
-
* What a create, an edit or a delete answers: the id of the thing it touched.
|
|
160
|
-
* A client that needs the whole item reads it, so a write never ships a second
|
|
161
|
-
* projection of it that could drift from the read path's.
|
|
162
|
-
*/
|
|
163
|
-
export declare const idResponseSchema: z.ZodObject<{
|
|
164
|
-
id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
165
|
-
}, z.core.$strip>;
|
|
166
|
-
export type IdResponse = z.infer<typeof idResponseSchema>;
|
|
167
|
-
//# sourceMappingURL=common.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/responses/common.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;iBAEhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE;;;GAGG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAIzB,CAAC;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAEtD,mDAAmD;AACnD,eAAO,MAAM,qBAAqB,GAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC;;;;iBAK9D,CAAC;AAEL,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAG9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC;;;iBAIxD,CAAC;AAEL;;;;GAIG;AACH,eAAO,MAAM,gBAAgB;;iBAAmC,CAAC;AACjE,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
|
package/dist/responses/common.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* founder-schema — API response envelope
|
|
3
|
-
*
|
|
4
|
-
* Every Founder Circle endpoint answers in this shape. The clients read the
|
|
5
|
-
* failure reason at `error.message`, so a handler must never return a bare
|
|
6
|
-
* string body.
|
|
7
|
-
*
|
|
8
|
-
* @module founder-schema/responses/common
|
|
9
|
-
*/
|
|
10
|
-
import { z } from 'zod';
|
|
11
|
-
import { objectIdSchema } from '../common.js';
|
|
12
|
-
import { apiErrorCodeSchema } from '../errors.js';
|
|
13
|
-
import { validationIssueSchema } from '../validation.js';
|
|
14
|
-
/**
|
|
15
|
-
* Where the per-field problems of a 400 go.
|
|
16
|
-
*
|
|
17
|
-
* `issues` is EVERY problem, each with its path, built on the server by
|
|
18
|
-
* `toValidationIssues`. Until 0.2.0 this was typed as a record of strings that
|
|
19
|
-
* nothing ever sent, while the real 400s carried only the first issue's
|
|
20
|
-
* message, so a client could not tell which field to mark.
|
|
21
|
-
*/
|
|
22
|
-
export const apiErrorDetailsSchema = z.object({
|
|
23
|
-
issues: z.array(validationIssueSchema).optional(),
|
|
24
|
-
});
|
|
25
|
-
/**
|
|
26
|
-
* `code` is one of `API_ERROR_CODE_VALUES`, and it is what a client acts on.
|
|
27
|
-
* `message` is English, for logs and for a client that predates the code.
|
|
28
|
-
*/
|
|
29
|
-
export const apiErrorSchema = z.object({
|
|
30
|
-
code: apiErrorCodeSchema,
|
|
31
|
-
message: z.string(),
|
|
32
|
-
details: apiErrorDetailsSchema.optional(),
|
|
33
|
-
});
|
|
34
|
-
/** Success envelope. `T` is the payload schema. */
|
|
35
|
-
export const successResponseSchema = (data) => z.object({
|
|
36
|
-
success: z.literal(true),
|
|
37
|
-
data,
|
|
38
|
-
message: z.string().optional(),
|
|
39
|
-
});
|
|
40
|
-
export const errorResponseSchema = z.object({
|
|
41
|
-
success: z.literal(false),
|
|
42
|
-
error: apiErrorSchema,
|
|
43
|
-
});
|
|
44
|
-
/**
|
|
45
|
-
* Cursor-paginated list envelope.
|
|
46
|
-
*
|
|
47
|
-
* `nextCursor` is REQUIRED and is `null` at the end of the list. Omitting the
|
|
48
|
-
* key is not the same thing and does not validate: a handler must decide
|
|
49
|
-
* whether there is another page and say so, because "the key is missing" and
|
|
50
|
-
* "there are no more results" are indistinguishable to a client that has to
|
|
51
|
-
* choose between showing a Load more button and not showing one.
|
|
52
|
-
*
|
|
53
|
-
* An earlier version of this comment said absence and null were equivalent.
|
|
54
|
-
* They never were; the schema has always required the key. Corrected
|
|
55
|
-
* 2026-09-21, and pinned by a test.
|
|
56
|
-
*/
|
|
57
|
-
export const paginatedSchema = (item) => z.object({
|
|
58
|
-
items: z.array(item),
|
|
59
|
-
nextCursor: z.string().nullable(),
|
|
60
|
-
});
|
|
61
|
-
/**
|
|
62
|
-
* What a create, an edit or a delete answers: the id of the thing it touched.
|
|
63
|
-
* A client that needs the whole item reads it, so a write never ships a second
|
|
64
|
-
* projection of it that could drift from the read path's.
|
|
65
|
-
*/
|
|
66
|
-
export const idResponseSchema = z.object({ id: objectIdSchema });
|
|
67
|
-
//# sourceMappingURL=common.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"common.js","sourceRoot":"","sources":["../../src/responses/common.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAEzD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC,QAAQ,EAAE;CAClD,CAAC,CAAC;AAGH;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC;IACrC,IAAI,EAAE,kBAAkB;IACxB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,OAAO,EAAE,qBAAqB,CAAC,QAAQ,EAAE;CAC1C,CAAC,CAAC;AAGH,mDAAmD;AACnD,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAsB,IAAO,EAAE,EAAE,CACpE,CAAC,CAAC,MAAM,CAAC;IACP,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;IACxB,IAAI;IACJ,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC/B,CAAC,CAAC;AAEL,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1C,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;IACzB,KAAK,EAAE,cAAc;CACtB,CAAC,CAAC;AAGH;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAsB,IAAO,EAAE,EAAE,CAC9D,CAAC,CAAC,MAAM,CAAC;IACP,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;IACpB,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAClC,CAAC,CAAC;AAEL;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,cAAc,EAAE,CAAC,CAAC","sourcesContent":["/**\n * founder-schema — API response envelope\n *\n * Every Founder Circle endpoint answers in this shape. The clients read the\n * failure reason at `error.message`, so a handler must never return a bare\n * string body.\n *\n * @module founder-schema/responses/common\n */\n\nimport { z } from 'zod';\nimport { objectIdSchema } from '../common.js';\nimport { apiErrorCodeSchema } from '../errors.js';\nimport { validationIssueSchema } from '../validation.js';\n\n/**\n * Where the per-field problems of a 400 go.\n *\n * `issues` is EVERY problem, each with its path, built on the server by\n * `toValidationIssues`. Until 0.2.0 this was typed as a record of strings that\n * nothing ever sent, while the real 400s carried only the first issue's\n * message, so a client could not tell which field to mark.\n */\nexport const apiErrorDetailsSchema = z.object({\n issues: z.array(validationIssueSchema).optional(),\n});\nexport type ApiErrorDetails = z.infer<typeof apiErrorDetailsSchema>;\n\n/**\n * `code` is one of `API_ERROR_CODE_VALUES`, and it is what a client acts on.\n * `message` is English, for logs and for a client that predates the code.\n */\nexport const apiErrorSchema = z.object({\n code: apiErrorCodeSchema,\n message: z.string(),\n details: apiErrorDetailsSchema.optional(),\n});\nexport type ApiError = z.infer<typeof apiErrorSchema>;\n\n/** Success envelope. `T` is the payload schema. */\nexport const successResponseSchema = <T extends z.ZodType>(data: T) =>\n z.object({\n success: z.literal(true),\n data,\n message: z.string().optional(),\n });\n\nexport const errorResponseSchema = z.object({\n success: z.literal(false),\n error: apiErrorSchema,\n});\nexport type ErrorResponse = z.infer<typeof errorResponseSchema>;\n\n/**\n * Cursor-paginated list envelope.\n *\n * `nextCursor` is REQUIRED and is `null` at the end of the list. Omitting the\n * key is not the same thing and does not validate: a handler must decide\n * whether there is another page and say so, because \"the key is missing\" and\n * \"there are no more results\" are indistinguishable to a client that has to\n * choose between showing a Load more button and not showing one.\n *\n * An earlier version of this comment said absence and null were equivalent.\n * They never were; the schema has always required the key. Corrected\n * 2026-09-21, and pinned by a test.\n */\nexport const paginatedSchema = <T extends z.ZodType>(item: T) =>\n z.object({\n items: z.array(item),\n nextCursor: z.string().nullable(),\n });\n\n/**\n * What a create, an edit or a delete answers: the id of the thing it touched.\n * A client that needs the whole item reads it, so a write never ships a second\n * projection of it that could drift from the read path's.\n */\nexport const idResponseSchema = z.object({ id: objectIdSchema });\nexport type IdResponse = z.infer<typeof idResponseSchema>;\n"]}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* founder-schema — response barrel
|
|
3
|
-
*
|
|
4
|
-
* @module founder-schema/responses
|
|
5
|
-
*/
|
|
6
|
-
export { type ApiError, type ApiErrorDetails, apiErrorDetailsSchema, apiErrorSchema, type ErrorResponse, errorResponseSchema, type IdResponse, idResponseSchema, paginatedSchema, successResponseSchema, } from './common.js';
|
|
7
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/responses/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,qBAAqB,EACrB,cAAc,EACd,KAAK,aAAa,EAClB,mBAAmB,EACnB,KAAK,UAAU,EACf,gBAAgB,EAChB,eAAe,EACf,qBAAqB,GACtB,MAAM,aAAa,CAAC"}
|
package/dist/responses/index.js
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* founder-schema — response barrel
|
|
3
|
-
*
|
|
4
|
-
* @module founder-schema/responses
|
|
5
|
-
*/
|
|
6
|
-
export { apiErrorDetailsSchema, apiErrorSchema, errorResponseSchema, idResponseSchema, paginatedSchema, successResponseSchema, } from './common.js';
|
|
7
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/responses/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAGL,qBAAqB,EACrB,cAAc,EAEd,mBAAmB,EAEnB,gBAAgB,EAChB,eAAe,EACf,qBAAqB,GACtB,MAAM,aAAa,CAAC","sourcesContent":["/**\n * founder-schema — response barrel\n *\n * @module founder-schema/responses\n */\n\nexport {\n type ApiError,\n type ApiErrorDetails,\n apiErrorDetailsSchema,\n apiErrorSchema,\n type ErrorResponse,\n errorResponseSchema,\n type IdResponse,\n idResponseSchema,\n paginatedSchema,\n successResponseSchema,\n} from './common.js';\n"]}
|
package/dist/uploads.d.ts
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* founder-schema — Uploads: the key a client carries, and asking for a slot
|
|
3
|
-
*
|
|
4
|
-
* Its own module so that both `feed` (a post's media) and `onboarding` (the
|
|
5
|
-
* avatar) can use the one key rule. It lived in `feed` until 0.2.0, and
|
|
6
|
-
* `onboarding` could not import it from there without a cycle (`feed` imports
|
|
7
|
-
* `onboarding` for the topic list), so the avatar key was a bare string and
|
|
8
|
-
* `'../../etc/passwd'` passed.
|
|
9
|
-
*
|
|
10
|
-
* Imports only the limits and the message keys.
|
|
11
|
-
*
|
|
12
|
-
* @module founder-schema/uploads
|
|
13
|
-
*/
|
|
14
|
-
import { z } from 'zod';
|
|
15
|
-
/**
|
|
16
|
-
* ⚠ IMAGES ONLY, and that is a product decision rather than an oversight.
|
|
17
|
-
*
|
|
18
|
-
* The asymmetry was real and was raised: `feedVideoSchema` is in the read union
|
|
19
|
-
* and a card can render a video, while nothing could ever upload one. The user
|
|
20
|
-
* settled it on 2026-09-21: members upload images, and share video by putting a
|
|
21
|
-
* link in the post text, which the client renders inline. So the minter stays
|
|
22
|
-
* images-only by intent.
|
|
23
|
-
*
|
|
24
|
-
* `feedVideoSchema` is deliberately KEPT. Widening the write path later is
|
|
25
|
-
* additive; removing video from the read union once a client handles it is not.
|
|
26
|
-
* A test named for refusing a video key holds this visible, so whoever adds
|
|
27
|
-
* video uploads has to delete a test that says out loud why it exists.
|
|
28
|
-
*/
|
|
29
|
-
export declare const UPLOAD_KEY_EXTENSIONS: readonly ["jpg", "jpeg", "png", "webp", "gif"];
|
|
30
|
-
export declare const uploadKeyExtensionSchema: z.ZodEnum<{
|
|
31
|
-
jpg: "jpg";
|
|
32
|
-
jpeg: "jpeg";
|
|
33
|
-
png: "png";
|
|
34
|
-
webp: "webp";
|
|
35
|
-
gif: "gif";
|
|
36
|
-
}>;
|
|
37
|
-
export type UploadKeyExtension = z.infer<typeof uploadKeyExtensionSchema>;
|
|
38
|
-
/**
|
|
39
|
-
* The content type each extension is signed for, and the only types an
|
|
40
|
-
* attached object may turn out to have. The server signs the PUT with this
|
|
41
|
-
* type and refuses to attach an object whose stored type is anything else.
|
|
42
|
-
*/
|
|
43
|
-
export declare const UPLOAD_CONTENT_TYPES: {
|
|
44
|
-
readonly jpg: "image/jpeg";
|
|
45
|
-
readonly jpeg: "image/jpeg";
|
|
46
|
-
readonly png: "image/png";
|
|
47
|
-
readonly webp: "image/webp";
|
|
48
|
-
readonly gif: "image/gif";
|
|
49
|
-
};
|
|
50
|
-
export declare const UPLOAD_KEY_MAX_CHARS = 300;
|
|
51
|
-
/**
|
|
52
|
-
* An attachment on the way IN is a KEY, never a URL. FORMAT, minted
|
|
53
|
-
* server-side and never by a client:
|
|
54
|
-
*
|
|
55
|
-
* media/<userId>/<uuid>.<ext>
|
|
56
|
-
*
|
|
57
|
-
* Built from `UPLOAD_KEY_EXTENSIONS`, so the extension list exists once. The
|
|
58
|
-
* backend reads the owner segment with this same pattern.
|
|
59
|
-
*
|
|
60
|
-
* ⚠ THIS IS THE FIRST OF THREE CHECKS AND THE WEAKEST. It proves the key is
|
|
61
|
-
* well FORMED. It cannot prove the two things that actually matter:
|
|
62
|
-
*
|
|
63
|
-
* 1. OWNERSHIP. The `userId` segment is what makes "is this your object"
|
|
64
|
-
* checkable, and the backend compares it against the authenticated caller.
|
|
65
|
-
* A pattern happily accepts a well-formed key belonging to somebody else.
|
|
66
|
-
* 2. EXISTENCE. The backend reads the object before attaching it, so a key
|
|
67
|
-
* for an object nobody uploaded cannot be committed.
|
|
68
|
-
*/
|
|
69
|
-
export declare const UPLOAD_KEY_PATTERN: RegExp;
|
|
70
|
-
/**
|
|
71
|
-
* The length ceiling and the traversal refusal are redundant against the
|
|
72
|
-
* pattern, which is anchored and admits no dots outside the extension. They are
|
|
73
|
-
* kept on purpose: they are what still holds if the pattern is ever loosened.
|
|
74
|
-
*/
|
|
75
|
-
export declare const uploadKeySchema: z.ZodString;
|
|
76
|
-
export type UploadKey = z.infer<typeof uploadKeySchema>;
|
|
77
|
-
/**
|
|
78
|
-
* Asking for an upload slot: `POST /api/v1/uploads/presign`.
|
|
79
|
-
*
|
|
80
|
-
* The server chooses the key (so it carries the caller's id), the content type
|
|
81
|
-
* and the expiry; a client that could name its own key could aim it at
|
|
82
|
-
* someone else's folder.
|
|
83
|
-
*
|
|
84
|
-
* `contentLength` is the file's exact size in bytes, and it is SIGNED INTO the
|
|
85
|
-
* URL: storage refuses a PUT whose body is any other size. Until 0.2.0 nothing
|
|
86
|
-
* capped the size at upload, so a member could put 40 MB into the bucket and
|
|
87
|
-
* only learn at attach time that it was refused.
|
|
88
|
-
*/
|
|
89
|
-
export declare const uploadPresignBodySchema: z.ZodObject<{
|
|
90
|
-
extension: z.ZodEnum<{
|
|
91
|
-
jpg: "jpg";
|
|
92
|
-
jpeg: "jpeg";
|
|
93
|
-
png: "png";
|
|
94
|
-
webp: "webp";
|
|
95
|
-
gif: "gif";
|
|
96
|
-
}>;
|
|
97
|
-
contentLength: z.ZodNumber;
|
|
98
|
-
}, z.core.$strip>;
|
|
99
|
-
export type UploadPresignBody = z.infer<typeof uploadPresignBodySchema>;
|
|
100
|
-
/**
|
|
101
|
-
* The upload slot. `headers` are part of the signature, not advice: send every
|
|
102
|
-
* one of them verbatim on the PUT, with a body of exactly `contentLength`
|
|
103
|
-
* bytes, or storage answers with a signature mismatch. `key` is what a create
|
|
104
|
-
* body then carries, exactly as given.
|
|
105
|
-
*/
|
|
106
|
-
export declare const uploadPresignResponseSchema: z.ZodObject<{
|
|
107
|
-
key: z.ZodString;
|
|
108
|
-
url: z.ZodURL;
|
|
109
|
-
headers: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
110
|
-
expiresIn: z.ZodNumber;
|
|
111
|
-
}, z.core.$strip>;
|
|
112
|
-
export type UploadPresignResponse = z.infer<typeof uploadPresignResponseSchema>;
|
|
113
|
-
//# sourceMappingURL=uploads.d.ts.map
|
package/dist/uploads.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"uploads.d.ts","sourceRoot":"","sources":["../src/uploads.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,qBAAqB,gDAAiD,CAAC;AACpF,eAAO,MAAM,wBAAwB;;;;;;EAAgC,CAAC;AACtE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E;;;;GAIG;AACH,eAAO,MAAM,oBAAoB;;;;;;CAMiC,CAAC;AAEnE,eAAO,MAAM,oBAAoB,MAAM,CAAC;AAExC;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,kBAAkB,QAE9B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,eAAe,aAMuD,CAAC;AACpF,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;iBAGlC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B;;;;;iBAMtC,CAAC;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC"}
|
package/dist/uploads.js
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* founder-schema — Uploads: the key a client carries, and asking for a slot
|
|
3
|
-
*
|
|
4
|
-
* Its own module so that both `feed` (a post's media) and `onboarding` (the
|
|
5
|
-
* avatar) can use the one key rule. It lived in `feed` until 0.2.0, and
|
|
6
|
-
* `onboarding` could not import it from there without a cycle (`feed` imports
|
|
7
|
-
* `onboarding` for the topic list), so the avatar key was a bare string and
|
|
8
|
-
* `'../../etc/passwd'` passed.
|
|
9
|
-
*
|
|
10
|
-
* Imports only the limits and the message keys.
|
|
11
|
-
*
|
|
12
|
-
* @module founder-schema/uploads
|
|
13
|
-
*/
|
|
14
|
-
import { z } from 'zod';
|
|
15
|
-
import { UPLOAD_MAX_BYTES } from './limits.js';
|
|
16
|
-
import { VALIDATION_MESSAGE } from './validation.js';
|
|
17
|
-
/**
|
|
18
|
-
* ⚠ IMAGES ONLY, and that is a product decision rather than an oversight.
|
|
19
|
-
*
|
|
20
|
-
* The asymmetry was real and was raised: `feedVideoSchema` is in the read union
|
|
21
|
-
* and a card can render a video, while nothing could ever upload one. The user
|
|
22
|
-
* settled it on 2026-09-21: members upload images, and share video by putting a
|
|
23
|
-
* link in the post text, which the client renders inline. So the minter stays
|
|
24
|
-
* images-only by intent.
|
|
25
|
-
*
|
|
26
|
-
* `feedVideoSchema` is deliberately KEPT. Widening the write path later is
|
|
27
|
-
* additive; removing video from the read union once a client handles it is not.
|
|
28
|
-
* A test named for refusing a video key holds this visible, so whoever adds
|
|
29
|
-
* video uploads has to delete a test that says out loud why it exists.
|
|
30
|
-
*/
|
|
31
|
-
export const UPLOAD_KEY_EXTENSIONS = ['jpg', 'jpeg', 'png', 'webp', 'gif'];
|
|
32
|
-
export const uploadKeyExtensionSchema = z.enum(UPLOAD_KEY_EXTENSIONS);
|
|
33
|
-
/**
|
|
34
|
-
* The content type each extension is signed for, and the only types an
|
|
35
|
-
* attached object may turn out to have. The server signs the PUT with this
|
|
36
|
-
* type and refuses to attach an object whose stored type is anything else.
|
|
37
|
-
*/
|
|
38
|
-
export const UPLOAD_CONTENT_TYPES = {
|
|
39
|
-
jpg: 'image/jpeg',
|
|
40
|
-
jpeg: 'image/jpeg',
|
|
41
|
-
png: 'image/png',
|
|
42
|
-
webp: 'image/webp',
|
|
43
|
-
gif: 'image/gif',
|
|
44
|
-
};
|
|
45
|
-
export const UPLOAD_KEY_MAX_CHARS = 300;
|
|
46
|
-
/**
|
|
47
|
-
* An attachment on the way IN is a KEY, never a URL. FORMAT, minted
|
|
48
|
-
* server-side and never by a client:
|
|
49
|
-
*
|
|
50
|
-
* media/<userId>/<uuid>.<ext>
|
|
51
|
-
*
|
|
52
|
-
* Built from `UPLOAD_KEY_EXTENSIONS`, so the extension list exists once. The
|
|
53
|
-
* backend reads the owner segment with this same pattern.
|
|
54
|
-
*
|
|
55
|
-
* ⚠ THIS IS THE FIRST OF THREE CHECKS AND THE WEAKEST. It proves the key is
|
|
56
|
-
* well FORMED. It cannot prove the two things that actually matter:
|
|
57
|
-
*
|
|
58
|
-
* 1. OWNERSHIP. The `userId` segment is what makes "is this your object"
|
|
59
|
-
* checkable, and the backend compares it against the authenticated caller.
|
|
60
|
-
* A pattern happily accepts a well-formed key belonging to somebody else.
|
|
61
|
-
* 2. EXISTENCE. The backend reads the object before attaching it, so a key
|
|
62
|
-
* for an object nobody uploaded cannot be committed.
|
|
63
|
-
*/
|
|
64
|
-
export const UPLOAD_KEY_PATTERN = new RegExp(`^media/[0-9a-f]{24}/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\.(${UPLOAD_KEY_EXTENSIONS.join('|')})$`);
|
|
65
|
-
/**
|
|
66
|
-
* The length ceiling and the traversal refusal are redundant against the
|
|
67
|
-
* pattern, which is anchored and admits no dots outside the extension. They are
|
|
68
|
-
* kept on purpose: they are what still holds if the pattern is ever loosened.
|
|
69
|
-
*/
|
|
70
|
-
export const uploadKeySchema = z
|
|
71
|
-
.string()
|
|
72
|
-
.trim()
|
|
73
|
-
.min(1)
|
|
74
|
-
.max(UPLOAD_KEY_MAX_CHARS)
|
|
75
|
-
.regex(UPLOAD_KEY_PATTERN, VALIDATION_MESSAGE.uploadKeyInvalid)
|
|
76
|
-
.refine((k) => !k.includes('..'), { error: VALIDATION_MESSAGE.uploadKeyInvalid });
|
|
77
|
-
/**
|
|
78
|
-
* Asking for an upload slot: `POST /api/v1/uploads/presign`.
|
|
79
|
-
*
|
|
80
|
-
* The server chooses the key (so it carries the caller's id), the content type
|
|
81
|
-
* and the expiry; a client that could name its own key could aim it at
|
|
82
|
-
* someone else's folder.
|
|
83
|
-
*
|
|
84
|
-
* `contentLength` is the file's exact size in bytes, and it is SIGNED INTO the
|
|
85
|
-
* URL: storage refuses a PUT whose body is any other size. Until 0.2.0 nothing
|
|
86
|
-
* capped the size at upload, so a member could put 40 MB into the bucket and
|
|
87
|
-
* only learn at attach time that it was refused.
|
|
88
|
-
*/
|
|
89
|
-
export const uploadPresignBodySchema = z.object({
|
|
90
|
-
extension: uploadKeyExtensionSchema,
|
|
91
|
-
contentLength: z.number().int().min(1).max(UPLOAD_MAX_BYTES),
|
|
92
|
-
});
|
|
93
|
-
/**
|
|
94
|
-
* The upload slot. `headers` are part of the signature, not advice: send every
|
|
95
|
-
* one of them verbatim on the PUT, with a body of exactly `contentLength`
|
|
96
|
-
* bytes, or storage answers with a signature mismatch. `key` is what a create
|
|
97
|
-
* body then carries, exactly as given.
|
|
98
|
-
*/
|
|
99
|
-
export const uploadPresignResponseSchema = z.object({
|
|
100
|
-
key: uploadKeySchema,
|
|
101
|
-
url: z.url(),
|
|
102
|
-
headers: z.record(z.string(), z.string()),
|
|
103
|
-
/** Seconds the URL stays valid. */
|
|
104
|
-
expiresIn: z.number().int().positive(),
|
|
105
|
-
});
|
|
106
|
-
//# sourceMappingURL=uploads.js.map
|
package/dist/uploads.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"uploads.js","sourceRoot":"","sources":["../src/uploads.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErD;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAU,CAAC;AACpF,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;AAGtE;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,GAAG,EAAE,YAAY;IACjB,IAAI,EAAE,YAAY;IAClB,GAAG,EAAE,WAAW;IAChB,IAAI,EAAE,YAAY;IAClB,GAAG,EAAE,WAAW;CACgD,CAAC;AAEnE,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,CAAC;AAExC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,MAAM,CAC1C,uFAAuF,qBAAqB,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAC3H,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC;KAC7B,MAAM,EAAE;KACR,IAAI,EAAE;KACN,GAAG,CAAC,CAAC,CAAC;KACN,GAAG,CAAC,oBAAoB,CAAC;KACzB,KAAK,CAAC,kBAAkB,EAAE,kBAAkB,CAAC,gBAAgB,CAAC;KAC9D,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,kBAAkB,CAAC,gBAAgB,EAAE,CAAC,CAAC;AAGpF;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,SAAS,EAAE,wBAAwB;IACnC,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,gBAAgB,CAAC;CAC7D,CAAC,CAAC;AAGH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,CAAC,MAAM,CAAC;IAClD,GAAG,EAAE,eAAe;IACpB,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE;IACZ,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;IACzC,mCAAmC;IACnC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;CACvC,CAAC,CAAC","sourcesContent":["/**\n * founder-schema — Uploads: the key a client carries, and asking for a slot\n *\n * Its own module so that both `feed` (a post's media) and `onboarding` (the\n * avatar) can use the one key rule. It lived in `feed` until 0.2.0, and\n * `onboarding` could not import it from there without a cycle (`feed` imports\n * `onboarding` for the topic list), so the avatar key was a bare string and\n * `'../../etc/passwd'` passed.\n *\n * Imports only the limits and the message keys.\n *\n * @module founder-schema/uploads\n */\n\nimport { z } from 'zod';\nimport { UPLOAD_MAX_BYTES } from './limits.js';\nimport { VALIDATION_MESSAGE } from './validation.js';\n\n/**\n * ⚠ IMAGES ONLY, and that is a product decision rather than an oversight.\n *\n * The asymmetry was real and was raised: `feedVideoSchema` is in the read union\n * and a card can render a video, while nothing could ever upload one. The user\n * settled it on 2026-09-21: members upload images, and share video by putting a\n * link in the post text, which the client renders inline. So the minter stays\n * images-only by intent.\n *\n * `feedVideoSchema` is deliberately KEPT. Widening the write path later is\n * additive; removing video from the read union once a client handles it is not.\n * A test named for refusing a video key holds this visible, so whoever adds\n * video uploads has to delete a test that says out loud why it exists.\n */\nexport const UPLOAD_KEY_EXTENSIONS = ['jpg', 'jpeg', 'png', 'webp', 'gif'] as const;\nexport const uploadKeyExtensionSchema = z.enum(UPLOAD_KEY_EXTENSIONS);\nexport type UploadKeyExtension = z.infer<typeof uploadKeyExtensionSchema>;\n\n/**\n * The content type each extension is signed for, and the only types an\n * attached object may turn out to have. The server signs the PUT with this\n * type and refuses to attach an object whose stored type is anything else.\n */\nexport const UPLOAD_CONTENT_TYPES = {\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n png: 'image/png',\n webp: 'image/webp',\n gif: 'image/gif',\n} as const satisfies Record<UploadKeyExtension, `image/${string}`>;\n\nexport const UPLOAD_KEY_MAX_CHARS = 300;\n\n/**\n * An attachment on the way IN is a KEY, never a URL. FORMAT, minted\n * server-side and never by a client:\n *\n * media/<userId>/<uuid>.<ext>\n *\n * Built from `UPLOAD_KEY_EXTENSIONS`, so the extension list exists once. The\n * backend reads the owner segment with this same pattern.\n *\n * ⚠ THIS IS THE FIRST OF THREE CHECKS AND THE WEAKEST. It proves the key is\n * well FORMED. It cannot prove the two things that actually matter:\n *\n * 1. OWNERSHIP. The `userId` segment is what makes \"is this your object\"\n * checkable, and the backend compares it against the authenticated caller.\n * A pattern happily accepts a well-formed key belonging to somebody else.\n * 2. EXISTENCE. The backend reads the object before attaching it, so a key\n * for an object nobody uploaded cannot be committed.\n */\nexport const UPLOAD_KEY_PATTERN = new RegExp(\n `^media/[0-9a-f]{24}/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\\\.(${UPLOAD_KEY_EXTENSIONS.join('|')})$`,\n);\n\n/**\n * The length ceiling and the traversal refusal are redundant against the\n * pattern, which is anchored and admits no dots outside the extension. They are\n * kept on purpose: they are what still holds if the pattern is ever loosened.\n */\nexport const uploadKeySchema = z\n .string()\n .trim()\n .min(1)\n .max(UPLOAD_KEY_MAX_CHARS)\n .regex(UPLOAD_KEY_PATTERN, VALIDATION_MESSAGE.uploadKeyInvalid)\n .refine((k) => !k.includes('..'), { error: VALIDATION_MESSAGE.uploadKeyInvalid });\nexport type UploadKey = z.infer<typeof uploadKeySchema>;\n\n/**\n * Asking for an upload slot: `POST /api/v1/uploads/presign`.\n *\n * The server chooses the key (so it carries the caller's id), the content type\n * and the expiry; a client that could name its own key could aim it at\n * someone else's folder.\n *\n * `contentLength` is the file's exact size in bytes, and it is SIGNED INTO the\n * URL: storage refuses a PUT whose body is any other size. Until 0.2.0 nothing\n * capped the size at upload, so a member could put 40 MB into the bucket and\n * only learn at attach time that it was refused.\n */\nexport const uploadPresignBodySchema = z.object({\n extension: uploadKeyExtensionSchema,\n contentLength: z.number().int().min(1).max(UPLOAD_MAX_BYTES),\n});\nexport type UploadPresignBody = z.infer<typeof uploadPresignBodySchema>;\n\n/**\n * The upload slot. `headers` are part of the signature, not advice: send every\n * one of them verbatim on the PUT, with a body of exactly `contentLength`\n * bytes, or storage answers with a signature mismatch. `key` is what a create\n * body then carries, exactly as given.\n */\nexport const uploadPresignResponseSchema = z.object({\n key: uploadKeySchema,\n url: z.url(),\n headers: z.record(z.string(), z.string()),\n /** Seconds the URL stays valid. */\n expiresIn: z.number().int().positive(),\n});\nexport type UploadPresignResponse = z.infer<typeof uploadPresignResponseSchema>;\n"]}
|