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.
- package/README.md +263 -19
- package/dist/access/access.d.ts +1 -1
- package/dist/access/access.d.ts.map +1 -1
- package/dist/access/access.js +4 -1
- package/dist/access/access.js.map +1 -1
- package/dist/access/permissions.d.ts +27 -29
- package/dist/access/permissions.d.ts.map +1 -1
- package/dist/access/permissions.js +39 -3
- package/dist/access/permissions.js.map +1 -1
- package/dist/core/common.d.ts +117 -23
- package/dist/core/common.d.ts.map +1 -1
- package/dist/core/common.js +104 -21
- package/dist/core/common.js.map +1 -1
- package/dist/core/errors.d.ts +7 -5
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +17 -11
- package/dist/core/errors.js.map +1 -1
- package/dist/core/limits.d.ts +55 -17
- package/dist/core/limits.d.ts.map +1 -1
- package/dist/core/limits.js +59 -22
- package/dist/core/limits.js.map +1 -1
- package/dist/core/money.d.ts +33 -0
- package/dist/core/money.d.ts.map +1 -0
- package/dist/core/money.js +36 -0
- package/dist/core/money.js.map +1 -0
- package/dist/core/responses.d.ts +14 -4
- package/dist/core/responses.d.ts.map +1 -1
- package/dist/core/responses.js.map +1 -1
- package/dist/core/validation.d.ts +32 -2
- package/dist/core/validation.d.ts.map +1 -1
- package/dist/core/validation.js +32 -2
- package/dist/core/validation.js.map +1 -1
- package/dist/domains/billing/billing.d.ts +61 -8
- package/dist/domains/billing/billing.d.ts.map +1 -1
- package/dist/domains/billing/billing.js +58 -22
- package/dist/domains/billing/billing.js.map +1 -1
- package/dist/domains/billing/gst.d.ts.map +1 -1
- package/dist/domains/billing/gst.js +4 -3
- package/dist/domains/billing/gst.js.map +1 -1
- package/dist/domains/chat/chat.d.ts +0 -21
- package/dist/domains/chat/chat.d.ts.map +1 -1
- package/dist/domains/chat/chat.js +0 -20
- package/dist/domains/chat/chat.js.map +1 -1
- package/dist/domains/communities/communities.d.ts +30 -12
- package/dist/domains/communities/communities.d.ts.map +1 -1
- package/dist/domains/communities/communities.js +43 -15
- package/dist/domains/communities/communities.js.map +1 -1
- package/dist/domains/contributions/contributions.d.ts +1 -1
- package/dist/domains/contributions/contributions.js +1 -1
- package/dist/domains/contributions/contributions.js.map +1 -1
- package/dist/domains/dashboard/dashboard.d.ts +3 -1
- package/dist/domains/dashboard/dashboard.d.ts.map +1 -1
- package/dist/domains/dashboard/dashboard.js +23 -7
- package/dist/domains/dashboard/dashboard.js.map +1 -1
- package/dist/domains/discover/discover.d.ts +8 -8
- package/dist/domains/feed/feed.d.ts +55 -153
- package/dist/domains/feed/feed.d.ts.map +1 -1
- package/dist/domains/feed/feed.js +53 -58
- package/dist/domains/feed/feed.js.map +1 -1
- package/dist/domains/location/india.d.ts +215 -0
- package/dist/domains/location/india.d.ts.map +1 -0
- package/dist/domains/location/india.js +1203 -0
- package/dist/domains/location/india.js.map +1 -0
- package/dist/domains/members/members.d.ts +2 -2
- package/dist/domains/membership/membership.d.ts +26 -0
- package/dist/domains/membership/membership.d.ts.map +1 -1
- package/dist/domains/membership/membership.js +55 -1
- package/dist/domains/membership/membership.js.map +1 -1
- package/dist/domains/moderation/moderation.d.ts +2 -2
- package/dist/domains/moderation/moderation.js +3 -3
- package/dist/domains/moderation/moderation.js.map +1 -1
- package/dist/domains/notifications/notifications.d.ts +1 -10
- package/dist/domains/notifications/notifications.d.ts.map +1 -1
- package/dist/domains/notifications/notifications.js +2 -3
- package/dist/domains/notifications/notifications.js.map +1 -1
- package/dist/domains/onboarding/onboarding-answers.d.ts +112 -0
- package/dist/domains/onboarding/onboarding-answers.d.ts.map +1 -0
- package/dist/domains/onboarding/onboarding-answers.js +164 -0
- package/dist/domains/onboarding/onboarding-answers.js.map +1 -0
- package/dist/domains/onboarding/onboarding.d.ts +398 -62
- package/dist/domains/onboarding/onboarding.d.ts.map +1 -1
- package/dist/domains/onboarding/onboarding.js +229 -68
- package/dist/domains/onboarding/onboarding.js.map +1 -1
- package/dist/domains/products/products.d.ts +43 -8
- package/dist/domains/products/products.d.ts.map +1 -1
- package/dist/domains/products/products.js +57 -11
- package/dist/domains/products/products.js.map +1 -1
- package/dist/domains/profile/founder-profile.d.ts +120 -68
- package/dist/domains/profile/founder-profile.d.ts.map +1 -1
- package/dist/domains/profile/founder-profile.js +80 -29
- package/dist/domains/profile/founder-profile.js.map +1 -1
- package/dist/domains/profile/my-profile.d.ts +3 -3
- package/dist/domains/reviews/reviews.d.ts +1 -1
- package/dist/domains/reviews/reviews.d.ts.map +1 -1
- package/dist/domains/reviews/reviews.js +2 -2
- package/dist/domains/reviews/reviews.js.map +1 -1
- package/dist/domains/support/support.d.ts +22 -3
- package/dist/domains/support/support.d.ts.map +1 -1
- package/dist/domains/support/support.js +19 -5
- package/dist/domains/support/support.js.map +1 -1
- package/dist/domains/team/team.d.ts.map +1 -1
- package/dist/domains/team/team.js +3 -6
- package/dist/domains/team/team.js.map +1 -1
- package/dist/domains/uploads/uploads.d.ts +7 -22
- package/dist/domains/uploads/uploads.d.ts.map +1 -1
- package/dist/domains/uploads/uploads.js +7 -21
- package/dist/domains/uploads/uploads.js.map +1 -1
- package/dist/env.d.ts +26 -13
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +58 -23
- package/dist/env.js.map +1 -1
- package/dist/index.d.ts +18 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -14
- package/dist/index.js.map +1 -1
- 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"]}
|
package/dist/core/common.d.ts
CHANGED
|
@@ -16,8 +16,14 @@ import { z } from 'zod';
|
|
|
16
16
|
* alone. Jansathi shipped that bug in dating onboarding.
|
|
17
17
|
*/
|
|
18
18
|
export declare const emptyStringToUndefined: (value: unknown) => unknown;
|
|
19
|
-
/**
|
|
20
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Optional trimmed string: a blank one (`''`, spaces) is absent (`undefined`).
|
|
21
|
+
* Trimmed, checked, then blank dropped, never a preprocess (0.18.0: a
|
|
22
|
+
* preprocess types its INPUT as `unknown`, so a caller typing a body by
|
|
23
|
+
* `z.input` could send a number and still compile). `.optional()` OUTSIDE, so
|
|
24
|
+
* the key stays optional in the output type as well as the input.
|
|
25
|
+
*/
|
|
26
|
+
export declare const optionalTrimmedString: (maxLength?: number) => z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string | undefined, string>>>;
|
|
21
27
|
/**
|
|
22
28
|
* A repeated query param (`?skill=growth&skill=sales`) or a single value,
|
|
23
29
|
* always as an array, at most `max` values.
|
|
@@ -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
|
|
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
|
|
288
|
-
*
|
|
289
|
-
*
|
|
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.
|
|
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":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/core/common.ts"],"names":[],"mappings":"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"}
|
package/dist/core/common.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @module founder-schema/core/common
|
|
8
8
|
*/
|
|
9
9
|
import { z } from 'zod';
|
|
10
|
-
import { CITY_MAX, COUNTRY_MAX, DISPLAY_NAME_MAX, LINK_URL_MAX, PAGE_SIZE_DEFAULT, PAGE_SIZE_MAX, STATE_MAX, } from './limits.js';
|
|
10
|
+
import { CITY_MAX, COUNTRY_MAX, CURSOR_MAX, DISPLAY_NAME_MAX, GOALS_MIN, LINK_URL_MAX, PAGE_SIZE_DEFAULT, PAGE_SIZE_MAX, SKILLS_MAX, STATE_MAX, } from './limits.js';
|
|
11
11
|
import { VALIDATION_MESSAGE } from './validation.js';
|
|
12
12
|
// ─── String helpers ──────────────────────────────────────────────────────────
|
|
13
13
|
/**
|
|
@@ -23,8 +23,19 @@ export const emptyStringToUndefined = (value) => {
|
|
|
23
23
|
return undefined;
|
|
24
24
|
return value;
|
|
25
25
|
};
|
|
26
|
-
/**
|
|
27
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Optional trimmed string: a blank one (`''`, spaces) is absent (`undefined`).
|
|
28
|
+
* Trimmed, checked, then blank dropped, never a preprocess (0.18.0: a
|
|
29
|
+
* preprocess types its INPUT as `unknown`, so a caller typing a body by
|
|
30
|
+
* `z.input` could send a number and still compile). `.optional()` OUTSIDE, so
|
|
31
|
+
* the key stays optional in the output type as well as the input.
|
|
32
|
+
*/
|
|
33
|
+
export const optionalTrimmedString = (maxLength = 100) => z
|
|
34
|
+
.string()
|
|
35
|
+
.trim()
|
|
36
|
+
.max(maxLength)
|
|
37
|
+
.transform((value) => value || undefined)
|
|
38
|
+
.optional();
|
|
28
39
|
/**
|
|
29
40
|
* A repeated query param (`?skill=growth&skill=sales`) or a single value,
|
|
30
41
|
* always as an array, at most `max` values.
|
|
@@ -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
|
|
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
|
|
362
|
-
*
|
|
363
|
-
*
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|