founder-schema 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -1
- package/dist/access/permissions.d.ts +24 -2
- package/dist/access/permissions.d.ts.map +1 -1
- package/dist/access/permissions.js +9 -0
- package/dist/access/permissions.js.map +1 -1
- package/dist/core/common.d.ts +37 -0
- package/dist/core/common.d.ts.map +1 -1
- package/dist/core/common.js +49 -0
- package/dist/core/common.js.map +1 -1
- package/dist/core/errors.d.ts +41 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +75 -4
- package/dist/core/errors.js.map +1 -1
- package/dist/core/limits.d.ts +76 -4
- package/dist/core/limits.d.ts.map +1 -1
- package/dist/core/limits.js +80 -4
- package/dist/core/limits.js.map +1 -1
- package/dist/core/responses.d.ts +55 -0
- package/dist/core/responses.d.ts.map +1 -1
- package/dist/core/responses.js +3 -1
- package/dist/core/responses.js.map +1 -1
- package/dist/core/validation.d.ts +16 -0
- package/dist/core/validation.d.ts.map +1 -1
- package/dist/core/validation.js +16 -0
- package/dist/core/validation.js.map +1 -1
- package/dist/domains/account/account.d.ts +2 -0
- package/dist/domains/account/account.d.ts.map +1 -1
- package/dist/domains/account/account.js +2 -0
- package/dist/domains/account/account.js.map +1 -1
- package/dist/domains/audit/audit.d.ts +4 -0
- package/dist/domains/audit/audit.d.ts.map +1 -1
- package/dist/domains/audit/audit.js +6 -3
- package/dist/domains/audit/audit.js.map +1 -1
- package/dist/domains/contributions/contributions.d.ts +158 -0
- package/dist/domains/contributions/contributions.d.ts.map +1 -0
- package/dist/domains/contributions/contributions.js +93 -0
- package/dist/domains/contributions/contributions.js.map +1 -0
- package/dist/domains/discover/discover.d.ts +5 -0
- package/dist/domains/discover/discover.d.ts.map +1 -1
- package/dist/domains/discover/discover.js +3 -13
- package/dist/domains/discover/discover.js.map +1 -1
- package/dist/domains/feed/feed.d.ts +293 -32
- package/dist/domains/feed/feed.d.ts.map +1 -1
- package/dist/domains/feed/feed.js +109 -38
- package/dist/domains/feed/feed.js.map +1 -1
- package/dist/domains/media/media.d.ts +159 -0
- package/dist/domains/media/media.d.ts.map +1 -0
- package/dist/domains/media/media.js +136 -0
- package/dist/domains/media/media.js.map +1 -0
- package/dist/domains/members/members.d.ts +5 -5
- package/dist/domains/moderation/moderation.d.ts +53 -3
- package/dist/domains/moderation/moderation.d.ts.map +1 -1
- package/dist/domains/moderation/moderation.js +47 -8
- package/dist/domains/moderation/moderation.js.map +1 -1
- package/dist/domains/notifications/notifications.d.ts +212 -0
- package/dist/domains/notifications/notifications.d.ts.map +1 -0
- package/dist/domains/notifications/notifications.js +144 -0
- package/dist/domains/notifications/notifications.js.map +1 -0
- package/dist/domains/onboarding/onboarding.d.ts +21 -24
- package/dist/domains/onboarding/onboarding.d.ts.map +1 -1
- package/dist/domains/onboarding/onboarding.js +21 -20
- package/dist/domains/onboarding/onboarding.js.map +1 -1
- package/dist/domains/products/products.d.ts +1449 -0
- package/dist/domains/products/products.d.ts.map +1 -0
- package/dist/domains/products/products.js +453 -0
- package/dist/domains/products/products.js.map +1 -0
- package/dist/domains/profile/founder-profile.d.ts +66 -0
- package/dist/domains/profile/founder-profile.d.ts.map +1 -1
- package/dist/domains/profile/founder-profile.js +70 -1
- package/dist/domains/profile/founder-profile.js.map +1 -1
- package/dist/domains/reviews/reviews.d.ts +184 -0
- package/dist/domains/reviews/reviews.d.ts.map +1 -0
- package/dist/domains/reviews/reviews.js +99 -0
- package/dist/domains/reviews/reviews.js.map +1 -0
- package/dist/domains/uploads/uploads.d.ts +54 -64
- package/dist/domains/uploads/uploads.d.ts.map +1 -1
- package/dist/domains/uploads/uploads.js +43 -60
- package/dist/domains/uploads/uploads.js.map +1 -1
- package/dist/index.d.ts +11 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/core/errors.js
CHANGED
|
@@ -59,13 +59,37 @@ export const API_ERROR_CODE_VALUES = [
|
|
|
59
59
|
'ALREADY_ONBOARDED',
|
|
60
60
|
/** Under `MIN_AGE_YEARS`. Refused at the draft and again at finish. */
|
|
61
61
|
'UNDER_AGE',
|
|
62
|
-
// ── Uploads
|
|
63
|
-
/**
|
|
62
|
+
// ── Uploads: completing one, and attaching it ──
|
|
63
|
+
/**
|
|
64
|
+
* Not an upload of yours, not processed yet, already attached somewhere, or
|
|
65
|
+
* for the other purpose (an avatar upload on a post). One code for all of
|
|
66
|
+
* them on purpose: telling "someone else's" apart from "does not exist" would
|
|
67
|
+
* let a caller probe other members' media ids.
|
|
68
|
+
*/
|
|
64
69
|
'UPLOAD_NOT_ATTACHABLE',
|
|
65
70
|
/** Larger than `UPLOAD_MAX_BYTES`. */
|
|
66
71
|
'UPLOAD_TOO_LARGE',
|
|
67
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* Not one of the image types this product accepts (by the file's own bytes,
|
|
74
|
+
* whatever its name or declared type), or not an image at all.
|
|
75
|
+
*/
|
|
68
76
|
'UPLOAD_WRONG_TYPE',
|
|
77
|
+
/** Completed before the file reached storage: upload it, then complete. */
|
|
78
|
+
'UPLOAD_NOT_RECEIVED',
|
|
79
|
+
/**
|
|
80
|
+
* Another request is processing this upload right now (a retry or a double
|
|
81
|
+
* tap). Nothing is wrong: complete again in a moment and it answers with
|
|
82
|
+
* the image. 409.
|
|
83
|
+
*/
|
|
84
|
+
'UPLOAD_PROCESSING',
|
|
85
|
+
/** A JPEG, PNG, WebP or GIF by its bytes that cannot be decoded: truncated or corrupt. */
|
|
86
|
+
'UPLOAD_UNREADABLE',
|
|
87
|
+
/** More than `MEDIA_MAX_INPUT_PIXELS`; refused before decoding. */
|
|
88
|
+
'UPLOAD_TOO_MANY_PIXELS',
|
|
89
|
+
/** `MEDIA_PENDING_MAX` uploads are already waiting to be attached. */
|
|
90
|
+
'UPLOAD_LIMIT_REACHED',
|
|
91
|
+
/** Discarding an upload that is attached: remove it from its post or avatar instead. */
|
|
92
|
+
'UPLOAD_IN_USE',
|
|
69
93
|
// ── The feed ──
|
|
70
94
|
/** The post is older than `EDIT_WINDOW_MS`. */
|
|
71
95
|
'EDIT_WINDOW_CLOSED',
|
|
@@ -77,8 +101,55 @@ export const API_ERROR_CODE_VALUES = [
|
|
|
77
101
|
'POLL_CHOICE_INVALID',
|
|
78
102
|
/** A reply to a reply. One level of nesting only. */
|
|
79
103
|
'REPLY_TOO_DEEP',
|
|
80
|
-
/** Reporting your own post, poll or comment. */
|
|
104
|
+
/** Reporting your own post, poll or comment (or, since 0.6.0, your own product or review). */
|
|
81
105
|
'CANNOT_REPORT_OWN',
|
|
106
|
+
/**
|
|
107
|
+
* Marking a comment "this helped" that is not feedback on your product: the
|
|
108
|
+
* post is not tagged with a product you are on, or the comment is your own
|
|
109
|
+
* team's.
|
|
110
|
+
*/
|
|
111
|
+
'FEEDBACK_NOT_MARKABLE',
|
|
112
|
+
// ── Products ──
|
|
113
|
+
/** Founders own at most `PRODUCTS_OWNED_MAX` products. Delete one to add another. */
|
|
114
|
+
'PRODUCT_LIMIT_REACHED',
|
|
115
|
+
/**
|
|
116
|
+
* Listing a product that is not ready for Discover, or removing what keeps a
|
|
117
|
+
* listed one ready. `details.missing`: some of `logo`, `tagline`, `screenshot`.
|
|
118
|
+
*/
|
|
119
|
+
'PRODUCT_NOT_LISTABLE',
|
|
120
|
+
/** The product is archived: no new posts, reviews or upvotes until it is restored. */
|
|
121
|
+
'PRODUCT_ARCHIVED',
|
|
122
|
+
/** Tagging a post with, or acting for, a product you are not on the team of. */
|
|
123
|
+
'PRODUCT_NOT_YOURS',
|
|
124
|
+
/** Inviting someone who is already on the team or already invited. */
|
|
125
|
+
'PRODUCT_ALREADY_MEMBER',
|
|
126
|
+
/**
|
|
127
|
+
* Inviting someone who cannot join a team: not finished onboarding, paused,
|
|
128
|
+
* suspended or deleted. One code for all four, so it reveals none of them.
|
|
129
|
+
*/
|
|
130
|
+
'PRODUCT_MEMBER_NOT_INVITABLE',
|
|
131
|
+
/** Handing over to, or re-roling, someone who is not on the team (or invited, for a role). */
|
|
132
|
+
'PRODUCT_NOT_ON_TEAM',
|
|
133
|
+
/**
|
|
134
|
+
* The owner leaving, being removed, or having their role changed. Ownership
|
|
135
|
+
* moves only by handing the product to someone first.
|
|
136
|
+
*/
|
|
137
|
+
'PRODUCT_OWNER_MUST_TRANSFER',
|
|
138
|
+
/** Upvoting, reviewing or confirming yourself as a tester of your own team's product. */
|
|
139
|
+
'PRODUCT_OWN_TEAM',
|
|
140
|
+
/** Confirming as a tester someone who has given this product no feedback. */
|
|
141
|
+
'TESTER_NOT_ELIGIBLE',
|
|
142
|
+
// ── Reviews ──
|
|
143
|
+
/** One review per member per product. Edit yours instead. */
|
|
144
|
+
'REVIEW_EXISTS',
|
|
145
|
+
/** Voting your own review helpful. */
|
|
146
|
+
'REVIEW_OWN_VOTE',
|
|
147
|
+
// ── Moderation ──
|
|
148
|
+
/**
|
|
149
|
+
* A decision that does not apply to that kind of thing: removing a product
|
|
150
|
+
* (staff hide one, never delete it). `MODERATION_ACTIONS_BY_TARGET_KIND`.
|
|
151
|
+
*/
|
|
152
|
+
'MODERATION_ACTION_INVALID',
|
|
82
153
|
// ── Chat ──
|
|
83
154
|
/** Opening a conversation with yourself. */
|
|
84
155
|
'CHAT_WITH_SELF',
|
package/dist/core/errors.js.map
CHANGED
|
@@ -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,+CAA+C;IAC/C,2EAA2E;IAC3E,uBAAuB;IACvB,sCAAsC;IACtC,kBAAkB;IAClB,+EAA+E;IAC/E,mBAAmB;IAEnB,iBAAiB;IACjB,+CAA+C;IAC/C,oBAAoB;IACpB,iEAAiE;IACjE,mBAAmB;IACnB,kDAAkD;IAClD,aAAa;IACb,oFAAoF;IACpF,qBAAqB;IACrB,qDAAqD;IACrD,gBAAgB;IAChB,gDAAgD;IAChD,mBAAmB;IAEnB,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, at the moment one is attached ──\n /** Not an upload of yours, or nothing was ever uploaded under that key. */\n 'UPLOAD_NOT_ATTACHABLE',\n /** Larger than `UPLOAD_MAX_BYTES`. */\n 'UPLOAD_TOO_LARGE',\n /** Not one of the image types this product accepts, or not an image at all. */\n 'UPLOAD_WRONG_TYPE',\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. */\n 'CANNOT_REPORT_OWN',\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":"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,sFAAsF;IACtF,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 /** The product is archived: no new posts, reviews or upvotes until it is restored. */\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"]}
|
package/dist/core/limits.d.ts
CHANGED
|
@@ -78,12 +78,35 @@ export declare const SAVED_REPLIES_MAX = 200;
|
|
|
78
78
|
/**
|
|
79
79
|
* Largest file accepted for upload: 10 MB.
|
|
80
80
|
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
81
|
+
* Enforced three times. The upload-slot body refuses a bigger `contentLength`;
|
|
82
|
+
* the size is SIGNED INTO the upload URL, so storage refuses a body of any other
|
|
83
|
+
* size; and the server checks the stored object again before processing it.
|
|
84
|
+
* The client checks first only to fail fast: it shrinks a photo well below this
|
|
85
|
+
* before uploading (see `MEDIA_SIZES`), so a member rarely meets the limit.
|
|
85
86
|
*/
|
|
86
87
|
export declare const UPLOAD_MAX_BYTES: number;
|
|
88
|
+
/**
|
|
89
|
+
* Largest image, in pixels, the server will decode: 50 megapixels, one frame.
|
|
90
|
+
*
|
|
91
|
+
* Bytes do not bound memory. A 50,000 x 50,000 PNG of one colour compresses to
|
|
92
|
+
* a few kilobytes and decodes to ten gigabytes. The server reads the dimensions
|
|
93
|
+
* first and refuses anything above this before decoding (`UPLOAD_TOO_MANY_PIXELS`).
|
|
94
|
+
* 50 MP admits every phone camera's full-resolution photo up to 48 MP.
|
|
95
|
+
*/
|
|
96
|
+
export declare const MEDIA_MAX_INPUT_PIXELS = 50000000;
|
|
97
|
+
/**
|
|
98
|
+
* Hours an upload may wait to be attached to a post or set as an avatar. After
|
|
99
|
+
* that the server deletes it: the composer was abandoned, or the tab closed.
|
|
100
|
+
*/
|
|
101
|
+
export declare const MEDIA_UNATTACHED_TTL_HOURS = 24;
|
|
102
|
+
/**
|
|
103
|
+
* Uploads one member may have waiting (requested or processed, not yet
|
|
104
|
+
* attached) at once. A full post is `POST_MAX_MEDIA` (10); this leaves room for
|
|
105
|
+
* a retry or two without letting one account fill storage.
|
|
106
|
+
*/
|
|
107
|
+
export declare const MEDIA_PENDING_MAX = 30;
|
|
108
|
+
/** Longest placeholder string: a base64 ThumbHash is about 30 characters. */
|
|
109
|
+
export declare const MEDIA_PLACEHOLDER_MAX = 100;
|
|
87
110
|
/** Values one Discover multi-select filter may carry (`?skill=a&skill=b`). */
|
|
88
111
|
export declare const DISCOVER_FILTER_VALUES_MAX = 10;
|
|
89
112
|
/** Discover's free-text inputs: the name search, city and country. */
|
|
@@ -96,4 +119,53 @@ export declare const PAGE_SIZE_DEFAULT = 20;
|
|
|
96
119
|
* used to page through the directory.
|
|
97
120
|
*/
|
|
98
121
|
export declare const CHAT_PARTICIPANTS_MAX = 100;
|
|
122
|
+
export declare const PRODUCT_NAME_MAX = 60;
|
|
123
|
+
/** One line on a Discover card: short enough to never wrap twice on a phone. */
|
|
124
|
+
export declare const PRODUCT_TAGLINE_MAX = 80;
|
|
125
|
+
export declare const PRODUCT_DESCRIPTION_MAX = 3000;
|
|
126
|
+
/** Screenshots on one product page. The first is its cover on Discover. */
|
|
127
|
+
export declare const PRODUCT_SCREENSHOTS_MAX = 8;
|
|
128
|
+
export declare const PRODUCT_CAPTION_MAX = 140;
|
|
129
|
+
/**
|
|
130
|
+
* Products one founder may OWN at once (archived ones count; deleting frees a
|
|
131
|
+
* place). Being on someone else's team is not counted: the user ruled there is
|
|
132
|
+
* no limit on a product's team, and so none on how many teams one joins.
|
|
133
|
+
*/
|
|
134
|
+
export declare const PRODUCTS_OWNED_MAX = 5;
|
|
135
|
+
/** Team members a product page shows; the rest are one tap away (`/team`). */
|
|
136
|
+
export declare const PRODUCT_TEAM_SHOWN = 12;
|
|
137
|
+
/**
|
|
138
|
+
* Days a post's ask keeps its product on the "asking for testers" (or feedback,
|
|
139
|
+
* launch, ideas) shelf. Each new post with that ask restarts the clock.
|
|
140
|
+
*/
|
|
141
|
+
export declare const PRODUCT_ASK_OPEN_DAYS = 14;
|
|
142
|
+
/**
|
|
143
|
+
* A review says WHY. A star with no reason does not help the founder, which is
|
|
144
|
+
* the whole point of reviewing here, so the text is required and has a floor.
|
|
145
|
+
*/
|
|
146
|
+
export declare const REVIEW_TEXT_MIN = 30;
|
|
147
|
+
export declare const REVIEW_TEXT_MAX = 2000;
|
|
148
|
+
export declare const REVIEW_REPLY_MAX = 1000;
|
|
149
|
+
/**
|
|
150
|
+
* Reviews before a product shows an average (and enters "Top rated"). One or two
|
|
151
|
+
* ratings say more about who wrote them than about the product.
|
|
152
|
+
*/
|
|
153
|
+
export declare const REVIEW_AVERAGE_MIN_COUNT = 3;
|
|
154
|
+
/** Points one review can earn its writer through other members' Helpful votes. */
|
|
155
|
+
export declare const REVIEW_HELPFUL_POINTS_MAX = 10;
|
|
156
|
+
/** Reviews a day that earn points for writing them. More are welcome; they earn nothing. */
|
|
157
|
+
export declare const REVIEWS_REWARDED_PER_DAY = 3;
|
|
158
|
+
/**
|
|
159
|
+
* Points one member can earn from ONE other member over any 30 days, all kinds
|
|
160
|
+
* together, so two founders cannot farm each other by trading Helpful marks.
|
|
161
|
+
*/
|
|
162
|
+
export declare const CONTRIBUTION_PAIR_POINTS_MAX = 30;
|
|
163
|
+
/** Window of the "this month" score and the top-helpers list. */
|
|
164
|
+
export declare const CONTRIBUTION_RECENT_DAYS = 30;
|
|
165
|
+
/** Rows on the top-helpers list. */
|
|
166
|
+
export declare const CONTRIBUTION_LEADERBOARD_SIZE = 20;
|
|
167
|
+
/** Days a notification is kept. Neither Jansathi nor GymMonk ever deletes one. */
|
|
168
|
+
export declare const NOTIFICATION_RETENTION_DAYS = 90;
|
|
169
|
+
/** The first words of a comment or review a notification shows. */
|
|
170
|
+
export declare const NOTIFICATION_EXCERPT_MAX = 140;
|
|
99
171
|
//# sourceMappingURL=limits.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../../src/core/limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,eAAO,MAAM,YAAY,MAAM,CAAC;AAChC,eAAO,MAAM,SAAS,OAAO,CAAC;AAC9B,0DAA0D;AAC1D,eAAO,MAAM,UAAU,IAAI,CAAC;AAC5B,gFAAgF;AAChF,eAAO,MAAM,SAAS,IAAI,CAAC;AAI3B,eAAO,MAAM,QAAQ,KAAK,CAAC;AAC3B,eAAO,MAAM,SAAS,KAAK,CAAC;AAC5B,eAAO,MAAM,WAAW,KAAK,CAAC;AAI9B,eAAO,MAAM,gBAAgB,MAAM,CAAC;AACpC,eAAO,MAAM,mBAAmB,MAAM,CAAC;AACvC,uCAAuC;AACvC,eAAO,MAAM,aAAa,IAAI,CAAC;AAC/B,eAAO,MAAM,aAAa,SAAU,CAAC;AACrC,mFAAmF;AACnF,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAIrC;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,MAAM,CAAC;AAIhC,kFAAkF;AAClF,eAAO,MAAM,aAAa,MAAM,CAAC;AACjC,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAC1C,eAAO,MAAM,4BAA4B,MAAM,CAAC;AAChD,sCAAsC;AACtC,eAAO,MAAM,eAAe,OAAO,CAAC;AACpC,kFAAkF;AAClF,eAAO,MAAM,mBAAmB,MAAM,CAAC;AACvC,iFAAiF;AACjF,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAI1C,kFAAkF;AAClF,eAAO,MAAM,qBAAqB,IAAI,CAAC;AACvC,eAAO,MAAM,qBAAqB,MAAM,CAAC;AACzC,8EAA8E;AAC9E,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAIrC,eAAO,MAAM,kBAAkB,IAAI,CAAC;AACpC,eAAO,MAAM,kBAAkB,MAAM,CAAC;AACtC,2DAA2D;AAC3D,eAAO,MAAM,kBAAkB,OAAO,CAAC;AACvC;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,MAAM,CAAC;AACvC,eAAO,MAAM,qBAAqB,KAAK,CAAC;AACxC,eAAO,MAAM,oBAAoB,OAAO,CAAC;AACzC,mEAAmE;AACnE,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAIrC
|
|
1
|
+
{"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../../src/core/limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,eAAO,MAAM,YAAY,MAAM,CAAC;AAChC,eAAO,MAAM,SAAS,OAAO,CAAC;AAC9B,0DAA0D;AAC1D,eAAO,MAAM,UAAU,IAAI,CAAC;AAC5B,gFAAgF;AAChF,eAAO,MAAM,SAAS,IAAI,CAAC;AAI3B,eAAO,MAAM,QAAQ,KAAK,CAAC;AAC3B,eAAO,MAAM,SAAS,KAAK,CAAC;AAC5B,eAAO,MAAM,WAAW,KAAK,CAAC;AAI9B,eAAO,MAAM,gBAAgB,MAAM,CAAC;AACpC,eAAO,MAAM,mBAAmB,MAAM,CAAC;AACvC,uCAAuC;AACvC,eAAO,MAAM,aAAa,IAAI,CAAC;AAC/B,eAAO,MAAM,aAAa,SAAU,CAAC;AACrC,mFAAmF;AACnF,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAIrC;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,MAAM,CAAC;AAIhC,kFAAkF;AAClF,eAAO,MAAM,aAAa,MAAM,CAAC;AACjC,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAC1C,eAAO,MAAM,4BAA4B,MAAM,CAAC;AAChD,sCAAsC;AACtC,eAAO,MAAM,eAAe,OAAO,CAAC;AACpC,kFAAkF;AAClF,eAAO,MAAM,mBAAmB,MAAM,CAAC;AACvC,iFAAiF;AACjF,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAI1C,kFAAkF;AAClF,eAAO,MAAM,qBAAqB,IAAI,CAAC;AACvC,eAAO,MAAM,qBAAqB,MAAM,CAAC;AACzC,8EAA8E;AAC9E,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAIrC,eAAO,MAAM,kBAAkB,IAAI,CAAC;AACpC,eAAO,MAAM,kBAAkB,MAAM,CAAC;AACtC,2DAA2D;AAC3D,eAAO,MAAM,kBAAkB,OAAO,CAAC;AACvC;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,MAAM,CAAC;AACvC,eAAO,MAAM,qBAAqB,KAAK,CAAC;AACxC,eAAO,MAAM,oBAAoB,OAAO,CAAC;AACzC,mEAAmE;AACnE,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAIrC;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,QAAmB,CAAC;AAEjD;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,WAAa,CAAC;AAEjD;;;GAGG;AACH,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAE7C;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC,6EAA6E;AAC7E,eAAO,MAAM,qBAAqB,MAAM,CAAC;AAIzC,8EAA8E;AAC9E,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAC7C,sEAAsE;AACtE,eAAO,MAAM,iBAAiB,KAAK,CAAC;AACpC,oDAAoD;AACpD,eAAO,MAAM,aAAa,KAAK,CAAC;AAChC,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAIpC;;;GAGG;AACH,eAAO,MAAM,qBAAqB,MAAM,CAAC;AAIzC,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,gFAAgF;AAChF,eAAO,MAAM,mBAAmB,KAAK,CAAC;AACtC,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAC5C,2EAA2E;AAC3E,eAAO,MAAM,uBAAuB,IAAI,CAAC;AACzC,eAAO,MAAM,mBAAmB,MAAM,CAAC;AACvC;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,IAAI,CAAC;AACpC,8EAA8E;AAC9E,eAAO,MAAM,kBAAkB,KAAK,CAAC;AACrC;;;GAGG;AACH,eAAO,MAAM,qBAAqB,KAAK,CAAC;AAIxC;;;GAGG;AACH,eAAO,MAAM,eAAe,KAAK,CAAC;AAClC,eAAO,MAAM,eAAe,OAAO,CAAC;AACpC,eAAO,MAAM,gBAAgB,OAAO,CAAC;AACrC;;;GAGG;AACH,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAI1C,kFAAkF;AAClF,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAC5C,4FAA4F;AAC5F,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAC1C;;;GAGG;AACH,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAC/C,iEAAiE;AACjE,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAC3C,oCAAoC;AACpC,eAAO,MAAM,6BAA6B,KAAK,CAAC;AAIhD,kFAAkF;AAClF,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAC9C,mEAAmE;AACnE,eAAO,MAAM,wBAAwB,MAAM,CAAC"}
|
package/dist/core/limits.js
CHANGED
|
@@ -86,12 +86,35 @@ export const SAVED_REPLIES_MAX = 200;
|
|
|
86
86
|
/**
|
|
87
87
|
* Largest file accepted for upload: 10 MB.
|
|
88
88
|
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
89
|
+
* Enforced three times. The upload-slot body refuses a bigger `contentLength`;
|
|
90
|
+
* the size is SIGNED INTO the upload URL, so storage refuses a body of any other
|
|
91
|
+
* size; and the server checks the stored object again before processing it.
|
|
92
|
+
* The client checks first only to fail fast: it shrinks a photo well below this
|
|
93
|
+
* before uploading (see `MEDIA_SIZES`), so a member rarely meets the limit.
|
|
93
94
|
*/
|
|
94
95
|
export const UPLOAD_MAX_BYTES = 10 * 1024 * 1024;
|
|
96
|
+
/**
|
|
97
|
+
* Largest image, in pixels, the server will decode: 50 megapixels, one frame.
|
|
98
|
+
*
|
|
99
|
+
* Bytes do not bound memory. A 50,000 x 50,000 PNG of one colour compresses to
|
|
100
|
+
* a few kilobytes and decodes to ten gigabytes. The server reads the dimensions
|
|
101
|
+
* first and refuses anything above this before decoding (`UPLOAD_TOO_MANY_PIXELS`).
|
|
102
|
+
* 50 MP admits every phone camera's full-resolution photo up to 48 MP.
|
|
103
|
+
*/
|
|
104
|
+
export const MEDIA_MAX_INPUT_PIXELS = 50_000_000;
|
|
105
|
+
/**
|
|
106
|
+
* Hours an upload may wait to be attached to a post or set as an avatar. After
|
|
107
|
+
* that the server deletes it: the composer was abandoned, or the tab closed.
|
|
108
|
+
*/
|
|
109
|
+
export const MEDIA_UNATTACHED_TTL_HOURS = 24;
|
|
110
|
+
/**
|
|
111
|
+
* Uploads one member may have waiting (requested or processed, not yet
|
|
112
|
+
* attached) at once. A full post is `POST_MAX_MEDIA` (10); this leaves room for
|
|
113
|
+
* a retry or two without letting one account fill storage.
|
|
114
|
+
*/
|
|
115
|
+
export const MEDIA_PENDING_MAX = 30;
|
|
116
|
+
/** Longest placeholder string: a base64 ThumbHash is about 30 characters. */
|
|
117
|
+
export const MEDIA_PLACEHOLDER_MAX = 100;
|
|
95
118
|
// ─── Lists and search ────────────────────────────────────────────────────────
|
|
96
119
|
/** Values one Discover multi-select filter may carry (`?skill=a&skill=b`). */
|
|
97
120
|
export const DISCOVER_FILTER_VALUES_MAX = 10;
|
|
@@ -106,4 +129,57 @@ export const PAGE_SIZE_DEFAULT = 20;
|
|
|
106
129
|
* used to page through the directory.
|
|
107
130
|
*/
|
|
108
131
|
export const CHAT_PARTICIPANTS_MAX = 100;
|
|
132
|
+
// ─── Products ────────────────────────────────────────────────────────────────
|
|
133
|
+
export const PRODUCT_NAME_MAX = 60;
|
|
134
|
+
/** One line on a Discover card: short enough to never wrap twice on a phone. */
|
|
135
|
+
export const PRODUCT_TAGLINE_MAX = 80;
|
|
136
|
+
export const PRODUCT_DESCRIPTION_MAX = 3000;
|
|
137
|
+
/** Screenshots on one product page. The first is its cover on Discover. */
|
|
138
|
+
export const PRODUCT_SCREENSHOTS_MAX = 8;
|
|
139
|
+
export const PRODUCT_CAPTION_MAX = 140;
|
|
140
|
+
/**
|
|
141
|
+
* Products one founder may OWN at once (archived ones count; deleting frees a
|
|
142
|
+
* place). Being on someone else's team is not counted: the user ruled there is
|
|
143
|
+
* no limit on a product's team, and so none on how many teams one joins.
|
|
144
|
+
*/
|
|
145
|
+
export const PRODUCTS_OWNED_MAX = 5;
|
|
146
|
+
/** Team members a product page shows; the rest are one tap away (`/team`). */
|
|
147
|
+
export const PRODUCT_TEAM_SHOWN = 12;
|
|
148
|
+
/**
|
|
149
|
+
* Days a post's ask keeps its product on the "asking for testers" (or feedback,
|
|
150
|
+
* launch, ideas) shelf. Each new post with that ask restarts the clock.
|
|
151
|
+
*/
|
|
152
|
+
export const PRODUCT_ASK_OPEN_DAYS = 14;
|
|
153
|
+
// ─── Reviews ─────────────────────────────────────────────────────────────────
|
|
154
|
+
/**
|
|
155
|
+
* A review says WHY. A star with no reason does not help the founder, which is
|
|
156
|
+
* the whole point of reviewing here, so the text is required and has a floor.
|
|
157
|
+
*/
|
|
158
|
+
export const REVIEW_TEXT_MIN = 30;
|
|
159
|
+
export const REVIEW_TEXT_MAX = 2000;
|
|
160
|
+
export const REVIEW_REPLY_MAX = 1000;
|
|
161
|
+
/**
|
|
162
|
+
* Reviews before a product shows an average (and enters "Top rated"). One or two
|
|
163
|
+
* ratings say more about who wrote them than about the product.
|
|
164
|
+
*/
|
|
165
|
+
export const REVIEW_AVERAGE_MIN_COUNT = 3;
|
|
166
|
+
// ─── Contribution ────────────────────────────────────────────────────────────
|
|
167
|
+
/** Points one review can earn its writer through other members' Helpful votes. */
|
|
168
|
+
export const REVIEW_HELPFUL_POINTS_MAX = 10;
|
|
169
|
+
/** Reviews a day that earn points for writing them. More are welcome; they earn nothing. */
|
|
170
|
+
export const REVIEWS_REWARDED_PER_DAY = 3;
|
|
171
|
+
/**
|
|
172
|
+
* Points one member can earn from ONE other member over any 30 days, all kinds
|
|
173
|
+
* together, so two founders cannot farm each other by trading Helpful marks.
|
|
174
|
+
*/
|
|
175
|
+
export const CONTRIBUTION_PAIR_POINTS_MAX = 30;
|
|
176
|
+
/** Window of the "this month" score and the top-helpers list. */
|
|
177
|
+
export const CONTRIBUTION_RECENT_DAYS = 30;
|
|
178
|
+
/** Rows on the top-helpers list. */
|
|
179
|
+
export const CONTRIBUTION_LEADERBOARD_SIZE = 20;
|
|
180
|
+
// ─── Notifications ───────────────────────────────────────────────────────────
|
|
181
|
+
/** Days a notification is kept. Neither Jansathi nor GymMonk ever deletes one. */
|
|
182
|
+
export const NOTIFICATION_RETENTION_DAYS = 90;
|
|
183
|
+
/** The first words of a comment or review a notification shows. */
|
|
184
|
+
export const NOTIFICATION_EXCERPT_MAX = 140;
|
|
109
185
|
//# sourceMappingURL=limits.js.map
|
package/dist/core/limits.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"limits.js","sourceRoot":"","sources":["../../src/core/limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,gFAAgF;AAEhF,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AACnC,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,CAAC;AAChC,MAAM,CAAC,MAAM,SAAS,GAAG,IAAI,CAAC;AAC9B,0DAA0D;AAC1D,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC;AAC5B,gFAAgF;AAChF,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAC;AAE3B,gFAAgF;AAEhF,MAAM,CAAC,MAAM,QAAQ,GAAG,EAAE,CAAC;AAC3B,MAAM,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC;AAC5B,MAAM,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AAE9B,gFAAgF;AAEhF,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,CAAC;AACpC,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,CAAC;AACvC,uCAAuC;AACvC,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC;AAC/B,MAAM,CAAC,MAAM,aAAa,GAAG,OAAO,CAAC;AACrC,mFAAmF;AACnF,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAErC,gFAAgF;AAEhF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,CAAC;AAEhC,gFAAgF;AAEhF,kFAAkF;AAClF,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,CAAC;AACjC,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,CAAC;AAC1C,MAAM,CAAC,MAAM,4BAA4B,GAAG,GAAG,CAAC;AAChD,sCAAsC;AACtC,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC;AACpC,kFAAkF;AAClF,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,CAAC;AACvC,iFAAiF;AACjF,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,CAAC;AAE1C,gFAAgF;AAEhF,kFAAkF;AAClF,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC;AACvC,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAG,CAAC;AACzC,8EAA8E;AAC9E,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAErC,gFAAgF;AAEhF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC;AACpC,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAG,CAAC;AACtC,2DAA2D;AAC3D,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC;AACvC;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,CAAC;AACvC,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,CAAC;AACxC,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC;AACzC,mEAAmE;AACnE,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAErC,gFAAgF;AAEhF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAEjD,gFAAgF;AAEhF,8EAA8E;AAC9E,MAAM,CAAC,MAAM,0BAA0B,GAAG,EAAE,CAAC;AAC7C,sEAAsE;AACtE,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAC;AACpC,oDAAoD;AACpD,MAAM,CAAC,MAAM,aAAa,GAAG,EAAE,CAAC;AAChC,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAEpC,gFAAgF;AAEhF;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAG,CAAC","sourcesContent":["/**\n * founder-schema — Every length, size and count limit, by name\n *\n * ONE PLACE FOR EVERY NUMBER a client and the server must agree on. Before this,\n * the limits lived inline (`.max(80)`, `.max(1000)`) where nothing else could\n * read them, so the web client re-typed each one for its counters and the\n * backend re-typed them as Mongoose `maxlength`. Three copies of one number is\n * how the image alt text came to be 280 here and 500 in the backend.\n *\n * The rule from 0.2.0: a schema uses one of these constants, a consumer imports\n * the same constant, and nobody writes the digits again.\n *\n * Imports nothing, so any module here can use it without a cycle. Limits that\n * already had a name in their own module (POST_MAX_BODY_CHARS, POLL_*,\n * USERNAME_*, INTERESTS_*, MIN_AGE_YEARS...) stay where they are and keep their\n * names; this file holds the ones that had none.\n *\n * ⚠️ Lowering a limit is a breaking change for stored data: a value that was\n * valid when written fails the next edit. Raise freely; lower deliberately.\n *\n * @module founder-schema/core/limits\n */\n\n// ─── Profile ─────────────────────────────────────────────────────────────────\n\nexport const DISPLAY_NAME_MAX = 80;\nexport const HEADLINE_MAX = 120;\nexport const ABOUT_MAX = 1000;\n/** Skills a member may list. Discover filters on them. */\nexport const SKILLS_MAX = 8;\n/** Goals a member must pick at onboarding. The list itself caps the maximum. */\nexport const GOALS_MIN = 1;\n\n// ─── Location ────────────────────────────────────────────────────────────────\n\nexport const CITY_MAX = 80;\nexport const STATE_MAX = 80;\nexport const COUNTRY_MAX = 80;\n\n// ─── Venture ─────────────────────────────────────────────────────────────────\n\nexport const VENTURE_NAME_MAX = 120;\nexport const VENTURE_TAGLINE_MAX = 160;\n/** Team size including the founder. */\nexport const TEAM_SIZE_MIN = 1;\nexport const TEAM_SIZE_MAX = 100_000;\n/** Earliest founding year accepted. The ceiling is \"this year\", read per parse. */\nexport const FOUNDED_YEAR_MIN = 1900;\n\n// ─── Links ───────────────────────────────────────────────────────────────────\n\n/**\n * Longest URL a member may TYPE. Applied by `httpUrlSchema` itself, so every\n * member URL carries it: profile links, a venture website.\n *\n * ⚠ NOT applied to URLs the server reads from elsewhere (a link preview's\n * page or image), which use `safeUrlSchema`: a 314-character CDN image URL is\n * ordinary, and one of them would otherwise fail a whole feed page.\n */\nexport const LINK_URL_MAX = 300;\n\n// ─── Feed and moderation ─────────────────────────────────────────────────────\n\n/** Author-supplied image description. The backend's stored copy uses this too. */\nexport const MEDIA_ALT_MAX = 280;\nexport const LINK_PREVIEW_TITLE_MAX = 200;\nexport const LINK_PREVIEW_DESCRIPTION_MAX = 500;\n/** The free-text note on a report. */\nexport const REPORT_NOTE_MAX = 1000;\n/** A moderator's note on a decision (why it was hidden, removed or dismissed). */\nexport const MODERATION_NOTE_MAX = 500;\n/** What a post, poll or comment is shown as in a staff list: its first words. */\nexport const MODERATION_EXCERPT_MAX = 160;\n\n// ─── Staff ───────────────────────────────────────────────────────────────────\n\n/** Why a member was suspended. Shown to staff, and on record in the audit log. */\nexport const SUSPENSION_REASON_MIN = 3;\nexport const SUSPENSION_REASON_MAX = 500;\n/** The staff member directory's search box (a name, a handle or an email). */\nexport const MEMBER_SEARCH_MAX = 120;\n\n// ─── Support ─────────────────────────────────────────────────────────────────\n\nexport const TICKET_SUBJECT_MIN = 3;\nexport const TICKET_SUBJECT_MAX = 120;\n/** One message in a ticket, the member's or an agent's. */\nexport const TICKET_MESSAGE_MAX = 4000;\n/**\n * Messages one ticket may hold. A conversation that long is a new problem, so\n * the member is asked to open a new ticket rather than the thread growing\n * without bound.\n */\nexport const TICKET_MESSAGES_MAX = 200;\nexport const SAVED_REPLY_TITLE_MAX = 80;\nexport const SAVED_REPLY_BODY_MAX = 4000;\n/** Saved replies kept in all: a team's library, not an archive. */\nexport const SAVED_REPLIES_MAX = 200;\n\n// ─── Uploads ─────────────────────────────────────────────────────────────────\n\n/**\n * Largest file accepted for upload: 10 MB.\n *\n * Checked on BOTH sides and for different reasons. The server refuses a bigger\n * object when it is attached. The client checks before uploading, because a\n * presigned PUT caps the content type but not the size, so a member on a phone\n * connection would otherwise watch a 40 MB upload finish and then fail.\n */\nexport const UPLOAD_MAX_BYTES = 10 * 1024 * 1024;\n\n// ─── Lists and search ────────────────────────────────────────────────────────\n\n/** Values one Discover multi-select filter may carry (`?skill=a&skill=b`). */\nexport const DISCOVER_FILTER_VALUES_MAX = 10;\n/** Discover's free-text inputs: the name search, city and country. */\nexport const DISCOVER_TEXT_MAX = 80;\n/** Page size bounds for every cursor-paged list. */\nexport const PAGE_SIZE_MAX = 50;\nexport const PAGE_SIZE_DEFAULT = 20;\n\n// ─── Chat ────────────────────────────────────────────────────────────────────\n\n/**\n * Members one participants lookup may name. Bounded so the lookup cannot be\n * used to page through the directory.\n */\nexport const CHAT_PARTICIPANTS_MAX = 100;\n"]}
|
|
1
|
+
{"version":3,"file":"limits.js","sourceRoot":"","sources":["../../src/core/limits.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema — Every length, size and count limit, by name\n *\n * ONE PLACE FOR EVERY NUMBER a client and the server must agree on. Before this,\n * the limits lived inline (`.max(80)`, `.max(1000)`) where nothing else could\n * read them, so the web client re-typed each one for its counters and the\n * backend re-typed them as Mongoose `maxlength`. Three copies of one number is\n * how the image alt text came to be 280 here and 500 in the backend.\n *\n * The rule from 0.2.0: a schema uses one of these constants, a consumer imports\n * the same constant, and nobody writes the digits again.\n *\n * Imports nothing, so any module here can use it without a cycle. Limits that\n * already had a name in their own module (POST_MAX_BODY_CHARS, POLL_*,\n * USERNAME_*, INTERESTS_*, MIN_AGE_YEARS...) stay where they are and keep their\n * names; this file holds the ones that had none.\n *\n * ⚠️ Lowering a limit is a breaking change for stored data: a value that was\n * valid when written fails the next edit. Raise freely; lower deliberately.\n *\n * @module founder-schema/core/limits\n */\n\n// ─── Profile ─────────────────────────────────────────────────────────────────\n\nexport const DISPLAY_NAME_MAX = 80;\nexport const HEADLINE_MAX = 120;\nexport const ABOUT_MAX = 1000;\n/** Skills a member may list. Discover filters on them. */\nexport const SKILLS_MAX = 8;\n/** Goals a member must pick at onboarding. The list itself caps the maximum. */\nexport const GOALS_MIN = 1;\n\n// ─── Location ────────────────────────────────────────────────────────────────\n\nexport const CITY_MAX = 80;\nexport const STATE_MAX = 80;\nexport const COUNTRY_MAX = 80;\n\n// ─── Venture ─────────────────────────────────────────────────────────────────\n\nexport const VENTURE_NAME_MAX = 120;\nexport const VENTURE_TAGLINE_MAX = 160;\n/** Team size including the founder. */\nexport const TEAM_SIZE_MIN = 1;\nexport const TEAM_SIZE_MAX = 100_000;\n/** Earliest founding year accepted. The ceiling is \"this year\", read per parse. */\nexport const FOUNDED_YEAR_MIN = 1900;\n\n// ─── Links ───────────────────────────────────────────────────────────────────\n\n/**\n * Longest URL a member may TYPE. Applied by `httpUrlSchema` itself, so every\n * member URL carries it: profile links, a venture website.\n *\n * ⚠ NOT applied to URLs the server reads from elsewhere (a link preview's\n * page or image), which use `safeUrlSchema`: a 314-character CDN image URL is\n * ordinary, and one of them would otherwise fail a whole feed page.\n */\nexport const LINK_URL_MAX = 300;\n\n// ─── Feed and moderation ─────────────────────────────────────────────────────\n\n/** Author-supplied image description. The backend's stored copy uses this too. */\nexport const MEDIA_ALT_MAX = 280;\nexport const LINK_PREVIEW_TITLE_MAX = 200;\nexport const LINK_PREVIEW_DESCRIPTION_MAX = 500;\n/** The free-text note on a report. */\nexport const REPORT_NOTE_MAX = 1000;\n/** A moderator's note on a decision (why it was hidden, removed or dismissed). */\nexport const MODERATION_NOTE_MAX = 500;\n/** What a post, poll or comment is shown as in a staff list: its first words. */\nexport const MODERATION_EXCERPT_MAX = 160;\n\n// ─── Staff ───────────────────────────────────────────────────────────────────\n\n/** Why a member was suspended. Shown to staff, and on record in the audit log. */\nexport const SUSPENSION_REASON_MIN = 3;\nexport const SUSPENSION_REASON_MAX = 500;\n/** The staff member directory's search box (a name, a handle or an email). */\nexport const MEMBER_SEARCH_MAX = 120;\n\n// ─── Support ─────────────────────────────────────────────────────────────────\n\nexport const TICKET_SUBJECT_MIN = 3;\nexport const TICKET_SUBJECT_MAX = 120;\n/** One message in a ticket, the member's or an agent's. */\nexport const TICKET_MESSAGE_MAX = 4000;\n/**\n * Messages one ticket may hold. A conversation that long is a new problem, so\n * the member is asked to open a new ticket rather than the thread growing\n * without bound.\n */\nexport const TICKET_MESSAGES_MAX = 200;\nexport const SAVED_REPLY_TITLE_MAX = 80;\nexport const SAVED_REPLY_BODY_MAX = 4000;\n/** Saved replies kept in all: a team's library, not an archive. */\nexport const SAVED_REPLIES_MAX = 200;\n\n// ─── Uploads ─────────────────────────────────────────────────────────────────\n\n/**\n * Largest file accepted for upload: 10 MB.\n *\n * Enforced three times. The upload-slot body refuses a bigger `contentLength`;\n * the size is SIGNED INTO the upload URL, so storage refuses a body of any other\n * size; and the server checks the stored object again before processing it.\n * The client checks first only to fail fast: it shrinks a photo well below this\n * before uploading (see `MEDIA_SIZES`), so a member rarely meets the limit.\n */\nexport const UPLOAD_MAX_BYTES = 10 * 1024 * 1024;\n\n/**\n * Largest image, in pixels, the server will decode: 50 megapixels, one frame.\n *\n * Bytes do not bound memory. A 50,000 x 50,000 PNG of one colour compresses to\n * a few kilobytes and decodes to ten gigabytes. The server reads the dimensions\n * first and refuses anything above this before decoding (`UPLOAD_TOO_MANY_PIXELS`).\n * 50 MP admits every phone camera's full-resolution photo up to 48 MP.\n */\nexport const MEDIA_MAX_INPUT_PIXELS = 50_000_000;\n\n/**\n * Hours an upload may wait to be attached to a post or set as an avatar. After\n * that the server deletes it: the composer was abandoned, or the tab closed.\n */\nexport const MEDIA_UNATTACHED_TTL_HOURS = 24;\n\n/**\n * Uploads one member may have waiting (requested or processed, not yet\n * attached) at once. A full post is `POST_MAX_MEDIA` (10); this leaves room for\n * a retry or two without letting one account fill storage.\n */\nexport const MEDIA_PENDING_MAX = 30;\n\n/** Longest placeholder string: a base64 ThumbHash is about 30 characters. */\nexport const MEDIA_PLACEHOLDER_MAX = 100;\n\n// ─── Lists and search ────────────────────────────────────────────────────────\n\n/** Values one Discover multi-select filter may carry (`?skill=a&skill=b`). */\nexport const DISCOVER_FILTER_VALUES_MAX = 10;\n/** Discover's free-text inputs: the name search, city and country. */\nexport const DISCOVER_TEXT_MAX = 80;\n/** Page size bounds for every cursor-paged list. */\nexport const PAGE_SIZE_MAX = 50;\nexport const PAGE_SIZE_DEFAULT = 20;\n\n// ─── Chat ────────────────────────────────────────────────────────────────────\n\n/**\n * Members one participants lookup may name. Bounded so the lookup cannot be\n * used to page through the directory.\n */\nexport const CHAT_PARTICIPANTS_MAX = 100;\n\n// ─── Products ────────────────────────────────────────────────────────────────\n\nexport const PRODUCT_NAME_MAX = 60;\n/** One line on a Discover card: short enough to never wrap twice on a phone. */\nexport const PRODUCT_TAGLINE_MAX = 80;\nexport const PRODUCT_DESCRIPTION_MAX = 3000;\n/** Screenshots on one product page. The first is its cover on Discover. */\nexport const PRODUCT_SCREENSHOTS_MAX = 8;\nexport const PRODUCT_CAPTION_MAX = 140;\n/**\n * Products one founder may OWN at once (archived ones count; deleting frees a\n * place). Being on someone else's team is not counted: the user ruled there is\n * no limit on a product's team, and so none on how many teams one joins.\n */\nexport const PRODUCTS_OWNED_MAX = 5;\n/** Team members a product page shows; the rest are one tap away (`/team`). */\nexport const PRODUCT_TEAM_SHOWN = 12;\n/**\n * Days a post's ask keeps its product on the \"asking for testers\" (or feedback,\n * launch, ideas) shelf. Each new post with that ask restarts the clock.\n */\nexport const PRODUCT_ASK_OPEN_DAYS = 14;\n\n// ─── Reviews ─────────────────────────────────────────────────────────────────\n\n/**\n * A review says WHY. A star with no reason does not help the founder, which is\n * the whole point of reviewing here, so the text is required and has a floor.\n */\nexport const REVIEW_TEXT_MIN = 30;\nexport const REVIEW_TEXT_MAX = 2000;\nexport const REVIEW_REPLY_MAX = 1000;\n/**\n * Reviews before a product shows an average (and enters \"Top rated\"). One or two\n * ratings say more about who wrote them than about the product.\n */\nexport const REVIEW_AVERAGE_MIN_COUNT = 3;\n\n// ─── Contribution ────────────────────────────────────────────────────────────\n\n/** Points one review can earn its writer through other members' Helpful votes. */\nexport const REVIEW_HELPFUL_POINTS_MAX = 10;\n/** Reviews a day that earn points for writing them. More are welcome; they earn nothing. */\nexport const REVIEWS_REWARDED_PER_DAY = 3;\n/**\n * Points one member can earn from ONE other member over any 30 days, all kinds\n * together, so two founders cannot farm each other by trading Helpful marks.\n */\nexport const CONTRIBUTION_PAIR_POINTS_MAX = 30;\n/** Window of the \"this month\" score and the top-helpers list. */\nexport const CONTRIBUTION_RECENT_DAYS = 30;\n/** Rows on the top-helpers list. */\nexport const CONTRIBUTION_LEADERBOARD_SIZE = 20;\n\n// ─── Notifications ───────────────────────────────────────────────────────────\n\n/** Days a notification is kept. Neither Jansathi nor GymMonk ever deletes one. */\nexport const NOTIFICATION_RETENTION_DAYS = 90;\n/** The first words of a comment or review a notification shows. */\nexport const NOTIFICATION_EXCERPT_MAX = 140;\n"]}
|
package/dist/core/responses.d.ts
CHANGED
|
@@ -35,6 +35,11 @@ export declare const apiErrorDetailsSchema: z.ZodObject<{
|
|
|
35
35
|
}, z.core.$strip>>>;
|
|
36
36
|
attemptsLeft: z.ZodOptional<z.ZodNumber>;
|
|
37
37
|
lockedUntil: z.ZodOptional<z.ZodISODateTime>;
|
|
38
|
+
missing: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
39
|
+
logo: "logo";
|
|
40
|
+
tagline: "tagline";
|
|
41
|
+
screenshot: "screenshot";
|
|
42
|
+
}>>>;
|
|
38
43
|
}, z.core.$strip>;
|
|
39
44
|
export type ApiErrorDetails = z.infer<typeof apiErrorDetailsSchema>;
|
|
40
45
|
/**
|
|
@@ -60,12 +65,32 @@ export declare const apiErrorSchema: z.ZodObject<{
|
|
|
60
65
|
UPLOAD_NOT_ATTACHABLE: "UPLOAD_NOT_ATTACHABLE";
|
|
61
66
|
UPLOAD_TOO_LARGE: "UPLOAD_TOO_LARGE";
|
|
62
67
|
UPLOAD_WRONG_TYPE: "UPLOAD_WRONG_TYPE";
|
|
68
|
+
UPLOAD_NOT_RECEIVED: "UPLOAD_NOT_RECEIVED";
|
|
69
|
+
UPLOAD_PROCESSING: "UPLOAD_PROCESSING";
|
|
70
|
+
UPLOAD_UNREADABLE: "UPLOAD_UNREADABLE";
|
|
71
|
+
UPLOAD_TOO_MANY_PIXELS: "UPLOAD_TOO_MANY_PIXELS";
|
|
72
|
+
UPLOAD_LIMIT_REACHED: "UPLOAD_LIMIT_REACHED";
|
|
73
|
+
UPLOAD_IN_USE: "UPLOAD_IN_USE";
|
|
63
74
|
EDIT_WINDOW_CLOSED: "EDIT_WINDOW_CLOSED";
|
|
64
75
|
POLL_NOT_EDITABLE: "POLL_NOT_EDITABLE";
|
|
65
76
|
POLL_CLOSED: "POLL_CLOSED";
|
|
66
77
|
POLL_CHOICE_INVALID: "POLL_CHOICE_INVALID";
|
|
67
78
|
REPLY_TOO_DEEP: "REPLY_TOO_DEEP";
|
|
68
79
|
CANNOT_REPORT_OWN: "CANNOT_REPORT_OWN";
|
|
80
|
+
FEEDBACK_NOT_MARKABLE: "FEEDBACK_NOT_MARKABLE";
|
|
81
|
+
PRODUCT_LIMIT_REACHED: "PRODUCT_LIMIT_REACHED";
|
|
82
|
+
PRODUCT_NOT_LISTABLE: "PRODUCT_NOT_LISTABLE";
|
|
83
|
+
PRODUCT_ARCHIVED: "PRODUCT_ARCHIVED";
|
|
84
|
+
PRODUCT_NOT_YOURS: "PRODUCT_NOT_YOURS";
|
|
85
|
+
PRODUCT_ALREADY_MEMBER: "PRODUCT_ALREADY_MEMBER";
|
|
86
|
+
PRODUCT_MEMBER_NOT_INVITABLE: "PRODUCT_MEMBER_NOT_INVITABLE";
|
|
87
|
+
PRODUCT_NOT_ON_TEAM: "PRODUCT_NOT_ON_TEAM";
|
|
88
|
+
PRODUCT_OWNER_MUST_TRANSFER: "PRODUCT_OWNER_MUST_TRANSFER";
|
|
89
|
+
PRODUCT_OWN_TEAM: "PRODUCT_OWN_TEAM";
|
|
90
|
+
TESTER_NOT_ELIGIBLE: "TESTER_NOT_ELIGIBLE";
|
|
91
|
+
REVIEW_EXISTS: "REVIEW_EXISTS";
|
|
92
|
+
REVIEW_OWN_VOTE: "REVIEW_OWN_VOTE";
|
|
93
|
+
MODERATION_ACTION_INVALID: "MODERATION_ACTION_INVALID";
|
|
69
94
|
CHAT_WITH_SELF: "CHAT_WITH_SELF";
|
|
70
95
|
ACCOUNT_SUSPENDED: "ACCOUNT_SUSPENDED";
|
|
71
96
|
ROLE_NOT_HELD: "ROLE_NOT_HELD";
|
|
@@ -96,6 +121,11 @@ export declare const apiErrorSchema: z.ZodObject<{
|
|
|
96
121
|
}, z.core.$strip>>>;
|
|
97
122
|
attemptsLeft: z.ZodOptional<z.ZodNumber>;
|
|
98
123
|
lockedUntil: z.ZodOptional<z.ZodISODateTime>;
|
|
124
|
+
missing: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
125
|
+
logo: "logo";
|
|
126
|
+
tagline: "tagline";
|
|
127
|
+
screenshot: "screenshot";
|
|
128
|
+
}>>>;
|
|
99
129
|
}, z.core.$strip>>;
|
|
100
130
|
}, z.core.$strip>;
|
|
101
131
|
export type ApiError = z.infer<typeof apiErrorSchema>;
|
|
@@ -126,12 +156,32 @@ export declare const errorResponseSchema: z.ZodObject<{
|
|
|
126
156
|
UPLOAD_NOT_ATTACHABLE: "UPLOAD_NOT_ATTACHABLE";
|
|
127
157
|
UPLOAD_TOO_LARGE: "UPLOAD_TOO_LARGE";
|
|
128
158
|
UPLOAD_WRONG_TYPE: "UPLOAD_WRONG_TYPE";
|
|
159
|
+
UPLOAD_NOT_RECEIVED: "UPLOAD_NOT_RECEIVED";
|
|
160
|
+
UPLOAD_PROCESSING: "UPLOAD_PROCESSING";
|
|
161
|
+
UPLOAD_UNREADABLE: "UPLOAD_UNREADABLE";
|
|
162
|
+
UPLOAD_TOO_MANY_PIXELS: "UPLOAD_TOO_MANY_PIXELS";
|
|
163
|
+
UPLOAD_LIMIT_REACHED: "UPLOAD_LIMIT_REACHED";
|
|
164
|
+
UPLOAD_IN_USE: "UPLOAD_IN_USE";
|
|
129
165
|
EDIT_WINDOW_CLOSED: "EDIT_WINDOW_CLOSED";
|
|
130
166
|
POLL_NOT_EDITABLE: "POLL_NOT_EDITABLE";
|
|
131
167
|
POLL_CLOSED: "POLL_CLOSED";
|
|
132
168
|
POLL_CHOICE_INVALID: "POLL_CHOICE_INVALID";
|
|
133
169
|
REPLY_TOO_DEEP: "REPLY_TOO_DEEP";
|
|
134
170
|
CANNOT_REPORT_OWN: "CANNOT_REPORT_OWN";
|
|
171
|
+
FEEDBACK_NOT_MARKABLE: "FEEDBACK_NOT_MARKABLE";
|
|
172
|
+
PRODUCT_LIMIT_REACHED: "PRODUCT_LIMIT_REACHED";
|
|
173
|
+
PRODUCT_NOT_LISTABLE: "PRODUCT_NOT_LISTABLE";
|
|
174
|
+
PRODUCT_ARCHIVED: "PRODUCT_ARCHIVED";
|
|
175
|
+
PRODUCT_NOT_YOURS: "PRODUCT_NOT_YOURS";
|
|
176
|
+
PRODUCT_ALREADY_MEMBER: "PRODUCT_ALREADY_MEMBER";
|
|
177
|
+
PRODUCT_MEMBER_NOT_INVITABLE: "PRODUCT_MEMBER_NOT_INVITABLE";
|
|
178
|
+
PRODUCT_NOT_ON_TEAM: "PRODUCT_NOT_ON_TEAM";
|
|
179
|
+
PRODUCT_OWNER_MUST_TRANSFER: "PRODUCT_OWNER_MUST_TRANSFER";
|
|
180
|
+
PRODUCT_OWN_TEAM: "PRODUCT_OWN_TEAM";
|
|
181
|
+
TESTER_NOT_ELIGIBLE: "TESTER_NOT_ELIGIBLE";
|
|
182
|
+
REVIEW_EXISTS: "REVIEW_EXISTS";
|
|
183
|
+
REVIEW_OWN_VOTE: "REVIEW_OWN_VOTE";
|
|
184
|
+
MODERATION_ACTION_INVALID: "MODERATION_ACTION_INVALID";
|
|
135
185
|
CHAT_WITH_SELF: "CHAT_WITH_SELF";
|
|
136
186
|
ACCOUNT_SUSPENDED: "ACCOUNT_SUSPENDED";
|
|
137
187
|
ROLE_NOT_HELD: "ROLE_NOT_HELD";
|
|
@@ -162,6 +212,11 @@ export declare const errorResponseSchema: z.ZodObject<{
|
|
|
162
212
|
}, z.core.$strip>>>;
|
|
163
213
|
attemptsLeft: z.ZodOptional<z.ZodNumber>;
|
|
164
214
|
lockedUntil: z.ZodOptional<z.ZodISODateTime>;
|
|
215
|
+
missing: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
216
|
+
logo: "logo";
|
|
217
|
+
tagline: "tagline";
|
|
218
|
+
screenshot: "screenshot";
|
|
219
|
+
}>>>;
|
|
165
220
|
}, z.core.$strip>>;
|
|
166
221
|
}, z.core.$strip>;
|
|
167
222
|
}, z.core.$strip>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"responses.d.ts","sourceRoot":"","sources":["../../src/core/responses.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB
|
|
1
|
+
{"version":3,"file":"responses.d.ts","sourceRoot":"","sources":["../../src/core/responses.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;iBAQhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE;;;GAGG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAIzB,CAAC;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAEtD,mDAAmD;AACnD,eAAO,MAAM,qBAAqB,GAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC;;;;iBAK9D,CAAC;AAEL,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAG9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC;;;iBAIxD,CAAC;AAEL;;;;GAIG;AACH,eAAO,MAAM,gBAAgB;;iBAAmC,CAAC;AACjE,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
|
package/dist/core/responses.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @module founder-schema/core/responses
|
|
9
9
|
*/
|
|
10
10
|
import { z } from 'zod';
|
|
11
|
-
import { objectIdSchema } from './common.js';
|
|
11
|
+
import { objectIdSchema, productListingRequirementSchema } from './common.js';
|
|
12
12
|
import { apiErrorCodeSchema } from './errors.js';
|
|
13
13
|
import { validationIssueSchema } from './validation.js';
|
|
14
14
|
/**
|
|
@@ -27,6 +27,8 @@ export const apiErrorDetailsSchema = z.object({
|
|
|
27
27
|
attemptsLeft: z.number().int().min(0).optional(),
|
|
28
28
|
/** `MPIN_LOCKED`: when the MPIN can be tried again. */
|
|
29
29
|
lockedUntil: z.iso.datetime().optional(),
|
|
30
|
+
/** `PRODUCT_NOT_LISTABLE`: what the product lacks to be (or stay) listed. */
|
|
31
|
+
missing: z.array(productListingRequirementSchema).optional(),
|
|
30
32
|
});
|
|
31
33
|
/**
|
|
32
34
|
* `code` is one of `API_ERROR_CODE_VALUES`, and it is what a client acts on.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"responses.js","sourceRoot":"","sources":["../../src/core/responses.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"responses.js","sourceRoot":"","sources":["../../src/core/responses.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,cAAc,EAAE,+BAA+B,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAExD;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC,QAAQ,EAAE;IACjD,gEAAgE;IAChE,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IAChD,uDAAuD;IACvD,WAAW,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IACxC,6EAA6E;IAC7E,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC,QAAQ,EAAE;CAC7D,CAAC,CAAC;AAGH;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC;IACrC,IAAI,EAAE,kBAAkB;IACxB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,OAAO,EAAE,qBAAqB,CAAC,QAAQ,EAAE;CAC1C,CAAC,CAAC;AAGH,mDAAmD;AACnD,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAsB,IAAO,EAAE,EAAE,CACpE,CAAC,CAAC,MAAM,CAAC;IACP,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;IACxB,IAAI;IACJ,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC/B,CAAC,CAAC;AAEL,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1C,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;IACzB,KAAK,EAAE,cAAc;CACtB,CAAC,CAAC;AAGH;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAsB,IAAO,EAAE,EAAE,CAC9D,CAAC,CAAC,MAAM,CAAC;IACP,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;IACpB,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAClC,CAAC,CAAC;AAEL;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,cAAc,EAAE,CAAC,CAAC","sourcesContent":["/**\n * founder-schema — API response envelope\n *\n * Every Founder Circle endpoint answers in this shape. The clients read the\n * failure reason at `error.message`, so a handler must never return a bare\n * string body.\n *\n * @module founder-schema/core/responses\n */\n\nimport { z } from 'zod';\nimport { objectIdSchema, productListingRequirementSchema } from './common.js';\nimport { apiErrorCodeSchema } from './errors.js';\nimport { validationIssueSchema } from './validation.js';\n\n/**\n * What an error carries beyond its code, each field named by the code that\n * sends it. Declared here, not loosely typed, because a client parses errors\n * with this schema and Zod drops a key the schema does not name.\n *\n * `issues` is EVERY problem of a 400, each with its path, built on the server\n * by `toValidationIssues`. Until 0.2.0 this was typed as a record of strings\n * that nothing ever sent, while the real 400s carried only the first issue's\n * message, so a client could not tell which field to mark.\n */\nexport const apiErrorDetailsSchema = z.object({\n issues: z.array(validationIssueSchema).optional(),\n /** `MPIN_INCORRECT`: wrong tries left before the MPIN locks. */\n attemptsLeft: z.number().int().min(0).optional(),\n /** `MPIN_LOCKED`: when the MPIN can be tried again. */\n lockedUntil: z.iso.datetime().optional(),\n /** `PRODUCT_NOT_LISTABLE`: what the product lacks to be (or stay) listed. */\n missing: z.array(productListingRequirementSchema).optional(),\n});\nexport type ApiErrorDetails = z.infer<typeof apiErrorDetailsSchema>;\n\n/**\n * `code` is one of `API_ERROR_CODE_VALUES`, and it is what a client acts on.\n * `message` is English, for logs and for a client that predates the code.\n */\nexport const apiErrorSchema = z.object({\n code: apiErrorCodeSchema,\n message: z.string(),\n details: apiErrorDetailsSchema.optional(),\n});\nexport type ApiError = z.infer<typeof apiErrorSchema>;\n\n/** Success envelope. `T` is the payload schema. */\nexport const successResponseSchema = <T extends z.ZodType>(data: T) =>\n z.object({\n success: z.literal(true),\n data,\n message: z.string().optional(),\n });\n\nexport const errorResponseSchema = z.object({\n success: z.literal(false),\n error: apiErrorSchema,\n});\nexport type ErrorResponse = z.infer<typeof errorResponseSchema>;\n\n/**\n * Cursor-paginated list envelope.\n *\n * `nextCursor` is REQUIRED and is `null` at the end of the list. Omitting the\n * key is not the same thing and does not validate: a handler must decide\n * whether there is another page and say so, because \"the key is missing\" and\n * \"there are no more results\" are indistinguishable to a client that has to\n * choose between showing a Load more button and not showing one.\n *\n * An earlier version of this comment said absence and null were equivalent.\n * They never were; the schema has always required the key. Corrected\n * 2026-09-21, and pinned by a test.\n */\nexport const paginatedSchema = <T extends z.ZodType>(item: T) =>\n z.object({\n items: z.array(item),\n nextCursor: z.string().nullable(),\n });\n\n/**\n * What a create, an edit or a delete answers: the id of the thing it touched.\n * A client that needs the whole item reads it, so a write never ships a second\n * projection of it that could drift from the read path's.\n */\nexport const idResponseSchema = z.object({ id: objectIdSchema });\nexport type IdResponse = z.infer<typeof idResponseSchema>;\n"]}
|
|
@@ -57,6 +57,8 @@ export declare const VALIDATION_MESSAGE: {
|
|
|
57
57
|
readonly foundedYearFuture: "foundedYearFuture";
|
|
58
58
|
/** An upload key that is not one this product mints. */
|
|
59
59
|
readonly uploadKeyInvalid: "uploadKeyInvalid";
|
|
60
|
+
/** The same uploaded image attached twice to one post. */
|
|
61
|
+
readonly mediaDuplicate: "mediaDuplicate";
|
|
60
62
|
/** A post with neither text nor media. */
|
|
61
63
|
readonly postEmpty: "postEmpty";
|
|
62
64
|
/** Two poll options with the same label. */
|
|
@@ -77,6 +79,20 @@ export declare const VALIDATION_MESSAGE: {
|
|
|
77
79
|
readonly mpinRequired: "mpinRequired";
|
|
78
80
|
/** The same role twice in one list. */
|
|
79
81
|
readonly rolesDuplicate: "rolesDuplicate";
|
|
82
|
+
/** An ask ("testers wanted") on a post or poll that names no product. */
|
|
83
|
+
readonly askWithoutProduct: "askWithoutProduct";
|
|
84
|
+
/** An App Store link that is not on apps.apple.com. */
|
|
85
|
+
readonly appStoreLinkHost: "appStoreLinkHost";
|
|
86
|
+
/** A Play Store link that is not on play.google.com. */
|
|
87
|
+
readonly playStoreLinkHost: "playStoreLinkHost";
|
|
88
|
+
/** A GitHub link that is not on github.com. */
|
|
89
|
+
readonly githubLinkHost: "githubLinkHost";
|
|
90
|
+
/** A demo video that is not a YouTube link. Video is shared, never uploaded. */
|
|
91
|
+
readonly videoLinkHost: "videoLinkHost";
|
|
92
|
+
/** The same screenshot twice in one gallery. */
|
|
93
|
+
readonly screenshotsDuplicate: "screenshotsDuplicate";
|
|
94
|
+
/** The same platform twice in one list. */
|
|
95
|
+
readonly platformsDuplicate: "platformsDuplicate";
|
|
80
96
|
};
|
|
81
97
|
export type ValidationMessageKey = (typeof VALIDATION_MESSAGE)[keyof typeof VALIDATION_MESSAGE];
|
|
82
98
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/core/validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB;IAC7B,6BAA6B;;IAE7B,6DAA6D;;IAE7D,2CAA2C;;IAE3C,+BAA+B;;IAE/B,uDAAuD;;IAEvD,6BAA6B;;IAE7B,8DAA8D;;IAE9D,uCAAuC;;IAEvC,wDAAwD;;IAExD,0CAA0C;;IAE1C,4CAA4C;;IAE5C,uEAAuE;;IAEvE,uFAAuF;;IAEvF,sEAAsE;;IAEtE,yEAAyE;;IAEzE,8CAA8C;;IAE9C,4CAA4C;;IAE5C,kDAAkD;;IAElD,uCAAuC;;
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/core/validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB;IAC7B,6BAA6B;;IAE7B,6DAA6D;;IAE7D,2CAA2C;;IAE3C,+BAA+B;;IAE/B,uDAAuD;;IAEvD,6BAA6B;;IAE7B,8DAA8D;;IAE9D,uCAAuC;;IAEvC,wDAAwD;;IAExD,0DAA0D;;IAE1D,0CAA0C;;IAE1C,4CAA4C;;IAE5C,uEAAuE;;IAEvE,uFAAuF;;IAEvF,sEAAsE;;IAEtE,yEAAyE;;IAEzE,8CAA8C;;IAE9C,4CAA4C;;IAE5C,kDAAkD;;IAElD,uCAAuC;;IAEvC,yEAAyE;;IAEzE,uDAAuD;;IAEvD,wDAAwD;;IAExD,+CAA+C;;IAE/C,gFAAgF;;IAEhF,gDAAgD;;IAEhD,2CAA2C;;CAEnC,CAAC;AACX,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,OAAO,kBAAkB,CAAC,CAAC;AAEhG;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,sCAAuC,CAAC;AACxE,eAAO,MAAM,iBAAiB;;;;EAA8B,CAAC;AAC7D,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;iBAkBhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,6EAA6E;AAC7E,UAAU,YAAY;IACpB,IAAI,EAAE,SAAS,WAAW,EAAE,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAKD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE;IAAE,MAAM,EAAE,SAAS,YAAY,EAAE,CAAA;CAAE,EAC1C,QAAQ,CAAC,EAAE,WAAW,GACrB,eAAe,EAAE,CAkBnB"}
|