founder-schema 0.15.0 → 0.16.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 +59 -3
- package/dist/core/common.d.ts +30 -4
- package/dist/core/common.d.ts.map +1 -1
- package/dist/core/common.js +37 -3
- package/dist/core/common.js.map +1 -1
- package/dist/domains/communities/communities.d.ts +9 -9
- package/dist/domains/discover/discover.d.ts +7 -0
- package/dist/domains/discover/discover.d.ts.map +1 -1
- package/dist/domains/feed/feed.d.ts +11 -11
- package/dist/domains/onboarding/onboarding.d.ts +245 -21
- package/dist/domains/onboarding/onboarding.d.ts.map +1 -1
- package/dist/domains/onboarding/onboarding.js +104 -16
- package/dist/domains/onboarding/onboarding.js.map +1 -1
- package/dist/domains/products/products.d.ts +3 -3
- package/dist/domains/profile/founder-profile.d.ts +28 -0
- package/dist/domains/profile/founder-profile.d.ts.map +1 -1
- package/dist/domains/profile/founder-profile.js +3 -0
- package/dist/domains/profile/founder-profile.js.map +1 -1
- package/dist/domains/profile/my-profile.d.ts +386 -0
- package/dist/domains/profile/my-profile.d.ts.map +1 -0
- package/dist/domains/profile/my-profile.js +52 -0
- package/dist/domains/profile/my-profile.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -99,6 +99,57 @@ instance; this package must not bring its own.
|
|
|
99
99
|
|
|
100
100
|
---
|
|
101
101
|
|
|
102
|
+
## Breaking in 0.16.0: onboarding in nine steps (2026-10-18)
|
|
103
|
+
|
|
104
|
+
The user's rework of onboarding. The finish body takes the same answers plus one.
|
|
105
|
+
|
|
106
|
+
- **Breaking:** `ONBOARDING_STEP_KEYS` is nine: `basics` (full name, gender,
|
|
107
|
+
date of birth, nothing else), `photo`, `professional` (title, about, skills),
|
|
108
|
+
then `contact`, `location`, `goals`, `interests`, `business`, `guidelines` as
|
|
109
|
+
before. `identity` and `ONBOARDING_STEP_SCHEMAS.identity` are gone;
|
|
110
|
+
`ONBOARDING_TOTAL_STEPS` is 9. The resume cursor is an index into this list:
|
|
111
|
+
the backend translates a cursor saved under the seven steps once, by step
|
|
112
|
+
name (its progress rows carry `stepsVersion`), so nobody resumes on the wrong
|
|
113
|
+
step or loses an answer.
|
|
114
|
+
- **Breaking:** `onboardingFormSchema` gains `legalStructure` (nullable), so a
|
|
115
|
+
form built from it must hold the key.
|
|
116
|
+
- **New:** `LEGAL_STRUCTURE_VALUES` / `legalStructureSchema` / `LegalStructure`:
|
|
117
|
+
how a venture is registered in India (sole proprietorship, partnership, LLP,
|
|
118
|
+
OPC, private and public limited, Section 8, trust or society, co-operative),
|
|
119
|
+
or not yet, or outside India. Optional on the finish body and the business
|
|
120
|
+
step; stored on the profile's venture beside `businessType`, and shown only
|
|
121
|
+
to the member (below).
|
|
122
|
+
- **New: the member's private answers in Me** (`domains/profile/my-profile.ts`,
|
|
123
|
+
the user, 2026-10-18): `GET`/`PUT /profiles/me/business` (`myBusinessSchema`,
|
|
124
|
+
`putMyBusinessBodySchema`: the WHOLE venture with its `businessType` and
|
|
125
|
+
`legalStructure`, which are `myVentureSchema`, or `null` to remove it) and
|
|
126
|
+
`GET`/`PUT /profiles/me/interests` (`myInterestsSchema`,
|
|
127
|
+
`putMyInterestsBodySchema`). Owner only: none of the three is on
|
|
128
|
+
`founderProfileSchema`, a card, a byline or a member's page.
|
|
129
|
+
- **Stricter:** `interestsSchema` is the ONE interests rule, used by the finish
|
|
130
|
+
body and the Me editor, and it now refuses a topic twice (`topicsDuplicate`):
|
|
131
|
+
three copies of one topic met the minimum of three. Neither client sends one.
|
|
132
|
+
- **New:** `splitFullName(fullName)`: the first and last name a full name
|
|
133
|
+
deduces to, at the first space; a one-word name is whole. Derived, never
|
|
134
|
+
stored: `displayName` stays the one owner of a name.
|
|
135
|
+
- **Appended:** `GOAL_VALUES` +7 (`get_feedback`, `find_testers`,
|
|
136
|
+
`mentor_others`, `invest_in_startups`, `join_startup`,
|
|
137
|
+
`find_service_providers`, `attend_events`); `BUSINESS_TYPE_VALUES` +5
|
|
138
|
+
(`freelancer`, `agency`, `social_enterprise`, `family_business`, `franchise`).
|
|
139
|
+
Nothing renamed or removed.
|
|
140
|
+
- **Deploy order: the backend, then the web straight after.** A 0.15 backend
|
|
141
|
+
refuses the `legalStructure` draft key and steps above 7, so a 0.16 web on it
|
|
142
|
+
is stuck at the business step; a 0.15 web cannot parse a 9-step status or a
|
|
143
|
+
card holding a new goal. Open tabs and an installed app's cached copy keep the
|
|
144
|
+
old bundle until they update.
|
|
145
|
+
- **Rollback floor:** once a member saves a new goal, a build below 0.16
|
|
146
|
+
cannot parse their card, member page or own profile; roll back only after a
|
|
147
|
+
backfill. A new business type or registration travels on none of those (only
|
|
148
|
+
on the owner's own `/profiles/me/business`, new in 0.16), so it does not
|
|
149
|
+
move the floor.
|
|
150
|
+
- **Backend, at deploy:** no new index and no migration (old cursors are
|
|
151
|
+
translated on the member's next visit).
|
|
152
|
+
|
|
102
153
|
## Breaking in 0.15.0: the launch waitlist (2026-10-16)
|
|
103
154
|
|
|
104
155
|
Until the launch instant `FC_LAUNCH_AT` (a server-only env on BOTH the backend
|
|
@@ -574,8 +625,13 @@ is imported, so a server process still up on 1 January rejects the new year unti
|
|
|
574
625
|
somebody restarts it.
|
|
575
626
|
|
|
576
627
|
**Changing a member of an enum array is a data change, not a type change.** The
|
|
577
|
-
|
|
578
|
-
|
|
628
|
+
`*_VALUES` arrays the backend's profile model uses (stage, industry, skill, goal,
|
|
629
|
+
gender, topic, business type, legal structure) are Mongoose `enum` constraints.
|
|
630
|
+
Removing or renaming a value orphans documents already saved with it. Appending
|
|
631
|
+
is safe for the database, but not for an OLDER CLIENT, which parses strictly and
|
|
632
|
+
refuses a value it does not know: deploy the readers (backend, then web) before
|
|
633
|
+
anything can write the new value, and a rollback below the version that added it
|
|
634
|
+
needs a backfill first.
|
|
579
635
|
`STARTUP_STAGE_VALUES` is ordered earliest to latest and that order is load
|
|
580
636
|
bearing, so append at the end and never insert.
|
|
581
637
|
|
|
@@ -584,7 +640,7 @@ bearing, so append at the end and never insert.
|
|
|
584
640
|
## Settled since the scaffold
|
|
585
641
|
|
|
586
642
|
- **The npm name and the first publish:** `founder-schema`, 0.1.0, by the user.
|
|
587
|
-
- **The onboarding steps:**
|
|
643
|
+
- **The onboarding steps:** nine since 0.16.0 (the user, 2026-10-18), seven before (ruled 2026-09-22); see `src/domains/onboarding/onboarding.ts`.
|
|
588
644
|
|
|
589
645
|
## Contract decisions, 2026-09-21
|
|
590
646
|
|
package/dist/core/common.d.ts
CHANGED
|
@@ -238,11 +238,12 @@ export type Skill = z.infer<typeof skillSchema>;
|
|
|
238
238
|
* venture works only for a member who ALREADY HAS a venture row, and somebody
|
|
239
239
|
* who wants to start a company is precisely the member with no venture at all,
|
|
240
240
|
* so there is nothing to infer from. It also cannot express a founder with a
|
|
241
|
-
* running company who wants to start another.
|
|
242
|
-
*
|
|
243
|
-
*
|
|
241
|
+
* running company who wants to start another. The others were all asks of other
|
|
242
|
+
* people and this one an ask of the product, which is the whole of the oddness.
|
|
243
|
+
* (hary, 2026-09-21.) Since 0.16.0 the list also holds offers (`mentor_others`,
|
|
244
|
+
* `invest_in_startups`) and a second ask of the product (`attend_events`).
|
|
244
245
|
*/
|
|
245
|
-
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"];
|
|
246
|
+
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", "get_feedback", "find_testers", "mentor_others", "invest_in_startups", "join_startup", "find_service_providers", "attend_events"];
|
|
246
247
|
export declare const goalSchema: z.ZodEnum<{
|
|
247
248
|
find_cofounder: "find_cofounder";
|
|
248
249
|
find_investors: "find_investors";
|
|
@@ -255,6 +256,13 @@ export declare const goalSchema: z.ZodEnum<{
|
|
|
255
256
|
market_insights: "market_insights";
|
|
256
257
|
learn_startup: "learn_startup";
|
|
257
258
|
start_company: "start_company";
|
|
259
|
+
get_feedback: "get_feedback";
|
|
260
|
+
find_testers: "find_testers";
|
|
261
|
+
mentor_others: "mentor_others";
|
|
262
|
+
invest_in_startups: "invest_in_startups";
|
|
263
|
+
join_startup: "join_startup";
|
|
264
|
+
find_service_providers: "find_service_providers";
|
|
265
|
+
attend_events: "attend_events";
|
|
258
266
|
}>;
|
|
259
267
|
export type Goal = z.infer<typeof goalSchema>;
|
|
260
268
|
/**
|
|
@@ -281,6 +289,24 @@ export type Gender = z.infer<typeof genderSchema>;
|
|
|
281
289
|
* words it.
|
|
282
290
|
*/
|
|
283
291
|
export declare const displayNameSchema: z.ZodString;
|
|
292
|
+
/**
|
|
293
|
+
* A full name's first and last name, DEDUCED, never asked (the user,
|
|
294
|
+
* 2026-10-18: onboarding asks one "Full name" and works the two out from the
|
|
295
|
+
* space). The first word is the first name and everything after the first
|
|
296
|
+
* space is the last name, kept as written ("Asha Rao Kulkarni" is Asha and
|
|
297
|
+
* Rao Kulkarni).
|
|
298
|
+
*
|
|
299
|
+
* ⚠ A ONE-WORD NAME IS A WHOLE NAME. Many people in India go by a single name,
|
|
300
|
+
* so `lastName` is then `''`, and nothing may refuse it for that.
|
|
301
|
+
*
|
|
302
|
+
* Derived wherever it is needed, never stored as a second copy of the name:
|
|
303
|
+
* `displayName` stays the one owner of a member's name, so a rename can never
|
|
304
|
+
* leave a stale first name behind.
|
|
305
|
+
*/
|
|
306
|
+
export declare function splitFullName(fullName: string): {
|
|
307
|
+
firstName: string;
|
|
308
|
+
lastName: string;
|
|
309
|
+
};
|
|
284
310
|
/**
|
|
285
311
|
* Indian mobile number: ten raw digits, no country code, matching the standard
|
|
286
312
|
* used across this family. Display adds the `+91`.
|
|
@@ -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;AAcxB;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,GAAI,OAAO,OAAO,KAAG,OAGvD,CAAC;AAEF,yEAAyE;AACzE,eAAO,MAAM,qBAAqB,GAAI,kBAAe,yDAC8B,CAAC;AAEpF;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,KAAK,MAAM,2DAKlC,CAAC;AAEzC,4EAA4E;AAC5E,eAAO,MAAM,cAAc,wDAGS,CAAC;AACrC,yFAAyF;AACzF,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAI5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,aAAa,UAIxB,CAAC;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEpD;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,UAAkC,CAAC;AAC7D,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEpD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAM1E;AAED,2EAA2E;AAC3E,eAAO,MAAM,cAAc,GAAI,OAAO,SAAS,MAAM,EAAE,EAAE,SAAS,MAAM,aACI,CAAC;AAE7E;;;;GAIG;AACH,eAAO,MAAM,kCAAkC,4CAA6C,CAAC;AAC7F,eAAO,MAAM,+BAA+B;;;;EAA6C,CAAC;AAC1F,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAC;AAExF;;;;GAIG;AACH,eAAO,MAAM,aAAa,8HAQhB,CAAC;AAEX;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAgBzD;AAID;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,IAAI,CAAC;AAC9B,eAAO,MAAM,YAAY,KAAK,CAAC;AAE/B,eAAO,MAAM,cAAc,aAOqD,CAAC;AACjF,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAEtD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,kBAAkB,uNAqBrB,CAAC;AAIX,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,aAE/B,CAAC;AAIH;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,qGASvB,CAAC;AACX,eAAO,MAAM,kBAAkB;;;;;;;;;EAA+B,CAAC;AAC/D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D;;;GAGG;AACH,eAAO,MAAM,eAAe,2PAsBlB,CAAC;AACX,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;EAA0B,CAAC;AACtD,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAEtD;;;;GAIG;AACH,eAAO,MAAM,YAAY,8LAgBf,CAAC;AACX,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;EAAuB,CAAC;AAChD,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEhD
|
|
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
|
@@ -312,9 +312,10 @@ export const skillSchema = z.enum(SKILL_VALUES);
|
|
|
312
312
|
* venture works only for a member who ALREADY HAS a venture row, and somebody
|
|
313
313
|
* who wants to start a company is precisely the member with no venture at all,
|
|
314
314
|
* so there is nothing to infer from. It also cannot express a founder with a
|
|
315
|
-
* running company who wants to start another.
|
|
316
|
-
*
|
|
317
|
-
*
|
|
315
|
+
* running company who wants to start another. The others were all asks of other
|
|
316
|
+
* people and this one an ask of the product, which is the whole of the oddness.
|
|
317
|
+
* (hary, 2026-09-21.) Since 0.16.0 the list also holds offers (`mentor_others`,
|
|
318
|
+
* `invest_in_startups`) and a second ask of the product (`attend_events`).
|
|
318
319
|
*/
|
|
319
320
|
export const GOAL_VALUES = [
|
|
320
321
|
'find_cofounder',
|
|
@@ -328,6 +329,18 @@ export const GOAL_VALUES = [
|
|
|
328
329
|
'market_insights',
|
|
329
330
|
'learn_startup',
|
|
330
331
|
'start_company',
|
|
332
|
+
// 0.16.0 (the user, 2026-10-18: "a few more ... cover most of the goals"):
|
|
333
|
+
// the journey's own asks (feedback on an idea, early testers), the other
|
|
334
|
+
// side of mentorship and of fundraising (mentoring, investing), and three
|
|
335
|
+
// more a founder network is asked for: joining a startup, finding
|
|
336
|
+
// service providers, and events. Appended, per the rule above.
|
|
337
|
+
'get_feedback',
|
|
338
|
+
'find_testers',
|
|
339
|
+
'mentor_others',
|
|
340
|
+
'invest_in_startups',
|
|
341
|
+
'join_startup',
|
|
342
|
+
'find_service_providers',
|
|
343
|
+
'attend_events',
|
|
331
344
|
];
|
|
332
345
|
export const goalSchema = z.enum(GOAL_VALUES);
|
|
333
346
|
// ─── Gender ──────────────────────────────────────────────────────────────────
|
|
@@ -350,6 +363,27 @@ export const genderSchema = z.enum(GENDER_VALUES);
|
|
|
350
363
|
* words it.
|
|
351
364
|
*/
|
|
352
365
|
export const displayNameSchema = z.string().trim().min(1).max(DISPLAY_NAME_MAX);
|
|
366
|
+
/**
|
|
367
|
+
* A full name's first and last name, DEDUCED, never asked (the user,
|
|
368
|
+
* 2026-10-18: onboarding asks one "Full name" and works the two out from the
|
|
369
|
+
* space). The first word is the first name and everything after the first
|
|
370
|
+
* space is the last name, kept as written ("Asha Rao Kulkarni" is Asha and
|
|
371
|
+
* Rao Kulkarni).
|
|
372
|
+
*
|
|
373
|
+
* ⚠ A ONE-WORD NAME IS A WHOLE NAME. Many people in India go by a single name,
|
|
374
|
+
* so `lastName` is then `''`, and nothing may refuse it for that.
|
|
375
|
+
*
|
|
376
|
+
* Derived wherever it is needed, never stored as a second copy of the name:
|
|
377
|
+
* `displayName` stays the one owner of a member's name, so a rename can never
|
|
378
|
+
* leave a stale first name behind.
|
|
379
|
+
*/
|
|
380
|
+
export function splitFullName(fullName) {
|
|
381
|
+
const name = fullName.trim().replace(/\s+/g, ' ');
|
|
382
|
+
const space = name.indexOf(' ');
|
|
383
|
+
return space === -1
|
|
384
|
+
? { firstName: name, lastName: '' }
|
|
385
|
+
: { firstName: name.slice(0, space), lastName: name.slice(space + 1) };
|
|
386
|
+
}
|
|
353
387
|
// ─── Phone ───────────────────────────────────────────────────────────────────
|
|
354
388
|
/**
|
|
355
389
|
* Indian mobile number: ten raw digits, no country code, matching the standard
|
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 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 * 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. Every other value is an ask of\n * other people and this one is an ask of the product, which is the whole of the\n * oddness. (hary, 2026-09-21.)\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] as const;\nexport const goalSchema = z.enum(GOAL_VALUES);\nexport type Goal = z.infer<typeof goalSchema>;\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 * 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. Plain bounds, no custom\n * message: `too_small` with minimum 1 already says \"required\", and the client\n * words it.\n */\nexport const displayNameSchema = z.string().trim().min(1).max(DISPLAY_NAME_MAX);\n\n// ─── Phone ───────────────────────────────────────────────────────────────────\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(/^[6-9]\\d{9}$/, VALIDATION_MESSAGE.phoneInvalid);\nexport type Phone = z.infer<typeof phoneSchema>;\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/**\n * The city these coordinates look like, for the member to CONFIRM, or `null`\n * when the lookup could not place them confidently (or they are outside India,\n * whose area data is all there is). A suggestion, never a write: only what the\n * member then submits becomes their location.\n */\nexport const suggestedLocationSchema = locationSchema.nullable();\nexport type SuggestedLocation = z.infer<typeof suggestedLocationSchema>;\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 DISPLAY_NAME_MAX,\n LINK_URL_MAX,\n PAGE_SIZE_DEFAULT,\n PAGE_SIZE_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 * 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// ─── 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 * 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. Plain bounds, no custom\n * message: `too_small` with minimum 1 already says \"required\", and the client\n * words it.\n */\nexport const displayNameSchema = z.string().trim().min(1).max(DISPLAY_NAME_MAX);\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 = fullName.trim().replace(/\\s+/g, ' ');\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/**\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(/^[6-9]\\d{9}$/, VALIDATION_MESSAGE.phoneInvalid);\nexport type Phone = z.infer<typeof phoneSchema>;\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/**\n * The city these coordinates look like, for the member to CONFIRM, or `null`\n * when the lookup could not place them confidently (or they are outside India,\n * whose area data is all there is). A suggestion, never a write: only what the\n * member then submits becomes their location.\n */\nexport const suggestedLocationSchema = locationSchema.nullable();\nexport type SuggestedLocation = z.infer<typeof suggestedLocationSchema>;\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"]}
|
|
@@ -250,6 +250,7 @@ export declare const communityCardSchema: z.ZodObject<{
|
|
|
250
250
|
marketing: "marketing";
|
|
251
251
|
supply_chain: "supply_chain";
|
|
252
252
|
fundraising: "fundraising";
|
|
253
|
+
social_enterprise: "social_enterprise";
|
|
253
254
|
mentoring: "mentoring";
|
|
254
255
|
leadership: "leadership";
|
|
255
256
|
networking: "networking";
|
|
@@ -308,7 +309,6 @@ export declare const communityCardSchema: z.ZodObject<{
|
|
|
308
309
|
environmental: "environmental";
|
|
309
310
|
women_empowerment: "women_empowerment";
|
|
310
311
|
community_service: "community_service";
|
|
311
|
-
social_enterprise: "social_enterprise";
|
|
312
312
|
education_access: "education_access";
|
|
313
313
|
}>>;
|
|
314
314
|
memberCount: z.ZodNumber;
|
|
@@ -420,6 +420,7 @@ export declare const communitySchema: z.ZodObject<{
|
|
|
420
420
|
marketing: "marketing";
|
|
421
421
|
supply_chain: "supply_chain";
|
|
422
422
|
fundraising: "fundraising";
|
|
423
|
+
social_enterprise: "social_enterprise";
|
|
423
424
|
mentoring: "mentoring";
|
|
424
425
|
leadership: "leadership";
|
|
425
426
|
networking: "networking";
|
|
@@ -478,7 +479,6 @@ export declare const communitySchema: z.ZodObject<{
|
|
|
478
479
|
environmental: "environmental";
|
|
479
480
|
women_empowerment: "women_empowerment";
|
|
480
481
|
community_service: "community_service";
|
|
481
|
-
social_enterprise: "social_enterprise";
|
|
482
482
|
education_access: "education_access";
|
|
483
483
|
}>>;
|
|
484
484
|
memberCount: z.ZodNumber;
|
|
@@ -663,6 +663,7 @@ export declare const communitySuggestionSchema: z.ZodObject<{
|
|
|
663
663
|
marketing: "marketing";
|
|
664
664
|
supply_chain: "supply_chain";
|
|
665
665
|
fundraising: "fundraising";
|
|
666
|
+
social_enterprise: "social_enterprise";
|
|
666
667
|
mentoring: "mentoring";
|
|
667
668
|
leadership: "leadership";
|
|
668
669
|
networking: "networking";
|
|
@@ -721,7 +722,6 @@ export declare const communitySuggestionSchema: z.ZodObject<{
|
|
|
721
722
|
environmental: "environmental";
|
|
722
723
|
women_empowerment: "women_empowerment";
|
|
723
724
|
community_service: "community_service";
|
|
724
|
-
social_enterprise: "social_enterprise";
|
|
725
725
|
education_access: "education_access";
|
|
726
726
|
}>>;
|
|
727
727
|
status: z.ZodEnum<{
|
|
@@ -791,6 +791,7 @@ export declare const communityCardPageSchema: z.ZodObject<{
|
|
|
791
791
|
marketing: "marketing";
|
|
792
792
|
supply_chain: "supply_chain";
|
|
793
793
|
fundraising: "fundraising";
|
|
794
|
+
social_enterprise: "social_enterprise";
|
|
794
795
|
mentoring: "mentoring";
|
|
795
796
|
leadership: "leadership";
|
|
796
797
|
networking: "networking";
|
|
@@ -849,7 +850,6 @@ export declare const communityCardPageSchema: z.ZodObject<{
|
|
|
849
850
|
environmental: "environmental";
|
|
850
851
|
women_empowerment: "women_empowerment";
|
|
851
852
|
community_service: "community_service";
|
|
852
|
-
social_enterprise: "social_enterprise";
|
|
853
853
|
education_access: "education_access";
|
|
854
854
|
}>>;
|
|
855
855
|
memberCount: z.ZodNumber;
|
|
@@ -978,6 +978,7 @@ export declare const communitySuggestionPageSchema: z.ZodObject<{
|
|
|
978
978
|
marketing: "marketing";
|
|
979
979
|
supply_chain: "supply_chain";
|
|
980
980
|
fundraising: "fundraising";
|
|
981
|
+
social_enterprise: "social_enterprise";
|
|
981
982
|
mentoring: "mentoring";
|
|
982
983
|
leadership: "leadership";
|
|
983
984
|
networking: "networking";
|
|
@@ -1036,7 +1037,6 @@ export declare const communitySuggestionPageSchema: z.ZodObject<{
|
|
|
1036
1037
|
environmental: "environmental";
|
|
1037
1038
|
women_empowerment: "women_empowerment";
|
|
1038
1039
|
community_service: "community_service";
|
|
1039
|
-
social_enterprise: "social_enterprise";
|
|
1040
1040
|
education_access: "education_access";
|
|
1041
1041
|
}>>;
|
|
1042
1042
|
status: z.ZodEnum<{
|
|
@@ -1090,6 +1090,7 @@ export declare const communityDiscoverQuerySchema: z.ZodObject<{
|
|
|
1090
1090
|
marketing: "marketing";
|
|
1091
1091
|
supply_chain: "supply_chain";
|
|
1092
1092
|
fundraising: "fundraising";
|
|
1093
|
+
social_enterprise: "social_enterprise";
|
|
1093
1094
|
mentoring: "mentoring";
|
|
1094
1095
|
leadership: "leadership";
|
|
1095
1096
|
networking: "networking";
|
|
@@ -1148,7 +1149,6 @@ export declare const communityDiscoverQuerySchema: z.ZodObject<{
|
|
|
1148
1149
|
environmental: "environmental";
|
|
1149
1150
|
women_empowerment: "women_empowerment";
|
|
1150
1151
|
community_service: "community_service";
|
|
1151
|
-
social_enterprise: "social_enterprise";
|
|
1152
1152
|
education_access: "education_access";
|
|
1153
1153
|
}>>>, unknown>;
|
|
1154
1154
|
}, z.core.$strip>;
|
|
@@ -1233,6 +1233,7 @@ export declare const createCommunityBodySchema: z.ZodObject<{
|
|
|
1233
1233
|
marketing: "marketing";
|
|
1234
1234
|
supply_chain: "supply_chain";
|
|
1235
1235
|
fundraising: "fundraising";
|
|
1236
|
+
social_enterprise: "social_enterprise";
|
|
1236
1237
|
mentoring: "mentoring";
|
|
1237
1238
|
leadership: "leadership";
|
|
1238
1239
|
networking: "networking";
|
|
@@ -1291,7 +1292,6 @@ export declare const createCommunityBodySchema: z.ZodObject<{
|
|
|
1291
1292
|
environmental: "environmental";
|
|
1292
1293
|
women_empowerment: "women_empowerment";
|
|
1293
1294
|
community_service: "community_service";
|
|
1294
|
-
social_enterprise: "social_enterprise";
|
|
1295
1295
|
education_access: "education_access";
|
|
1296
1296
|
}>>>;
|
|
1297
1297
|
iconMediaId: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
@@ -1313,6 +1313,7 @@ export declare const updateCommunityBodySchema: z.ZodObject<{
|
|
|
1313
1313
|
marketing: "marketing";
|
|
1314
1314
|
supply_chain: "supply_chain";
|
|
1315
1315
|
fundraising: "fundraising";
|
|
1316
|
+
social_enterprise: "social_enterprise";
|
|
1316
1317
|
mentoring: "mentoring";
|
|
1317
1318
|
leadership: "leadership";
|
|
1318
1319
|
networking: "networking";
|
|
@@ -1371,7 +1372,6 @@ export declare const updateCommunityBodySchema: z.ZodObject<{
|
|
|
1371
1372
|
environmental: "environmental";
|
|
1372
1373
|
women_empowerment: "women_empowerment";
|
|
1373
1374
|
community_service: "community_service";
|
|
1374
|
-
social_enterprise: "social_enterprise";
|
|
1375
1375
|
education_access: "education_access";
|
|
1376
1376
|
}>>>;
|
|
1377
1377
|
}, z.core.$strip>;
|
|
@@ -1465,6 +1465,7 @@ export declare const createCommunitySuggestionBodySchema: z.ZodObject<{
|
|
|
1465
1465
|
marketing: "marketing";
|
|
1466
1466
|
supply_chain: "supply_chain";
|
|
1467
1467
|
fundraising: "fundraising";
|
|
1468
|
+
social_enterprise: "social_enterprise";
|
|
1468
1469
|
mentoring: "mentoring";
|
|
1469
1470
|
leadership: "leadership";
|
|
1470
1471
|
networking: "networking";
|
|
@@ -1523,7 +1524,6 @@ export declare const createCommunitySuggestionBodySchema: z.ZodObject<{
|
|
|
1523
1524
|
environmental: "environmental";
|
|
1524
1525
|
women_empowerment: "women_empowerment";
|
|
1525
1526
|
community_service: "community_service";
|
|
1526
|
-
social_enterprise: "social_enterprise";
|
|
1527
1527
|
education_access: "education_access";
|
|
1528
1528
|
}>>>;
|
|
1529
1529
|
}, z.core.$strip>;
|
|
@@ -164,6 +164,13 @@ export declare const discoverPageSchema: z.ZodObject<{
|
|
|
164
164
|
market_insights: "market_insights";
|
|
165
165
|
learn_startup: "learn_startup";
|
|
166
166
|
start_company: "start_company";
|
|
167
|
+
get_feedback: "get_feedback";
|
|
168
|
+
find_testers: "find_testers";
|
|
169
|
+
mentor_others: "mentor_others";
|
|
170
|
+
invest_in_startups: "invest_in_startups";
|
|
171
|
+
join_startup: "join_startup";
|
|
172
|
+
find_service_providers: "find_service_providers";
|
|
173
|
+
attend_events: "attend_events";
|
|
167
174
|
}>>>;
|
|
168
175
|
}, z.core.$strip>>;
|
|
169
176
|
nextCursor: z.ZodNullable<z.ZodString>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../../../src/domains/discover/discover.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAiBxB;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAQ9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAChE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAErE,4DAA4D;AAC5D,eAAO,MAAM,kBAAkB
|
|
1
|
+
{"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../../../src/domains/discover/discover.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAiBxB;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAQ9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAChE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAErE,4DAA4D;AAC5D,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAqC,CAAC;AACrE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAM9D;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAK/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC"}
|