founder-schema 0.16.0 → 0.18.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 (116) hide show
  1. package/README.md +263 -19
  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 +117 -23
  11. package/dist/core/common.d.ts.map +1 -1
  12. package/dist/core/common.js +104 -21
  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 +55 -17
  19. package/dist/core/limits.d.ts.map +1 -1
  20. package/dist/core/limits.js +59 -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 +32 -2
  30. package/dist/core/validation.d.ts.map +1 -1
  31. package/dist/core/validation.js +32 -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/india.d.ts +215 -0
  61. package/dist/domains/location/india.d.ts.map +1 -0
  62. package/dist/domains/location/india.js +1203 -0
  63. package/dist/domains/location/india.js.map +1 -0
  64. package/dist/domains/members/members.d.ts +2 -2
  65. package/dist/domains/membership/membership.d.ts +26 -0
  66. package/dist/domains/membership/membership.d.ts.map +1 -1
  67. package/dist/domains/membership/membership.js +55 -1
  68. package/dist/domains/membership/membership.js.map +1 -1
  69. package/dist/domains/moderation/moderation.d.ts +2 -2
  70. package/dist/domains/moderation/moderation.js +3 -3
  71. package/dist/domains/moderation/moderation.js.map +1 -1
  72. package/dist/domains/notifications/notifications.d.ts +1 -10
  73. package/dist/domains/notifications/notifications.d.ts.map +1 -1
  74. package/dist/domains/notifications/notifications.js +2 -3
  75. package/dist/domains/notifications/notifications.js.map +1 -1
  76. package/dist/domains/onboarding/onboarding-answers.d.ts +112 -0
  77. package/dist/domains/onboarding/onboarding-answers.d.ts.map +1 -0
  78. package/dist/domains/onboarding/onboarding-answers.js +164 -0
  79. package/dist/domains/onboarding/onboarding-answers.js.map +1 -0
  80. package/dist/domains/onboarding/onboarding.d.ts +398 -62
  81. package/dist/domains/onboarding/onboarding.d.ts.map +1 -1
  82. package/dist/domains/onboarding/onboarding.js +229 -68
  83. package/dist/domains/onboarding/onboarding.js.map +1 -1
  84. package/dist/domains/products/products.d.ts +43 -8
  85. package/dist/domains/products/products.d.ts.map +1 -1
  86. package/dist/domains/products/products.js +57 -11
  87. package/dist/domains/products/products.js.map +1 -1
  88. package/dist/domains/profile/founder-profile.d.ts +120 -68
  89. package/dist/domains/profile/founder-profile.d.ts.map +1 -1
  90. package/dist/domains/profile/founder-profile.js +80 -29
  91. package/dist/domains/profile/founder-profile.js.map +1 -1
  92. package/dist/domains/profile/my-profile.d.ts +3 -3
  93. package/dist/domains/reviews/reviews.d.ts +1 -1
  94. package/dist/domains/reviews/reviews.d.ts.map +1 -1
  95. package/dist/domains/reviews/reviews.js +2 -2
  96. package/dist/domains/reviews/reviews.js.map +1 -1
  97. package/dist/domains/support/support.d.ts +22 -3
  98. package/dist/domains/support/support.d.ts.map +1 -1
  99. package/dist/domains/support/support.js +19 -5
  100. package/dist/domains/support/support.js.map +1 -1
  101. package/dist/domains/team/team.d.ts.map +1 -1
  102. package/dist/domains/team/team.js +3 -6
  103. package/dist/domains/team/team.js.map +1 -1
  104. package/dist/domains/uploads/uploads.d.ts +7 -22
  105. package/dist/domains/uploads/uploads.d.ts.map +1 -1
  106. package/dist/domains/uploads/uploads.js +7 -21
  107. package/dist/domains/uploads/uploads.js.map +1 -1
  108. package/dist/env.d.ts +26 -13
  109. package/dist/env.d.ts.map +1 -1
  110. package/dist/env.js +58 -23
  111. package/dist/env.js.map +1 -1
  112. package/dist/index.d.ts +18 -15
  113. package/dist/index.d.ts.map +1 -1
  114. package/dist/index.js +19 -14
  115. package/dist/index.js.map +1 -1
  116. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../src/access/permissions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,QAAQ;IACR,QAAQ;IACR,MAAM;IACN,QAAQ;IACR,QAAQ;IACR,UAAU;IACV,QAAQ;IACR,SAAS;IACT,SAAS;IACT,OAAO;IACP,WAAW;IACX,OAAO;IACP,MAAM;IACN,MAAM;IACN,MAAM;IACN,SAAS;IACT,QAAQ;CACA,CAAC;AACX,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;AAGlD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,KAAK;IACL,WAAW;IACX,QAAQ;IACR,gBAAgB;IAChB,MAAM;IACN,MAAM;IACN,SAAS;IACT,QAAQ;IACR,eAAe;IACf,YAAY;IACZ,cAAc;IACd,YAAY;IACZ,kBAAkB;IAClB,SAAS;IACT,QAAQ;IACR,WAAW;IACX,qBAAqB;IACrB,YAAY;IACZ,SAAS;IACT,QAAQ;CACA,CAAC;AACX,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;AA6FpD;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC","sourcesContent":["/**\n * founder-schema: what a role may do, in CASL's terms\n *\n * Permissions are ACTION on SUBJECT, optionally narrowed by conditions on the\n * subject's fields (\"update a Post whose authorId is mine\"). The backend writes\n * one small policy per role from these words; the web receives the resulting\n * rules and asks the same questions to decide what to draw. Because both sides\n * spell actions and subjects from this file, a permission check with a typo is\n * a compile error, not a button that silently never shows.\n *\n * The SERVER decides; the web only mirrors. Every rule the web holds came from\n * the server for the role being acted as, and every request is checked again\n * on the server, so a tampered client can show itself a button and nothing\n * more.\n *\n * @module founder-schema/access/permissions\n */\n\nimport type { ForcedSubject, MongoAbility } from '@casl/ability';\nimport { z } from 'zod';\nimport type { PurchaseKind } from '../domains/billing/billing.js';\nimport type { PlatformRole } from './roles.js';\nimport type { Standing } from './standing.js';\n\n/**\n * The verbs.\n *\n * manage every action (CASL's wildcard). Only superadmin holds it on `all`.\n * create, read, update, delete\n * moderate hide, restore or remove someone else's content, act on a report\n * assign grant or revoke (a platform role); take or hand over (a ticket)\n * suspend stop a member signing in to the product, and lift it\n * resolve close a support ticket or a report\n * reset clear a credential its owner forgot (a staff member's MPIN)\n * represent act for a product as one of its team: tag a post with it, reply\n * to its reviews, mark feedback helpful, confirm a tester\n *\n * Taking part (0.12.0, membership): the verbs a viewer is refused and a member\n * holds. Creating content stays `create`; these are the rest.\n *\n * react a reaction on a post, poll or comment; an upvote on a product;\n * marking a review helpful\n * vote vote in a poll\n * save bookmark a post or poll\n * join join a community, ask to, or accept its invitation; accept an\n * invitation to a product's team\n * message start a conversation with a Member\n * refund give a payment's money back (staff)\n *\n * Append only: a stored or packed rule names these.\n */\nexport const ACTION_VALUES = [\n 'manage',\n 'create',\n 'read',\n 'update',\n 'delete',\n 'moderate',\n 'assign',\n 'suspend',\n 'resolve',\n 'reset',\n 'represent',\n 'react',\n 'vote',\n 'save',\n 'join',\n 'message',\n 'refund',\n] as const;\nexport const actionSchema = z.enum(ACTION_VALUES);\nexport type Action = z.infer<typeof actionSchema>;\n\n/**\n * The things.\n *\n * all CASL's wildcard subject\n * Dashboard the staff overview: counts and trends\n * Member a person's account in this product\n * FounderProfile a member's public profile\n * Post, Poll, Comment\n * Report a member's report of a post, poll or comment\n * SupportTicket a member's request for help\n * SavedReply a support agent's reusable answer\n * PlatformRole a staff role, as something granted and revoked\n * AuditEntry the record of a staff action\n * PlatformSettings product-wide configuration\n * Product something a founder builds, with its team\n * Review a member's stars and words on a product\n * Community an official community (0.9.0)\n * CommunitySuggestion a member's suggestion for a new community (0.9.0)\n * Membership a person's platform membership: granted, revoked (0.12.0)\n * Payment a purchase and its invoice, and its refund (0.12.0)\n * Coupon a code that gives a membership or a discount (0.12.0)\n */\nexport const SUBJECT_VALUES = [\n 'all',\n 'Dashboard',\n 'Member',\n 'FounderProfile',\n 'Post',\n 'Poll',\n 'Comment',\n 'Report',\n 'SupportTicket',\n 'SavedReply',\n 'PlatformRole',\n 'AuditEntry',\n 'PlatformSettings',\n 'Product',\n 'Review',\n 'Community',\n 'CommunitySuggestion',\n 'Membership',\n 'Payment',\n 'Coupon',\n] as const;\nexport const subjectSchema = z.enum(SUBJECT_VALUES);\nexport type Subject = z.infer<typeof subjectSchema>;\n\n/**\n * The fields a rule may set CONDITIONS on, per subject: the server's stored\n * names, ids as strings. Declared here so a condition on a field the subject\n * does not have (`{ ownerId: me }` on a Post) is a compile error, on both sides.\n *\n * A web screen does not evaluate these against an API response: a response\n * names things differently (`author.userId`, not `authorId`). Row-level answers\n * come from the server on the item itself (`viewer.canEdit`); the web's own\n * checks are type-level (\"may I moderate Reports at all\").\n */\nexport interface SubjectFields {\n Dashboard: Record<never, never>;\n /**\n * `appRoles` is the staff roles held, so a rule can protect a role (\"never\n * suspend a superadmin\").\n *\n * For `message` (0.12.0), what the asker's relation to them is:\n * `contact` says the asker knows them (a teammate, a private community in\n * common, a conversation already); `standing` is theirs, so nobody starts a\n * conversation with a viewer, who cannot chat; `acceptsStrangers` is their\n * own setting (Premium and Business members may start one, or only people\n * they know). Optional because only the messaging check computes them: a\n * rule on a field that is absent never matches, so a check that forgets them\n * is refused, never let through.\n */\n Member: {\n _id: string;\n appRoles: PlatformRole[];\n contact?: boolean;\n standing?: Standing;\n acceptsStrangers?: boolean;\n };\n FounderProfile: { userId: string };\n Post: { authorId: string };\n Poll: { authorId: string };\n Comment: { authorId: string };\n Report: { reporterId: string; status: string };\n SupportTicket: { memberId: string; assigneeId: string | null };\n SavedReply: { authorId: string };\n /** One role on one person: `memberId` is whose, so \"never your own\" is a rule too. */\n PlatformRole: { role: PlatformRole; memberId: string };\n /** `actorId` is null for an entry a server script wrote. */\n AuditEntry: { actorId: string | null };\n PlatformSettings: Record<never, never>;\n /**\n * The team, as three lists of member ids, so a rule reads \"a product whose\n * managers include me\" (`{ managerIds: me }`). `managerIds` is the owner and\n * the co-founders; `memberIds` is everyone on the team, owner included.\n */\n Product: { ownerId: string; managerIds: string[]; memberIds: string[] };\n Review: { authorId: string };\n /**\n * Its admins and moderators as id lists (0.9.0), so a rule reads \"a\n * community whose admins include me\" (`{ adminIds: me }`). Both short by\n * nature; ordinary members are never listed here (a community has no cap).\n */\n Community: { _id: string; adminIds: string[]; moderatorIds: string[] };\n /** `suggestedBy` so \"your own\" is a rule; `status` so only open ones are decided. */\n CommunitySuggestion: { suggestedBy: string; status: string };\n /** Whose membership (0.12.0), so \"never your own\" is a rule. */\n Membership: { userId: string };\n /**\n * Who pays (0.12.0), what for, and where it stands: a viewer may buy only a\n * membership (a plan needs one), only a paid one is refunded, and never by\n * its own payer.\n */\n Payment: { userId: string; status: string; kind: PurchaseKind };\n /** Coupons are the platform's (0.12.0): no condition is ever written on one. */\n Coupon: Record<never, never>;\n}\n\n/** A document tagged with its subject type, as CASL's `subject('Post', doc)` makes one. */\nexport type SubjectInstance = {\n [K in keyof SubjectFields]: SubjectFields[K] & ForcedSubject<K>;\n}[keyof SubjectFields];\n\n/**\n * The ability both sides build: the backend from its policy, the web from the\n * rules the backend sends. Mongo-style conditions, typed by `SubjectFields`.\n */\nexport type AppAbility = MongoAbility<[Action, Subject | SubjectInstance]>;\n\n/**\n * Compile-time proof that `SubjectFields` names exactly the subjects, no more\n * and no fewer: adding a subject without its fields fails the build here.\n */\ntype Same<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false;\ntype Assert<T extends true> = T;\nexport type EverySubjectHasFields = Assert<Same<keyof SubjectFields, Exclude<Subject, 'all'>>>;\n\n/**\n * Rules as they cross the wire: CASL's `packRules` output, an array of\n * compact arrays. The web unpacks them with `unpackRules` before building its\n * ability; their inner shape is CASL's, so it is not re-described here.\n */\nexport const packedRulesSchema = z.array(z.array(z.unknown()));\nexport type PackedRules = z.infer<typeof packedRulesSchema>;\n"]}
1
+ {"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../src/access/permissions.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema: what a role may do, in CASL's terms\n *\n * Permissions are ACTION on SUBJECT, optionally narrowed by conditions on the\n * subject's fields (\"update a Post whose authorId is mine\"). The backend writes\n * one small policy per role from these words; the web receives the resulting\n * rules and asks the same questions to decide what to draw. Because both sides\n * spell actions and subjects from this file, a permission check with a typo is\n * a compile error, not a button that silently never shows.\n *\n * The SERVER decides; the web only mirrors. Every rule the web holds came from\n * the server for the role being acted as, and every request is checked again\n * on the server, so a tampered client can show itself a button and nothing\n * more.\n *\n * @module founder-schema/access/permissions\n */\n\nimport type { ForcedSubject, MongoAbility, RawRuleOf } from '@casl/ability';\nimport { z } from 'zod';\nimport type { OrderStatus, PurchaseKind } from '../domains/billing/billing.js';\nimport type { CommunitySuggestionStatus } from '../domains/communities/communities.js';\nimport type { ReportStatus } from '../domains/feed/feed.js';\nimport type { PlatformRole } from './roles.js';\nimport type { Standing } from './standing.js';\n\n/**\n * The verbs.\n *\n * manage every action (CASL's wildcard). Only superadmin holds it on `all`.\n * create, read, update, delete\n * moderate hide, restore or remove someone else's content, act on a report\n * assign grant or revoke (a platform role); take or hand over (a ticket)\n * suspend stop a member signing in to the product, and lift it\n * resolve close a support ticket or a report\n * reset clear a credential its owner forgot (a staff member's MPIN)\n * represent act for a product as one of its team: tag a post with it, reply\n * to its reviews, mark feedback helpful, confirm a tester\n *\n * Taking part (0.12.0, membership): the verbs a viewer is refused and a member\n * holds. Creating content stays `create`; these are the rest.\n *\n * react a reaction on a post, poll or comment; an upvote on a product;\n * marking a review helpful\n * vote vote in a poll\n * save bookmark a post or poll\n * join join a community, ask to, or accept its invitation; accept an\n * invitation to a product's team\n * message start a conversation with a Member\n * refund give a payment's money back (staff)\n *\n * Append only: a stored or packed rule names these.\n */\nexport const ACTION_VALUES = [\n 'manage',\n 'create',\n 'read',\n 'update',\n 'delete',\n 'moderate',\n 'assign',\n 'suspend',\n 'resolve',\n 'reset',\n 'represent',\n 'react',\n 'vote',\n 'save',\n 'join',\n 'message',\n 'refund',\n] as const;\nexport type Action = (typeof ACTION_VALUES)[number];\n\n/**\n * The things.\n *\n * all CASL's wildcard subject\n * Dashboard the staff overview: counts and trends\n * Member a person's account in this product\n * FounderProfile a member's public profile\n * Post, Poll, Comment\n * Report a member's report of a post, poll or comment\n * SupportTicket a member's request for help\n * SavedReply a support agent's reusable answer\n * PlatformRole a staff role, as something granted and revoked\n * AuditEntry the record of a staff action\n * PlatformSettings product-wide configuration\n * Product something a founder builds, with its team\n * Review a member's stars and words on a product\n * Community an official community (0.9.0)\n * CommunitySuggestion a member's suggestion for a new community (0.9.0)\n * Membership a person's platform membership: granted, revoked (0.12.0)\n * Payment a purchase and its invoice, and its refund (0.12.0)\n * Coupon a code that gives a membership or a discount (0.12.0)\n */\nexport const SUBJECT_VALUES = [\n 'all',\n 'Dashboard',\n 'Member',\n 'FounderProfile',\n 'Post',\n 'Poll',\n 'Comment',\n 'Report',\n 'SupportTicket',\n 'SavedReply',\n 'PlatformRole',\n 'AuditEntry',\n 'PlatformSettings',\n 'Product',\n 'Review',\n 'Community',\n 'CommunitySuggestion',\n 'Membership',\n 'Payment',\n 'Coupon',\n] as const;\nexport const subjectSchema = z.enum(SUBJECT_VALUES);\nexport type Subject = z.infer<typeof subjectSchema>;\n\n/**\n * The fields a rule may set CONDITIONS on, per subject: the server's stored\n * names, ids as strings. Declared here so a condition on a field the subject\n * does not have (`{ ownerId: me }` on a Post) is a compile error, on both sides.\n *\n * A web screen does not evaluate these against an API response: a response\n * names things differently (`author.userId`, not `authorId`). Row-level answers\n * come from the server on the item itself (`viewer.canEdit`); the web's own\n * checks are type-level (\"may I moderate Reports at all\").\n */\nexport interface SubjectFields {\n Dashboard: Record<never, never>;\n /**\n * `appRoles` is the staff roles held, so a rule can protect a role (\"never\n * suspend a superadmin\").\n *\n * For `message` (0.12.0), what the asker's relation to them is:\n * `contact` says the asker knows them (a teammate, a private community in\n * common, a conversation already); `standing` is theirs, so nobody starts a\n * conversation with a viewer, who cannot chat; `acceptsStrangers` is their\n * own setting (Premium and Business members may start one, or only people\n * they know). Optional because only the messaging check computes them: a\n * rule on a field that is absent never matches, so a check that forgets them\n * is refused, never let through.\n */\n Member: {\n _id: string;\n appRoles: PlatformRole[];\n contact?: boolean;\n standing?: Standing;\n acceptsStrangers?: boolean;\n };\n FounderProfile: { userId: string };\n Post: { authorId: string };\n Poll: { authorId: string };\n Comment: { authorId: string };\n Report: { reporterId: string; status: ReportStatus };\n SupportTicket: { memberId: string; assigneeId: string | null };\n SavedReply: { authorId: string };\n /** One role on one person: `memberId` is whose, so \"never your own\" is a rule too. */\n PlatformRole: { role: PlatformRole; memberId: string };\n /** `actorId` is null for an entry a server script wrote. */\n AuditEntry: { actorId: string | null };\n PlatformSettings: Record<never, never>;\n /**\n * The team, as three lists of member ids, so a rule reads \"a product whose\n * managers include me\" (`{ managerIds: me }`). `managerIds` is the owner and\n * the co-founders; `memberIds` is everyone on the team, owner included.\n */\n Product: { ownerId: string; managerIds: string[]; memberIds: string[] };\n Review: { authorId: string };\n /**\n * Its admins and moderators as id lists (0.9.0), so a rule reads \"a\n * community whose admins include me\" (`{ adminIds: me }`). Both short by\n * nature; ordinary members are never listed here (a community has no cap).\n */\n Community: { _id: string; adminIds: string[]; moderatorIds: string[] };\n /** `suggestedBy` so \"your own\" is a rule; `status` so only open ones are decided. */\n CommunitySuggestion: { suggestedBy: string; status: CommunitySuggestionStatus };\n /** Whose membership (0.12.0), so \"never your own\" is a rule. */\n Membership: { userId: string };\n /**\n * Who pays (0.12.0), what for, and where it stands: a viewer may buy only a\n * membership (a plan needs one), only a paid one is refunded, and never by\n * its own payer.\n */\n Payment: { userId: string; status: OrderStatus; kind: PurchaseKind };\n /** Coupons are the platform's (0.12.0): no condition is ever written on one. */\n Coupon: Record<never, never>;\n}\n\n/** A document tagged with its subject type, as CASL's `subject('Post', doc)` makes one. */\nexport type SubjectInstance = {\n [K in keyof SubjectFields]: SubjectFields[K] & ForcedSubject<K>;\n}[keyof SubjectFields];\n\n/**\n * The ability both sides build: the backend from its policy, the web from the\n * rules the backend sends. Mongo-style conditions, typed by `SubjectFields`.\n */\nexport type AppAbility = MongoAbility<[Action, Subject | SubjectInstance]>;\n\n/**\n * Compile-time proof that `SubjectFields` names exactly the subjects, no more\n * and no fewer: adding a subject without its fields fails the build here.\n */\ntype Same<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false;\ntype Assert<T extends true> = T;\nexport type EverySubjectHasFields = Assert<Same<keyof SubjectFields, Exclude<Subject, 'all'>>>;\n\n/**\n * Rules as they cross the wire: CASL's `packRules` output, an array of\n * compact arrays. The web unpacks them with `unpackRules` before building its\n * ability. Each starts with its action(s) and subject(s), comma-joined\n * strings (checked, 0.18.0: an unchecked rule list could silently turn staff\n * routing off); what follows (conditions, inverted, fields, reason) is CASL's\n * own compact form.\n */\nconst zeroOrOne = z.union([z.literal(0), z.literal(1)]);\n/**\n * One packed rule, in exactly the shapes CASL's `PackRule` takes (a union of\n * tuples, not a rest: `unpackRules` needs one of these), with `conditions` as\n * `unknown`, which `isAppRawRule` and the ability itself then judge.\n */\nexport const packedRuleSchema = z.union([\n z.tuple([z.string().min(1), z.string().min(1)]),\n z.tuple([z.string().min(1), z.string().min(1), z.unknown()]),\n z.tuple([z.string().min(1), z.string().min(1), z.unknown(), z.literal(1)]),\n z.tuple([z.string().min(1), z.string().min(1), z.unknown(), zeroOrOne, z.string()]),\n z.tuple([\n z.string().min(1),\n z.string().min(1),\n z.unknown(),\n zeroOrOne,\n z.union([z.string(), z.literal(0)]),\n z.string(),\n ]),\n]);\nexport const packedRulesSchema = z.array(packedRuleSchema);\nexport type PackedRules = z.infer<typeof packedRulesSchema>;\n\n/** One rule as CASL's `unpackRules` gives it back, typed for this app. */\nexport type AppRawRule = RawRuleOf<AppAbility>;\n\nconst ACTION_SET: ReadonlySet<string> = new Set(ACTION_VALUES);\nconst SUBJECT_SET: ReadonlySet<string> = new Set(SUBJECT_VALUES);\nconst everyIn = (value: string | string[] | undefined, known: ReadonlySet<string>) =>\n value !== undefined && (Array.isArray(value) ? value : [value]).every((v) => known.has(v));\n\n/**\n * Whether an UNPACKED rule names only this app's actions and subjects\n * (0.18.0). Packed rules cross the wire as strings (`packRules` joins several\n * actions or subjects with commas, so the wire shape cannot be an enum); this\n * is the check that turns them back into `AppRawRule`s, with no cast:\n * `rules.every(isAppRawRule)` narrows the list, and a list that fails is a\n * contract mismatch, never an ability built from it.\n */\nexport function isAppRawRule(rule: {\n action: string | string[];\n subject?: string | string[];\n}): rule is AppRawRule {\n return everyIn(rule.action, ACTION_SET) && everyIn(rule.subject, SUBJECT_SET);\n}\n"]}
@@ -16,8 +16,14 @@ import { z } from 'zod';
16
16
  * alone. Jansathi shipped that bug in dating onboarding.
17
17
  */
18
18
  export declare const emptyStringToUndefined: (value: unknown) => unknown;
19
- /** Optional trimmed string. Empty strings are coerced to `undefined`. */
20
- export declare const optionalTrimmedString: (maxLength?: number) => z.ZodPreprocess<z.ZodOptional<z.ZodString>, unknown>;
19
+ /**
20
+ * Optional trimmed string: a blank one (`''`, spaces) is absent (`undefined`).
21
+ * Trimmed, checked, then blank dropped, never a preprocess (0.18.0: a
22
+ * preprocess types its INPUT as `unknown`, so a caller typing a body by
23
+ * `z.input` could send a number and still compile). `.optional()` OUTSIDE, so
24
+ * the key stays optional in the output type as well as the input.
25
+ */
26
+ export declare const optionalTrimmedString: (maxLength?: number) => z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string | undefined, string>>>;
21
27
  /**
22
28
  * A repeated query param (`?skill=growth&skill=sales`) or a single value,
23
29
  * always as an array, at most `max` values.
@@ -78,6 +84,14 @@ export type SafeUrl = z.infer<typeof safeUrlSchema>;
78
84
  */
79
85
  export declare const httpUrlSchema: z.ZodURL;
80
86
  export type HttpUrl = z.infer<typeof httpUrlSchema>;
87
+ /**
88
+ * A link as people paste it, made into what `httpUrlSchema` checks (0.17.0,
89
+ * moved here from the web so every client cleans a link the same way):
90
+ * `linkedin.com/in/asha` gains `https://`; blank stays blank. Something that
91
+ * already names a scheme keeps it, so `javascript:` is never quietly "fixed"
92
+ * into a link: the schema then refuses it, as it only takes http and https.
93
+ */
94
+ export declare function normalizeLinkUrl(raw: string): string;
81
95
  /**
82
96
  * True when `url` parses and its hostname is EXACTLY one of `hosts`
83
97
  * (lowercase). The one host check in this package, written the way the warning
@@ -104,12 +118,13 @@ export type ProductListingRequirement = z.infer<typeof productListingRequirement
104
118
  * is shared as a link and played in the card, never uploaded. The web plays it
105
119
  * through youtube-nocookie.com, the one video frame its CSP admits.
106
120
  */
107
- export declare const YOUTUBE_HOSTS: readonly ["youtube.com", "www.youtube.com", "m.youtube.com", "youtu.be", "youtube-nocookie.com", "www.youtube-nocookie.com"];
121
+ export declare const YOUTUBE_HOSTS: readonly ["youtube.com", "www.youtube.com", "m.youtube.com", "music.youtube.com", "youtu.be", "www.youtu.be", "youtube-nocookie.com", "www.youtube-nocookie.com"];
108
122
  /**
109
123
  * The 11-character id of a YouTube VIDEO link, or null for anything else (a
110
124
  * channel, a playlist, the home page, another site): what a page can embed.
111
- * Reads `youtu.be/<id>`, `/watch?v=<id>`, and `/shorts/`, `/embed/`, `/live/<id>`
112
- * on a `YOUTUBE_HOSTS` host (0.7.0).
125
+ * Reads `youtu.be/<id>`, `/watch?v=<id>`, and `/shorts/`, `/embed/`, `/live/`,
126
+ * `/v/<id>` on a `YOUTUBE_HOSTS` host (0.7.0; `/v/` and the feed's hosts
127
+ * 0.18.0). THE rule for a product's video and a post's embed alike.
113
128
  */
114
129
  export declare function youtubeVideoId(url: string): string | null;
115
130
  /**
@@ -265,6 +280,80 @@ export declare const goalSchema: z.ZodEnum<{
265
280
  attend_events: "attend_events";
266
281
  }>;
267
282
  export type Goal = z.infer<typeof goalSchema>;
283
+ /**
284
+ * True when no value is in the list twice: THE duplicate rule (0.18.0, exported;
285
+ * every list in this package refines with it instead of its own copy).
286
+ */
287
+ export declare const eachOnce: (items: readonly unknown[]) => boolean;
288
+ /**
289
+ * A member's goals as a list (0.17.0): each goal once, at most every goal.
290
+ * ONE rule for onboarding's finish and the Me tab's editor, which both add the
291
+ * minimum (`requiredGoalListSchema`, 0.18.0): two copies of one goal met the
292
+ * minimum while meaning one, and the profile would show it twice.
293
+ */
294
+ export declare const goalListSchema: z.ZodArray<z.ZodEnum<{
295
+ find_cofounder: "find_cofounder";
296
+ find_investors: "find_investors";
297
+ find_customers: "find_customers";
298
+ hiring: "hiring";
299
+ partnerships: "partnerships";
300
+ mentorship: "mentorship";
301
+ grow_network: "grow_network";
302
+ business_ideas: "business_ideas";
303
+ market_insights: "market_insights";
304
+ learn_startup: "learn_startup";
305
+ start_company: "start_company";
306
+ get_feedback: "get_feedback";
307
+ find_testers: "find_testers";
308
+ mentor_others: "mentor_others";
309
+ invest_in_startups: "invest_in_startups";
310
+ join_startup: "join_startup";
311
+ find_service_providers: "find_service_providers";
312
+ attend_events: "attend_events";
313
+ }>>;
314
+ /**
315
+ * A member's goals where they are REQUIRED (0.18.0): at least `GOALS_MIN`.
316
+ * ONE schema for onboarding's finish and the Me editor (the user: "we already
317
+ * have at least one goal required"), so the two can never disagree.
318
+ */
319
+ export declare const requiredGoalListSchema: z.ZodArray<z.ZodEnum<{
320
+ find_cofounder: "find_cofounder";
321
+ find_investors: "find_investors";
322
+ find_customers: "find_customers";
323
+ hiring: "hiring";
324
+ partnerships: "partnerships";
325
+ mentorship: "mentorship";
326
+ grow_network: "grow_network";
327
+ business_ideas: "business_ideas";
328
+ market_insights: "market_insights";
329
+ learn_startup: "learn_startup";
330
+ start_company: "start_company";
331
+ get_feedback: "get_feedback";
332
+ find_testers: "find_testers";
333
+ mentor_others: "mentor_others";
334
+ invest_in_startups: "invest_in_startups";
335
+ join_startup: "join_startup";
336
+ find_service_providers: "find_service_providers";
337
+ attend_events: "attend_events";
338
+ }>>;
339
+ /** A member's skills (0.17.0): each skill once, at most `SKILLS_MAX`. ONE rule for onboarding and Me. */
340
+ export declare const skillListSchema: z.ZodArray<z.ZodEnum<{
341
+ engineering: "engineering";
342
+ product: "product";
343
+ design: "design";
344
+ data: "data";
345
+ growth: "growth";
346
+ sales: "sales";
347
+ marketing: "marketing";
348
+ operations: "operations";
349
+ finance: "finance";
350
+ legal: "legal";
351
+ hr_people: "hr_people";
352
+ supply_chain: "supply_chain";
353
+ research: "research";
354
+ fundraising: "fundraising";
355
+ community: "community";
356
+ }>>;
268
357
  /**
269
358
  * Demographic only. Nothing here computes anything from it, unlike the sibling
270
359
  * gym product where gender feeds body-metric maths.
@@ -281,14 +370,22 @@ export declare const genderSchema: z.ZodEnum<{
281
370
  prefer_not_to_say: "prefer_not_to_say";
282
371
  }>;
283
372
  export type Gender = z.infer<typeof genderSchema>;
373
+ /**
374
+ * A full name as it is sent and stored (0.17.0, moved here from the web):
375
+ * trimmed, and every run of spaces made one. `displayNameSchema` applies it,
376
+ * so the server stores it from every client; a client applies it to show the
377
+ * member what will be kept. What `splitFullName` reads.
378
+ */
379
+ export declare const tidyFullName: (fullName: string) => string;
284
380
  /**
285
381
  * The name a member is shown by. ONE rule for every body that writes it:
286
382
  * onboarding's finish, the profile patch and the personal-details patch, so a
287
- * name one accepts the others can never refuse. Plain bounds, no custom
288
- * message: `too_small` with minimum 1 already says "required", and the client
289
- * words it.
383
+ * name one accepts the others can never refuse, and each stores it TIDIED
384
+ * (`tidyFullName`, 0.17.0: "Asha Rao" from the Me editor was stored as
385
+ * typed, while onboarding sent it tidy). Plain bounds, no custom message:
386
+ * `too_small` with minimum 1 already says "required", and the client words it.
290
387
  */
291
- export declare const displayNameSchema: z.ZodString;
388
+ export declare const displayNameSchema: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
292
389
  /**
293
390
  * A full name's first and last name, DEDUCED, never asked (the user,
294
391
  * 2026-10-18: onboarding asks one "Full name" and works the two out from the
@@ -307,6 +404,8 @@ export declare function splitFullName(fullName: string): {
307
404
  firstName: string;
308
405
  lastName: string;
309
406
  };
407
+ /** The digits an Indian mobile number has: what `phoneSchema` takes and the field keeps. */
408
+ export declare const PHONE_DIGITS = 10;
310
409
  /**
311
410
  * Indian mobile number: ten raw digits, no country code, matching the standard
312
411
  * used across this family. Display adds the `+91`.
@@ -318,6 +417,13 @@ export declare function splitFullName(fullName: string): {
318
417
  */
319
418
  export declare const phoneSchema: z.ZodString;
320
419
  export type Phone = z.infer<typeof phoneSchema>;
420
+ /**
421
+ * An Indian mobile number as `phoneSchema`'s ten digits, from however it was
422
+ * typed (0.17.0, moved here from the web): `98765 43210`, `+91 98765-43210`,
423
+ * `091 9876543210`. Anything else comes back digits only and fails the schema
424
+ * with `phoneInvalid`, rather than being guessed at.
425
+ */
426
+ export declare function normalizePhone(raw: string): string;
321
427
  /**
322
428
  * Where the founder is based. City and country are what Discover filters on.
323
429
  *
@@ -337,7 +443,7 @@ export type Phone = z.infer<typeof phoneSchema>;
337
443
  */
338
444
  export declare const locationSchema: z.ZodObject<{
339
445
  city: z.ZodString;
340
- state: z.ZodPreprocess<z.ZodOptional<z.ZodString>, unknown>;
446
+ state: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string | undefined, string>>>;
341
447
  country: z.ZodString;
342
448
  }, z.core.$strip>;
343
449
  export type FounderLocation = z.infer<typeof locationSchema>;
@@ -354,18 +460,6 @@ export declare const coordinatesQuerySchema: z.ZodObject<{
354
460
  }, z.core.$strip>;
355
461
  export type CoordinatesQuery = z.infer<typeof coordinatesQuerySchema>;
356
462
  export type CoordinatesQueryInput = z.input<typeof coordinatesQuerySchema>;
357
- /**
358
- * The city these coordinates look like, for the member to CONFIRM, or `null`
359
- * when the lookup could not place them confidently (or they are outside India,
360
- * whose area data is all there is). A suggestion, never a write: only what the
361
- * member then submits becomes their location.
362
- */
363
- export declare const suggestedLocationSchema: z.ZodNullable<z.ZodObject<{
364
- city: z.ZodString;
365
- state: z.ZodPreprocess<z.ZodOptional<z.ZodString>, unknown>;
366
- country: z.ZodString;
367
- }, z.core.$strip>>;
368
- export type SuggestedLocation = z.infer<typeof suggestedLocationSchema>;
369
463
  /**
370
464
  * Cursor pagination query. Feed and Discover both use it.
371
465
  *
@@ -374,7 +468,7 @@ export type SuggestedLocation = z.infer<typeof suggestedLocationSchema>;
374
468
  */
375
469
  export declare const paginationQuerySchema: z.ZodObject<{
376
470
  limit: z.ZodPreprocess<z.ZodDefault<z.ZodCoercedNumber<unknown>>, unknown>;
377
- cursor: z.ZodOptional<z.ZodString>;
471
+ cursor: z.ZodPreprocess<z.ZodOptional<z.ZodString>, unknown>;
378
472
  }, z.core.$strip>;
379
473
  export type PaginationQuery = z.infer<typeof paginationQuerySchema>;
380
474
  /** What a client may put IN the URL: every field optional, numbers as strings. */
@@ -1 +1 @@
1
- {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/core/common.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/core/common.ts"],"names":[],"mappings":"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"}
@@ -7,7 +7,7 @@
7
7
  * @module founder-schema/core/common
8
8
  */
9
9
  import { z } from 'zod';
10
- import { CITY_MAX, COUNTRY_MAX, DISPLAY_NAME_MAX, LINK_URL_MAX, PAGE_SIZE_DEFAULT, PAGE_SIZE_MAX, STATE_MAX, } from './limits.js';
10
+ import { CITY_MAX, COUNTRY_MAX, CURSOR_MAX, DISPLAY_NAME_MAX, GOALS_MIN, LINK_URL_MAX, PAGE_SIZE_DEFAULT, PAGE_SIZE_MAX, SKILLS_MAX, STATE_MAX, } from './limits.js';
11
11
  import { VALIDATION_MESSAGE } from './validation.js';
12
12
  // ─── String helpers ──────────────────────────────────────────────────────────
13
13
  /**
@@ -23,8 +23,19 @@ export const emptyStringToUndefined = (value) => {
23
23
  return undefined;
24
24
  return value;
25
25
  };
26
- /** Optional trimmed string. Empty strings are coerced to `undefined`. */
27
- export const optionalTrimmedString = (maxLength = 100) => z.preprocess(emptyStringToUndefined, z.string().trim().max(maxLength).optional());
26
+ /**
27
+ * Optional trimmed string: a blank one (`''`, spaces) is absent (`undefined`).
28
+ * Trimmed, checked, then blank dropped, never a preprocess (0.18.0: a
29
+ * preprocess types its INPUT as `unknown`, so a caller typing a body by
30
+ * `z.input` could send a number and still compile). `.optional()` OUTSIDE, so
31
+ * the key stays optional in the output type as well as the input.
32
+ */
33
+ export const optionalTrimmedString = (maxLength = 100) => z
34
+ .string()
35
+ .trim()
36
+ .max(maxLength)
37
+ .transform((value) => value || undefined)
38
+ .optional();
28
39
  /**
29
40
  * A repeated query param (`?skill=growth&skill=sales`) or a single value,
30
41
  * always as an array, at most `max` values.
@@ -94,6 +105,19 @@ export const safeUrlSchema = z.url({
94
105
  * `imageUrl` would have failed a whole feed page.
95
106
  */
96
107
  export const httpUrlSchema = safeUrlSchema.max(LINK_URL_MAX);
108
+ /**
109
+ * A link as people paste it, made into what `httpUrlSchema` checks (0.17.0,
110
+ * moved here from the web so every client cleans a link the same way):
111
+ * `linkedin.com/in/asha` gains `https://`; blank stays blank. Something that
112
+ * already names a scheme keeps it, so `javascript:` is never quietly "fixed"
113
+ * into a link: the schema then refuses it, as it only takes http and https.
114
+ */
115
+ export function normalizeLinkUrl(raw) {
116
+ const trimmed = raw.trim();
117
+ if (!trimmed)
118
+ return '';
119
+ return /^[a-z][a-z0-9+.-]*:/i.test(trimmed) ? trimmed : `https://${trimmed}`;
120
+ }
97
121
  /**
98
122
  * True when `url` parses and its hostname is EXACTLY one of `hosts`
99
123
  * (lowercase). The one host check in this package, written the way the warning
@@ -126,7 +150,10 @@ export const YOUTUBE_HOSTS = [
126
150
  'youtube.com',
127
151
  'www.youtube.com',
128
152
  'm.youtube.com',
153
+ // 0.18.0: the feed's links too, so one rule plays a post's video and a product's.
154
+ 'music.youtube.com',
129
155
  'youtu.be',
156
+ 'www.youtu.be',
130
157
  // The privacy-mode player's own links (0.7.0).
131
158
  'youtube-nocookie.com',
132
159
  'www.youtube-nocookie.com',
@@ -134,8 +161,9 @@ export const YOUTUBE_HOSTS = [
134
161
  /**
135
162
  * The 11-character id of a YouTube VIDEO link, or null for anything else (a
136
163
  * channel, a playlist, the home page, another site): what a page can embed.
137
- * Reads `youtu.be/<id>`, `/watch?v=<id>`, and `/shorts/`, `/embed/`, `/live/<id>`
138
- * on a `YOUTUBE_HOSTS` host (0.7.0).
164
+ * Reads `youtu.be/<id>`, `/watch?v=<id>`, and `/shorts/`, `/embed/`, `/live/`,
165
+ * `/v/<id>` on a `YOUTUBE_HOSTS` host (0.7.0; `/v/` and the feed's hosts
166
+ * 0.18.0). THE rule for a product's video and a post's embed alike.
139
167
  */
140
168
  export function youtubeVideoId(url) {
141
169
  let u;
@@ -148,11 +176,11 @@ export function youtubeVideoId(url) {
148
176
  const host = u.hostname.toLowerCase();
149
177
  if (!YOUTUBE_HOSTS.includes(host))
150
178
  return null;
151
- const id = host === 'youtu.be'
179
+ const id = host === 'youtu.be' || host === 'www.youtu.be'
152
180
  ? (u.pathname.split('/')[1] ?? null)
153
181
  : u.pathname === '/watch'
154
182
  ? u.searchParams.get('v')
155
- : (/^\/(?:shorts|embed|live)\/([^/]+)/.exec(u.pathname)?.[1] ?? null);
183
+ : (/^\/(?:shorts|embed|live|v)\/([^/]+)/.exec(u.pathname)?.[1] ?? null);
156
184
  return id && /^[A-Za-z0-9_-]{11}$/.test(id) ? id : null;
157
185
  }
158
186
  // ─── Username ────────────────────────────────────────────────────────────────
@@ -343,6 +371,32 @@ export const GOAL_VALUES = [
343
371
  'attend_events',
344
372
  ];
345
373
  export const goalSchema = z.enum(GOAL_VALUES);
374
+ /**
375
+ * True when no value is in the list twice: THE duplicate rule (0.18.0, exported;
376
+ * every list in this package refines with it instead of its own copy).
377
+ */
378
+ export const eachOnce = (items) => new Set(items).size === items.length;
379
+ /**
380
+ * A member's goals as a list (0.17.0): each goal once, at most every goal.
381
+ * ONE rule for onboarding's finish and the Me tab's editor, which both add the
382
+ * minimum (`requiredGoalListSchema`, 0.18.0): two copies of one goal met the
383
+ * minimum while meaning one, and the profile would show it twice.
384
+ */
385
+ export const goalListSchema = z
386
+ .array(goalSchema)
387
+ .max(GOAL_VALUES.length)
388
+ .refine(eachOnce, { error: VALIDATION_MESSAGE.goalsDuplicate });
389
+ /**
390
+ * A member's goals where they are REQUIRED (0.18.0): at least `GOALS_MIN`.
391
+ * ONE schema for onboarding's finish and the Me editor (the user: "we already
392
+ * have at least one goal required"), so the two can never disagree.
393
+ */
394
+ export const requiredGoalListSchema = goalListSchema.min(GOALS_MIN);
395
+ /** A member's skills (0.17.0): each skill once, at most `SKILLS_MAX`. ONE rule for onboarding and Me. */
396
+ export const skillListSchema = z
397
+ .array(skillSchema)
398
+ .max(SKILLS_MAX)
399
+ .refine(eachOnce, { error: VALIDATION_MESSAGE.skillsDuplicate });
346
400
  // ─── Gender ──────────────────────────────────────────────────────────────────
347
401
  /**
348
402
  * Demographic only. Nothing here computes anything from it, unlike the sibling
@@ -355,14 +409,27 @@ export const goalSchema = z.enum(GOAL_VALUES);
355
409
  export const GENDER_VALUES = ['male', 'female', 'other', 'prefer_not_to_say'];
356
410
  export const genderSchema = z.enum(GENDER_VALUES);
357
411
  // ─── Display name ────────────────────────────────────────────────────────────
412
+ /**
413
+ * A full name as it is sent and stored (0.17.0, moved here from the web):
414
+ * trimmed, and every run of spaces made one. `displayNameSchema` applies it,
415
+ * so the server stores it from every client; a client applies it to show the
416
+ * member what will be kept. What `splitFullName` reads.
417
+ */
418
+ export const tidyFullName = (fullName) => fullName.trim().replace(/\s+/g, ' ');
358
419
  /**
359
420
  * The name a member is shown by. ONE rule for every body that writes it:
360
421
  * onboarding's finish, the profile patch and the personal-details patch, so a
361
- * name one accepts the others can never refuse. Plain bounds, no custom
362
- * message: `too_small` with minimum 1 already says "required", and the client
363
- * words it.
422
+ * name one accepts the others can never refuse, and each stores it TIDIED
423
+ * (`tidyFullName`, 0.17.0: "Asha Rao" from the Me editor was stored as
424
+ * typed, while onboarding sent it tidy). Plain bounds, no custom message:
425
+ * `too_small` with minimum 1 already says "required", and the client words it.
364
426
  */
365
- export const displayNameSchema = z.string().trim().min(1).max(DISPLAY_NAME_MAX);
427
+ export const displayNameSchema = z
428
+ .string()
429
+ .trim()
430
+ .min(1)
431
+ .max(DISPLAY_NAME_MAX)
432
+ .transform(tidyFullName);
366
433
  /**
367
434
  * A full name's first and last name, DEDUCED, never asked (the user,
368
435
  * 2026-10-18: onboarding asks one "Full name" and works the two out from the
@@ -378,13 +445,15 @@ export const displayNameSchema = z.string().trim().min(1).max(DISPLAY_NAME_MAX);
378
445
  * leave a stale first name behind.
379
446
  */
380
447
  export function splitFullName(fullName) {
381
- const name = fullName.trim().replace(/\s+/g, ' ');
448
+ const name = tidyFullName(fullName);
382
449
  const space = name.indexOf(' ');
383
450
  return space === -1
384
451
  ? { firstName: name, lastName: '' }
385
452
  : { firstName: name.slice(0, space), lastName: name.slice(space + 1) };
386
453
  }
387
454
  // ─── Phone ───────────────────────────────────────────────────────────────────
455
+ /** The digits an Indian mobile number has: what `phoneSchema` takes and the field keeps. */
456
+ export const PHONE_DIGITS = 10;
388
457
  /**
389
458
  * Indian mobile number: ten raw digits, no country code, matching the standard
390
459
  * used across this family. Display adds the `+91`.
@@ -397,7 +466,23 @@ export function splitFullName(fullName) {
397
466
  export const phoneSchema = z
398
467
  .string()
399
468
  .trim()
400
- .regex(/^[6-9]\d{9}$/, VALIDATION_MESSAGE.phoneInvalid);
469
+ .regex(new RegExp(`^[6-9]\\d{${PHONE_DIGITS - 1}}$`), VALIDATION_MESSAGE.phoneInvalid);
470
+ /**
471
+ * An Indian mobile number as `phoneSchema`'s ten digits, from however it was
472
+ * typed (0.17.0, moved here from the web): `98765 43210`, `+91 98765-43210`,
473
+ * `091 9876543210`. Anything else comes back digits only and fails the schema
474
+ * with `phoneInvalid`, rather than being guessed at.
475
+ */
476
+ export function normalizePhone(raw) {
477
+ let digits = raw.replace(/\D/g, '');
478
+ if (digits.length === PHONE_DIGITS + 2 && digits.startsWith('91'))
479
+ digits = digits.slice(2);
480
+ else if (digits.length === PHONE_DIGITS + 1 && digits.startsWith('0'))
481
+ digits = digits.slice(1);
482
+ else if (digits.length === PHONE_DIGITS + 3 && digits.startsWith('091'))
483
+ digits = digits.slice(3);
484
+ return digits;
485
+ }
401
486
  // ─── Location ────────────────────────────────────────────────────────────────
402
487
  /**
403
488
  * Where the founder is based. City and country are what Discover filters on.
@@ -432,13 +517,6 @@ export const coordinatesQuerySchema = z.object({
432
517
  lat: z.preprocess(emptyStringToUndefined, z.coerce.number().min(-90).max(90)),
433
518
  lng: z.preprocess(emptyStringToUndefined, z.coerce.number().min(-180).max(180)),
434
519
  });
435
- /**
436
- * The city these coordinates look like, for the member to CONFIRM, or `null`
437
- * when the lookup could not place them confidently (or they are outside India,
438
- * whose area data is all there is). A suggestion, never a write: only what the
439
- * member then submits becomes their location.
440
- */
441
- export const suggestedLocationSchema = locationSchema.nullable();
442
520
  // ─── Pagination ──────────────────────────────────────────────────────────────
443
521
  /**
444
522
  * Cursor pagination query. Feed and Discover both use it.
@@ -454,6 +532,11 @@ export const paginationQuerySchema = z.object({
454
532
  * `.min(1)`: the member sees a 400 for a page they never asked to size.
455
533
  */
456
534
  limit: z.preprocess(emptyStringToUndefined, z.coerce.number().int().min(1).max(PAGE_SIZE_MAX).default(PAGE_SIZE_DEFAULT)),
457
- cursor: z.string().trim().min(1).optional(),
535
+ /**
536
+ * The opaque cursor a page answered with. Empty means "not provided" (the
537
+ * same as `limit`, 0.18.0: an emptied control answered 400), and it is
538
+ * bounded: the server's cursors are short, so a long one is not one of them.
539
+ */
540
+ cursor: z.preprocess(emptyStringToUndefined, z.string().trim().min(1).max(CURSOR_MAX).optional()),
458
541
  });
459
542
  //# sourceMappingURL=common.js.map