founder-schema 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/README.md +18 -15
  2. package/dist/access/access.d.ts +76 -0
  3. package/dist/access/access.d.ts.map +1 -0
  4. package/dist/access/access.js +66 -0
  5. package/dist/access/access.js.map +1 -0
  6. package/dist/access/permissions.d.ts +153 -0
  7. package/dist/access/permissions.d.ts.map +1 -0
  8. package/dist/access/permissions.js +80 -0
  9. package/dist/access/permissions.js.map +1 -0
  10. package/dist/access/roles.d.ts +71 -0
  11. package/dist/access/roles.d.ts.map +1 -0
  12. package/dist/access/roles.js +72 -0
  13. package/dist/access/roles.js.map +1 -0
  14. package/dist/account.d.ts +40 -0
  15. package/dist/account.d.ts.map +1 -0
  16. package/dist/account.js +39 -0
  17. package/dist/account.js.map +1 -0
  18. package/dist/chat.d.ts +4 -4
  19. package/dist/chat.d.ts.map +1 -1
  20. package/dist/chat.js +7 -2
  21. package/dist/chat.js.map +1 -1
  22. package/dist/core/common.d.ts +304 -0
  23. package/dist/core/common.d.ts.map +1 -0
  24. package/dist/core/common.js +340 -0
  25. package/dist/core/common.js.map +1 -0
  26. package/dist/core/errors.d.ts +105 -0
  27. package/dist/core/errors.d.ts.map +1 -0
  28. package/dist/core/errors.js +115 -0
  29. package/dist/core/errors.js.map +1 -0
  30. package/dist/core/limits.d.ts +99 -0
  31. package/dist/core/limits.d.ts.map +1 -0
  32. package/dist/core/limits.js +109 -0
  33. package/dist/core/limits.js.map +1 -0
  34. package/dist/core/responses.d.ts +195 -0
  35. package/dist/core/responses.d.ts.map +1 -0
  36. package/dist/core/responses.js +73 -0
  37. package/dist/core/responses.js.map +1 -0
  38. package/dist/core/validation.d.ts +144 -0
  39. package/dist/core/validation.d.ts.map +1 -0
  40. package/dist/core/validation.js +150 -0
  41. package/dist/core/validation.js.map +1 -0
  42. package/dist/domains/account/account.d.ts +40 -0
  43. package/dist/domains/account/account.d.ts.map +1 -0
  44. package/dist/domains/account/account.js +39 -0
  45. package/dist/domains/account/account.js.map +1 -0
  46. package/dist/domains/audit/audit.d.ts +169 -0
  47. package/dist/domains/audit/audit.d.ts.map +1 -0
  48. package/dist/domains/audit/audit.js +84 -0
  49. package/dist/domains/audit/audit.js.map +1 -0
  50. package/dist/domains/chat/chat.d.ts +187 -0
  51. package/dist/domains/chat/chat.d.ts.map +1 -0
  52. package/dist/domains/chat/chat.js +189 -0
  53. package/dist/domains/chat/chat.js.map +1 -0
  54. package/dist/domains/dashboard/dashboard.d.ts +48 -0
  55. package/dist/domains/dashboard/dashboard.d.ts.map +1 -0
  56. package/dist/domains/dashboard/dashboard.js +58 -0
  57. package/dist/domains/dashboard/dashboard.js.map +1 -0
  58. package/dist/domains/discover/discover.d.ts +245 -0
  59. package/dist/domains/discover/discover.d.ts.map +1 -0
  60. package/dist/domains/discover/discover.js +68 -0
  61. package/dist/domains/discover/discover.js.map +1 -0
  62. package/dist/domains/feed/feed.d.ts +1658 -0
  63. package/dist/domains/feed/feed.d.ts.map +1 -0
  64. package/dist/domains/feed/feed.js +623 -0
  65. package/dist/domains/feed/feed.js.map +1 -0
  66. package/dist/domains/members/members.d.ts +175 -0
  67. package/dist/domains/members/members.d.ts.map +1 -0
  68. package/dist/domains/members/members.js +106 -0
  69. package/dist/domains/members/members.js.map +1 -0
  70. package/dist/domains/moderation/moderation.d.ts +184 -0
  71. package/dist/domains/moderation/moderation.d.ts.map +1 -0
  72. package/dist/domains/moderation/moderation.js +90 -0
  73. package/dist/domains/moderation/moderation.js.map +1 -0
  74. package/dist/domains/onboarding/onboarding.d.ts +1018 -0
  75. package/dist/domains/onboarding/onboarding.d.ts.map +1 -0
  76. package/dist/domains/onboarding/onboarding.js +717 -0
  77. package/dist/domains/onboarding/onboarding.js.map +1 -0
  78. package/dist/domains/profile/founder-profile.d.ts +387 -0
  79. package/dist/domains/profile/founder-profile.d.ts.map +1 -0
  80. package/dist/domains/profile/founder-profile.js +180 -0
  81. package/dist/domains/profile/founder-profile.js.map +1 -0
  82. package/dist/domains/support/support.d.ts +305 -0
  83. package/dist/domains/support/support.d.ts.map +1 -0
  84. package/dist/domains/support/support.js +129 -0
  85. package/dist/domains/support/support.js.map +1 -0
  86. package/dist/domains/team/team.d.ts +79 -0
  87. package/dist/domains/team/team.d.ts.map +1 -0
  88. package/dist/domains/team/team.js +54 -0
  89. package/dist/domains/team/team.js.map +1 -0
  90. package/dist/domains/uploads/uploads.d.ts +113 -0
  91. package/dist/domains/uploads/uploads.d.ts.map +1 -0
  92. package/dist/domains/uploads/uploads.js +106 -0
  93. package/dist/domains/uploads/uploads.js.map +1 -0
  94. package/dist/env.d.ts +85 -0
  95. package/dist/env.d.ts.map +1 -0
  96. package/dist/env.js +150 -0
  97. package/dist/env.js.map +1 -0
  98. package/dist/index.d.ts +21 -11
  99. package/dist/index.d.ts.map +1 -1
  100. package/dist/index.js +30 -17
  101. package/dist/index.js.map +1 -1
  102. package/dist/onboarding.d.ts +58 -32
  103. package/dist/onboarding.d.ts.map +1 -1
  104. package/dist/onboarding.js +155 -32
  105. package/dist/onboarding.js.map +1 -1
  106. package/package.json +14 -2
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The member's Founder Circle account
3
+ * ===================================
4
+ * Deleting it is PER PRODUCT. It removes the member from Founder Circle and
5
+ * leaves their central-auth account, and every other product they use,
6
+ * exactly as it was. Central auth deleting the whole account arrives here as
7
+ * its `user.deleted` webhook and has the same outcome.
8
+ *
9
+ * @module founder-schema/account
10
+ */
11
+ import { z } from 'zod';
12
+ /**
13
+ * `DELETE /api/v1/users/me`. No body: the confirmation (typing the account's
14
+ * email) is the client's, and the server's check is the signed-in session.
15
+ *
16
+ * What it does, all of it before it answers:
17
+ * - the founder profile stops being listed or reachable;
18
+ * - the member's posts, polls and comments are deleted, and the comment
19
+ * counts on other people's posts go down with them;
20
+ * - their reactions, bookmarks and their votes on polls still open are
21
+ * withdrawn (a closed poll's result is final and is not rewritten);
22
+ * - their messages are erased from chat and they leave every conversation;
23
+ * - the onboarding draft is discarded.
24
+ *
25
+ * Signing in to Founder Circle again later starts a NEW, empty account.
26
+ *
27
+ * Refusals: `SERVICE_UNAVAILABLE` (503) when the chat server could not erase
28
+ * the messages. NOTHING was deleted then, and retrying is safe.
29
+ * `RATE_LIMITED` (429). `UNAUTHORIZED` / `TOKEN_EXPIRED` /
30
+ * `SESSION_INVALIDATED` (401) and `FORBIDDEN` (403, an account that is not
31
+ * active) as for every signed-in call.
32
+ *
33
+ * The client signs out after a success: the account is gone, the cookies on
34
+ * the device are not.
35
+ */
36
+ export declare const deleteAccountResponseSchema: z.ZodObject<{
37
+ deleted: z.ZodLiteral<true>;
38
+ }, z.core.$strip>;
39
+ export type DeleteAccountResponse = z.infer<typeof deleteAccountResponseSchema>;
40
+ //# sourceMappingURL=account.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account.d.ts","sourceRoot":"","sources":["../src/account.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,2BAA2B;;iBAEtC,CAAC;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The member's Founder Circle account
3
+ * ===================================
4
+ * Deleting it is PER PRODUCT. It removes the member from Founder Circle and
5
+ * leaves their central-auth account, and every other product they use,
6
+ * exactly as it was. Central auth deleting the whole account arrives here as
7
+ * its `user.deleted` webhook and has the same outcome.
8
+ *
9
+ * @module founder-schema/account
10
+ */
11
+ import { z } from 'zod';
12
+ /**
13
+ * `DELETE /api/v1/users/me`. No body: the confirmation (typing the account's
14
+ * email) is the client's, and the server's check is the signed-in session.
15
+ *
16
+ * What it does, all of it before it answers:
17
+ * - the founder profile stops being listed or reachable;
18
+ * - the member's posts, polls and comments are deleted, and the comment
19
+ * counts on other people's posts go down with them;
20
+ * - their reactions, bookmarks and their votes on polls still open are
21
+ * withdrawn (a closed poll's result is final and is not rewritten);
22
+ * - their messages are erased from chat and they leave every conversation;
23
+ * - the onboarding draft is discarded.
24
+ *
25
+ * Signing in to Founder Circle again later starts a NEW, empty account.
26
+ *
27
+ * Refusals: `SERVICE_UNAVAILABLE` (503) when the chat server could not erase
28
+ * the messages. NOTHING was deleted then, and retrying is safe.
29
+ * `RATE_LIMITED` (429). `UNAUTHORIZED` / `TOKEN_EXPIRED` /
30
+ * `SESSION_INVALIDATED` (401) and `FORBIDDEN` (403, an account that is not
31
+ * active) as for every signed-in call.
32
+ *
33
+ * The client signs out after a success: the account is gone, the cookies on
34
+ * the device are not.
35
+ */
36
+ export const deleteAccountResponseSchema = z.object({
37
+ deleted: z.literal(true),
38
+ });
39
+ //# sourceMappingURL=account.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account.js","sourceRoot":"","sources":["../src/account.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,CAAC,MAAM,CAAC;IAClD,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;CACzB,CAAC,CAAC","sourcesContent":["/**\n * The member's Founder Circle account\n * ===================================\n * Deleting it is PER PRODUCT. It removes the member from Founder Circle and\n * leaves their central-auth account, and every other product they use,\n * exactly as it was. Central auth deleting the whole account arrives here as\n * its `user.deleted` webhook and has the same outcome.\n *\n * @module founder-schema/account\n */\n\nimport { z } from 'zod';\n\n/**\n * `DELETE /api/v1/users/me`. No body: the confirmation (typing the account's\n * email) is the client's, and the server's check is the signed-in session.\n *\n * What it does, all of it before it answers:\n * - the founder profile stops being listed or reachable;\n * - the member's posts, polls and comments are deleted, and the comment\n * counts on other people's posts go down with them;\n * - their reactions, bookmarks and their votes on polls still open are\n * withdrawn (a closed poll's result is final and is not rewritten);\n * - their messages are erased from chat and they leave every conversation;\n * - the onboarding draft is discarded.\n *\n * Signing in to Founder Circle again later starts a NEW, empty account.\n *\n * Refusals: `SERVICE_UNAVAILABLE` (503) when the chat server could not erase\n * the messages. NOTHING was deleted then, and retrying is safe.\n * `RATE_LIMITED` (429). `UNAUTHORIZED` / `TOKEN_EXPIRED` /\n * `SESSION_INVALIDATED` (401) and `FORBIDDEN` (403, an account that is not\n * active) as for every signed-in call.\n *\n * The client signs out after a success: the account is gone, the cookies on\n * the device are not.\n */\nexport const deleteAccountResponseSchema = z.object({\n deleted: z.literal(true),\n});\nexport type DeleteAccountResponse = z.infer<typeof deleteAccountResponseSchema>;\n"]}
package/dist/chat.d.ts CHANGED
@@ -166,22 +166,22 @@ export declare const chatParticipantsResponseSchema: z.ZodObject<{
166
166
  export type ChatParticipantsResponse = z.infer<typeof chatParticipantsResponseSchema>;
167
167
  /**
168
168
  * @deprecated Since 0.2.0: use `openChatBodySchema`, the same shape under the
169
- * name of the route that takes it. Removed in 0.3.0.
169
+ * name of the route that takes it.
170
170
  */
171
171
  export declare const chatInvitationBodySchema: z.ZodObject<{
172
172
  userId: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
173
173
  }, z.core.$strip>;
174
- /** @deprecated Since 0.2.0: use `OpenChatBody`. Removed in 0.3.0. */
174
+ /** @deprecated Since 0.2.0: use `OpenChatBody`. */
175
175
  export type ChatInvitationBody = OpenChatBody;
176
176
  /**
177
177
  * @deprecated Since 0.2.0, and it describes nothing that exists. The browser
178
178
  * never holds an invitation: `/chat/open` mints and redeems it server to
179
179
  * server. The old `/invitation` route that handed one out could never have
180
- * worked and is gone. Removed in 0.3.0.
180
+ * worked and is gone.
181
181
  */
182
182
  export declare const chatInvitationSchema: z.ZodObject<{
183
183
  invitationToken: z.ZodString;
184
184
  }, z.core.$strip>;
185
- /** @deprecated Since 0.2.0. Removed in 0.3.0. */
185
+ /** @deprecated Since 0.2.0. */
186
186
  export type ChatInvitation = z.infer<typeof chatInvitationSchema>;
187
187
  //# sourceMappingURL=chat.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../src/chat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,EAAG,gBAAyB,CAAC;AACzD,eAAO,MAAM,mBAAmB,gCAA6B,CAAC;AAC9D,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAIhE;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,gBAAgB;;;;;iBAQ3B,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAI1D;;;;;;;GAOG;AACH,eAAO,MAAM,iCAAiC,KAAK,CAAC;AAEpD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,QAAU,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,mBAAmB;;;;;iBAQ9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAIhE;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB;;iBAG7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D;;;GAGG;AACH,eAAO,MAAM,sBAAsB;;;iBAGjC,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAItE;;;;;;;;GAQG;AACH,eAAO,MAAM,0BAA0B;;iBAErC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAE9E,wEAAwE;AACxE,eAAO,MAAM,qBAAqB;;;;iBAKhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE;;;GAGG;AACH,eAAO,MAAM,8BAA8B;;;;;;iBAEzC,CAAC;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC;AAItF;;;GAGG;AACH,eAAO,MAAM,wBAAwB;;iBAAqB,CAAC;AAC3D,qEAAqE;AACrE,MAAM,MAAM,kBAAkB,GAAG,YAAY,CAAC;AAE9C;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB;;iBAE/B,CAAC;AACH,iDAAiD;AACjD,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC"}
1
+ {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../src/chat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,EAAG,gBAAyB,CAAC;AACzD,eAAO,MAAM,mBAAmB,gCAA6B,CAAC;AAC9D,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAIhE;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,gBAAgB;;;;;iBAQ3B,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAI1D;;;;;;;GAOG;AACH,eAAO,MAAM,iCAAiC,KAAK,CAAC;AAEpD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,QAAU,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,mBAAmB;;;;;iBAQ9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAIhE;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB;;iBAG7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D;;;GAGG;AACH,eAAO,MAAM,sBAAsB;;;iBAGjC,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAItE;;;;;;;;GAQG;AACH,eAAO,MAAM,0BAA0B;;iBAErC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAE9E,wEAAwE;AACxE,eAAO,MAAM,qBAAqB;;;;iBAKhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE;;;GAGG;AACH,eAAO,MAAM,8BAA8B;;;;;;iBAEzC,CAAC;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC;AAStF;;;GAGG;AACH,eAAO,MAAM,wBAAwB;;iBAAqB,CAAC;AAC3D,mDAAmD;AACnD,MAAM,MAAM,kBAAkB,GAAG,YAAY,CAAC;AAE9C;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB;;iBAE/B,CAAC;AACH,+BAA+B;AAC/B,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC"}
package/dist/chat.js CHANGED
@@ -167,16 +167,21 @@ export const chatParticipantsResponseSchema = z.object({
167
167
  participants: z.record(z.string(), chatParticipantSchema),
168
168
  });
169
169
  // ─── Deprecated ──────────────────────────────────────────────────────────────
170
+ //
171
+ // Unused by every consumer, and still exported: removing an export trips the
172
+ // publish rule that refuses a release missing anything the live one has, so it
173
+ // is the user's decision, not a side effect. They were meant to go in 0.3.0;
174
+ // that release kept them.
170
175
  /**
171
176
  * @deprecated Since 0.2.0: use `openChatBodySchema`, the same shape under the
172
- * name of the route that takes it. Removed in 0.3.0.
177
+ * name of the route that takes it.
173
178
  */
174
179
  export const chatInvitationBodySchema = openChatBodySchema;
175
180
  /**
176
181
  * @deprecated Since 0.2.0, and it describes nothing that exists. The browser
177
182
  * never holds an invitation: `/chat/open` mints and redeems it server to
178
183
  * server. The old `/invitation` route that handed one out could never have
179
- * worked and is gone. Removed in 0.3.0.
184
+ * worked and is gone.
180
185
  */
181
186
  export const chatInvitationSchema = z.object({
182
187
  invitationToken: z.string().min(1),
package/dist/chat.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"chat.js","sourceRoot":"","sources":["../src/chat.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema — Chat: config, auth token, opening a conversation, participants\n *\n * Founder Circle does not run a chat server. It uses the shared one, which\n * serves several products, and that is the whole reason this file is careful:\n * when one server serves many tenants, the failure mode is SILENT. A push once\n * returned success having sent nothing, because the caller held the wrong\n * product's key and nothing in the exchange said which product it was for.\n *\n * So the rule these shapes exist to hold: TENANCY COMES FROM THE CREDENTIAL,\n * never from a request body. The token says which product it is for, the chat\n * server resolves the product from the token and from nothing else, and a\n * Founder Circle token cannot address another product's conversation even when\n * handed a valid-looking id.\n *\n * ⚠️ TEXT ONLY. There is deliberately no `callsEnabled` flag here, and its\n * absence is the feature: the user ruled chat is text only, there is no call\n * code to branch on, and the browser is told to deny camera and microphone\n * outright. A boolean that can never be true is an invitation to turn it on.\n *\n * @module founder-schema/chat\n */\n\nimport { z } from 'zod';\nimport { objectIdSchema, usernameSchema } from './common.js';\nimport { CHAT_PARTICIPANTS_MAX } from './limits.js';\n\n/**\n * The product this package speaks for.\n *\n * Typed as a LITERAL on purpose. If the shared chat server ever hands back a\n * config or a token minted for a different product, parsing fails loudly here\n * rather than the client quietly talking to the wrong tenant. That is exactly\n * the class of bug this file exists to prevent, so it should be impossible to\n * carry past the first parse.\n */\nexport const CHAT_PRODUCT_ID = 'founder-circle' as const;\nexport const chatProductIdSchema = z.literal(CHAT_PRODUCT_ID);\nexport type ChatProductId = z.infer<typeof chatProductIdSchema>;\n\n// ─── Config ──────────────────────────────────────────────────────────────────\n\n/**\n * What the client needs before it can open a socket.\n *\n * ⚠️ `enabled` IS MANDATORY AND IS NOT DERIVED FROM `url`. \"Messaging is not\n * switched on yet\" is a first-class state, not a failure: a 404 is the expected\n * answer today, and the client collapses a disabled flag, a null url, a non-2xx\n * and a thrown fetch to the same quiet \"not on yet\" rather than an error. If\n * `enabled` were inferred from a missing url, \"off\" and \"broken\" would be the\n * same wire shape and the inbox could not tell a member which one it was.\n *\n * ⚠️ `userId` is required and is the field nothing works without. The client\n * needs to know WHO THE VIEWER IS to decide which side a bubble sits on, to\n * filter the viewer out of presence and typing, to pick the other participant\n * for a row's name and avatar, and to tell its own optimistic message from one\n * echoed back. Without it every conversation renders as though the viewer were\n * nobody. It was missing from the first sketch of this endpoint.\n */\nexport const chatConfigSchema = z.object({\n enabled: z.boolean(),\n /** Null whenever chat is not switched on. Never a placeholder string. */\n url: z.url().nullable(),\n /** The VIEWER's id, as the chat server knows them. */\n userId: z.string(),\n /** Which product this config is for. See `chatProductIdSchema`. */\n productId: chatProductIdSchema,\n});\nexport type ChatConfig = z.infer<typeof chatConfigSchema>;\n\n// ─── Auth token ──────────────────────────────────────────────────────────────\n\n/**\n * How long before expiry the client re-mints, in seconds.\n *\n * Exported because it is a contract detail and not an implementation one: a\n * token handed over with two seconds left is a socket that authenticates and is\n * closed again immediately, so the minting side needs to know the consuming\n * side will not use the last minute of a token's life.\n */\nexport const CHAT_TOKEN_REFRESH_MARGIN_SECONDS = 60;\n\n/**\n * How often the live socket re-authenticates, in seconds.\n *\n * Auth is an EVENT on the open socket rather than a handshake parameter, so\n * re-authenticating does not drop the connection.\n *\n * ⚠️ THIS MUST STAY BELOW THE REAL TOKEN LIFETIME, by more than the refresh\n * margin. It assumes a 15 minute token. A re-auth interval longer than the\n * lifetime is a socket that dies every quarter of an hour with no explanation,\n * and the symptom appears far from the cause. If the issued lifetime changes,\n * this number moves with it or the two silently disagree.\n */\nexport const CHAT_SOCKET_REAUTH_SECONDS = 10 * 60;\n\n/**\n * A short-lived token for the shared chat server.\n *\n * ⚠️ BOTH TIME FIELDS ARE KEPT, and they are not redundant, because two\n * different parties read them with two different clocks:\n *\n * `expiresAt` an absolute instant, for the SERVER. The chat server holds the\n * authoritative clock and must reject an expired token on its\n * own, whatever any client believes.\n * `expiresIn` a duration in seconds, for the CLIENT. Scheduling a refresh\n * from `receivedAt + expiresIn` uses only the client's own\n * elapsed time, so a device whose absolute clock is wrong still\n * refreshes correctly. Scheduling from `expiresAt` instead means\n * a browser an hour fast treats every fresh token as already\n * expired and re-mints forever.\n *\n * Dropping either one breaks the party that reads it. This is the same reason\n * OAuth carries `expires_in` on the wire while the token itself carries `exp`.\n *\n * ⚠️ AND THE CLIENT IS NOT THE ENFORCER. Its margin is an optimisation to avoid\n * a pointless round trip. A wrong clock, a tab asleep for an hour, or a modified\n * client all defeat anything the client does, so the chat server rejects an\n * expired token on its own.\n */\nexport const chatAuthTokenSchema = z.object({\n token: z.string().min(1),\n /** ISO 8601. Parsed with `Date.parse`, never an epoch number. */\n expiresAt: z.iso.datetime(),\n /** Seconds from issue. See the note above on why both exist. */\n expiresIn: z.number().int().positive(),\n /** The product this token is scoped to. A mismatch must fail loudly. */\n productId: chatProductIdSchema,\n});\nexport type ChatAuthToken = z.infer<typeof chatAuthTokenSchema>;\n\n// ─── Opening a conversation ──────────────────────────────────────────────────\n\n/**\n * `POST /api/v1/chat/open`: start (or find) the direct conversation with\n * another member.\n *\n * Our backend decides who may talk to whom, because only it knows this\n * product's rules, and it does the whole exchange with the chat server itself:\n * it mints the viewer's token and an invitation naming both participants, and\n * redeems them server to server. No credential reaches the browser.\n */\nexport const openChatBodySchema = z.object({\n /** The OTHER participant. The viewer is taken from the credential, never the body. */\n userId: objectIdSchema,\n});\nexport type OpenChatBody = z.infer<typeof openChatBodySchema>;\n\n/**\n * The conversation to navigate to. `201` with `wasCreated: true` the first\n * time, `200` and `false` on every later open of the same pair.\n */\nexport const openChatResponseSchema = z.object({\n conversationId: z.string().min(1),\n wasCreated: z.boolean(),\n});\nexport type OpenChatResponse = z.infer<typeof openChatResponseSchema>;\n\n// ─── Participants ────────────────────────────────────────────────────────────\n\n/**\n * `POST /api/v1/chat/participants`: names and avatars for the people in the\n * viewer's conversations.\n *\n * The chat server knows ids and nothing about who anybody is, because it serves\n * several products and identity belongs to each; so the conversation list asks\n * here, once per screen. Bounded by `CHAT_PARTICIPANTS_MAX`, so it cannot page\n * through the directory.\n */\nexport const chatParticipantsBodySchema = z.object({\n userIds: z.array(objectIdSchema).min(1).max(CHAT_PARTICIPANTS_MAX),\n});\nexport type ChatParticipantsBody = z.infer<typeof chatParticipantsBodySchema>;\n\n/** The public card fields any member already sees, and nothing more. */\nexport const chatParticipantSchema = z.object({\n displayName: z.string(),\n username: usernameSchema,\n /** Null when the member has no photo. Ours, or from the login provider. */\n avatarUrl: z.url().nullable(),\n});\nexport type ChatParticipant = z.infer<typeof chatParticipantSchema>;\n\n/**\n * Keyed by user id. ⚠ An id with no active, onboarded profile is ABSENT, not\n * an error: the client renders it as a former member rather than guessing.\n */\nexport const chatParticipantsResponseSchema = z.object({\n participants: z.record(z.string(), chatParticipantSchema),\n});\nexport type ChatParticipantsResponse = z.infer<typeof chatParticipantsResponseSchema>;\n\n// ─── Deprecated ──────────────────────────────────────────────────────────────\n\n/**\n * @deprecated Since 0.2.0: use `openChatBodySchema`, the same shape under the\n * name of the route that takes it. Removed in 0.3.0.\n */\nexport const chatInvitationBodySchema = openChatBodySchema;\n/** @deprecated Since 0.2.0: use `OpenChatBody`. Removed in 0.3.0. */\nexport type ChatInvitationBody = OpenChatBody;\n\n/**\n * @deprecated Since 0.2.0, and it describes nothing that exists. The browser\n * never holds an invitation: `/chat/open` mints and redeems it server to\n * server. The old `/invitation` route that handed one out could never have\n * worked and is gone. Removed in 0.3.0.\n */\nexport const chatInvitationSchema = z.object({\n invitationToken: z.string().min(1),\n});\n/** @deprecated Since 0.2.0. Removed in 0.3.0. */\nexport type ChatInvitation = z.infer<typeof chatInvitationSchema>;\n"]}
1
+ {"version":3,"file":"chat.js","sourceRoot":"","sources":["../src/chat.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema — Chat: config, auth token, opening a conversation, participants\n *\n * Founder Circle does not run a chat server. It uses the shared one, which\n * serves several products, and that is the whole reason this file is careful:\n * when one server serves many tenants, the failure mode is SILENT. A push once\n * returned success having sent nothing, because the caller held the wrong\n * product's key and nothing in the exchange said which product it was for.\n *\n * So the rule these shapes exist to hold: TENANCY COMES FROM THE CREDENTIAL,\n * never from a request body. The token says which product it is for, the chat\n * server resolves the product from the token and from nothing else, and a\n * Founder Circle token cannot address another product's conversation even when\n * handed a valid-looking id.\n *\n * ⚠️ TEXT ONLY. There is deliberately no `callsEnabled` flag here, and its\n * absence is the feature: the user ruled chat is text only, there is no call\n * code to branch on, and the browser is told to deny camera and microphone\n * outright. A boolean that can never be true is an invitation to turn it on.\n *\n * @module founder-schema/chat\n */\n\nimport { z } from 'zod';\nimport { objectIdSchema, usernameSchema } from './common.js';\nimport { CHAT_PARTICIPANTS_MAX } from './limits.js';\n\n/**\n * The product this package speaks for.\n *\n * Typed as a LITERAL on purpose. If the shared chat server ever hands back a\n * config or a token minted for a different product, parsing fails loudly here\n * rather than the client quietly talking to the wrong tenant. That is exactly\n * the class of bug this file exists to prevent, so it should be impossible to\n * carry past the first parse.\n */\nexport const CHAT_PRODUCT_ID = 'founder-circle' as const;\nexport const chatProductIdSchema = z.literal(CHAT_PRODUCT_ID);\nexport type ChatProductId = z.infer<typeof chatProductIdSchema>;\n\n// ─── Config ──────────────────────────────────────────────────────────────────\n\n/**\n * What the client needs before it can open a socket.\n *\n * ⚠️ `enabled` IS MANDATORY AND IS NOT DERIVED FROM `url`. \"Messaging is not\n * switched on yet\" is a first-class state, not a failure: a 404 is the expected\n * answer today, and the client collapses a disabled flag, a null url, a non-2xx\n * and a thrown fetch to the same quiet \"not on yet\" rather than an error. If\n * `enabled` were inferred from a missing url, \"off\" and \"broken\" would be the\n * same wire shape and the inbox could not tell a member which one it was.\n *\n * ⚠️ `userId` is required and is the field nothing works without. The client\n * needs to know WHO THE VIEWER IS to decide which side a bubble sits on, to\n * filter the viewer out of presence and typing, to pick the other participant\n * for a row's name and avatar, and to tell its own optimistic message from one\n * echoed back. Without it every conversation renders as though the viewer were\n * nobody. It was missing from the first sketch of this endpoint.\n */\nexport const chatConfigSchema = z.object({\n enabled: z.boolean(),\n /** Null whenever chat is not switched on. Never a placeholder string. */\n url: z.url().nullable(),\n /** The VIEWER's id, as the chat server knows them. */\n userId: z.string(),\n /** Which product this config is for. See `chatProductIdSchema`. */\n productId: chatProductIdSchema,\n});\nexport type ChatConfig = z.infer<typeof chatConfigSchema>;\n\n// ─── Auth token ──────────────────────────────────────────────────────────────\n\n/**\n * How long before expiry the client re-mints, in seconds.\n *\n * Exported because it is a contract detail and not an implementation one: a\n * token handed over with two seconds left is a socket that authenticates and is\n * closed again immediately, so the minting side needs to know the consuming\n * side will not use the last minute of a token's life.\n */\nexport const CHAT_TOKEN_REFRESH_MARGIN_SECONDS = 60;\n\n/**\n * How often the live socket re-authenticates, in seconds.\n *\n * Auth is an EVENT on the open socket rather than a handshake parameter, so\n * re-authenticating does not drop the connection.\n *\n * ⚠️ THIS MUST STAY BELOW THE REAL TOKEN LIFETIME, by more than the refresh\n * margin. It assumes a 15 minute token. A re-auth interval longer than the\n * lifetime is a socket that dies every quarter of an hour with no explanation,\n * and the symptom appears far from the cause. If the issued lifetime changes,\n * this number moves with it or the two silently disagree.\n */\nexport const CHAT_SOCKET_REAUTH_SECONDS = 10 * 60;\n\n/**\n * A short-lived token for the shared chat server.\n *\n * ⚠️ BOTH TIME FIELDS ARE KEPT, and they are not redundant, because two\n * different parties read them with two different clocks:\n *\n * `expiresAt` an absolute instant, for the SERVER. The chat server holds the\n * authoritative clock and must reject an expired token on its\n * own, whatever any client believes.\n * `expiresIn` a duration in seconds, for the CLIENT. Scheduling a refresh\n * from `receivedAt + expiresIn` uses only the client's own\n * elapsed time, so a device whose absolute clock is wrong still\n * refreshes correctly. Scheduling from `expiresAt` instead means\n * a browser an hour fast treats every fresh token as already\n * expired and re-mints forever.\n *\n * Dropping either one breaks the party that reads it. This is the same reason\n * OAuth carries `expires_in` on the wire while the token itself carries `exp`.\n *\n * ⚠️ AND THE CLIENT IS NOT THE ENFORCER. Its margin is an optimisation to avoid\n * a pointless round trip. A wrong clock, a tab asleep for an hour, or a modified\n * client all defeat anything the client does, so the chat server rejects an\n * expired token on its own.\n */\nexport const chatAuthTokenSchema = z.object({\n token: z.string().min(1),\n /** ISO 8601. Parsed with `Date.parse`, never an epoch number. */\n expiresAt: z.iso.datetime(),\n /** Seconds from issue. See the note above on why both exist. */\n expiresIn: z.number().int().positive(),\n /** The product this token is scoped to. A mismatch must fail loudly. */\n productId: chatProductIdSchema,\n});\nexport type ChatAuthToken = z.infer<typeof chatAuthTokenSchema>;\n\n// ─── Opening a conversation ──────────────────────────────────────────────────\n\n/**\n * `POST /api/v1/chat/open`: start (or find) the direct conversation with\n * another member.\n *\n * Our backend decides who may talk to whom, because only it knows this\n * product's rules, and it does the whole exchange with the chat server itself:\n * it mints the viewer's token and an invitation naming both participants, and\n * redeems them server to server. No credential reaches the browser.\n */\nexport const openChatBodySchema = z.object({\n /** The OTHER participant. The viewer is taken from the credential, never the body. */\n userId: objectIdSchema,\n});\nexport type OpenChatBody = z.infer<typeof openChatBodySchema>;\n\n/**\n * The conversation to navigate to. `201` with `wasCreated: true` the first\n * time, `200` and `false` on every later open of the same pair.\n */\nexport const openChatResponseSchema = z.object({\n conversationId: z.string().min(1),\n wasCreated: z.boolean(),\n});\nexport type OpenChatResponse = z.infer<typeof openChatResponseSchema>;\n\n// ─── Participants ────────────────────────────────────────────────────────────\n\n/**\n * `POST /api/v1/chat/participants`: names and avatars for the people in the\n * viewer's conversations.\n *\n * The chat server knows ids and nothing about who anybody is, because it serves\n * several products and identity belongs to each; so the conversation list asks\n * here, once per screen. Bounded by `CHAT_PARTICIPANTS_MAX`, so it cannot page\n * through the directory.\n */\nexport const chatParticipantsBodySchema = z.object({\n userIds: z.array(objectIdSchema).min(1).max(CHAT_PARTICIPANTS_MAX),\n});\nexport type ChatParticipantsBody = z.infer<typeof chatParticipantsBodySchema>;\n\n/** The public card fields any member already sees, and nothing more. */\nexport const chatParticipantSchema = z.object({\n displayName: z.string(),\n username: usernameSchema,\n /** Null when the member has no photo. Ours, or from the login provider. */\n avatarUrl: z.url().nullable(),\n});\nexport type ChatParticipant = z.infer<typeof chatParticipantSchema>;\n\n/**\n * Keyed by user id. ⚠ An id with no active, onboarded profile is ABSENT, not\n * an error: the client renders it as a former member rather than guessing.\n */\nexport const chatParticipantsResponseSchema = z.object({\n participants: z.record(z.string(), chatParticipantSchema),\n});\nexport type ChatParticipantsResponse = z.infer<typeof chatParticipantsResponseSchema>;\n\n// ─── Deprecated ──────────────────────────────────────────────────────────────\n//\n// Unused by every consumer, and still exported: removing an export trips the\n// publish rule that refuses a release missing anything the live one has, so it\n// is the user's decision, not a side effect. They were meant to go in 0.3.0;\n// that release kept them.\n\n/**\n * @deprecated Since 0.2.0: use `openChatBodySchema`, the same shape under the\n * name of the route that takes it.\n */\nexport const chatInvitationBodySchema = openChatBodySchema;\n/** @deprecated Since 0.2.0: use `OpenChatBody`. */\nexport type ChatInvitationBody = OpenChatBody;\n\n/**\n * @deprecated Since 0.2.0, and it describes nothing that exists. The browser\n * never holds an invitation: `/chat/open` mints and redeems it server to\n * server. The old `/invitation` route that handed one out could never have\n * worked and is gone.\n */\nexport const chatInvitationSchema = z.object({\n invitationToken: z.string().min(1),\n});\n/** @deprecated Since 0.2.0. */\nexport type ChatInvitation = z.infer<typeof chatInvitationSchema>;\n"]}
@@ -0,0 +1,304 @@
1
+ /**
2
+ * founder-schema — Shared helpers and core enums
3
+ *
4
+ * The stable building blocks reused across every Founder Circle schema.
5
+ * Anything that both the backend and a client need to agree on lives here.
6
+ *
7
+ * @module founder-schema/core/common
8
+ */
9
+ import { z } from 'zod';
10
+ /**
11
+ * Converts empty / whitespace-only strings to `undefined` so Zod `.optional()`
12
+ * treats them as "not provided".
13
+ *
14
+ * Why this exists: an HTML form submits `""` for a cleared field, and without
15
+ * this an optional field would be stored as an empty string rather than left
16
+ * alone. Jansathi shipped that bug in dating onboarding.
17
+ */
18
+ export declare const emptyStringToUndefined: (value: unknown) => unknown;
19
+ /** Optional trimmed string. Empty strings are coerced to `undefined`. */
20
+ export declare const optionalTrimmedString: (maxLength?: number) => z.ZodPreprocess<z.ZodOptional<z.ZodString>, unknown>;
21
+ /** A Mongo ObjectId as it crosses the wire: 24 lowercase hex characters. */
22
+ export declare const objectIdSchema: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
23
+ /** Named for what it is on the wire, so it never collides with Mongoose's `ObjectId`. */
24
+ export type ObjectIdString = z.infer<typeof objectIdSchema>;
25
+ /**
26
+ * A URL safe to store and, after the consumer's own check, to render.
27
+ *
28
+ * Use this for EVERY member-supplied URL. Never bare `z.url()`: it accepts any
29
+ * scheme the URL parser accepts, so `javascript:alert(document.cookie)`,
30
+ * `data:text/html,<script>...`, `vbscript:` and `file:///etc/passwd` all pass.
31
+ * Each of those is stored XSS the moment a client renders it as an `href`, and
32
+ * `data:` is the same in an `<img src>`.
33
+ *
34
+ * `hostname` is restricted too, not only the scheme. A bare host like
35
+ * `localhost` or a literal IP is refused, which closes server-side request
36
+ * forgery: this product runs on EC2 with an instance role, so a member-supplied
37
+ * `http://169.254.169.254/latest/meta-data/` that anything server-side ever
38
+ * fetches is a credential read.
39
+ *
40
+ * Validating here is necessary and not sufficient. A consumer still treats a
41
+ * stored URL as untrusted at render time, because rows written before this
42
+ * existed were never checked.
43
+ *
44
+ * ⚠️ IF THIS EVER GROWS A HOST ALLOWLIST, compare the PARSED hostname for
45
+ * exact equality. Never `includes`, never `endsWith`, never a regex over the
46
+ * whole URL. Each of these is a real bypass of a naive "is it YouTube" check,
47
+ * and all three read as obviously fine:
48
+ *
49
+ * https://evil.example/?next=youtube.com/watch contains the string
50
+ * youtube.com.evil.example starts with it
51
+ * youtu.be.evil.example a suffix check passes it
52
+ *
53
+ * The same mistake in the opposite direction to the `javascript:` hole: there
54
+ * the scheme was too permissive, here the host check only LOOKS strict.
55
+ * (krish hit this rendering inline video, 2026-09-21, and handles it by parsing
56
+ * and comparing the hostname exactly.)
57
+ */
58
+ export declare const safeUrlSchema: z.ZodURL;
59
+ export type SafeUrl = z.infer<typeof safeUrlSchema>;
60
+ /**
61
+ * A URL a member TYPES: `safeUrlSchema` plus the `LINK_URL_MAX` ceiling, so
62
+ * every member URL carries the same cap and no consumer has to remember it.
63
+ *
64
+ * Two schemas, not one, because the cap is about what a person types and is
65
+ * wrong for what the server reads from elsewhere. Until 0.2.0 there was only
66
+ * this one, and it capped a link preview's CDN image URL too: one 314-character
67
+ * `imageUrl` would have failed a whole feed page.
68
+ */
69
+ export declare const httpUrlSchema: z.ZodURL;
70
+ export type HttpUrl = z.infer<typeof httpUrlSchema>;
71
+ /**
72
+ * Public handle. Derived by the server at onboarding, changeable later.
73
+ *
74
+ * Rules are deliberately narrow because the handle appears in URLs and in
75
+ * mentions: lowercase letters, digits, underscore and a single dot, 3 to 30
76
+ * characters, must start with a letter.
77
+ *
78
+ * This is the FORMAT, and it is what every read shape uses. A handle being
79
+ * CHOSEN must also not be reserved: that is `chosenUsernameSchema`.
80
+ */
81
+ export declare const USERNAME_MIN = 3;
82
+ export declare const USERNAME_MAX = 30;
83
+ export declare const usernameSchema: z.ZodString;
84
+ export type Username = z.infer<typeof usernameSchema>;
85
+ /**
86
+ * Handles this product keeps for itself: route names a handle would shadow,
87
+ * and words that would let a member pass as staff.
88
+ *
89
+ * ⚠ HERE, IN THE CONTRACT, since 0.2.0. It used to live in the backend so it
90
+ * could grow without a publish, and nothing ever read it: the server derived
91
+ * handles without checking it, so a member called "Support" was handed
92
+ * `support`. One list, read by the server when it derives a handle and by any
93
+ * form that lets a member choose one, is worth a publish per addition.
94
+ *
95
+ * Matched exactly, after lowercasing.
96
+ */
97
+ export declare const RESERVED_USERNAMES: readonly ["admin", "about", "api", "chat", "discover", "feed", "founder", "foundercircle", "help", "home", "me", "meetup", "moderator", "onboarding", "settings", "staff", "superadmin", "support", "system", "team"];
98
+ export declare function isReservedUsername(handle: string): boolean;
99
+ /**
100
+ * A handle a member is CHOOSING: the format, and not reserved.
101
+ *
102
+ * ⚠ Not used on read shapes, deliberately. A handle stored before the reserved
103
+ * check existed must still READ, or one member's old handle would fail every
104
+ * page that shows their name.
105
+ */
106
+ export declare const chosenUsernameSchema: z.ZodString;
107
+ /**
108
+ * Stage of the founder's current venture.
109
+ *
110
+ * Ordered from earliest to latest so a range filter can compare by index. Add
111
+ * new stages at the END or the stored ordinal of existing rows shifts.
112
+ */
113
+ export declare const STARTUP_STAGE_VALUES: readonly ["idea", "validation", "mvp", "pre_seed", "seed", "series_a_plus", "profitable", "exited"];
114
+ export declare const startupStageSchema: z.ZodEnum<{
115
+ idea: "idea";
116
+ validation: "validation";
117
+ mvp: "mvp";
118
+ pre_seed: "pre_seed";
119
+ seed: "seed";
120
+ series_a_plus: "series_a_plus";
121
+ profitable: "profitable";
122
+ exited: "exited";
123
+ }>;
124
+ export type StartupStage = z.infer<typeof startupStageSchema>;
125
+ /**
126
+ * Industry the venture operates in. Kept as a closed list so Discover filters
127
+ * are a single indexed equality match rather than a text search.
128
+ */
129
+ export declare const INDUSTRY_VALUES: readonly ["agritech", "ai_ml", "b2b_saas", "consumer", "climate", "d2c", "deeptech", "edtech", "fintech", "gaming", "healthtech", "logistics", "manufacturing", "marketplace", "media", "mobility", "proptech", "robotics", "services", "space", "other"];
130
+ export declare const industrySchema: z.ZodEnum<{
131
+ agritech: "agritech";
132
+ ai_ml: "ai_ml";
133
+ b2b_saas: "b2b_saas";
134
+ consumer: "consumer";
135
+ climate: "climate";
136
+ d2c: "d2c";
137
+ deeptech: "deeptech";
138
+ edtech: "edtech";
139
+ fintech: "fintech";
140
+ gaming: "gaming";
141
+ healthtech: "healthtech";
142
+ logistics: "logistics";
143
+ manufacturing: "manufacturing";
144
+ marketplace: "marketplace";
145
+ media: "media";
146
+ mobility: "mobility";
147
+ proptech: "proptech";
148
+ robotics: "robotics";
149
+ services: "services";
150
+ space: "space";
151
+ other: "other";
152
+ }>;
153
+ export type Industry = z.infer<typeof industrySchema>;
154
+ /**
155
+ * What the founder brings. Free-text skills were considered and rejected: two
156
+ * people typing "growth" and "Growth Marketing" would never match each other in
157
+ * a filter, and the Discover tab is the whole point of the product.
158
+ */
159
+ export declare const SKILL_VALUES: readonly ["engineering", "product", "design", "data", "growth", "sales", "marketing", "operations", "finance", "legal", "hr_people", "supply_chain", "research", "fundraising", "community"];
160
+ export declare const skillSchema: z.ZodEnum<{
161
+ engineering: "engineering";
162
+ product: "product";
163
+ design: "design";
164
+ data: "data";
165
+ growth: "growth";
166
+ sales: "sales";
167
+ marketing: "marketing";
168
+ operations: "operations";
169
+ finance: "finance";
170
+ legal: "legal";
171
+ hr_people: "hr_people";
172
+ supply_chain: "supply_chain";
173
+ research: "research";
174
+ fundraising: "fundraising";
175
+ community: "community";
176
+ }>;
177
+ export type Skill = z.infer<typeof skillSchema>;
178
+ /**
179
+ * What the member is here for. ONE list, replacing three.
180
+ *
181
+ * This was `lookingFor` with six values. The design draws eight "Goals &
182
+ * Passion" tiles and four more on the business step; all three were the same
183
+ * idea at three grains, and shipping them together would have meant nobody
184
+ * could say which one Discover filters on - the answer settled by whichever
185
+ * query got written first. Merged on hary's ruling, 2026-09-21, while there
186
+ * were still no rows in any database.
187
+ *
188
+ * ⚠️ Stored per member and used as a Mongoose enum. Append only; removing a
189
+ * value orphans documents already saved with it.
190
+ *
191
+ * ⚠️ `start_company` READS ODDLY next to its neighbours and is correct. It was
192
+ * queried on the grounds that it restates `venture.stage: 'idea'`; it does not,
193
+ * and the reason is worth keeping so nobody re-opens it. Inferring it from the
194
+ * venture works only for a member who ALREADY HAS a venture row, and somebody
195
+ * who wants to start a company is precisely the member with no venture at all,
196
+ * so there is nothing to infer from. It also cannot express a founder with a
197
+ * running company who wants to start another. Every other value is an ask of
198
+ * other people and this one is an ask of the product, which is the whole of the
199
+ * oddness. (hary, 2026-09-21.)
200
+ */
201
+ export declare const GOAL_VALUES: readonly ["find_cofounder", "find_investors", "find_customers", "hiring", "partnerships", "mentorship", "grow_network", "business_ideas", "market_insights", "learn_startup", "start_company"];
202
+ export declare const goalSchema: z.ZodEnum<{
203
+ find_cofounder: "find_cofounder";
204
+ find_investors: "find_investors";
205
+ find_customers: "find_customers";
206
+ hiring: "hiring";
207
+ partnerships: "partnerships";
208
+ mentorship: "mentorship";
209
+ grow_network: "grow_network";
210
+ business_ideas: "business_ideas";
211
+ market_insights: "market_insights";
212
+ learn_startup: "learn_startup";
213
+ start_company: "start_company";
214
+ }>;
215
+ export type Goal = z.infer<typeof goalSchema>;
216
+ /**
217
+ * Demographic only. Nothing here computes anything from it, unlike the sibling
218
+ * gym product where gender feeds body-metric maths.
219
+ *
220
+ * Takes the family's three values and ADDS one rather than renaming any: a
221
+ * network for founders should let somebody decline the question without having
222
+ * to describe themselves as "other".
223
+ */
224
+ export declare const GENDER_VALUES: readonly ["male", "female", "other", "prefer_not_to_say"];
225
+ export declare const genderSchema: z.ZodEnum<{
226
+ other: "other";
227
+ male: "male";
228
+ female: "female";
229
+ prefer_not_to_say: "prefer_not_to_say";
230
+ }>;
231
+ export type Gender = z.infer<typeof genderSchema>;
232
+ /**
233
+ * Indian mobile number: ten raw digits, no country code, matching the standard
234
+ * used across this family. Display adds the `+91`.
235
+ *
236
+ * ⚠️ WHERE IT IS STORED IS NOT A SCHEMA QUESTION, and it has been ruled: the
237
+ * phone lives on the account mirror, never as a second copy on the profile.
238
+ * Two stored copies drift the first time somebody edits one, and the profile's
239
+ * would be the copy nobody keeps in sync.
240
+ */
241
+ export declare const phoneSchema: z.ZodString;
242
+ export type Phone = z.infer<typeof phoneSchema>;
243
+ /**
244
+ * Where the founder is based. City and country are what Discover filters on.
245
+ *
246
+ * ONE SHAPE for the profile, the Me tab's patch, onboarding's finish and the
247
+ * location suggestion. It was written out three times until 0.2.0, the same
248
+ * kind of copy that let the profile model lose Instagram.
249
+ *
250
+ * ⚠ `country` is REQUIRED and has NO default. A default here (it used to be
251
+ * `'India'`) is destructive on every write path: a founder in Berlin who
252
+ * corrects only their city would be moved to India by a value they never saw.
253
+ * A body that carries a location carries the whole location. Every stored
254
+ * location has a country, because the model requires one, so the read side
255
+ * loses nothing.
256
+ *
257
+ * This is a DECLARED location, not a GPS reading. Jansathi's rule applies
258
+ * here too: never write a declared location from a GPS capture.
259
+ */
260
+ export declare const locationSchema: z.ZodObject<{
261
+ city: z.ZodString;
262
+ state: z.ZodPreprocess<z.ZodOptional<z.ZodString>, unknown>;
263
+ country: z.ZodString;
264
+ }, z.core.$strip>;
265
+ export type FounderLocation = z.infer<typeof locationSchema>;
266
+ /**
267
+ * `GET /api/v1/location/areas/reverse?lat=&lng=`: plain decimal degrees.
268
+ *
269
+ * A QUERY, so coercion is right here: everything in a URL is a string. An
270
+ * emptied parameter is "missing", never 0, because 0,0 is a real place in the
271
+ * Gulf of Guinea and would be looked up rather than refused.
272
+ */
273
+ export declare const coordinatesQuerySchema: z.ZodObject<{
274
+ lat: z.ZodPreprocess<z.ZodCoercedNumber<unknown>, unknown>;
275
+ lng: z.ZodPreprocess<z.ZodCoercedNumber<unknown>, unknown>;
276
+ }, z.core.$strip>;
277
+ export type CoordinatesQuery = z.infer<typeof coordinatesQuerySchema>;
278
+ export type CoordinatesQueryInput = z.input<typeof coordinatesQuerySchema>;
279
+ /**
280
+ * The city these coordinates look like, for the member to CONFIRM, or `null`
281
+ * when the lookup could not place them confidently (or they are outside India,
282
+ * whose area data is all there is). A suggestion, never a write: only what the
283
+ * member then submits becomes their location.
284
+ */
285
+ export declare const suggestedLocationSchema: z.ZodNullable<z.ZodObject<{
286
+ city: z.ZodString;
287
+ state: z.ZodPreprocess<z.ZodOptional<z.ZodString>, unknown>;
288
+ country: z.ZodString;
289
+ }, z.core.$strip>>;
290
+ export type SuggestedLocation = z.infer<typeof suggestedLocationSchema>;
291
+ /**
292
+ * Cursor pagination query. Feed and Discover both use it.
293
+ *
294
+ * Cursor, not page number: the community feed changes under the reader, and an
295
+ * offset would repeat or skip posts as new ones land.
296
+ */
297
+ export declare const paginationQuerySchema: z.ZodObject<{
298
+ limit: z.ZodPreprocess<z.ZodDefault<z.ZodCoercedNumber<unknown>>, unknown>;
299
+ cursor: z.ZodOptional<z.ZodString>;
300
+ }, z.core.$strip>;
301
+ export type PaginationQuery = z.infer<typeof paginationQuerySchema>;
302
+ /** What a client may put IN the URL: every field optional, numbers as strings. */
303
+ export type PaginationQueryInput = z.input<typeof paginationQuerySchema>;
304
+ //# sourceMappingURL=common.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/core/common.ts"],"names":[],"mappings":"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"}