founder-schema 0.7.0 → 0.9.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 (64) hide show
  1. package/README.md +39 -0
  2. package/dist/access/capabilities.d.ts +34 -0
  3. package/dist/access/capabilities.d.ts.map +1 -0
  4. package/dist/access/capabilities.js +35 -0
  5. package/dist/access/capabilities.js.map +1 -0
  6. package/dist/access/permissions.d.ts +20 -1
  7. package/dist/access/permissions.d.ts.map +1 -1
  8. package/dist/access/permissions.js +4 -0
  9. package/dist/access/permissions.js.map +1 -1
  10. package/dist/core/errors.d.ts +39 -1
  11. package/dist/core/errors.d.ts.map +1 -1
  12. package/dist/core/errors.js +63 -0
  13. package/dist/core/errors.js.map +1 -1
  14. package/dist/core/limits.d.ts +12 -0
  15. package/dist/core/limits.d.ts.map +1 -1
  16. package/dist/core/limits.js +13 -0
  17. package/dist/core/limits.js.map +1 -1
  18. package/dist/core/responses.d.ts +53 -0
  19. package/dist/core/responses.d.ts.map +1 -1
  20. package/dist/core/responses.js +7 -0
  21. package/dist/core/responses.js.map +1 -1
  22. package/dist/core/validation.d.ts +6 -0
  23. package/dist/core/validation.d.ts.map +1 -1
  24. package/dist/core/validation.js +6 -0
  25. package/dist/core/validation.js.map +1 -1
  26. package/dist/domains/audit/audit.d.ts +47 -3
  27. package/dist/domains/audit/audit.d.ts.map +1 -1
  28. package/dist/domains/audit/audit.js +40 -4
  29. package/dist/domains/audit/audit.js.map +1 -1
  30. package/dist/domains/communities/communities.d.ts +1537 -0
  31. package/dist/domains/communities/communities.d.ts.map +1 -0
  32. package/dist/domains/communities/communities.js +464 -0
  33. package/dist/domains/communities/communities.js.map +1 -0
  34. package/dist/domains/feed/feed.d.ts +996 -45
  35. package/dist/domains/feed/feed.d.ts.map +1 -1
  36. package/dist/domains/feed/feed.js +197 -10
  37. package/dist/domains/feed/feed.js.map +1 -1
  38. package/dist/domains/media/media.d.ts +7 -1
  39. package/dist/domains/media/media.d.ts.map +1 -1
  40. package/dist/domains/media/media.js +5 -0
  41. package/dist/domains/media/media.js.map +1 -1
  42. package/dist/domains/members/members.d.ts +6 -0
  43. package/dist/domains/members/members.d.ts.map +1 -1
  44. package/dist/domains/members/members.js +5 -0
  45. package/dist/domains/members/members.js.map +1 -1
  46. package/dist/domains/moderation/moderation.d.ts +6 -4
  47. package/dist/domains/moderation/moderation.d.ts.map +1 -1
  48. package/dist/domains/moderation/moderation.js +9 -2
  49. package/dist/domains/moderation/moderation.js.map +1 -1
  50. package/dist/domains/notifications/notifications.d.ts +133 -4
  51. package/dist/domains/notifications/notifications.d.ts.map +1 -1
  52. package/dist/domains/notifications/notifications.js +80 -3
  53. package/dist/domains/notifications/notifications.js.map +1 -1
  54. package/dist/domains/team/team.d.ts +22 -0
  55. package/dist/domains/team/team.d.ts.map +1 -1
  56. package/dist/domains/team/team.js +22 -0
  57. package/dist/domains/team/team.js.map +1 -1
  58. package/dist/domains/uploads/uploads.d.ts +1 -0
  59. package/dist/domains/uploads/uploads.d.ts.map +1 -1
  60. package/dist/index.d.ts +5 -3
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +6 -3
  63. package/dist/index.js.map +1 -1
  64. package/package.json +1 -1
package/README.md CHANGED
@@ -99,6 +99,45 @@ instance; this package must not bring its own.
99
99
 
100
100
  ---
101
101
 
102
+ ## Breaking in 0.9.0: communities (2026-10-08)
103
+
104
+ Official communities (domains/communities), a capability system
105
+ (access/capabilities), and pinned announcements. No export removed. Design:
106
+ founder-circle-backend `docs/communities.md`.
107
+
108
+ - **REQUIRED in answers, so the backend and the web deploy together:**
109
+ `notificationPreferencesSchema.communities` (a new switch), and on the staff
110
+ member detail `capabilities` and `viewer.grantableCapabilities`.
111
+ - **Added, optional:** a post or poll's `community` (the "in <community>" chip)
112
+ and `pinnedAt`; a notification's `community` and `suggestion`.
113
+ - **Added, optional:** `apiErrorDetailsSchema.nextAt`, when
114
+ `COMMUNITY_ANNOUNCE_TOO_SOON` allows notifying every member again, and
115
+ `apiErrorDetailsSchema.communities` ({ id, name }), the communities an
116
+ account deletion refused with `COMMUNITY_LAST_ADMIN` must hand over first.
117
+ - **Added, optional, for the web to gate controls:** a feed item's
118
+ `viewer.canTakePart` (community content only), a member row's `viewer`
119
+ ({ canSetRole, canRemove, canBan }), and the community page's `orphaned`
120
+ (managers and super admins only).
121
+ - **Three new validation messages to word:** `topicsDuplicate`,
122
+ `communityWithProduct`, `capabilitiesDuplicate`.
123
+ - **Added to a union:** `auditActorSchema` gains `{ kind: 'member', id,
124
+ displayName }`, a community admin who is not staff. A client switching on
125
+ `kind` must handle it (the web's audit view ships with the backend).
126
+ - **Added to request bodies:** `communityId` on `createPostBodySchema` and
127
+ `createPollBodySchema`, refused together with `productId` or `ask`
128
+ (`communityWithProduct`): community content carries no product.
129
+ - **Appended to stored lists:** five notification kinds (`community_*`), the
130
+ `communities` preference, the `community_icon` media purpose, the
131
+ `Community` and `CommunitySuggestion` CASL subjects, eight audit actions, and
132
+ sixteen error codes (`COMMUNITY_*`, `CANNOT_CHANGE_OWN_CAPABILITIES`,
133
+ `CAPABILITY_NOT_GRANTABLE`), and the audit action `community.orphaned`
134
+ (nine in all).
135
+ - **Capabilities** are always-on powers on a member, not a role. The web never
136
+ reads `CAPABILITY_VALUES` against a member: the packed rules of
137
+ `GET /me/access` already say `can('create', 'Community')`.
138
+
139
+ ---
140
+
102
141
  ## Changes in 0.7.0: the products double check (2026-10-07)
103
142
 
104
143
  No export removed; two added (`youtubeVideoId`, `CONTRIBUTION_AWARDED_KIND_VALUES`).
@@ -0,0 +1,34 @@
1
+ /**
2
+ * founder-schema: capabilities, powers given to selected members (0.9.0)
3
+ *
4
+ * A CAPABILITY is one specific power held always-on by a member, without a
5
+ * staff role: no MPIN, no role switch, no 12-hour lapse, no staff area, and it
6
+ * does not count them as staff. The first is `create_communities` (the user,
7
+ * 2026-10-08: "only manager or super admin can create communities, or a few
8
+ * select people that we give this power").
9
+ *
10
+ * The server turns a held capability into CASL rules for the member baseline
11
+ * (`can('create', 'Community')`), so the web learns it only through the packed
12
+ * rules of `GET /me/access`, never by reading this list against a member.
13
+ * It is read from the member's record on every request, never cached in a
14
+ * token or session: a revoke bites on the very next request.
15
+ *
16
+ * Granted and revoked as a whole set (`PUT /team/:memberId/capabilities`,
17
+ * domains/team) by the roles in `CAPABILITY_GRANTED_BY`, never on oneself,
18
+ * each change audited (`capability.granted` / `capability.revoked`). Revoking
19
+ * takes nothing already made: the creator stays its community's admin.
20
+ *
21
+ * @module founder-schema/access/capabilities
22
+ */
23
+ import { z } from 'zod';
24
+ import type { PlatformRole, Role } from './roles.js';
25
+ /** ⚠ Stored on the member: append only. */
26
+ export declare const CAPABILITY_VALUES: readonly ["create_communities"];
27
+ export declare const capabilitySchema: z.ZodEnum<{
28
+ create_communities: "create_communities";
29
+ }>;
30
+ export type Capability = z.infer<typeof capabilitySchema>;
31
+ /** Which active role may grant or revoke each capability. */
32
+ export declare const CAPABILITY_GRANTED_BY: Readonly<Record<Capability, readonly PlatformRole[]>>;
33
+ export declare function canGrantCapability(actingAs: Role, capability: Capability): boolean;
34
+ //# sourceMappingURL=capabilities.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../../src/access/capabilities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAGrD,2CAA2C;AAC3C,eAAO,MAAM,iBAAiB,iCAAkC,CAAC;AACjE,eAAO,MAAM,gBAAgB;;EAA4B,CAAC;AAC1D,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,6DAA6D;AAC7D,eAAO,MAAM,qBAAqB,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,SAAS,YAAY,EAAE,CAAC,CAEvF,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAElF"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * founder-schema: capabilities, powers given to selected members (0.9.0)
3
+ *
4
+ * A CAPABILITY is one specific power held always-on by a member, without a
5
+ * staff role: no MPIN, no role switch, no 12-hour lapse, no staff area, and it
6
+ * does not count them as staff. The first is `create_communities` (the user,
7
+ * 2026-10-08: "only manager or super admin can create communities, or a few
8
+ * select people that we give this power").
9
+ *
10
+ * The server turns a held capability into CASL rules for the member baseline
11
+ * (`can('create', 'Community')`), so the web learns it only through the packed
12
+ * rules of `GET /me/access`, never by reading this list against a member.
13
+ * It is read from the member's record on every request, never cached in a
14
+ * token or session: a revoke bites on the very next request.
15
+ *
16
+ * Granted and revoked as a whole set (`PUT /team/:memberId/capabilities`,
17
+ * domains/team) by the roles in `CAPABILITY_GRANTED_BY`, never on oneself,
18
+ * each change audited (`capability.granted` / `capability.revoked`). Revoking
19
+ * takes nothing already made: the creator stays its community's admin.
20
+ *
21
+ * @module founder-schema/access/capabilities
22
+ */
23
+ import { z } from 'zod';
24
+ import { isPlatformRole } from './roles.js';
25
+ /** ⚠ Stored on the member: append only. */
26
+ export const CAPABILITY_VALUES = ['create_communities'];
27
+ export const capabilitySchema = z.enum(CAPABILITY_VALUES);
28
+ /** Which active role may grant or revoke each capability. */
29
+ export const CAPABILITY_GRANTED_BY = {
30
+ create_communities: ['superadmin', 'manager'],
31
+ };
32
+ export function canGrantCapability(actingAs, capability) {
33
+ return isPlatformRole(actingAs) && CAPABILITY_GRANTED_BY[capability].includes(actingAs);
34
+ }
35
+ //# sourceMappingURL=capabilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../../src/access/capabilities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,2CAA2C;AAC3C,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,oBAAoB,CAAU,CAAC;AACjE,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;AAG1D,6DAA6D;AAC7D,MAAM,CAAC,MAAM,qBAAqB,GAA0D;IAC1F,kBAAkB,EAAE,CAAC,YAAY,EAAE,SAAS,CAAC;CAC9C,CAAC;AAEF,MAAM,UAAU,kBAAkB,CAAC,QAAc,EAAE,UAAsB;IACvE,OAAO,cAAc,CAAC,QAAQ,CAAC,IAAI,qBAAqB,CAAC,UAAU,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAC1F,CAAC","sourcesContent":["/**\n * founder-schema: capabilities, powers given to selected members (0.9.0)\n *\n * A CAPABILITY is one specific power held always-on by a member, without a\n * staff role: no MPIN, no role switch, no 12-hour lapse, no staff area, and it\n * does not count them as staff. The first is `create_communities` (the user,\n * 2026-10-08: \"only manager or super admin can create communities, or a few\n * select people that we give this power\").\n *\n * The server turns a held capability into CASL rules for the member baseline\n * (`can('create', 'Community')`), so the web learns it only through the packed\n * rules of `GET /me/access`, never by reading this list against a member.\n * It is read from the member's record on every request, never cached in a\n * token or session: a revoke bites on the very next request.\n *\n * Granted and revoked as a whole set (`PUT /team/:memberId/capabilities`,\n * domains/team) by the roles in `CAPABILITY_GRANTED_BY`, never on oneself,\n * each change audited (`capability.granted` / `capability.revoked`). Revoking\n * takes nothing already made: the creator stays its community's admin.\n *\n * @module founder-schema/access/capabilities\n */\n\nimport { z } from 'zod';\nimport type { PlatformRole, Role } from './roles.js';\nimport { isPlatformRole } from './roles.js';\n\n/** ⚠ Stored on the member: append only. */\nexport const CAPABILITY_VALUES = ['create_communities'] as const;\nexport const capabilitySchema = z.enum(CAPABILITY_VALUES);\nexport type Capability = z.infer<typeof capabilitySchema>;\n\n/** Which active role may grant or revoke each capability. */\nexport const CAPABILITY_GRANTED_BY: Readonly<Record<Capability, readonly PlatformRole[]>> = {\n create_communities: ['superadmin', 'manager'],\n};\n\nexport function canGrantCapability(actingAs: Role, capability: Capability): boolean {\n return isPlatformRole(actingAs) && CAPABILITY_GRANTED_BY[capability].includes(actingAs);\n}\n"]}
@@ -64,8 +64,10 @@ export type Action = z.infer<typeof actionSchema>;
64
64
  * PlatformSettings product-wide configuration
65
65
  * Product something a founder builds, with its team
66
66
  * Review a member's stars and words on a product
67
+ * Community an official community (0.9.0)
68
+ * CommunitySuggestion a member's suggestion for a new community (0.9.0)
67
69
  */
68
- export declare const SUBJECT_VALUES: readonly ["all", "Dashboard", "Member", "FounderProfile", "Post", "Poll", "Comment", "Report", "SupportTicket", "SavedReply", "PlatformRole", "AuditEntry", "PlatformSettings", "Product", "Review"];
70
+ export declare const SUBJECT_VALUES: readonly ["all", "Dashboard", "Member", "FounderProfile", "Post", "Poll", "Comment", "Report", "SupportTicket", "SavedReply", "PlatformRole", "AuditEntry", "PlatformSettings", "Product", "Review", "Community", "CommunitySuggestion"];
69
71
  export declare const subjectSchema: z.ZodEnum<{
70
72
  all: "all";
71
73
  Dashboard: "Dashboard";
@@ -82,6 +84,8 @@ export declare const subjectSchema: z.ZodEnum<{
82
84
  PlatformSettings: "PlatformSettings";
83
85
  Product: "Product";
84
86
  Review: "Review";
87
+ Community: "Community";
88
+ CommunitySuggestion: "CommunitySuggestion";
85
89
  }>;
86
90
  export type Subject = z.infer<typeof subjectSchema>;
87
91
  /**
@@ -147,6 +151,21 @@ export interface SubjectFields {
147
151
  Review: {
148
152
  authorId: string;
149
153
  };
154
+ /**
155
+ * Its admins and moderators as id lists (0.9.0), so a rule reads "a
156
+ * community whose admins include me" (`{ adminIds: me }`). Both short by
157
+ * nature; ordinary members are never listed here (a community has no cap).
158
+ */
159
+ Community: {
160
+ _id: string;
161
+ adminIds: string[];
162
+ moderatorIds: string[];
163
+ };
164
+ /** `suggestedBy` so "your own" is a rule; `status` so only open ones are decided. */
165
+ CommunitySuggestion: {
166
+ suggestedBy: string;
167
+ status: string;
168
+ };
150
169
  }
151
170
  /** A document tagged with its subject type, as CASL's `subject('Post', doc)` makes one. */
152
171
  export type SubjectInstance = {
@@ -1 +1 @@
1
- {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../src/access/permissions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../src/access/permissions.ts"],"names":[],"mappings":"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"}
@@ -61,6 +61,8 @@ export const actionSchema = z.enum(ACTION_VALUES);
61
61
  * PlatformSettings product-wide configuration
62
62
  * Product something a founder builds, with its team
63
63
  * Review a member's stars and words on a product
64
+ * Community an official community (0.9.0)
65
+ * CommunitySuggestion a member's suggestion for a new community (0.9.0)
64
66
  */
65
67
  export const SUBJECT_VALUES = [
66
68
  'all',
@@ -78,6 +80,8 @@ export const SUBJECT_VALUES = [
78
80
  'PlatformSettings',
79
81
  'Product',
80
82
  'Review',
83
+ 'Community',
84
+ 'CommunitySuggestion',
81
85
  ];
82
86
  export const subjectSchema = z.enum(SUBJECT_VALUES);
83
87
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../src/access/permissions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,QAAQ;IACR,QAAQ;IACR,MAAM;IACN,QAAQ;IACR,QAAQ;IACR,UAAU;IACV,QAAQ;IACR,SAAS;IACT,SAAS;IACT,OAAO;IACP,WAAW;CACH,CAAC;AACX,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;AAGlD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,KAAK;IACL,WAAW;IACX,QAAQ;IACR,gBAAgB;IAChB,MAAM;IACN,MAAM;IACN,SAAS;IACT,QAAQ;IACR,eAAe;IACf,YAAY;IACZ,cAAc;IACd,YAAY;IACZ,kBAAkB;IAClB,SAAS;IACT,QAAQ;CACA,CAAC;AACX,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;AAyDpD;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC","sourcesContent":["/**\n * founder-schema: what a role may do, in CASL's terms\n *\n * Permissions are ACTION on SUBJECT, optionally narrowed by conditions on the\n * subject's fields (\"update a Post whose authorId is mine\"). The backend writes\n * one small policy per role from these words; the web receives the resulting\n * rules and asks the same questions to decide what to draw. Because both sides\n * spell actions and subjects from this file, a permission check with a typo is\n * a compile error, not a button that silently never shows.\n *\n * The SERVER decides; the web only mirrors. Every rule the web holds came from\n * the server for the role being acted as, and every request is checked again\n * on the server, so a tampered client can show itself a button and nothing\n * more.\n *\n * @module founder-schema/access/permissions\n */\n\nimport type { ForcedSubject, MongoAbility } from '@casl/ability';\nimport { z } from 'zod';\nimport type { PlatformRole } from './roles.js';\n\n/**\n * The verbs.\n *\n * manage every action (CASL's wildcard). Only superadmin holds it on `all`.\n * create, read, update, delete\n * moderate hide, restore or remove someone else's content, act on a report\n * assign grant or revoke (a platform role); take or hand over (a ticket)\n * suspend stop a member signing in to the product, and lift it\n * resolve close a support ticket or a report\n * reset clear a credential its owner forgot (a staff member's MPIN)\n * represent act for a product as one of its team: tag a post with it, reply\n * to its reviews, mark feedback helpful, confirm a tester\n *\n * Append only: a stored or packed rule names these.\n */\nexport const ACTION_VALUES = [\n 'manage',\n 'create',\n 'read',\n 'update',\n 'delete',\n 'moderate',\n 'assign',\n 'suspend',\n 'resolve',\n 'reset',\n 'represent',\n] as const;\nexport const actionSchema = z.enum(ACTION_VALUES);\nexport type Action = z.infer<typeof actionSchema>;\n\n/**\n * The things.\n *\n * all CASL's wildcard subject\n * Dashboard the staff overview: counts and trends\n * Member a person's account in this product\n * FounderProfile a member's public profile\n * Post, Poll, Comment\n * Report a member's report of a post, poll or comment\n * SupportTicket a member's request for help\n * SavedReply a support agent's reusable answer\n * PlatformRole a staff role, as something granted and revoked\n * AuditEntry the record of a staff action\n * PlatformSettings product-wide configuration\n * Product something a founder builds, with its team\n * Review a member's stars and words on a product\n */\nexport const SUBJECT_VALUES = [\n 'all',\n 'Dashboard',\n 'Member',\n 'FounderProfile',\n 'Post',\n 'Poll',\n 'Comment',\n 'Report',\n 'SupportTicket',\n 'SavedReply',\n 'PlatformRole',\n 'AuditEntry',\n 'PlatformSettings',\n 'Product',\n 'Review',\n] as const;\nexport const subjectSchema = z.enum(SUBJECT_VALUES);\nexport type Subject = z.infer<typeof subjectSchema>;\n\n/**\n * The fields a rule may set CONDITIONS on, per subject: the server's stored\n * names, ids as strings. Declared here so a condition on a field the subject\n * does not have (`{ ownerId: me }` on a Post) is a compile error, on both sides.\n *\n * A web screen does not evaluate these against an API response: a response\n * names things differently (`author.userId`, not `authorId`). Row-level answers\n * come from the server on the item itself (`viewer.canEdit`); the web's own\n * checks are type-level (\"may I moderate Reports at all\").\n */\nexport interface SubjectFields {\n Dashboard: Record<never, never>;\n /** `appRoles` is the staff roles held, so a rule can protect a role (\"never suspend a superadmin\"). */\n Member: { _id: string; appRoles: PlatformRole[] };\n FounderProfile: { userId: string };\n Post: { authorId: string };\n Poll: { authorId: string };\n Comment: { authorId: string };\n Report: { reporterId: string; status: string };\n SupportTicket: { memberId: string; assigneeId: string | null };\n SavedReply: { authorId: string };\n /** One role on one person: `memberId` is whose, so \"never your own\" is a rule too. */\n PlatformRole: { role: PlatformRole; memberId: string };\n /** `actorId` is null for an entry a server script wrote. */\n AuditEntry: { actorId: string | null };\n PlatformSettings: Record<never, never>;\n /**\n * The team, as three lists of member ids, so a rule reads \"a product whose\n * managers include me\" (`{ managerIds: me }`). `managerIds` is the owner and\n * the co-founders; `memberIds` is everyone on the team, owner included.\n */\n Product: { ownerId: string; managerIds: string[]; memberIds: string[] };\n Review: { authorId: string };\n}\n\n/** A document tagged with its subject type, as CASL's `subject('Post', doc)` makes one. */\nexport type SubjectInstance = {\n [K in keyof SubjectFields]: SubjectFields[K] & ForcedSubject<K>;\n}[keyof SubjectFields];\n\n/**\n * The ability both sides build: the backend from its policy, the web from the\n * rules the backend sends. Mongo-style conditions, typed by `SubjectFields`.\n */\nexport type AppAbility = MongoAbility<[Action, Subject | SubjectInstance]>;\n\n/**\n * Compile-time proof that `SubjectFields` names exactly the subjects, no more\n * and no fewer: adding a subject without its fields fails the build here.\n */\ntype Same<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false;\ntype Assert<T extends true> = T;\nexport type EverySubjectHasFields = Assert<Same<keyof SubjectFields, Exclude<Subject, 'all'>>>;\n\n/**\n * Rules as they cross the wire: CASL's `packRules` output, an array of\n * compact arrays. The web unpacks them with `unpackRules` before building its\n * ability; their inner shape is CASL's, so it is not re-described here.\n */\nexport const packedRulesSchema = z.array(z.array(z.unknown()));\nexport type PackedRules = z.infer<typeof packedRulesSchema>;\n"]}
1
+ {"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../src/access/permissions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,QAAQ;IACR,QAAQ;IACR,MAAM;IACN,QAAQ;IACR,QAAQ;IACR,UAAU;IACV,QAAQ;IACR,SAAS;IACT,SAAS;IACT,OAAO;IACP,WAAW;CACH,CAAC;AACX,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;AAGlD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,KAAK;IACL,WAAW;IACX,QAAQ;IACR,gBAAgB;IAChB,MAAM;IACN,MAAM;IACN,SAAS;IACT,QAAQ;IACR,eAAe;IACf,YAAY;IACZ,cAAc;IACd,YAAY;IACZ,kBAAkB;IAClB,SAAS;IACT,QAAQ;IACR,WAAW;IACX,qBAAqB;CACb,CAAC;AACX,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;AAiEpD;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC","sourcesContent":["/**\n * founder-schema: what a role may do, in CASL's terms\n *\n * Permissions are ACTION on SUBJECT, optionally narrowed by conditions on the\n * subject's fields (\"update a Post whose authorId is mine\"). The backend writes\n * one small policy per role from these words; the web receives the resulting\n * rules and asks the same questions to decide what to draw. Because both sides\n * spell actions and subjects from this file, a permission check with a typo is\n * a compile error, not a button that silently never shows.\n *\n * The SERVER decides; the web only mirrors. Every rule the web holds came from\n * the server for the role being acted as, and every request is checked again\n * on the server, so a tampered client can show itself a button and nothing\n * more.\n *\n * @module founder-schema/access/permissions\n */\n\nimport type { ForcedSubject, MongoAbility } from '@casl/ability';\nimport { z } from 'zod';\nimport type { PlatformRole } from './roles.js';\n\n/**\n * The verbs.\n *\n * manage every action (CASL's wildcard). Only superadmin holds it on `all`.\n * create, read, update, delete\n * moderate hide, restore or remove someone else's content, act on a report\n * assign grant or revoke (a platform role); take or hand over (a ticket)\n * suspend stop a member signing in to the product, and lift it\n * resolve close a support ticket or a report\n * reset clear a credential its owner forgot (a staff member's MPIN)\n * represent act for a product as one of its team: tag a post with it, reply\n * to its reviews, mark feedback helpful, confirm a tester\n *\n * Append only: a stored or packed rule names these.\n */\nexport const ACTION_VALUES = [\n 'manage',\n 'create',\n 'read',\n 'update',\n 'delete',\n 'moderate',\n 'assign',\n 'suspend',\n 'resolve',\n 'reset',\n 'represent',\n] as const;\nexport const actionSchema = z.enum(ACTION_VALUES);\nexport type Action = z.infer<typeof actionSchema>;\n\n/**\n * The things.\n *\n * all CASL's wildcard subject\n * Dashboard the staff overview: counts and trends\n * Member a person's account in this product\n * FounderProfile a member's public profile\n * Post, Poll, Comment\n * Report a member's report of a post, poll or comment\n * SupportTicket a member's request for help\n * SavedReply a support agent's reusable answer\n * PlatformRole a staff role, as something granted and revoked\n * AuditEntry the record of a staff action\n * PlatformSettings product-wide configuration\n * Product something a founder builds, with its team\n * Review a member's stars and words on a product\n * Community an official community (0.9.0)\n * CommunitySuggestion a member's suggestion for a new community (0.9.0)\n */\nexport const SUBJECT_VALUES = [\n 'all',\n 'Dashboard',\n 'Member',\n 'FounderProfile',\n 'Post',\n 'Poll',\n 'Comment',\n 'Report',\n 'SupportTicket',\n 'SavedReply',\n 'PlatformRole',\n 'AuditEntry',\n 'PlatformSettings',\n 'Product',\n 'Review',\n 'Community',\n 'CommunitySuggestion',\n] as const;\nexport const subjectSchema = z.enum(SUBJECT_VALUES);\nexport type Subject = z.infer<typeof subjectSchema>;\n\n/**\n * The fields a rule may set CONDITIONS on, per subject: the server's stored\n * names, ids as strings. Declared here so a condition on a field the subject\n * does not have (`{ ownerId: me }` on a Post) is a compile error, on both sides.\n *\n * A web screen does not evaluate these against an API response: a response\n * names things differently (`author.userId`, not `authorId`). Row-level answers\n * come from the server on the item itself (`viewer.canEdit`); the web's own\n * checks are type-level (\"may I moderate Reports at all\").\n */\nexport interface SubjectFields {\n Dashboard: Record<never, never>;\n /** `appRoles` is the staff roles held, so a rule can protect a role (\"never suspend a superadmin\"). */\n Member: { _id: string; appRoles: PlatformRole[] };\n FounderProfile: { userId: string };\n Post: { authorId: string };\n Poll: { authorId: string };\n Comment: { authorId: string };\n Report: { reporterId: string; status: string };\n SupportTicket: { memberId: string; assigneeId: string | null };\n SavedReply: { authorId: string };\n /** One role on one person: `memberId` is whose, so \"never your own\" is a rule too. */\n PlatformRole: { role: PlatformRole; memberId: string };\n /** `actorId` is null for an entry a server script wrote. */\n AuditEntry: { actorId: string | null };\n PlatformSettings: Record<never, never>;\n /**\n * The team, as three lists of member ids, so a rule reads \"a product whose\n * managers include me\" (`{ managerIds: me }`). `managerIds` is the owner and\n * the co-founders; `memberIds` is everyone on the team, owner included.\n */\n Product: { ownerId: string; managerIds: string[]; memberIds: string[] };\n Review: { authorId: string };\n /**\n * Its admins and moderators as id lists (0.9.0), so a rule reads \"a\n * community whose admins include me\" (`{ adminIds: me }`). Both short by\n * nature; ordinary members are never listed here (a community has no cap).\n */\n Community: { _id: string; adminIds: string[]; moderatorIds: string[] };\n /** `suggestedBy` so \"your own\" is a rule; `status` so only open ones are decided. */\n CommunitySuggestion: { suggestedBy: string; status: string };\n}\n\n/** A document tagged with its subject type, as CASL's `subject('Post', doc)` makes one. */\nexport type SubjectInstance = {\n [K in keyof SubjectFields]: SubjectFields[K] & ForcedSubject<K>;\n}[keyof SubjectFields];\n\n/**\n * The ability both sides build: the backend from its policy, the web from the\n * rules the backend sends. Mongo-style conditions, typed by `SubjectFields`.\n */\nexport type AppAbility = MongoAbility<[Action, Subject | SubjectInstance]>;\n\n/**\n * Compile-time proof that `SubjectFields` names exactly the subjects, no more\n * and no fewer: adding a subject without its fields fails the build here.\n */\ntype Same<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false;\ntype Assert<T extends true> = T;\nexport type EverySubjectHasFields = Assert<Same<keyof SubjectFields, Exclude<Subject, 'all'>>>;\n\n/**\n * Rules as they cross the wire: CASL's `packRules` output, an array of\n * compact arrays. The web unpacks them with `unpackRules` before building its\n * ability; their inner shape is CASL's, so it is not re-described here.\n */\nexport const packedRulesSchema = z.array(z.array(z.unknown()));\nexport type PackedRules = z.infer<typeof packedRulesSchema>;\n"]}
@@ -27,7 +27,7 @@ import { z } from 'zod';
27
27
  * As a tuple, the way `GENDER_VALUES` is, so a consumer can iterate it (a
28
28
  * translation-coverage test does exactly that) and TypeScript keeps the union.
29
29
  */
30
- export declare const API_ERROR_CODE_VALUES: readonly ["INTERNAL_ERROR", "VALIDATION_ERROR", "NOT_FOUND", "CONFLICT", "RATE_LIMITED", "SERVICE_UNAVAILABLE", "UNAUTHORIZED", "INVALID_TOKEN", "TOKEN_EXPIRED", "SESSION_INVALIDATED", "FORBIDDEN", "ONBOARDING_INCOMPLETE", "ALREADY_ONBOARDED", "UNDER_AGE", "UPLOAD_NOT_ATTACHABLE", "UPLOAD_TOO_LARGE", "UPLOAD_WRONG_TYPE", "UPLOAD_NOT_RECEIVED", "UPLOAD_PROCESSING", "UPLOAD_UNREADABLE", "UPLOAD_TOO_MANY_PIXELS", "UPLOAD_LIMIT_REACHED", "UPLOAD_IN_USE", "EDIT_WINDOW_CLOSED", "POLL_NOT_EDITABLE", "POLL_CLOSED", "POLL_CHOICE_INVALID", "REPLY_TOO_DEEP", "CANNOT_REPORT_OWN", "FEEDBACK_NOT_MARKABLE", "PRODUCT_LIMIT_REACHED", "PRODUCT_NOT_LISTABLE", "PRODUCT_ARCHIVED", "PRODUCT_NOT_YOURS", "PRODUCT_ALREADY_MEMBER", "PRODUCT_MEMBER_NOT_INVITABLE", "PRODUCT_NOT_ON_TEAM", "PRODUCT_OWNER_MUST_TRANSFER", "PRODUCT_OWN_TEAM", "TESTER_NOT_ELIGIBLE", "REVIEW_EXISTS", "REVIEW_OWN_VOTE", "MODERATION_ACTION_INVALID", "CHAT_WITH_SELF", "ACCOUNT_SUSPENDED", "ROLE_NOT_HELD", "MPIN_NOT_SET", "MPIN_ALREADY_SET", "MPIN_INCORRECT", "MPIN_LOCKED", "CANNOT_CHANGE_OWN_ROLES", "ROLE_NOT_GRANTABLE", "TICKET_FULL", "SAVED_REPLIES_FULL"];
30
+ export declare const API_ERROR_CODE_VALUES: readonly ["INTERNAL_ERROR", "VALIDATION_ERROR", "NOT_FOUND", "CONFLICT", "RATE_LIMITED", "SERVICE_UNAVAILABLE", "UNAUTHORIZED", "INVALID_TOKEN", "TOKEN_EXPIRED", "SESSION_INVALIDATED", "FORBIDDEN", "ONBOARDING_INCOMPLETE", "ALREADY_ONBOARDED", "UNDER_AGE", "UPLOAD_NOT_ATTACHABLE", "UPLOAD_TOO_LARGE", "UPLOAD_WRONG_TYPE", "UPLOAD_NOT_RECEIVED", "UPLOAD_PROCESSING", "UPLOAD_UNREADABLE", "UPLOAD_TOO_MANY_PIXELS", "UPLOAD_LIMIT_REACHED", "UPLOAD_IN_USE", "EDIT_WINDOW_CLOSED", "POLL_NOT_EDITABLE", "POLL_CLOSED", "POLL_CHOICE_INVALID", "REPLY_TOO_DEEP", "CANNOT_REPORT_OWN", "FEEDBACK_NOT_MARKABLE", "CANNOT_REPOST_OWN", "CANNOT_REPOST_A_REPOST", "ALREADY_REPOSTED", "PRODUCT_LIMIT_REACHED", "PRODUCT_NOT_LISTABLE", "PRODUCT_ARCHIVED", "PRODUCT_NOT_YOURS", "PRODUCT_ALREADY_MEMBER", "PRODUCT_MEMBER_NOT_INVITABLE", "PRODUCT_NOT_ON_TEAM", "PRODUCT_OWNER_MUST_TRANSFER", "PRODUCT_OWN_TEAM", "TESTER_NOT_ELIGIBLE", "COMMUNITY_JOIN_REQUIRED", "COMMUNITY_NAME_TAKEN", "COMMUNITY_ALREADY_MEMBER", "COMMUNITY_MEMBER_NOT_INVITABLE", "COMMUNITY_MEMBER_BANNED", "COMMUNITY_INVITE_NOT_NEEDED", "COMMUNITY_LAST_ADMIN", "COMMUNITY_ARCHIVED", "COMMUNITY_EXPOSURE_UNCONFIRMED", "COMMUNITY_PINS_FULL", "COMMUNITY_ANNOUNCE_TOO_SOON", "COMMUNITY_CONTENT_NOT_REPOSTABLE", "COMMUNITY_SUGGESTIONS_FULL", "COMMUNITY_SUGGESTION_DECIDED", "REVIEW_EXISTS", "REVIEW_OWN_VOTE", "MODERATION_ACTION_INVALID", "CHAT_WITH_SELF", "ACCOUNT_SUSPENDED", "ROLE_NOT_HELD", "MPIN_NOT_SET", "MPIN_ALREADY_SET", "MPIN_INCORRECT", "MPIN_LOCKED", "CANNOT_CHANGE_OWN_ROLES", "ROLE_NOT_GRANTABLE", "CANNOT_CHANGE_OWN_CAPABILITIES", "CAPABILITY_NOT_GRANTABLE", "TICKET_FULL", "SAVED_REPLIES_FULL"];
31
31
  export declare const apiErrorCodeSchema: z.ZodEnum<{
32
32
  INTERNAL_ERROR: "INTERNAL_ERROR";
33
33
  VALIDATION_ERROR: "VALIDATION_ERROR";
@@ -59,6 +59,9 @@ export declare const apiErrorCodeSchema: z.ZodEnum<{
59
59
  REPLY_TOO_DEEP: "REPLY_TOO_DEEP";
60
60
  CANNOT_REPORT_OWN: "CANNOT_REPORT_OWN";
61
61
  FEEDBACK_NOT_MARKABLE: "FEEDBACK_NOT_MARKABLE";
62
+ CANNOT_REPOST_OWN: "CANNOT_REPOST_OWN";
63
+ CANNOT_REPOST_A_REPOST: "CANNOT_REPOST_A_REPOST";
64
+ ALREADY_REPOSTED: "ALREADY_REPOSTED";
62
65
  PRODUCT_LIMIT_REACHED: "PRODUCT_LIMIT_REACHED";
63
66
  PRODUCT_NOT_LISTABLE: "PRODUCT_NOT_LISTABLE";
64
67
  PRODUCT_ARCHIVED: "PRODUCT_ARCHIVED";
@@ -69,6 +72,20 @@ export declare const apiErrorCodeSchema: z.ZodEnum<{
69
72
  PRODUCT_OWNER_MUST_TRANSFER: "PRODUCT_OWNER_MUST_TRANSFER";
70
73
  PRODUCT_OWN_TEAM: "PRODUCT_OWN_TEAM";
71
74
  TESTER_NOT_ELIGIBLE: "TESTER_NOT_ELIGIBLE";
75
+ COMMUNITY_JOIN_REQUIRED: "COMMUNITY_JOIN_REQUIRED";
76
+ COMMUNITY_NAME_TAKEN: "COMMUNITY_NAME_TAKEN";
77
+ COMMUNITY_ALREADY_MEMBER: "COMMUNITY_ALREADY_MEMBER";
78
+ COMMUNITY_MEMBER_NOT_INVITABLE: "COMMUNITY_MEMBER_NOT_INVITABLE";
79
+ COMMUNITY_MEMBER_BANNED: "COMMUNITY_MEMBER_BANNED";
80
+ COMMUNITY_INVITE_NOT_NEEDED: "COMMUNITY_INVITE_NOT_NEEDED";
81
+ COMMUNITY_LAST_ADMIN: "COMMUNITY_LAST_ADMIN";
82
+ COMMUNITY_ARCHIVED: "COMMUNITY_ARCHIVED";
83
+ COMMUNITY_EXPOSURE_UNCONFIRMED: "COMMUNITY_EXPOSURE_UNCONFIRMED";
84
+ COMMUNITY_PINS_FULL: "COMMUNITY_PINS_FULL";
85
+ COMMUNITY_ANNOUNCE_TOO_SOON: "COMMUNITY_ANNOUNCE_TOO_SOON";
86
+ COMMUNITY_CONTENT_NOT_REPOSTABLE: "COMMUNITY_CONTENT_NOT_REPOSTABLE";
87
+ COMMUNITY_SUGGESTIONS_FULL: "COMMUNITY_SUGGESTIONS_FULL";
88
+ COMMUNITY_SUGGESTION_DECIDED: "COMMUNITY_SUGGESTION_DECIDED";
72
89
  REVIEW_EXISTS: "REVIEW_EXISTS";
73
90
  REVIEW_OWN_VOTE: "REVIEW_OWN_VOTE";
74
91
  MODERATION_ACTION_INVALID: "MODERATION_ACTION_INVALID";
@@ -81,6 +98,8 @@ export declare const apiErrorCodeSchema: z.ZodEnum<{
81
98
  MPIN_LOCKED: "MPIN_LOCKED";
82
99
  CANNOT_CHANGE_OWN_ROLES: "CANNOT_CHANGE_OWN_ROLES";
83
100
  ROLE_NOT_GRANTABLE: "ROLE_NOT_GRANTABLE";
101
+ CANNOT_CHANGE_OWN_CAPABILITIES: "CANNOT_CHANGE_OWN_CAPABILITIES";
102
+ CAPABILITY_NOT_GRANTABLE: "CAPABILITY_NOT_GRANTABLE";
84
103
  TICKET_FULL: "TICKET_FULL";
85
104
  SAVED_REPLIES_FULL: "SAVED_REPLIES_FULL";
86
105
  }>;
@@ -117,6 +136,9 @@ export declare const API_ERROR_CODE: {
117
136
  REPLY_TOO_DEEP: "REPLY_TOO_DEEP";
118
137
  CANNOT_REPORT_OWN: "CANNOT_REPORT_OWN";
119
138
  FEEDBACK_NOT_MARKABLE: "FEEDBACK_NOT_MARKABLE";
139
+ CANNOT_REPOST_OWN: "CANNOT_REPOST_OWN";
140
+ CANNOT_REPOST_A_REPOST: "CANNOT_REPOST_A_REPOST";
141
+ ALREADY_REPOSTED: "ALREADY_REPOSTED";
120
142
  PRODUCT_LIMIT_REACHED: "PRODUCT_LIMIT_REACHED";
121
143
  PRODUCT_NOT_LISTABLE: "PRODUCT_NOT_LISTABLE";
122
144
  PRODUCT_ARCHIVED: "PRODUCT_ARCHIVED";
@@ -127,6 +149,20 @@ export declare const API_ERROR_CODE: {
127
149
  PRODUCT_OWNER_MUST_TRANSFER: "PRODUCT_OWNER_MUST_TRANSFER";
128
150
  PRODUCT_OWN_TEAM: "PRODUCT_OWN_TEAM";
129
151
  TESTER_NOT_ELIGIBLE: "TESTER_NOT_ELIGIBLE";
152
+ COMMUNITY_JOIN_REQUIRED: "COMMUNITY_JOIN_REQUIRED";
153
+ COMMUNITY_NAME_TAKEN: "COMMUNITY_NAME_TAKEN";
154
+ COMMUNITY_ALREADY_MEMBER: "COMMUNITY_ALREADY_MEMBER";
155
+ COMMUNITY_MEMBER_NOT_INVITABLE: "COMMUNITY_MEMBER_NOT_INVITABLE";
156
+ COMMUNITY_MEMBER_BANNED: "COMMUNITY_MEMBER_BANNED";
157
+ COMMUNITY_INVITE_NOT_NEEDED: "COMMUNITY_INVITE_NOT_NEEDED";
158
+ COMMUNITY_LAST_ADMIN: "COMMUNITY_LAST_ADMIN";
159
+ COMMUNITY_ARCHIVED: "COMMUNITY_ARCHIVED";
160
+ COMMUNITY_EXPOSURE_UNCONFIRMED: "COMMUNITY_EXPOSURE_UNCONFIRMED";
161
+ COMMUNITY_PINS_FULL: "COMMUNITY_PINS_FULL";
162
+ COMMUNITY_ANNOUNCE_TOO_SOON: "COMMUNITY_ANNOUNCE_TOO_SOON";
163
+ COMMUNITY_CONTENT_NOT_REPOSTABLE: "COMMUNITY_CONTENT_NOT_REPOSTABLE";
164
+ COMMUNITY_SUGGESTIONS_FULL: "COMMUNITY_SUGGESTIONS_FULL";
165
+ COMMUNITY_SUGGESTION_DECIDED: "COMMUNITY_SUGGESTION_DECIDED";
130
166
  REVIEW_EXISTS: "REVIEW_EXISTS";
131
167
  REVIEW_OWN_VOTE: "REVIEW_OWN_VOTE";
132
168
  MODERATION_ACTION_INVALID: "MODERATION_ACTION_INVALID";
@@ -139,6 +175,8 @@ export declare const API_ERROR_CODE: {
139
175
  MPIN_LOCKED: "MPIN_LOCKED";
140
176
  CANNOT_CHANGE_OWN_ROLES: "CANNOT_CHANGE_OWN_ROLES";
141
177
  ROLE_NOT_GRANTABLE: "ROLE_NOT_GRANTABLE";
178
+ CANNOT_CHANGE_OWN_CAPABILITIES: "CANNOT_CHANGE_OWN_CAPABILITIES";
179
+ CAPABILITY_NOT_GRANTABLE: "CAPABILITY_NOT_GRANTABLE";
142
180
  TICKET_FULL: "TICKET_FULL";
143
181
  SAVED_REPLIES_FULL: "SAVED_REPLIES_FULL";
144
182
  };
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;GAGG;AACH,eAAO,MAAM,qBAAqB,kmCAuKxB,CAAC;AAEX,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAAgC,CAAC;AAChE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,4DAA4D;AAC5D,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAA0B,CAAC"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;GAGG;AACH,eAAO,MAAM,qBAAqB,snDAsOxB,CAAC;AAEX,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAAgC,CAAC;AAChE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,4DAA4D;AAC5D,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAA0B,CAAC"}
@@ -109,6 +109,12 @@ export const API_ERROR_CODE_VALUES = [
109
109
  * team's.
110
110
  */
111
111
  'FEEDBACK_NOT_MARKABLE',
112
+ /** Reposting your own post or poll (0.8.0). */
113
+ 'CANNOT_REPOST_OWN',
114
+ /** Reposting a repost (0.8.0): share the original instead. */
115
+ 'CANNOT_REPOST_A_REPOST',
116
+ /** You already have an active repost of this item (0.8.0). Undo it first. 409. */
117
+ 'ALREADY_REPOSTED',
112
118
  // ── Products ──
113
119
  /** Founders own at most `PRODUCTS_OWNED_MAX` products. Delete one to add another. */
114
120
  'PRODUCT_LIMIT_REACHED',
@@ -143,6 +149,59 @@ export const API_ERROR_CODE_VALUES = [
143
149
  'PRODUCT_OWN_TEAM',
144
150
  /** Confirming as a tester someone who has given this product no feedback. */
145
151
  'TESTER_NOT_ELIGIBLE',
152
+ // ── Communities (0.9.0) ──
153
+ /**
154
+ * Something in a community you can SEE but must join first: taking part in
155
+ * it (posting into it, commenting, reacting, voting, saving, reporting,
156
+ * editing), or reading a private community's feed, pinned posts or
157
+ * members. Join (public) or request to join (private). 403. What you cannot
158
+ * see at all answers NOT_FOUND, as content of a private or hidden community
159
+ * looked up by its id does.
160
+ */
161
+ 'COMMUNITY_JOIN_REQUIRED',
162
+ /** A live community already has this name (case and spacing ignored). 409. */
163
+ 'COMMUNITY_NAME_TAKEN',
164
+ /** Joining, requesting or inviting someone who is already a member, has requested or is invited. 409. */
165
+ 'COMMUNITY_ALREADY_MEMBER',
166
+ /**
167
+ * Inviting someone who cannot join: not finished onboarding, paused,
168
+ * suspended or deleted. One code for all four, so it reveals none of them.
169
+ */
170
+ 'COMMUNITY_MEMBER_NOT_INVITABLE',
171
+ /** Inviting someone banned from this community. Unban them first. */
172
+ 'COMMUNITY_MEMBER_BANNED',
173
+ /** Inviting into a public community: anyone can join it, share its link instead. */
174
+ 'COMMUNITY_INVITE_NOT_NEEDED',
175
+ /**
176
+ * The last admin leaving, stepping down or being removed, or deleting their
177
+ * account while someone else could be made admin. Make someone else admin
178
+ * first. On an account deletion, `details.communities` names them.
179
+ */
180
+ 'COMMUNITY_LAST_ADMIN',
181
+ /**
182
+ * The community is archived (read-only): no new posts, polls, comments,
183
+ * reactions, votes, saves, reports, edits, joins, approvals, invitations
184
+ * or pins. Its moderators may still hide what breaks its rules.
185
+ */
186
+ 'COMMUNITY_ARCHIVED',
187
+ /**
188
+ * A type change that exposes what was already posted (toward public, or
189
+ * hidden to private) sent without `confirmExposure: true`.
190
+ */
191
+ 'COMMUNITY_EXPOSURE_UNCONFIRMED',
192
+ /** `COMMUNITY_PINS_MAX` announcements are pinned already. Unpin one first. */
193
+ 'COMMUNITY_PINS_FULL',
194
+ /**
195
+ * Notifying every member again within `COMMUNITY_ANNOUNCE_INTERVAL_MS`.
196
+ * `details.nextAt` says when it is allowed. Pinning without notifying works.
197
+ */
198
+ 'COMMUNITY_ANNOUNCE_TOO_SOON',
199
+ /** Reposting a post or poll that lives in a community: community content stays in it. */
200
+ 'COMMUNITY_CONTENT_NOT_REPOSTABLE',
201
+ /** `COMMUNITY_SUGGESTIONS_OPEN_MAX` suggestions are already waiting for review. */
202
+ 'COMMUNITY_SUGGESTIONS_FULL',
203
+ /** Approving or declining a suggestion that was already decided. 409. */
204
+ 'COMMUNITY_SUGGESTION_DECIDED',
146
205
  // ── Reviews ──
147
206
  /** One review per member per product. Edit yours instead. */
148
207
  'REVIEW_EXISTS',
@@ -178,6 +237,10 @@ export const API_ERROR_CODE_VALUES = [
178
237
  'CANNOT_CHANGE_OWN_ROLES',
179
238
  /** Granting or revoking a role the role you act as may not touch. */
180
239
  'ROLE_NOT_GRANTABLE',
240
+ /** Changing your own capabilities (0.9.0). Someone else has to. */
241
+ 'CANNOT_CHANGE_OWN_CAPABILITIES',
242
+ /** Granting or revoking a capability the role you act as may not touch (0.9.0). */
243
+ 'CAPABILITY_NOT_GRANTABLE',
181
244
  // ── Support ──
182
245
  /** The ticket holds `TICKET_MESSAGES_MAX` messages. Open a new one. */
183
246
  'TICKET_FULL',
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,iBAAiB;IACjB,gFAAgF;IAChF,gBAAgB;IAChB,yEAAyE;IACzE,kBAAkB;IAClB,2EAA2E;IAC3E,WAAW;IACX,wEAAwE;IACxE,UAAU;IACV,uEAAuE;IACvE,cAAc;IACd,2EAA2E;IAC3E,qBAAqB;IAErB,oBAAoB;IACpB,4BAA4B;IAC5B,cAAc;IACd,sDAAsD;IACtD,eAAe;IACf,qEAAqE;IACrE,eAAe;IACf,oEAAoE;IACpE,qBAAqB;IACrB,6CAA6C;IAC7C,WAAW;IAEX,mBAAmB;IACnB,mFAAmF;IACnF,uBAAuB;IACvB,gFAAgF;IAChF,mBAAmB;IACnB,uEAAuE;IACvE,WAAW;IAEX,kDAAkD;IAClD;;;;;OAKG;IACH,uBAAuB;IACvB,sCAAsC;IACtC,kBAAkB;IAClB;;;OAGG;IACH,mBAAmB;IACnB,2EAA2E;IAC3E,qBAAqB;IACrB;;;;OAIG;IACH,mBAAmB;IACnB,0FAA0F;IAC1F,mBAAmB;IACnB,mEAAmE;IACnE,wBAAwB;IACxB,sEAAsE;IACtE,sBAAsB;IACtB,wFAAwF;IACxF,eAAe;IAEf,iBAAiB;IACjB,+CAA+C;IAC/C,oBAAoB;IACpB,iEAAiE;IACjE,mBAAmB;IACnB,kDAAkD;IAClD,aAAa;IACb,oFAAoF;IACpF,qBAAqB;IACrB,qDAAqD;IACrD,gBAAgB;IAChB,8FAA8F;IAC9F,mBAAmB;IACnB;;;;OAIG;IACH,uBAAuB;IAEvB,iBAAiB;IACjB,qFAAqF;IACrF,uBAAuB;IACvB;;;OAGG;IACH,sBAAsB;IACtB;;;;OAIG;IACH,kBAAkB;IAClB,gFAAgF;IAChF,mBAAmB;IACnB,sEAAsE;IACtE,wBAAwB;IACxB;;;OAGG;IACH,8BAA8B;IAC9B,8FAA8F;IAC9F,qBAAqB;IACrB;;;OAGG;IACH,6BAA6B;IAC7B,yFAAyF;IACzF,kBAAkB;IAClB,6EAA6E;IAC7E,qBAAqB;IAErB,gBAAgB;IAChB,6DAA6D;IAC7D,eAAe;IACf,sCAAsC;IACtC,iBAAiB;IAEjB,mBAAmB;IACnB;;;OAGG;IACH,2BAA2B;IAE3B,aAAa;IACb,4CAA4C;IAC5C,gBAAgB;IAEhB,oBAAoB;IACpB,qFAAqF;IACrF,mBAAmB;IAEnB,2BAA2B;IAC3B;;;OAGG;IACH,eAAe;IACf,4EAA4E;IAC5E,cAAc;IACd,8DAA8D;IAC9D,kBAAkB;IAClB,4EAA4E;IAC5E,gBAAgB;IAChB,0EAA0E;IAC1E,aAAa;IACb,oDAAoD;IACpD,yBAAyB;IACzB,qEAAqE;IACrE,oBAAoB;IAEpB,gBAAgB;IAChB,uEAAuE;IACvE,aAAa;IACb,0EAA0E;IAC1E,oBAAoB;CACZ,CAAC;AAEX,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;AAGhE,4DAA4D;AAC5D,MAAM,CAAC,MAAM,cAAc,GAAG,kBAAkB,CAAC,IAAI,CAAC","sourcesContent":["/**\n * founder-schema — Every error code the API answers with\n *\n * THE CODE IS WHAT A CLIENT ACTS ON; the `message` beside it is English for\n * logs and for a client too old to know the code. A client words each code in\n * the member's language, and a test on the web side asserts every value here\n * has a message in every locale it ships.\n *\n * Before 0.2.0 the backend kept this list to itself, and every domain refusal\n * (a closed poll, a reply to a reply, the edit window) went out as\n * `VALIDATION_ERROR` with an English sentence, so the only way for a client to\n * tell them apart was to match on the wording.\n *\n * WHEN TO USE WHICH:\n * - A request that does not match its schema: `VALIDATION_ERROR`, with every\n * problem in `error.details.issues`.\n * - A well-formed request the current STATE of things refuses: its own code\n * below. These carry no issues; the code says it all.\n *\n * ⚠ Append freely; remove deliberately. A code removed here that an older\n * server still sends fails the client's parse of the error envelope.\n *\n * @module founder-schema/core/errors\n */\n\nimport { z } from 'zod';\n\n/**\n * As a tuple, the way `GENDER_VALUES` is, so a consumer can iterate it (a\n * translation-coverage test does exactly that) and TypeScript keeps the union.\n */\nexport const API_ERROR_CODE_VALUES = [\n // ── Anything ──\n /** A bug or an outage on our side. The message says nothing more on purpose. */\n 'INTERNAL_ERROR',\n /** The request did not match its schema. `details.issues` says where. */\n 'VALIDATION_ERROR',\n /** No such thing, or not one you may see. Deliberately the same answer. */\n 'NOT_FOUND',\n /** Two writes raced for something unique; retrying usually succeeds. */\n 'CONFLICT',\n /** Too many requests from this member or address. Wait, then retry. */\n 'RATE_LIMITED',\n /** Something we depend on (the chat server) is down or not switched on. */\n 'SERVICE_UNAVAILABLE',\n\n // ── Who you are ──\n /** No credential at all. */\n 'UNAUTHORIZED',\n /** A credential that is not ours, or not readable. */\n 'INVALID_TOKEN',\n /** A credential that was ours and has expired. Refresh and retry. */\n 'TOKEN_EXPIRED',\n /** Signed out everywhere, or the account changed. Sign in again. */\n 'SESSION_INVALIDATED',\n /** Signed in, and not allowed to do this. */\n 'FORBIDDEN',\n\n // ── Onboarding ──\n /** Finish onboarding first. The client routes here instead of showing an error. */\n 'ONBOARDING_INCOMPLETE',\n /** Onboarding is already finished; there is nothing to save or finish again. */\n 'ALREADY_ONBOARDED',\n /** Under `MIN_AGE_YEARS`. Refused at the draft and again at finish. */\n 'UNDER_AGE',\n\n // ── Uploads: completing one, and attaching it ──\n /**\n * Not an upload of yours, not processed yet, already attached somewhere, or\n * for the other purpose (an avatar upload on a post). One code for all of\n * them on purpose: telling \"someone else's\" apart from \"does not exist\" would\n * let a caller probe other members' media ids.\n */\n 'UPLOAD_NOT_ATTACHABLE',\n /** Larger than `UPLOAD_MAX_BYTES`. */\n 'UPLOAD_TOO_LARGE',\n /**\n * Not one of the image types this product accepts (by the file's own bytes,\n * whatever its name or declared type), or not an image at all.\n */\n 'UPLOAD_WRONG_TYPE',\n /** Completed before the file reached storage: upload it, then complete. */\n 'UPLOAD_NOT_RECEIVED',\n /**\n * Another request is processing this upload right now (a retry or a double\n * tap). Nothing is wrong: complete again in a moment and it answers with\n * the image. 409.\n */\n 'UPLOAD_PROCESSING',\n /** A JPEG, PNG, WebP or GIF by its bytes that cannot be decoded: truncated or corrupt. */\n 'UPLOAD_UNREADABLE',\n /** More than `MEDIA_MAX_INPUT_PIXELS`; refused before decoding. */\n 'UPLOAD_TOO_MANY_PIXELS',\n /** `MEDIA_PENDING_MAX` uploads are already waiting to be attached. */\n 'UPLOAD_LIMIT_REACHED',\n /** Discarding an upload that is attached: remove it from its post or avatar instead. */\n 'UPLOAD_IN_USE',\n\n // ── The feed ──\n /** The post is older than `EDIT_WINDOW_MS`. */\n 'EDIT_WINDOW_CLOSED',\n /** A poll is never editable: people have already voted on it. */\n 'POLL_NOT_EDITABLE',\n /** The poll has closed, by the server's clock. */\n 'POLL_CLOSED',\n /** No option, several on a single-choice poll, or an option that does not exist. */\n 'POLL_CHOICE_INVALID',\n /** A reply to a reply. One level of nesting only. */\n 'REPLY_TOO_DEEP',\n /** Reporting your own post, poll or comment (or, since 0.6.0, your own product or review). */\n 'CANNOT_REPORT_OWN',\n /**\n * Marking a comment \"this helped\" that is not feedback on your product: the\n * post is not tagged with a product you are on, or the comment is your own\n * team's.\n */\n 'FEEDBACK_NOT_MARKABLE',\n\n // ── Products ──\n /** Founders own at most `PRODUCTS_OWNED_MAX` products. Delete one to add another. */\n 'PRODUCT_LIMIT_REACHED',\n /**\n * Listing a product that is not ready for Discover, or removing what keeps a\n * listed one ready. `details.missing`: some of `logo`, `tagline`, `screenshot`.\n */\n 'PRODUCT_NOT_LISTABLE',\n /**\n * The product is archived and frozen until its owner restores it: no new\n * posts about it, reviews, upvotes, replies, helpful marks, testers or\n * invitations.\n */\n 'PRODUCT_ARCHIVED',\n /** Tagging a post with, or acting for, a product you are not on the team of. */\n 'PRODUCT_NOT_YOURS',\n /** Inviting someone who is already on the team or already invited. */\n 'PRODUCT_ALREADY_MEMBER',\n /**\n * Inviting someone who cannot join a team: not finished onboarding, paused,\n * suspended or deleted. One code for all four, so it reveals none of them.\n */\n 'PRODUCT_MEMBER_NOT_INVITABLE',\n /** Handing over to, or re-roling, someone who is not on the team (or invited, for a role). */\n 'PRODUCT_NOT_ON_TEAM',\n /**\n * The owner leaving, being removed, or having their role changed. Ownership\n * moves only by handing the product to someone first.\n */\n 'PRODUCT_OWNER_MUST_TRANSFER',\n /** Upvoting, reviewing or confirming yourself as a tester of your own team's product. */\n 'PRODUCT_OWN_TEAM',\n /** Confirming as a tester someone who has given this product no feedback. */\n 'TESTER_NOT_ELIGIBLE',\n\n // ── Reviews ──\n /** One review per member per product. Edit yours instead. */\n 'REVIEW_EXISTS',\n /** Voting your own review helpful. */\n 'REVIEW_OWN_VOTE',\n\n // ── Moderation ──\n /**\n * A decision that does not apply to that kind of thing: removing a product\n * (staff hide one, never delete it). `MODERATION_ACTIONS_BY_TARGET_KIND`.\n */\n 'MODERATION_ACTION_INVALID',\n\n // ── Chat ──\n /** Opening a conversation with yourself. */\n 'CHAT_WITH_SELF',\n\n // ── The account ──\n /** Staff suspended this account: nothing in the product works until it is lifted. */\n 'ACCOUNT_SUSPENDED',\n\n // ── Roles and the MPIN ──\n /**\n * Switching to a role this person does not hold (or no longer holds), or\n * setting an MPIN while holding no staff role at all.\n */\n 'ROLE_NOT_HELD',\n /** A staff role needs an MPIN, and none has been set yet. Set one first. */\n 'MPIN_NOT_SET',\n /** An MPIN is set once; changing it needs the current one. */\n 'MPIN_ALREADY_SET',\n /** The MPIN is wrong. `details.attemptsLeft` says how many tries remain. */\n 'MPIN_INCORRECT',\n /** Too many wrong MPINs. `details.lockedUntil` says when to try again. */\n 'MPIN_LOCKED',\n /** Changing your own roles. Someone else has to. */\n 'CANNOT_CHANGE_OWN_ROLES',\n /** Granting or revoking a role the role you act as may not touch. */\n 'ROLE_NOT_GRANTABLE',\n\n // ── Support ──\n /** The ticket holds `TICKET_MESSAGES_MAX` messages. Open a new one. */\n 'TICKET_FULL',\n /** The team keeps `SAVED_REPLIES_MAX` saved replies. Delete one first. */\n 'SAVED_REPLIES_FULL',\n] as const;\n\nexport const apiErrorCodeSchema = z.enum(API_ERROR_CODE_VALUES);\nexport type ApiErrorCode = z.infer<typeof apiErrorCodeSchema>;\n\n/** The same codes by name: `API_ERROR_CODE.POLL_CLOSED`. */\nexport const API_ERROR_CODE = apiErrorCodeSchema.enum;\n"]}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema — Every error code the API answers with\n *\n * THE CODE IS WHAT A CLIENT ACTS ON; the `message` beside it is English for\n * logs and for a client too old to know the code. A client words each code in\n * the member's language, and a test on the web side asserts every value here\n * has a message in every locale it ships.\n *\n * Before 0.2.0 the backend kept this list to itself, and every domain refusal\n * (a closed poll, a reply to a reply, the edit window) went out as\n * `VALIDATION_ERROR` with an English sentence, so the only way for a client to\n * tell them apart was to match on the wording.\n *\n * WHEN TO USE WHICH:\n * - A request that does not match its schema: `VALIDATION_ERROR`, with every\n * problem in `error.details.issues`.\n * - A well-formed request the current STATE of things refuses: its own code\n * below. These carry no issues; the code says it all.\n *\n * ⚠ Append freely; remove deliberately. A code removed here that an older\n * server still sends fails the client's parse of the error envelope.\n *\n * @module founder-schema/core/errors\n */\n\nimport { z } from 'zod';\n\n/**\n * As a tuple, the way `GENDER_VALUES` is, so a consumer can iterate it (a\n * translation-coverage test does exactly that) and TypeScript keeps the union.\n */\nexport const API_ERROR_CODE_VALUES = [\n // ── Anything ──\n /** A bug or an outage on our side. The message says nothing more on purpose. */\n 'INTERNAL_ERROR',\n /** The request did not match its schema. `details.issues` says where. */\n 'VALIDATION_ERROR',\n /** No such thing, or not one you may see. Deliberately the same answer. */\n 'NOT_FOUND',\n /** Two writes raced for something unique; retrying usually succeeds. */\n 'CONFLICT',\n /** Too many requests from this member or address. Wait, then retry. */\n 'RATE_LIMITED',\n /** Something we depend on (the chat server) is down or not switched on. */\n 'SERVICE_UNAVAILABLE',\n\n // ── Who you are ──\n /** No credential at all. */\n 'UNAUTHORIZED',\n /** A credential that is not ours, or not readable. */\n 'INVALID_TOKEN',\n /** A credential that was ours and has expired. Refresh and retry. */\n 'TOKEN_EXPIRED',\n /** Signed out everywhere, or the account changed. Sign in again. */\n 'SESSION_INVALIDATED',\n /** Signed in, and not allowed to do this. */\n 'FORBIDDEN',\n\n // ── Onboarding ──\n /** Finish onboarding first. The client routes here instead of showing an error. */\n 'ONBOARDING_INCOMPLETE',\n /** Onboarding is already finished; there is nothing to save or finish again. */\n 'ALREADY_ONBOARDED',\n /** Under `MIN_AGE_YEARS`. Refused at the draft and again at finish. */\n 'UNDER_AGE',\n\n // ── Uploads: completing one, and attaching it ──\n /**\n * Not an upload of yours, not processed yet, already attached somewhere, or\n * for the other purpose (an avatar upload on a post). One code for all of\n * them on purpose: telling \"someone else's\" apart from \"does not exist\" would\n * let a caller probe other members' media ids.\n */\n 'UPLOAD_NOT_ATTACHABLE',\n /** Larger than `UPLOAD_MAX_BYTES`. */\n 'UPLOAD_TOO_LARGE',\n /**\n * Not one of the image types this product accepts (by the file's own bytes,\n * whatever its name or declared type), or not an image at all.\n */\n 'UPLOAD_WRONG_TYPE',\n /** Completed before the file reached storage: upload it, then complete. */\n 'UPLOAD_NOT_RECEIVED',\n /**\n * Another request is processing this upload right now (a retry or a double\n * tap). Nothing is wrong: complete again in a moment and it answers with\n * the image. 409.\n */\n 'UPLOAD_PROCESSING',\n /** A JPEG, PNG, WebP or GIF by its bytes that cannot be decoded: truncated or corrupt. */\n 'UPLOAD_UNREADABLE',\n /** More than `MEDIA_MAX_INPUT_PIXELS`; refused before decoding. */\n 'UPLOAD_TOO_MANY_PIXELS',\n /** `MEDIA_PENDING_MAX` uploads are already waiting to be attached. */\n 'UPLOAD_LIMIT_REACHED',\n /** Discarding an upload that is attached: remove it from its post or avatar instead. */\n 'UPLOAD_IN_USE',\n\n // ── The feed ──\n /** The post is older than `EDIT_WINDOW_MS`. */\n 'EDIT_WINDOW_CLOSED',\n /** A poll is never editable: people have already voted on it. */\n 'POLL_NOT_EDITABLE',\n /** The poll has closed, by the server's clock. */\n 'POLL_CLOSED',\n /** No option, several on a single-choice poll, or an option that does not exist. */\n 'POLL_CHOICE_INVALID',\n /** A reply to a reply. One level of nesting only. */\n 'REPLY_TOO_DEEP',\n /** Reporting your own post, poll or comment (or, since 0.6.0, your own product or review). */\n 'CANNOT_REPORT_OWN',\n /**\n * Marking a comment \"this helped\" that is not feedback on your product: the\n * post is not tagged with a product you are on, or the comment is your own\n * team's.\n */\n 'FEEDBACK_NOT_MARKABLE',\n /** Reposting your own post or poll (0.8.0). */\n 'CANNOT_REPOST_OWN',\n /** Reposting a repost (0.8.0): share the original instead. */\n 'CANNOT_REPOST_A_REPOST',\n /** You already have an active repost of this item (0.8.0). Undo it first. 409. */\n 'ALREADY_REPOSTED',\n\n // ── Products ──\n /** Founders own at most `PRODUCTS_OWNED_MAX` products. Delete one to add another. */\n 'PRODUCT_LIMIT_REACHED',\n /**\n * Listing a product that is not ready for Discover, or removing what keeps a\n * listed one ready. `details.missing`: some of `logo`, `tagline`, `screenshot`.\n */\n 'PRODUCT_NOT_LISTABLE',\n /**\n * The product is archived and frozen until its owner restores it: no new\n * posts about it, reviews, upvotes, replies, helpful marks, testers or\n * invitations.\n */\n 'PRODUCT_ARCHIVED',\n /** Tagging a post with, or acting for, a product you are not on the team of. */\n 'PRODUCT_NOT_YOURS',\n /** Inviting someone who is already on the team or already invited. */\n 'PRODUCT_ALREADY_MEMBER',\n /**\n * Inviting someone who cannot join a team: not finished onboarding, paused,\n * suspended or deleted. One code for all four, so it reveals none of them.\n */\n 'PRODUCT_MEMBER_NOT_INVITABLE',\n /** Handing over to, or re-roling, someone who is not on the team (or invited, for a role). */\n 'PRODUCT_NOT_ON_TEAM',\n /**\n * The owner leaving, being removed, or having their role changed. Ownership\n * moves only by handing the product to someone first.\n */\n 'PRODUCT_OWNER_MUST_TRANSFER',\n /** Upvoting, reviewing or confirming yourself as a tester of your own team's product. */\n 'PRODUCT_OWN_TEAM',\n /** Confirming as a tester someone who has given this product no feedback. */\n 'TESTER_NOT_ELIGIBLE',\n\n // ── Communities (0.9.0) ──\n /**\n * Something in a community you can SEE but must join first: taking part in\n * it (posting into it, commenting, reacting, voting, saving, reporting,\n * editing), or reading a private community's feed, pinned posts or\n * members. Join (public) or request to join (private). 403. What you cannot\n * see at all answers NOT_FOUND, as content of a private or hidden community\n * looked up by its id does.\n */\n 'COMMUNITY_JOIN_REQUIRED',\n /** A live community already has this name (case and spacing ignored). 409. */\n 'COMMUNITY_NAME_TAKEN',\n /** Joining, requesting or inviting someone who is already a member, has requested or is invited. 409. */\n 'COMMUNITY_ALREADY_MEMBER',\n /**\n * Inviting someone who cannot join: not finished onboarding, paused,\n * suspended or deleted. One code for all four, so it reveals none of them.\n */\n 'COMMUNITY_MEMBER_NOT_INVITABLE',\n /** Inviting someone banned from this community. Unban them first. */\n 'COMMUNITY_MEMBER_BANNED',\n /** Inviting into a public community: anyone can join it, share its link instead. */\n 'COMMUNITY_INVITE_NOT_NEEDED',\n /**\n * The last admin leaving, stepping down or being removed, or deleting their\n * account while someone else could be made admin. Make someone else admin\n * first. On an account deletion, `details.communities` names them.\n */\n 'COMMUNITY_LAST_ADMIN',\n /**\n * The community is archived (read-only): no new posts, polls, comments,\n * reactions, votes, saves, reports, edits, joins, approvals, invitations\n * or pins. Its moderators may still hide what breaks its rules.\n */\n 'COMMUNITY_ARCHIVED',\n /**\n * A type change that exposes what was already posted (toward public, or\n * hidden to private) sent without `confirmExposure: true`.\n */\n 'COMMUNITY_EXPOSURE_UNCONFIRMED',\n /** `COMMUNITY_PINS_MAX` announcements are pinned already. Unpin one first. */\n 'COMMUNITY_PINS_FULL',\n /**\n * Notifying every member again within `COMMUNITY_ANNOUNCE_INTERVAL_MS`.\n * `details.nextAt` says when it is allowed. Pinning without notifying works.\n */\n 'COMMUNITY_ANNOUNCE_TOO_SOON',\n /** Reposting a post or poll that lives in a community: community content stays in it. */\n 'COMMUNITY_CONTENT_NOT_REPOSTABLE',\n /** `COMMUNITY_SUGGESTIONS_OPEN_MAX` suggestions are already waiting for review. */\n 'COMMUNITY_SUGGESTIONS_FULL',\n /** Approving or declining a suggestion that was already decided. 409. */\n 'COMMUNITY_SUGGESTION_DECIDED',\n // ── Reviews ──\n /** One review per member per product. Edit yours instead. */\n 'REVIEW_EXISTS',\n /** Voting your own review helpful. */\n 'REVIEW_OWN_VOTE',\n\n // ── Moderation ──\n /**\n * A decision that does not apply to that kind of thing: removing a product\n * (staff hide one, never delete it). `MODERATION_ACTIONS_BY_TARGET_KIND`.\n */\n 'MODERATION_ACTION_INVALID',\n\n // ── Chat ──\n /** Opening a conversation with yourself. */\n 'CHAT_WITH_SELF',\n\n // ── The account ──\n /** Staff suspended this account: nothing in the product works until it is lifted. */\n 'ACCOUNT_SUSPENDED',\n\n // ── Roles and the MPIN ──\n /**\n * Switching to a role this person does not hold (or no longer holds), or\n * setting an MPIN while holding no staff role at all.\n */\n 'ROLE_NOT_HELD',\n /** A staff role needs an MPIN, and none has been set yet. Set one first. */\n 'MPIN_NOT_SET',\n /** An MPIN is set once; changing it needs the current one. */\n 'MPIN_ALREADY_SET',\n /** The MPIN is wrong. `details.attemptsLeft` says how many tries remain. */\n 'MPIN_INCORRECT',\n /** Too many wrong MPINs. `details.lockedUntil` says when to try again. */\n 'MPIN_LOCKED',\n /** Changing your own roles. Someone else has to. */\n 'CANNOT_CHANGE_OWN_ROLES',\n /** Granting or revoking a role the role you act as may not touch. */\n 'ROLE_NOT_GRANTABLE',\n /** Changing your own capabilities (0.9.0). Someone else has to. */\n 'CANNOT_CHANGE_OWN_CAPABILITIES',\n /** Granting or revoking a capability the role you act as may not touch (0.9.0). */\n 'CAPABILITY_NOT_GRANTABLE',\n\n // ── Support ──\n /** The ticket holds `TICKET_MESSAGES_MAX` messages. Open a new one. */\n 'TICKET_FULL',\n /** The team keeps `SAVED_REPLIES_MAX` saved replies. Delete one first. */\n 'SAVED_REPLIES_FULL',\n] as const;\n\nexport const apiErrorCodeSchema = z.enum(API_ERROR_CODE_VALUES);\nexport type ApiErrorCode = z.infer<typeof apiErrorCodeSchema>;\n\n/** The same codes by name: `API_ERROR_CODE.POLL_CLOSED`. */\nexport const API_ERROR_CODE = apiErrorCodeSchema.enum;\n"]}
@@ -139,6 +139,18 @@ export declare const PRODUCT_TEAM_SHOWN = 12;
139
139
  * launch, ideas) shelf. Each new post with that ask restarts the clock.
140
140
  */
141
141
  export declare const PRODUCT_ASK_OPEN_DAYS = 14;
142
+ export declare const COMMUNITY_NAME_MIN = 3;
143
+ export declare const COMMUNITY_NAME_MAX = 60;
144
+ export declare const COMMUNITY_DESCRIPTION_MAX = 500;
145
+ /** Rules on one community, each one line. */
146
+ export declare const COMMUNITY_RULES_MAX = 10;
147
+ export declare const COMMUNITY_RULE_MAX = 200;
148
+ /** What a suggester says the community is for, beyond its description. */
149
+ export declare const COMMUNITY_SUGGESTION_WHY_MAX = 500;
150
+ /** A declined suggestion says why: long enough to be a reason, not a novel. */
151
+ export declare const COMMUNITY_DECLINE_REASON_MIN = 3;
152
+ export declare const COMMUNITY_DECLINE_REASON_MAX = 500;
153
+ export declare const COMMUNITY_BAN_REASON_MAX = 500;
142
154
  /**
143
155
  * A review says WHY. A star with no reason does not help the founder, which is
144
156
  * the whole point of reviewing here, so the text is required and has a floor.
@@ -1 +1 @@
1
- {"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../../src/core/limits.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../../src/core/limits.ts"],"names":[],"mappings":"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"}
@@ -150,6 +150,19 @@ export const PRODUCT_TEAM_SHOWN = 12;
150
150
  * launch, ideas) shelf. Each new post with that ask restarts the clock.
151
151
  */
152
152
  export const PRODUCT_ASK_OPEN_DAYS = 14;
153
+ // ─── Communities (0.9.0) ─────────────────────────────────────────────────────
154
+ export const COMMUNITY_NAME_MIN = 3;
155
+ export const COMMUNITY_NAME_MAX = 60;
156
+ export const COMMUNITY_DESCRIPTION_MAX = 500;
157
+ /** Rules on one community, each one line. */
158
+ export const COMMUNITY_RULES_MAX = 10;
159
+ export const COMMUNITY_RULE_MAX = 200;
160
+ /** What a suggester says the community is for, beyond its description. */
161
+ export const COMMUNITY_SUGGESTION_WHY_MAX = 500;
162
+ /** A declined suggestion says why: long enough to be a reason, not a novel. */
163
+ export const COMMUNITY_DECLINE_REASON_MIN = 3;
164
+ export const COMMUNITY_DECLINE_REASON_MAX = 500;
165
+ export const COMMUNITY_BAN_REASON_MAX = 500;
153
166
  // ─── Reviews ─────────────────────────────────────────────────────────────────
154
167
  /**
155
168
  * A review says WHY. A star with no reason does not help the founder, which is