founder-schema 0.19.0 → 0.20.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 (142) hide show
  1. package/README.md +46 -0
  2. package/dist/access/permissions.d.ts +3 -3
  3. package/dist/access/permissions.d.ts.map +1 -1
  4. package/dist/access/permissions.js.map +1 -1
  5. package/dist/core/common.d.ts +42 -15
  6. package/dist/core/common.d.ts.map +1 -1
  7. package/dist/core/common.js +58 -15
  8. package/dist/core/common.js.map +1 -1
  9. package/dist/core/deletion.d.ts +40 -0
  10. package/dist/core/deletion.d.ts.map +1 -0
  11. package/dist/core/deletion.js +39 -0
  12. package/dist/core/deletion.js.map +1 -0
  13. package/dist/domains/audit/audit.d.ts +36 -12
  14. package/dist/domains/audit/audit.d.ts.map +1 -1
  15. package/dist/domains/audit/audit.js +9 -10
  16. package/dist/domains/audit/audit.js.map +1 -1
  17. package/dist/domains/billing/billing.d.ts +29 -13
  18. package/dist/domains/billing/billing.d.ts.map +1 -1
  19. package/dist/domains/billing/billing.js +2 -3
  20. package/dist/domains/billing/billing.js.map +1 -1
  21. package/dist/domains/chat/chat.d.ts +19 -5
  22. package/dist/domains/chat/chat.d.ts.map +1 -1
  23. package/dist/domains/chat/chat.js +10 -10
  24. package/dist/domains/chat/chat.js.map +1 -1
  25. package/dist/domains/communities/communities.d.ts +95 -57
  26. package/dist/domains/communities/communities.d.ts.map +1 -1
  27. package/dist/domains/contributions/contributions.d.ts +28 -12
  28. package/dist/domains/contributions/contributions.d.ts.map +1 -1
  29. package/dist/domains/contributions/contributions.js +3 -0
  30. package/dist/domains/contributions/contributions.js.map +1 -1
  31. package/dist/domains/discover/discover.d.ts +2 -2
  32. package/dist/domains/feed/feed.d.ts +111 -63
  33. package/dist/domains/feed/feed.d.ts.map +1 -1
  34. package/dist/domains/feed/feed.js +3 -0
  35. package/dist/domains/feed/feed.js.map +1 -1
  36. package/dist/domains/location/areas.d.ts +7 -0
  37. package/dist/domains/location/areas.d.ts.map +1 -1
  38. package/dist/domains/location/areas.js +3 -0
  39. package/dist/domains/location/areas.js.map +1 -1
  40. package/dist/domains/media/media.d.ts +22 -0
  41. package/dist/domains/media/media.d.ts.map +1 -1
  42. package/dist/domains/media/media.js +20 -0
  43. package/dist/domains/media/media.js.map +1 -1
  44. package/dist/domains/members/members.d.ts +26 -9
  45. package/dist/domains/members/members.d.ts.map +1 -1
  46. package/dist/domains/members/members.js +9 -10
  47. package/dist/domains/members/members.js.map +1 -1
  48. package/dist/domains/membership/membership.d.ts +19 -0
  49. package/dist/domains/membership/membership.d.ts.map +1 -1
  50. package/dist/domains/membership/membership.js +10 -0
  51. package/dist/domains/membership/membership.js.map +1 -1
  52. package/dist/domains/moderation/moderation.d.ts +8 -2
  53. package/dist/domains/moderation/moderation.d.ts.map +1 -1
  54. package/dist/domains/moderation/moderation.js +3 -6
  55. package/dist/domains/moderation/moderation.js.map +1 -1
  56. package/dist/domains/notifications/notifications.d.ts +10 -6
  57. package/dist/domains/notifications/notifications.d.ts.map +1 -1
  58. package/dist/domains/onboarding/onboarding-answers.js +1 -1
  59. package/dist/domains/onboarding/onboarding-answers.js.map +1 -1
  60. package/dist/domains/onboarding/onboarding.d.ts +11 -10
  61. package/dist/domains/onboarding/onboarding.d.ts.map +1 -1
  62. package/dist/domains/onboarding/onboarding.js +16 -9
  63. package/dist/domains/onboarding/onboarding.js.map +1 -1
  64. package/dist/domains/people/person.d.ts +54 -0
  65. package/dist/domains/people/person.d.ts.map +1 -0
  66. package/dist/domains/people/person.js +73 -0
  67. package/dist/domains/people/person.js.map +1 -0
  68. package/dist/domains/products/products.d.ts +62 -33
  69. package/dist/domains/products/products.d.ts.map +1 -1
  70. package/dist/domains/products/products.js +3 -0
  71. package/dist/domains/products/products.js.map +1 -1
  72. package/dist/domains/profile/founder-profile.d.ts +36 -19
  73. package/dist/domains/profile/founder-profile.d.ts.map +1 -1
  74. package/dist/domains/profile/founder-profile.js +43 -21
  75. package/dist/domains/profile/founder-profile.js.map +1 -1
  76. package/dist/domains/reviews/reviews.d.ts +25 -15
  77. package/dist/domains/reviews/reviews.d.ts.map +1 -1
  78. package/dist/domains/support/support.d.ts +47 -10
  79. package/dist/domains/support/support.d.ts.map +1 -1
  80. package/dist/domains/support/support.js +4 -2
  81. package/dist/domains/support/support.js.map +1 -1
  82. package/dist/domains/team/team.d.ts +9 -3
  83. package/dist/domains/team/team.d.ts.map +1 -1
  84. package/dist/domains/team/team.js +3 -6
  85. package/dist/domains/team/team.js.map +1 -1
  86. package/dist/index.d.ts +11 -9
  87. package/dist/index.d.ts.map +1 -1
  88. package/dist/index.js +13 -9
  89. package/dist/index.js.map +1 -1
  90. package/package.json +3 -2
  91. package/dist/account.d.ts +0 -40
  92. package/dist/account.d.ts.map +0 -1
  93. package/dist/account.js +0 -39
  94. package/dist/account.js.map +0 -1
  95. package/dist/chat.d.ts +0 -187
  96. package/dist/chat.d.ts.map +0 -1
  97. package/dist/chat.js +0 -189
  98. package/dist/chat.js.map +0 -1
  99. package/dist/common.d.ts +0 -304
  100. package/dist/common.d.ts.map +0 -1
  101. package/dist/common.js +0 -340
  102. package/dist/common.js.map +0 -1
  103. package/dist/discover.d.ts +0 -245
  104. package/dist/discover.d.ts.map +0 -1
  105. package/dist/discover.js +0 -68
  106. package/dist/discover.js.map +0 -1
  107. package/dist/errors.d.ts +0 -85
  108. package/dist/errors.d.ts.map +0 -1
  109. package/dist/errors.js +0 -89
  110. package/dist/errors.js.map +0 -1
  111. package/dist/feed.d.ts +0 -1658
  112. package/dist/feed.d.ts.map +0 -1
  113. package/dist/feed.js +0 -623
  114. package/dist/feed.js.map +0 -1
  115. package/dist/founder-profile.d.ts +0 -387
  116. package/dist/founder-profile.d.ts.map +0 -1
  117. package/dist/founder-profile.js +0 -180
  118. package/dist/founder-profile.js.map +0 -1
  119. package/dist/limits.d.ts +0 -76
  120. package/dist/limits.d.ts.map +0 -1
  121. package/dist/limits.js +0 -84
  122. package/dist/limits.js.map +0 -1
  123. package/dist/onboarding.d.ts +0 -1018
  124. package/dist/onboarding.d.ts.map +0 -1
  125. package/dist/onboarding.js +0 -717
  126. package/dist/onboarding.js.map +0 -1
  127. package/dist/responses/common.d.ts +0 -167
  128. package/dist/responses/common.d.ts.map +0 -1
  129. package/dist/responses/common.js +0 -67
  130. package/dist/responses/common.js.map +0 -1
  131. package/dist/responses/index.d.ts +0 -7
  132. package/dist/responses/index.d.ts.map +0 -1
  133. package/dist/responses/index.js +0 -7
  134. package/dist/responses/index.js.map +0 -1
  135. package/dist/uploads.d.ts +0 -113
  136. package/dist/uploads.d.ts.map +0 -1
  137. package/dist/uploads.js +0 -106
  138. package/dist/uploads.js.map +0 -1
  139. package/dist/validation.d.ts +0 -136
  140. package/dist/validation.d.ts.map +0 -1
  141. package/dist/validation.js +0 -142
  142. package/dist/validation.js.map +0 -1
@@ -64,12 +64,17 @@ export declare const memberDirectoryQuerySchema: z.ZodObject<{
64
64
  }, z.core.$strip>;
65
65
  export type MemberDirectoryQuery = z.infer<typeof memberDirectoryQuerySchema>;
66
66
  export type MemberDirectoryQueryInput = z.input<typeof memberDirectoryQuerySchema>;
67
- /** One row of the directory. */
67
+ /**
68
+ * One row of the directory: the person (0.20.0, `personSchema`, so `userId`
69
+ * where it was `id`) and what staff see beside them.
70
+ */
68
71
  export declare const memberSummarySchema: z.ZodObject<{
69
- id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
70
- displayName: z.ZodString;
72
+ userId: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
71
73
  username: z.ZodNullable<z.ZodString>;
74
+ displayName: z.ZodString;
75
+ firstName: z.ZodString;
72
76
  avatarUrl: z.ZodNullable<z.ZodURL>;
77
+ former: z.ZodBoolean;
73
78
  email: z.ZodNullable<z.ZodString>;
74
79
  status: z.ZodEnum<{
75
80
  active: "active";
@@ -87,10 +92,12 @@ export declare const memberSummarySchema: z.ZodObject<{
87
92
  export type MemberSummary = z.infer<typeof memberSummarySchema>;
88
93
  export declare const memberDirectoryPageSchema: z.ZodObject<{
89
94
  items: z.ZodArray<z.ZodObject<{
90
- id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
91
- displayName: z.ZodString;
95
+ userId: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
92
96
  username: z.ZodNullable<z.ZodString>;
97
+ displayName: z.ZodString;
98
+ firstName: z.ZodString;
93
99
  avatarUrl: z.ZodNullable<z.ZodURL>;
100
+ former: z.ZodBoolean;
94
101
  email: z.ZodNullable<z.ZodString>;
95
102
  status: z.ZodEnum<{
96
103
  active: "active";
@@ -113,17 +120,23 @@ export declare const suspensionSchema: z.ZodObject<{
113
120
  since: z.ZodISODateTime;
114
121
  reason: z.ZodString;
115
122
  by: z.ZodNullable<z.ZodObject<{
116
- id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
123
+ userId: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
124
+ username: z.ZodNullable<z.ZodString>;
117
125
  displayName: z.ZodString;
126
+ firstName: z.ZodString;
127
+ avatarUrl: z.ZodNullable<z.ZodURL>;
128
+ former: z.ZodBoolean;
118
129
  }, z.core.$strip>>;
119
130
  }, z.core.$strip>;
120
131
  export type Suspension = z.infer<typeof suspensionSchema>;
121
132
  /** One member, as staff see them. */
122
133
  export declare const memberDetailSchema: z.ZodObject<{
123
- id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
124
- displayName: z.ZodString;
134
+ userId: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
125
135
  username: z.ZodNullable<z.ZodString>;
136
+ displayName: z.ZodString;
137
+ firstName: z.ZodString;
126
138
  avatarUrl: z.ZodNullable<z.ZodURL>;
139
+ former: z.ZodBoolean;
127
140
  email: z.ZodNullable<z.ZodString>;
128
141
  status: z.ZodEnum<{
129
142
  active: "active";
@@ -150,8 +163,12 @@ export declare const memberDetailSchema: z.ZodObject<{
150
163
  since: z.ZodISODateTime;
151
164
  reason: z.ZodString;
152
165
  by: z.ZodNullable<z.ZodObject<{
153
- id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
166
+ userId: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
167
+ username: z.ZodNullable<z.ZodString>;
154
168
  displayName: z.ZodString;
169
+ firstName: z.ZodString;
170
+ avatarUrl: z.ZodNullable<z.ZodURL>;
171
+ former: z.ZodBoolean;
155
172
  }, z.core.$strip>>;
156
173
  }, z.core.$strip>>;
157
174
  viewer: z.ZodObject<{
@@ -1 +1 @@
1
- {"version":3,"file":"members.d.ts","sourceRoot":"","sources":["../../../src/domains/members/members.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAmBxB,iFAAiF;AACjF,eAAO,MAAM,oBAAoB,kCAAmC,CAAC;AACrE,eAAO,MAAM,kBAAkB;;;EAA+B,CAAC;AAC/D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,sCAAsC;AACtC,eAAO,MAAM,kBAAkB;;iBAAyC,CAAC;AACzE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D;;;;GAIG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;iBAKrC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAC9E,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAEnF,gCAAgC;AAChC,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;iBAa9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;iBAAuC,CAAC;AAC9E,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E,6BAA6B;AAC7B,eAAO,MAAM,gBAAgB;;;;;;;iBAK3B,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,qCAAqC;AACrC,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiD7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,sFAAsF;AACtF,eAAO,MAAM,uBAAuB;;iBAElC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC"}
1
+ {"version":3,"file":"members.d.ts","sourceRoot":"","sources":["../../../src/domains/members/members.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAmBxB,iFAAiF;AACjF,eAAO,MAAM,oBAAoB,kCAAmC,CAAC;AACrE,eAAO,MAAM,kBAAkB;;;EAA+B,CAAC;AAC/D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,sCAAsC;AACtC,eAAO,MAAM,kBAAkB;;iBAAyC,CAAC;AACzE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D;;;;GAIG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;iBAKrC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAC9E,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAEnF;;;GAGG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;iBAQ9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;iBAAuC,CAAC;AAC9E,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E,6BAA6B;AAC7B,eAAO,MAAM,gBAAgB;;;;;;;;;;;iBAK3B,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,qCAAqC;AACrC,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiD7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,sFAAsF;AACtF,eAAO,MAAM,uBAAuB;;iBAElC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC"}
@@ -33,10 +33,11 @@ import { z } from 'zod';
33
33
  import { capabilitySchema } from '../../access/capabilities.js';
34
34
  import { platformRoleSchema } from '../../access/roles.js';
35
35
  import { standingSchema } from '../../access/standing.js';
36
- import { emptyStringToUndefined, objectIdSchema, optionalTrimmedString, paginationQuerySchema, usernameSchema, } from '../../core/common.js';
36
+ import { emptyStringToUndefined, objectIdSchema, optionalTrimmedString, paginationQuerySchema, } from '../../core/common.js';
37
37
  import { MEMBER_SEARCH_MAX, SUSPENSION_REASON_MAX, SUSPENSION_REASON_MIN, } from '../../core/limits.js';
38
38
  import { paginatedSchema } from '../../core/responses.js';
39
39
  import { membershipSchema, planTierSchema } from '../membership/membership.js';
40
+ import { personSchema } from '../people/person.js';
40
41
  /** What staff can see an account's state as. A deleted account is not listed. */
41
42
  export const MEMBER_STATUS_VALUES = ['active', 'suspended'];
42
43
  export const memberStatusSchema = z.enum(MEMBER_STATUS_VALUES);
@@ -53,13 +54,11 @@ export const memberDirectoryQuerySchema = paginationQuerySchema.extend({
53
54
  /** Only people holding this staff role. */
54
55
  role: z.preprocess(emptyStringToUndefined, platformRoleSchema.optional()),
55
56
  });
56
- /** One row of the directory. */
57
- export const memberSummarySchema = z.object({
58
- id: objectIdSchema,
59
- displayName: z.string(),
60
- /** Null when they have no founder profile (never finished onboarding). */
61
- username: usernameSchema.nullable(),
62
- avatarUrl: z.url().nullable(),
57
+ /**
58
+ * One row of the directory: the person (0.20.0, `personSchema`, so `userId`
59
+ * where it was `id`) and what staff see beside them.
60
+ */
61
+ export const memberSummarySchema = personSchema.extend({
63
62
  /** Null when the asking role may not see it. */
64
63
  email: z.string().nullable(),
65
64
  status: memberStatusSchema,
@@ -73,8 +72,8 @@ export const memberDirectoryPageSchema = paginatedSchema(memberSummarySchema);
73
72
  export const suspensionSchema = z.object({
74
73
  since: z.iso.datetime(),
75
74
  reason: z.string(),
76
- /** Who suspended them, by name at the time. */
77
- by: z.object({ id: objectIdSchema, displayName: z.string() }).nullable(),
75
+ /** Who suspended them (0.20.0: the person, live); null when a script did. */
76
+ by: personSchema.nullable(),
78
77
  });
79
78
  /** One member, as staff see them. */
80
79
  export const memberDetailSchema = memberSummarySchema.extend({
@@ -1 +1 @@
1
- {"version":3,"file":"members.js","sourceRoot":"","sources":["../../../src/domains/members/members.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema: the staff member directory\n *\n * GET /api/v1/members ?q&status&role&limit&cursor\n * GET /api/v1/members/:memberId one member, as staff see them\n * POST /api/v1/members/:memberId/suspension { reason } suspend\n * DELETE /api/v1/members/:memberId/suspension lift it\n *\n * Every staff role has a Members tab: a moderator looks up who wrote\n * something, a support agent finds the person who wrote in, a manager decides\n * on a suspension. What each sees is the server's call, per field: a\n * moderator gets no email (`email: null`), because finding a member's\n * address is support's job, not moderation's.\n *\n * A SUSPENDED member cannot use the product at all, on any device, from the\n * next request on (every call answers `ACCOUNT_SUSPENDED`); their posts stay\n * up unless moderated separately, and a staff role they were acting in is\n * switched off. Lifting it restores everything else.\n *\n * SEARCH (`q`) finds people by the start of their name or handle among listed\n * founders, or by their exact email among everyone (only for a role that may\n * see emails: a moderator's email search finds nothing). It returns up to\n * `limit` matches in one page (type more to narrow), not a pageable list. Who may suspend whom is the access policy's: never\n * yourself, never a superadmin, and a manager never a fellow manager.\n *\n * Each detail carries a `viewer` block of what the asking staff member may do\n * to THIS member, worked out by the server, so the screen shows only the\n * buttons that will work.\n *\n * @module founder-schema/domains/members\n */\n\nimport { z } from 'zod';\nimport { capabilitySchema } from '../../access/capabilities.js';\nimport { platformRoleSchema } from '../../access/roles.js';\nimport { standingSchema } from '../../access/standing.js';\nimport {\n emptyStringToUndefined,\n objectIdSchema,\n optionalTrimmedString,\n paginationQuerySchema,\n usernameSchema,\n} from '../../core/common.js';\nimport {\n MEMBER_SEARCH_MAX,\n SUSPENSION_REASON_MAX,\n SUSPENSION_REASON_MIN,\n} from '../../core/limits.js';\nimport { paginatedSchema } from '../../core/responses.js';\nimport { membershipSchema, planTierSchema } from '../membership/membership.js';\n\n/** What staff can see an account's state as. A deleted account is not listed. */\nexport const MEMBER_STATUS_VALUES = ['active', 'suspended'] as const;\nexport const memberStatusSchema = z.enum(MEMBER_STATUS_VALUES);\nexport type MemberStatus = z.infer<typeof memberStatusSchema>;\n\n/** `:memberId` in the paths above. */\nexport const memberParamsSchema = z.object({ memberId: objectIdSchema });\nexport type MemberParams = z.infer<typeof memberParamsSchema>;\n\n/**\n * The directory's query. `q` is a name or handle prefix, or a whole email\n * address (anything with an `@` is matched as an email, exactly). An emptied\n * box is \"no search\", not an error.\n */\nexport const memberDirectoryQuerySchema = paginationQuerySchema.extend({\n q: optionalTrimmedString(MEMBER_SEARCH_MAX),\n status: z.preprocess(emptyStringToUndefined, memberStatusSchema.optional()),\n /** Only people holding this staff role. */\n role: z.preprocess(emptyStringToUndefined, platformRoleSchema.optional()),\n});\nexport type MemberDirectoryQuery = z.infer<typeof memberDirectoryQuerySchema>;\nexport type MemberDirectoryQueryInput = z.input<typeof memberDirectoryQuerySchema>;\n\n/** One row of the directory. */\nexport const memberSummarySchema = z.object({\n id: objectIdSchema,\n displayName: z.string(),\n /** Null when they have no founder profile (never finished onboarding). */\n username: usernameSchema.nullable(),\n avatarUrl: z.url().nullable(),\n /** Null when the asking role may not see it. */\n email: z.string().nullable(),\n status: memberStatusSchema,\n /** Staff roles held, highest first. Empty for an ordinary member. */\n roles: z.array(platformRoleSchema),\n joinedAt: z.iso.datetime(),\n lastActiveAt: z.iso.datetime().nullable(),\n});\nexport type MemberSummary = z.infer<typeof memberSummarySchema>;\n\nexport const memberDirectoryPageSchema = paginatedSchema(memberSummarySchema);\nexport type MemberDirectoryPage = z.infer<typeof memberDirectoryPageSchema>;\n\n/** A suspension in force. */\nexport const suspensionSchema = z.object({\n since: z.iso.datetime(),\n reason: z.string(),\n /** Who suspended them, by name at the time. */\n by: z.object({ id: objectIdSchema, displayName: z.string() }).nullable(),\n});\nexport type Suspension = z.infer<typeof suspensionSchema>;\n\n/** One member, as staff see them. */\nexport const memberDetailSchema = memberSummarySchema.extend({\n headline: z.string().nullable(),\n /** Their city, as declared on their profile. */\n city: z.string().nullable(),\n counts: z.object({\n posts: z.number().int().min(0),\n polls: z.number().int().min(0),\n comments: z.number().int().min(0),\n /** Reports filed about their posts, polls and comments that are still up. */\n reportsAgainst: z.number().int().min(0),\n /** Tickets they have raised. */\n tickets: z.number().int().min(0),\n }),\n suspension: suspensionSchema.nullable(),\n /** What the asking staff member may do to this member. */\n viewer: z.object({\n canSuspend: z.boolean(),\n /** Roles the asker may grant or revoke on this member (never their own). */\n grantableRoles: z.array(platformRoleSchema),\n /** May clear this member's forgotten MPIN (superadmin, someone else). */\n canResetMpin: z.boolean(),\n /** Capabilities the asker may grant or revoke on this member (never their own) (0.9.0). */\n grantableCapabilities: z.array(capabilitySchema),\n /** May give them a membership (0.12.0): they hold none, and the asker may, and it is not themselves. */\n canGrantMembership: z.boolean(),\n /**\n * May take their membership away (0.12.0): founding, coupon or granted\n * (a paid one is refunded instead), and the asker may, and not their own.\n */\n canRevokeMembership: z.boolean(),\n /** May list their payments (0.12.0): support, managers, superadmin. */\n canReadPayments: z.boolean(),\n /**\n * May see their membership and plan (0.12.0): support, managers,\n * superadmin. Moderators may not: `membership` and `plan` come null to\n * them, which is not \"not a member\".\n */\n canReadMembership: z.boolean(),\n }),\n /** Whether they take part (0.12.0). Staff always do. */\n standing: standingSchema,\n /** Their membership (0.12.0), or null (a viewer, staff holding none, or an asker who may not see it). */\n membership: membershipSchema.nullable(),\n /** The plan in force now and when all their plan time ends (0.12.0), or null (none, or not the asker's to see). */\n plan: z.object({ tier: planTierSchema, endsAt: z.iso.datetime() }).nullable(),\n /** The capabilities they hold (0.9.0), e.g. `create_communities`. */\n capabilities: z.array(capabilitySchema),\n /** Whether they have set an MPIN. Only meaningful for staff. */\n mpinSet: z.boolean(),\n});\nexport type MemberDetail = z.infer<typeof memberDetailSchema>;\n\n/** `POST /members/:memberId/suspension`. The reason is on record in the audit log. */\nexport const suspendMemberBodySchema = z.object({\n reason: z.string().trim().min(SUSPENSION_REASON_MIN).max(SUSPENSION_REASON_MAX),\n});\nexport type SuspendMemberBody = z.infer<typeof suspendMemberBodySchema>;\n"]}
1
+ {"version":3,"file":"members.js","sourceRoot":"","sources":["../../../src/domains/members/members.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema: the staff member directory\n *\n * GET /api/v1/members ?q&status&role&limit&cursor\n * GET /api/v1/members/:memberId one member, as staff see them\n * POST /api/v1/members/:memberId/suspension { reason } suspend\n * DELETE /api/v1/members/:memberId/suspension lift it\n *\n * Every staff role has a Members tab: a moderator looks up who wrote\n * something, a support agent finds the person who wrote in, a manager decides\n * on a suspension. What each sees is the server's call, per field: a\n * moderator gets no email (`email: null`), because finding a member's\n * address is support's job, not moderation's.\n *\n * A SUSPENDED member cannot use the product at all, on any device, from the\n * next request on (every call answers `ACCOUNT_SUSPENDED`); their posts stay\n * up unless moderated separately, and a staff role they were acting in is\n * switched off. Lifting it restores everything else.\n *\n * SEARCH (`q`) finds people by the start of their name or handle among listed\n * founders, or by their exact email among everyone (only for a role that may\n * see emails: a moderator's email search finds nothing). It returns up to\n * `limit` matches in one page (type more to narrow), not a pageable list. Who may suspend whom is the access policy's: never\n * yourself, never a superadmin, and a manager never a fellow manager.\n *\n * Each detail carries a `viewer` block of what the asking staff member may do\n * to THIS member, worked out by the server, so the screen shows only the\n * buttons that will work.\n *\n * @module founder-schema/domains/members\n */\n\nimport { z } from 'zod';\nimport { capabilitySchema } from '../../access/capabilities.js';\nimport { platformRoleSchema } from '../../access/roles.js';\nimport { standingSchema } from '../../access/standing.js';\nimport {\n emptyStringToUndefined,\n objectIdSchema,\n optionalTrimmedString,\n paginationQuerySchema,\n} from '../../core/common.js';\nimport {\n MEMBER_SEARCH_MAX,\n SUSPENSION_REASON_MAX,\n SUSPENSION_REASON_MIN,\n} from '../../core/limits.js';\nimport { paginatedSchema } from '../../core/responses.js';\nimport { membershipSchema, planTierSchema } from '../membership/membership.js';\nimport { personSchema } from '../people/person.js';\n\n/** What staff can see an account's state as. A deleted account is not listed. */\nexport const MEMBER_STATUS_VALUES = ['active', 'suspended'] as const;\nexport const memberStatusSchema = z.enum(MEMBER_STATUS_VALUES);\nexport type MemberStatus = z.infer<typeof memberStatusSchema>;\n\n/** `:memberId` in the paths above. */\nexport const memberParamsSchema = z.object({ memberId: objectIdSchema });\nexport type MemberParams = z.infer<typeof memberParamsSchema>;\n\n/**\n * The directory's query. `q` is a name or handle prefix, or a whole email\n * address (anything with an `@` is matched as an email, exactly). An emptied\n * box is \"no search\", not an error.\n */\nexport const memberDirectoryQuerySchema = paginationQuerySchema.extend({\n q: optionalTrimmedString(MEMBER_SEARCH_MAX),\n status: z.preprocess(emptyStringToUndefined, memberStatusSchema.optional()),\n /** Only people holding this staff role. */\n role: z.preprocess(emptyStringToUndefined, platformRoleSchema.optional()),\n});\nexport type MemberDirectoryQuery = z.infer<typeof memberDirectoryQuerySchema>;\nexport type MemberDirectoryQueryInput = z.input<typeof memberDirectoryQuerySchema>;\n\n/**\n * One row of the directory: the person (0.20.0, `personSchema`, so `userId`\n * where it was `id`) and what staff see beside them.\n */\nexport const memberSummarySchema = personSchema.extend({\n /** Null when the asking role may not see it. */\n email: z.string().nullable(),\n status: memberStatusSchema,\n /** Staff roles held, highest first. Empty for an ordinary member. */\n roles: z.array(platformRoleSchema),\n joinedAt: z.iso.datetime(),\n lastActiveAt: z.iso.datetime().nullable(),\n});\nexport type MemberSummary = z.infer<typeof memberSummarySchema>;\n\nexport const memberDirectoryPageSchema = paginatedSchema(memberSummarySchema);\nexport type MemberDirectoryPage = z.infer<typeof memberDirectoryPageSchema>;\n\n/** A suspension in force. */\nexport const suspensionSchema = z.object({\n since: z.iso.datetime(),\n reason: z.string(),\n /** Who suspended them (0.20.0: the person, live); null when a script did. */\n by: personSchema.nullable(),\n});\nexport type Suspension = z.infer<typeof suspensionSchema>;\n\n/** One member, as staff see them. */\nexport const memberDetailSchema = memberSummarySchema.extend({\n headline: z.string().nullable(),\n /** Their city, as declared on their profile. */\n city: z.string().nullable(),\n counts: z.object({\n posts: z.number().int().min(0),\n polls: z.number().int().min(0),\n comments: z.number().int().min(0),\n /** Reports filed about their posts, polls and comments that are still up. */\n reportsAgainst: z.number().int().min(0),\n /** Tickets they have raised. */\n tickets: z.number().int().min(0),\n }),\n suspension: suspensionSchema.nullable(),\n /** What the asking staff member may do to this member. */\n viewer: z.object({\n canSuspend: z.boolean(),\n /** Roles the asker may grant or revoke on this member (never their own). */\n grantableRoles: z.array(platformRoleSchema),\n /** May clear this member's forgotten MPIN (superadmin, someone else). */\n canResetMpin: z.boolean(),\n /** Capabilities the asker may grant or revoke on this member (never their own) (0.9.0). */\n grantableCapabilities: z.array(capabilitySchema),\n /** May give them a membership (0.12.0): they hold none, and the asker may, and it is not themselves. */\n canGrantMembership: z.boolean(),\n /**\n * May take their membership away (0.12.0): founding, coupon or granted\n * (a paid one is refunded instead), and the asker may, and not their own.\n */\n canRevokeMembership: z.boolean(),\n /** May list their payments (0.12.0): support, managers, superadmin. */\n canReadPayments: z.boolean(),\n /**\n * May see their membership and plan (0.12.0): support, managers,\n * superadmin. Moderators may not: `membership` and `plan` come null to\n * them, which is not \"not a member\".\n */\n canReadMembership: z.boolean(),\n }),\n /** Whether they take part (0.12.0). Staff always do. */\n standing: standingSchema,\n /** Their membership (0.12.0), or null (a viewer, staff holding none, or an asker who may not see it). */\n membership: membershipSchema.nullable(),\n /** The plan in force now and when all their plan time ends (0.12.0), or null (none, or not the asker's to see). */\n plan: z.object({ tier: planTierSchema, endsAt: z.iso.datetime() }).nullable(),\n /** The capabilities they hold (0.9.0), e.g. `create_communities`. */\n capabilities: z.array(capabilitySchema),\n /** Whether they have set an MPIN. Only meaningful for staff. */\n mpinSet: z.boolean(),\n});\nexport type MemberDetail = z.infer<typeof memberDetailSchema>;\n\n/** `POST /members/:memberId/suspension`. The reason is on record in the audit log. */\nexport const suspendMemberBodySchema = z.object({\n reason: z.string().trim().min(SUSPENSION_REASON_MIN).max(SUSPENSION_REASON_MAX),\n});\nexport type SuspendMemberBody = z.infer<typeof suspendMemberBodySchema>;\n"]}
@@ -40,6 +40,25 @@ export declare const membershipKindSchema: z.ZodEnum<{
40
40
  granted: "granted";
41
41
  }>;
42
42
  export type MembershipKind = z.infer<typeof membershipKindSchema>;
43
+ /**
44
+ * A stored membership's state (0.20.0: memberships are their own rows, so
45
+ * none is ever erased). `active` is held now, at most one per member; `ended`
46
+ * is history: a refunded payment, a staff revoke, or the account's deletion,
47
+ * said by `MEMBERSHIP_END_REASON_VALUES`.
48
+ */
49
+ export declare const MEMBERSHIP_STATUS_VALUES: readonly ["active", "ended"];
50
+ export declare const membershipStatusSchema: z.ZodEnum<{
51
+ active: "active";
52
+ ended: "ended";
53
+ }>;
54
+ export type MembershipStatus = z.infer<typeof membershipStatusSchema>;
55
+ export declare const MEMBERSHIP_END_REASON_VALUES: readonly ["refunded", "revoked", "account_deleted"];
56
+ export declare const membershipEndReasonSchema: z.ZodEnum<{
57
+ refunded: "refunded";
58
+ revoked: "revoked";
59
+ account_deleted: "account_deleted";
60
+ }>;
61
+ export type MembershipEndReason = z.infer<typeof membershipEndReasonSchema>;
43
62
  /** ⚠ Stored: append only. Highest last is NOT implied: compare with `PLAN_TIER_RANK`. */
44
63
  export declare const PLAN_TIER_VALUES: readonly ["premium", "business"];
45
64
  export declare const planTierSchema: z.ZodEnum<{
@@ -1 +1 @@
1
- {"version":3,"file":"membership.d.ts","sourceRoot":"","sources":["../../../src/domains/membership/membership.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"membership.d.ts","sourceRoot":"","sources":["../../../src/domains/membership/membership.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,oDAAqD,CAAC;AACzF,eAAO,MAAM,oBAAoB;;;;;EAAiC,CAAC;AACnE,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,8BAA+B,CAAC;AACrE,eAAO,MAAM,sBAAsB;;;EAAmC,CAAC;AACvE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,eAAO,MAAM,4BAA4B,qDAAsD,CAAC;AAChG,eAAO,MAAM,yBAAyB;;;;EAAuC,CAAC;AAC9E,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E,yFAAyF;AACzF,eAAO,MAAM,gBAAgB,kCAAmC,CAAC;AACjE,eAAO,MAAM,cAAc;;;EAA2B,CAAC;AACvD,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAEtD,yDAAyD;AACzD,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAA+B,CAAC;AAE9F,mFAAmF;AACnF,eAAO,MAAM,kBAAkB,4BAA6B,CAAC;AAC7D,eAAO,MAAM,gBAAgB;;;EAA6B,CAAC;AAC3D,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,sDAAsD;AACtD,eAAO,MAAM,WAAW,aAAiC,CAAC;AAC1D,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEhD;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB;;;iBAG3B,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAW1D,sCAAsC;AACtC,eAAO,MAAM,sBAAsB,OAAQ,CAAC;AAE5C,qGAAqG;AACrG,eAAO,MAAM,mBAAmB,EAAE,UAAsD,CAAC;AAEzF,+DAA+D;AAC/D,eAAO,MAAM,UAAU,EAAE,QAAQ,CAC/B,MAAM,CACJ,QAAQ,EACR;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;IAAC,MAAM,EAAE,UAAU,CAAA;CAAE,CACnF,CAYF,CAAC;AASF;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,SAAI,GAAG,IAAI,CAiB7E;AAED,iGAAiG;AACjG,eAAO,MAAM,gBAAgB,QAAmE,CAAC;AACjG,oHAAoH;AACpH,eAAO,MAAM,iBAAiB,QAAqE,CAAC;AACpG,8DAA8D;AAC9D,eAAO,MAAM,sBAAsB,KAAK,CAAC;AACzC,6FAA6F;AAC7F,eAAO,MAAM,eAAe,QAA6C,CAAC;AAE1E;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,GAAG,MAAM,CAYnE;AAED;;;;;GAKG;AACH,eAAO,MAAM,eAAe,MAAM,CAAC;AAInC,eAAO,MAAM,eAAe;;;;;;;;;;;;;;iBAO1B,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiB5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAI5D,eAAO,MAAM,gBAAgB;;;;;;;;;iBAK3B,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,qFAAqF;AACrF,eAAO,MAAM,eAAe;;;;;;;iBAI1B,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD,qFAAqF;AACrF,eAAO,MAAM,yBAAyB,kCAAmC,CAAC;AAC1E,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1E,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiC7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC"}
@@ -37,6 +37,16 @@ import { billingDetailsSchema } from '../billing/gst.js';
37
37
  */
38
38
  export const MEMBERSHIP_KIND_VALUES = ['founding', 'coupon', 'paid', 'granted'];
39
39
  export const membershipKindSchema = z.enum(MEMBERSHIP_KIND_VALUES);
40
+ /**
41
+ * A stored membership's state (0.20.0: memberships are their own rows, so
42
+ * none is ever erased). `active` is held now, at most one per member; `ended`
43
+ * is history: a refunded payment, a staff revoke, or the account's deletion,
44
+ * said by `MEMBERSHIP_END_REASON_VALUES`.
45
+ */
46
+ export const MEMBERSHIP_STATUS_VALUES = ['active', 'ended'];
47
+ export const membershipStatusSchema = z.enum(MEMBERSHIP_STATUS_VALUES);
48
+ export const MEMBERSHIP_END_REASON_VALUES = ['refunded', 'revoked', 'account_deleted'];
49
+ export const membershipEndReasonSchema = z.enum(MEMBERSHIP_END_REASON_VALUES);
40
50
  /** ⚠ Stored: append only. Highest last is NOT implied: compare with `PLAN_TIER_RANK`. */
41
51
  export const PLAN_TIER_VALUES = ['premium', 'business'];
42
52
  export const planTierSchema = z.enum(PLAN_TIER_VALUES);
@@ -1 +1 @@
1
- {"version":3,"file":"membership.js","sourceRoot":"","sources":["../../../src/domains/membership/membership.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema: membership and plans, as a member holds them (0.12.0)\n *\n * GET /api/v1/plans planCatalogSchema (anyone: prices and what each includes)\n * GET /api/v1/me/membership meMembershipSchema (signed in: what I hold, what I have used)\n *\n * THE USER'S RULING (2026-10-11): looking is free; taking part needs a\n * MEMBERSHIP, Rs 99 once, for life (the first 100 free with a coupon). On top,\n * two PLANS: Premium (5 live products, 15 new people a month to message) and\n * Business (unlimited products, 50 new people). Prices and plan contents come\n * from the server, so a change needs no client release.\n *\n * A PLAN IS TIME, PREPAID, IN TWO BLOCKS: Business time first, then Premium\n * time. Buying more APPENDS; buying Premium while on Business waits its turn;\n * buying Business while on Premium is an upgrade that turns the Premium time\n * left into Business for the difference (`domains/billing`). `blocks` says it\n * as it stands, from now on.\n *\n * Money is integer PAISE everywhere in this package: never a float of rupees.\n *\n * @module founder-schema/domains/membership\n */\n\nimport { z } from 'zod';\nimport { standingSchema } from '../../access/standing.js';\nimport { DAY_MS, INDIA_UTC_OFFSET_MS } from '../../core/limits.js';\nimport { billingDetailsSchema } from '../billing/gst.js';\n\n/**\n * How someone came to hold a membership. ⚠ Stored: append only.\n *\n * founding free for life, numbered \"Founding member #N\": one of the first\n * `FOUNDING_PLACES` people to join the launch WAITLIST (0.14.0,\n * the user's rule of 2026-10-16). Before that, the founding\n * coupon (`FIRST100`), which no longer exists on main.\n * coupon another free-membership coupon\n * paid Rs 99, captured\n * granted staff gave it (a partner, a press account), with a reason on record\n */\nexport const MEMBERSHIP_KIND_VALUES = ['founding', 'coupon', 'paid', 'granted'] as const;\nexport const membershipKindSchema = z.enum(MEMBERSHIP_KIND_VALUES);\nexport type MembershipKind = z.infer<typeof membershipKindSchema>;\n\n/** ⚠ Stored: append only. Highest last is NOT implied: compare with `PLAN_TIER_RANK`. */\nexport const PLAN_TIER_VALUES = ['premium', 'business'] as const;\nexport const planTierSchema = z.enum(PLAN_TIER_VALUES);\nexport type PlanTier = z.infer<typeof planTierSchema>;\n\n/** Which tier outranks which: Business above Premium. */\nexport const PLAN_TIER_RANK: Readonly<Record<PlanTier, number>> = { premium: 1, business: 2 };\n\n/** One payment buys a calendar month or a calendar year. ⚠ Stored: append only. */\nexport const PLAN_PERIOD_VALUES = ['month', 'year'] as const;\nexport const planPeriodSchema = z.enum(PLAN_PERIOD_VALUES);\nexport type PlanPeriod = z.infer<typeof planPeriodSchema>;\n\n/** Integer paise, never negative. Rs 399 is 39900. */\nexport const paiseSchema = z.number().int().nonnegative();\nexport type Paise = z.infer<typeof paiseSchema>;\n\n/**\n * What a standing or a plan allows that is COUNTED (the ability says whether;\n * these say how many).\n *\n * productsLive products live at once; null is unlimited (the server\n * still holds a safety ceiling, `PRODUCTS_LIVE_SAFETY_MAX`)\n * newPeoplePerMonth conversations a month with people you do not know\n */\nexport const planLimitsSchema = z.object({\n productsLive: z.number().int().nonnegative().nullable(),\n newPeoplePerMonth: z.number().int().nonnegative(),\n});\nexport type PlanLimits = z.infer<typeof planLimitsSchema>;\n\n// ─── The price list ──────────────────────────────────────────────────────────\n//\n// THE ONE PLACE a price or a plan's limit is written (0.12.0). The backend\n// prices and counts from these (`modules/plans/catalog.ts`), `GET /plans`\n// serves them, and the web's Terms of Service are written FROM them, so the\n// Terms can never state a price the server does not charge. A change here is\n// a schema release, then the backend, then the web. Integer PAISE, GST\n// included. hary's India-first prices (the user: \"you decide, India first\").\n\n/** The membership: once, for life. */\nexport const MEMBERSHIP_PRICE_PAISE = 9_900;\n\n/** What a member with no plan may: 1 live product, no conversations with people they do not know. */\nexport const MEMBER_LIMIT_VALUES: PlanLimits = { productsLive: 1, newPeoplePerMonth: 0 };\n\n/** Each plan's name, prices and limits. Exhaustive by type. */\nexport const PLAN_SPECS: Readonly<\n Record<\n PlanTier,\n { name: string; prices: Readonly<Record<PlanPeriod, number>>; limits: PlanLimits }\n >\n> = {\n premium: {\n name: 'Premium',\n prices: { month: 39_900, year: 399_900 },\n limits: { productsLive: 5, newPeoplePerMonth: 15 },\n },\n business: {\n name: 'Business',\n prices: { month: 99_900, year: 999_900 },\n limits: { productsLive: null, newPeoplePerMonth: 50 },\n },\n};\n\n// ─── Plan time on India's calendar, and an upgrade's price (0.18.0) ──────────\n//\n// Moved here from the backend's plan timeline so the price a member is charged\n// and the rule the Terms of Service state are ONE function: the Terms said\n// \"Rs 20 a day for the rest\" for a week after the server stopped charging it\n// (2026-10-13).\n\n/**\n * A calendar month or year later on India's calendar (`count` of them), the\n * day clamped to the month's last: 31 January plus a month is 28 or 29\n * February. India has one offset all year, so its calendar is UTC's, shifted.\n */\nexport function addPlanPeriod(from: Date, period: PlanPeriod, count = 1): Date {\n const months = period === 'year' ? 12 * count : count;\n const local = new Date(from.getTime() + INDIA_UTC_OFFSET_MS);\n const m = local.getUTCMonth() + months;\n const targetYear = local.getUTCFullYear() + Math.floor(m / 12);\n const targetMonth = ((m % 12) + 12) % 12;\n const lastDay = new Date(Date.UTC(targetYear, targetMonth + 1, 0)).getUTCDate();\n const shifted = Date.UTC(\n targetYear,\n targetMonth,\n Math.min(local.getUTCDate(), lastDay),\n local.getUTCHours(),\n local.getUTCMinutes(),\n local.getUTCSeconds(),\n local.getUTCMilliseconds(),\n );\n return new Date(shifted - INDIA_UTC_OFFSET_MS);\n}\n\n/** What an upgrade costs for each whole calendar year of Premium left: the yearly difference. */\nexport const UPGRADE_PER_YEAR = PLAN_SPECS.business.prices.year - PLAN_SPECS.premium.prices.year;\n/** ...for each whole calendar month after the years: the monthly difference (a 31-day month and February alike). */\nexport const UPGRADE_PER_MONTH = PLAN_SPECS.business.prices.month - PLAN_SPECS.premium.prices.month;\n/** The days left over are charged as this many to a month. */\nexport const UPGRADE_DAYS_PER_MONTH = 30;\n/** ...so each day left over costs this, never more than `UPGRADE_PER_MONTH` for them all. */\nexport const UPGRADE_PER_DAY = UPGRADE_PER_MONTH / UPGRADE_DAYS_PER_MONTH;\n\n/**\n * What an upgrade of `convertedMs` of Premium, from `now`, costs in paise (the\n * user: \"ask for the rest of the money, the difference\"). Each whole calendar\n * year left costs `UPGRADE_PER_YEAR`; each whole calendar month after that,\n * `UPGRADE_PER_MONTH`; the days left over `UPGRADE_PER_DAY` each, but never\n * more than one month's. The part below a year never costs more than a year's.\n * A part day counts as a day; rounded up to the rupee.\n */\nexport function upgradePrice(convertedMs: number, now: Date): number {\n const end = now.getTime() + Math.max(convertedMs, 1);\n let years = 0;\n while (addPlanPeriod(now, 'year', years + 1).getTime() <= end) years += 1;\n const afterYears = addPlanPeriod(now, 'year', years);\n let months = 0;\n while (addPlanPeriod(afterYears, 'month', months + 1).getTime() <= end) months += 1;\n const restDays = Math.ceil((end - addPlanPeriod(afterYears, 'month', months).getTime()) / DAY_MS);\n const belowAYear =\n months * UPGRADE_PER_MONTH + Math.min(restDays * UPGRADE_PER_DAY, UPGRADE_PER_MONTH);\n const paise = years * UPGRADE_PER_YEAR + Math.min(belowAYear, UPGRADE_PER_YEAR);\n return Math.ceil(paise / 100) * 100;\n}\n\n/**\n * Free founding memberships (0.14.0): the first 100 people to join the launch\n * waitlist get their membership free, numbered #1 to #100 by their place\n * (the user, 2026-10-16). Everyone else pays `MEMBERSHIP_PRICE_PAISE` once.\n * No code gives a founding place any more.\n */\nexport const FOUNDING_PLACES = 100;\n\n// ─── GET /plans ──────────────────────────────────────────────────────────────\n\nexport const planOfferSchema = z.object({\n tier: planTierSchema,\n /** \"Premium\", \"Business\": the server's word, so a rename needs no client release. */\n name: z.string(),\n /** GST included. */\n prices: z.object({ month: paiseSchema, year: paiseSchema }),\n limits: planLimitsSchema,\n});\nexport type PlanOffer = z.infer<typeof planOfferSchema>;\n\nexport const planCatalogSchema = z.object({\n currency: z.literal('INR'),\n membership: z.object({\n /** Once, for life, GST included. */\n price: paiseSchema,\n limits: planLimitsSchema,\n }),\n /** Lowest first. */\n plans: z.array(planOfferSchema),\n /** Every purchase is refunded in full within this many days of paying. */\n refundWindowDays: z.number().int().positive(),\n /**\n * Whether a payment can be taken here now. False when the gateway is not\n * configured: a free coupon still works, a paid purchase answers\n * `PAYMENTS_UNAVAILABLE`.\n */\n paymentsEnabled: z.boolean(),\n});\nexport type PlanCatalog = z.infer<typeof planCatalogSchema>;\n\n// ─── GET /me/membership ──────────────────────────────────────────────────────\n\nexport const membershipSchema = z.object({\n kind: membershipKindSchema,\n since: z.iso.datetime(),\n /** \"Founding member #N\": set only for `founding`. */\n foundingNumber: z.number().int().positive().nullable(),\n});\nexport type Membership = z.infer<typeof membershipSchema>;\n\n/** A stretch of one tier. Blocks never overlap and follow each other with no gap. */\nexport const planBlockSchema = z.object({\n tier: planTierSchema,\n startsAt: z.iso.datetime(),\n endsAt: z.iso.datetime(),\n});\nexport type PlanBlock = z.infer<typeof planBlockSchema>;\n\n/** Why someone takes part: their membership, or a staff role (staff are members). */\nexport const TAKES_PART_THROUGH_VALUES = ['membership', 'staff'] as const;\nexport type TakesPartThrough = (typeof TAKES_PART_THROUGH_VALUES)[number];\n\nexport const meMembershipSchema = z.object({\n standing: standingSchema,\n /** Null for a viewer, and for staff who hold none (they take part through their role). */\n membership: membershipSchema.nullable(),\n /** Why they take part: their membership, or a staff role (staff are members). Null for a viewer. */\n through: z.enum(TAKES_PART_THROUGH_VALUES).nullable(),\n plan: z.object({\n /** The tier in force now; null with no plan. */\n tier: planTierSchema.nullable(),\n /** From now on, Business first, then Premium. Empty with no plan. */\n blocks: z.array(planBlockSchema),\n }),\n limits: planLimitsSchema,\n products: z.object({\n /** Products they own that are live now. */\n live: z.number().int().nonnegative(),\n /** Products they own, paused ones included. */\n owned: z.number().int().nonnegative(),\n /**\n * What adding another counts against (0.13.0): live ones and the ones\n * they paused themselves, not those a plan's end paused. \"Add a product\"\n * stops when this reaches `limits.productsLive`: pausing never makes room.\n */\n counted: z.number().int().nonnegative(),\n }),\n messages: z.object({\n /** New people they started a conversation with this month (India time). */\n used: z.number().int().nonnegative(),\n /** When this month's count starts again. */\n resetsAt: z.iso.datetime(),\n }),\n /** The billing details they last used, to prefill the next checkout; null before the first. */\n billing: billingDetailsSchema.nullable(),\n});\nexport type MeMembership = z.infer<typeof meMembershipSchema>;\n"]}
1
+ {"version":3,"file":"membership.js","sourceRoot":"","sources":["../../../src/domains/membership/membership.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema: membership and plans, as a member holds them (0.12.0)\n *\n * GET /api/v1/plans planCatalogSchema (anyone: prices and what each includes)\n * GET /api/v1/me/membership meMembershipSchema (signed in: what I hold, what I have used)\n *\n * THE USER'S RULING (2026-10-11): looking is free; taking part needs a\n * MEMBERSHIP, Rs 99 once, for life (the first 100 free with a coupon). On top,\n * two PLANS: Premium (5 live products, 15 new people a month to message) and\n * Business (unlimited products, 50 new people). Prices and plan contents come\n * from the server, so a change needs no client release.\n *\n * A PLAN IS TIME, PREPAID, IN TWO BLOCKS: Business time first, then Premium\n * time. Buying more APPENDS; buying Premium while on Business waits its turn;\n * buying Business while on Premium is an upgrade that turns the Premium time\n * left into Business for the difference (`domains/billing`). `blocks` says it\n * as it stands, from now on.\n *\n * Money is integer PAISE everywhere in this package: never a float of rupees.\n *\n * @module founder-schema/domains/membership\n */\n\nimport { z } from 'zod';\nimport { standingSchema } from '../../access/standing.js';\nimport { DAY_MS, INDIA_UTC_OFFSET_MS } from '../../core/limits.js';\nimport { billingDetailsSchema } from '../billing/gst.js';\n\n/**\n * How someone came to hold a membership. ⚠ Stored: append only.\n *\n * founding free for life, numbered \"Founding member #N\": one of the first\n * `FOUNDING_PLACES` people to join the launch WAITLIST (0.14.0,\n * the user's rule of 2026-10-16). Before that, the founding\n * coupon (`FIRST100`), which no longer exists on main.\n * coupon another free-membership coupon\n * paid Rs 99, captured\n * granted staff gave it (a partner, a press account), with a reason on record\n */\nexport const MEMBERSHIP_KIND_VALUES = ['founding', 'coupon', 'paid', 'granted'] as const;\nexport const membershipKindSchema = z.enum(MEMBERSHIP_KIND_VALUES);\nexport type MembershipKind = z.infer<typeof membershipKindSchema>;\n\n/**\n * A stored membership's state (0.20.0: memberships are their own rows, so\n * none is ever erased). `active` is held now, at most one per member; `ended`\n * is history: a refunded payment, a staff revoke, or the account's deletion,\n * said by `MEMBERSHIP_END_REASON_VALUES`.\n */\nexport const MEMBERSHIP_STATUS_VALUES = ['active', 'ended'] as const;\nexport const membershipStatusSchema = z.enum(MEMBERSHIP_STATUS_VALUES);\nexport type MembershipStatus = z.infer<typeof membershipStatusSchema>;\n\nexport const MEMBERSHIP_END_REASON_VALUES = ['refunded', 'revoked', 'account_deleted'] as const;\nexport const membershipEndReasonSchema = z.enum(MEMBERSHIP_END_REASON_VALUES);\nexport type MembershipEndReason = z.infer<typeof membershipEndReasonSchema>;\n\n/** ⚠ Stored: append only. Highest last is NOT implied: compare with `PLAN_TIER_RANK`. */\nexport const PLAN_TIER_VALUES = ['premium', 'business'] as const;\nexport const planTierSchema = z.enum(PLAN_TIER_VALUES);\nexport type PlanTier = z.infer<typeof planTierSchema>;\n\n/** Which tier outranks which: Business above Premium. */\nexport const PLAN_TIER_RANK: Readonly<Record<PlanTier, number>> = { premium: 1, business: 2 };\n\n/** One payment buys a calendar month or a calendar year. ⚠ Stored: append only. */\nexport const PLAN_PERIOD_VALUES = ['month', 'year'] as const;\nexport const planPeriodSchema = z.enum(PLAN_PERIOD_VALUES);\nexport type PlanPeriod = z.infer<typeof planPeriodSchema>;\n\n/** Integer paise, never negative. Rs 399 is 39900. */\nexport const paiseSchema = z.number().int().nonnegative();\nexport type Paise = z.infer<typeof paiseSchema>;\n\n/**\n * What a standing or a plan allows that is COUNTED (the ability says whether;\n * these say how many).\n *\n * productsLive products live at once; null is unlimited (the server\n * still holds a safety ceiling, `PRODUCTS_LIVE_SAFETY_MAX`)\n * newPeoplePerMonth conversations a month with people you do not know\n */\nexport const planLimitsSchema = z.object({\n productsLive: z.number().int().nonnegative().nullable(),\n newPeoplePerMonth: z.number().int().nonnegative(),\n});\nexport type PlanLimits = z.infer<typeof planLimitsSchema>;\n\n// ─── The price list ──────────────────────────────────────────────────────────\n//\n// THE ONE PLACE a price or a plan's limit is written (0.12.0). The backend\n// prices and counts from these (`modules/plans/catalog.ts`), `GET /plans`\n// serves them, and the web's Terms of Service are written FROM them, so the\n// Terms can never state a price the server does not charge. A change here is\n// a schema release, then the backend, then the web. Integer PAISE, GST\n// included. hary's India-first prices (the user: \"you decide, India first\").\n\n/** The membership: once, for life. */\nexport const MEMBERSHIP_PRICE_PAISE = 9_900;\n\n/** What a member with no plan may: 1 live product, no conversations with people they do not know. */\nexport const MEMBER_LIMIT_VALUES: PlanLimits = { productsLive: 1, newPeoplePerMonth: 0 };\n\n/** Each plan's name, prices and limits. Exhaustive by type. */\nexport const PLAN_SPECS: Readonly<\n Record<\n PlanTier,\n { name: string; prices: Readonly<Record<PlanPeriod, number>>; limits: PlanLimits }\n >\n> = {\n premium: {\n name: 'Premium',\n prices: { month: 39_900, year: 399_900 },\n limits: { productsLive: 5, newPeoplePerMonth: 15 },\n },\n business: {\n name: 'Business',\n prices: { month: 99_900, year: 999_900 },\n limits: { productsLive: null, newPeoplePerMonth: 50 },\n },\n};\n\n// ─── Plan time on India's calendar, and an upgrade's price (0.18.0) ──────────\n//\n// Moved here from the backend's plan timeline so the price a member is charged\n// and the rule the Terms of Service state are ONE function: the Terms said\n// \"Rs 20 a day for the rest\" for a week after the server stopped charging it\n// (2026-10-13).\n\n/**\n * A calendar month or year later on India's calendar (`count` of them), the\n * day clamped to the month's last: 31 January plus a month is 28 or 29\n * February. India has one offset all year, so its calendar is UTC's, shifted.\n */\nexport function addPlanPeriod(from: Date, period: PlanPeriod, count = 1): Date {\n const months = period === 'year' ? 12 * count : count;\n const local = new Date(from.getTime() + INDIA_UTC_OFFSET_MS);\n const m = local.getUTCMonth() + months;\n const targetYear = local.getUTCFullYear() + Math.floor(m / 12);\n const targetMonth = ((m % 12) + 12) % 12;\n const lastDay = new Date(Date.UTC(targetYear, targetMonth + 1, 0)).getUTCDate();\n const shifted = Date.UTC(\n targetYear,\n targetMonth,\n Math.min(local.getUTCDate(), lastDay),\n local.getUTCHours(),\n local.getUTCMinutes(),\n local.getUTCSeconds(),\n local.getUTCMilliseconds(),\n );\n return new Date(shifted - INDIA_UTC_OFFSET_MS);\n}\n\n/** What an upgrade costs for each whole calendar year of Premium left: the yearly difference. */\nexport const UPGRADE_PER_YEAR = PLAN_SPECS.business.prices.year - PLAN_SPECS.premium.prices.year;\n/** ...for each whole calendar month after the years: the monthly difference (a 31-day month and February alike). */\nexport const UPGRADE_PER_MONTH = PLAN_SPECS.business.prices.month - PLAN_SPECS.premium.prices.month;\n/** The days left over are charged as this many to a month. */\nexport const UPGRADE_DAYS_PER_MONTH = 30;\n/** ...so each day left over costs this, never more than `UPGRADE_PER_MONTH` for them all. */\nexport const UPGRADE_PER_DAY = UPGRADE_PER_MONTH / UPGRADE_DAYS_PER_MONTH;\n\n/**\n * What an upgrade of `convertedMs` of Premium, from `now`, costs in paise (the\n * user: \"ask for the rest of the money, the difference\"). Each whole calendar\n * year left costs `UPGRADE_PER_YEAR`; each whole calendar month after that,\n * `UPGRADE_PER_MONTH`; the days left over `UPGRADE_PER_DAY` each, but never\n * more than one month's. The part below a year never costs more than a year's.\n * A part day counts as a day; rounded up to the rupee.\n */\nexport function upgradePrice(convertedMs: number, now: Date): number {\n const end = now.getTime() + Math.max(convertedMs, 1);\n let years = 0;\n while (addPlanPeriod(now, 'year', years + 1).getTime() <= end) years += 1;\n const afterYears = addPlanPeriod(now, 'year', years);\n let months = 0;\n while (addPlanPeriod(afterYears, 'month', months + 1).getTime() <= end) months += 1;\n const restDays = Math.ceil((end - addPlanPeriod(afterYears, 'month', months).getTime()) / DAY_MS);\n const belowAYear =\n months * UPGRADE_PER_MONTH + Math.min(restDays * UPGRADE_PER_DAY, UPGRADE_PER_MONTH);\n const paise = years * UPGRADE_PER_YEAR + Math.min(belowAYear, UPGRADE_PER_YEAR);\n return Math.ceil(paise / 100) * 100;\n}\n\n/**\n * Free founding memberships (0.14.0): the first 100 people to join the launch\n * waitlist get their membership free, numbered #1 to #100 by their place\n * (the user, 2026-10-16). Everyone else pays `MEMBERSHIP_PRICE_PAISE` once.\n * No code gives a founding place any more.\n */\nexport const FOUNDING_PLACES = 100;\n\n// ─── GET /plans ──────────────────────────────────────────────────────────────\n\nexport const planOfferSchema = z.object({\n tier: planTierSchema,\n /** \"Premium\", \"Business\": the server's word, so a rename needs no client release. */\n name: z.string(),\n /** GST included. */\n prices: z.object({ month: paiseSchema, year: paiseSchema }),\n limits: planLimitsSchema,\n});\nexport type PlanOffer = z.infer<typeof planOfferSchema>;\n\nexport const planCatalogSchema = z.object({\n currency: z.literal('INR'),\n membership: z.object({\n /** Once, for life, GST included. */\n price: paiseSchema,\n limits: planLimitsSchema,\n }),\n /** Lowest first. */\n plans: z.array(planOfferSchema),\n /** Every purchase is refunded in full within this many days of paying. */\n refundWindowDays: z.number().int().positive(),\n /**\n * Whether a payment can be taken here now. False when the gateway is not\n * configured: a free coupon still works, a paid purchase answers\n * `PAYMENTS_UNAVAILABLE`.\n */\n paymentsEnabled: z.boolean(),\n});\nexport type PlanCatalog = z.infer<typeof planCatalogSchema>;\n\n// ─── GET /me/membership ──────────────────────────────────────────────────────\n\nexport const membershipSchema = z.object({\n kind: membershipKindSchema,\n since: z.iso.datetime(),\n /** \"Founding member #N\": set only for `founding`. */\n foundingNumber: z.number().int().positive().nullable(),\n});\nexport type Membership = z.infer<typeof membershipSchema>;\n\n/** A stretch of one tier. Blocks never overlap and follow each other with no gap. */\nexport const planBlockSchema = z.object({\n tier: planTierSchema,\n startsAt: z.iso.datetime(),\n endsAt: z.iso.datetime(),\n});\nexport type PlanBlock = z.infer<typeof planBlockSchema>;\n\n/** Why someone takes part: their membership, or a staff role (staff are members). */\nexport const TAKES_PART_THROUGH_VALUES = ['membership', 'staff'] as const;\nexport type TakesPartThrough = (typeof TAKES_PART_THROUGH_VALUES)[number];\n\nexport const meMembershipSchema = z.object({\n standing: standingSchema,\n /** Null for a viewer, and for staff who hold none (they take part through their role). */\n membership: membershipSchema.nullable(),\n /** Why they take part: their membership, or a staff role (staff are members). Null for a viewer. */\n through: z.enum(TAKES_PART_THROUGH_VALUES).nullable(),\n plan: z.object({\n /** The tier in force now; null with no plan. */\n tier: planTierSchema.nullable(),\n /** From now on, Business first, then Premium. Empty with no plan. */\n blocks: z.array(planBlockSchema),\n }),\n limits: planLimitsSchema,\n products: z.object({\n /** Products they own that are live now. */\n live: z.number().int().nonnegative(),\n /** Products they own, paused ones included. */\n owned: z.number().int().nonnegative(),\n /**\n * What adding another counts against (0.13.0): live ones and the ones\n * they paused themselves, not those a plan's end paused. \"Add a product\"\n * stops when this reaches `limits.productsLive`: pausing never makes room.\n */\n counted: z.number().int().nonnegative(),\n }),\n messages: z.object({\n /** New people they started a conversation with this month (India time). */\n used: z.number().int().nonnegative(),\n /** When this month's count starts again. */\n resetsAt: z.iso.datetime(),\n }),\n /** The billing details they last used, to prefill the next checkout; null before the first. */\n billing: billingDetailsSchema.nullable(),\n});\nexport type MeMembership = z.infer<typeof meMembershipSchema>;\n"]}
@@ -103,9 +103,12 @@ export declare const moderationItemSchema: z.ZodObject<{
103
103
  excerpt: z.ZodString;
104
104
  asReported: z.ZodOptional<z.ZodString>;
105
105
  author: z.ZodObject<{
106
- id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
106
+ userId: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
107
+ username: z.ZodNullable<z.ZodString>;
107
108
  displayName: z.ZodString;
109
+ firstName: z.ZodString;
108
110
  avatarUrl: z.ZodNullable<z.ZodURL>;
111
+ former: z.ZodBoolean;
109
112
  }, z.core.$strip>;
110
113
  createdAt: z.ZodISODateTime;
111
114
  state: z.ZodEnum<{
@@ -161,9 +164,12 @@ export declare const moderationPageSchema: z.ZodObject<{
161
164
  excerpt: z.ZodString;
162
165
  asReported: z.ZodOptional<z.ZodString>;
163
166
  author: z.ZodObject<{
164
- id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
167
+ userId: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
168
+ username: z.ZodNullable<z.ZodString>;
165
169
  displayName: z.ZodString;
170
+ firstName: z.ZodString;
166
171
  avatarUrl: z.ZodNullable<z.ZodURL>;
172
+ former: z.ZodBoolean;
167
173
  }, z.core.$strip>;
168
174
  createdAt: z.ZodISODateTime;
169
175
  state: z.ZodEnum<{
@@ -1 +1 @@
1
- {"version":3,"file":"moderation.d.ts","sourceRoot":"","sources":["../../../src/domains/moderation/moderation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAWxB;;;GAGG;AACH,eAAO,MAAM,8BAA8B,sCAAuC,CAAC;AACnF,eAAO,MAAM,2BAA2B;;;;EAAyC,CAAC;AAClF,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAEhF,6DAA6D;AAC7D,eAAO,MAAM,uBAAuB,2CAA4C,CAAC;AACjF,eAAO,MAAM,qBAAqB;;;;EAAkC,CAAC;AACrE,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,eAAO,MAAM,wBAAwB,mDAAoD,CAAC;AAC1F,eAAO,MAAM,sBAAsB;;;;;EAAmC,CAAC;AACvE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE;;;;;;GAMG;AACH,eAAO,MAAM,iCAAiC;;;;;;CAM4B,CAAC;AAI3E,wEAAwE;AACxE,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8C/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAwC,CAAC;AAC1E,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,wDAAwD;AACxD,eAAO,MAAM,4BAA4B;;;;;;;;iBAEvC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAClF,MAAM,MAAM,2BAA2B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAEvF,+EAA+E;AAC/E,eAAO,MAAM,4BAA4B;;;;;;;;;iBAGvC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAElF,2DAA2D;AAC3D,eAAO,MAAM,4BAA4B;;;;;;;;iBAIvC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC"}
1
+ {"version":3,"file":"moderation.d.ts","sourceRoot":"","sources":["../../../src/domains/moderation/moderation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAYxB;;;GAGG;AACH,eAAO,MAAM,8BAA8B,sCAAuC,CAAC;AACnF,eAAO,MAAM,2BAA2B;;;;EAAyC,CAAC;AAClF,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAEhF,6DAA6D;AAC7D,eAAO,MAAM,uBAAuB,2CAA4C,CAAC;AACjF,eAAO,MAAM,qBAAqB;;;;EAAkC,CAAC;AACrE,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,eAAO,MAAM,wBAAwB,mDAAoD,CAAC;AAC1F,eAAO,MAAM,sBAAsB;;;;;EAAmC,CAAC;AACvE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE;;;;;;GAMG;AACH,eAAO,MAAM,iCAAiC;;;;;;CAM4B,CAAC;AAI3E,wEAAwE;AACxE,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA0C/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAwC,CAAC;AAC1E,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,wDAAwD;AACxD,eAAO,MAAM,4BAA4B;;;;;;;;iBAEvC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAClF,MAAM,MAAM,2BAA2B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAEvF,+EAA+E;AAC/E,eAAO,MAAM,4BAA4B;;;;;;;;;iBAGvC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAElF,2DAA2D;AAC3D,eAAO,MAAM,4BAA4B;;;;;;;;iBAIvC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC"}
@@ -43,6 +43,7 @@ import { emptyStringToUndefined, objectIdSchema, optionalTrimmedString, paginati
43
43
  import { MODERATION_EXCERPT_MAX, MODERATION_NOTE_MAX } from '../../core/limits.js';
44
44
  import { paginatedSchema } from '../../core/responses.js';
45
45
  import { reportReasonSchema, reportTargetKindSchema } from '../feed/feed.js';
46
+ import { personSchema } from '../people/person.js';
46
47
  /**
47
48
  * Where something is read in the app: a post or poll, or a product page (for a
48
49
  * product, and for a review of it).
@@ -89,12 +90,8 @@ export const moderationItemSchema = z.object({
89
90
  * Absent when unedited since the report.
90
91
  */
91
92
  asReported: z.string().max(MODERATION_EXCERPT_MAX).optional(),
92
- /** Who wrote it; for a product, its owner. */
93
- author: z.object({
94
- id: objectIdSchema,
95
- displayName: z.string(),
96
- avatarUrl: z.url().nullable(),
97
- }),
93
+ /** Who wrote it; for a product, its owner (0.20.0: the person, with a handle to link). */
94
+ author: personSchema,
98
95
  createdAt: z.iso.datetime(),
99
96
  state: moderationStateSchema,
100
97
  reports: z.object({
@@ -1 +1 @@
1
- {"version":3,"file":"moderation.js","sourceRoot":"","sources":["../../../src/domains/moderation/moderation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema: moderation\n *\n * GET /api/v1/moderation/queue ?limit&cursor\n * what members reported and nobody has decided on, oldest report first\n * GET /api/v1/moderation/content ?state&limit&cursor\n * by state, newest first: visible is posts and polls by when posted;\n * hidden and removed also hold comments (so a hidden comment can be\n * found and put back), by when hidden or removed\n * A decision that changes nothing (hiding what is already hidden, removing\n * what its author deleted) leaves the reports open and writes nothing: for\n * those, dismiss.\n * POST /api/v1/moderation/:contentKind/:contentId/decision { action, note? }\n * decide on one post, poll, comment, product or review; answers the\n * item as it now is\n *\n * PRODUCTS AND REVIEWS (0.6.0) are reported and decided here too. A product is\n * hidden or restored, never removed by staff (it is its team's work; hiding\n * takes it out of Discover and off every page but its team's). A review is\n * hidden, restored or removed like a comment. The hidden and removed lists\n * hold them too, so a hidden product can be found and put back.\n *\n * ONE ITEM PER THING, NOT PER REPORT. Five members reporting one post is one\n * row with five reports, so a moderator decides once. The queue's order is\n * the oldest open report first: whoever has waited longest is seen first, and\n * a pile-on cannot jump the line.\n *\n * THE DECISIONS\n * dismiss it stays up; its open reports close as dismissed\n * hide out of every feed and thread, kept, restorable; reports actioned\n * restore a hidden item goes back up\n * remove deleted as its author deleting it would (counts move), not\n * restorable from here; reports actioned\n * Every decision on someone else's content is written to the audit log, with\n * the note when there is one.\n *\n * A report's `note` was written by a member: untrusted text, shown as text.\n *\n * @module founder-schema/domains/moderation\n */\n\nimport { z } from 'zod';\nimport {\n emptyStringToUndefined,\n objectIdSchema,\n optionalTrimmedString,\n paginationQuerySchema,\n} from '../../core/common.js';\nimport { MODERATION_EXCERPT_MAX, MODERATION_NOTE_MAX } from '../../core/limits.js';\nimport { paginatedSchema } from '../../core/responses.js';\nimport { type ReportTargetKind, reportReasonSchema, reportTargetKindSchema } from '../feed/feed.js';\n\n/**\n * Where something is read in the app: a post or poll, or a product page (for a\n * product, and for a review of it).\n */\nexport const MODERATION_CONTEXT_KIND_VALUES = ['post', 'poll', 'product'] as const;\nexport const moderationContextKindSchema = z.enum(MODERATION_CONTEXT_KIND_VALUES);\nexport type ModerationContextKind = z.infer<typeof moderationContextKindSchema>;\n\n/** Where a post, poll, comment, product or review stands. */\nexport const MODERATION_STATE_VALUES = ['visible', 'hidden', 'removed'] as const;\nexport const moderationStateSchema = z.enum(MODERATION_STATE_VALUES);\nexport type ModerationState = z.infer<typeof moderationStateSchema>;\n\nexport const MODERATION_ACTION_VALUES = ['dismiss', 'hide', 'restore', 'remove'] as const;\nexport const moderationActionSchema = z.enum(MODERATION_ACTION_VALUES);\nexport type ModerationAction = z.infer<typeof moderationActionSchema>;\n\n/**\n * Which decisions apply to which kind of thing: everything, except that a\n * product is never removed by staff (it is its team's work; hiding it takes it\n * out of Discover and off every page but its team's). The decision dialog\n * offers these, and the server refuses anything else with\n * `MODERATION_ACTION_INVALID`. Exhaustive by type.\n */\nexport const MODERATION_ACTIONS_BY_TARGET_KIND = {\n post: MODERATION_ACTION_VALUES,\n poll: MODERATION_ACTION_VALUES,\n comment: MODERATION_ACTION_VALUES,\n review: MODERATION_ACTION_VALUES,\n product: ['dismiss', 'hide', 'restore'],\n} as const satisfies Record<ReportTargetKind, readonly ModerationAction[]>;\n\nconst count = z.number().int().min(0);\n\n/** One post, poll, comment, product or review as moderation sees it. */\nexport const moderationItemSchema = z.object({\n target: z.object({ kind: reportTargetKindSchema, id: objectIdSchema }),\n /**\n * Where it is read in the app: the post or poll itself, or the one a comment\n * is on; the product page for a product or a review.\n */\n content: z.object({ kind: moderationContextKindSchema, id: objectIdSchema }),\n /**\n * Its first words AS THEY ARE NOW, cut to `MODERATION_EXCERPT_MAX`: a poll's\n * question, a product's name and tagline, a review's stars and text.\n */\n excerpt: z.string().max(MODERATION_EXCERPT_MAX),\n /**\n * The words as they were when first reported, when the author has edited\n * them since (0.8.0): both are shown, so an edit can neither hide what was\n * reported nor slip new words past a moderator judging the old ones.\n * Absent when unedited since the report.\n */\n asReported: z.string().max(MODERATION_EXCERPT_MAX).optional(),\n /** Who wrote it; for a product, its owner. */\n author: z.object({\n id: objectIdSchema,\n displayName: z.string(),\n avatarUrl: z.url().nullable(),\n }),\n createdAt: z.iso.datetime(),\n state: moderationStateSchema,\n reports: z.object({\n /** Reports nobody has decided on. */\n open: count,\n /** Every report ever filed about it. */\n total: count,\n /** Why it was reported, among the open ones, most-given first. */\n reasons: z.array(z.object({ reason: reportReasonSchema, count })),\n /** The reporters' notes, among the open ones, newest first (a few, not all). */\n notes: z.array(z.string()),\n /** When the oldest open report was filed. Null when none is open. */\n oldestOpenAt: z.iso.datetime().nullable(),\n }),\n /** What the asking staff member may do to it now. */\n viewer: z.object({\n canDismiss: z.boolean(),\n canHide: z.boolean(),\n canRestore: z.boolean(),\n canRemove: z.boolean(),\n }),\n});\nexport type ModerationItem = z.infer<typeof moderationItemSchema>;\n\nexport const moderationPageSchema = paginatedSchema(moderationItemSchema);\nexport type ModerationPage = z.infer<typeof moderationPageSchema>;\n\n/** `GET /moderation/content`. Visible when not said. */\nexport const moderationContentQuerySchema = paginationQuerySchema.extend({\n state: z.preprocess(emptyStringToUndefined, moderationStateSchema.default('visible')),\n});\nexport type ModerationContentQuery = z.infer<typeof moderationContentQuerySchema>;\nexport type ModerationContentQueryInput = z.input<typeof moderationContentQuerySchema>;\n\n/** `POST /moderation/:contentKind/:contentId/decision`: what is decided on. */\nexport const moderationTargetParamsSchema = z.object({\n contentKind: reportTargetKindSchema,\n contentId: objectIdSchema,\n});\nexport type ModerationTargetParams = z.infer<typeof moderationTargetParamsSchema>;\n\n/** `POST /moderation/:contentKind/:contentId/decision`. */\nexport const moderationDecisionBodySchema = z.object({\n action: moderationActionSchema,\n /** Why, for the audit log. Optional: an obvious spam removal needs no essay. */\n note: optionalTrimmedString(MODERATION_NOTE_MAX),\n});\nexport type ModerationDecisionBody = z.infer<typeof moderationDecisionBodySchema>;\n"]}
1
+ {"version":3,"file":"moderation.js","sourceRoot":"","sources":["../../../src/domains/moderation/moderation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema: moderation\n *\n * GET /api/v1/moderation/queue ?limit&cursor\n * what members reported and nobody has decided on, oldest report first\n * GET /api/v1/moderation/content ?state&limit&cursor\n * by state, newest first: visible is posts and polls by when posted;\n * hidden and removed also hold comments (so a hidden comment can be\n * found and put back), by when hidden or removed\n * A decision that changes nothing (hiding what is already hidden, removing\n * what its author deleted) leaves the reports open and writes nothing: for\n * those, dismiss.\n * POST /api/v1/moderation/:contentKind/:contentId/decision { action, note? }\n * decide on one post, poll, comment, product or review; answers the\n * item as it now is\n *\n * PRODUCTS AND REVIEWS (0.6.0) are reported and decided here too. A product is\n * hidden or restored, never removed by staff (it is its team's work; hiding\n * takes it out of Discover and off every page but its team's). A review is\n * hidden, restored or removed like a comment. The hidden and removed lists\n * hold them too, so a hidden product can be found and put back.\n *\n * ONE ITEM PER THING, NOT PER REPORT. Five members reporting one post is one\n * row with five reports, so a moderator decides once. The queue's order is\n * the oldest open report first: whoever has waited longest is seen first, and\n * a pile-on cannot jump the line.\n *\n * THE DECISIONS\n * dismiss it stays up; its open reports close as dismissed\n * hide out of every feed and thread, kept, restorable; reports actioned\n * restore a hidden item goes back up\n * remove deleted as its author deleting it would (counts move), not\n * restorable from here; reports actioned\n * Every decision on someone else's content is written to the audit log, with\n * the note when there is one.\n *\n * A report's `note` was written by a member: untrusted text, shown as text.\n *\n * @module founder-schema/domains/moderation\n */\n\nimport { z } from 'zod';\nimport {\n emptyStringToUndefined,\n objectIdSchema,\n optionalTrimmedString,\n paginationQuerySchema,\n} from '../../core/common.js';\nimport { MODERATION_EXCERPT_MAX, MODERATION_NOTE_MAX } from '../../core/limits.js';\nimport { paginatedSchema } from '../../core/responses.js';\nimport { type ReportTargetKind, reportReasonSchema, reportTargetKindSchema } from '../feed/feed.js';\nimport { personSchema } from '../people/person.js';\n\n/**\n * Where something is read in the app: a post or poll, or a product page (for a\n * product, and for a review of it).\n */\nexport const MODERATION_CONTEXT_KIND_VALUES = ['post', 'poll', 'product'] as const;\nexport const moderationContextKindSchema = z.enum(MODERATION_CONTEXT_KIND_VALUES);\nexport type ModerationContextKind = z.infer<typeof moderationContextKindSchema>;\n\n/** Where a post, poll, comment, product or review stands. */\nexport const MODERATION_STATE_VALUES = ['visible', 'hidden', 'removed'] as const;\nexport const moderationStateSchema = z.enum(MODERATION_STATE_VALUES);\nexport type ModerationState = z.infer<typeof moderationStateSchema>;\n\nexport const MODERATION_ACTION_VALUES = ['dismiss', 'hide', 'restore', 'remove'] as const;\nexport const moderationActionSchema = z.enum(MODERATION_ACTION_VALUES);\nexport type ModerationAction = z.infer<typeof moderationActionSchema>;\n\n/**\n * Which decisions apply to which kind of thing: everything, except that a\n * product is never removed by staff (it is its team's work; hiding it takes it\n * out of Discover and off every page but its team's). The decision dialog\n * offers these, and the server refuses anything else with\n * `MODERATION_ACTION_INVALID`. Exhaustive by type.\n */\nexport const MODERATION_ACTIONS_BY_TARGET_KIND = {\n post: MODERATION_ACTION_VALUES,\n poll: MODERATION_ACTION_VALUES,\n comment: MODERATION_ACTION_VALUES,\n review: MODERATION_ACTION_VALUES,\n product: ['dismiss', 'hide', 'restore'],\n} as const satisfies Record<ReportTargetKind, readonly ModerationAction[]>;\n\nconst count = z.number().int().min(0);\n\n/** One post, poll, comment, product or review as moderation sees it. */\nexport const moderationItemSchema = z.object({\n target: z.object({ kind: reportTargetKindSchema, id: objectIdSchema }),\n /**\n * Where it is read in the app: the post or poll itself, or the one a comment\n * is on; the product page for a product or a review.\n */\n content: z.object({ kind: moderationContextKindSchema, id: objectIdSchema }),\n /**\n * Its first words AS THEY ARE NOW, cut to `MODERATION_EXCERPT_MAX`: a poll's\n * question, a product's name and tagline, a review's stars and text.\n */\n excerpt: z.string().max(MODERATION_EXCERPT_MAX),\n /**\n * The words as they were when first reported, when the author has edited\n * them since (0.8.0): both are shown, so an edit can neither hide what was\n * reported nor slip new words past a moderator judging the old ones.\n * Absent when unedited since the report.\n */\n asReported: z.string().max(MODERATION_EXCERPT_MAX).optional(),\n /** Who wrote it; for a product, its owner (0.20.0: the person, with a handle to link). */\n author: personSchema,\n createdAt: z.iso.datetime(),\n state: moderationStateSchema,\n reports: z.object({\n /** Reports nobody has decided on. */\n open: count,\n /** Every report ever filed about it. */\n total: count,\n /** Why it was reported, among the open ones, most-given first. */\n reasons: z.array(z.object({ reason: reportReasonSchema, count })),\n /** The reporters' notes, among the open ones, newest first (a few, not all). */\n notes: z.array(z.string()),\n /** When the oldest open report was filed. Null when none is open. */\n oldestOpenAt: z.iso.datetime().nullable(),\n }),\n /** What the asking staff member may do to it now. */\n viewer: z.object({\n canDismiss: z.boolean(),\n canHide: z.boolean(),\n canRestore: z.boolean(),\n canRemove: z.boolean(),\n }),\n});\nexport type ModerationItem = z.infer<typeof moderationItemSchema>;\n\nexport const moderationPageSchema = paginatedSchema(moderationItemSchema);\nexport type ModerationPage = z.infer<typeof moderationPageSchema>;\n\n/** `GET /moderation/content`. Visible when not said. */\nexport const moderationContentQuerySchema = paginationQuerySchema.extend({\n state: z.preprocess(emptyStringToUndefined, moderationStateSchema.default('visible')),\n});\nexport type ModerationContentQuery = z.infer<typeof moderationContentQuerySchema>;\nexport type ModerationContentQueryInput = z.input<typeof moderationContentQuerySchema>;\n\n/** `POST /moderation/:contentKind/:contentId/decision`: what is decided on. */\nexport const moderationTargetParamsSchema = z.object({\n contentKind: reportTargetKindSchema,\n contentId: objectIdSchema,\n});\nexport type ModerationTargetParams = z.infer<typeof moderationTargetParamsSchema>;\n\n/** `POST /moderation/:contentKind/:contentId/decision`. */\nexport const moderationDecisionBodySchema = z.object({\n action: moderationActionSchema,\n /** Why, for the audit log. Optional: an obvious spam removal needs no essay. */\n note: optionalTrimmedString(MODERATION_NOTE_MAX),\n});\nexport type ModerationDecisionBody = z.infer<typeof moderationDecisionBodySchema>;\n"]}
@@ -163,10 +163,12 @@ export declare const notificationSchema: z.ZodObject<{
163
163
  payment_refunded: "payment_refunded";
164
164
  }>;
165
165
  actor: z.ZodNullable<z.ZodObject<{
166
- userId: z.ZodString;
167
- username: z.ZodString;
166
+ userId: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
167
+ username: z.ZodNullable<z.ZodString>;
168
168
  displayName: z.ZodString;
169
- avatarUrl: z.ZodOptional<z.ZodURL>;
169
+ firstName: z.ZodString;
170
+ avatarUrl: z.ZodNullable<z.ZodURL>;
171
+ former: z.ZodBoolean;
170
172
  headline: z.ZodOptional<z.ZodString>;
171
173
  }, z.core.$strip>>;
172
174
  count: z.ZodNumber;
@@ -263,10 +265,12 @@ export declare const notificationPageSchema: z.ZodObject<{
263
265
  payment_refunded: "payment_refunded";
264
266
  }>;
265
267
  actor: z.ZodNullable<z.ZodObject<{
266
- userId: z.ZodString;
267
- username: z.ZodString;
268
+ userId: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
269
+ username: z.ZodNullable<z.ZodString>;
268
270
  displayName: z.ZodString;
269
- avatarUrl: z.ZodOptional<z.ZodURL>;
271
+ firstName: z.ZodString;
272
+ avatarUrl: z.ZodNullable<z.ZodURL>;
273
+ former: z.ZodBoolean;
270
274
  headline: z.ZodOptional<z.ZodString>;
271
275
  }, z.core.$strip>>;
272
276
  count: z.ZodNumber;
@@ -1 +1 @@
1
- {"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../../../src/domains/notifications/notifications.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAaxB;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,wBAAwB,+YAoB3B,CAAC;AACX,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;EAAmC,CAAC;AACvE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,kEAAkE;AAClE,eAAO,MAAM,8BAA8B,2GAQjC,CAAC;AACX,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,8BAA8B,CAAC,CAAC,MAAM,CAAC,CAAC;AAErF;;;GAGG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;CAoBqC,CAAC;AAE7E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAyD7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAsC,CAAC;AAC1E,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,iBAAiB;AACjB,eAAO,MAAM,6BAA6B;;iBAExC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEpF,eAAO,MAAM,wBAAwB;;iBAA+C,CAAC;AACrF,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,+CAA+C;AAC/C,eAAO,MAAM,6BAA6B;;;;;;;;iBAWxC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEpF,kFAAkF;AAClF,eAAO,MAAM,uCAAuC;;;;;;;;iBAQlD,CAAC;AACH,MAAM,MAAM,iCAAiC,GAAG,CAAC,CAAC,KAAK,CACrD,OAAO,uCAAuC,CAC/C,CAAC"}
1
+ {"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../../../src/domains/notifications/notifications.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAaxB;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,wBAAwB,+YAoB3B,CAAC;AACX,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;EAAmC,CAAC;AACvE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,kEAAkE;AAClE,eAAO,MAAM,8BAA8B,2GAQjC,CAAC;AACX,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,8BAA8B,CAAC,CAAC,MAAM,CAAC,CAAC;AAErF;;;GAGG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;CAoBqC,CAAC;AAE7E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAyD7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAsC,CAAC;AAC1E,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,iBAAiB;AACjB,eAAO,MAAM,6BAA6B;;iBAExC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEpF,eAAO,MAAM,wBAAwB;;iBAA+C,CAAC;AACrF,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,+CAA+C;AAC/C,eAAO,MAAM,6BAA6B;;;;;;;;iBAWxC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEpF,kFAAkF;AAClF,eAAO,MAAM,uCAAuC;;;;;;;;iBAQlD,CAAC;AACH,MAAM,MAAM,iCAAiC,GAAG,CAAC,CAAC,KAAK,CACrD,OAAO,uCAAuC,CAC/C,CAAC"}
@@ -121,7 +121,7 @@ export function completeOnboardingBodyFrom(form) {
121
121
  links[k] = url;
122
122
  }
123
123
  const body = {
124
- displayName: tidyFullName(form.displayName),
124
+ fullName: tidyFullName(form.fullName),
125
125
  gender: form.gender ?? undefined,
126
126
  dateOfBirth: form.dateOfBirth.trim(),
127
127
  phone: normalizePhone(form.phone),
@@ -1 +1 @@
1
- {"version":3,"file":"onboarding-answers.js","sourceRoot":"","sources":["../../../src/domains/onboarding/onboarding-answers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema — From a member's onboarding answers to what is sent (0.17.0)\n *\n * The answers are an `OnboardingForm` (the in-progress shape: raw text, `null`\n * for a choice not made). Two things are built from them, and until 0.17.0\n * each client built them by hand, so a second client (the app) would have had\n * to copy every rule below and keep it in step:\n *\n * the draft a step SAVES exactly the fields that step owns, as typed\n * (`onboardingDraftForStep`)\n * the body FINISH sends completed answers only, cleaned, every\n * unanswered optional field ABSENT\n * (`completeOnboardingBodyFrom`)\n *\n * Neither validates: `ONBOARDING_STEP_SCHEMAS` and `completeOnboardingBodySchema`\n * do, on what these build. A half-filled form builds a half-filled body on\n * purpose, so the schema refuses it with the reason under each field instead\n * of a builder hiding the gap.\n *\n * @module founder-schema/domains/onboarding/onboarding-answers\n */\n\nimport {\n type Industry,\n normalizeLinkUrl,\n normalizePhone,\n type StartupStage,\n tidyFullName,\n} from '../../core/common.js';\nimport { FOUNDED_YEAR_MIN } from '../../core/limits.js';\nimport { type Place, pickOfPlace } from '../location/areas.js';\nimport {\n type CompleteOnboardingBodyInput,\n FOUNDER_ONBOARDING_STEPS,\n GUIDELINES_VERSION,\n ONBOARDING_STEP_KEYS,\n type OnboardingForm,\n type OnboardingStepKey,\n readOnboardingDraft,\n teamSizeDigits,\n} from './onboarding.js';\n\ntype DeepPartial<T> = T extends readonly (infer Item)[]\n ? DeepPartial<Item>[]\n : T extends object\n ? { [K in keyof T]?: DeepPartial<T[K]> }\n : T;\n\n/**\n * A founding year as typed, kept to digits, as many as a year has (0.18.0,\n * moved from the web's Me editor beside `teamSizeDigits`).\n */\nexport const foundedYearDigits = (raw: string): string =>\n raw.replace(/\\D/g, '').slice(0, String(FOUNDED_YEAR_MIN).length);\n\n/**\n * A venture body from what was typed (0.18.0): ONE builder for onboarding's\n * business step and Me's business editor, which each built it by hand and\n * cleaned the team size two ways. Text trimmed, the website made a link\n * (`normalizeLinkUrl`), digits kept (`teamSizeDigits`, `foundedYearDigits`);\n * an empty optional answer is ABSENT. A missing name, industry or stage is\n * left missing, so `ventureSchema` refuses it under its field.\n */\nexport function ventureBodyFrom(v: {\n name: string;\n website: string;\n industry: Industry | null;\n stage: StartupStage | null;\n teamSize: string;\n tagline?: string;\n foundedYear?: string;\n}): {\n name: string;\n industry: Industry | undefined;\n stage: StartupStage | undefined;\n tagline?: string;\n website?: string;\n teamSize?: number;\n foundedYear?: number;\n} {\n const tagline = v.tagline?.trim();\n const website = normalizeLinkUrl(v.website);\n const teamSize = teamSizeDigits(v.teamSize);\n const foundedYear = foundedYearDigits(v.foundedYear ?? '');\n return {\n name: v.name.trim(),\n industry: v.industry ?? undefined,\n stage: v.stage ?? undefined,\n ...(tagline ? { tagline } : {}),\n ...(website ? { website } : {}),\n ...(teamSize ? { teamSize: Number(teamSize) } : {}),\n ...(foundedYear ? { foundedYear: Number(foundedYear) } : {}),\n };\n}\n\n/**\n * What `completeOnboardingBodyFrom` builds: the finish body's INPUT, any part\n * of it possibly still missing. Not a valid body until\n * `completeOnboardingBodySchema` says so.\n */\nexport type CompleteOnboardingBodyCandidate = DeepPartial<CompleteOnboardingBodyInput>;\n\n/** Answers with nothing filled in: what a new member starts from. */\nexport function emptyOnboardingForm(): OnboardingForm {\n return readOnboardingDraft({});\n}\n\n/**\n * True when the member has started describing a business: any of the\n * venture's own answers. Then it is sent, and the schema asks for its name,\n * industry and stage.\n *\n * The business type and the registration do not start it: they DESCRIBE a\n * business. Picked without one, each is sent anyway and refused on its own\n * field (`ventureRequired`: add the business, or clear this), never dropped\n * without a word (hary's ruling).\n */\nexport function ventureStarted(form: Pick<OnboardingForm, 'venture'>): boolean {\n const v = form.venture;\n return Boolean(v.name.trim() || v.website.trim() || v.industry || v.stage || v.teamSize.trim());\n}\n\n/**\n * True when a place has been given (0.19.0). The drawer hands back only a\n * whole place (state, district, and a locality or the whole district), so\n * there is no half answer: a place is there or it is not.\n */\nexport function locationStarted(place: Place | null): boolean {\n return place !== null;\n}\n\n/**\n * The draft one step saves: exactly the fields that step owns\n * (`FOUNDER_ONBOARDING_STEPS[].fields`, the same picks its validation uses),\n * as typed. Only the step's own fields, so a save can never overwrite another\n * step's answers (the server merges per field); raw, so a member who comes back\n * sees what they typed. The photo step's field is its upload id, which the\n * server holds rather than drafts.\n */\nexport function onboardingDraftForStep(\n form: OnboardingForm,\n key: OnboardingStepKey,\n): Partial<OnboardingForm> {\n const step = FOUNDER_ONBOARDING_STEPS[ONBOARDING_STEP_KEYS.indexOf(key)];\n const draft: Partial<OnboardingForm> = {};\n for (const field of step?.fields ?? []) copyField(draft, form, field);\n return draft;\n}\n\n/** One field across, typed by its key (0.18.0: the draft is the FORM's shape, never `unknown`). */\nfunction copyField<K extends keyof OnboardingForm>(\n to: Partial<OnboardingForm>,\n from: OnboardingForm,\n key: K,\n): void {\n to[key] = from[key];\n}\n\n/**\n * What Finish sends, and what every step is CHECKED as (each step's slice\n * reads its own fields from it).\n *\n * ⚠ ABSENT, NEVER A DEFAULT. The sibling gym product sent `bloodGroup ?? 'O+'`\n * and recorded everyone who skipped the question as O-positive. An optional\n * answer not given is left out; nothing is filled with a plausible value.\n *\n * Cleaned the way the schema reads it: the name tidied (`tidyFullName`), the\n * phone to ten digits (`normalizePhone`), links given their scheme\n * (`normalizeLinkUrl`), the place as its area ids (`pickOfPlace`, 0.19.0:\n * the server names it), the team size a number. A half-answered part is\n * SENT, not hidden: a business without its stage reaches the schema and is\n * refused there, under the field.\n */\nexport function completeOnboardingBodyFrom(form: OnboardingForm): CompleteOnboardingBodyCandidate {\n const headline = form.headline.trim();\n const about = form.about.trim();\n\n const links: Partial<Record<keyof OnboardingForm['links'], string>> = {};\n for (const k of Object.keys(form.links) as (keyof OnboardingForm['links'])[]) {\n const url = normalizeLinkUrl(form.links[k]);\n if (url) links[k] = url;\n }\n\n const body: CompleteOnboardingBodyCandidate = {\n displayName: tidyFullName(form.displayName),\n gender: form.gender ?? undefined,\n dateOfBirth: form.dateOfBirth.trim(),\n phone: normalizePhone(form.phone),\n goals: form.goals,\n interests: form.interests,\n // Only agreement to the CURRENT text is sent; anything else is absent, so\n // the schema refuses it as not agreed. Consent to an older text is not\n // consent to this one.\n guidelinesVersion:\n form.guidelinesVersion === GUIDELINES_VERSION ? GUIDELINES_VERSION : undefined,\n };\n if (headline) body.headline = headline;\n if (about) body.about = about;\n if (form.avatarMediaId) body.avatarMediaId = form.avatarMediaId;\n if (form.skills.length) body.skills = form.skills;\n if (Object.keys(links).length) body.links = links;\n\n if (form.location) body.location = pickOfPlace(form.location);\n\n if (ventureStarted(form)) body.venture = ventureBodyFrom(form.venture);\n // Sent whenever given: with a business they describe it, without one the\n // schema refuses each on its own field rather than anything vanishing.\n if (form.businessType) body.businessType = form.businessType;\n if (form.legalStructure) body.legalStructure = form.legalStructure;\n\n return body;\n}\n"]}
1
+ {"version":3,"file":"onboarding-answers.js","sourceRoot":"","sources":["../../../src/domains/onboarding/onboarding-answers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema — From a member's onboarding answers to what is sent (0.17.0)\n *\n * The answers are an `OnboardingForm` (the in-progress shape: raw text, `null`\n * for a choice not made). Two things are built from them, and until 0.17.0\n * each client built them by hand, so a second client (the app) would have had\n * to copy every rule below and keep it in step:\n *\n * the draft a step SAVES exactly the fields that step owns, as typed\n * (`onboardingDraftForStep`)\n * the body FINISH sends completed answers only, cleaned, every\n * unanswered optional field ABSENT\n * (`completeOnboardingBodyFrom`)\n *\n * Neither validates: `ONBOARDING_STEP_SCHEMAS` and `completeOnboardingBodySchema`\n * do, on what these build. A half-filled form builds a half-filled body on\n * purpose, so the schema refuses it with the reason under each field instead\n * of a builder hiding the gap.\n *\n * @module founder-schema/domains/onboarding/onboarding-answers\n */\n\nimport {\n type Industry,\n normalizeLinkUrl,\n normalizePhone,\n type StartupStage,\n tidyFullName,\n} from '../../core/common.js';\nimport { FOUNDED_YEAR_MIN } from '../../core/limits.js';\nimport { type Place, pickOfPlace } from '../location/areas.js';\nimport {\n type CompleteOnboardingBodyInput,\n FOUNDER_ONBOARDING_STEPS,\n GUIDELINES_VERSION,\n ONBOARDING_STEP_KEYS,\n type OnboardingForm,\n type OnboardingStepKey,\n readOnboardingDraft,\n teamSizeDigits,\n} from './onboarding.js';\n\ntype DeepPartial<T> = T extends readonly (infer Item)[]\n ? DeepPartial<Item>[]\n : T extends object\n ? { [K in keyof T]?: DeepPartial<T[K]> }\n : T;\n\n/**\n * A founding year as typed, kept to digits, as many as a year has (0.18.0,\n * moved from the web's Me editor beside `teamSizeDigits`).\n */\nexport const foundedYearDigits = (raw: string): string =>\n raw.replace(/\\D/g, '').slice(0, String(FOUNDED_YEAR_MIN).length);\n\n/**\n * A venture body from what was typed (0.18.0): ONE builder for onboarding's\n * business step and Me's business editor, which each built it by hand and\n * cleaned the team size two ways. Text trimmed, the website made a link\n * (`normalizeLinkUrl`), digits kept (`teamSizeDigits`, `foundedYearDigits`);\n * an empty optional answer is ABSENT. A missing name, industry or stage is\n * left missing, so `ventureSchema` refuses it under its field.\n */\nexport function ventureBodyFrom(v: {\n name: string;\n website: string;\n industry: Industry | null;\n stage: StartupStage | null;\n teamSize: string;\n tagline?: string;\n foundedYear?: string;\n}): {\n name: string;\n industry: Industry | undefined;\n stage: StartupStage | undefined;\n tagline?: string;\n website?: string;\n teamSize?: number;\n foundedYear?: number;\n} {\n const tagline = v.tagline?.trim();\n const website = normalizeLinkUrl(v.website);\n const teamSize = teamSizeDigits(v.teamSize);\n const foundedYear = foundedYearDigits(v.foundedYear ?? '');\n return {\n name: v.name.trim(),\n industry: v.industry ?? undefined,\n stage: v.stage ?? undefined,\n ...(tagline ? { tagline } : {}),\n ...(website ? { website } : {}),\n ...(teamSize ? { teamSize: Number(teamSize) } : {}),\n ...(foundedYear ? { foundedYear: Number(foundedYear) } : {}),\n };\n}\n\n/**\n * What `completeOnboardingBodyFrom` builds: the finish body's INPUT, any part\n * of it possibly still missing. Not a valid body until\n * `completeOnboardingBodySchema` says so.\n */\nexport type CompleteOnboardingBodyCandidate = DeepPartial<CompleteOnboardingBodyInput>;\n\n/** Answers with nothing filled in: what a new member starts from. */\nexport function emptyOnboardingForm(): OnboardingForm {\n return readOnboardingDraft({});\n}\n\n/**\n * True when the member has started describing a business: any of the\n * venture's own answers. Then it is sent, and the schema asks for its name,\n * industry and stage.\n *\n * The business type and the registration do not start it: they DESCRIBE a\n * business. Picked without one, each is sent anyway and refused on its own\n * field (`ventureRequired`: add the business, or clear this), never dropped\n * without a word (hary's ruling).\n */\nexport function ventureStarted(form: Pick<OnboardingForm, 'venture'>): boolean {\n const v = form.venture;\n return Boolean(v.name.trim() || v.website.trim() || v.industry || v.stage || v.teamSize.trim());\n}\n\n/**\n * True when a place has been given (0.19.0). The drawer hands back only a\n * whole place (state, district, and a locality or the whole district), so\n * there is no half answer: a place is there or it is not.\n */\nexport function locationStarted(place: Place | null): boolean {\n return place !== null;\n}\n\n/**\n * The draft one step saves: exactly the fields that step owns\n * (`FOUNDER_ONBOARDING_STEPS[].fields`, the same picks its validation uses),\n * as typed. Only the step's own fields, so a save can never overwrite another\n * step's answers (the server merges per field); raw, so a member who comes back\n * sees what they typed. The photo step's field is its upload id, which the\n * server holds rather than drafts.\n */\nexport function onboardingDraftForStep(\n form: OnboardingForm,\n key: OnboardingStepKey,\n): Partial<OnboardingForm> {\n const step = FOUNDER_ONBOARDING_STEPS[ONBOARDING_STEP_KEYS.indexOf(key)];\n const draft: Partial<OnboardingForm> = {};\n for (const field of step?.fields ?? []) copyField(draft, form, field);\n return draft;\n}\n\n/** One field across, typed by its key (0.18.0: the draft is the FORM's shape, never `unknown`). */\nfunction copyField<K extends keyof OnboardingForm>(\n to: Partial<OnboardingForm>,\n from: OnboardingForm,\n key: K,\n): void {\n to[key] = from[key];\n}\n\n/**\n * What Finish sends, and what every step is CHECKED as (each step's slice\n * reads its own fields from it).\n *\n * ⚠ ABSENT, NEVER A DEFAULT. The sibling gym product sent `bloodGroup ?? 'O+'`\n * and recorded everyone who skipped the question as O-positive. An optional\n * answer not given is left out; nothing is filled with a plausible value.\n *\n * Cleaned the way the schema reads it: the name tidied (`tidyFullName`), the\n * phone to ten digits (`normalizePhone`), links given their scheme\n * (`normalizeLinkUrl`), the place as its area ids (`pickOfPlace`, 0.19.0:\n * the server names it), the team size a number. A half-answered part is\n * SENT, not hidden: a business without its stage reaches the schema and is\n * refused there, under the field.\n */\nexport function completeOnboardingBodyFrom(form: OnboardingForm): CompleteOnboardingBodyCandidate {\n const headline = form.headline.trim();\n const about = form.about.trim();\n\n const links: Partial<Record<keyof OnboardingForm['links'], string>> = {};\n for (const k of Object.keys(form.links) as (keyof OnboardingForm['links'])[]) {\n const url = normalizeLinkUrl(form.links[k]);\n if (url) links[k] = url;\n }\n\n const body: CompleteOnboardingBodyCandidate = {\n fullName: tidyFullName(form.fullName),\n gender: form.gender ?? undefined,\n dateOfBirth: form.dateOfBirth.trim(),\n phone: normalizePhone(form.phone),\n goals: form.goals,\n interests: form.interests,\n // Only agreement to the CURRENT text is sent; anything else is absent, so\n // the schema refuses it as not agreed. Consent to an older text is not\n // consent to this one.\n guidelinesVersion:\n form.guidelinesVersion === GUIDELINES_VERSION ? GUIDELINES_VERSION : undefined,\n };\n if (headline) body.headline = headline;\n if (about) body.about = about;\n if (form.avatarMediaId) body.avatarMediaId = form.avatarMediaId;\n if (form.skills.length) body.skills = form.skills;\n if (Object.keys(links).length) body.links = links;\n\n if (form.location) body.location = pickOfPlace(form.location);\n\n if (ventureStarted(form)) body.venture = ventureBodyFrom(form.venture);\n // Sent whenever given: with a business they describe it, without one the\n // schema refuses each on its own field rather than anything vanishing.\n if (form.businessType) body.businessType = form.businessType;\n if (form.legalStructure) body.legalStructure = form.legalStructure;\n\n return body;\n}\n"]}