founder-schema 0.17.0 → 0.19.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 +186 -17
- package/dist/access/access.d.ts +1 -1
- package/dist/access/access.d.ts.map +1 -1
- package/dist/access/access.js +4 -1
- package/dist/access/access.js.map +1 -1
- package/dist/access/permissions.d.ts +27 -29
- package/dist/access/permissions.d.ts.map +1 -1
- package/dist/access/permissions.js +39 -3
- package/dist/access/permissions.js.map +1 -1
- package/dist/core/common.d.ts +54 -23
- package/dist/core/common.d.ts.map +1 -1
- package/dist/core/common.js +49 -24
- package/dist/core/common.js.map +1 -1
- package/dist/core/errors.d.ts +7 -5
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +17 -11
- package/dist/core/errors.js.map +1 -1
- package/dist/core/limits.d.ts +73 -17
- package/dist/core/limits.d.ts.map +1 -1
- package/dist/core/limits.js +77 -22
- package/dist/core/limits.js.map +1 -1
- package/dist/core/money.d.ts +33 -0
- package/dist/core/money.d.ts.map +1 -0
- package/dist/core/money.js +36 -0
- package/dist/core/money.js.map +1 -0
- package/dist/core/responses.d.ts +14 -4
- package/dist/core/responses.d.ts.map +1 -1
- package/dist/core/responses.js.map +1 -1
- package/dist/core/validation.d.ts +19 -2
- package/dist/core/validation.d.ts.map +1 -1
- package/dist/core/validation.js +19 -2
- package/dist/core/validation.js.map +1 -1
- package/dist/domains/billing/billing.d.ts +61 -8
- package/dist/domains/billing/billing.d.ts.map +1 -1
- package/dist/domains/billing/billing.js +58 -22
- package/dist/domains/billing/billing.js.map +1 -1
- package/dist/domains/billing/gst.d.ts.map +1 -1
- package/dist/domains/billing/gst.js +4 -3
- package/dist/domains/billing/gst.js.map +1 -1
- package/dist/domains/chat/chat.d.ts +0 -21
- package/dist/domains/chat/chat.d.ts.map +1 -1
- package/dist/domains/chat/chat.js +0 -20
- package/dist/domains/chat/chat.js.map +1 -1
- package/dist/domains/communities/communities.d.ts +30 -12
- package/dist/domains/communities/communities.d.ts.map +1 -1
- package/dist/domains/communities/communities.js +43 -15
- package/dist/domains/communities/communities.js.map +1 -1
- package/dist/domains/contributions/contributions.d.ts +1 -1
- package/dist/domains/contributions/contributions.js +1 -1
- package/dist/domains/contributions/contributions.js.map +1 -1
- package/dist/domains/dashboard/dashboard.d.ts +3 -1
- package/dist/domains/dashboard/dashboard.d.ts.map +1 -1
- package/dist/domains/dashboard/dashboard.js +23 -7
- package/dist/domains/dashboard/dashboard.js.map +1 -1
- package/dist/domains/discover/discover.d.ts +8 -8
- package/dist/domains/feed/feed.d.ts +55 -153
- package/dist/domains/feed/feed.d.ts.map +1 -1
- package/dist/domains/feed/feed.js +53 -58
- package/dist/domains/feed/feed.js.map +1 -1
- package/dist/domains/location/areas.d.ts +331 -0
- package/dist/domains/location/areas.d.ts.map +1 -0
- package/dist/domains/location/areas.js +238 -0
- package/dist/domains/location/areas.js.map +1 -0
- package/dist/domains/location/india.d.ts +41 -123
- package/dist/domains/location/india.d.ts.map +1 -1
- package/dist/domains/location/india.js +121 -59
- package/dist/domains/location/india.js.map +1 -1
- package/dist/domains/members/members.d.ts +2 -2
- package/dist/domains/membership/membership.d.ts +26 -0
- package/dist/domains/membership/membership.d.ts.map +1 -1
- package/dist/domains/membership/membership.js +55 -1
- package/dist/domains/membership/membership.js.map +1 -1
- package/dist/domains/moderation/moderation.d.ts +2 -2
- package/dist/domains/moderation/moderation.js +3 -3
- package/dist/domains/moderation/moderation.js.map +1 -1
- package/dist/domains/notifications/notifications.d.ts +1 -10
- package/dist/domains/notifications/notifications.d.ts.map +1 -1
- package/dist/domains/notifications/notifications.js +2 -3
- package/dist/domains/notifications/notifications.js.map +1 -1
- package/dist/domains/onboarding/onboarding-answers.d.ts +42 -14
- package/dist/domains/onboarding/onboarding-answers.d.ts.map +1 -1
- package/dist/domains/onboarding/onboarding-answers.js +49 -33
- package/dist/domains/onboarding/onboarding-answers.js.map +1 -1
- package/dist/domains/onboarding/onboarding.d.ts +332 -138
- package/dist/domains/onboarding/onboarding.d.ts.map +1 -1
- package/dist/domains/onboarding/onboarding.js +47 -59
- package/dist/domains/onboarding/onboarding.js.map +1 -1
- package/dist/domains/products/products.d.ts +43 -8
- package/dist/domains/products/products.d.ts.map +1 -1
- package/dist/domains/products/products.js +57 -11
- package/dist/domains/products/products.js.map +1 -1
- package/dist/domains/profile/founder-profile.d.ts +123 -143
- package/dist/domains/profile/founder-profile.d.ts.map +1 -1
- package/dist/domains/profile/founder-profile.js +77 -35
- package/dist/domains/profile/founder-profile.js.map +1 -1
- package/dist/domains/profile/my-profile.d.ts +3 -3
- package/dist/domains/reviews/reviews.d.ts +1 -1
- package/dist/domains/reviews/reviews.d.ts.map +1 -1
- package/dist/domains/reviews/reviews.js +2 -2
- package/dist/domains/reviews/reviews.js.map +1 -1
- package/dist/domains/support/support.d.ts +22 -3
- package/dist/domains/support/support.d.ts.map +1 -1
- package/dist/domains/support/support.js +19 -5
- package/dist/domains/support/support.js.map +1 -1
- 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/domains/uploads/uploads.d.ts +7 -22
- package/dist/domains/uploads/uploads.d.ts.map +1 -1
- package/dist/domains/uploads/uploads.js +7 -21
- package/dist/domains/uploads/uploads.js.map +1 -1
- package/dist/env.d.ts +26 -13
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +58 -23
- package/dist/env.js.map +1 -1
- package/dist/index.d.ts +19 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/core/common.d.ts
CHANGED
|
@@ -16,8 +16,14 @@ import { z } from 'zod';
|
|
|
16
16
|
* alone. Jansathi shipped that bug in dating onboarding.
|
|
17
17
|
*/
|
|
18
18
|
export declare const emptyStringToUndefined: (value: unknown) => unknown;
|
|
19
|
-
/**
|
|
20
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Optional trimmed string: a blank one (`''`, spaces) is absent (`undefined`).
|
|
21
|
+
* Trimmed, checked, then blank dropped, never a preprocess (0.18.0: a
|
|
22
|
+
* preprocess types its INPUT as `unknown`, so a caller typing a body by
|
|
23
|
+
* `z.input` could send a number and still compile). `.optional()` OUTSIDE, so
|
|
24
|
+
* the key stays optional in the output type as well as the input.
|
|
25
|
+
*/
|
|
26
|
+
export declare const optionalTrimmedString: (maxLength?: number) => z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string | undefined, string>>>;
|
|
21
27
|
/**
|
|
22
28
|
* A repeated query param (`?skill=growth&skill=sales`) or a single value,
|
|
23
29
|
* always as an array, at most `max` values.
|
|
@@ -112,12 +118,13 @@ export type ProductListingRequirement = z.infer<typeof productListingRequirement
|
|
|
112
118
|
* is shared as a link and played in the card, never uploaded. The web plays it
|
|
113
119
|
* through youtube-nocookie.com, the one video frame its CSP admits.
|
|
114
120
|
*/
|
|
115
|
-
export declare const YOUTUBE_HOSTS: readonly ["youtube.com", "www.youtube.com", "m.youtube.com", "youtu.be", "youtube-nocookie.com", "www.youtube-nocookie.com"];
|
|
121
|
+
export declare const YOUTUBE_HOSTS: readonly ["youtube.com", "www.youtube.com", "m.youtube.com", "music.youtube.com", "youtu.be", "www.youtu.be", "youtube-nocookie.com", "www.youtube-nocookie.com"];
|
|
116
122
|
/**
|
|
117
123
|
* The 11-character id of a YouTube VIDEO link, or null for anything else (a
|
|
118
124
|
* channel, a playlist, the home page, another site): what a page can embed.
|
|
119
|
-
* Reads `youtu.be/<id>`, `/watch?v=<id>`, and `/shorts/`, `/embed/`, `/live
|
|
120
|
-
* on a `YOUTUBE_HOSTS` host (0.7.0
|
|
125
|
+
* Reads `youtu.be/<id>`, `/watch?v=<id>`, and `/shorts/`, `/embed/`, `/live/`,
|
|
126
|
+
* `/v/<id>` on a `YOUTUBE_HOSTS` host (0.7.0; `/v/` and the feed's hosts
|
|
127
|
+
* 0.18.0). THE rule for a product's video and a post's embed alike.
|
|
121
128
|
*/
|
|
122
129
|
export declare function youtubeVideoId(url: string): string | null;
|
|
123
130
|
/**
|
|
@@ -273,11 +280,16 @@ export declare const goalSchema: z.ZodEnum<{
|
|
|
273
280
|
attend_events: "attend_events";
|
|
274
281
|
}>;
|
|
275
282
|
export type Goal = z.infer<typeof goalSchema>;
|
|
283
|
+
/**
|
|
284
|
+
* True when no value is in the list twice: THE duplicate rule (0.18.0, exported;
|
|
285
|
+
* every list in this package refines with it instead of its own copy).
|
|
286
|
+
*/
|
|
287
|
+
export declare const eachOnce: (items: readonly unknown[]) => boolean;
|
|
276
288
|
/**
|
|
277
289
|
* A member's goals as a list (0.17.0): each goal once, at most every goal.
|
|
278
|
-
* ONE rule for onboarding's finish
|
|
279
|
-
*
|
|
280
|
-
* one, and the profile would show it twice.
|
|
290
|
+
* ONE rule for onboarding's finish and the Me tab's editor, which both add the
|
|
291
|
+
* minimum (`requiredGoalListSchema`, 0.18.0): two copies of one goal met the
|
|
292
|
+
* minimum while meaning one, and the profile would show it twice.
|
|
281
293
|
*/
|
|
282
294
|
export declare const goalListSchema: z.ZodArray<z.ZodEnum<{
|
|
283
295
|
find_cofounder: "find_cofounder";
|
|
@@ -299,6 +311,31 @@ export declare const goalListSchema: z.ZodArray<z.ZodEnum<{
|
|
|
299
311
|
find_service_providers: "find_service_providers";
|
|
300
312
|
attend_events: "attend_events";
|
|
301
313
|
}>>;
|
|
314
|
+
/**
|
|
315
|
+
* A member's goals where they are REQUIRED (0.18.0): at least `GOALS_MIN`.
|
|
316
|
+
* ONE schema for onboarding's finish and the Me editor (the user: "we already
|
|
317
|
+
* have at least one goal required"), so the two can never disagree.
|
|
318
|
+
*/
|
|
319
|
+
export declare const requiredGoalListSchema: z.ZodArray<z.ZodEnum<{
|
|
320
|
+
find_cofounder: "find_cofounder";
|
|
321
|
+
find_investors: "find_investors";
|
|
322
|
+
find_customers: "find_customers";
|
|
323
|
+
hiring: "hiring";
|
|
324
|
+
partnerships: "partnerships";
|
|
325
|
+
mentorship: "mentorship";
|
|
326
|
+
grow_network: "grow_network";
|
|
327
|
+
business_ideas: "business_ideas";
|
|
328
|
+
market_insights: "market_insights";
|
|
329
|
+
learn_startup: "learn_startup";
|
|
330
|
+
start_company: "start_company";
|
|
331
|
+
get_feedback: "get_feedback";
|
|
332
|
+
find_testers: "find_testers";
|
|
333
|
+
mentor_others: "mentor_others";
|
|
334
|
+
invest_in_startups: "invest_in_startups";
|
|
335
|
+
join_startup: "join_startup";
|
|
336
|
+
find_service_providers: "find_service_providers";
|
|
337
|
+
attend_events: "attend_events";
|
|
338
|
+
}>>;
|
|
302
339
|
/** A member's skills (0.17.0): each skill once, at most `SKILLS_MAX`. ONE rule for onboarding and Me. */
|
|
303
340
|
export declare const skillListSchema: z.ZodArray<z.ZodEnum<{
|
|
304
341
|
engineering: "engineering";
|
|
@@ -401,28 +438,22 @@ export declare function normalizePhone(raw: string): string;
|
|
|
401
438
|
* location has a country, because the model requires one, so the read side
|
|
402
439
|
* loses nothing.
|
|
403
440
|
*
|
|
404
|
-
* This is a DECLARED location, not a GPS reading
|
|
405
|
-
*
|
|
441
|
+
* This is a DECLARED location, not a GPS reading (Jansathi's rule): a GPS
|
|
442
|
+
* fix only ever fills the member's field; the member saves it (0.19.0,
|
|
443
|
+
* `domains/location/areas.ts`).
|
|
406
444
|
*/
|
|
407
445
|
export declare const locationSchema: z.ZodObject<{
|
|
408
446
|
city: z.ZodString;
|
|
409
|
-
state: z.
|
|
447
|
+
state: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string | undefined, string>>>;
|
|
410
448
|
country: z.ZodString;
|
|
411
449
|
}, z.core.$strip>;
|
|
412
450
|
export type FounderLocation = z.infer<typeof locationSchema>;
|
|
413
451
|
/**
|
|
414
|
-
*
|
|
415
|
-
*
|
|
416
|
-
*
|
|
417
|
-
* emptied parameter is "missing", never 0, because 0,0 is a real place in the
|
|
418
|
-
* Gulf of Guinea and would be looked up rather than refused.
|
|
452
|
+
* A whole number in a QUERY (everything in a URL is text, so coercion is right
|
|
453
|
+
* here and only here). An emptied parameter is "not provided": the fallback,
|
|
454
|
+
* never 0 by accident.
|
|
419
455
|
*/
|
|
420
|
-
export declare const
|
|
421
|
-
lat: z.ZodPreprocess<z.ZodCoercedNumber<unknown>, unknown>;
|
|
422
|
-
lng: z.ZodPreprocess<z.ZodCoercedNumber<unknown>, unknown>;
|
|
423
|
-
}, z.core.$strip>;
|
|
424
|
-
export type CoordinatesQuery = z.infer<typeof coordinatesQuerySchema>;
|
|
425
|
-
export type CoordinatesQueryInput = z.input<typeof coordinatesQuerySchema>;
|
|
456
|
+
export declare const queryInt: (min: number, max: number, fallback: number) => z.ZodPreprocess<z.ZodDefault<z.ZodCoercedNumber<unknown>>, unknown>;
|
|
426
457
|
/**
|
|
427
458
|
* Cursor pagination query. Feed and Discover both use it.
|
|
428
459
|
*
|
|
@@ -431,7 +462,7 @@ export type CoordinatesQueryInput = z.input<typeof coordinatesQuerySchema>;
|
|
|
431
462
|
*/
|
|
432
463
|
export declare const paginationQuerySchema: z.ZodObject<{
|
|
433
464
|
limit: z.ZodPreprocess<z.ZodDefault<z.ZodCoercedNumber<unknown>>, unknown>;
|
|
434
|
-
cursor: z.ZodOptional<z.ZodString>;
|
|
465
|
+
cursor: z.ZodPreprocess<z.ZodOptional<z.ZodString>, unknown>;
|
|
435
466
|
}, z.core.$strip>;
|
|
436
467
|
export type PaginationQuery = z.infer<typeof paginationQuerySchema>;
|
|
437
468
|
/** What a client may put IN the URL: every field optional, numbers as strings. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/core/common.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/core/common.ts"],"names":[],"mappings":"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"}
|
package/dist/core/common.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @module founder-schema/core/common
|
|
8
8
|
*/
|
|
9
9
|
import { z } from 'zod';
|
|
10
|
-
import { CITY_MAX, COUNTRY_MAX, DISPLAY_NAME_MAX, LINK_URL_MAX, PAGE_SIZE_DEFAULT, PAGE_SIZE_MAX, SKILLS_MAX, STATE_MAX, } from './limits.js';
|
|
10
|
+
import { CITY_MAX, COUNTRY_MAX, CURSOR_MAX, DISPLAY_NAME_MAX, GOALS_MIN, LINK_URL_MAX, PAGE_SIZE_DEFAULT, PAGE_SIZE_MAX, SKILLS_MAX, STATE_MAX, } from './limits.js';
|
|
11
11
|
import { VALIDATION_MESSAGE } from './validation.js';
|
|
12
12
|
// ─── String helpers ──────────────────────────────────────────────────────────
|
|
13
13
|
/**
|
|
@@ -23,8 +23,19 @@ export const emptyStringToUndefined = (value) => {
|
|
|
23
23
|
return undefined;
|
|
24
24
|
return value;
|
|
25
25
|
};
|
|
26
|
-
/**
|
|
27
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Optional trimmed string: a blank one (`''`, spaces) is absent (`undefined`).
|
|
28
|
+
* Trimmed, checked, then blank dropped, never a preprocess (0.18.0: a
|
|
29
|
+
* preprocess types its INPUT as `unknown`, so a caller typing a body by
|
|
30
|
+
* `z.input` could send a number and still compile). `.optional()` OUTSIDE, so
|
|
31
|
+
* the key stays optional in the output type as well as the input.
|
|
32
|
+
*/
|
|
33
|
+
export const optionalTrimmedString = (maxLength = 100) => z
|
|
34
|
+
.string()
|
|
35
|
+
.trim()
|
|
36
|
+
.max(maxLength)
|
|
37
|
+
.transform((value) => value || undefined)
|
|
38
|
+
.optional();
|
|
28
39
|
/**
|
|
29
40
|
* A repeated query param (`?skill=growth&skill=sales`) or a single value,
|
|
30
41
|
* always as an array, at most `max` values.
|
|
@@ -139,7 +150,10 @@ export const YOUTUBE_HOSTS = [
|
|
|
139
150
|
'youtube.com',
|
|
140
151
|
'www.youtube.com',
|
|
141
152
|
'm.youtube.com',
|
|
153
|
+
// 0.18.0: the feed's links too, so one rule plays a post's video and a product's.
|
|
154
|
+
'music.youtube.com',
|
|
142
155
|
'youtu.be',
|
|
156
|
+
'www.youtu.be',
|
|
143
157
|
// The privacy-mode player's own links (0.7.0).
|
|
144
158
|
'youtube-nocookie.com',
|
|
145
159
|
'www.youtube-nocookie.com',
|
|
@@ -147,8 +161,9 @@ export const YOUTUBE_HOSTS = [
|
|
|
147
161
|
/**
|
|
148
162
|
* The 11-character id of a YouTube VIDEO link, or null for anything else (a
|
|
149
163
|
* channel, a playlist, the home page, another site): what a page can embed.
|
|
150
|
-
* Reads `youtu.be/<id>`, `/watch?v=<id>`, and `/shorts/`, `/embed/`, `/live
|
|
151
|
-
* on a `YOUTUBE_HOSTS` host (0.7.0
|
|
164
|
+
* Reads `youtu.be/<id>`, `/watch?v=<id>`, and `/shorts/`, `/embed/`, `/live/`,
|
|
165
|
+
* `/v/<id>` on a `YOUTUBE_HOSTS` host (0.7.0; `/v/` and the feed's hosts
|
|
166
|
+
* 0.18.0). THE rule for a product's video and a post's embed alike.
|
|
152
167
|
*/
|
|
153
168
|
export function youtubeVideoId(url) {
|
|
154
169
|
let u;
|
|
@@ -161,11 +176,11 @@ export function youtubeVideoId(url) {
|
|
|
161
176
|
const host = u.hostname.toLowerCase();
|
|
162
177
|
if (!YOUTUBE_HOSTS.includes(host))
|
|
163
178
|
return null;
|
|
164
|
-
const id = host === 'youtu.be'
|
|
179
|
+
const id = host === 'youtu.be' || host === 'www.youtu.be'
|
|
165
180
|
? (u.pathname.split('/')[1] ?? null)
|
|
166
181
|
: u.pathname === '/watch'
|
|
167
182
|
? u.searchParams.get('v')
|
|
168
|
-
: (/^\/(?:shorts|embed|live)\/([^/]+)/.exec(u.pathname)?.[1] ?? null);
|
|
183
|
+
: (/^\/(?:shorts|embed|live|v)\/([^/]+)/.exec(u.pathname)?.[1] ?? null);
|
|
169
184
|
return id && /^[A-Za-z0-9_-]{11}$/.test(id) ? id : null;
|
|
170
185
|
}
|
|
171
186
|
// ─── Username ────────────────────────────────────────────────────────────────
|
|
@@ -356,18 +371,27 @@ export const GOAL_VALUES = [
|
|
|
356
371
|
'attend_events',
|
|
357
372
|
];
|
|
358
373
|
export const goalSchema = z.enum(GOAL_VALUES);
|
|
359
|
-
/**
|
|
360
|
-
|
|
374
|
+
/**
|
|
375
|
+
* True when no value is in the list twice: THE duplicate rule (0.18.0, exported;
|
|
376
|
+
* every list in this package refines with it instead of its own copy).
|
|
377
|
+
*/
|
|
378
|
+
export const eachOnce = (items) => new Set(items).size === items.length;
|
|
361
379
|
/**
|
|
362
380
|
* A member's goals as a list (0.17.0): each goal once, at most every goal.
|
|
363
|
-
* ONE rule for onboarding's finish
|
|
364
|
-
*
|
|
365
|
-
* one, and the profile would show it twice.
|
|
381
|
+
* ONE rule for onboarding's finish and the Me tab's editor, which both add the
|
|
382
|
+
* minimum (`requiredGoalListSchema`, 0.18.0): two copies of one goal met the
|
|
383
|
+
* minimum while meaning one, and the profile would show it twice.
|
|
366
384
|
*/
|
|
367
385
|
export const goalListSchema = z
|
|
368
386
|
.array(goalSchema)
|
|
369
387
|
.max(GOAL_VALUES.length)
|
|
370
388
|
.refine(eachOnce, { error: VALIDATION_MESSAGE.goalsDuplicate });
|
|
389
|
+
/**
|
|
390
|
+
* A member's goals where they are REQUIRED (0.18.0): at least `GOALS_MIN`.
|
|
391
|
+
* ONE schema for onboarding's finish and the Me editor (the user: "we already
|
|
392
|
+
* have at least one goal required"), so the two can never disagree.
|
|
393
|
+
*/
|
|
394
|
+
export const requiredGoalListSchema = goalListSchema.min(GOALS_MIN);
|
|
371
395
|
/** A member's skills (0.17.0): each skill once, at most `SKILLS_MAX`. ONE rule for onboarding and Me. */
|
|
372
396
|
export const skillListSchema = z
|
|
373
397
|
.array(skillSchema)
|
|
@@ -474,8 +498,9 @@ export function normalizePhone(raw) {
|
|
|
474
498
|
* location has a country, because the model requires one, so the read side
|
|
475
499
|
* loses nothing.
|
|
476
500
|
*
|
|
477
|
-
* This is a DECLARED location, not a GPS reading
|
|
478
|
-
*
|
|
501
|
+
* This is a DECLARED location, not a GPS reading (Jansathi's rule): a GPS
|
|
502
|
+
* fix only ever fills the member's field; the member saves it (0.19.0,
|
|
503
|
+
* `domains/location/areas.ts`).
|
|
479
504
|
*/
|
|
480
505
|
export const locationSchema = z.object({
|
|
481
506
|
city: z.string().trim().min(1).max(CITY_MAX),
|
|
@@ -483,16 +508,11 @@ export const locationSchema = z.object({
|
|
|
483
508
|
country: z.string().trim().min(1).max(COUNTRY_MAX),
|
|
484
509
|
});
|
|
485
510
|
/**
|
|
486
|
-
*
|
|
487
|
-
*
|
|
488
|
-
*
|
|
489
|
-
* emptied parameter is "missing", never 0, because 0,0 is a real place in the
|
|
490
|
-
* Gulf of Guinea and would be looked up rather than refused.
|
|
511
|
+
* A whole number in a QUERY (everything in a URL is text, so coercion is right
|
|
512
|
+
* here and only here). An emptied parameter is "not provided": the fallback,
|
|
513
|
+
* never 0 by accident.
|
|
491
514
|
*/
|
|
492
|
-
export const
|
|
493
|
-
lat: z.preprocess(emptyStringToUndefined, z.coerce.number().min(-90).max(90)),
|
|
494
|
-
lng: z.preprocess(emptyStringToUndefined, z.coerce.number().min(-180).max(180)),
|
|
495
|
-
});
|
|
515
|
+
export const queryInt = (min, max, fallback) => z.preprocess(emptyStringToUndefined, z.coerce.number().int().min(min).max(max).default(fallback));
|
|
496
516
|
// ─── Pagination ──────────────────────────────────────────────────────────────
|
|
497
517
|
/**
|
|
498
518
|
* Cursor pagination query. Feed and Discover both use it.
|
|
@@ -508,6 +528,11 @@ export const paginationQuerySchema = z.object({
|
|
|
508
528
|
* `.min(1)`: the member sees a 400 for a page they never asked to size.
|
|
509
529
|
*/
|
|
510
530
|
limit: z.preprocess(emptyStringToUndefined, z.coerce.number().int().min(1).max(PAGE_SIZE_MAX).default(PAGE_SIZE_DEFAULT)),
|
|
511
|
-
|
|
531
|
+
/**
|
|
532
|
+
* The opaque cursor a page answered with. Empty means "not provided" (the
|
|
533
|
+
* same as `limit`, 0.18.0: an emptied control answered 400), and it is
|
|
534
|
+
* bounded: the server's cursors are short, so a long one is not one of them.
|
|
535
|
+
*/
|
|
536
|
+
cursor: z.preprocess(emptyStringToUndefined, z.string().trim().min(1).max(CURSOR_MAX).optional()),
|
|
512
537
|
});
|
|
513
538
|
//# sourceMappingURL=common.js.map
|
package/dist/core/common.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"common.js","sourceRoot":"","sources":["../../src/core/common.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema — Shared helpers and core enums\n *\n * The stable building blocks reused across every Founder Circle schema.\n * Anything that both the backend and a client need to agree on lives here.\n *\n * @module founder-schema/core/common\n */\n\nimport { z } from 'zod';\nimport {\n CITY_MAX,\n COUNTRY_MAX,\n DISPLAY_NAME_MAX,\n LINK_URL_MAX,\n PAGE_SIZE_DEFAULT,\n PAGE_SIZE_MAX,\n SKILLS_MAX,\n STATE_MAX,\n} from './limits.js';\nimport { VALIDATION_MESSAGE } from './validation.js';\n\n// ─── String helpers ──────────────────────────────────────────────────────────\n\n/**\n * Converts empty / whitespace-only strings to `undefined` so Zod `.optional()`\n * treats them as \"not provided\".\n *\n * Why this exists: an HTML form submits `\"\"` for a cleared field, and without\n * this an optional field would be stored as an empty string rather than left\n * alone. Jansathi shipped that bug in dating onboarding.\n */\nexport const emptyStringToUndefined = (value: unknown): unknown => {\n if (typeof value === 'string' && value.trim() === '') return undefined;\n return value;\n};\n\n/** Optional trimmed string. Empty strings are coerced to `undefined`. */\nexport const optionalTrimmedString = (maxLength = 100) =>\n z.preprocess(emptyStringToUndefined, z.string().trim().max(maxLength).optional());\n\n/**\n * A repeated query param (`?skill=growth&skill=sales`) or a single value,\n * always as an array, at most `max` values.\n *\n * Express gives `string` for one occurrence and `string[]` for several, so\n * without this the same URL shape parses two different ways. EMPTY values are\n * dropped (`?industry=` is a select the member just reset): asking for fewer\n * filters is never a 400.\n */\nexport const multiValueQuery = <T extends z.ZodType>(inner: T, max: number) =>\n z.preprocess((value) => {\n if (value === undefined || value === null) return undefined;\n const list = (Array.isArray(value) ? value : [value]).filter((v) => v !== '');\n return list.length > 0 ? list : undefined;\n }, z.array(inner).max(max).optional());\n\n/** A Mongo ObjectId as it crosses the wire: 24 lowercase hex characters. */\nexport const objectIdSchema = z\n .string()\n .regex(/^[0-9a-f]{24}$/i, VALIDATION_MESSAGE.invalidId)\n .transform((v) => v.toLowerCase());\n/** Named for what it is on the wire, so it never collides with Mongoose's `ObjectId`. */\nexport type ObjectIdString = z.infer<typeof objectIdSchema>;\n\n// ─── URLs ────────────────────────────────────────────────────────────────────\n\n/**\n * A URL safe to store and, after the consumer's own check, to render.\n *\n * Use this for EVERY member-supplied URL. Never bare `z.url()`: it accepts any\n * scheme the URL parser accepts, so `javascript:alert(document.cookie)`,\n * `data:text/html,<script>...`, `vbscript:` and `file:///etc/passwd` all pass.\n * Each of those is stored XSS the moment a client renders it as an `href`, and\n * `data:` is the same in an `<img src>`.\n *\n * `hostname` is restricted too, not only the scheme. A bare host like\n * `localhost` or a literal IP is refused, which closes server-side request\n * forgery: this product runs on EC2 with an instance role, so a member-supplied\n * `http://169.254.169.254/latest/meta-data/` that anything server-side ever\n * fetches is a credential read.\n *\n * Validating here is necessary and not sufficient. A consumer still treats a\n * stored URL as untrusted at render time, because rows written before this\n * existed were never checked.\n *\n * ⚠️ IF THIS EVER GROWS A HOST ALLOWLIST, compare the PARSED hostname for\n * exact equality. Never `includes`, never `endsWith`, never a regex over the\n * whole URL. Each of these is a real bypass of a naive \"is it YouTube\" check,\n * and all three read as obviously fine:\n *\n * https://evil.example/?next=youtube.com/watch contains the string\n * youtube.com.evil.example starts with it\n * youtu.be.evil.example a suffix check passes it\n *\n * The same mistake in the opposite direction to the `javascript:` hole: there\n * the scheme was too permissive, here the host check only LOOKS strict.\n * (krish hit this rendering inline video, 2026-09-21, and handles it by parsing\n * and comparing the hostname exactly.)\n */\nexport const safeUrlSchema = z.url({\n protocol: /^https?$/,\n hostname: z.regexes.domain,\n error: VALIDATION_MESSAGE.urlInvalid,\n});\nexport type SafeUrl = z.infer<typeof safeUrlSchema>;\n\n/**\n * A URL a member TYPES: `safeUrlSchema` plus the `LINK_URL_MAX` ceiling, so\n * every member URL carries the same cap and no consumer has to remember it.\n *\n * Two schemas, not one, because the cap is about what a person types and is\n * wrong for what the server reads from elsewhere. Until 0.2.0 there was only\n * this one, and it capped a link preview's CDN image URL too: one 314-character\n * `imageUrl` would have failed a whole feed page.\n */\nexport const httpUrlSchema = safeUrlSchema.max(LINK_URL_MAX);\nexport type HttpUrl = z.infer<typeof httpUrlSchema>;\n\n/**\n * A link as people paste it, made into what `httpUrlSchema` checks (0.17.0,\n * moved here from the web so every client cleans a link the same way):\n * `linkedin.com/in/asha` gains `https://`; blank stays blank. Something that\n * already names a scheme keeps it, so `javascript:` is never quietly \"fixed\"\n * into a link: the schema then refuses it, as it only takes http and https.\n */\nexport function normalizeLinkUrl(raw: string): string {\n const trimmed = raw.trim();\n if (!trimmed) return '';\n return /^[a-z][a-z0-9+.-]*:/i.test(trimmed) ? trimmed : `https://${trimmed}`;\n}\n\n/**\n * True when `url` parses and its hostname is EXACTLY one of `hosts`\n * (lowercase). The one host check in this package, written the way the warning\n * on `safeUrlSchema` demands: parsed, compared for equality, never matched as\n * text. `youtube.com.evil.example` and `evil.example/?u=youtube.com` are refused.\n */\nexport function isUrlOnHost(url: string, hosts: readonly string[]): boolean {\n try {\n return hosts.includes(new URL(url).hostname.toLowerCase());\n } catch {\n return false;\n }\n}\n\n/** A typed URL (`httpUrlSchema`) that must also live on one of `hosts`. */\nexport const httpUrlOnHosts = (hosts: readonly string[], message: string) =>\n httpUrlSchema.refine((url) => isUrlOnHost(url, hosts), { error: message });\n\n/**\n * What a product needs before it can be listed in Discover (0.6.0). Here, not\n * in the products domain, because an error's details carry it\n * (`PRODUCT_NOT_LISTABLE` -> `details.missing`) and core never imports a domain.\n */\nexport const PRODUCT_LISTING_REQUIREMENT_VALUES = ['logo', 'tagline', 'screenshot'] as const;\nexport const productListingRequirementSchema = z.enum(PRODUCT_LISTING_REQUIREMENT_VALUES);\nexport type ProductListingRequirement = z.infer<typeof productListingRequirementSchema>;\n\n/**\n * The hosts a shared video may come from. The user's ruling (2026-09-21): video\n * is shared as a link and played in the card, never uploaded. The web plays it\n * through youtube-nocookie.com, the one video frame its CSP admits.\n */\nexport const YOUTUBE_HOSTS = [\n 'youtube.com',\n 'www.youtube.com',\n 'm.youtube.com',\n 'youtu.be',\n // The privacy-mode player's own links (0.7.0).\n 'youtube-nocookie.com',\n 'www.youtube-nocookie.com',\n] as const;\n\n/**\n * The 11-character id of a YouTube VIDEO link, or null for anything else (a\n * channel, a playlist, the home page, another site): what a page can embed.\n * Reads `youtu.be/<id>`, `/watch?v=<id>`, and `/shorts/`, `/embed/`, `/live/<id>`\n * on a `YOUTUBE_HOSTS` host (0.7.0).\n */\nexport function youtubeVideoId(url: string): string | null {\n let u: URL;\n try {\n u = new URL(url);\n } catch {\n return null;\n }\n const host = u.hostname.toLowerCase();\n if (!(YOUTUBE_HOSTS as readonly string[]).includes(host)) return null;\n const id =\n host === 'youtu.be'\n ? (u.pathname.split('/')[1] ?? null)\n : u.pathname === '/watch'\n ? u.searchParams.get('v')\n : (/^\\/(?:shorts|embed|live)\\/([^/]+)/.exec(u.pathname)?.[1] ?? null);\n return id && /^[A-Za-z0-9_-]{11}$/.test(id) ? id : null;\n}\n\n// ─── Username ────────────────────────────────────────────────────────────────\n\n/**\n * Public handle. Derived by the server at onboarding, changeable later.\n *\n * Rules are deliberately narrow because the handle appears in URLs and in\n * mentions: lowercase letters, digits, underscore and a single dot, 3 to 30\n * characters, must start with a letter.\n *\n * This is the FORMAT, and it is what every read shape uses. A handle being\n * CHOSEN must also not be reserved: that is `chosenUsernameSchema`.\n */\nexport const USERNAME_MIN = 3;\nexport const USERNAME_MAX = 30;\n\nexport const usernameSchema = z\n .string()\n .trim()\n .toLowerCase()\n // Plain bounds: the client words them from the issue's own minimum/maximum.\n .min(USERNAME_MIN)\n .max(USERNAME_MAX)\n .regex(/^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)?$/, VALIDATION_MESSAGE.usernameInvalid);\nexport type Username = z.infer<typeof usernameSchema>;\n\n/**\n * Handles this product keeps for itself: route names a handle would shadow,\n * and words that would let a member pass as staff.\n *\n * ⚠ HERE, IN THE CONTRACT, since 0.2.0. It used to live in the backend so it\n * could grow without a publish, and nothing ever read it: the server derived\n * handles without checking it, so a member called \"Support\" was handed\n * `support`. One list, read by the server when it derives a handle and by any\n * form that lets a member choose one, is worth a publish per addition.\n *\n * Matched exactly, after lowercasing.\n */\nexport const RESERVED_USERNAMES = [\n 'admin',\n 'about',\n 'api',\n 'chat',\n 'discover',\n 'feed',\n 'founder',\n 'foundercircle',\n 'help',\n 'home',\n 'me',\n 'meetup',\n 'moderator',\n 'onboarding',\n 'settings',\n 'staff',\n 'superadmin',\n 'support',\n 'system',\n 'team',\n] as const;\n\nconst RESERVED = new Set<string>(RESERVED_USERNAMES);\n\nexport function isReservedUsername(handle: string): boolean {\n return RESERVED.has(handle.trim().toLowerCase());\n}\n\n/**\n * A handle a member is CHOOSING: the format, and not reserved.\n *\n * ⚠ Not used on read shapes, deliberately. A handle stored before the reserved\n * check existed must still READ, or one member's old handle would fail every\n * page that shows their name.\n */\nexport const chosenUsernameSchema = usernameSchema.refine((u) => !isReservedUsername(u), {\n error: VALIDATION_MESSAGE.usernameReserved,\n});\n\n// ─── Founder-facing enums ────────────────────────────────────────────────────\n\n/**\n * Stage of the founder's current venture.\n *\n * Ordered from earliest to latest so a range filter can compare by index. Add\n * new stages at the END or the stored ordinal of existing rows shifts.\n */\nexport const STARTUP_STAGE_VALUES = [\n 'idea',\n 'validation',\n 'mvp',\n 'pre_seed',\n 'seed',\n 'series_a_plus',\n 'profitable',\n 'exited',\n] as const;\nexport const startupStageSchema = z.enum(STARTUP_STAGE_VALUES);\nexport type StartupStage = z.infer<typeof startupStageSchema>;\n\n/**\n * Industry the venture operates in. Kept as a closed list so Discover filters\n * are a single indexed equality match rather than a text search.\n */\nexport const INDUSTRY_VALUES = [\n 'agritech',\n 'ai_ml',\n 'b2b_saas',\n 'consumer',\n 'climate',\n 'd2c',\n 'deeptech',\n 'edtech',\n 'fintech',\n 'gaming',\n 'healthtech',\n 'logistics',\n 'manufacturing',\n 'marketplace',\n 'media',\n 'mobility',\n 'proptech',\n 'robotics',\n 'services',\n 'space',\n 'other',\n] as const;\nexport const industrySchema = z.enum(INDUSTRY_VALUES);\nexport type Industry = z.infer<typeof industrySchema>;\n\n/**\n * What the founder brings. Free-text skills were considered and rejected: two\n * people typing \"growth\" and \"Growth Marketing\" would never match each other in\n * a filter, and the Discover tab is the whole point of the product.\n */\nexport const SKILL_VALUES = [\n 'engineering',\n 'product',\n 'design',\n 'data',\n 'growth',\n 'sales',\n 'marketing',\n 'operations',\n 'finance',\n 'legal',\n 'hr_people',\n 'supply_chain',\n 'research',\n 'fundraising',\n 'community',\n] as const;\nexport const skillSchema = z.enum(SKILL_VALUES);\nexport type Skill = z.infer<typeof skillSchema>;\n\n/**\n * What the member is here for. ONE list, replacing three.\n *\n * This was `lookingFor` with six values. The design draws eight \"Goals &\n * Passion\" tiles and four more on the business step; all three were the same\n * idea at three grains, and shipping them together would have meant nobody\n * could say which one Discover filters on - the answer settled by whichever\n * query got written first. Merged on hary's ruling, 2026-09-21, while there\n * were still no rows in any database.\n *\n * ⚠️ Stored per member and used as a Mongoose enum. Append only; removing a\n * value orphans documents already saved with it.\n *\n * ⚠️ `start_company` READS ODDLY next to its neighbours and is correct. It was\n * queried on the grounds that it restates `venture.stage: 'idea'`; it does not,\n * and the reason is worth keeping so nobody re-opens it. Inferring it from the\n * venture works only for a member who ALREADY HAS a venture row, and somebody\n * who wants to start a company is precisely the member with no venture at all,\n * so there is nothing to infer from. It also cannot express a founder with a\n * running company who wants to start another. The others were all asks of other\n * people and this one an ask of the product, which is the whole of the oddness.\n * (hary, 2026-09-21.) Since 0.16.0 the list also holds offers (`mentor_others`,\n * `invest_in_startups`) and a second ask of the product (`attend_events`).\n */\nexport const GOAL_VALUES = [\n 'find_cofounder',\n 'find_investors',\n 'find_customers',\n 'hiring',\n 'partnerships',\n 'mentorship',\n 'grow_network',\n 'business_ideas',\n 'market_insights',\n 'learn_startup',\n 'start_company',\n // 0.16.0 (the user, 2026-10-18: \"a few more ... cover most of the goals\"):\n // the journey's own asks (feedback on an idea, early testers), the other\n // side of mentorship and of fundraising (mentoring, investing), and three\n // more a founder network is asked for: joining a startup, finding\n // service providers, and events. Appended, per the rule above.\n 'get_feedback',\n 'find_testers',\n 'mentor_others',\n 'invest_in_startups',\n 'join_startup',\n 'find_service_providers',\n 'attend_events',\n] as const;\nexport const goalSchema = z.enum(GOAL_VALUES);\nexport type Goal = z.infer<typeof goalSchema>;\n\n/** True when no value is in the list twice. */\nconst eachOnce = (items: readonly unknown[]): boolean => new Set(items).size === items.length;\n\n/**\n * A member's goals as a list (0.17.0): each goal once, at most every goal.\n * ONE rule for onboarding's finish (which adds its minimum, `GOALS_MIN`) and\n * the Me tab's editor: two copies of one goal met the minimum while meaning\n * one, and the profile would show it twice.\n */\nexport const goalListSchema = z\n .array(goalSchema)\n .max(GOAL_VALUES.length)\n .refine(eachOnce, { error: VALIDATION_MESSAGE.goalsDuplicate });\n\n/** A member's skills (0.17.0): each skill once, at most `SKILLS_MAX`. ONE rule for onboarding and Me. */\nexport const skillListSchema = z\n .array(skillSchema)\n .max(SKILLS_MAX)\n .refine(eachOnce, { error: VALIDATION_MESSAGE.skillsDuplicate });\n\n// ─── Gender ──────────────────────────────────────────────────────────────────\n\n/**\n * Demographic only. Nothing here computes anything from it, unlike the sibling\n * gym product where gender feeds body-metric maths.\n *\n * Takes the family's three values and ADDS one rather than renaming any: a\n * network for founders should let somebody decline the question without having\n * to describe themselves as \"other\".\n */\nexport const GENDER_VALUES = ['male', 'female', 'other', 'prefer_not_to_say'] as const;\nexport const genderSchema = z.enum(GENDER_VALUES);\nexport type Gender = z.infer<typeof genderSchema>;\n\n// ─── Display name ────────────────────────────────────────────────────────────\n\n/**\n * A full name as it is sent and stored (0.17.0, moved here from the web):\n * trimmed, and every run of spaces made one. `displayNameSchema` applies it,\n * so the server stores it from every client; a client applies it to show the\n * member what will be kept. What `splitFullName` reads.\n */\nexport const tidyFullName = (fullName: string): string => fullName.trim().replace(/\\s+/g, ' ');\n\n/**\n * The name a member is shown by. ONE rule for every body that writes it:\n * onboarding's finish, the profile patch and the personal-details patch, so a\n * name one accepts the others can never refuse, and each stores it TIDIED\n * (`tidyFullName`, 0.17.0: \"Asha Rao\" from the Me editor was stored as\n * typed, while onboarding sent it tidy). Plain bounds, no custom message:\n * `too_small` with minimum 1 already says \"required\", and the client words it.\n */\nexport const displayNameSchema = z\n .string()\n .trim()\n .min(1)\n .max(DISPLAY_NAME_MAX)\n .transform(tidyFullName);\n\n/**\n * A full name's first and last name, DEDUCED, never asked (the user,\n * 2026-10-18: onboarding asks one \"Full name\" and works the two out from the\n * space). The first word is the first name and everything after the first\n * space is the last name, kept as written (\"Asha Rao Kulkarni\" is Asha and\n * Rao Kulkarni).\n *\n * ⚠ A ONE-WORD NAME IS A WHOLE NAME. Many people in India go by a single name,\n * so `lastName` is then `''`, and nothing may refuse it for that.\n *\n * Derived wherever it is needed, never stored as a second copy of the name:\n * `displayName` stays the one owner of a member's name, so a rename can never\n * leave a stale first name behind.\n */\nexport function splitFullName(fullName: string): { firstName: string; lastName: string } {\n const name = tidyFullName(fullName);\n const space = name.indexOf(' ');\n return space === -1\n ? { firstName: name, lastName: '' }\n : { firstName: name.slice(0, space), lastName: name.slice(space + 1) };\n}\n\n// ─── Phone ───────────────────────────────────────────────────────────────────\n\n/** The digits an Indian mobile number has: what `phoneSchema` takes and the field keeps. */\nexport const PHONE_DIGITS = 10;\n\n/**\n * Indian mobile number: ten raw digits, no country code, matching the standard\n * used across this family. Display adds the `+91`.\n *\n * ⚠️ WHERE IT IS STORED IS NOT A SCHEMA QUESTION, and it has been ruled: the\n * phone lives on the account mirror, never as a second copy on the profile.\n * Two stored copies drift the first time somebody edits one, and the profile's\n * would be the copy nobody keeps in sync.\n */\nexport const phoneSchema = z\n .string()\n .trim()\n .regex(new RegExp(`^[6-9]\\\\d{${PHONE_DIGITS - 1}}$`), VALIDATION_MESSAGE.phoneInvalid);\nexport type Phone = z.infer<typeof phoneSchema>;\n\n/**\n * An Indian mobile number as `phoneSchema`'s ten digits, from however it was\n * typed (0.17.0, moved here from the web): `98765 43210`, `+91 98765-43210`,\n * `091 9876543210`. Anything else comes back digits only and fails the schema\n * with `phoneInvalid`, rather than being guessed at.\n */\nexport function normalizePhone(raw: string): string {\n let digits = raw.replace(/\\D/g, '');\n if (digits.length === PHONE_DIGITS + 2 && digits.startsWith('91')) digits = digits.slice(2);\n else if (digits.length === PHONE_DIGITS + 1 && digits.startsWith('0')) digits = digits.slice(1);\n else if (digits.length === PHONE_DIGITS + 3 && digits.startsWith('091')) digits = digits.slice(3);\n return digits;\n}\n\n// ─── Location ────────────────────────────────────────────────────────────────\n\n/**\n * Where the founder is based. City and country are what Discover filters on.\n *\n * ONE SHAPE for the profile, the Me tab's patch, onboarding's finish and the\n * location suggestion. It was written out three times until 0.2.0, the same\n * kind of copy that let the profile model lose Instagram.\n *\n * ⚠ `country` is REQUIRED and has NO default. A default here (it used to be\n * `'India'`) is destructive on every write path: a founder in Berlin who\n * corrects only their city would be moved to India by a value they never saw.\n * A body that carries a location carries the whole location. Every stored\n * location has a country, because the model requires one, so the read side\n * loses nothing.\n *\n * This is a DECLARED location, not a GPS reading. Jansathi's rule applies\n * here too: never write a declared location from a GPS capture.\n */\nexport const locationSchema = z.object({\n city: z.string().trim().min(1).max(CITY_MAX),\n state: optionalTrimmedString(STATE_MAX),\n country: z.string().trim().min(1).max(COUNTRY_MAX),\n});\nexport type FounderLocation = z.infer<typeof locationSchema>;\n\n/**\n * `GET /api/v1/location/areas/reverse?lat=&lng=`: plain decimal degrees.\n *\n * A QUERY, so coercion is right here: everything in a URL is a string. An\n * emptied parameter is \"missing\", never 0, because 0,0 is a real place in the\n * Gulf of Guinea and would be looked up rather than refused.\n */\nexport const coordinatesQuerySchema = z.object({\n lat: z.preprocess(emptyStringToUndefined, z.coerce.number().min(-90).max(90)),\n lng: z.preprocess(emptyStringToUndefined, z.coerce.number().min(-180).max(180)),\n});\nexport type CoordinatesQuery = z.infer<typeof coordinatesQuerySchema>;\nexport type CoordinatesQueryInput = z.input<typeof coordinatesQuerySchema>;\n\n// ─── Pagination ──────────────────────────────────────────────────────────────\n\n/**\n * Cursor pagination query. Feed and Discover both use it.\n *\n * Cursor, not page number: the community feed changes under the reader, and an\n * offset would repeat or skip posts as new ones land.\n */\nexport const paginationQuerySchema = z.object({\n /**\n * `?limit=` with nothing after it is treated as \"not provided\", not as a\n * validation failure. A client that clears the control sends the empty\n * string, and `z.coerce.number()` turns that into 0, which then fails\n * `.min(1)`: the member sees a 400 for a page they never asked to size.\n */\n limit: z.preprocess(\n emptyStringToUndefined,\n z.coerce.number().int().min(1).max(PAGE_SIZE_MAX).default(PAGE_SIZE_DEFAULT),\n ),\n cursor: z.string().trim().min(1).optional(),\n});\nexport type PaginationQuery = z.infer<typeof paginationQuerySchema>;\n/** What a client may put IN the URL: every field optional, numbers as strings. */\nexport type PaginationQueryInput = z.input<typeof paginationQuerySchema>;\n"]}
|
|
1
|
+
{"version":3,"file":"common.js","sourceRoot":"","sources":["../../src/core/common.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema — Shared helpers and core enums\n *\n * The stable building blocks reused across every Founder Circle schema.\n * Anything that both the backend and a client need to agree on lives here.\n *\n * @module founder-schema/core/common\n */\n\nimport { z } from 'zod';\nimport {\n CITY_MAX,\n COUNTRY_MAX,\n CURSOR_MAX,\n DISPLAY_NAME_MAX,\n GOALS_MIN,\n LINK_URL_MAX,\n PAGE_SIZE_DEFAULT,\n PAGE_SIZE_MAX,\n SKILLS_MAX,\n STATE_MAX,\n} from './limits.js';\nimport { VALIDATION_MESSAGE } from './validation.js';\n\n// ─── String helpers ──────────────────────────────────────────────────────────\n\n/**\n * Converts empty / whitespace-only strings to `undefined` so Zod `.optional()`\n * treats them as \"not provided\".\n *\n * Why this exists: an HTML form submits `\"\"` for a cleared field, and without\n * this an optional field would be stored as an empty string rather than left\n * alone. Jansathi shipped that bug in dating onboarding.\n */\nexport const emptyStringToUndefined = (value: unknown): unknown => {\n if (typeof value === 'string' && value.trim() === '') return undefined;\n return value;\n};\n\n/**\n * Optional trimmed string: a blank one (`''`, spaces) is absent (`undefined`).\n * Trimmed, checked, then blank dropped, never a preprocess (0.18.0: a\n * preprocess types its INPUT as `unknown`, so a caller typing a body by\n * `z.input` could send a number and still compile). `.optional()` OUTSIDE, so\n * the key stays optional in the output type as well as the input.\n */\nexport const optionalTrimmedString = (maxLength = 100) =>\n z\n .string()\n .trim()\n .max(maxLength)\n .transform((value) => value || undefined)\n .optional();\n\n/**\n * A repeated query param (`?skill=growth&skill=sales`) or a single value,\n * always as an array, at most `max` values.\n *\n * Express gives `string` for one occurrence and `string[]` for several, so\n * without this the same URL shape parses two different ways. EMPTY values are\n * dropped (`?industry=` is a select the member just reset): asking for fewer\n * filters is never a 400.\n */\nexport const multiValueQuery = <T extends z.ZodType>(inner: T, max: number) =>\n z.preprocess((value) => {\n if (value === undefined || value === null) return undefined;\n const list = (Array.isArray(value) ? value : [value]).filter((v) => v !== '');\n return list.length > 0 ? list : undefined;\n }, z.array(inner).max(max).optional());\n\n/** A Mongo ObjectId as it crosses the wire: 24 lowercase hex characters. */\nexport const objectIdSchema = z\n .string()\n .regex(/^[0-9a-f]{24}$/i, VALIDATION_MESSAGE.invalidId)\n .transform((v) => v.toLowerCase());\n/** Named for what it is on the wire, so it never collides with Mongoose's `ObjectId`. */\nexport type ObjectIdString = z.infer<typeof objectIdSchema>;\n\n// ─── URLs ────────────────────────────────────────────────────────────────────\n\n/**\n * A URL safe to store and, after the consumer's own check, to render.\n *\n * Use this for EVERY member-supplied URL. Never bare `z.url()`: it accepts any\n * scheme the URL parser accepts, so `javascript:alert(document.cookie)`,\n * `data:text/html,<script>...`, `vbscript:` and `file:///etc/passwd` all pass.\n * Each of those is stored XSS the moment a client renders it as an `href`, and\n * `data:` is the same in an `<img src>`.\n *\n * `hostname` is restricted too, not only the scheme. A bare host like\n * `localhost` or a literal IP is refused, which closes server-side request\n * forgery: this product runs on EC2 with an instance role, so a member-supplied\n * `http://169.254.169.254/latest/meta-data/` that anything server-side ever\n * fetches is a credential read.\n *\n * Validating here is necessary and not sufficient. A consumer still treats a\n * stored URL as untrusted at render time, because rows written before this\n * existed were never checked.\n *\n * ⚠️ IF THIS EVER GROWS A HOST ALLOWLIST, compare the PARSED hostname for\n * exact equality. Never `includes`, never `endsWith`, never a regex over the\n * whole URL. Each of these is a real bypass of a naive \"is it YouTube\" check,\n * and all three read as obviously fine:\n *\n * https://evil.example/?next=youtube.com/watch contains the string\n * youtube.com.evil.example starts with it\n * youtu.be.evil.example a suffix check passes it\n *\n * The same mistake in the opposite direction to the `javascript:` hole: there\n * the scheme was too permissive, here the host check only LOOKS strict.\n * (krish hit this rendering inline video, 2026-09-21, and handles it by parsing\n * and comparing the hostname exactly.)\n */\nexport const safeUrlSchema = z.url({\n protocol: /^https?$/,\n hostname: z.regexes.domain,\n error: VALIDATION_MESSAGE.urlInvalid,\n});\nexport type SafeUrl = z.infer<typeof safeUrlSchema>;\n\n/**\n * A URL a member TYPES: `safeUrlSchema` plus the `LINK_URL_MAX` ceiling, so\n * every member URL carries the same cap and no consumer has to remember it.\n *\n * Two schemas, not one, because the cap is about what a person types and is\n * wrong for what the server reads from elsewhere. Until 0.2.0 there was only\n * this one, and it capped a link preview's CDN image URL too: one 314-character\n * `imageUrl` would have failed a whole feed page.\n */\nexport const httpUrlSchema = safeUrlSchema.max(LINK_URL_MAX);\nexport type HttpUrl = z.infer<typeof httpUrlSchema>;\n\n/**\n * A link as people paste it, made into what `httpUrlSchema` checks (0.17.0,\n * moved here from the web so every client cleans a link the same way):\n * `linkedin.com/in/asha` gains `https://`; blank stays blank. Something that\n * already names a scheme keeps it, so `javascript:` is never quietly \"fixed\"\n * into a link: the schema then refuses it, as it only takes http and https.\n */\nexport function normalizeLinkUrl(raw: string): string {\n const trimmed = raw.trim();\n if (!trimmed) return '';\n return /^[a-z][a-z0-9+.-]*:/i.test(trimmed) ? trimmed : `https://${trimmed}`;\n}\n\n/**\n * True when `url` parses and its hostname is EXACTLY one of `hosts`\n * (lowercase). The one host check in this package, written the way the warning\n * on `safeUrlSchema` demands: parsed, compared for equality, never matched as\n * text. `youtube.com.evil.example` and `evil.example/?u=youtube.com` are refused.\n */\nexport function isUrlOnHost(url: string, hosts: readonly string[]): boolean {\n try {\n return hosts.includes(new URL(url).hostname.toLowerCase());\n } catch {\n return false;\n }\n}\n\n/** A typed URL (`httpUrlSchema`) that must also live on one of `hosts`. */\nexport const httpUrlOnHosts = (hosts: readonly string[], message: string) =>\n httpUrlSchema.refine((url) => isUrlOnHost(url, hosts), { error: message });\n\n/**\n * What a product needs before it can be listed in Discover (0.6.0). Here, not\n * in the products domain, because an error's details carry it\n * (`PRODUCT_NOT_LISTABLE` -> `details.missing`) and core never imports a domain.\n */\nexport const PRODUCT_LISTING_REQUIREMENT_VALUES = ['logo', 'tagline', 'screenshot'] as const;\nexport const productListingRequirementSchema = z.enum(PRODUCT_LISTING_REQUIREMENT_VALUES);\nexport type ProductListingRequirement = z.infer<typeof productListingRequirementSchema>;\n\n/**\n * The hosts a shared video may come from. The user's ruling (2026-09-21): video\n * is shared as a link and played in the card, never uploaded. The web plays it\n * through youtube-nocookie.com, the one video frame its CSP admits.\n */\nexport const YOUTUBE_HOSTS = [\n 'youtube.com',\n 'www.youtube.com',\n 'm.youtube.com',\n // 0.18.0: the feed's links too, so one rule plays a post's video and a product's.\n 'music.youtube.com',\n 'youtu.be',\n 'www.youtu.be',\n // The privacy-mode player's own links (0.7.0).\n 'youtube-nocookie.com',\n 'www.youtube-nocookie.com',\n] as const;\n\n/**\n * The 11-character id of a YouTube VIDEO link, or null for anything else (a\n * channel, a playlist, the home page, another site): what a page can embed.\n * Reads `youtu.be/<id>`, `/watch?v=<id>`, and `/shorts/`, `/embed/`, `/live/`,\n * `/v/<id>` on a `YOUTUBE_HOSTS` host (0.7.0; `/v/` and the feed's hosts\n * 0.18.0). THE rule for a product's video and a post's embed alike.\n */\nexport function youtubeVideoId(url: string): string | null {\n let u: URL;\n try {\n u = new URL(url);\n } catch {\n return null;\n }\n const host = u.hostname.toLowerCase();\n if (!(YOUTUBE_HOSTS as readonly string[]).includes(host)) return null;\n const id =\n host === 'youtu.be' || host === 'www.youtu.be'\n ? (u.pathname.split('/')[1] ?? null)\n : u.pathname === '/watch'\n ? u.searchParams.get('v')\n : (/^\\/(?:shorts|embed|live|v)\\/([^/]+)/.exec(u.pathname)?.[1] ?? null);\n return id && /^[A-Za-z0-9_-]{11}$/.test(id) ? id : null;\n}\n\n// ─── Username ────────────────────────────────────────────────────────────────\n\n/**\n * Public handle. Derived by the server at onboarding, changeable later.\n *\n * Rules are deliberately narrow because the handle appears in URLs and in\n * mentions: lowercase letters, digits, underscore and a single dot, 3 to 30\n * characters, must start with a letter.\n *\n * This is the FORMAT, and it is what every read shape uses. A handle being\n * CHOSEN must also not be reserved: that is `chosenUsernameSchema`.\n */\nexport const USERNAME_MIN = 3;\nexport const USERNAME_MAX = 30;\n\nexport const usernameSchema = z\n .string()\n .trim()\n .toLowerCase()\n // Plain bounds: the client words them from the issue's own minimum/maximum.\n .min(USERNAME_MIN)\n .max(USERNAME_MAX)\n .regex(/^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)?$/, VALIDATION_MESSAGE.usernameInvalid);\nexport type Username = z.infer<typeof usernameSchema>;\n\n/**\n * Handles this product keeps for itself: route names a handle would shadow,\n * and words that would let a member pass as staff.\n *\n * ⚠ HERE, IN THE CONTRACT, since 0.2.0. It used to live in the backend so it\n * could grow without a publish, and nothing ever read it: the server derived\n * handles without checking it, so a member called \"Support\" was handed\n * `support`. One list, read by the server when it derives a handle and by any\n * form that lets a member choose one, is worth a publish per addition.\n *\n * Matched exactly, after lowercasing.\n */\nexport const RESERVED_USERNAMES = [\n 'admin',\n 'about',\n 'api',\n 'chat',\n 'discover',\n 'feed',\n 'founder',\n 'foundercircle',\n 'help',\n 'home',\n 'me',\n 'meetup',\n 'moderator',\n 'onboarding',\n 'settings',\n 'staff',\n 'superadmin',\n 'support',\n 'system',\n 'team',\n] as const;\n\nconst RESERVED = new Set<string>(RESERVED_USERNAMES);\n\nexport function isReservedUsername(handle: string): boolean {\n return RESERVED.has(handle.trim().toLowerCase());\n}\n\n/**\n * A handle a member is CHOOSING: the format, and not reserved.\n *\n * ⚠ Not used on read shapes, deliberately. A handle stored before the reserved\n * check existed must still READ, or one member's old handle would fail every\n * page that shows their name.\n */\nexport const chosenUsernameSchema = usernameSchema.refine((u) => !isReservedUsername(u), {\n error: VALIDATION_MESSAGE.usernameReserved,\n});\n\n// ─── Founder-facing enums ────────────────────────────────────────────────────\n\n/**\n * Stage of the founder's current venture.\n *\n * Ordered from earliest to latest so a range filter can compare by index. Add\n * new stages at the END or the stored ordinal of existing rows shifts.\n */\nexport const STARTUP_STAGE_VALUES = [\n 'idea',\n 'validation',\n 'mvp',\n 'pre_seed',\n 'seed',\n 'series_a_plus',\n 'profitable',\n 'exited',\n] as const;\nexport const startupStageSchema = z.enum(STARTUP_STAGE_VALUES);\nexport type StartupStage = z.infer<typeof startupStageSchema>;\n\n/**\n * Industry the venture operates in. Kept as a closed list so Discover filters\n * are a single indexed equality match rather than a text search.\n */\nexport const INDUSTRY_VALUES = [\n 'agritech',\n 'ai_ml',\n 'b2b_saas',\n 'consumer',\n 'climate',\n 'd2c',\n 'deeptech',\n 'edtech',\n 'fintech',\n 'gaming',\n 'healthtech',\n 'logistics',\n 'manufacturing',\n 'marketplace',\n 'media',\n 'mobility',\n 'proptech',\n 'robotics',\n 'services',\n 'space',\n 'other',\n] as const;\nexport const industrySchema = z.enum(INDUSTRY_VALUES);\nexport type Industry = z.infer<typeof industrySchema>;\n\n/**\n * What the founder brings. Free-text skills were considered and rejected: two\n * people typing \"growth\" and \"Growth Marketing\" would never match each other in\n * a filter, and the Discover tab is the whole point of the product.\n */\nexport const SKILL_VALUES = [\n 'engineering',\n 'product',\n 'design',\n 'data',\n 'growth',\n 'sales',\n 'marketing',\n 'operations',\n 'finance',\n 'legal',\n 'hr_people',\n 'supply_chain',\n 'research',\n 'fundraising',\n 'community',\n] as const;\nexport const skillSchema = z.enum(SKILL_VALUES);\nexport type Skill = z.infer<typeof skillSchema>;\n\n/**\n * What the member is here for. ONE list, replacing three.\n *\n * This was `lookingFor` with six values. The design draws eight \"Goals &\n * Passion\" tiles and four more on the business step; all three were the same\n * idea at three grains, and shipping them together would have meant nobody\n * could say which one Discover filters on - the answer settled by whichever\n * query got written first. Merged on hary's ruling, 2026-09-21, while there\n * were still no rows in any database.\n *\n * ⚠️ Stored per member and used as a Mongoose enum. Append only; removing a\n * value orphans documents already saved with it.\n *\n * ⚠️ `start_company` READS ODDLY next to its neighbours and is correct. It was\n * queried on the grounds that it restates `venture.stage: 'idea'`; it does not,\n * and the reason is worth keeping so nobody re-opens it. Inferring it from the\n * venture works only for a member who ALREADY HAS a venture row, and somebody\n * who wants to start a company is precisely the member with no venture at all,\n * so there is nothing to infer from. It also cannot express a founder with a\n * running company who wants to start another. The others were all asks of other\n * people and this one an ask of the product, which is the whole of the oddness.\n * (hary, 2026-09-21.) Since 0.16.0 the list also holds offers (`mentor_others`,\n * `invest_in_startups`) and a second ask of the product (`attend_events`).\n */\nexport const GOAL_VALUES = [\n 'find_cofounder',\n 'find_investors',\n 'find_customers',\n 'hiring',\n 'partnerships',\n 'mentorship',\n 'grow_network',\n 'business_ideas',\n 'market_insights',\n 'learn_startup',\n 'start_company',\n // 0.16.0 (the user, 2026-10-18: \"a few more ... cover most of the goals\"):\n // the journey's own asks (feedback on an idea, early testers), the other\n // side of mentorship and of fundraising (mentoring, investing), and three\n // more a founder network is asked for: joining a startup, finding\n // service providers, and events. Appended, per the rule above.\n 'get_feedback',\n 'find_testers',\n 'mentor_others',\n 'invest_in_startups',\n 'join_startup',\n 'find_service_providers',\n 'attend_events',\n] as const;\nexport const goalSchema = z.enum(GOAL_VALUES);\nexport type Goal = z.infer<typeof goalSchema>;\n\n/**\n * True when no value is in the list twice: THE duplicate rule (0.18.0, exported;\n * every list in this package refines with it instead of its own copy).\n */\nexport const eachOnce = (items: readonly unknown[]): boolean =>\n new Set(items).size === items.length;\n\n/**\n * A member's goals as a list (0.17.0): each goal once, at most every goal.\n * ONE rule for onboarding's finish and the Me tab's editor, which both add the\n * minimum (`requiredGoalListSchema`, 0.18.0): two copies of one goal met the\n * minimum while meaning one, and the profile would show it twice.\n */\nexport const goalListSchema = z\n .array(goalSchema)\n .max(GOAL_VALUES.length)\n .refine(eachOnce, { error: VALIDATION_MESSAGE.goalsDuplicate });\n\n/**\n * A member's goals where they are REQUIRED (0.18.0): at least `GOALS_MIN`.\n * ONE schema for onboarding's finish and the Me editor (the user: \"we already\n * have at least one goal required\"), so the two can never disagree.\n */\nexport const requiredGoalListSchema = goalListSchema.min(GOALS_MIN);\n\n/** A member's skills (0.17.0): each skill once, at most `SKILLS_MAX`. ONE rule for onboarding and Me. */\nexport const skillListSchema = z\n .array(skillSchema)\n .max(SKILLS_MAX)\n .refine(eachOnce, { error: VALIDATION_MESSAGE.skillsDuplicate });\n\n// ─── Gender ──────────────────────────────────────────────────────────────────\n\n/**\n * Demographic only. Nothing here computes anything from it, unlike the sibling\n * gym product where gender feeds body-metric maths.\n *\n * Takes the family's three values and ADDS one rather than renaming any: a\n * network for founders should let somebody decline the question without having\n * to describe themselves as \"other\".\n */\nexport const GENDER_VALUES = ['male', 'female', 'other', 'prefer_not_to_say'] as const;\nexport const genderSchema = z.enum(GENDER_VALUES);\nexport type Gender = z.infer<typeof genderSchema>;\n\n// ─── Display name ────────────────────────────────────────────────────────────\n\n/**\n * A full name as it is sent and stored (0.17.0, moved here from the web):\n * trimmed, and every run of spaces made one. `displayNameSchema` applies it,\n * so the server stores it from every client; a client applies it to show the\n * member what will be kept. What `splitFullName` reads.\n */\nexport const tidyFullName = (fullName: string): string => fullName.trim().replace(/\\s+/g, ' ');\n\n/**\n * The name a member is shown by. ONE rule for every body that writes it:\n * onboarding's finish, the profile patch and the personal-details patch, so a\n * name one accepts the others can never refuse, and each stores it TIDIED\n * (`tidyFullName`, 0.17.0: \"Asha Rao\" from the Me editor was stored as\n * typed, while onboarding sent it tidy). Plain bounds, no custom message:\n * `too_small` with minimum 1 already says \"required\", and the client words it.\n */\nexport const displayNameSchema = z\n .string()\n .trim()\n .min(1)\n .max(DISPLAY_NAME_MAX)\n .transform(tidyFullName);\n\n/**\n * A full name's first and last name, DEDUCED, never asked (the user,\n * 2026-10-18: onboarding asks one \"Full name\" and works the two out from the\n * space). The first word is the first name and everything after the first\n * space is the last name, kept as written (\"Asha Rao Kulkarni\" is Asha and\n * Rao Kulkarni).\n *\n * ⚠ A ONE-WORD NAME IS A WHOLE NAME. Many people in India go by a single name,\n * so `lastName` is then `''`, and nothing may refuse it for that.\n *\n * Derived wherever it is needed, never stored as a second copy of the name:\n * `displayName` stays the one owner of a member's name, so a rename can never\n * leave a stale first name behind.\n */\nexport function splitFullName(fullName: string): { firstName: string; lastName: string } {\n const name = tidyFullName(fullName);\n const space = name.indexOf(' ');\n return space === -1\n ? { firstName: name, lastName: '' }\n : { firstName: name.slice(0, space), lastName: name.slice(space + 1) };\n}\n\n// ─── Phone ───────────────────────────────────────────────────────────────────\n\n/** The digits an Indian mobile number has: what `phoneSchema` takes and the field keeps. */\nexport const PHONE_DIGITS = 10;\n\n/**\n * Indian mobile number: ten raw digits, no country code, matching the standard\n * used across this family. Display adds the `+91`.\n *\n * ⚠️ WHERE IT IS STORED IS NOT A SCHEMA QUESTION, and it has been ruled: the\n * phone lives on the account mirror, never as a second copy on the profile.\n * Two stored copies drift the first time somebody edits one, and the profile's\n * would be the copy nobody keeps in sync.\n */\nexport const phoneSchema = z\n .string()\n .trim()\n .regex(new RegExp(`^[6-9]\\\\d{${PHONE_DIGITS - 1}}$`), VALIDATION_MESSAGE.phoneInvalid);\nexport type Phone = z.infer<typeof phoneSchema>;\n\n/**\n * An Indian mobile number as `phoneSchema`'s ten digits, from however it was\n * typed (0.17.0, moved here from the web): `98765 43210`, `+91 98765-43210`,\n * `091 9876543210`. Anything else comes back digits only and fails the schema\n * with `phoneInvalid`, rather than being guessed at.\n */\nexport function normalizePhone(raw: string): string {\n let digits = raw.replace(/\\D/g, '');\n if (digits.length === PHONE_DIGITS + 2 && digits.startsWith('91')) digits = digits.slice(2);\n else if (digits.length === PHONE_DIGITS + 1 && digits.startsWith('0')) digits = digits.slice(1);\n else if (digits.length === PHONE_DIGITS + 3 && digits.startsWith('091')) digits = digits.slice(3);\n return digits;\n}\n\n// ─── Location ────────────────────────────────────────────────────────────────\n\n/**\n * Where the founder is based. City and country are what Discover filters on.\n *\n * ONE SHAPE for the profile, the Me tab's patch, onboarding's finish and the\n * location suggestion. It was written out three times until 0.2.0, the same\n * kind of copy that let the profile model lose Instagram.\n *\n * ⚠ `country` is REQUIRED and has NO default. A default here (it used to be\n * `'India'`) is destructive on every write path: a founder in Berlin who\n * corrects only their city would be moved to India by a value they never saw.\n * A body that carries a location carries the whole location. Every stored\n * location has a country, because the model requires one, so the read side\n * loses nothing.\n *\n * This is a DECLARED location, not a GPS reading (Jansathi's rule): a GPS\n * fix only ever fills the member's field; the member saves it (0.19.0,\n * `domains/location/areas.ts`).\n */\nexport const locationSchema = z.object({\n city: z.string().trim().min(1).max(CITY_MAX),\n state: optionalTrimmedString(STATE_MAX),\n country: z.string().trim().min(1).max(COUNTRY_MAX),\n});\nexport type FounderLocation = z.infer<typeof locationSchema>;\n\n/**\n * A whole number in a QUERY (everything in a URL is text, so coercion is right\n * here and only here). An emptied parameter is \"not provided\": the fallback,\n * never 0 by accident.\n */\nexport const queryInt = (min: number, max: number, fallback: number) =>\n z.preprocess(emptyStringToUndefined, z.coerce.number().int().min(min).max(max).default(fallback));\n\n// ─── Pagination ──────────────────────────────────────────────────────────────\n\n/**\n * Cursor pagination query. Feed and Discover both use it.\n *\n * Cursor, not page number: the community feed changes under the reader, and an\n * offset would repeat or skip posts as new ones land.\n */\nexport const paginationQuerySchema = z.object({\n /**\n * `?limit=` with nothing after it is treated as \"not provided\", not as a\n * validation failure. A client that clears the control sends the empty\n * string, and `z.coerce.number()` turns that into 0, which then fails\n * `.min(1)`: the member sees a 400 for a page they never asked to size.\n */\n limit: z.preprocess(\n emptyStringToUndefined,\n z.coerce.number().int().min(1).max(PAGE_SIZE_MAX).default(PAGE_SIZE_DEFAULT),\n ),\n /**\n * The opaque cursor a page answered with. Empty means \"not provided\" (the\n * same as `limit`, 0.18.0: an emptied control answered 400), and it is\n * bounded: the server's cursors are short, so a long one is not one of them.\n */\n cursor: z.preprocess(emptyStringToUndefined, z.string().trim().min(1).max(CURSOR_MAX).optional()),\n});\nexport type PaginationQuery = z.infer<typeof paginationQuerySchema>;\n/** What a client may put IN the URL: every field optional, numbers as strings. */\nexport type PaginationQueryInput = z.input<typeof paginationQuerySchema>;\n"]}
|
package/dist/core/errors.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ import { z } from 'zod';
|
|
|
27
27
|
* As a tuple, the way `GENDER_VALUES` is, so a consumer can iterate it (a
|
|
28
28
|
* translation-coverage test does exactly that) and TypeScript keeps the union.
|
|
29
29
|
*/
|
|
30
|
-
export declare const API_ERROR_CODE_VALUES: readonly ["INTERNAL_ERROR", "VALIDATION_ERROR", "NOT_FOUND", "CONFLICT", "RATE_LIMITED", "SERVICE_UNAVAILABLE", "UNAUTHORIZED", "INVALID_TOKEN", "TOKEN_EXPIRED", "SESSION_INVALIDATED", "FORBIDDEN", "ONBOARDING_INCOMPLETE", "ALREADY_ONBOARDED", "UNDER_AGE", "UPLOAD_NOT_ATTACHABLE", "UPLOAD_TOO_LARGE", "UPLOAD_WRONG_TYPE", "UPLOAD_NOT_RECEIVED", "UPLOAD_PROCESSING", "UPLOAD_UNREADABLE", "UPLOAD_TOO_MANY_PIXELS", "UPLOAD_LIMIT_REACHED", "UPLOAD_IN_USE", "EDIT_WINDOW_CLOSED", "POLL_NOT_EDITABLE", "POLL_CLOSED", "POLL_CHOICE_INVALID", "REPLY_TOO_DEEP", "CANNOT_REPORT_OWN", "FEEDBACK_NOT_MARKABLE", "CANNOT_REPOST_OWN", "CANNOT_REPOST_A_REPOST", "ALREADY_REPOSTED", "PRODUCT_LIMIT_REACHED", "PRODUCT_NOT_LISTABLE", "PRODUCT_ARCHIVED", "PRODUCT_NOT_YOURS", "PRODUCT_ALREADY_MEMBER", "PRODUCT_MEMBER_NOT_INVITABLE", "PRODUCT_NOT_ON_TEAM", "PRODUCT_OWNER_MUST_TRANSFER", "PRODUCT_OWN_TEAM", "TESTER_NOT_ELIGIBLE", "PRODUCT_PAUSED", "MEMBERSHIP_REQUIRED", "PLAN_REQUIRED", "ALREADY_MEMBER", "MEMBERSHIP_NOT_REVOCABLE", "COUPON_INVALID", "COUPON_NOT_APPLICABLE", "COUPON_EXHAUSTED", "COUPON_ALREADY_USED", "COUPON_CODE_TAKEN", "USERNAME_TAKEN", "
|
|
30
|
+
export declare const API_ERROR_CODE_VALUES: readonly ["INTERNAL_ERROR", "VALIDATION_ERROR", "NOT_FOUND", "CONFLICT", "RATE_LIMITED", "SERVICE_UNAVAILABLE", "UNAUTHORIZED", "INVALID_TOKEN", "TOKEN_EXPIRED", "SESSION_INVALIDATED", "FORBIDDEN", "ONBOARDING_INCOMPLETE", "ALREADY_ONBOARDED", "UNDER_AGE", "UPLOAD_NOT_ATTACHABLE", "UPLOAD_TOO_LARGE", "UPLOAD_WRONG_TYPE", "UPLOAD_NOT_RECEIVED", "UPLOAD_PROCESSING", "UPLOAD_UNREADABLE", "UPLOAD_TOO_MANY_PIXELS", "UPLOAD_LIMIT_REACHED", "UPLOAD_IN_USE", "EDIT_WINDOW_CLOSED", "POLL_NOT_EDITABLE", "POLL_CLOSED", "POLL_CHOICE_INVALID", "REPLY_TOO_DEEP", "CANNOT_REPORT_OWN", "FEEDBACK_NOT_MARKABLE", "CANNOT_REPOST_OWN", "CANNOT_REPOST_A_REPOST", "ALREADY_REPOSTED", "PRODUCT_LIMIT_REACHED", "PRODUCT_NOT_LISTABLE", "PRODUCT_ARCHIVED", "PRODUCT_NOT_YOURS", "PRODUCT_ALREADY_MEMBER", "PRODUCT_MEMBER_NOT_INVITABLE", "PRODUCT_NOT_ON_TEAM", "PRODUCT_OWNER_MUST_TRANSFER", "PRODUCT_OWN_TEAM", "TESTER_NOT_ELIGIBLE", "PRODUCT_PAUSED", "MEMBERSHIP_REQUIRED", "PLAN_REQUIRED", "ALREADY_MEMBER", "MEMBERSHIP_NOT_REVOCABLE", "COUPON_INVALID", "COUPON_NOT_APPLICABLE", "COUPON_EXHAUSTED", "COUPON_ALREADY_USED", "COUPON_CODE_TAKEN", "USERNAME_TAKEN", "COUPON_FOUNDING_RETIRED", "PLAN_UPGRADE_INSTEAD", "PLAN_NOTHING_TO_UPGRADE", "PLAN_TERM_LIMIT", "PLAN_CHANGED", "PAYMENTS_UNAVAILABLE", "PAYMENT_NOT_VERIFIED", "REFUND_WINDOW_CLOSED", "PAYMENT_NOT_REFUNDABLE", "REFUND_FAILED", "MESSAGE_ALLOWANCE_EXHAUSTED", "MESSAGE_ALLOWANCE_UNCONFIRMED", "MESSAGE_NOT_ACCEPTED", "COMMUNITY_JOIN_REQUIRED", "COMMUNITY_NAME_TAKEN", "COMMUNITY_ALREADY_MEMBER", "COMMUNITY_MEMBER_NOT_INVITABLE", "COMMUNITY_MEMBER_BANNED", "COMMUNITY_INVITE_NOT_NEEDED", "COMMUNITY_LAST_ADMIN", "COMMUNITY_NOT_ON_YOURSELF", "HIDDEN_BY_STAFF", "COMMUNITY_ARCHIVED", "COMMUNITY_EXPOSURE_UNCONFIRMED", "COMMUNITY_PINS_FULL", "COMMUNITY_ANNOUNCE_TOO_SOON", "COMMUNITY_CONTENT_NOT_REPOSTABLE", "COMMUNITY_SUGGESTIONS_FULL", "COMMUNITY_SUGGESTION_DECIDED", "REVIEW_EXISTS", "REVIEW_OWN_VOTE", "MODERATION_ACTION_INVALID", "CHAT_WITH_SELF", "ACCOUNT_SUSPENDED", "ROLE_NOT_HELD", "MPIN_NOT_SET", "MPIN_ALREADY_SET", "MPIN_INCORRECT", "MPIN_LOCKED", "CANNOT_CHANGE_OWN_ROLES", "ROLE_NOT_GRANTABLE", "CANNOT_CHANGE_OWN_CAPABILITIES", "CAPABILITY_NOT_GRANTABLE", "TICKET_FULL", "TICKET_MEMBER_ONLY", "SAVED_REPLIES_FULL", "NOT_LAUNCHED", "WAITLIST_CLOSED", "WAITLIST_STAFF", "WAITLIST_PLACE_USED"];
|
|
31
31
|
export declare const apiErrorCodeSchema: z.ZodEnum<{
|
|
32
32
|
INTERNAL_ERROR: "INTERNAL_ERROR";
|
|
33
33
|
VALIDATION_ERROR: "VALIDATION_ERROR";
|
|
@@ -83,7 +83,6 @@ export declare const apiErrorCodeSchema: z.ZodEnum<{
|
|
|
83
83
|
COUPON_ALREADY_USED: "COUPON_ALREADY_USED";
|
|
84
84
|
COUPON_CODE_TAKEN: "COUPON_CODE_TAKEN";
|
|
85
85
|
USERNAME_TAKEN: "USERNAME_TAKEN";
|
|
86
|
-
COUPON_FOUNDING_EXISTS: "COUPON_FOUNDING_EXISTS";
|
|
87
86
|
COUPON_FOUNDING_RETIRED: "COUPON_FOUNDING_RETIRED";
|
|
88
87
|
PLAN_UPGRADE_INSTEAD: "PLAN_UPGRADE_INSTEAD";
|
|
89
88
|
PLAN_NOTHING_TO_UPGRADE: "PLAN_NOTHING_TO_UPGRADE";
|
|
@@ -91,7 +90,6 @@ export declare const apiErrorCodeSchema: z.ZodEnum<{
|
|
|
91
90
|
PLAN_CHANGED: "PLAN_CHANGED";
|
|
92
91
|
PAYMENTS_UNAVAILABLE: "PAYMENTS_UNAVAILABLE";
|
|
93
92
|
PAYMENT_NOT_VERIFIED: "PAYMENT_NOT_VERIFIED";
|
|
94
|
-
ORDER_EXPIRED: "ORDER_EXPIRED";
|
|
95
93
|
REFUND_WINDOW_CLOSED: "REFUND_WINDOW_CLOSED";
|
|
96
94
|
PAYMENT_NOT_REFUNDABLE: "PAYMENT_NOT_REFUNDABLE";
|
|
97
95
|
REFUND_FAILED: "REFUND_FAILED";
|
|
@@ -105,6 +103,8 @@ export declare const apiErrorCodeSchema: z.ZodEnum<{
|
|
|
105
103
|
COMMUNITY_MEMBER_BANNED: "COMMUNITY_MEMBER_BANNED";
|
|
106
104
|
COMMUNITY_INVITE_NOT_NEEDED: "COMMUNITY_INVITE_NOT_NEEDED";
|
|
107
105
|
COMMUNITY_LAST_ADMIN: "COMMUNITY_LAST_ADMIN";
|
|
106
|
+
COMMUNITY_NOT_ON_YOURSELF: "COMMUNITY_NOT_ON_YOURSELF";
|
|
107
|
+
HIDDEN_BY_STAFF: "HIDDEN_BY_STAFF";
|
|
108
108
|
COMMUNITY_ARCHIVED: "COMMUNITY_ARCHIVED";
|
|
109
109
|
COMMUNITY_EXPOSURE_UNCONFIRMED: "COMMUNITY_EXPOSURE_UNCONFIRMED";
|
|
110
110
|
COMMUNITY_PINS_FULL: "COMMUNITY_PINS_FULL";
|
|
@@ -127,6 +127,7 @@ export declare const apiErrorCodeSchema: z.ZodEnum<{
|
|
|
127
127
|
CANNOT_CHANGE_OWN_CAPABILITIES: "CANNOT_CHANGE_OWN_CAPABILITIES";
|
|
128
128
|
CAPABILITY_NOT_GRANTABLE: "CAPABILITY_NOT_GRANTABLE";
|
|
129
129
|
TICKET_FULL: "TICKET_FULL";
|
|
130
|
+
TICKET_MEMBER_ONLY: "TICKET_MEMBER_ONLY";
|
|
130
131
|
SAVED_REPLIES_FULL: "SAVED_REPLIES_FULL";
|
|
131
132
|
NOT_LAUNCHED: "NOT_LAUNCHED";
|
|
132
133
|
WAITLIST_CLOSED: "WAITLIST_CLOSED";
|
|
@@ -190,7 +191,6 @@ export declare const API_ERROR_CODE: {
|
|
|
190
191
|
COUPON_ALREADY_USED: "COUPON_ALREADY_USED";
|
|
191
192
|
COUPON_CODE_TAKEN: "COUPON_CODE_TAKEN";
|
|
192
193
|
USERNAME_TAKEN: "USERNAME_TAKEN";
|
|
193
|
-
COUPON_FOUNDING_EXISTS: "COUPON_FOUNDING_EXISTS";
|
|
194
194
|
COUPON_FOUNDING_RETIRED: "COUPON_FOUNDING_RETIRED";
|
|
195
195
|
PLAN_UPGRADE_INSTEAD: "PLAN_UPGRADE_INSTEAD";
|
|
196
196
|
PLAN_NOTHING_TO_UPGRADE: "PLAN_NOTHING_TO_UPGRADE";
|
|
@@ -198,7 +198,6 @@ export declare const API_ERROR_CODE: {
|
|
|
198
198
|
PLAN_CHANGED: "PLAN_CHANGED";
|
|
199
199
|
PAYMENTS_UNAVAILABLE: "PAYMENTS_UNAVAILABLE";
|
|
200
200
|
PAYMENT_NOT_VERIFIED: "PAYMENT_NOT_VERIFIED";
|
|
201
|
-
ORDER_EXPIRED: "ORDER_EXPIRED";
|
|
202
201
|
REFUND_WINDOW_CLOSED: "REFUND_WINDOW_CLOSED";
|
|
203
202
|
PAYMENT_NOT_REFUNDABLE: "PAYMENT_NOT_REFUNDABLE";
|
|
204
203
|
REFUND_FAILED: "REFUND_FAILED";
|
|
@@ -212,6 +211,8 @@ export declare const API_ERROR_CODE: {
|
|
|
212
211
|
COMMUNITY_MEMBER_BANNED: "COMMUNITY_MEMBER_BANNED";
|
|
213
212
|
COMMUNITY_INVITE_NOT_NEEDED: "COMMUNITY_INVITE_NOT_NEEDED";
|
|
214
213
|
COMMUNITY_LAST_ADMIN: "COMMUNITY_LAST_ADMIN";
|
|
214
|
+
COMMUNITY_NOT_ON_YOURSELF: "COMMUNITY_NOT_ON_YOURSELF";
|
|
215
|
+
HIDDEN_BY_STAFF: "HIDDEN_BY_STAFF";
|
|
215
216
|
COMMUNITY_ARCHIVED: "COMMUNITY_ARCHIVED";
|
|
216
217
|
COMMUNITY_EXPOSURE_UNCONFIRMED: "COMMUNITY_EXPOSURE_UNCONFIRMED";
|
|
217
218
|
COMMUNITY_PINS_FULL: "COMMUNITY_PINS_FULL";
|
|
@@ -234,6 +235,7 @@ export declare const API_ERROR_CODE: {
|
|
|
234
235
|
CANNOT_CHANGE_OWN_CAPABILITIES: "CANNOT_CHANGE_OWN_CAPABILITIES";
|
|
235
236
|
CAPABILITY_NOT_GRANTABLE: "CAPABILITY_NOT_GRANTABLE";
|
|
236
237
|
TICKET_FULL: "TICKET_FULL";
|
|
238
|
+
TICKET_MEMBER_ONLY: "TICKET_MEMBER_ONLY";
|
|
237
239
|
SAVED_REPLIES_FULL: "SAVED_REPLIES_FULL";
|
|
238
240
|
NOT_LAUNCHED: "NOT_LAUNCHED";
|
|
239
241
|
WAITLIST_CLOSED: "WAITLIST_CLOSED";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;GAGG;AACH,eAAO,MAAM,qBAAqB,
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;GAGG;AACH,eAAO,MAAM,qBAAqB,yyEAgWxB,CAAC;AAEX,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAAgC,CAAC;AAChE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,4DAA4D;AAC5D,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAA0B,CAAC"}
|
package/dist/core/errors.js
CHANGED
|
@@ -117,10 +117,11 @@ export const API_ERROR_CODE_VALUES = [
|
|
|
117
117
|
'ALREADY_REPOSTED',
|
|
118
118
|
// ── Products ──
|
|
119
119
|
/**
|
|
120
|
-
* Their plan's number of
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
120
|
+
* Their plan's number of products is reached (0.12.0: 1 for a member, 5 on
|
|
121
|
+
* Premium, unlimited on Business, within `PRODUCTS_LIVE_SAFETY_MAX`).
|
|
122
|
+
* Creating a product, making a paused one live, or being handed one. Live
|
|
123
|
+
* and owner-paused products both count, so pausing one frees nothing: delete
|
|
124
|
+
* or hand one over, or upgrade. `details.limit`.
|
|
124
125
|
*/
|
|
125
126
|
'PRODUCT_LIMIT_REACHED',
|
|
126
127
|
/**
|
|
@@ -191,11 +192,6 @@ export const API_ERROR_CODE_VALUES = [
|
|
|
191
192
|
'COUPON_CODE_TAKEN',
|
|
192
193
|
/** A member choosing a username someone else holds (0.13.0). 409. */
|
|
193
194
|
'USERNAME_TAKEN',
|
|
194
|
-
/**
|
|
195
|
-
* Staff creating a second FOUNDING coupon (0.13.0): founding numbers count
|
|
196
|
-
* up on the one founding coupon, so a second would hand out #1 again. 409.
|
|
197
|
-
*/
|
|
198
|
-
'COUPON_FOUNDING_EXISTS',
|
|
199
195
|
/**
|
|
200
196
|
* Staff creating ANY founding coupon (0.14.0): founding places come from the
|
|
201
197
|
* launch waitlist alone (the user, 2026-10-16), so no code may hand one out.
|
|
@@ -222,8 +218,6 @@ export const API_ERROR_CODE_VALUES = [
|
|
|
222
218
|
* still settles it.
|
|
223
219
|
*/
|
|
224
220
|
'PAYMENT_NOT_VERIFIED',
|
|
225
|
-
/** The checkout was not paid within its time (`ORDER_TTL_MINUTES`). Start again. 409. */
|
|
226
|
-
'ORDER_EXPIRED',
|
|
227
221
|
/** More than `REFUND_WINDOW_DAYS` days since the payment. 409. */
|
|
228
222
|
'REFUND_WINDOW_CLOSED',
|
|
229
223
|
/** Not a captured payment, or already refunded. 409. */
|
|
@@ -272,6 +266,16 @@ export const API_ERROR_CODE_VALUES = [
|
|
|
272
266
|
* first. On an account deletion, `details.communities` names them.
|
|
273
267
|
*/
|
|
274
268
|
'COMMUNITY_LAST_ADMIN',
|
|
269
|
+
/**
|
|
270
|
+
* A moderator removing or banning THEMSELF from their community (0.18.0:
|
|
271
|
+
* was a bare FORBIDDEN). Leave the community instead. 403.
|
|
272
|
+
*/
|
|
273
|
+
'COMMUNITY_NOT_ON_YOURSELF',
|
|
274
|
+
/**
|
|
275
|
+
* A community's moderators putting back something Founder Circle STAFF hid
|
|
276
|
+
* (0.18.0: was a bare FORBIDDEN): only staff can restore it. 403.
|
|
277
|
+
*/
|
|
278
|
+
'HIDDEN_BY_STAFF',
|
|
275
279
|
/**
|
|
276
280
|
* The community is archived (read-only): no new posts, polls, comments,
|
|
277
281
|
* reactions, votes, saves, reports, edits, joins, approvals, invitations
|
|
@@ -338,6 +342,8 @@ export const API_ERROR_CODE_VALUES = [
|
|
|
338
342
|
// ── Support ──
|
|
339
343
|
/** The ticket holds `TICKET_MESSAGES_MAX` messages. Open a new one. */
|
|
340
344
|
'TICKET_FULL',
|
|
345
|
+
/** Reopening a ticket is its member's alone (0.18.0: was a bare FORBIDDEN). 403. */
|
|
346
|
+
'TICKET_MEMBER_ONLY',
|
|
341
347
|
/** The team keeps `SAVED_REPLIES_MAX` saved replies. Delete one first. */
|
|
342
348
|
'SAVED_REPLIES_FULL',
|
|
343
349
|
// ── Waitlist (0.15.0) ──
|