founder-schema 0.17.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/README.md +186 -17
  2. package/dist/access/access.d.ts +1 -1
  3. package/dist/access/access.d.ts.map +1 -1
  4. package/dist/access/access.js +4 -1
  5. package/dist/access/access.js.map +1 -1
  6. package/dist/access/permissions.d.ts +27 -29
  7. package/dist/access/permissions.d.ts.map +1 -1
  8. package/dist/access/permissions.js +39 -3
  9. package/dist/access/permissions.js.map +1 -1
  10. package/dist/core/common.d.ts +54 -23
  11. package/dist/core/common.d.ts.map +1 -1
  12. package/dist/core/common.js +49 -24
  13. package/dist/core/common.js.map +1 -1
  14. package/dist/core/errors.d.ts +7 -5
  15. package/dist/core/errors.d.ts.map +1 -1
  16. package/dist/core/errors.js +17 -11
  17. package/dist/core/errors.js.map +1 -1
  18. package/dist/core/limits.d.ts +73 -17
  19. package/dist/core/limits.d.ts.map +1 -1
  20. package/dist/core/limits.js +77 -22
  21. package/dist/core/limits.js.map +1 -1
  22. package/dist/core/money.d.ts +33 -0
  23. package/dist/core/money.d.ts.map +1 -0
  24. package/dist/core/money.js +36 -0
  25. package/dist/core/money.js.map +1 -0
  26. package/dist/core/responses.d.ts +14 -4
  27. package/dist/core/responses.d.ts.map +1 -1
  28. package/dist/core/responses.js.map +1 -1
  29. package/dist/core/validation.d.ts +19 -2
  30. package/dist/core/validation.d.ts.map +1 -1
  31. package/dist/core/validation.js +19 -2
  32. package/dist/core/validation.js.map +1 -1
  33. package/dist/domains/billing/billing.d.ts +61 -8
  34. package/dist/domains/billing/billing.d.ts.map +1 -1
  35. package/dist/domains/billing/billing.js +58 -22
  36. package/dist/domains/billing/billing.js.map +1 -1
  37. package/dist/domains/billing/gst.d.ts.map +1 -1
  38. package/dist/domains/billing/gst.js +4 -3
  39. package/dist/domains/billing/gst.js.map +1 -1
  40. package/dist/domains/chat/chat.d.ts +0 -21
  41. package/dist/domains/chat/chat.d.ts.map +1 -1
  42. package/dist/domains/chat/chat.js +0 -20
  43. package/dist/domains/chat/chat.js.map +1 -1
  44. package/dist/domains/communities/communities.d.ts +30 -12
  45. package/dist/domains/communities/communities.d.ts.map +1 -1
  46. package/dist/domains/communities/communities.js +43 -15
  47. package/dist/domains/communities/communities.js.map +1 -1
  48. package/dist/domains/contributions/contributions.d.ts +1 -1
  49. package/dist/domains/contributions/contributions.js +1 -1
  50. package/dist/domains/contributions/contributions.js.map +1 -1
  51. package/dist/domains/dashboard/dashboard.d.ts +3 -1
  52. package/dist/domains/dashboard/dashboard.d.ts.map +1 -1
  53. package/dist/domains/dashboard/dashboard.js +23 -7
  54. package/dist/domains/dashboard/dashboard.js.map +1 -1
  55. package/dist/domains/discover/discover.d.ts +8 -8
  56. package/dist/domains/feed/feed.d.ts +55 -153
  57. package/dist/domains/feed/feed.d.ts.map +1 -1
  58. package/dist/domains/feed/feed.js +53 -58
  59. package/dist/domains/feed/feed.js.map +1 -1
  60. package/dist/domains/location/areas.d.ts +331 -0
  61. package/dist/domains/location/areas.d.ts.map +1 -0
  62. package/dist/domains/location/areas.js +238 -0
  63. package/dist/domains/location/areas.js.map +1 -0
  64. package/dist/domains/location/india.d.ts +41 -123
  65. package/dist/domains/location/india.d.ts.map +1 -1
  66. package/dist/domains/location/india.js +121 -59
  67. package/dist/domains/location/india.js.map +1 -1
  68. package/dist/domains/members/members.d.ts +2 -2
  69. package/dist/domains/membership/membership.d.ts +26 -0
  70. package/dist/domains/membership/membership.d.ts.map +1 -1
  71. package/dist/domains/membership/membership.js +55 -1
  72. package/dist/domains/membership/membership.js.map +1 -1
  73. package/dist/domains/moderation/moderation.d.ts +2 -2
  74. package/dist/domains/moderation/moderation.js +3 -3
  75. package/dist/domains/moderation/moderation.js.map +1 -1
  76. package/dist/domains/notifications/notifications.d.ts +1 -10
  77. package/dist/domains/notifications/notifications.d.ts.map +1 -1
  78. package/dist/domains/notifications/notifications.js +2 -3
  79. package/dist/domains/notifications/notifications.js.map +1 -1
  80. package/dist/domains/onboarding/onboarding-answers.d.ts +42 -14
  81. package/dist/domains/onboarding/onboarding-answers.d.ts.map +1 -1
  82. package/dist/domains/onboarding/onboarding-answers.js +49 -33
  83. package/dist/domains/onboarding/onboarding-answers.js.map +1 -1
  84. package/dist/domains/onboarding/onboarding.d.ts +332 -138
  85. package/dist/domains/onboarding/onboarding.d.ts.map +1 -1
  86. package/dist/domains/onboarding/onboarding.js +47 -59
  87. package/dist/domains/onboarding/onboarding.js.map +1 -1
  88. package/dist/domains/products/products.d.ts +43 -8
  89. package/dist/domains/products/products.d.ts.map +1 -1
  90. package/dist/domains/products/products.js +57 -11
  91. package/dist/domains/products/products.js.map +1 -1
  92. package/dist/domains/profile/founder-profile.d.ts +123 -143
  93. package/dist/domains/profile/founder-profile.d.ts.map +1 -1
  94. package/dist/domains/profile/founder-profile.js +77 -35
  95. package/dist/domains/profile/founder-profile.js.map +1 -1
  96. package/dist/domains/profile/my-profile.d.ts +3 -3
  97. package/dist/domains/reviews/reviews.d.ts +1 -1
  98. package/dist/domains/reviews/reviews.d.ts.map +1 -1
  99. package/dist/domains/reviews/reviews.js +2 -2
  100. package/dist/domains/reviews/reviews.js.map +1 -1
  101. package/dist/domains/support/support.d.ts +22 -3
  102. package/dist/domains/support/support.d.ts.map +1 -1
  103. package/dist/domains/support/support.js +19 -5
  104. package/dist/domains/support/support.js.map +1 -1
  105. package/dist/domains/team/team.d.ts.map +1 -1
  106. package/dist/domains/team/team.js +3 -6
  107. package/dist/domains/team/team.js.map +1 -1
  108. package/dist/domains/uploads/uploads.d.ts +7 -22
  109. package/dist/domains/uploads/uploads.d.ts.map +1 -1
  110. package/dist/domains/uploads/uploads.js +7 -21
  111. package/dist/domains/uploads/uploads.js.map +1 -1
  112. package/dist/env.d.ts +26 -13
  113. package/dist/env.d.ts.map +1 -1
  114. package/dist/env.js +58 -23
  115. package/dist/env.js.map +1 -1
  116. package/dist/index.d.ts +19 -17
  117. package/dist/index.d.ts.map +1 -1
  118. package/dist/index.js +21 -17
  119. package/dist/index.js.map +1 -1
  120. package/package.json +1 -1
@@ -81,16 +81,7 @@ export declare const notificationKindSchema: z.ZodEnum<{
81
81
  export type NotificationKind = z.infer<typeof notificationKindSchema>;
82
82
  /** The switches a member has, each covering one or more kinds. */
83
83
  export declare const NOTIFICATION_PREFERENCE_VALUES: readonly ["comments", "replies", "productActivity", "recognition", "reactions", "reposts", "communities"];
84
- export declare const notificationPreferenceSchema: z.ZodEnum<{
85
- communities: "communities";
86
- comments: "comments";
87
- replies: "replies";
88
- productActivity: "productActivity";
89
- recognition: "recognition";
90
- reactions: "reactions";
91
- reposts: "reposts";
92
- }>;
93
- export type NotificationPreference = z.infer<typeof notificationPreferenceSchema>;
84
+ export type NotificationPreference = (typeof NOTIFICATION_PREFERENCE_VALUES)[number];
94
85
  /**
95
86
  * Which switch silences which kind. Null: always delivered. Exhaustive by
96
87
  * type, so a kind added without a decision here does not compile.
@@ -1 +1 @@
1
- {"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../../../src/domains/notifications/notifications.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAUxB;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,wBAAwB,+YAoB3B,CAAC;AACX,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;EAAmC,CAAC;AACvE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,kEAAkE;AAClE,eAAO,MAAM,8BAA8B,2GAQjC,CAAC;AACX,eAAO,MAAM,4BAA4B;;;;;;;;EAAyC,CAAC;AACnF,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAElF;;;GAGG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;CAoBqC,CAAC;AAE7E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAyD7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAsC,CAAC;AAC1E,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,iBAAiB;AACjB,eAAO,MAAM,6BAA6B;;iBAExC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEpF,eAAO,MAAM,wBAAwB;;iBAA+C,CAAC;AACrF,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,+CAA+C;AAC/C,eAAO,MAAM,6BAA6B;;;;;;;;iBAWxC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEpF,kFAAkF;AAClF,eAAO,MAAM,uCAAuC;;;;;;;;iBAQlD,CAAC;AACH,MAAM,MAAM,iCAAiC,GAAG,CAAC,CAAC,KAAK,CACrD,OAAO,uCAAuC,CAC/C,CAAC"}
1
+ {"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../../../src/domains/notifications/notifications.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAaxB;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,wBAAwB,+YAoB3B,CAAC;AACX,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;EAAmC,CAAC;AACvE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,kEAAkE;AAClE,eAAO,MAAM,8BAA8B,2GAQjC,CAAC;AACX,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,8BAA8B,CAAC,CAAC,MAAM,CAAC,CAAC;AAErF;;;GAGG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;CAoBqC,CAAC;AAE7E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAyD7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAsC,CAAC;AAC1E,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,iBAAiB;AACjB,eAAO,MAAM,6BAA6B;;iBAExC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEpF,eAAO,MAAM,wBAAwB;;iBAA+C,CAAC;AACrF,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,+CAA+C;AAC/C,eAAO,MAAM,6BAA6B;;;;;;;;iBAWxC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEpF,kFAAkF;AAClF,eAAO,MAAM,uCAAuC;;;;;;;;iBAQlD,CAAC;AACH,MAAM,MAAM,iCAAiC,GAAG,CAAC,CAAC,KAAK,CACrD,OAAO,uCAAuC,CAC/C,CAAC"}
@@ -32,7 +32,7 @@ import { z } from 'zod';
32
32
  import { objectIdSchema } from '../../core/common.js';
33
33
  import { NOTIFICATION_EXCERPT_MAX } from '../../core/limits.js';
34
34
  import { paginatedSchema } from '../../core/responses.js';
35
- import { communityChipSchema } from '../communities/communities.js';
35
+ import { communityChipSchema, communitySuggestionDecisionSchema, } from '../communities/communities.js';
36
36
  import { contentKindSchema, reactionTypeSchema } from '../feed/feed.js';
37
37
  import { planTierSchema } from '../membership/membership.js';
38
38
  import { productChipSchema, productInvitableRoleSchema } from '../products/products.js';
@@ -96,7 +96,6 @@ export const NOTIFICATION_PREFERENCE_VALUES = [
96
96
  'reposts',
97
97
  'communities',
98
98
  ];
99
- export const notificationPreferenceSchema = z.enum(NOTIFICATION_PREFERENCE_VALUES);
100
99
  /**
101
100
  * Which switch silences which kind. Null: always delivered. Exhaustive by
102
101
  * type, so a kind added without a decision here does not compile.
@@ -184,7 +183,7 @@ export const notificationSchema = z.object({
184
183
  .object({
185
184
  id: objectIdSchema,
186
185
  name: z.string(),
187
- status: z.enum(['approved', 'declined']),
186
+ status: communitySuggestionDecisionSchema,
188
187
  /** Why it was declined. */
189
188
  reason: z.string().optional(),
190
189
  })
@@ -1 +1 @@
1
- {"version":3,"file":"notifications.js","sourceRoot":"","sources":["../../../src/domains/notifications/notifications.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema — Notifications: the bell\n *\n * The design the user approved on 2026-10-05 (\"Founder Circle Schema Review\"):\n *\n * - ONE ROW PER RECIPIENT, written when the event happens.\n * - REPEATS MERGE: a second comment on your post updates the same row\n * (\"2 new comments, latest from Asha\"), it never adds one. `count` is how\n * many events it holds and `actor` the latest. A member repeating the same\n * act (reacting, taking it back, reacting again) does not ring it again.\n * - THE ACTOR IS LOOKED UP WHEN READ, never copied in: a renamed member shows\n * their new name, and a deleted one shows as null (\"A former member\").\n * - PREFERENCES ARE CHECKED before writing (Jansathi stores them and never\n * reads them). A team invitation is always delivered: it needs an answer.\n * - KEPT `NOTIFICATION_RETENTION_DAYS`, then deleted.\n * - Nobody is notified of their own action.\n *\n * In-app only. Push stays chat's (async-service) until a channel is designed:\n * a client polls the unread count (on focus, and every minute while visible).\n *\n * Routes (all signed in):\n * GET /api/v1/notifications paginationQuerySchema -> notificationPageSchema\n * GET /api/v1/notifications/unread-count -> notificationUnreadCountSchema\n * POST /api/v1/notifications/read mark every one read -> notificationUnreadCountSchema\n * PUT /api/v1/notifications/:notificationId/read -> notificationSchema\n * GET /api/v1/notifications/preferences -> notificationPreferencesSchema\n * PUT /api/v1/notifications/preferences updateNotificationPreferencesBodySchema -> notificationPreferencesSchema\n *\n * @module founder-schema/domains/notifications\n */\n\nimport { z } from 'zod';\nimport { objectIdSchema } from '../../core/common.js';\nimport { NOTIFICATION_EXCERPT_MAX } from '../../core/limits.js';\nimport { paginatedSchema } from '../../core/responses.js';\nimport { communityChipSchema } from '../communities/communities.js';\nimport { contentKindSchema, reactionTypeSchema } from '../feed/feed.js';\nimport { planTierSchema } from '../membership/membership.js';\nimport { productChipSchema, productInvitableRoleSchema } from '../products/products.js';\nimport { memberBylineSchema } from '../profile/founder-profile.js';\n\n/**\n * What happened. ⚠ Stored per row: append only.\n *\n * comment_on_post someone commented on your post or poll\n * reply_to_comment someone replied to your comment\n * product_invite a team invited you (always delivered)\n * product_invite_accepted someone accepted your team's invitation\n * product_review someone reviewed a product you are on the team of\n * review_reply a product's team answered your review\n * feedback_helpful a team marked your feedback as having helped\n * tester_confirmed a team confirmed you tested their product\n * post_reaction someone reacted to your post or poll (0.8.0)\n * comment_reaction someone reacted to your comment (0.8.0)\n * repost someone reposted your post or poll (0.8.0)\n * community_join_request someone asked to join a community you moderate (0.9.0)\n * community_request_approved your request to join was approved (always delivered)\n * community_invite a community invited you (always delivered)\n * community_announcement a community you are in pinned an announcement for everyone\n * community_suggestion_decided your community suggestion was approved or declined (always delivered)\n * plan_ending your plan ends in 7 days, then in 1 day (0.12.0, always delivered)\n * plan_ended your plan ended; `paused` products were paused (0.12.0, always delivered)\n * payment_refunded staff refunded one of your payments (0.12.0, always delivered)\n *\n * The three 0.8.0 kinds merge per target like the others. A reaction counts\n * once: changing it to another type is not a new event, and taking it back\n * does not take the event back.\n */\nexport const NOTIFICATION_KIND_VALUES = [\n 'comment_on_post',\n 'reply_to_comment',\n 'product_invite',\n 'product_invite_accepted',\n 'product_review',\n 'review_reply',\n 'feedback_helpful',\n 'tester_confirmed',\n 'post_reaction',\n 'comment_reaction',\n 'repost',\n 'community_join_request',\n 'community_request_approved',\n 'community_invite',\n 'community_announcement',\n 'community_suggestion_decided',\n 'plan_ending',\n 'plan_ended',\n 'payment_refunded',\n] as const;\nexport const notificationKindSchema = z.enum(NOTIFICATION_KIND_VALUES);\nexport type NotificationKind = z.infer<typeof notificationKindSchema>;\n\n/** The switches a member has, each covering one or more kinds. */\nexport const NOTIFICATION_PREFERENCE_VALUES = [\n 'comments',\n 'replies',\n 'productActivity',\n 'recognition',\n 'reactions',\n 'reposts',\n 'communities',\n] as const;\nexport const notificationPreferenceSchema = z.enum(NOTIFICATION_PREFERENCE_VALUES);\nexport type NotificationPreference = z.infer<typeof notificationPreferenceSchema>;\n\n/**\n * Which switch silences which kind. Null: always delivered. Exhaustive by\n * type, so a kind added without a decision here does not compile.\n */\nexport const NOTIFICATION_PREFERENCE_OF = {\n comment_on_post: 'comments',\n reply_to_comment: 'replies',\n product_invite: null,\n product_invite_accepted: 'productActivity',\n product_review: 'productActivity',\n review_reply: 'productActivity',\n feedback_helpful: 'recognition',\n tester_confirmed: 'recognition',\n post_reaction: 'reactions',\n comment_reaction: 'reactions',\n repost: 'reposts',\n community_join_request: 'communities',\n community_request_approved: null,\n community_invite: null,\n community_announcement: 'communities',\n community_suggestion_decided: null,\n plan_ending: null,\n plan_ended: null,\n payment_refunded: null,\n} as const satisfies Record<NotificationKind, NotificationPreference | null>;\n\n/**\n * One row of the bell. Which of the optional targets are present depends on\n * `kind`:\n *\n * comment_on_post content, commentId\n * reply_to_comment content, commentId (the reply)\n * product_invite product, role\n * product_invite_accepted product\n * product_review product, reviewId\n * review_reply product, reviewId\n * feedback_helpful product, content, commentId\n * tester_confirmed product\n * post_reaction content, reaction\n * comment_reaction content, commentId, reaction, excerpt (your comment)\n * repost content (yours, the one reposted), repostId (the latest repost, to read their words)\n * community_join_request community\n * community_request_approved community\n * community_invite community\n * community_announcement community, content (the pinned post or poll)\n * community_suggestion_decided suggestion, community (once approved)\n * plan_ending plan (the tier, and when it ends)\n * plan_ended plan (the tier that ended), paused (how many products)\n * payment_refunded payment\n *\n * Any row about something inside a community also carries `community`\n * (0.9.0), whatever its kind. When the recipient leaves the community, is\n * removed or is banned, its rows about that community lose their content,\n * comment, excerpt and repost: what they could read there, they no longer can.\n *\n * A target that no longer exists (a deleted post, product or review) is\n * absent rather than pointing at a 404; the row still says what happened.\n */\nexport const notificationSchema = z.object({\n id: objectIdSchema,\n kind: notificationKindSchema,\n /** The latest person to do it, live; null for a deleted account. */\n actor: memberBylineSchema.nullable(),\n /**\n * How many EVENTS this row merges, at least 1: three comments by one person\n * are 3. Word it as events (\"3 new comments, latest from Asha\"), never as\n * people (\"Asha and 2 others\"), which this count does not know.\n */\n count: z.number().int().positive(),\n content: z.object({ kind: contentKindSchema, id: objectIdSchema }).optional(),\n commentId: objectIdSchema.optional(),\n product: productChipSchema.optional(),\n reviewId: objectIdSchema.optional(),\n /**\n * For `repost` (0.8.0): the latest repost itself, so the row opens the\n * reposter's words above your post. Gone once that repost is.\n */\n repostId: objectIdSchema.optional(),\n /**\n * The community the row is about, or that its content is in (0.9.0). Absent\n * once that community is deleted, or the recipient may no longer see it.\n */\n community: communityChipSchema.optional(),\n /** For `community_suggestion_decided` (0.9.0): which suggestion, and how it went. */\n suggestion: z\n .object({\n id: objectIdSchema,\n name: z.string(),\n status: z.enum(['approved', 'declined']),\n /** Why it was declined. */\n reason: z.string().optional(),\n })\n .optional(),\n /** For `plan_ending` and `plan_ended` (0.12.0): which tier, and when it ends or ended. */\n plan: z.object({ tier: planTierSchema, endsAt: z.iso.datetime() }).optional(),\n /** For `plan_ended` (0.12.0): products paused because the plan's live places went. */\n paused: z.number().int().nonnegative().optional(),\n /** For `payment_refunded` (0.12.0): which purchase, by its title. */\n payment: z.object({ id: objectIdSchema, title: z.string() }).optional(),\n /** For `product_invite`: the role offered. */\n role: productInvitableRoleSchema.optional(),\n /** For the reaction kinds: the latest reaction, as it is now (0.8.0). */\n reaction: reactionTypeSchema.optional(),\n /**\n * The first words of what was written, for the comment and review kinds\n * (the latest one, on a merged row), cut to `NOTIFICATION_EXCERPT_MAX`. Gone\n * when that comment or review is deleted or hidden, and when its writer's\n * account is.\n */\n excerpt: z.string().max(NOTIFICATION_EXCERPT_MAX).optional(),\n /** Null until opened (or until \"mark all read\"). A merged repeat makes it unread again. */\n readAt: z.iso.datetime().nullable(),\n /** When it last happened: the list's order. */\n lastAt: z.iso.datetime(),\n createdAt: z.iso.datetime(),\n});\nexport type Notification = z.infer<typeof notificationSchema>;\n\nexport const notificationPageSchema = paginatedSchema(notificationSchema);\nexport type NotificationPage = z.infer<typeof notificationPageSchema>;\n\n/** The badge. */\nexport const notificationUnreadCountSchema = z.object({\n unread: z.number().int().nonnegative(),\n});\nexport type NotificationUnreadCount = z.infer<typeof notificationUnreadCountSchema>;\n\nexport const notificationParamsSchema = z.object({ notificationId: objectIdSchema });\nexport type NotificationParams = z.infer<typeof notificationParamsSchema>;\n\n/** Every switch, as stored (on by default). */\nexport const notificationPreferencesSchema = z.object({\n comments: z.boolean(),\n replies: z.boolean(),\n productActivity: z.boolean(),\n recognition: z.boolean(),\n /** Reactions to your posts, polls and comments (0.8.0). */\n reactions: z.boolean(),\n /** Reposts of your posts and polls (0.8.0). */\n reposts: z.boolean(),\n /** Announcements in your communities, and join requests to communities you moderate (0.9.0). */\n communities: z.boolean(),\n});\nexport type NotificationPreferences = z.infer<typeof notificationPreferencesSchema>;\n\n/** Only the switches that changed; no defaults (a patch states what was sent). */\nexport const updateNotificationPreferencesBodySchema = z.object({\n comments: z.boolean().optional(),\n replies: z.boolean().optional(),\n productActivity: z.boolean().optional(),\n recognition: z.boolean().optional(),\n reactions: z.boolean().optional(),\n reposts: z.boolean().optional(),\n communities: z.boolean().optional(),\n});\nexport type UpdateNotificationPreferencesBody = z.infer<\n typeof updateNotificationPreferencesBodySchema\n>;\n"]}
1
+ {"version":3,"file":"notifications.js","sourceRoot":"","sources":["../../../src/domains/notifications/notifications.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema — Notifications: the bell\n *\n * The design the user approved on 2026-10-05 (\"Founder Circle Schema Review\"):\n *\n * - ONE ROW PER RECIPIENT, written when the event happens.\n * - REPEATS MERGE: a second comment on your post updates the same row\n * (\"2 new comments, latest from Asha\"), it never adds one. `count` is how\n * many events it holds and `actor` the latest. A member repeating the same\n * act (reacting, taking it back, reacting again) does not ring it again.\n * - THE ACTOR IS LOOKED UP WHEN READ, never copied in: a renamed member shows\n * their new name, and a deleted one shows as null (\"A former member\").\n * - PREFERENCES ARE CHECKED before writing (Jansathi stores them and never\n * reads them). A team invitation is always delivered: it needs an answer.\n * - KEPT `NOTIFICATION_RETENTION_DAYS`, then deleted.\n * - Nobody is notified of their own action.\n *\n * In-app only. Push stays chat's (async-service) until a channel is designed:\n * a client polls the unread count (on focus, and every minute while visible).\n *\n * Routes (all signed in):\n * GET /api/v1/notifications paginationQuerySchema -> notificationPageSchema\n * GET /api/v1/notifications/unread-count -> notificationUnreadCountSchema\n * POST /api/v1/notifications/read mark every one read -> notificationUnreadCountSchema\n * PUT /api/v1/notifications/:notificationId/read -> notificationSchema\n * GET /api/v1/notifications/preferences -> notificationPreferencesSchema\n * PUT /api/v1/notifications/preferences updateNotificationPreferencesBodySchema -> notificationPreferencesSchema\n *\n * @module founder-schema/domains/notifications\n */\n\nimport { z } from 'zod';\nimport { objectIdSchema } from '../../core/common.js';\nimport { NOTIFICATION_EXCERPT_MAX } from '../../core/limits.js';\nimport { paginatedSchema } from '../../core/responses.js';\nimport {\n communityChipSchema,\n communitySuggestionDecisionSchema,\n} from '../communities/communities.js';\nimport { contentKindSchema, reactionTypeSchema } from '../feed/feed.js';\nimport { planTierSchema } from '../membership/membership.js';\nimport { productChipSchema, productInvitableRoleSchema } from '../products/products.js';\nimport { memberBylineSchema } from '../profile/founder-profile.js';\n\n/**\n * What happened. ⚠ Stored per row: append only.\n *\n * comment_on_post someone commented on your post or poll\n * reply_to_comment someone replied to your comment\n * product_invite a team invited you (always delivered)\n * product_invite_accepted someone accepted your team's invitation\n * product_review someone reviewed a product you are on the team of\n * review_reply a product's team answered your review\n * feedback_helpful a team marked your feedback as having helped\n * tester_confirmed a team confirmed you tested their product\n * post_reaction someone reacted to your post or poll (0.8.0)\n * comment_reaction someone reacted to your comment (0.8.0)\n * repost someone reposted your post or poll (0.8.0)\n * community_join_request someone asked to join a community you moderate (0.9.0)\n * community_request_approved your request to join was approved (always delivered)\n * community_invite a community invited you (always delivered)\n * community_announcement a community you are in pinned an announcement for everyone\n * community_suggestion_decided your community suggestion was approved or declined (always delivered)\n * plan_ending your plan ends in 7 days, then in 1 day (0.12.0, always delivered)\n * plan_ended your plan ended; `paused` products were paused (0.12.0, always delivered)\n * payment_refunded staff refunded one of your payments (0.12.0, always delivered)\n *\n * The three 0.8.0 kinds merge per target like the others. A reaction counts\n * once: changing it to another type is not a new event, and taking it back\n * does not take the event back.\n */\nexport const NOTIFICATION_KIND_VALUES = [\n 'comment_on_post',\n 'reply_to_comment',\n 'product_invite',\n 'product_invite_accepted',\n 'product_review',\n 'review_reply',\n 'feedback_helpful',\n 'tester_confirmed',\n 'post_reaction',\n 'comment_reaction',\n 'repost',\n 'community_join_request',\n 'community_request_approved',\n 'community_invite',\n 'community_announcement',\n 'community_suggestion_decided',\n 'plan_ending',\n 'plan_ended',\n 'payment_refunded',\n] as const;\nexport const notificationKindSchema = z.enum(NOTIFICATION_KIND_VALUES);\nexport type NotificationKind = z.infer<typeof notificationKindSchema>;\n\n/** The switches a member has, each covering one or more kinds. */\nexport const NOTIFICATION_PREFERENCE_VALUES = [\n 'comments',\n 'replies',\n 'productActivity',\n 'recognition',\n 'reactions',\n 'reposts',\n 'communities',\n] as const;\nexport type NotificationPreference = (typeof NOTIFICATION_PREFERENCE_VALUES)[number];\n\n/**\n * Which switch silences which kind. Null: always delivered. Exhaustive by\n * type, so a kind added without a decision here does not compile.\n */\nexport const NOTIFICATION_PREFERENCE_OF = {\n comment_on_post: 'comments',\n reply_to_comment: 'replies',\n product_invite: null,\n product_invite_accepted: 'productActivity',\n product_review: 'productActivity',\n review_reply: 'productActivity',\n feedback_helpful: 'recognition',\n tester_confirmed: 'recognition',\n post_reaction: 'reactions',\n comment_reaction: 'reactions',\n repost: 'reposts',\n community_join_request: 'communities',\n community_request_approved: null,\n community_invite: null,\n community_announcement: 'communities',\n community_suggestion_decided: null,\n plan_ending: null,\n plan_ended: null,\n payment_refunded: null,\n} as const satisfies Record<NotificationKind, NotificationPreference | null>;\n\n/**\n * One row of the bell. Which of the optional targets are present depends on\n * `kind`:\n *\n * comment_on_post content, commentId\n * reply_to_comment content, commentId (the reply)\n * product_invite product, role\n * product_invite_accepted product\n * product_review product, reviewId\n * review_reply product, reviewId\n * feedback_helpful product, content, commentId\n * tester_confirmed product\n * post_reaction content, reaction\n * comment_reaction content, commentId, reaction, excerpt (your comment)\n * repost content (yours, the one reposted), repostId (the latest repost, to read their words)\n * community_join_request community\n * community_request_approved community\n * community_invite community\n * community_announcement community, content (the pinned post or poll)\n * community_suggestion_decided suggestion, community (once approved)\n * plan_ending plan (the tier, and when it ends)\n * plan_ended plan (the tier that ended), paused (how many products)\n * payment_refunded payment\n *\n * Any row about something inside a community also carries `community`\n * (0.9.0), whatever its kind. When the recipient leaves the community, is\n * removed or is banned, its rows about that community lose their content,\n * comment, excerpt and repost: what they could read there, they no longer can.\n *\n * A target that no longer exists (a deleted post, product or review) is\n * absent rather than pointing at a 404; the row still says what happened.\n */\nexport const notificationSchema = z.object({\n id: objectIdSchema,\n kind: notificationKindSchema,\n /** The latest person to do it, live; null for a deleted account. */\n actor: memberBylineSchema.nullable(),\n /**\n * How many EVENTS this row merges, at least 1: three comments by one person\n * are 3. Word it as events (\"3 new comments, latest from Asha\"), never as\n * people (\"Asha and 2 others\"), which this count does not know.\n */\n count: z.number().int().positive(),\n content: z.object({ kind: contentKindSchema, id: objectIdSchema }).optional(),\n commentId: objectIdSchema.optional(),\n product: productChipSchema.optional(),\n reviewId: objectIdSchema.optional(),\n /**\n * For `repost` (0.8.0): the latest repost itself, so the row opens the\n * reposter's words above your post. Gone once that repost is.\n */\n repostId: objectIdSchema.optional(),\n /**\n * The community the row is about, or that its content is in (0.9.0). Absent\n * once that community is deleted, or the recipient may no longer see it.\n */\n community: communityChipSchema.optional(),\n /** For `community_suggestion_decided` (0.9.0): which suggestion, and how it went. */\n suggestion: z\n .object({\n id: objectIdSchema,\n name: z.string(),\n status: communitySuggestionDecisionSchema,\n /** Why it was declined. */\n reason: z.string().optional(),\n })\n .optional(),\n /** For `plan_ending` and `plan_ended` (0.12.0): which tier, and when it ends or ended. */\n plan: z.object({ tier: planTierSchema, endsAt: z.iso.datetime() }).optional(),\n /** For `plan_ended` (0.12.0): products paused because the plan's live places went. */\n paused: z.number().int().nonnegative().optional(),\n /** For `payment_refunded` (0.12.0): which purchase, by its title. */\n payment: z.object({ id: objectIdSchema, title: z.string() }).optional(),\n /** For `product_invite`: the role offered. */\n role: productInvitableRoleSchema.optional(),\n /** For the reaction kinds: the latest reaction, as it is now (0.8.0). */\n reaction: reactionTypeSchema.optional(),\n /**\n * The first words of what was written, for the comment and review kinds\n * (the latest one, on a merged row), cut to `NOTIFICATION_EXCERPT_MAX`. Gone\n * when that comment or review is deleted or hidden, and when its writer's\n * account is.\n */\n excerpt: z.string().max(NOTIFICATION_EXCERPT_MAX).optional(),\n /** Null until opened (or until \"mark all read\"). A merged repeat makes it unread again. */\n readAt: z.iso.datetime().nullable(),\n /** When it last happened: the list's order. */\n lastAt: z.iso.datetime(),\n createdAt: z.iso.datetime(),\n});\nexport type Notification = z.infer<typeof notificationSchema>;\n\nexport const notificationPageSchema = paginatedSchema(notificationSchema);\nexport type NotificationPage = z.infer<typeof notificationPageSchema>;\n\n/** The badge. */\nexport const notificationUnreadCountSchema = z.object({\n unread: z.number().int().nonnegative(),\n});\nexport type NotificationUnreadCount = z.infer<typeof notificationUnreadCountSchema>;\n\nexport const notificationParamsSchema = z.object({ notificationId: objectIdSchema });\nexport type NotificationParams = z.infer<typeof notificationParamsSchema>;\n\n/** Every switch, as stored (on by default). */\nexport const notificationPreferencesSchema = z.object({\n comments: z.boolean(),\n replies: z.boolean(),\n productActivity: z.boolean(),\n recognition: z.boolean(),\n /** Reactions to your posts, polls and comments (0.8.0). */\n reactions: z.boolean(),\n /** Reposts of your posts and polls (0.8.0). */\n reposts: z.boolean(),\n /** Announcements in your communities, and join requests to communities you moderate (0.9.0). */\n communities: z.boolean(),\n});\nexport type NotificationPreferences = z.infer<typeof notificationPreferencesSchema>;\n\n/** Only the switches that changed; no defaults (a patch states what was sent). */\nexport const updateNotificationPreferencesBodySchema = z.object({\n comments: z.boolean().optional(),\n replies: z.boolean().optional(),\n productActivity: z.boolean().optional(),\n recognition: z.boolean().optional(),\n reactions: z.boolean().optional(),\n reposts: z.boolean().optional(),\n communities: z.boolean().optional(),\n});\nexport type UpdateNotificationPreferencesBody = z.infer<\n typeof updateNotificationPreferencesBodySchema\n>;\n"]}
@@ -19,10 +19,42 @@
19
19
  *
20
20
  * @module founder-schema/domains/onboarding/onboarding-answers
21
21
  */
22
- import { type CompleteOnboardingBodyInput, type OnboardingDraft, type OnboardingForm, type OnboardingStepKey } from './onboarding.js';
22
+ import { type Industry, type StartupStage } from '../../core/common.js';
23
+ import { type Place } from '../location/areas.js';
24
+ import { type CompleteOnboardingBodyInput, type OnboardingForm, type OnboardingStepKey } from './onboarding.js';
23
25
  type DeepPartial<T> = T extends readonly (infer Item)[] ? DeepPartial<Item>[] : T extends object ? {
24
26
  [K in keyof T]?: DeepPartial<T[K]>;
25
27
  } : T;
28
+ /**
29
+ * A founding year as typed, kept to digits, as many as a year has (0.18.0,
30
+ * moved from the web's Me editor beside `teamSizeDigits`).
31
+ */
32
+ export declare const foundedYearDigits: (raw: string) => string;
33
+ /**
34
+ * A venture body from what was typed (0.18.0): ONE builder for onboarding's
35
+ * business step and Me's business editor, which each built it by hand and
36
+ * cleaned the team size two ways. Text trimmed, the website made a link
37
+ * (`normalizeLinkUrl`), digits kept (`teamSizeDigits`, `foundedYearDigits`);
38
+ * an empty optional answer is ABSENT. A missing name, industry or stage is
39
+ * left missing, so `ventureSchema` refuses it under its field.
40
+ */
41
+ export declare function ventureBodyFrom(v: {
42
+ name: string;
43
+ website: string;
44
+ industry: Industry | null;
45
+ stage: StartupStage | null;
46
+ teamSize: string;
47
+ tagline?: string;
48
+ foundedYear?: string;
49
+ }): {
50
+ name: string;
51
+ industry: Industry | undefined;
52
+ stage: StartupStage | undefined;
53
+ tagline?: string;
54
+ website?: string;
55
+ teamSize?: number;
56
+ foundedYear?: number;
57
+ };
26
58
  /**
27
59
  * What `completeOnboardingBodyFrom` builds: the finish body's INPUT, any part
28
60
  * of it possibly still missing. Not a valid body until
@@ -43,15 +75,11 @@ export declare function emptyOnboardingForm(): OnboardingForm;
43
75
  */
44
76
  export declare function ventureStarted(form: Pick<OnboardingForm, 'venture'>): boolean;
45
77
  /**
46
- * True when a place has been started: a city or a state given. A place opened
47
- * and left empty is NO place (the member chose not to say), never a half
48
- * answer to refuse; a place with only one of the two IS half an answer, and
49
- * the schema asks for the other.
78
+ * True when a place has been given (0.19.0). The drawer hands back only a
79
+ * whole place (state, district, and a locality or the whole district), so
80
+ * there is no half answer: a place is there or it is not.
50
81
  */
51
- export declare function locationStarted(place: {
52
- city: string;
53
- state?: string | null;
54
- } | null): boolean;
82
+ export declare function locationStarted(place: Place | null): boolean;
55
83
  /**
56
84
  * The draft one step saves: exactly the fields that step owns
57
85
  * (`FOUNDER_ONBOARDING_STEPS[].fields`, the same picks its validation uses),
@@ -60,7 +88,7 @@ export declare function locationStarted(place: {
60
88
  * sees what they typed. The photo step's field is its upload id, which the
61
89
  * server holds rather than drafts.
62
90
  */
63
- export declare function onboardingDraftForStep(form: OnboardingForm, key: OnboardingStepKey): OnboardingDraft;
91
+ export declare function onboardingDraftForStep(form: OnboardingForm, key: OnboardingStepKey): Partial<OnboardingForm>;
64
92
  /**
65
93
  * What Finish sends, and what every step is CHECKED as (each step's slice
66
94
  * reads its own fields from it).
@@ -71,10 +99,10 @@ export declare function onboardingDraftForStep(form: OnboardingForm, key: Onboar
71
99
  *
72
100
  * Cleaned the way the schema reads it: the name tidied (`tidyFullName`), the
73
101
  * phone to ten digits (`normalizePhone`), links given their scheme
74
- * (`normalizeLinkUrl`), the place in the list's own spelling in India
75
- * (`canonicalIndiaLocation`), the team size a number. A half-answered part is
76
- * SENT, not hidden: a location without a city, a business without its stage,
77
- * reach the schema and are refused there, under the field.
102
+ * (`normalizeLinkUrl`), the place as its area ids (`pickOfPlace`, 0.19.0:
103
+ * the server names it), the team size a number. A half-answered part is
104
+ * SENT, not hidden: a business without its stage reaches the schema and is
105
+ * refused there, under the field.
78
106
  */
79
107
  export declare function completeOnboardingBodyFrom(form: OnboardingForm): CompleteOnboardingBodyCandidate;
80
108
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"onboarding-answers.d.ts","sourceRoot":"","sources":["../../../src/domains/onboarding/onboarding-answers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,OAAO,EACL,KAAK,2BAA2B,EAIhC,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EAEvB,MAAM,iBAAiB,CAAC;AAEzB,KAAK,WAAW,CAAC,CAAC,IAAI,CAAC,SAAS,SAAS,CAAC,MAAM,IAAI,CAAC,EAAE,GACnD,WAAW,CAAC,IAAI,CAAC,EAAE,GACnB,CAAC,SAAS,MAAM,GACd;KAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,GACtC,CAAC,CAAC;AAER;;;;GAIG;AACH,MAAM,MAAM,+BAA+B,GAAG,WAAW,CAAC,2BAA2B,CAAC,CAAC;AAEvF,qEAAqE;AACrE,wBAAgB,mBAAmB,IAAI,cAAc,CAEpD;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,GAAG,OAAO,CAG7E;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,GAAG,OAAO,CAE9F;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,cAAc,EACpB,GAAG,EAAE,iBAAiB,GACrB,eAAe,CAKjB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,cAAc,GAAG,+BAA+B,CAwDhG"}
1
+ {"version":3,"file":"onboarding-answers.d.ts","sourceRoot":"","sources":["../../../src/domains/onboarding/onboarding-answers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EACL,KAAK,QAAQ,EAGb,KAAK,YAAY,EAElB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,KAAK,KAAK,EAAe,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EACL,KAAK,2BAA2B,EAIhC,KAAK,cAAc,EACnB,KAAK,iBAAiB,EAGvB,MAAM,iBAAiB,CAAC;AAEzB,KAAK,WAAW,CAAC,CAAC,IAAI,CAAC,SAAS,SAAS,CAAC,MAAM,IAAI,CAAC,EAAE,GACnD,WAAW,CAAC,IAAI,CAAC,EAAE,GACnB,CAAC,SAAS,MAAM,GACd;KAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,GACtC,CAAC,CAAC;AAER;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAAI,KAAK,MAAM,KAAG,MACkB,CAAC;AAEnE;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG;IACF,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC/B,KAAK,EAAE,YAAY,GAAG,SAAS,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAcA;AAED;;;;GAIG;AACH,MAAM,MAAM,+BAA+B,GAAG,WAAW,CAAC,2BAA2B,CAAC,CAAC;AAEvF,qEAAqE;AACrE,wBAAgB,mBAAmB,IAAI,cAAc,CAEpD;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,GAAG,OAAO,CAG7E;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,GAAG,OAAO,CAE5D;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,cAAc,EACpB,GAAG,EAAE,iBAAiB,GACrB,OAAO,CAAC,cAAc,CAAC,CAKzB;AAWD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,cAAc,GAAG,+BAA+B,CAsChG"}
@@ -19,9 +19,38 @@
19
19
  *
20
20
  * @module founder-schema/domains/onboarding/onboarding-answers
21
21
  */
22
- import { normalizeLinkUrl, normalizePhone, tidyFullName } from '../../core/common.js';
23
- import { canonicalIndiaLocation } from '../location/india.js';
24
- import { FOUNDER_ONBOARDING_STEPS, GUIDELINES_VERSION, ONBOARDING_STEP_KEYS, readOnboardingDraft, } from './onboarding.js';
22
+ import { normalizeLinkUrl, normalizePhone, tidyFullName, } from '../../core/common.js';
23
+ import { FOUNDED_YEAR_MIN } from '../../core/limits.js';
24
+ import { pickOfPlace } from '../location/areas.js';
25
+ import { FOUNDER_ONBOARDING_STEPS, GUIDELINES_VERSION, ONBOARDING_STEP_KEYS, readOnboardingDraft, teamSizeDigits, } from './onboarding.js';
26
+ /**
27
+ * A founding year as typed, kept to digits, as many as a year has (0.18.0,
28
+ * moved from the web's Me editor beside `teamSizeDigits`).
29
+ */
30
+ export const foundedYearDigits = (raw) => raw.replace(/\D/g, '').slice(0, String(FOUNDED_YEAR_MIN).length);
31
+ /**
32
+ * A venture body from what was typed (0.18.0): ONE builder for onboarding's
33
+ * business step and Me's business editor, which each built it by hand and
34
+ * cleaned the team size two ways. Text trimmed, the website made a link
35
+ * (`normalizeLinkUrl`), digits kept (`teamSizeDigits`, `foundedYearDigits`);
36
+ * an empty optional answer is ABSENT. A missing name, industry or stage is
37
+ * left missing, so `ventureSchema` refuses it under its field.
38
+ */
39
+ export function ventureBodyFrom(v) {
40
+ const tagline = v.tagline?.trim();
41
+ const website = normalizeLinkUrl(v.website);
42
+ const teamSize = teamSizeDigits(v.teamSize);
43
+ const foundedYear = foundedYearDigits(v.foundedYear ?? '');
44
+ return {
45
+ name: v.name.trim(),
46
+ industry: v.industry ?? undefined,
47
+ stage: v.stage ?? undefined,
48
+ ...(tagline ? { tagline } : {}),
49
+ ...(website ? { website } : {}),
50
+ ...(teamSize ? { teamSize: Number(teamSize) } : {}),
51
+ ...(foundedYear ? { foundedYear: Number(foundedYear) } : {}),
52
+ };
53
+ }
25
54
  /** Answers with nothing filled in: what a new member starts from. */
26
55
  export function emptyOnboardingForm() {
27
56
  return readOnboardingDraft({});
@@ -41,13 +70,12 @@ export function ventureStarted(form) {
41
70
  return Boolean(v.name.trim() || v.website.trim() || v.industry || v.stage || v.teamSize.trim());
42
71
  }
43
72
  /**
44
- * True when a place has been started: a city or a state given. A place opened
45
- * and left empty is NO place (the member chose not to say), never a half
46
- * answer to refuse; a place with only one of the two IS half an answer, and
47
- * the schema asks for the other.
73
+ * True when a place has been given (0.19.0). The drawer hands back only a
74
+ * whole place (state, district, and a locality or the whole district), so
75
+ * there is no half answer: a place is there or it is not.
48
76
  */
49
77
  export function locationStarted(place) {
50
- return Boolean(place && (place.city.trim() || (place.state ?? '').trim()));
78
+ return place !== null;
51
79
  }
52
80
  /**
53
81
  * The draft one step saves: exactly the fields that step owns
@@ -61,9 +89,13 @@ export function onboardingDraftForStep(form, key) {
61
89
  const step = FOUNDER_ONBOARDING_STEPS[ONBOARDING_STEP_KEYS.indexOf(key)];
62
90
  const draft = {};
63
91
  for (const field of step?.fields ?? [])
64
- draft[field] = form[field];
92
+ copyField(draft, form, field);
65
93
  return draft;
66
94
  }
95
+ /** One field across, typed by its key (0.18.0: the draft is the FORM's shape, never `unknown`). */
96
+ function copyField(to, from, key) {
97
+ to[key] = from[key];
98
+ }
67
99
  /**
68
100
  * What Finish sends, and what every step is CHECKED as (each step's slice
69
101
  * reads its own fields from it).
@@ -74,10 +106,10 @@ export function onboardingDraftForStep(form, key) {
74
106
  *
75
107
  * Cleaned the way the schema reads it: the name tidied (`tidyFullName`), the
76
108
  * phone to ten digits (`normalizePhone`), links given their scheme
77
- * (`normalizeLinkUrl`), the place in the list's own spelling in India
78
- * (`canonicalIndiaLocation`), the team size a number. A half-answered part is
79
- * SENT, not hidden: a location without a city, a business without its stage,
80
- * reach the schema and are refused there, under the field.
109
+ * (`normalizeLinkUrl`), the place as its area ids (`pickOfPlace`, 0.19.0:
110
+ * the server names it), the team size a number. A half-answered part is
111
+ * SENT, not hidden: a business without its stage reaches the schema and is
112
+ * refused there, under the field.
81
113
  */
82
114
  export function completeOnboardingBodyFrom(form) {
83
115
  const headline = form.headline.trim();
@@ -110,26 +142,10 @@ export function completeOnboardingBodyFrom(form) {
110
142
  body.skills = form.skills;
111
143
  if (Object.keys(links).length)
112
144
  body.links = links;
113
- if (form.location && locationStarted(form.location)) {
114
- const place = canonicalIndiaLocation(form.location);
115
- body.location = {
116
- city: place.city,
117
- ...(place.state ? { state: place.state } : {}),
118
- country: place.country,
119
- };
120
- }
121
- if (ventureStarted(form)) {
122
- const v = form.venture;
123
- const website = normalizeLinkUrl(v.website);
124
- const teamSize = v.teamSize.trim();
125
- body.venture = {
126
- name: v.name.trim(),
127
- industry: v.industry ?? undefined,
128
- stage: v.stage ?? undefined,
129
- ...(website ? { website } : {}),
130
- ...(teamSize ? { teamSize: Number(teamSize) } : {}),
131
- };
132
- }
145
+ if (form.location)
146
+ body.location = pickOfPlace(form.location);
147
+ if (ventureStarted(form))
148
+ body.venture = ventureBodyFrom(form.venture);
133
149
  // Sent whenever given: with a business they describe it, without one the
134
150
  // schema refuses each on its own field rather than anything vanishing.
135
151
  if (form.businessType)
@@ -1 +1 @@
1
- {"version":3,"file":"onboarding-answers.js","sourceRoot":"","sources":["../../../src/domains/onboarding/onboarding-answers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema — From a member's onboarding answers to what is sent (0.17.0)\n *\n * The answers are an `OnboardingForm` (the in-progress shape: raw text, `null`\n * for a choice not made). Two things are built from them, and until 0.17.0\n * each client built them by hand, so a second client (the app) would have had\n * to copy every rule below and keep it in step:\n *\n * the draft a step SAVES exactly the fields that step owns, as typed\n * (`onboardingDraftForStep`)\n * the body FINISH sends completed answers only, cleaned, every\n * unanswered optional field ABSENT\n * (`completeOnboardingBodyFrom`)\n *\n * Neither validates: `ONBOARDING_STEP_SCHEMAS` and `completeOnboardingBodySchema`\n * do, on what these build. A half-filled form builds a half-filled body on\n * purpose, so the schema refuses it with the reason under each field instead\n * of a builder hiding the gap.\n *\n * @module founder-schema/domains/onboarding/onboarding-answers\n */\n\nimport { normalizeLinkUrl, normalizePhone, tidyFullName } from '../../core/common.js';\nimport { canonicalIndiaLocation } from '../location/india.js';\nimport {\n type CompleteOnboardingBodyInput,\n FOUNDER_ONBOARDING_STEPS,\n GUIDELINES_VERSION,\n ONBOARDING_STEP_KEYS,\n type OnboardingDraft,\n type OnboardingForm,\n type OnboardingStepKey,\n readOnboardingDraft,\n} from './onboarding.js';\n\ntype DeepPartial<T> = T extends readonly (infer Item)[]\n ? DeepPartial<Item>[]\n : T extends object\n ? { [K in keyof T]?: DeepPartial<T[K]> }\n : T;\n\n/**\n * What `completeOnboardingBodyFrom` builds: the finish body's INPUT, any part\n * of it possibly still missing. Not a valid body until\n * `completeOnboardingBodySchema` says so.\n */\nexport type CompleteOnboardingBodyCandidate = DeepPartial<CompleteOnboardingBodyInput>;\n\n/** Answers with nothing filled in: what a new member starts from. */\nexport function emptyOnboardingForm(): OnboardingForm {\n return readOnboardingDraft({});\n}\n\n/**\n * True when the member has started describing a business: any of the\n * venture's own answers. Then it is sent, and the schema asks for its name,\n * industry and stage.\n *\n * The business type and the registration do not start it: they DESCRIBE a\n * business. Picked without one, each is sent anyway and refused on its own\n * field (`ventureRequired`: add the business, or clear this), never dropped\n * without a word (hary's ruling).\n */\nexport function ventureStarted(form: Pick<OnboardingForm, 'venture'>): boolean {\n const v = form.venture;\n return Boolean(v.name.trim() || v.website.trim() || v.industry || v.stage || v.teamSize.trim());\n}\n\n/**\n * True when a place has been started: a city or a state given. A place opened\n * and left empty is NO place (the member chose not to say), never a half\n * answer to refuse; a place with only one of the two IS half an answer, and\n * the schema asks for the other.\n */\nexport function locationStarted(place: { city: string; state?: string | null } | null): boolean {\n return Boolean(place && (place.city.trim() || (place.state ?? '').trim()));\n}\n\n/**\n * The draft one step saves: exactly the fields that step owns\n * (`FOUNDER_ONBOARDING_STEPS[].fields`, the same picks its validation uses),\n * as typed. Only the step's own fields, so a save can never overwrite another\n * step's answers (the server merges per field); raw, so a member who comes back\n * sees what they typed. The photo step's field is its upload id, which the\n * server holds rather than drafts.\n */\nexport function onboardingDraftForStep(\n form: OnboardingForm,\n key: OnboardingStepKey,\n): OnboardingDraft {\n const step = FOUNDER_ONBOARDING_STEPS[ONBOARDING_STEP_KEYS.indexOf(key)];\n const draft: OnboardingDraft = {};\n for (const field of step?.fields ?? []) draft[field] = form[field];\n return draft;\n}\n\n/**\n * What Finish sends, and what every step is CHECKED as (each step's slice\n * reads its own fields from it).\n *\n * ⚠ ABSENT, NEVER A DEFAULT. The sibling gym product sent `bloodGroup ?? 'O+'`\n * and recorded everyone who skipped the question as O-positive. An optional\n * answer not given is left out; nothing is filled with a plausible value.\n *\n * Cleaned the way the schema reads it: the name tidied (`tidyFullName`), the\n * phone to ten digits (`normalizePhone`), links given their scheme\n * (`normalizeLinkUrl`), the place in the list's own spelling in India\n * (`canonicalIndiaLocation`), the team size a number. A half-answered part is\n * SENT, not hidden: a location without a city, a business without its stage,\n * reach the schema and are refused there, under the field.\n */\nexport function completeOnboardingBodyFrom(form: OnboardingForm): CompleteOnboardingBodyCandidate {\n const headline = form.headline.trim();\n const about = form.about.trim();\n\n const links: Partial<Record<keyof OnboardingForm['links'], string>> = {};\n for (const k of Object.keys(form.links) as (keyof OnboardingForm['links'])[]) {\n const url = normalizeLinkUrl(form.links[k]);\n if (url) links[k] = url;\n }\n\n const body: CompleteOnboardingBodyCandidate = {\n displayName: tidyFullName(form.displayName),\n gender: form.gender ?? undefined,\n dateOfBirth: form.dateOfBirth.trim(),\n phone: normalizePhone(form.phone),\n goals: form.goals,\n interests: form.interests,\n // Only agreement to the CURRENT text is sent; anything else is absent, so\n // the schema refuses it as not agreed. Consent to an older text is not\n // consent to this one.\n guidelinesVersion:\n form.guidelinesVersion === GUIDELINES_VERSION ? GUIDELINES_VERSION : undefined,\n };\n if (headline) body.headline = headline;\n if (about) body.about = about;\n if (form.avatarMediaId) body.avatarMediaId = form.avatarMediaId;\n if (form.skills.length) body.skills = form.skills;\n if (Object.keys(links).length) body.links = links;\n\n if (form.location && locationStarted(form.location)) {\n const place = canonicalIndiaLocation(form.location);\n body.location = {\n city: place.city,\n ...(place.state ? { state: place.state } : {}),\n country: place.country,\n };\n }\n\n if (ventureStarted(form)) {\n const v = form.venture;\n const website = normalizeLinkUrl(v.website);\n const teamSize = v.teamSize.trim();\n body.venture = {\n name: v.name.trim(),\n industry: v.industry ?? undefined,\n stage: v.stage ?? undefined,\n ...(website ? { website } : {}),\n ...(teamSize ? { teamSize: Number(teamSize) } : {}),\n };\n }\n // Sent whenever given: with a business they describe it, without one the\n // schema refuses each on its own field rather than anything vanishing.\n if (form.businessType) body.businessType = form.businessType;\n if (form.legalStructure) body.legalStructure = form.legalStructure;\n\n return body;\n}\n"]}
1
+ {"version":3,"file":"onboarding-answers.js","sourceRoot":"","sources":["../../../src/domains/onboarding/onboarding-answers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema — From a member's onboarding answers to what is sent (0.17.0)\n *\n * The answers are an `OnboardingForm` (the in-progress shape: raw text, `null`\n * for a choice not made). Two things are built from them, and until 0.17.0\n * each client built them by hand, so a second client (the app) would have had\n * to copy every rule below and keep it in step:\n *\n * the draft a step SAVES exactly the fields that step owns, as typed\n * (`onboardingDraftForStep`)\n * the body FINISH sends completed answers only, cleaned, every\n * unanswered optional field ABSENT\n * (`completeOnboardingBodyFrom`)\n *\n * Neither validates: `ONBOARDING_STEP_SCHEMAS` and `completeOnboardingBodySchema`\n * do, on what these build. A half-filled form builds a half-filled body on\n * purpose, so the schema refuses it with the reason under each field instead\n * of a builder hiding the gap.\n *\n * @module founder-schema/domains/onboarding/onboarding-answers\n */\n\nimport {\n type Industry,\n normalizeLinkUrl,\n normalizePhone,\n type StartupStage,\n tidyFullName,\n} from '../../core/common.js';\nimport { FOUNDED_YEAR_MIN } from '../../core/limits.js';\nimport { type Place, pickOfPlace } from '../location/areas.js';\nimport {\n type CompleteOnboardingBodyInput,\n FOUNDER_ONBOARDING_STEPS,\n GUIDELINES_VERSION,\n ONBOARDING_STEP_KEYS,\n type OnboardingForm,\n type OnboardingStepKey,\n readOnboardingDraft,\n teamSizeDigits,\n} from './onboarding.js';\n\ntype DeepPartial<T> = T extends readonly (infer Item)[]\n ? DeepPartial<Item>[]\n : T extends object\n ? { [K in keyof T]?: DeepPartial<T[K]> }\n : T;\n\n/**\n * A founding year as typed, kept to digits, as many as a year has (0.18.0,\n * moved from the web's Me editor beside `teamSizeDigits`).\n */\nexport const foundedYearDigits = (raw: string): string =>\n raw.replace(/\\D/g, '').slice(0, String(FOUNDED_YEAR_MIN).length);\n\n/**\n * A venture body from what was typed (0.18.0): ONE builder for onboarding's\n * business step and Me's business editor, which each built it by hand and\n * cleaned the team size two ways. Text trimmed, the website made a link\n * (`normalizeLinkUrl`), digits kept (`teamSizeDigits`, `foundedYearDigits`);\n * an empty optional answer is ABSENT. A missing name, industry or stage is\n * left missing, so `ventureSchema` refuses it under its field.\n */\nexport function ventureBodyFrom(v: {\n name: string;\n website: string;\n industry: Industry | null;\n stage: StartupStage | null;\n teamSize: string;\n tagline?: string;\n foundedYear?: string;\n}): {\n name: string;\n industry: Industry | undefined;\n stage: StartupStage | undefined;\n tagline?: string;\n website?: string;\n teamSize?: number;\n foundedYear?: number;\n} {\n const tagline = v.tagline?.trim();\n const website = normalizeLinkUrl(v.website);\n const teamSize = teamSizeDigits(v.teamSize);\n const foundedYear = foundedYearDigits(v.foundedYear ?? '');\n return {\n name: v.name.trim(),\n industry: v.industry ?? undefined,\n stage: v.stage ?? undefined,\n ...(tagline ? { tagline } : {}),\n ...(website ? { website } : {}),\n ...(teamSize ? { teamSize: Number(teamSize) } : {}),\n ...(foundedYear ? { foundedYear: Number(foundedYear) } : {}),\n };\n}\n\n/**\n * What `completeOnboardingBodyFrom` builds: the finish body's INPUT, any part\n * of it possibly still missing. Not a valid body until\n * `completeOnboardingBodySchema` says so.\n */\nexport type CompleteOnboardingBodyCandidate = DeepPartial<CompleteOnboardingBodyInput>;\n\n/** Answers with nothing filled in: what a new member starts from. */\nexport function emptyOnboardingForm(): OnboardingForm {\n return readOnboardingDraft({});\n}\n\n/**\n * True when the member has started describing a business: any of the\n * venture's own answers. Then it is sent, and the schema asks for its name,\n * industry and stage.\n *\n * The business type and the registration do not start it: they DESCRIBE a\n * business. Picked without one, each is sent anyway and refused on its own\n * field (`ventureRequired`: add the business, or clear this), never dropped\n * without a word (hary's ruling).\n */\nexport function ventureStarted(form: Pick<OnboardingForm, 'venture'>): boolean {\n const v = form.venture;\n return Boolean(v.name.trim() || v.website.trim() || v.industry || v.stage || v.teamSize.trim());\n}\n\n/**\n * True when a place has been given (0.19.0). The drawer hands back only a\n * whole place (state, district, and a locality or the whole district), so\n * there is no half answer: a place is there or it is not.\n */\nexport function locationStarted(place: Place | null): boolean {\n return place !== null;\n}\n\n/**\n * The draft one step saves: exactly the fields that step owns\n * (`FOUNDER_ONBOARDING_STEPS[].fields`, the same picks its validation uses),\n * as typed. Only the step's own fields, so a save can never overwrite another\n * step's answers (the server merges per field); raw, so a member who comes back\n * sees what they typed. The photo step's field is its upload id, which the\n * server holds rather than drafts.\n */\nexport function onboardingDraftForStep(\n form: OnboardingForm,\n key: OnboardingStepKey,\n): Partial<OnboardingForm> {\n const step = FOUNDER_ONBOARDING_STEPS[ONBOARDING_STEP_KEYS.indexOf(key)];\n const draft: Partial<OnboardingForm> = {};\n for (const field of step?.fields ?? []) copyField(draft, form, field);\n return draft;\n}\n\n/** One field across, typed by its key (0.18.0: the draft is the FORM's shape, never `unknown`). */\nfunction copyField<K extends keyof OnboardingForm>(\n to: Partial<OnboardingForm>,\n from: OnboardingForm,\n key: K,\n): void {\n to[key] = from[key];\n}\n\n/**\n * What Finish sends, and what every step is CHECKED as (each step's slice\n * reads its own fields from it).\n *\n * ⚠ ABSENT, NEVER A DEFAULT. The sibling gym product sent `bloodGroup ?? 'O+'`\n * and recorded everyone who skipped the question as O-positive. An optional\n * answer not given is left out; nothing is filled with a plausible value.\n *\n * Cleaned the way the schema reads it: the name tidied (`tidyFullName`), the\n * phone to ten digits (`normalizePhone`), links given their scheme\n * (`normalizeLinkUrl`), the place as its area ids (`pickOfPlace`, 0.19.0:\n * the server names it), the team size a number. A half-answered part is\n * SENT, not hidden: a business without its stage reaches the schema and is\n * refused there, under the field.\n */\nexport function completeOnboardingBodyFrom(form: OnboardingForm): CompleteOnboardingBodyCandidate {\n const headline = form.headline.trim();\n const about = form.about.trim();\n\n const links: Partial<Record<keyof OnboardingForm['links'], string>> = {};\n for (const k of Object.keys(form.links) as (keyof OnboardingForm['links'])[]) {\n const url = normalizeLinkUrl(form.links[k]);\n if (url) links[k] = url;\n }\n\n const body: CompleteOnboardingBodyCandidate = {\n displayName: tidyFullName(form.displayName),\n gender: form.gender ?? undefined,\n dateOfBirth: form.dateOfBirth.trim(),\n phone: normalizePhone(form.phone),\n goals: form.goals,\n interests: form.interests,\n // Only agreement to the CURRENT text is sent; anything else is absent, so\n // the schema refuses it as not agreed. Consent to an older text is not\n // consent to this one.\n guidelinesVersion:\n form.guidelinesVersion === GUIDELINES_VERSION ? GUIDELINES_VERSION : undefined,\n };\n if (headline) body.headline = headline;\n if (about) body.about = about;\n if (form.avatarMediaId) body.avatarMediaId = form.avatarMediaId;\n if (form.skills.length) body.skills = form.skills;\n if (Object.keys(links).length) body.links = links;\n\n if (form.location) body.location = pickOfPlace(form.location);\n\n if (ventureStarted(form)) body.venture = ventureBodyFrom(form.venture);\n // Sent whenever given: with a business they describe it, without one the\n // schema refuses each on its own field rather than anything vanishing.\n if (form.businessType) body.businessType = form.businessType;\n if (form.legalStructure) body.legalStructure = form.legalStructure;\n\n return body;\n}\n"]}