founder-schema 0.16.0 → 0.18.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.
Files changed (116) hide show
  1. package/README.md +263 -19
  2. package/dist/access/access.d.ts +1 -1
  3. package/dist/access/access.d.ts.map +1 -1
  4. package/dist/access/access.js +4 -1
  5. package/dist/access/access.js.map +1 -1
  6. package/dist/access/permissions.d.ts +27 -29
  7. package/dist/access/permissions.d.ts.map +1 -1
  8. package/dist/access/permissions.js +39 -3
  9. package/dist/access/permissions.js.map +1 -1
  10. package/dist/core/common.d.ts +117 -23
  11. package/dist/core/common.d.ts.map +1 -1
  12. package/dist/core/common.js +104 -21
  13. package/dist/core/common.js.map +1 -1
  14. package/dist/core/errors.d.ts +7 -5
  15. package/dist/core/errors.d.ts.map +1 -1
  16. package/dist/core/errors.js +17 -11
  17. package/dist/core/errors.js.map +1 -1
  18. package/dist/core/limits.d.ts +55 -17
  19. package/dist/core/limits.d.ts.map +1 -1
  20. package/dist/core/limits.js +59 -22
  21. package/dist/core/limits.js.map +1 -1
  22. package/dist/core/money.d.ts +33 -0
  23. package/dist/core/money.d.ts.map +1 -0
  24. package/dist/core/money.js +36 -0
  25. package/dist/core/money.js.map +1 -0
  26. package/dist/core/responses.d.ts +14 -4
  27. package/dist/core/responses.d.ts.map +1 -1
  28. package/dist/core/responses.js.map +1 -1
  29. package/dist/core/validation.d.ts +32 -2
  30. package/dist/core/validation.d.ts.map +1 -1
  31. package/dist/core/validation.js +32 -2
  32. package/dist/core/validation.js.map +1 -1
  33. package/dist/domains/billing/billing.d.ts +61 -8
  34. package/dist/domains/billing/billing.d.ts.map +1 -1
  35. package/dist/domains/billing/billing.js +58 -22
  36. package/dist/domains/billing/billing.js.map +1 -1
  37. package/dist/domains/billing/gst.d.ts.map +1 -1
  38. package/dist/domains/billing/gst.js +4 -3
  39. package/dist/domains/billing/gst.js.map +1 -1
  40. package/dist/domains/chat/chat.d.ts +0 -21
  41. package/dist/domains/chat/chat.d.ts.map +1 -1
  42. package/dist/domains/chat/chat.js +0 -20
  43. package/dist/domains/chat/chat.js.map +1 -1
  44. package/dist/domains/communities/communities.d.ts +30 -12
  45. package/dist/domains/communities/communities.d.ts.map +1 -1
  46. package/dist/domains/communities/communities.js +43 -15
  47. package/dist/domains/communities/communities.js.map +1 -1
  48. package/dist/domains/contributions/contributions.d.ts +1 -1
  49. package/dist/domains/contributions/contributions.js +1 -1
  50. package/dist/domains/contributions/contributions.js.map +1 -1
  51. package/dist/domains/dashboard/dashboard.d.ts +3 -1
  52. package/dist/domains/dashboard/dashboard.d.ts.map +1 -1
  53. package/dist/domains/dashboard/dashboard.js +23 -7
  54. package/dist/domains/dashboard/dashboard.js.map +1 -1
  55. package/dist/domains/discover/discover.d.ts +8 -8
  56. package/dist/domains/feed/feed.d.ts +55 -153
  57. package/dist/domains/feed/feed.d.ts.map +1 -1
  58. package/dist/domains/feed/feed.js +53 -58
  59. package/dist/domains/feed/feed.js.map +1 -1
  60. package/dist/domains/location/india.d.ts +215 -0
  61. package/dist/domains/location/india.d.ts.map +1 -0
  62. package/dist/domains/location/india.js +1203 -0
  63. package/dist/domains/location/india.js.map +1 -0
  64. package/dist/domains/members/members.d.ts +2 -2
  65. package/dist/domains/membership/membership.d.ts +26 -0
  66. package/dist/domains/membership/membership.d.ts.map +1 -1
  67. package/dist/domains/membership/membership.js +55 -1
  68. package/dist/domains/membership/membership.js.map +1 -1
  69. package/dist/domains/moderation/moderation.d.ts +2 -2
  70. package/dist/domains/moderation/moderation.js +3 -3
  71. package/dist/domains/moderation/moderation.js.map +1 -1
  72. package/dist/domains/notifications/notifications.d.ts +1 -10
  73. package/dist/domains/notifications/notifications.d.ts.map +1 -1
  74. package/dist/domains/notifications/notifications.js +2 -3
  75. package/dist/domains/notifications/notifications.js.map +1 -1
  76. package/dist/domains/onboarding/onboarding-answers.d.ts +112 -0
  77. package/dist/domains/onboarding/onboarding-answers.d.ts.map +1 -0
  78. package/dist/domains/onboarding/onboarding-answers.js +164 -0
  79. package/dist/domains/onboarding/onboarding-answers.js.map +1 -0
  80. package/dist/domains/onboarding/onboarding.d.ts +398 -62
  81. package/dist/domains/onboarding/onboarding.d.ts.map +1 -1
  82. package/dist/domains/onboarding/onboarding.js +229 -68
  83. package/dist/domains/onboarding/onboarding.js.map +1 -1
  84. package/dist/domains/products/products.d.ts +43 -8
  85. package/dist/domains/products/products.d.ts.map +1 -1
  86. package/dist/domains/products/products.js +57 -11
  87. package/dist/domains/products/products.js.map +1 -1
  88. package/dist/domains/profile/founder-profile.d.ts +120 -68
  89. package/dist/domains/profile/founder-profile.d.ts.map +1 -1
  90. package/dist/domains/profile/founder-profile.js +80 -29
  91. package/dist/domains/profile/founder-profile.js.map +1 -1
  92. package/dist/domains/profile/my-profile.d.ts +3 -3
  93. package/dist/domains/reviews/reviews.d.ts +1 -1
  94. package/dist/domains/reviews/reviews.d.ts.map +1 -1
  95. package/dist/domains/reviews/reviews.js +2 -2
  96. package/dist/domains/reviews/reviews.js.map +1 -1
  97. package/dist/domains/support/support.d.ts +22 -3
  98. package/dist/domains/support/support.d.ts.map +1 -1
  99. package/dist/domains/support/support.js +19 -5
  100. package/dist/domains/support/support.js.map +1 -1
  101. package/dist/domains/team/team.d.ts.map +1 -1
  102. package/dist/domains/team/team.js +3 -6
  103. package/dist/domains/team/team.js.map +1 -1
  104. package/dist/domains/uploads/uploads.d.ts +7 -22
  105. package/dist/domains/uploads/uploads.d.ts.map +1 -1
  106. package/dist/domains/uploads/uploads.js +7 -21
  107. package/dist/domains/uploads/uploads.js.map +1 -1
  108. package/dist/env.d.ts +26 -13
  109. package/dist/env.d.ts.map +1 -1
  110. package/dist/env.js +58 -23
  111. package/dist/env.js.map +1 -1
  112. package/dist/index.d.ts +18 -15
  113. package/dist/index.d.ts.map +1 -1
  114. package/dist/index.js +19 -14
  115. package/dist/index.js.map +1 -1
  116. package/package.json +1 -1
@@ -0,0 +1,112 @@
1
+ /**
2
+ * founder-schema — From a member's onboarding answers to what is sent (0.17.0)
3
+ *
4
+ * The answers are an `OnboardingForm` (the in-progress shape: raw text, `null`
5
+ * for a choice not made). Two things are built from them, and until 0.17.0
6
+ * each client built them by hand, so a second client (the app) would have had
7
+ * to copy every rule below and keep it in step:
8
+ *
9
+ * the draft a step SAVES exactly the fields that step owns, as typed
10
+ * (`onboardingDraftForStep`)
11
+ * the body FINISH sends completed answers only, cleaned, every
12
+ * unanswered optional field ABSENT
13
+ * (`completeOnboardingBodyFrom`)
14
+ *
15
+ * Neither validates: `ONBOARDING_STEP_SCHEMAS` and `completeOnboardingBodySchema`
16
+ * do, on what these build. A half-filled form builds a half-filled body on
17
+ * purpose, so the schema refuses it with the reason under each field instead
18
+ * of a builder hiding the gap.
19
+ *
20
+ * @module founder-schema/domains/onboarding/onboarding-answers
21
+ */
22
+ import { type Industry, type StartupStage } from '../../core/common.js';
23
+ import { type CompleteOnboardingBodyInput, type OnboardingForm, type OnboardingStepKey } from './onboarding.js';
24
+ type DeepPartial<T> = T extends readonly (infer Item)[] ? DeepPartial<Item>[] : T extends object ? {
25
+ [K in keyof T]?: DeepPartial<T[K]>;
26
+ } : T;
27
+ /**
28
+ * A founding year as typed, kept to digits, as many as a year has (0.18.0,
29
+ * moved from the web's Me editor beside `teamSizeDigits`).
30
+ */
31
+ export declare const foundedYearDigits: (raw: string) => string;
32
+ /**
33
+ * A venture body from what was typed (0.18.0): ONE builder for onboarding's
34
+ * business step and Me's business editor, which each built it by hand and
35
+ * cleaned the team size two ways. Text trimmed, the website made a link
36
+ * (`normalizeLinkUrl`), digits kept (`teamSizeDigits`, `foundedYearDigits`);
37
+ * an empty optional answer is ABSENT. A missing name, industry or stage is
38
+ * left missing, so `ventureSchema` refuses it under its field.
39
+ */
40
+ export declare function ventureBodyFrom(v: {
41
+ name: string;
42
+ website: string;
43
+ industry: Industry | null;
44
+ stage: StartupStage | null;
45
+ teamSize: string;
46
+ tagline?: string;
47
+ foundedYear?: string;
48
+ }): {
49
+ name: string;
50
+ industry: Industry | undefined;
51
+ stage: StartupStage | undefined;
52
+ tagline?: string;
53
+ website?: string;
54
+ teamSize?: number;
55
+ foundedYear?: number;
56
+ };
57
+ /**
58
+ * What `completeOnboardingBodyFrom` builds: the finish body's INPUT, any part
59
+ * of it possibly still missing. Not a valid body until
60
+ * `completeOnboardingBodySchema` says so.
61
+ */
62
+ export type CompleteOnboardingBodyCandidate = DeepPartial<CompleteOnboardingBodyInput>;
63
+ /** Answers with nothing filled in: what a new member starts from. */
64
+ export declare function emptyOnboardingForm(): OnboardingForm;
65
+ /**
66
+ * True when the member has started describing a business: any of the
67
+ * venture's own answers. Then it is sent, and the schema asks for its name,
68
+ * industry and stage.
69
+ *
70
+ * The business type and the registration do not start it: they DESCRIBE a
71
+ * business. Picked without one, each is sent anyway and refused on its own
72
+ * field (`ventureRequired`: add the business, or clear this), never dropped
73
+ * without a word (hary's ruling).
74
+ */
75
+ export declare function ventureStarted(form: Pick<OnboardingForm, 'venture'>): boolean;
76
+ /**
77
+ * True when a place has been started: a city or a state given. A place opened
78
+ * and left empty is NO place (the member chose not to say), never a half
79
+ * answer to refuse; a place with only one of the two IS half an answer, and
80
+ * the schema asks for the other.
81
+ */
82
+ export declare function locationStarted(place: {
83
+ city: string;
84
+ state?: string | null;
85
+ } | null): boolean;
86
+ /**
87
+ * The draft one step saves: exactly the fields that step owns
88
+ * (`FOUNDER_ONBOARDING_STEPS[].fields`, the same picks its validation uses),
89
+ * as typed. Only the step's own fields, so a save can never overwrite another
90
+ * step's answers (the server merges per field); raw, so a member who comes back
91
+ * sees what they typed. The photo step's field is its upload id, which the
92
+ * server holds rather than drafts.
93
+ */
94
+ export declare function onboardingDraftForStep(form: OnboardingForm, key: OnboardingStepKey): Partial<OnboardingForm>;
95
+ /**
96
+ * What Finish sends, and what every step is CHECKED as (each step's slice
97
+ * reads its own fields from it).
98
+ *
99
+ * ⚠ ABSENT, NEVER A DEFAULT. The sibling gym product sent `bloodGroup ?? 'O+'`
100
+ * and recorded everyone who skipped the question as O-positive. An optional
101
+ * answer not given is left out; nothing is filled with a plausible value.
102
+ *
103
+ * Cleaned the way the schema reads it: the name tidied (`tidyFullName`), the
104
+ * phone to ten digits (`normalizePhone`), links given their scheme
105
+ * (`normalizeLinkUrl`), the place in the list's own spelling in India
106
+ * (`canonicalIndiaLocation`), the team size a number. A half-answered part is
107
+ * SENT, not hidden: a location without a city, a business without its stage,
108
+ * reach the schema and are refused there, under the field.
109
+ */
110
+ export declare function completeOnboardingBodyFrom(form: OnboardingForm): CompleteOnboardingBodyCandidate;
111
+ export {};
112
+ //# sourceMappingURL=onboarding-answers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"onboarding-answers.d.ts","sourceRoot":"","sources":["../../../src/domains/onboarding/onboarding-answers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EACL,KAAK,QAAQ,EAGb,KAAK,YAAY,EAElB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EACL,KAAK,2BAA2B,EAIhC,KAAK,cAAc,EACnB,KAAK,iBAAiB,EAGvB,MAAM,iBAAiB,CAAC;AAEzB,KAAK,WAAW,CAAC,CAAC,IAAI,CAAC,SAAS,SAAS,CAAC,MAAM,IAAI,CAAC,EAAE,GACnD,WAAW,CAAC,IAAI,CAAC,EAAE,GACnB,CAAC,SAAS,MAAM,GACd;KAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,GACtC,CAAC,CAAC;AAER;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAAI,KAAK,MAAM,KAAG,MACkB,CAAC;AAEnE;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG;IACF,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC/B,KAAK,EAAE,YAAY,GAAG,SAAS,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAcA;AAED;;;;GAIG;AACH,MAAM,MAAM,+BAA+B,GAAG,WAAW,CAAC,2BAA2B,CAAC,CAAC;AAEvF,qEAAqE;AACrE,wBAAgB,mBAAmB,IAAI,cAAc,CAEpD;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,GAAG,OAAO,CAG7E;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,GAAG,OAAO,CAE9F;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,cAAc,EACpB,GAAG,EAAE,iBAAiB,GACrB,OAAO,CAAC,cAAc,CAAC,CAKzB;AAWD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,cAAc,GAAG,+BAA+B,CA6ChG"}
@@ -0,0 +1,164 @@
1
+ /**
2
+ * founder-schema — From a member's onboarding answers to what is sent (0.17.0)
3
+ *
4
+ * The answers are an `OnboardingForm` (the in-progress shape: raw text, `null`
5
+ * for a choice not made). Two things are built from them, and until 0.17.0
6
+ * each client built them by hand, so a second client (the app) would have had
7
+ * to copy every rule below and keep it in step:
8
+ *
9
+ * the draft a step SAVES exactly the fields that step owns, as typed
10
+ * (`onboardingDraftForStep`)
11
+ * the body FINISH sends completed answers only, cleaned, every
12
+ * unanswered optional field ABSENT
13
+ * (`completeOnboardingBodyFrom`)
14
+ *
15
+ * Neither validates: `ONBOARDING_STEP_SCHEMAS` and `completeOnboardingBodySchema`
16
+ * do, on what these build. A half-filled form builds a half-filled body on
17
+ * purpose, so the schema refuses it with the reason under each field instead
18
+ * of a builder hiding the gap.
19
+ *
20
+ * @module founder-schema/domains/onboarding/onboarding-answers
21
+ */
22
+ import { normalizeLinkUrl, normalizePhone, tidyFullName, } from '../../core/common.js';
23
+ import { FOUNDED_YEAR_MIN } from '../../core/limits.js';
24
+ import { canonicalIndiaLocation } from '../location/india.js';
25
+ import { FOUNDER_ONBOARDING_STEPS, GUIDELINES_VERSION, ONBOARDING_STEP_KEYS, readOnboardingDraft, teamSizeDigits, } from './onboarding.js';
26
+ /**
27
+ * A founding year as typed, kept to digits, as many as a year has (0.18.0,
28
+ * moved from the web's Me editor beside `teamSizeDigits`).
29
+ */
30
+ export const foundedYearDigits = (raw) => raw.replace(/\D/g, '').slice(0, String(FOUNDED_YEAR_MIN).length);
31
+ /**
32
+ * A venture body from what was typed (0.18.0): ONE builder for onboarding's
33
+ * business step and Me's business editor, which each built it by hand and
34
+ * cleaned the team size two ways. Text trimmed, the website made a link
35
+ * (`normalizeLinkUrl`), digits kept (`teamSizeDigits`, `foundedYearDigits`);
36
+ * an empty optional answer is ABSENT. A missing name, industry or stage is
37
+ * left missing, so `ventureSchema` refuses it under its field.
38
+ */
39
+ export function ventureBodyFrom(v) {
40
+ const tagline = v.tagline?.trim();
41
+ const website = normalizeLinkUrl(v.website);
42
+ const teamSize = teamSizeDigits(v.teamSize);
43
+ const foundedYear = foundedYearDigits(v.foundedYear ?? '');
44
+ return {
45
+ name: v.name.trim(),
46
+ industry: v.industry ?? undefined,
47
+ stage: v.stage ?? undefined,
48
+ ...(tagline ? { tagline } : {}),
49
+ ...(website ? { website } : {}),
50
+ ...(teamSize ? { teamSize: Number(teamSize) } : {}),
51
+ ...(foundedYear ? { foundedYear: Number(foundedYear) } : {}),
52
+ };
53
+ }
54
+ /** Answers with nothing filled in: what a new member starts from. */
55
+ export function emptyOnboardingForm() {
56
+ return readOnboardingDraft({});
57
+ }
58
+ /**
59
+ * True when the member has started describing a business: any of the
60
+ * venture's own answers. Then it is sent, and the schema asks for its name,
61
+ * industry and stage.
62
+ *
63
+ * The business type and the registration do not start it: they DESCRIBE a
64
+ * business. Picked without one, each is sent anyway and refused on its own
65
+ * field (`ventureRequired`: add the business, or clear this), never dropped
66
+ * without a word (hary's ruling).
67
+ */
68
+ export function ventureStarted(form) {
69
+ const v = form.venture;
70
+ return Boolean(v.name.trim() || v.website.trim() || v.industry || v.stage || v.teamSize.trim());
71
+ }
72
+ /**
73
+ * True when a place has been started: a city or a state given. A place opened
74
+ * and left empty is NO place (the member chose not to say), never a half
75
+ * answer to refuse; a place with only one of the two IS half an answer, and
76
+ * the schema asks for the other.
77
+ */
78
+ export function locationStarted(place) {
79
+ return Boolean(place && (place.city.trim() || (place.state ?? '').trim()));
80
+ }
81
+ /**
82
+ * The draft one step saves: exactly the fields that step owns
83
+ * (`FOUNDER_ONBOARDING_STEPS[].fields`, the same picks its validation uses),
84
+ * as typed. Only the step's own fields, so a save can never overwrite another
85
+ * step's answers (the server merges per field); raw, so a member who comes back
86
+ * sees what they typed. The photo step's field is its upload id, which the
87
+ * server holds rather than drafts.
88
+ */
89
+ export function onboardingDraftForStep(form, key) {
90
+ const step = FOUNDER_ONBOARDING_STEPS[ONBOARDING_STEP_KEYS.indexOf(key)];
91
+ const draft = {};
92
+ for (const field of step?.fields ?? [])
93
+ copyField(draft, form, field);
94
+ return draft;
95
+ }
96
+ /** One field across, typed by its key (0.18.0: the draft is the FORM's shape, never `unknown`). */
97
+ function copyField(to, from, key) {
98
+ to[key] = from[key];
99
+ }
100
+ /**
101
+ * What Finish sends, and what every step is CHECKED as (each step's slice
102
+ * reads its own fields from it).
103
+ *
104
+ * ⚠ ABSENT, NEVER A DEFAULT. The sibling gym product sent `bloodGroup ?? 'O+'`
105
+ * and recorded everyone who skipped the question as O-positive. An optional
106
+ * answer not given is left out; nothing is filled with a plausible value.
107
+ *
108
+ * Cleaned the way the schema reads it: the name tidied (`tidyFullName`), the
109
+ * phone to ten digits (`normalizePhone`), links given their scheme
110
+ * (`normalizeLinkUrl`), the place in the list's own spelling in India
111
+ * (`canonicalIndiaLocation`), the team size a number. A half-answered part is
112
+ * SENT, not hidden: a location without a city, a business without its stage,
113
+ * reach the schema and are refused there, under the field.
114
+ */
115
+ export function completeOnboardingBodyFrom(form) {
116
+ const headline = form.headline.trim();
117
+ const about = form.about.trim();
118
+ const links = {};
119
+ for (const k of Object.keys(form.links)) {
120
+ const url = normalizeLinkUrl(form.links[k]);
121
+ if (url)
122
+ links[k] = url;
123
+ }
124
+ const body = {
125
+ displayName: tidyFullName(form.displayName),
126
+ gender: form.gender ?? undefined,
127
+ dateOfBirth: form.dateOfBirth.trim(),
128
+ phone: normalizePhone(form.phone),
129
+ goals: form.goals,
130
+ interests: form.interests,
131
+ // Only agreement to the CURRENT text is sent; anything else is absent, so
132
+ // the schema refuses it as not agreed. Consent to an older text is not
133
+ // consent to this one.
134
+ guidelinesVersion: form.guidelinesVersion === GUIDELINES_VERSION ? GUIDELINES_VERSION : undefined,
135
+ };
136
+ if (headline)
137
+ body.headline = headline;
138
+ if (about)
139
+ body.about = about;
140
+ if (form.avatarMediaId)
141
+ body.avatarMediaId = form.avatarMediaId;
142
+ if (form.skills.length)
143
+ body.skills = form.skills;
144
+ if (Object.keys(links).length)
145
+ body.links = links;
146
+ if (form.location && locationStarted(form.location)) {
147
+ const place = canonicalIndiaLocation(form.location);
148
+ body.location = {
149
+ city: place.city,
150
+ ...(place.state ? { state: place.state } : {}),
151
+ country: place.country,
152
+ };
153
+ }
154
+ if (ventureStarted(form))
155
+ body.venture = ventureBodyFrom(form.venture);
156
+ // Sent whenever given: with a business they describe it, without one the
157
+ // schema refuses each on its own field rather than anything vanishing.
158
+ if (form.businessType)
159
+ body.businessType = form.businessType;
160
+ if (form.legalStructure)
161
+ body.legalStructure = form.legalStructure;
162
+ return body;
163
+ }
164
+ //# sourceMappingURL=onboarding-answers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"onboarding-answers.js","sourceRoot":"","sources":["../../../src/domains/onboarding/onboarding-answers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema — From a member's onboarding answers to what is sent (0.17.0)\n *\n * The answers are an `OnboardingForm` (the in-progress shape: raw text, `null`\n * for a choice not made). Two things are built from them, and until 0.17.0\n * each client built them by hand, so a second client (the app) would have had\n * to copy every rule below and keep it in step:\n *\n * the draft a step SAVES exactly the fields that step owns, as typed\n * (`onboardingDraftForStep`)\n * the body FINISH sends completed answers only, cleaned, every\n * unanswered optional field ABSENT\n * (`completeOnboardingBodyFrom`)\n *\n * Neither validates: `ONBOARDING_STEP_SCHEMAS` and `completeOnboardingBodySchema`\n * do, on what these build. A half-filled form builds a half-filled body on\n * purpose, so the schema refuses it with the reason under each field instead\n * of a builder hiding the gap.\n *\n * @module founder-schema/domains/onboarding/onboarding-answers\n */\n\nimport {\n type Industry,\n normalizeLinkUrl,\n normalizePhone,\n type StartupStage,\n tidyFullName,\n} from '../../core/common.js';\nimport { FOUNDED_YEAR_MIN } from '../../core/limits.js';\nimport { canonicalIndiaLocation } from '../location/india.js';\nimport {\n type CompleteOnboardingBodyInput,\n FOUNDER_ONBOARDING_STEPS,\n GUIDELINES_VERSION,\n ONBOARDING_STEP_KEYS,\n type OnboardingForm,\n type OnboardingStepKey,\n readOnboardingDraft,\n teamSizeDigits,\n} from './onboarding.js';\n\ntype DeepPartial<T> = T extends readonly (infer Item)[]\n ? DeepPartial<Item>[]\n : T extends object\n ? { [K in keyof T]?: DeepPartial<T[K]> }\n : T;\n\n/**\n * A founding year as typed, kept to digits, as many as a year has (0.18.0,\n * moved from the web's Me editor beside `teamSizeDigits`).\n */\nexport const foundedYearDigits = (raw: string): string =>\n raw.replace(/\\D/g, '').slice(0, String(FOUNDED_YEAR_MIN).length);\n\n/**\n * A venture body from what was typed (0.18.0): ONE builder for onboarding's\n * business step and Me's business editor, which each built it by hand and\n * cleaned the team size two ways. Text trimmed, the website made a link\n * (`normalizeLinkUrl`), digits kept (`teamSizeDigits`, `foundedYearDigits`);\n * an empty optional answer is ABSENT. A missing name, industry or stage is\n * left missing, so `ventureSchema` refuses it under its field.\n */\nexport function ventureBodyFrom(v: {\n name: string;\n website: string;\n industry: Industry | null;\n stage: StartupStage | null;\n teamSize: string;\n tagline?: string;\n foundedYear?: string;\n}): {\n name: string;\n industry: Industry | undefined;\n stage: StartupStage | undefined;\n tagline?: string;\n website?: string;\n teamSize?: number;\n foundedYear?: number;\n} {\n const tagline = v.tagline?.trim();\n const website = normalizeLinkUrl(v.website);\n const teamSize = teamSizeDigits(v.teamSize);\n const foundedYear = foundedYearDigits(v.foundedYear ?? '');\n return {\n name: v.name.trim(),\n industry: v.industry ?? undefined,\n stage: v.stage ?? undefined,\n ...(tagline ? { tagline } : {}),\n ...(website ? { website } : {}),\n ...(teamSize ? { teamSize: Number(teamSize) } : {}),\n ...(foundedYear ? { foundedYear: Number(foundedYear) } : {}),\n };\n}\n\n/**\n * What `completeOnboardingBodyFrom` builds: the finish body's INPUT, any part\n * of it possibly still missing. Not a valid body until\n * `completeOnboardingBodySchema` says so.\n */\nexport type CompleteOnboardingBodyCandidate = DeepPartial<CompleteOnboardingBodyInput>;\n\n/** Answers with nothing filled in: what a new member starts from. */\nexport function emptyOnboardingForm(): OnboardingForm {\n return readOnboardingDraft({});\n}\n\n/**\n * True when the member has started describing a business: any of the\n * venture's own answers. Then it is sent, and the schema asks for its name,\n * industry and stage.\n *\n * The business type and the registration do not start it: they DESCRIBE a\n * business. Picked without one, each is sent anyway and refused on its own\n * field (`ventureRequired`: add the business, or clear this), never dropped\n * without a word (hary's ruling).\n */\nexport function ventureStarted(form: Pick<OnboardingForm, 'venture'>): boolean {\n const v = form.venture;\n return Boolean(v.name.trim() || v.website.trim() || v.industry || v.stage || v.teamSize.trim());\n}\n\n/**\n * True when a place has been started: a city or a state given. A place opened\n * and left empty is NO place (the member chose not to say), never a half\n * answer to refuse; a place with only one of the two IS half an answer, and\n * the schema asks for the other.\n */\nexport function locationStarted(place: { city: string; state?: string | null } | null): boolean {\n return Boolean(place && (place.city.trim() || (place.state ?? '').trim()));\n}\n\n/**\n * The draft one step saves: exactly the fields that step owns\n * (`FOUNDER_ONBOARDING_STEPS[].fields`, the same picks its validation uses),\n * as typed. Only the step's own fields, so a save can never overwrite another\n * step's answers (the server merges per field); raw, so a member who comes back\n * sees what they typed. The photo step's field is its upload id, which the\n * server holds rather than drafts.\n */\nexport function onboardingDraftForStep(\n form: OnboardingForm,\n key: OnboardingStepKey,\n): Partial<OnboardingForm> {\n const step = FOUNDER_ONBOARDING_STEPS[ONBOARDING_STEP_KEYS.indexOf(key)];\n const draft: Partial<OnboardingForm> = {};\n for (const field of step?.fields ?? []) copyField(draft, form, field);\n return draft;\n}\n\n/** One field across, typed by its key (0.18.0: the draft is the FORM's shape, never `unknown`). */\nfunction copyField<K extends keyof OnboardingForm>(\n to: Partial<OnboardingForm>,\n from: OnboardingForm,\n key: K,\n): void {\n to[key] = from[key];\n}\n\n/**\n * What Finish sends, and what every step is CHECKED as (each step's slice\n * reads its own fields from it).\n *\n * ⚠ ABSENT, NEVER A DEFAULT. The sibling gym product sent `bloodGroup ?? 'O+'`\n * and recorded everyone who skipped the question as O-positive. An optional\n * answer not given is left out; nothing is filled with a plausible value.\n *\n * Cleaned the way the schema reads it: the name tidied (`tidyFullName`), the\n * phone to ten digits (`normalizePhone`), links given their scheme\n * (`normalizeLinkUrl`), the place in the list's own spelling in India\n * (`canonicalIndiaLocation`), the team size a number. A half-answered part is\n * SENT, not hidden: a location without a city, a business without its stage,\n * reach the schema and are refused there, under the field.\n */\nexport function completeOnboardingBodyFrom(form: OnboardingForm): CompleteOnboardingBodyCandidate {\n const headline = form.headline.trim();\n const about = form.about.trim();\n\n const links: Partial<Record<keyof OnboardingForm['links'], string>> = {};\n for (const k of Object.keys(form.links) as (keyof OnboardingForm['links'])[]) {\n const url = normalizeLinkUrl(form.links[k]);\n if (url) links[k] = url;\n }\n\n const body: CompleteOnboardingBodyCandidate = {\n displayName: tidyFullName(form.displayName),\n gender: form.gender ?? undefined,\n dateOfBirth: form.dateOfBirth.trim(),\n phone: normalizePhone(form.phone),\n goals: form.goals,\n interests: form.interests,\n // Only agreement to the CURRENT text is sent; anything else is absent, so\n // the schema refuses it as not agreed. Consent to an older text is not\n // consent to this one.\n guidelinesVersion:\n form.guidelinesVersion === GUIDELINES_VERSION ? GUIDELINES_VERSION : undefined,\n };\n if (headline) body.headline = headline;\n if (about) body.about = about;\n if (form.avatarMediaId) body.avatarMediaId = form.avatarMediaId;\n if (form.skills.length) body.skills = form.skills;\n if (Object.keys(links).length) body.links = links;\n\n if (form.location && locationStarted(form.location)) {\n const place = canonicalIndiaLocation(form.location);\n body.location = {\n city: place.city,\n ...(place.state ? { state: place.state } : {}),\n country: place.country,\n };\n }\n\n if (ventureStarted(form)) body.venture = ventureBodyFrom(form.venture);\n // Sent whenever given: with a business they describe it, without one the\n // schema refuses each on its own field rather than anything vanishing.\n if (form.businessType) body.businessType = form.businessType;\n if (form.legalStructure) body.legalStructure = form.legalStructure;\n\n return body;\n}\n"]}