founder-schema 0.3.0 → 0.4.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 (102) hide show
  1. package/README.md +18 -15
  2. package/dist/access/access.d.ts +76 -0
  3. package/dist/access/access.d.ts.map +1 -0
  4. package/dist/access/access.js +66 -0
  5. package/dist/access/access.js.map +1 -0
  6. package/dist/access/permissions.d.ts +153 -0
  7. package/dist/access/permissions.d.ts.map +1 -0
  8. package/dist/access/permissions.js +80 -0
  9. package/dist/access/permissions.js.map +1 -0
  10. package/dist/access/roles.d.ts +71 -0
  11. package/dist/access/roles.d.ts.map +1 -0
  12. package/dist/access/roles.js +72 -0
  13. package/dist/access/roles.js.map +1 -0
  14. package/dist/chat.d.ts +4 -4
  15. package/dist/chat.d.ts.map +1 -1
  16. package/dist/chat.js +7 -2
  17. package/dist/chat.js.map +1 -1
  18. package/dist/core/common.d.ts +304 -0
  19. package/dist/core/common.d.ts.map +1 -0
  20. package/dist/core/common.js +340 -0
  21. package/dist/core/common.js.map +1 -0
  22. package/dist/core/errors.d.ts +105 -0
  23. package/dist/core/errors.d.ts.map +1 -0
  24. package/dist/core/errors.js +115 -0
  25. package/dist/core/errors.js.map +1 -0
  26. package/dist/core/limits.d.ts +99 -0
  27. package/dist/core/limits.d.ts.map +1 -0
  28. package/dist/core/limits.js +109 -0
  29. package/dist/core/limits.js.map +1 -0
  30. package/dist/core/responses.d.ts +195 -0
  31. package/dist/core/responses.d.ts.map +1 -0
  32. package/dist/core/responses.js +73 -0
  33. package/dist/core/responses.js.map +1 -0
  34. package/dist/core/validation.d.ts +144 -0
  35. package/dist/core/validation.d.ts.map +1 -0
  36. package/dist/core/validation.js +150 -0
  37. package/dist/core/validation.js.map +1 -0
  38. package/dist/domains/account/account.d.ts +40 -0
  39. package/dist/domains/account/account.d.ts.map +1 -0
  40. package/dist/domains/account/account.js +39 -0
  41. package/dist/domains/account/account.js.map +1 -0
  42. package/dist/domains/audit/audit.d.ts +169 -0
  43. package/dist/domains/audit/audit.d.ts.map +1 -0
  44. package/dist/domains/audit/audit.js +84 -0
  45. package/dist/domains/audit/audit.js.map +1 -0
  46. package/dist/domains/chat/chat.d.ts +187 -0
  47. package/dist/domains/chat/chat.d.ts.map +1 -0
  48. package/dist/domains/chat/chat.js +189 -0
  49. package/dist/domains/chat/chat.js.map +1 -0
  50. package/dist/domains/dashboard/dashboard.d.ts +48 -0
  51. package/dist/domains/dashboard/dashboard.d.ts.map +1 -0
  52. package/dist/domains/dashboard/dashboard.js +58 -0
  53. package/dist/domains/dashboard/dashboard.js.map +1 -0
  54. package/dist/domains/discover/discover.d.ts +245 -0
  55. package/dist/domains/discover/discover.d.ts.map +1 -0
  56. package/dist/domains/discover/discover.js +68 -0
  57. package/dist/domains/discover/discover.js.map +1 -0
  58. package/dist/domains/feed/feed.d.ts +1658 -0
  59. package/dist/domains/feed/feed.d.ts.map +1 -0
  60. package/dist/domains/feed/feed.js +623 -0
  61. package/dist/domains/feed/feed.js.map +1 -0
  62. package/dist/domains/members/members.d.ts +175 -0
  63. package/dist/domains/members/members.d.ts.map +1 -0
  64. package/dist/domains/members/members.js +106 -0
  65. package/dist/domains/members/members.js.map +1 -0
  66. package/dist/domains/moderation/moderation.d.ts +184 -0
  67. package/dist/domains/moderation/moderation.d.ts.map +1 -0
  68. package/dist/domains/moderation/moderation.js +90 -0
  69. package/dist/domains/moderation/moderation.js.map +1 -0
  70. package/dist/domains/onboarding/onboarding.d.ts +1018 -0
  71. package/dist/domains/onboarding/onboarding.d.ts.map +1 -0
  72. package/dist/domains/onboarding/onboarding.js +717 -0
  73. package/dist/domains/onboarding/onboarding.js.map +1 -0
  74. package/dist/domains/profile/founder-profile.d.ts +387 -0
  75. package/dist/domains/profile/founder-profile.d.ts.map +1 -0
  76. package/dist/domains/profile/founder-profile.js +180 -0
  77. package/dist/domains/profile/founder-profile.js.map +1 -0
  78. package/dist/domains/support/support.d.ts +305 -0
  79. package/dist/domains/support/support.d.ts.map +1 -0
  80. package/dist/domains/support/support.js +129 -0
  81. package/dist/domains/support/support.js.map +1 -0
  82. package/dist/domains/team/team.d.ts +79 -0
  83. package/dist/domains/team/team.d.ts.map +1 -0
  84. package/dist/domains/team/team.js +54 -0
  85. package/dist/domains/team/team.js.map +1 -0
  86. package/dist/domains/uploads/uploads.d.ts +113 -0
  87. package/dist/domains/uploads/uploads.d.ts.map +1 -0
  88. package/dist/domains/uploads/uploads.js +106 -0
  89. package/dist/domains/uploads/uploads.js.map +1 -0
  90. package/dist/env.d.ts +85 -0
  91. package/dist/env.d.ts.map +1 -0
  92. package/dist/env.js +150 -0
  93. package/dist/env.js.map +1 -0
  94. package/dist/index.d.ts +21 -12
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +29 -18
  97. package/dist/index.js.map +1 -1
  98. package/dist/onboarding.d.ts +58 -32
  99. package/dist/onboarding.d.ts.map +1 -1
  100. package/dist/onboarding.js +155 -32
  101. package/dist/onboarding.js.map +1 -1
  102. package/package.json +14 -2
@@ -0,0 +1,99 @@
1
+ /**
2
+ * founder-schema — Every length, size and count limit, by name
3
+ *
4
+ * ONE PLACE FOR EVERY NUMBER a client and the server must agree on. Before this,
5
+ * the limits lived inline (`.max(80)`, `.max(1000)`) where nothing else could
6
+ * read them, so the web client re-typed each one for its counters and the
7
+ * backend re-typed them as Mongoose `maxlength`. Three copies of one number is
8
+ * how the image alt text came to be 280 here and 500 in the backend.
9
+ *
10
+ * The rule from 0.2.0: a schema uses one of these constants, a consumer imports
11
+ * the same constant, and nobody writes the digits again.
12
+ *
13
+ * Imports nothing, so any module here can use it without a cycle. Limits that
14
+ * already had a name in their own module (POST_MAX_BODY_CHARS, POLL_*,
15
+ * USERNAME_*, INTERESTS_*, MIN_AGE_YEARS...) stay where they are and keep their
16
+ * names; this file holds the ones that had none.
17
+ *
18
+ * ⚠️ Lowering a limit is a breaking change for stored data: a value that was
19
+ * valid when written fails the next edit. Raise freely; lower deliberately.
20
+ *
21
+ * @module founder-schema/core/limits
22
+ */
23
+ export declare const DISPLAY_NAME_MAX = 80;
24
+ export declare const HEADLINE_MAX = 120;
25
+ export declare const ABOUT_MAX = 1000;
26
+ /** Skills a member may list. Discover filters on them. */
27
+ export declare const SKILLS_MAX = 8;
28
+ /** Goals a member must pick at onboarding. The list itself caps the maximum. */
29
+ export declare const GOALS_MIN = 1;
30
+ export declare const CITY_MAX = 80;
31
+ export declare const STATE_MAX = 80;
32
+ export declare const COUNTRY_MAX = 80;
33
+ export declare const VENTURE_NAME_MAX = 120;
34
+ export declare const VENTURE_TAGLINE_MAX = 160;
35
+ /** Team size including the founder. */
36
+ export declare const TEAM_SIZE_MIN = 1;
37
+ export declare const TEAM_SIZE_MAX = 100000;
38
+ /** Earliest founding year accepted. The ceiling is "this year", read per parse. */
39
+ export declare const FOUNDED_YEAR_MIN = 1900;
40
+ /**
41
+ * Longest URL a member may TYPE. Applied by `httpUrlSchema` itself, so every
42
+ * member URL carries it: profile links, a venture website.
43
+ *
44
+ * ⚠ NOT applied to URLs the server reads from elsewhere (a link preview's
45
+ * page or image), which use `safeUrlSchema`: a 314-character CDN image URL is
46
+ * ordinary, and one of them would otherwise fail a whole feed page.
47
+ */
48
+ export declare const LINK_URL_MAX = 300;
49
+ /** Author-supplied image description. The backend's stored copy uses this too. */
50
+ export declare const MEDIA_ALT_MAX = 280;
51
+ export declare const LINK_PREVIEW_TITLE_MAX = 200;
52
+ export declare const LINK_PREVIEW_DESCRIPTION_MAX = 500;
53
+ /** The free-text note on a report. */
54
+ export declare const REPORT_NOTE_MAX = 1000;
55
+ /** A moderator's note on a decision (why it was hidden, removed or dismissed). */
56
+ export declare const MODERATION_NOTE_MAX = 500;
57
+ /** What a post, poll or comment is shown as in a staff list: its first words. */
58
+ export declare const MODERATION_EXCERPT_MAX = 160;
59
+ /** Why a member was suspended. Shown to staff, and on record in the audit log. */
60
+ export declare const SUSPENSION_REASON_MIN = 3;
61
+ export declare const SUSPENSION_REASON_MAX = 500;
62
+ /** The staff member directory's search box (a name, a handle or an email). */
63
+ export declare const MEMBER_SEARCH_MAX = 120;
64
+ export declare const TICKET_SUBJECT_MIN = 3;
65
+ export declare const TICKET_SUBJECT_MAX = 120;
66
+ /** One message in a ticket, the member's or an agent's. */
67
+ export declare const TICKET_MESSAGE_MAX = 4000;
68
+ /**
69
+ * Messages one ticket may hold. A conversation that long is a new problem, so
70
+ * the member is asked to open a new ticket rather than the thread growing
71
+ * without bound.
72
+ */
73
+ export declare const TICKET_MESSAGES_MAX = 200;
74
+ export declare const SAVED_REPLY_TITLE_MAX = 80;
75
+ export declare const SAVED_REPLY_BODY_MAX = 4000;
76
+ /** Saved replies kept in all: a team's library, not an archive. */
77
+ export declare const SAVED_REPLIES_MAX = 200;
78
+ /**
79
+ * Largest file accepted for upload: 10 MB.
80
+ *
81
+ * Checked on BOTH sides and for different reasons. The server refuses a bigger
82
+ * object when it is attached. The client checks before uploading, because a
83
+ * presigned PUT caps the content type but not the size, so a member on a phone
84
+ * connection would otherwise watch a 40 MB upload finish and then fail.
85
+ */
86
+ export declare const UPLOAD_MAX_BYTES: number;
87
+ /** Values one Discover multi-select filter may carry (`?skill=a&skill=b`). */
88
+ export declare const DISCOVER_FILTER_VALUES_MAX = 10;
89
+ /** Discover's free-text inputs: the name search, city and country. */
90
+ export declare const DISCOVER_TEXT_MAX = 80;
91
+ /** Page size bounds for every cursor-paged list. */
92
+ export declare const PAGE_SIZE_MAX = 50;
93
+ export declare const PAGE_SIZE_DEFAULT = 20;
94
+ /**
95
+ * Members one participants lookup may name. Bounded so the lookup cannot be
96
+ * used to page through the directory.
97
+ */
98
+ export declare const CHAT_PARTICIPANTS_MAX = 100;
99
+ //# sourceMappingURL=limits.d.ts.map
@@ -0,0 +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;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,QAAmB,CAAC;AAIjD,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"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * founder-schema — Every length, size and count limit, by name
3
+ *
4
+ * ONE PLACE FOR EVERY NUMBER a client and the server must agree on. Before this,
5
+ * the limits lived inline (`.max(80)`, `.max(1000)`) where nothing else could
6
+ * read them, so the web client re-typed each one for its counters and the
7
+ * backend re-typed them as Mongoose `maxlength`. Three copies of one number is
8
+ * how the image alt text came to be 280 here and 500 in the backend.
9
+ *
10
+ * The rule from 0.2.0: a schema uses one of these constants, a consumer imports
11
+ * the same constant, and nobody writes the digits again.
12
+ *
13
+ * Imports nothing, so any module here can use it without a cycle. Limits that
14
+ * already had a name in their own module (POST_MAX_BODY_CHARS, POLL_*,
15
+ * USERNAME_*, INTERESTS_*, MIN_AGE_YEARS...) stay where they are and keep their
16
+ * names; this file holds the ones that had none.
17
+ *
18
+ * ⚠️ Lowering a limit is a breaking change for stored data: a value that was
19
+ * valid when written fails the next edit. Raise freely; lower deliberately.
20
+ *
21
+ * @module founder-schema/core/limits
22
+ */
23
+ // ─── Profile ─────────────────────────────────────────────────────────────────
24
+ export const DISPLAY_NAME_MAX = 80;
25
+ export const HEADLINE_MAX = 120;
26
+ export const ABOUT_MAX = 1000;
27
+ /** Skills a member may list. Discover filters on them. */
28
+ export const SKILLS_MAX = 8;
29
+ /** Goals a member must pick at onboarding. The list itself caps the maximum. */
30
+ export const GOALS_MIN = 1;
31
+ // ─── Location ────────────────────────────────────────────────────────────────
32
+ export const CITY_MAX = 80;
33
+ export const STATE_MAX = 80;
34
+ export const COUNTRY_MAX = 80;
35
+ // ─── Venture ─────────────────────────────────────────────────────────────────
36
+ export const VENTURE_NAME_MAX = 120;
37
+ export const VENTURE_TAGLINE_MAX = 160;
38
+ /** Team size including the founder. */
39
+ export const TEAM_SIZE_MIN = 1;
40
+ export const TEAM_SIZE_MAX = 100_000;
41
+ /** Earliest founding year accepted. The ceiling is "this year", read per parse. */
42
+ export const FOUNDED_YEAR_MIN = 1900;
43
+ // ─── Links ───────────────────────────────────────────────────────────────────
44
+ /**
45
+ * Longest URL a member may TYPE. Applied by `httpUrlSchema` itself, so every
46
+ * member URL carries it: profile links, a venture website.
47
+ *
48
+ * ⚠ NOT applied to URLs the server reads from elsewhere (a link preview's
49
+ * page or image), which use `safeUrlSchema`: a 314-character CDN image URL is
50
+ * ordinary, and one of them would otherwise fail a whole feed page.
51
+ */
52
+ export const LINK_URL_MAX = 300;
53
+ // ─── Feed and moderation ─────────────────────────────────────────────────────
54
+ /** Author-supplied image description. The backend's stored copy uses this too. */
55
+ export const MEDIA_ALT_MAX = 280;
56
+ export const LINK_PREVIEW_TITLE_MAX = 200;
57
+ export const LINK_PREVIEW_DESCRIPTION_MAX = 500;
58
+ /** The free-text note on a report. */
59
+ export const REPORT_NOTE_MAX = 1000;
60
+ /** A moderator's note on a decision (why it was hidden, removed or dismissed). */
61
+ export const MODERATION_NOTE_MAX = 500;
62
+ /** What a post, poll or comment is shown as in a staff list: its first words. */
63
+ export const MODERATION_EXCERPT_MAX = 160;
64
+ // ─── Staff ───────────────────────────────────────────────────────────────────
65
+ /** Why a member was suspended. Shown to staff, and on record in the audit log. */
66
+ export const SUSPENSION_REASON_MIN = 3;
67
+ export const SUSPENSION_REASON_MAX = 500;
68
+ /** The staff member directory's search box (a name, a handle or an email). */
69
+ export const MEMBER_SEARCH_MAX = 120;
70
+ // ─── Support ─────────────────────────────────────────────────────────────────
71
+ export const TICKET_SUBJECT_MIN = 3;
72
+ export const TICKET_SUBJECT_MAX = 120;
73
+ /** One message in a ticket, the member's or an agent's. */
74
+ export const TICKET_MESSAGE_MAX = 4000;
75
+ /**
76
+ * Messages one ticket may hold. A conversation that long is a new problem, so
77
+ * the member is asked to open a new ticket rather than the thread growing
78
+ * without bound.
79
+ */
80
+ export const TICKET_MESSAGES_MAX = 200;
81
+ export const SAVED_REPLY_TITLE_MAX = 80;
82
+ export const SAVED_REPLY_BODY_MAX = 4000;
83
+ /** Saved replies kept in all: a team's library, not an archive. */
84
+ export const SAVED_REPLIES_MAX = 200;
85
+ // ─── Uploads ─────────────────────────────────────────────────────────────────
86
+ /**
87
+ * Largest file accepted for upload: 10 MB.
88
+ *
89
+ * Checked on BOTH sides and for different reasons. The server refuses a bigger
90
+ * object when it is attached. The client checks before uploading, because a
91
+ * presigned PUT caps the content type but not the size, so a member on a phone
92
+ * connection would otherwise watch a 40 MB upload finish and then fail.
93
+ */
94
+ export const UPLOAD_MAX_BYTES = 10 * 1024 * 1024;
95
+ // ─── Lists and search ────────────────────────────────────────────────────────
96
+ /** Values one Discover multi-select filter may carry (`?skill=a&skill=b`). */
97
+ export const DISCOVER_FILTER_VALUES_MAX = 10;
98
+ /** Discover's free-text inputs: the name search, city and country. */
99
+ export const DISCOVER_TEXT_MAX = 80;
100
+ /** Page size bounds for every cursor-paged list. */
101
+ export const PAGE_SIZE_MAX = 50;
102
+ export const PAGE_SIZE_DEFAULT = 20;
103
+ // ─── Chat ────────────────────────────────────────────────────────────────────
104
+ /**
105
+ * Members one participants lookup may name. Bounded so the lookup cannot be
106
+ * used to page through the directory.
107
+ */
108
+ export const CHAT_PARTICIPANTS_MAX = 100;
109
+ //# sourceMappingURL=limits.js.map
@@ -0,0 +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"]}
@@ -0,0 +1,195 @@
1
+ /**
2
+ * founder-schema — API response envelope
3
+ *
4
+ * Every Founder Circle endpoint answers in this shape. The clients read the
5
+ * failure reason at `error.message`, so a handler must never return a bare
6
+ * string body.
7
+ *
8
+ * @module founder-schema/core/responses
9
+ */
10
+ import { z } from 'zod';
11
+ /**
12
+ * What an error carries beyond its code, each field named by the code that
13
+ * sends it. Declared here, not loosely typed, because a client parses errors
14
+ * with this schema and Zod drops a key the schema does not name.
15
+ *
16
+ * `issues` is EVERY problem of a 400, each with its path, built on the server
17
+ * by `toValidationIssues`. Until 0.2.0 this was typed as a record of strings
18
+ * that nothing ever sent, while the real 400s carried only the first issue's
19
+ * message, so a client could not tell which field to mark.
20
+ */
21
+ export declare const apiErrorDetailsSchema: z.ZodObject<{
22
+ issues: z.ZodOptional<z.ZodArray<z.ZodObject<{
23
+ location: z.ZodOptional<z.ZodEnum<{
24
+ params: "params";
25
+ body: "body";
26
+ query: "query";
27
+ }>>;
28
+ path: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
29
+ code: z.ZodString;
30
+ message: z.ZodString;
31
+ minimum: z.ZodOptional<z.ZodNumber>;
32
+ maximum: z.ZodOptional<z.ZodNumber>;
33
+ origin: z.ZodOptional<z.ZodString>;
34
+ format: z.ZodOptional<z.ZodString>;
35
+ }, z.core.$strip>>>;
36
+ attemptsLeft: z.ZodOptional<z.ZodNumber>;
37
+ lockedUntil: z.ZodOptional<z.ZodISODateTime>;
38
+ }, z.core.$strip>;
39
+ export type ApiErrorDetails = z.infer<typeof apiErrorDetailsSchema>;
40
+ /**
41
+ * `code` is one of `API_ERROR_CODE_VALUES`, and it is what a client acts on.
42
+ * `message` is English, for logs and for a client that predates the code.
43
+ */
44
+ export declare const apiErrorSchema: z.ZodObject<{
45
+ code: z.ZodEnum<{
46
+ INTERNAL_ERROR: "INTERNAL_ERROR";
47
+ VALIDATION_ERROR: "VALIDATION_ERROR";
48
+ NOT_FOUND: "NOT_FOUND";
49
+ CONFLICT: "CONFLICT";
50
+ RATE_LIMITED: "RATE_LIMITED";
51
+ SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE";
52
+ UNAUTHORIZED: "UNAUTHORIZED";
53
+ INVALID_TOKEN: "INVALID_TOKEN";
54
+ TOKEN_EXPIRED: "TOKEN_EXPIRED";
55
+ SESSION_INVALIDATED: "SESSION_INVALIDATED";
56
+ FORBIDDEN: "FORBIDDEN";
57
+ ONBOARDING_INCOMPLETE: "ONBOARDING_INCOMPLETE";
58
+ ALREADY_ONBOARDED: "ALREADY_ONBOARDED";
59
+ UNDER_AGE: "UNDER_AGE";
60
+ UPLOAD_NOT_ATTACHABLE: "UPLOAD_NOT_ATTACHABLE";
61
+ UPLOAD_TOO_LARGE: "UPLOAD_TOO_LARGE";
62
+ UPLOAD_WRONG_TYPE: "UPLOAD_WRONG_TYPE";
63
+ EDIT_WINDOW_CLOSED: "EDIT_WINDOW_CLOSED";
64
+ POLL_NOT_EDITABLE: "POLL_NOT_EDITABLE";
65
+ POLL_CLOSED: "POLL_CLOSED";
66
+ POLL_CHOICE_INVALID: "POLL_CHOICE_INVALID";
67
+ REPLY_TOO_DEEP: "REPLY_TOO_DEEP";
68
+ CANNOT_REPORT_OWN: "CANNOT_REPORT_OWN";
69
+ CHAT_WITH_SELF: "CHAT_WITH_SELF";
70
+ ACCOUNT_SUSPENDED: "ACCOUNT_SUSPENDED";
71
+ ROLE_NOT_HELD: "ROLE_NOT_HELD";
72
+ MPIN_NOT_SET: "MPIN_NOT_SET";
73
+ MPIN_ALREADY_SET: "MPIN_ALREADY_SET";
74
+ MPIN_INCORRECT: "MPIN_INCORRECT";
75
+ MPIN_LOCKED: "MPIN_LOCKED";
76
+ CANNOT_CHANGE_OWN_ROLES: "CANNOT_CHANGE_OWN_ROLES";
77
+ ROLE_NOT_GRANTABLE: "ROLE_NOT_GRANTABLE";
78
+ TICKET_FULL: "TICKET_FULL";
79
+ SAVED_REPLIES_FULL: "SAVED_REPLIES_FULL";
80
+ }>;
81
+ message: z.ZodString;
82
+ details: z.ZodOptional<z.ZodObject<{
83
+ issues: z.ZodOptional<z.ZodArray<z.ZodObject<{
84
+ location: z.ZodOptional<z.ZodEnum<{
85
+ params: "params";
86
+ body: "body";
87
+ query: "query";
88
+ }>>;
89
+ path: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
90
+ code: z.ZodString;
91
+ message: z.ZodString;
92
+ minimum: z.ZodOptional<z.ZodNumber>;
93
+ maximum: z.ZodOptional<z.ZodNumber>;
94
+ origin: z.ZodOptional<z.ZodString>;
95
+ format: z.ZodOptional<z.ZodString>;
96
+ }, z.core.$strip>>>;
97
+ attemptsLeft: z.ZodOptional<z.ZodNumber>;
98
+ lockedUntil: z.ZodOptional<z.ZodISODateTime>;
99
+ }, z.core.$strip>>;
100
+ }, z.core.$strip>;
101
+ export type ApiError = z.infer<typeof apiErrorSchema>;
102
+ /** Success envelope. `T` is the payload schema. */
103
+ export declare const successResponseSchema: <T extends z.ZodType>(data: T) => z.ZodObject<{
104
+ success: z.ZodLiteral<true>;
105
+ data: T;
106
+ message: z.ZodOptional<z.ZodString>;
107
+ }, z.core.$strip>;
108
+ export declare const errorResponseSchema: z.ZodObject<{
109
+ success: z.ZodLiteral<false>;
110
+ error: z.ZodObject<{
111
+ code: z.ZodEnum<{
112
+ INTERNAL_ERROR: "INTERNAL_ERROR";
113
+ VALIDATION_ERROR: "VALIDATION_ERROR";
114
+ NOT_FOUND: "NOT_FOUND";
115
+ CONFLICT: "CONFLICT";
116
+ RATE_LIMITED: "RATE_LIMITED";
117
+ SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE";
118
+ UNAUTHORIZED: "UNAUTHORIZED";
119
+ INVALID_TOKEN: "INVALID_TOKEN";
120
+ TOKEN_EXPIRED: "TOKEN_EXPIRED";
121
+ SESSION_INVALIDATED: "SESSION_INVALIDATED";
122
+ FORBIDDEN: "FORBIDDEN";
123
+ ONBOARDING_INCOMPLETE: "ONBOARDING_INCOMPLETE";
124
+ ALREADY_ONBOARDED: "ALREADY_ONBOARDED";
125
+ UNDER_AGE: "UNDER_AGE";
126
+ UPLOAD_NOT_ATTACHABLE: "UPLOAD_NOT_ATTACHABLE";
127
+ UPLOAD_TOO_LARGE: "UPLOAD_TOO_LARGE";
128
+ UPLOAD_WRONG_TYPE: "UPLOAD_WRONG_TYPE";
129
+ EDIT_WINDOW_CLOSED: "EDIT_WINDOW_CLOSED";
130
+ POLL_NOT_EDITABLE: "POLL_NOT_EDITABLE";
131
+ POLL_CLOSED: "POLL_CLOSED";
132
+ POLL_CHOICE_INVALID: "POLL_CHOICE_INVALID";
133
+ REPLY_TOO_DEEP: "REPLY_TOO_DEEP";
134
+ CANNOT_REPORT_OWN: "CANNOT_REPORT_OWN";
135
+ CHAT_WITH_SELF: "CHAT_WITH_SELF";
136
+ ACCOUNT_SUSPENDED: "ACCOUNT_SUSPENDED";
137
+ ROLE_NOT_HELD: "ROLE_NOT_HELD";
138
+ MPIN_NOT_SET: "MPIN_NOT_SET";
139
+ MPIN_ALREADY_SET: "MPIN_ALREADY_SET";
140
+ MPIN_INCORRECT: "MPIN_INCORRECT";
141
+ MPIN_LOCKED: "MPIN_LOCKED";
142
+ CANNOT_CHANGE_OWN_ROLES: "CANNOT_CHANGE_OWN_ROLES";
143
+ ROLE_NOT_GRANTABLE: "ROLE_NOT_GRANTABLE";
144
+ TICKET_FULL: "TICKET_FULL";
145
+ SAVED_REPLIES_FULL: "SAVED_REPLIES_FULL";
146
+ }>;
147
+ message: z.ZodString;
148
+ details: z.ZodOptional<z.ZodObject<{
149
+ issues: z.ZodOptional<z.ZodArray<z.ZodObject<{
150
+ location: z.ZodOptional<z.ZodEnum<{
151
+ params: "params";
152
+ body: "body";
153
+ query: "query";
154
+ }>>;
155
+ path: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
156
+ code: z.ZodString;
157
+ message: z.ZodString;
158
+ minimum: z.ZodOptional<z.ZodNumber>;
159
+ maximum: z.ZodOptional<z.ZodNumber>;
160
+ origin: z.ZodOptional<z.ZodString>;
161
+ format: z.ZodOptional<z.ZodString>;
162
+ }, z.core.$strip>>>;
163
+ attemptsLeft: z.ZodOptional<z.ZodNumber>;
164
+ lockedUntil: z.ZodOptional<z.ZodISODateTime>;
165
+ }, z.core.$strip>>;
166
+ }, z.core.$strip>;
167
+ }, z.core.$strip>;
168
+ export type ErrorResponse = z.infer<typeof errorResponseSchema>;
169
+ /**
170
+ * Cursor-paginated list envelope.
171
+ *
172
+ * `nextCursor` is REQUIRED and is `null` at the end of the list. Omitting the
173
+ * key is not the same thing and does not validate: a handler must decide
174
+ * whether there is another page and say so, because "the key is missing" and
175
+ * "there are no more results" are indistinguishable to a client that has to
176
+ * choose between showing a Load more button and not showing one.
177
+ *
178
+ * An earlier version of this comment said absence and null were equivalent.
179
+ * They never were; the schema has always required the key. Corrected
180
+ * 2026-09-21, and pinned by a test.
181
+ */
182
+ export declare const paginatedSchema: <T extends z.ZodType>(item: T) => z.ZodObject<{
183
+ items: z.ZodArray<T>;
184
+ nextCursor: z.ZodNullable<z.ZodString>;
185
+ }, z.core.$strip>;
186
+ /**
187
+ * What a create, an edit or a delete answers: the id of the thing it touched.
188
+ * A client that needs the whole item reads it, so a write never ships a second
189
+ * projection of it that could drift from the read path's.
190
+ */
191
+ export declare const idResponseSchema: z.ZodObject<{
192
+ id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
193
+ }, z.core.$strip>;
194
+ export type IdResponse = z.infer<typeof idResponseSchema>;
195
+ //# sourceMappingURL=responses.d.ts.map
@@ -0,0 +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;;;;;;;;;;;;;;;;;iBAMhC,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"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * founder-schema — API response envelope
3
+ *
4
+ * Every Founder Circle endpoint answers in this shape. The clients read the
5
+ * failure reason at `error.message`, so a handler must never return a bare
6
+ * string body.
7
+ *
8
+ * @module founder-schema/core/responses
9
+ */
10
+ import { z } from 'zod';
11
+ import { objectIdSchema } from './common.js';
12
+ import { apiErrorCodeSchema } from './errors.js';
13
+ import { validationIssueSchema } from './validation.js';
14
+ /**
15
+ * What an error carries beyond its code, each field named by the code that
16
+ * sends it. Declared here, not loosely typed, because a client parses errors
17
+ * with this schema and Zod drops a key the schema does not name.
18
+ *
19
+ * `issues` is EVERY problem of a 400, each with its path, built on the server
20
+ * by `toValidationIssues`. Until 0.2.0 this was typed as a record of strings
21
+ * that nothing ever sent, while the real 400s carried only the first issue's
22
+ * message, so a client could not tell which field to mark.
23
+ */
24
+ export const apiErrorDetailsSchema = z.object({
25
+ issues: z.array(validationIssueSchema).optional(),
26
+ /** `MPIN_INCORRECT`: wrong tries left before the MPIN locks. */
27
+ attemptsLeft: z.number().int().min(0).optional(),
28
+ /** `MPIN_LOCKED`: when the MPIN can be tried again. */
29
+ lockedUntil: z.iso.datetime().optional(),
30
+ });
31
+ /**
32
+ * `code` is one of `API_ERROR_CODE_VALUES`, and it is what a client acts on.
33
+ * `message` is English, for logs and for a client that predates the code.
34
+ */
35
+ export const apiErrorSchema = z.object({
36
+ code: apiErrorCodeSchema,
37
+ message: z.string(),
38
+ details: apiErrorDetailsSchema.optional(),
39
+ });
40
+ /** Success envelope. `T` is the payload schema. */
41
+ export const successResponseSchema = (data) => z.object({
42
+ success: z.literal(true),
43
+ data,
44
+ message: z.string().optional(),
45
+ });
46
+ export const errorResponseSchema = z.object({
47
+ success: z.literal(false),
48
+ error: apiErrorSchema,
49
+ });
50
+ /**
51
+ * Cursor-paginated list envelope.
52
+ *
53
+ * `nextCursor` is REQUIRED and is `null` at the end of the list. Omitting the
54
+ * key is not the same thing and does not validate: a handler must decide
55
+ * whether there is another page and say so, because "the key is missing" and
56
+ * "there are no more results" are indistinguishable to a client that has to
57
+ * choose between showing a Load more button and not showing one.
58
+ *
59
+ * An earlier version of this comment said absence and null were equivalent.
60
+ * They never were; the schema has always required the key. Corrected
61
+ * 2026-09-21, and pinned by a test.
62
+ */
63
+ export const paginatedSchema = (item) => z.object({
64
+ items: z.array(item),
65
+ nextCursor: z.string().nullable(),
66
+ });
67
+ /**
68
+ * What a create, an edit or a delete answers: the id of the thing it touched.
69
+ * A client that needs the whole item reads it, so a write never ships a second
70
+ * projection of it that could drift from the read path's.
71
+ */
72
+ export const idResponseSchema = z.object({ id: objectIdSchema });
73
+ //# sourceMappingURL=responses.js.map
@@ -0,0 +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;AAC7C,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;CACzC,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 } 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});\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"]}