founder-schema 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/README.md +110 -3
  2. package/dist/access/permissions.d.ts +3 -3
  3. package/dist/access/permissions.d.ts.map +1 -1
  4. package/dist/access/permissions.js.map +1 -1
  5. package/dist/core/common.d.ts +49 -28
  6. package/dist/core/common.d.ts.map +1 -1
  7. package/dist/core/common.js +65 -26
  8. package/dist/core/common.js.map +1 -1
  9. package/dist/core/deletion.d.ts +40 -0
  10. package/dist/core/deletion.d.ts.map +1 -0
  11. package/dist/core/deletion.js +39 -0
  12. package/dist/core/deletion.js.map +1 -0
  13. package/dist/core/limits.d.ts +25 -0
  14. package/dist/core/limits.d.ts.map +1 -1
  15. package/dist/core/limits.js +25 -0
  16. package/dist/core/limits.js.map +1 -1
  17. package/dist/core/validation.d.ts +13 -0
  18. package/dist/core/validation.d.ts.map +1 -1
  19. package/dist/core/validation.js +13 -0
  20. package/dist/core/validation.js.map +1 -1
  21. package/dist/domains/audit/audit.d.ts +36 -12
  22. package/dist/domains/audit/audit.d.ts.map +1 -1
  23. package/dist/domains/audit/audit.js +9 -10
  24. package/dist/domains/audit/audit.js.map +1 -1
  25. package/dist/domains/billing/billing.d.ts +29 -13
  26. package/dist/domains/billing/billing.d.ts.map +1 -1
  27. package/dist/domains/billing/billing.js +2 -3
  28. package/dist/domains/billing/billing.js.map +1 -1
  29. package/dist/domains/chat/chat.d.ts +19 -5
  30. package/dist/domains/chat/chat.d.ts.map +1 -1
  31. package/dist/domains/chat/chat.js +10 -10
  32. package/dist/domains/chat/chat.js.map +1 -1
  33. package/dist/domains/communities/communities.d.ts +95 -57
  34. package/dist/domains/communities/communities.d.ts.map +1 -1
  35. package/dist/domains/contributions/contributions.d.ts +28 -12
  36. package/dist/domains/contributions/contributions.d.ts.map +1 -1
  37. package/dist/domains/contributions/contributions.js +3 -0
  38. package/dist/domains/contributions/contributions.js.map +1 -1
  39. package/dist/domains/discover/discover.d.ts +2 -2
  40. package/dist/domains/feed/feed.d.ts +111 -63
  41. package/dist/domains/feed/feed.d.ts.map +1 -1
  42. package/dist/domains/feed/feed.js +3 -0
  43. package/dist/domains/feed/feed.js.map +1 -1
  44. package/dist/domains/location/areas.d.ts +338 -0
  45. package/dist/domains/location/areas.d.ts.map +1 -0
  46. package/dist/domains/location/areas.js +241 -0
  47. package/dist/domains/location/areas.js.map +1 -0
  48. package/dist/domains/location/india.d.ts +31 -123
  49. package/dist/domains/location/india.d.ts.map +1 -1
  50. package/dist/domains/location/india.js +98 -64
  51. package/dist/domains/location/india.js.map +1 -1
  52. package/dist/domains/media/media.d.ts +22 -0
  53. package/dist/domains/media/media.d.ts.map +1 -1
  54. package/dist/domains/media/media.js +20 -0
  55. package/dist/domains/media/media.js.map +1 -1
  56. package/dist/domains/members/members.d.ts +26 -9
  57. package/dist/domains/members/members.d.ts.map +1 -1
  58. package/dist/domains/members/members.js +9 -10
  59. package/dist/domains/members/members.js.map +1 -1
  60. package/dist/domains/membership/membership.d.ts +19 -0
  61. package/dist/domains/membership/membership.d.ts.map +1 -1
  62. package/dist/domains/membership/membership.js +10 -0
  63. package/dist/domains/membership/membership.js.map +1 -1
  64. package/dist/domains/moderation/moderation.d.ts +8 -2
  65. package/dist/domains/moderation/moderation.d.ts.map +1 -1
  66. package/dist/domains/moderation/moderation.js +3 -6
  67. package/dist/domains/moderation/moderation.js.map +1 -1
  68. package/dist/domains/notifications/notifications.d.ts +10 -6
  69. package/dist/domains/notifications/notifications.d.ts.map +1 -1
  70. package/dist/domains/onboarding/onboarding-answers.d.ts +9 -12
  71. package/dist/domains/onboarding/onboarding-answers.d.ts.map +1 -1
  72. package/dist/domains/onboarding/onboarding-answers.js +12 -19
  73. package/dist/domains/onboarding/onboarding-answers.js.map +1 -1
  74. package/dist/domains/onboarding/onboarding.d.ts +121 -98
  75. package/dist/domains/onboarding/onboarding.d.ts.map +1 -1
  76. package/dist/domains/onboarding/onboarding.js +30 -28
  77. package/dist/domains/onboarding/onboarding.js.map +1 -1
  78. package/dist/domains/people/person.d.ts +54 -0
  79. package/dist/domains/people/person.d.ts.map +1 -0
  80. package/dist/domains/people/person.js +73 -0
  81. package/dist/domains/people/person.js.map +1 -0
  82. package/dist/domains/products/products.d.ts +62 -33
  83. package/dist/domains/products/products.d.ts.map +1 -1
  84. package/dist/domains/products/products.js +3 -0
  85. package/dist/domains/products/products.js.map +1 -1
  86. package/dist/domains/profile/founder-profile.d.ts +93 -60
  87. package/dist/domains/profile/founder-profile.d.ts.map +1 -1
  88. package/dist/domains/profile/founder-profile.js +55 -25
  89. package/dist/domains/profile/founder-profile.js.map +1 -1
  90. package/dist/domains/reviews/reviews.d.ts +25 -15
  91. package/dist/domains/reviews/reviews.d.ts.map +1 -1
  92. package/dist/domains/support/support.d.ts +47 -10
  93. package/dist/domains/support/support.d.ts.map +1 -1
  94. package/dist/domains/support/support.js +4 -2
  95. package/dist/domains/support/support.js.map +1 -1
  96. package/dist/domains/team/team.d.ts +9 -3
  97. package/dist/domains/team/team.d.ts.map +1 -1
  98. package/dist/domains/team/team.js +3 -6
  99. package/dist/domains/team/team.js.map +1 -1
  100. package/dist/index.d.ts +13 -10
  101. package/dist/index.d.ts.map +1 -1
  102. package/dist/index.js +17 -11
  103. package/dist/index.js.map +1 -1
  104. package/package.json +3 -2
  105. package/dist/account.d.ts +0 -40
  106. package/dist/account.d.ts.map +0 -1
  107. package/dist/account.js +0 -39
  108. package/dist/account.js.map +0 -1
  109. package/dist/chat.d.ts +0 -187
  110. package/dist/chat.d.ts.map +0 -1
  111. package/dist/chat.js +0 -189
  112. package/dist/chat.js.map +0 -1
  113. package/dist/common.d.ts +0 -304
  114. package/dist/common.d.ts.map +0 -1
  115. package/dist/common.js +0 -340
  116. package/dist/common.js.map +0 -1
  117. package/dist/discover.d.ts +0 -245
  118. package/dist/discover.d.ts.map +0 -1
  119. package/dist/discover.js +0 -68
  120. package/dist/discover.js.map +0 -1
  121. package/dist/errors.d.ts +0 -85
  122. package/dist/errors.d.ts.map +0 -1
  123. package/dist/errors.js +0 -89
  124. package/dist/errors.js.map +0 -1
  125. package/dist/feed.d.ts +0 -1658
  126. package/dist/feed.d.ts.map +0 -1
  127. package/dist/feed.js +0 -623
  128. package/dist/feed.js.map +0 -1
  129. package/dist/founder-profile.d.ts +0 -387
  130. package/dist/founder-profile.d.ts.map +0 -1
  131. package/dist/founder-profile.js +0 -180
  132. package/dist/founder-profile.js.map +0 -1
  133. package/dist/limits.d.ts +0 -76
  134. package/dist/limits.d.ts.map +0 -1
  135. package/dist/limits.js +0 -84
  136. package/dist/limits.js.map +0 -1
  137. package/dist/onboarding.d.ts +0 -1018
  138. package/dist/onboarding.d.ts.map +0 -1
  139. package/dist/onboarding.js +0 -717
  140. package/dist/onboarding.js.map +0 -1
  141. package/dist/responses/common.d.ts +0 -167
  142. package/dist/responses/common.d.ts.map +0 -1
  143. package/dist/responses/common.js +0 -67
  144. package/dist/responses/common.js.map +0 -1
  145. package/dist/responses/index.d.ts +0 -7
  146. package/dist/responses/index.d.ts.map +0 -1
  147. package/dist/responses/index.js +0 -7
  148. package/dist/responses/index.js.map +0 -1
  149. package/dist/uploads.d.ts +0 -113
  150. package/dist/uploads.d.ts.map +0 -1
  151. package/dist/uploads.js +0 -106
  152. package/dist/uploads.js.map +0 -1
  153. package/dist/validation.d.ts +0 -136
  154. package/dist/validation.d.ts.map +0 -1
  155. package/dist/validation.js +0 -142
  156. package/dist/validation.js.map +0 -1
package/dist/common.d.ts DELETED
@@ -1,304 +0,0 @@
1
- /**
2
- * founder-schema — Shared helpers and core enums
3
- *
4
- * The stable building blocks reused across every Founder Circle schema.
5
- * Anything that both the backend and a client need to agree on lives here.
6
- *
7
- * @module founder-schema/common
8
- */
9
- import { z } from 'zod';
10
- /**
11
- * Converts empty / whitespace-only strings to `undefined` so Zod `.optional()`
12
- * treats them as "not provided".
13
- *
14
- * Why this exists: an HTML form submits `""` for a cleared field, and without
15
- * this an optional field would be stored as an empty string rather than left
16
- * alone. Jansathi shipped that bug in dating onboarding.
17
- */
18
- export declare const emptyStringToUndefined: (value: unknown) => unknown;
19
- /** Optional trimmed string. Empty strings are coerced to `undefined`. */
20
- export declare const optionalTrimmedString: (maxLength?: number) => z.ZodPreprocess<z.ZodOptional<z.ZodString>, unknown>;
21
- /** A Mongo ObjectId as it crosses the wire: 24 lowercase hex characters. */
22
- export declare const objectIdSchema: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
23
- /** Named for what it is on the wire, so it never collides with Mongoose's `ObjectId`. */
24
- export type ObjectIdString = z.infer<typeof objectIdSchema>;
25
- /**
26
- * A URL safe to store and, after the consumer's own check, to render.
27
- *
28
- * Use this for EVERY member-supplied URL. Never bare `z.url()`: it accepts any
29
- * scheme the URL parser accepts, so `javascript:alert(document.cookie)`,
30
- * `data:text/html,<script>...`, `vbscript:` and `file:///etc/passwd` all pass.
31
- * Each of those is stored XSS the moment a client renders it as an `href`, and
32
- * `data:` is the same in an `<img src>`.
33
- *
34
- * `hostname` is restricted too, not only the scheme. A bare host like
35
- * `localhost` or a literal IP is refused, which closes server-side request
36
- * forgery: this product runs on EC2 with an instance role, so a member-supplied
37
- * `http://169.254.169.254/latest/meta-data/` that anything server-side ever
38
- * fetches is a credential read.
39
- *
40
- * Validating here is necessary and not sufficient. A consumer still treats a
41
- * stored URL as untrusted at render time, because rows written before this
42
- * existed were never checked.
43
- *
44
- * ⚠️ IF THIS EVER GROWS A HOST ALLOWLIST, compare the PARSED hostname for
45
- * exact equality. Never `includes`, never `endsWith`, never a regex over the
46
- * whole URL. Each of these is a real bypass of a naive "is it YouTube" check,
47
- * and all three read as obviously fine:
48
- *
49
- * https://evil.example/?next=youtube.com/watch contains the string
50
- * youtube.com.evil.example starts with it
51
- * youtu.be.evil.example a suffix check passes it
52
- *
53
- * The same mistake in the opposite direction to the `javascript:` hole: there
54
- * the scheme was too permissive, here the host check only LOOKS strict.
55
- * (krish hit this rendering inline video, 2026-09-21, and handles it by parsing
56
- * and comparing the hostname exactly.)
57
- */
58
- export declare const safeUrlSchema: z.ZodURL;
59
- export type SafeUrl = z.infer<typeof safeUrlSchema>;
60
- /**
61
- * A URL a member TYPES: `safeUrlSchema` plus the `LINK_URL_MAX` ceiling, so
62
- * every member URL carries the same cap and no consumer has to remember it.
63
- *
64
- * Two schemas, not one, because the cap is about what a person types and is
65
- * wrong for what the server reads from elsewhere. Until 0.2.0 there was only
66
- * this one, and it capped a link preview's CDN image URL too: one 314-character
67
- * `imageUrl` would have failed a whole feed page.
68
- */
69
- export declare const httpUrlSchema: z.ZodURL;
70
- export type HttpUrl = z.infer<typeof httpUrlSchema>;
71
- /**
72
- * Public handle. Derived by the server at onboarding, changeable later.
73
- *
74
- * Rules are deliberately narrow because the handle appears in URLs and in
75
- * mentions: lowercase letters, digits, underscore and a single dot, 3 to 30
76
- * characters, must start with a letter.
77
- *
78
- * This is the FORMAT, and it is what every read shape uses. A handle being
79
- * CHOSEN must also not be reserved: that is `chosenUsernameSchema`.
80
- */
81
- export declare const USERNAME_MIN = 3;
82
- export declare const USERNAME_MAX = 30;
83
- export declare const usernameSchema: z.ZodString;
84
- export type Username = z.infer<typeof usernameSchema>;
85
- /**
86
- * Handles this product keeps for itself: route names a handle would shadow,
87
- * and words that would let a member pass as staff.
88
- *
89
- * ⚠ HERE, IN THE CONTRACT, since 0.2.0. It used to live in the backend so it
90
- * could grow without a publish, and nothing ever read it: the server derived
91
- * handles without checking it, so a member called "Support" was handed
92
- * `support`. One list, read by the server when it derives a handle and by any
93
- * form that lets a member choose one, is worth a publish per addition.
94
- *
95
- * Matched exactly, after lowercasing.
96
- */
97
- export declare const RESERVED_USERNAMES: readonly ["admin", "about", "api", "chat", "discover", "feed", "founder", "foundercircle", "help", "home", "me", "meetup", "moderator", "onboarding", "settings", "staff", "superadmin", "support", "system", "team"];
98
- export declare function isReservedUsername(handle: string): boolean;
99
- /**
100
- * A handle a member is CHOOSING: the format, and not reserved.
101
- *
102
- * ⚠ Not used on read shapes, deliberately. A handle stored before the reserved
103
- * check existed must still READ, or one member's old handle would fail every
104
- * page that shows their name.
105
- */
106
- export declare const chosenUsernameSchema: z.ZodString;
107
- /**
108
- * Stage of the founder's current venture.
109
- *
110
- * Ordered from earliest to latest so a range filter can compare by index. Add
111
- * new stages at the END or the stored ordinal of existing rows shifts.
112
- */
113
- export declare const STARTUP_STAGE_VALUES: readonly ["idea", "validation", "mvp", "pre_seed", "seed", "series_a_plus", "profitable", "exited"];
114
- export declare const startupStageSchema: z.ZodEnum<{
115
- idea: "idea";
116
- validation: "validation";
117
- mvp: "mvp";
118
- pre_seed: "pre_seed";
119
- seed: "seed";
120
- series_a_plus: "series_a_plus";
121
- profitable: "profitable";
122
- exited: "exited";
123
- }>;
124
- export type StartupStage = z.infer<typeof startupStageSchema>;
125
- /**
126
- * Industry the venture operates in. Kept as a closed list so Discover filters
127
- * are a single indexed equality match rather than a text search.
128
- */
129
- export declare const INDUSTRY_VALUES: readonly ["agritech", "ai_ml", "b2b_saas", "consumer", "climate", "d2c", "deeptech", "edtech", "fintech", "gaming", "healthtech", "logistics", "manufacturing", "marketplace", "media", "mobility", "proptech", "robotics", "services", "space", "other"];
130
- export declare const industrySchema: z.ZodEnum<{
131
- agritech: "agritech";
132
- ai_ml: "ai_ml";
133
- b2b_saas: "b2b_saas";
134
- consumer: "consumer";
135
- climate: "climate";
136
- d2c: "d2c";
137
- deeptech: "deeptech";
138
- edtech: "edtech";
139
- fintech: "fintech";
140
- gaming: "gaming";
141
- healthtech: "healthtech";
142
- logistics: "logistics";
143
- manufacturing: "manufacturing";
144
- marketplace: "marketplace";
145
- media: "media";
146
- mobility: "mobility";
147
- proptech: "proptech";
148
- robotics: "robotics";
149
- services: "services";
150
- space: "space";
151
- other: "other";
152
- }>;
153
- export type Industry = z.infer<typeof industrySchema>;
154
- /**
155
- * What the founder brings. Free-text skills were considered and rejected: two
156
- * people typing "growth" and "Growth Marketing" would never match each other in
157
- * a filter, and the Discover tab is the whole point of the product.
158
- */
159
- export declare const SKILL_VALUES: readonly ["engineering", "product", "design", "data", "growth", "sales", "marketing", "operations", "finance", "legal", "hr_people", "supply_chain", "research", "fundraising", "community"];
160
- export declare const skillSchema: z.ZodEnum<{
161
- engineering: "engineering";
162
- product: "product";
163
- design: "design";
164
- data: "data";
165
- growth: "growth";
166
- sales: "sales";
167
- marketing: "marketing";
168
- operations: "operations";
169
- finance: "finance";
170
- legal: "legal";
171
- hr_people: "hr_people";
172
- supply_chain: "supply_chain";
173
- research: "research";
174
- fundraising: "fundraising";
175
- community: "community";
176
- }>;
177
- export type Skill = z.infer<typeof skillSchema>;
178
- /**
179
- * What the member is here for. ONE list, replacing three.
180
- *
181
- * This was `lookingFor` with six values. The design draws eight "Goals &
182
- * Passion" tiles and four more on the business step; all three were the same
183
- * idea at three grains, and shipping them together would have meant nobody
184
- * could say which one Discover filters on - the answer settled by whichever
185
- * query got written first. Merged on hary's ruling, 2026-09-21, while there
186
- * were still no rows in any database.
187
- *
188
- * ⚠️ Stored per member and used as a Mongoose enum. Append only; removing a
189
- * value orphans documents already saved with it.
190
- *
191
- * ⚠️ `start_company` READS ODDLY next to its neighbours and is correct. It was
192
- * queried on the grounds that it restates `venture.stage: 'idea'`; it does not,
193
- * and the reason is worth keeping so nobody re-opens it. Inferring it from the
194
- * venture works only for a member who ALREADY HAS a venture row, and somebody
195
- * who wants to start a company is precisely the member with no venture at all,
196
- * so there is nothing to infer from. It also cannot express a founder with a
197
- * running company who wants to start another. Every other value is an ask of
198
- * other people and this one is an ask of the product, which is the whole of the
199
- * oddness. (hary, 2026-09-21.)
200
- */
201
- export declare const GOAL_VALUES: readonly ["find_cofounder", "find_investors", "find_customers", "hiring", "partnerships", "mentorship", "grow_network", "business_ideas", "market_insights", "learn_startup", "start_company"];
202
- export declare const goalSchema: z.ZodEnum<{
203
- find_cofounder: "find_cofounder";
204
- find_investors: "find_investors";
205
- find_customers: "find_customers";
206
- hiring: "hiring";
207
- partnerships: "partnerships";
208
- mentorship: "mentorship";
209
- grow_network: "grow_network";
210
- business_ideas: "business_ideas";
211
- market_insights: "market_insights";
212
- learn_startup: "learn_startup";
213
- start_company: "start_company";
214
- }>;
215
- export type Goal = z.infer<typeof goalSchema>;
216
- /**
217
- * Demographic only. Nothing here computes anything from it, unlike the sibling
218
- * gym product where gender feeds body-metric maths.
219
- *
220
- * Takes the family's three values and ADDS one rather than renaming any: a
221
- * network for founders should let somebody decline the question without having
222
- * to describe themselves as "other".
223
- */
224
- export declare const GENDER_VALUES: readonly ["male", "female", "other", "prefer_not_to_say"];
225
- export declare const genderSchema: z.ZodEnum<{
226
- other: "other";
227
- male: "male";
228
- female: "female";
229
- prefer_not_to_say: "prefer_not_to_say";
230
- }>;
231
- export type Gender = z.infer<typeof genderSchema>;
232
- /**
233
- * Indian mobile number: ten raw digits, no country code, matching the standard
234
- * used across this family. Display adds the `+91`.
235
- *
236
- * ⚠️ WHERE IT IS STORED IS NOT A SCHEMA QUESTION, and it has been ruled: the
237
- * phone lives on the account mirror, never as a second copy on the profile.
238
- * Two stored copies drift the first time somebody edits one, and the profile's
239
- * would be the copy nobody keeps in sync.
240
- */
241
- export declare const phoneSchema: z.ZodString;
242
- export type Phone = z.infer<typeof phoneSchema>;
243
- /**
244
- * Where the founder is based. City and country are what Discover filters on.
245
- *
246
- * ONE SHAPE for the profile, the Me tab's patch, onboarding's finish and the
247
- * location suggestion. It was written out three times until 0.2.0, the same
248
- * kind of copy that let the profile model lose Instagram.
249
- *
250
- * ⚠ `country` is REQUIRED and has NO default. A default here (it used to be
251
- * `'India'`) is destructive on every write path: a founder in Berlin who
252
- * corrects only their city would be moved to India by a value they never saw.
253
- * A body that carries a location carries the whole location. Every stored
254
- * location has a country, because the model requires one, so the read side
255
- * loses nothing.
256
- *
257
- * This is a DECLARED location, not a GPS reading. Jansathi's rule applies
258
- * here too: never write a declared location from a GPS capture.
259
- */
260
- export declare const locationSchema: z.ZodObject<{
261
- city: z.ZodString;
262
- state: z.ZodPreprocess<z.ZodOptional<z.ZodString>, unknown>;
263
- country: z.ZodString;
264
- }, z.core.$strip>;
265
- export type FounderLocation = z.infer<typeof locationSchema>;
266
- /**
267
- * `GET /api/v1/location/areas/reverse?lat=&lng=`: plain decimal degrees.
268
- *
269
- * A QUERY, so coercion is right here: everything in a URL is a string. An
270
- * emptied parameter is "missing", never 0, because 0,0 is a real place in the
271
- * Gulf of Guinea and would be looked up rather than refused.
272
- */
273
- export declare const coordinatesQuerySchema: z.ZodObject<{
274
- lat: z.ZodPreprocess<z.ZodCoercedNumber<unknown>, unknown>;
275
- lng: z.ZodPreprocess<z.ZodCoercedNumber<unknown>, unknown>;
276
- }, z.core.$strip>;
277
- export type CoordinatesQuery = z.infer<typeof coordinatesQuerySchema>;
278
- export type CoordinatesQueryInput = z.input<typeof coordinatesQuerySchema>;
279
- /**
280
- * The city these coordinates look like, for the member to CONFIRM, or `null`
281
- * when the lookup could not place them confidently (or they are outside India,
282
- * whose area data is all there is). A suggestion, never a write: only what the
283
- * member then submits becomes their location.
284
- */
285
- export declare const suggestedLocationSchema: z.ZodNullable<z.ZodObject<{
286
- city: z.ZodString;
287
- state: z.ZodPreprocess<z.ZodOptional<z.ZodString>, unknown>;
288
- country: z.ZodString;
289
- }, z.core.$strip>>;
290
- export type SuggestedLocation = z.infer<typeof suggestedLocationSchema>;
291
- /**
292
- * Cursor pagination query. Feed and Discover both use it.
293
- *
294
- * Cursor, not page number: the community feed changes under the reader, and an
295
- * offset would repeat or skip posts as new ones land.
296
- */
297
- export declare const paginationQuerySchema: z.ZodObject<{
298
- limit: z.ZodPreprocess<z.ZodDefault<z.ZodCoercedNumber<unknown>>, unknown>;
299
- cursor: z.ZodOptional<z.ZodString>;
300
- }, z.core.$strip>;
301
- export type PaginationQuery = z.infer<typeof paginationQuerySchema>;
302
- /** What a client may put IN the URL: every field optional, numbers as strings. */
303
- export type PaginationQueryInput = z.input<typeof paginationQuerySchema>;
304
- //# sourceMappingURL=common.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../src/common.ts"],"names":[],"mappings":"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"}
package/dist/common.js DELETED
@@ -1,340 +0,0 @@
1
- /**
2
- * founder-schema — Shared helpers and core enums
3
- *
4
- * The stable building blocks reused across every Founder Circle schema.
5
- * Anything that both the backend and a client need to agree on lives here.
6
- *
7
- * @module founder-schema/common
8
- */
9
- import { z } from 'zod';
10
- import { CITY_MAX, COUNTRY_MAX, LINK_URL_MAX, PAGE_SIZE_DEFAULT, PAGE_SIZE_MAX, STATE_MAX, } from './limits.js';
11
- import { VALIDATION_MESSAGE } from './validation.js';
12
- // ─── String helpers ──────────────────────────────────────────────────────────
13
- /**
14
- * Converts empty / whitespace-only strings to `undefined` so Zod `.optional()`
15
- * treats them as "not provided".
16
- *
17
- * Why this exists: an HTML form submits `""` for a cleared field, and without
18
- * this an optional field would be stored as an empty string rather than left
19
- * alone. Jansathi shipped that bug in dating onboarding.
20
- */
21
- export const emptyStringToUndefined = (value) => {
22
- if (typeof value === 'string' && value.trim() === '')
23
- return undefined;
24
- return value;
25
- };
26
- /** Optional trimmed string. Empty strings are coerced to `undefined`. */
27
- export const optionalTrimmedString = (maxLength = 100) => z.preprocess(emptyStringToUndefined, z.string().trim().max(maxLength).optional());
28
- /** A Mongo ObjectId as it crosses the wire: 24 lowercase hex characters. */
29
- export const objectIdSchema = z
30
- .string()
31
- .regex(/^[0-9a-f]{24}$/i, VALIDATION_MESSAGE.invalidId)
32
- .transform((v) => v.toLowerCase());
33
- // ─── URLs ────────────────────────────────────────────────────────────────────
34
- /**
35
- * A URL safe to store and, after the consumer's own check, to render.
36
- *
37
- * Use this for EVERY member-supplied URL. Never bare `z.url()`: it accepts any
38
- * scheme the URL parser accepts, so `javascript:alert(document.cookie)`,
39
- * `data:text/html,<script>...`, `vbscript:` and `file:///etc/passwd` all pass.
40
- * Each of those is stored XSS the moment a client renders it as an `href`, and
41
- * `data:` is the same in an `<img src>`.
42
- *
43
- * `hostname` is restricted too, not only the scheme. A bare host like
44
- * `localhost` or a literal IP is refused, which closes server-side request
45
- * forgery: this product runs on EC2 with an instance role, so a member-supplied
46
- * `http://169.254.169.254/latest/meta-data/` that anything server-side ever
47
- * fetches is a credential read.
48
- *
49
- * Validating here is necessary and not sufficient. A consumer still treats a
50
- * stored URL as untrusted at render time, because rows written before this
51
- * existed were never checked.
52
- *
53
- * ⚠️ IF THIS EVER GROWS A HOST ALLOWLIST, compare the PARSED hostname for
54
- * exact equality. Never `includes`, never `endsWith`, never a regex over the
55
- * whole URL. Each of these is a real bypass of a naive "is it YouTube" check,
56
- * and all three read as obviously fine:
57
- *
58
- * https://evil.example/?next=youtube.com/watch contains the string
59
- * youtube.com.evil.example starts with it
60
- * youtu.be.evil.example a suffix check passes it
61
- *
62
- * The same mistake in the opposite direction to the `javascript:` hole: there
63
- * the scheme was too permissive, here the host check only LOOKS strict.
64
- * (krish hit this rendering inline video, 2026-09-21, and handles it by parsing
65
- * and comparing the hostname exactly.)
66
- */
67
- export const safeUrlSchema = z.url({
68
- protocol: /^https?$/,
69
- hostname: z.regexes.domain,
70
- error: VALIDATION_MESSAGE.urlInvalid,
71
- });
72
- /**
73
- * A URL a member TYPES: `safeUrlSchema` plus the `LINK_URL_MAX` ceiling, so
74
- * every member URL carries the same cap and no consumer has to remember it.
75
- *
76
- * Two schemas, not one, because the cap is about what a person types and is
77
- * wrong for what the server reads from elsewhere. Until 0.2.0 there was only
78
- * this one, and it capped a link preview's CDN image URL too: one 314-character
79
- * `imageUrl` would have failed a whole feed page.
80
- */
81
- export const httpUrlSchema = safeUrlSchema.max(LINK_URL_MAX);
82
- // ─── Username ────────────────────────────────────────────────────────────────
83
- /**
84
- * Public handle. Derived by the server at onboarding, changeable later.
85
- *
86
- * Rules are deliberately narrow because the handle appears in URLs and in
87
- * mentions: lowercase letters, digits, underscore and a single dot, 3 to 30
88
- * characters, must start with a letter.
89
- *
90
- * This is the FORMAT, and it is what every read shape uses. A handle being
91
- * CHOSEN must also not be reserved: that is `chosenUsernameSchema`.
92
- */
93
- export const USERNAME_MIN = 3;
94
- export const USERNAME_MAX = 30;
95
- export const usernameSchema = z
96
- .string()
97
- .trim()
98
- .toLowerCase()
99
- // Plain bounds: the client words them from the issue's own minimum/maximum.
100
- .min(USERNAME_MIN)
101
- .max(USERNAME_MAX)
102
- .regex(/^[a-z][a-z0-9_]*(\.[a-z0-9_]+)?$/, VALIDATION_MESSAGE.usernameInvalid);
103
- /**
104
- * Handles this product keeps for itself: route names a handle would shadow,
105
- * and words that would let a member pass as staff.
106
- *
107
- * ⚠ HERE, IN THE CONTRACT, since 0.2.0. It used to live in the backend so it
108
- * could grow without a publish, and nothing ever read it: the server derived
109
- * handles without checking it, so a member called "Support" was handed
110
- * `support`. One list, read by the server when it derives a handle and by any
111
- * form that lets a member choose one, is worth a publish per addition.
112
- *
113
- * Matched exactly, after lowercasing.
114
- */
115
- export const RESERVED_USERNAMES = [
116
- 'admin',
117
- 'about',
118
- 'api',
119
- 'chat',
120
- 'discover',
121
- 'feed',
122
- 'founder',
123
- 'foundercircle',
124
- 'help',
125
- 'home',
126
- 'me',
127
- 'meetup',
128
- 'moderator',
129
- 'onboarding',
130
- 'settings',
131
- 'staff',
132
- 'superadmin',
133
- 'support',
134
- 'system',
135
- 'team',
136
- ];
137
- const RESERVED = new Set(RESERVED_USERNAMES);
138
- export function isReservedUsername(handle) {
139
- return RESERVED.has(handle.trim().toLowerCase());
140
- }
141
- /**
142
- * A handle a member is CHOOSING: the format, and not reserved.
143
- *
144
- * ⚠ Not used on read shapes, deliberately. A handle stored before the reserved
145
- * check existed must still READ, or one member's old handle would fail every
146
- * page that shows their name.
147
- */
148
- export const chosenUsernameSchema = usernameSchema.refine((u) => !isReservedUsername(u), {
149
- error: VALIDATION_MESSAGE.usernameReserved,
150
- });
151
- // ─── Founder-facing enums ────────────────────────────────────────────────────
152
- /**
153
- * Stage of the founder's current venture.
154
- *
155
- * Ordered from earliest to latest so a range filter can compare by index. Add
156
- * new stages at the END or the stored ordinal of existing rows shifts.
157
- */
158
- export const STARTUP_STAGE_VALUES = [
159
- 'idea',
160
- 'validation',
161
- 'mvp',
162
- 'pre_seed',
163
- 'seed',
164
- 'series_a_plus',
165
- 'profitable',
166
- 'exited',
167
- ];
168
- export const startupStageSchema = z.enum(STARTUP_STAGE_VALUES);
169
- /**
170
- * Industry the venture operates in. Kept as a closed list so Discover filters
171
- * are a single indexed equality match rather than a text search.
172
- */
173
- export const INDUSTRY_VALUES = [
174
- 'agritech',
175
- 'ai_ml',
176
- 'b2b_saas',
177
- 'consumer',
178
- 'climate',
179
- 'd2c',
180
- 'deeptech',
181
- 'edtech',
182
- 'fintech',
183
- 'gaming',
184
- 'healthtech',
185
- 'logistics',
186
- 'manufacturing',
187
- 'marketplace',
188
- 'media',
189
- 'mobility',
190
- 'proptech',
191
- 'robotics',
192
- 'services',
193
- 'space',
194
- 'other',
195
- ];
196
- export const industrySchema = z.enum(INDUSTRY_VALUES);
197
- /**
198
- * What the founder brings. Free-text skills were considered and rejected: two
199
- * people typing "growth" and "Growth Marketing" would never match each other in
200
- * a filter, and the Discover tab is the whole point of the product.
201
- */
202
- export const SKILL_VALUES = [
203
- 'engineering',
204
- 'product',
205
- 'design',
206
- 'data',
207
- 'growth',
208
- 'sales',
209
- 'marketing',
210
- 'operations',
211
- 'finance',
212
- 'legal',
213
- 'hr_people',
214
- 'supply_chain',
215
- 'research',
216
- 'fundraising',
217
- 'community',
218
- ];
219
- export const skillSchema = z.enum(SKILL_VALUES);
220
- /**
221
- * What the member is here for. ONE list, replacing three.
222
- *
223
- * This was `lookingFor` with six values. The design draws eight "Goals &
224
- * Passion" tiles and four more on the business step; all three were the same
225
- * idea at three grains, and shipping them together would have meant nobody
226
- * could say which one Discover filters on - the answer settled by whichever
227
- * query got written first. Merged on hary's ruling, 2026-09-21, while there
228
- * were still no rows in any database.
229
- *
230
- * ⚠️ Stored per member and used as a Mongoose enum. Append only; removing a
231
- * value orphans documents already saved with it.
232
- *
233
- * ⚠️ `start_company` READS ODDLY next to its neighbours and is correct. It was
234
- * queried on the grounds that it restates `venture.stage: 'idea'`; it does not,
235
- * and the reason is worth keeping so nobody re-opens it. Inferring it from the
236
- * venture works only for a member who ALREADY HAS a venture row, and somebody
237
- * who wants to start a company is precisely the member with no venture at all,
238
- * so there is nothing to infer from. It also cannot express a founder with a
239
- * running company who wants to start another. Every other value is an ask of
240
- * other people and this one is an ask of the product, which is the whole of the
241
- * oddness. (hary, 2026-09-21.)
242
- */
243
- export const GOAL_VALUES = [
244
- 'find_cofounder',
245
- 'find_investors',
246
- 'find_customers',
247
- 'hiring',
248
- 'partnerships',
249
- 'mentorship',
250
- 'grow_network',
251
- 'business_ideas',
252
- 'market_insights',
253
- 'learn_startup',
254
- 'start_company',
255
- ];
256
- export const goalSchema = z.enum(GOAL_VALUES);
257
- // ─── Gender ──────────────────────────────────────────────────────────────────
258
- /**
259
- * Demographic only. Nothing here computes anything from it, unlike the sibling
260
- * gym product where gender feeds body-metric maths.
261
- *
262
- * Takes the family's three values and ADDS one rather than renaming any: a
263
- * network for founders should let somebody decline the question without having
264
- * to describe themselves as "other".
265
- */
266
- export const GENDER_VALUES = ['male', 'female', 'other', 'prefer_not_to_say'];
267
- export const genderSchema = z.enum(GENDER_VALUES);
268
- // ─── Phone ───────────────────────────────────────────────────────────────────
269
- /**
270
- * Indian mobile number: ten raw digits, no country code, matching the standard
271
- * used across this family. Display adds the `+91`.
272
- *
273
- * ⚠️ WHERE IT IS STORED IS NOT A SCHEMA QUESTION, and it has been ruled: the
274
- * phone lives on the account mirror, never as a second copy on the profile.
275
- * Two stored copies drift the first time somebody edits one, and the profile's
276
- * would be the copy nobody keeps in sync.
277
- */
278
- export const phoneSchema = z
279
- .string()
280
- .trim()
281
- .regex(/^[6-9]\d{9}$/, VALIDATION_MESSAGE.phoneInvalid);
282
- // ─── Location ────────────────────────────────────────────────────────────────
283
- /**
284
- * Where the founder is based. City and country are what Discover filters on.
285
- *
286
- * ONE SHAPE for the profile, the Me tab's patch, onboarding's finish and the
287
- * location suggestion. It was written out three times until 0.2.0, the same
288
- * kind of copy that let the profile model lose Instagram.
289
- *
290
- * ⚠ `country` is REQUIRED and has NO default. A default here (it used to be
291
- * `'India'`) is destructive on every write path: a founder in Berlin who
292
- * corrects only their city would be moved to India by a value they never saw.
293
- * A body that carries a location carries the whole location. Every stored
294
- * location has a country, because the model requires one, so the read side
295
- * loses nothing.
296
- *
297
- * This is a DECLARED location, not a GPS reading. Jansathi's rule applies
298
- * here too: never write a declared location from a GPS capture.
299
- */
300
- export const locationSchema = z.object({
301
- city: z.string().trim().min(1).max(CITY_MAX),
302
- state: optionalTrimmedString(STATE_MAX),
303
- country: z.string().trim().min(1).max(COUNTRY_MAX),
304
- });
305
- /**
306
- * `GET /api/v1/location/areas/reverse?lat=&lng=`: plain decimal degrees.
307
- *
308
- * A QUERY, so coercion is right here: everything in a URL is a string. An
309
- * emptied parameter is "missing", never 0, because 0,0 is a real place in the
310
- * Gulf of Guinea and would be looked up rather than refused.
311
- */
312
- export const coordinatesQuerySchema = z.object({
313
- lat: z.preprocess(emptyStringToUndefined, z.coerce.number().min(-90).max(90)),
314
- lng: z.preprocess(emptyStringToUndefined, z.coerce.number().min(-180).max(180)),
315
- });
316
- /**
317
- * The city these coordinates look like, for the member to CONFIRM, or `null`
318
- * when the lookup could not place them confidently (or they are outside India,
319
- * whose area data is all there is). A suggestion, never a write: only what the
320
- * member then submits becomes their location.
321
- */
322
- export const suggestedLocationSchema = locationSchema.nullable();
323
- // ─── Pagination ──────────────────────────────────────────────────────────────
324
- /**
325
- * Cursor pagination query. Feed and Discover both use it.
326
- *
327
- * Cursor, not page number: the community feed changes under the reader, and an
328
- * offset would repeat or skip posts as new ones land.
329
- */
330
- export const paginationQuerySchema = z.object({
331
- /**
332
- * `?limit=` with nothing after it is treated as "not provided", not as a
333
- * validation failure. A client that clears the control sends the empty
334
- * string, and `z.coerce.number()` turns that into 0, which then fails
335
- * `.min(1)`: the member sees a 400 for a page they never asked to size.
336
- */
337
- limit: z.preprocess(emptyStringToUndefined, z.coerce.number().int().min(1).max(PAGE_SIZE_MAX).default(PAGE_SIZE_DEFAULT)),
338
- cursor: z.string().trim().min(1).optional(),
339
- });
340
- //# sourceMappingURL=common.js.map