founder-schema 0.18.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -3
- package/dist/access/permissions.d.ts +3 -3
- package/dist/access/permissions.d.ts.map +1 -1
- package/dist/access/permissions.js.map +1 -1
- package/dist/core/common.d.ts +49 -28
- package/dist/core/common.d.ts.map +1 -1
- package/dist/core/common.js +65 -26
- package/dist/core/common.js.map +1 -1
- package/dist/core/deletion.d.ts +40 -0
- package/dist/core/deletion.d.ts.map +1 -0
- package/dist/core/deletion.js +39 -0
- package/dist/core/deletion.js.map +1 -0
- package/dist/core/limits.d.ts +25 -0
- package/dist/core/limits.d.ts.map +1 -1
- package/dist/core/limits.js +25 -0
- package/dist/core/limits.js.map +1 -1
- package/dist/core/validation.d.ts +13 -0
- package/dist/core/validation.d.ts.map +1 -1
- package/dist/core/validation.js +13 -0
- package/dist/core/validation.js.map +1 -1
- package/dist/domains/audit/audit.d.ts +36 -12
- package/dist/domains/audit/audit.d.ts.map +1 -1
- package/dist/domains/audit/audit.js +9 -10
- package/dist/domains/audit/audit.js.map +1 -1
- package/dist/domains/billing/billing.d.ts +29 -13
- package/dist/domains/billing/billing.d.ts.map +1 -1
- package/dist/domains/billing/billing.js +2 -3
- package/dist/domains/billing/billing.js.map +1 -1
- package/dist/domains/chat/chat.d.ts +19 -5
- package/dist/domains/chat/chat.d.ts.map +1 -1
- package/dist/domains/chat/chat.js +10 -10
- package/dist/domains/chat/chat.js.map +1 -1
- package/dist/domains/communities/communities.d.ts +95 -57
- package/dist/domains/communities/communities.d.ts.map +1 -1
- package/dist/domains/contributions/contributions.d.ts +28 -12
- package/dist/domains/contributions/contributions.d.ts.map +1 -1
- package/dist/domains/contributions/contributions.js +3 -0
- package/dist/domains/contributions/contributions.js.map +1 -1
- package/dist/domains/discover/discover.d.ts +2 -2
- package/dist/domains/feed/feed.d.ts +111 -63
- package/dist/domains/feed/feed.d.ts.map +1 -1
- package/dist/domains/feed/feed.js +3 -0
- package/dist/domains/feed/feed.js.map +1 -1
- package/dist/domains/location/areas.d.ts +338 -0
- package/dist/domains/location/areas.d.ts.map +1 -0
- package/dist/domains/location/areas.js +241 -0
- package/dist/domains/location/areas.js.map +1 -0
- package/dist/domains/location/india.d.ts +31 -123
- package/dist/domains/location/india.d.ts.map +1 -1
- package/dist/domains/location/india.js +98 -64
- package/dist/domains/location/india.js.map +1 -1
- package/dist/domains/media/media.d.ts +22 -0
- package/dist/domains/media/media.d.ts.map +1 -1
- package/dist/domains/media/media.js +20 -0
- package/dist/domains/media/media.js.map +1 -1
- package/dist/domains/members/members.d.ts +26 -9
- package/dist/domains/members/members.d.ts.map +1 -1
- package/dist/domains/members/members.js +9 -10
- package/dist/domains/members/members.js.map +1 -1
- package/dist/domains/membership/membership.d.ts +19 -0
- package/dist/domains/membership/membership.d.ts.map +1 -1
- package/dist/domains/membership/membership.js +10 -0
- package/dist/domains/membership/membership.js.map +1 -1
- package/dist/domains/moderation/moderation.d.ts +8 -2
- package/dist/domains/moderation/moderation.d.ts.map +1 -1
- package/dist/domains/moderation/moderation.js +3 -6
- package/dist/domains/moderation/moderation.js.map +1 -1
- package/dist/domains/notifications/notifications.d.ts +10 -6
- package/dist/domains/notifications/notifications.d.ts.map +1 -1
- package/dist/domains/onboarding/onboarding-answers.d.ts +9 -12
- package/dist/domains/onboarding/onboarding-answers.d.ts.map +1 -1
- package/dist/domains/onboarding/onboarding-answers.js +12 -19
- package/dist/domains/onboarding/onboarding-answers.js.map +1 -1
- package/dist/domains/onboarding/onboarding.d.ts +121 -98
- package/dist/domains/onboarding/onboarding.d.ts.map +1 -1
- package/dist/domains/onboarding/onboarding.js +30 -28
- package/dist/domains/onboarding/onboarding.js.map +1 -1
- package/dist/domains/people/person.d.ts +54 -0
- package/dist/domains/people/person.d.ts.map +1 -0
- package/dist/domains/people/person.js +73 -0
- package/dist/domains/people/person.js.map +1 -0
- package/dist/domains/products/products.d.ts +62 -33
- package/dist/domains/products/products.d.ts.map +1 -1
- package/dist/domains/products/products.js +3 -0
- package/dist/domains/products/products.js.map +1 -1
- package/dist/domains/profile/founder-profile.d.ts +93 -60
- package/dist/domains/profile/founder-profile.d.ts.map +1 -1
- package/dist/domains/profile/founder-profile.js +55 -25
- package/dist/domains/profile/founder-profile.js.map +1 -1
- package/dist/domains/reviews/reviews.d.ts +25 -15
- package/dist/domains/reviews/reviews.d.ts.map +1 -1
- package/dist/domains/support/support.d.ts +47 -10
- package/dist/domains/support/support.d.ts.map +1 -1
- package/dist/domains/support/support.js +4 -2
- package/dist/domains/support/support.js.map +1 -1
- package/dist/domains/team/team.d.ts +9 -3
- package/dist/domains/team/team.d.ts.map +1 -1
- package/dist/domains/team/team.js +3 -6
- package/dist/domains/team/team.js.map +1 -1
- package/dist/index.d.ts +13 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -11
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/dist/account.d.ts +0 -40
- package/dist/account.d.ts.map +0 -1
- package/dist/account.js +0 -39
- package/dist/account.js.map +0 -1
- package/dist/chat.d.ts +0 -187
- package/dist/chat.d.ts.map +0 -1
- package/dist/chat.js +0 -189
- package/dist/chat.js.map +0 -1
- package/dist/common.d.ts +0 -304
- package/dist/common.d.ts.map +0 -1
- package/dist/common.js +0 -340
- package/dist/common.js.map +0 -1
- package/dist/discover.d.ts +0 -245
- package/dist/discover.d.ts.map +0 -1
- package/dist/discover.js +0 -68
- package/dist/discover.js.map +0 -1
- package/dist/errors.d.ts +0 -85
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -89
- package/dist/errors.js.map +0 -1
- package/dist/feed.d.ts +0 -1658
- package/dist/feed.d.ts.map +0 -1
- package/dist/feed.js +0 -623
- package/dist/feed.js.map +0 -1
- package/dist/founder-profile.d.ts +0 -387
- package/dist/founder-profile.d.ts.map +0 -1
- package/dist/founder-profile.js +0 -180
- package/dist/founder-profile.js.map +0 -1
- package/dist/limits.d.ts +0 -76
- package/dist/limits.d.ts.map +0 -1
- package/dist/limits.js +0 -84
- package/dist/limits.js.map +0 -1
- package/dist/onboarding.d.ts +0 -1018
- package/dist/onboarding.d.ts.map +0 -1
- package/dist/onboarding.js +0 -717
- package/dist/onboarding.js.map +0 -1
- package/dist/responses/common.d.ts +0 -167
- package/dist/responses/common.d.ts.map +0 -1
- package/dist/responses/common.js +0 -67
- package/dist/responses/common.js.map +0 -1
- package/dist/responses/index.d.ts +0 -7
- package/dist/responses/index.d.ts.map +0 -1
- package/dist/responses/index.js +0 -7
- package/dist/responses/index.js.map +0 -1
- package/dist/uploads.d.ts +0 -113
- package/dist/uploads.d.ts.map +0 -1
- package/dist/uploads.js +0 -106
- package/dist/uploads.js.map +0 -1
- package/dist/validation.d.ts +0 -136
- package/dist/validation.d.ts.map +0 -1
- package/dist/validation.js +0 -142
- package/dist/validation.js.map +0 -1
package/README.md
CHANGED
|
@@ -95,7 +95,7 @@ SAME schema object, so the two can never disagree about what is allowed.
|
|
|
95
95
|
- **Bodies are typed JSON; only queries coerce.** `teamSize: "5"` is refused in a
|
|
96
96
|
body, and `?limit=5` is read as 5.
|
|
97
97
|
- **Shapes are derived, never copied.** One `locationSchema` to READ a place and
|
|
98
|
-
one `
|
|
98
|
+
one `locationPickSchema` to WRITE one (area ids, 0.19.0), one `founderLinksSchema`, one
|
|
99
99
|
`ventureSchema`, one `mediaImageSchema`; the other places pick, omit or reuse
|
|
100
100
|
them, and a test checks by object identity.
|
|
101
101
|
|
|
@@ -104,6 +104,109 @@ instance; this package must not bring its own.
|
|
|
104
104
|
|
|
105
105
|
---
|
|
106
106
|
|
|
107
|
+
## Breaking in 0.20.0: one person everywhere, names as first + last, soft delete (2026-10-22)
|
|
108
|
+
|
|
109
|
+
The data model redesign the user approved on 2026-10-22 (`founder-circle/DATA-MODEL-PLAN-2026-10-22.md`).
|
|
110
|
+
|
|
111
|
+
**One person on every surface: `personSchema`.**
|
|
112
|
+
- Shape: `{ userId, username | null, displayName, firstName, avatarUrl | null, former }`.
|
|
113
|
+
- It replaces seven variants:
|
|
114
|
+
- `memberBylineSchema` is now the person plus a `headline`;
|
|
115
|
+
- `memberSummarySchema`, `teamMemberSchema`, `chatParticipantSchema`, the moderation `author`, every
|
|
116
|
+
support person, `payerSchema`, the audit actor and `suspension.by` are all built on it.
|
|
117
|
+
- Where these had `id`, they now have `userId`.
|
|
118
|
+
- **Former members.** A deleted account is `former: true`, with no name, handle or picture (`formerPerson`).
|
|
119
|
+
Clients word it ("Former member") and show a neutral icon. A nullable person still means "nobody" (the
|
|
120
|
+
system did it).
|
|
121
|
+
- `initialsOf` gives the avatar fallback: two letters (first name and last word), empty for a former
|
|
122
|
+
member.
|
|
123
|
+
- A chat participant is answered for every id asked; a deleted account comes back as a former person.
|
|
124
|
+
|
|
125
|
+
**The name.** The user's ruling: one "Full name" box, the display name derived from first + last.
|
|
126
|
+
- `displayNameSchema` is gone. What a member types is `fullNameSchema`, under the key `fullName`:
|
|
127
|
+
- in the onboarding form, draft and finish body (the basics step);
|
|
128
|
+
- in `PATCH /profiles/me/personal`.
|
|
129
|
+
- The server splits it at the first space (`splitFullName`) and stores `firstName` and `lastName`. The
|
|
130
|
+
display name is them joined (`joinFullName`), which is the tidied name as typed.
|
|
131
|
+
- A sign-in name is seeded the same way (`nameFromSignIn`), cut to `DISPLAY_NAME_MAX` without splitting a
|
|
132
|
+
character (`graphemesOf`).
|
|
133
|
+
- `readOnboardingDraft` still reads a draft saved with the old `displayName` key.
|
|
134
|
+
- `founderProfileSchema` (owner) and `myPersonalDetailsSchema` carry `firstName` and `lastName`.
|
|
135
|
+
|
|
136
|
+
**The photo.** `founderProfileSchema` carries `useProviderPhoto` and `hasProviderPhoto`. `PATCH
|
|
137
|
+
/profiles/me/photo-settings` (`updatePhotoSettingsBodySchema`) turns the sign-in provider's picture off or
|
|
138
|
+
on. Off, a member with no upload shows their initials everywhere.
|
|
139
|
+
|
|
140
|
+
**Removed from `founderProfileSchema`:** `isOnboarded` (a profile exists only once onboarding is finished)
|
|
141
|
+
and `isActive` (replaced by soft delete).
|
|
142
|
+
|
|
143
|
+
**Soft delete.** `DELETED_REASON_VALUES` / `deletedReasonSchema` name why a row was deleted. The backend
|
|
144
|
+
stamps them beside `deletedAt`, and one purge removes rows `ERASURE_RETENTION_DAYS` after.
|
|
145
|
+
|
|
146
|
+
**Release:** publish, pin EXACTLY in both consumers, deploy backend and web together, then run the dev
|
|
147
|
+
migrations in the plan's order.
|
|
148
|
+
|
|
149
|
+
## Breaking in 0.19.0: location is Jansathi's model (2026-10-22)
|
|
150
|
+
|
|
151
|
+
The user, 2026-10-21/22: Jansathi's location drawer (state, then district, then
|
|
152
|
+
locality, from `master_db.areas`) and its whole location model ("just copy
|
|
153
|
+
paste things from jansathi"). Plan, Jansathi analysis and rulings:
|
|
154
|
+
`founder-circle/LOCATION-PLAN-2026-10-21.md`. Contract:
|
|
155
|
+
`src/domains/location/areas.ts`.
|
|
156
|
+
|
|
157
|
+
**Removed** (listed in `public-surface.test.ts`, `REMOVED_IN_0_19`):
|
|
158
|
+
`indiaLocationSchema` (a typed city and state is no longer a write),
|
|
159
|
+
`suggestedLocationSchema` and `coordinatesQuerySchema` (`GET
|
|
160
|
+
/location/areas/reverse` is gone; GPS goes to `POST /me/geolocation`).
|
|
161
|
+
|
|
162
|
+
**Changed on the wire:**
|
|
163
|
+
- A location is WRITTEN as area ids, `locationPickSchema`
|
|
164
|
+
`{ stateId, districtId, localityId | null }` (`null`: "my locality is not
|
|
165
|
+
listed", the whole district): onboarding's finish and
|
|
166
|
+
`PATCH /profiles/me/personal`. The server names the place, the public city
|
|
167
|
+
(`publicCityOf`: the town the member is in, else their district, cleaned by
|
|
168
|
+
`townName` and spelled as the state's list spells it; Discover's city filter
|
|
169
|
+
is unchanged) and its coordinates from `master_db`. Refusals:
|
|
170
|
+
`stateRequired`, `districtRequired`, `localityRequired`, `areaNotListed`,
|
|
171
|
+
`stateUnknown`, each on its id.
|
|
172
|
+
- The onboarding draft's `location` is a `Place` (names and ids). A draft saved
|
|
173
|
+
before 0.19.0 holds a typed place, which names no area: it reads as not
|
|
174
|
+
answered.
|
|
175
|
+
- The owner's profile (`GET /profiles/me`) carries `place`; no card, member page
|
|
176
|
+
or staff view does. Other members still read `location` (city, state,
|
|
177
|
+
country), unchanged.
|
|
178
|
+
- New: `GET /location/areas/states|districts|localities` (`areaListSchema`,
|
|
179
|
+
`areaPageSchema`, `LOCALITY_PAGE_SIZE`), and `POST /me/geolocation`
|
|
180
|
+
(`geolocationFixSchema` -> `geolocationSyncResultSchema` `{ synced, match }`).
|
|
181
|
+
The GPS axis (fix, area, the last `LOCATION_HISTORY_MAX` of each) is kept as
|
|
182
|
+
Jansathi keeps it but NEVER becomes the declared location (the user, "No,
|
|
183
|
+
member saves") and is never shown to another member. History is kept at most
|
|
184
|
+
`LOCATION_HISTORY_MAX_DAYS` (365) days.
|
|
185
|
+
- The member's own control (the user, 2026-10-22, "Add stop + clear"):
|
|
186
|
+
`GET`/`PATCH /me/location-settings` (`locationSettingsSchema`,
|
|
187
|
+
`updateLocationSettingsBodySchema`; `dailyLocation` OFF keeps no position)
|
|
188
|
+
and `DELETE /me/location-history` (`clearLocationHistoryResultSchema`; the
|
|
189
|
+
chosen place stays).
|
|
190
|
+
- `queryInt(min, max, fallback)` is the query integer helper (core/common.ts).
|
|
191
|
+
- `areaOptionSchema.within` (a ward's town, a village's tehsil or block) tells
|
|
192
|
+
same-named places apart; `areaWords` reads a numbered ward as "Ward 25";
|
|
193
|
+
`placeWords` includes `within`. `AREA_NAME_MAX` is 300: the all-India tree has
|
|
194
|
+
names up to 250 characters.
|
|
195
|
+
- The public city is `publicCityOf`; `townName` cleans a council's name. Over
|
|
196
|
+
all of India (4,995 public cities, 2026-10-22) none is rewritten by
|
|
197
|
+
Discover's state-blind `canonicalCityName` (Bijapur is listed for
|
|
198
|
+
Chhattisgarh too), none keeps council words or brackets, the longest is 35.
|
|
199
|
+
A twin town is one hyphen however the tree spaced it (Murwara-Katni), the
|
|
200
|
+
list matches hyphen and space alike (the tree's "Pimpri Chinchwad" is
|
|
201
|
+
Pimpri-Chinchwad), and a name in capitals reads title-case (MAUGANJ is
|
|
202
|
+
Mauganj): over every town and district (5,745), no city is written two ways
|
|
203
|
+
in one state. It always fits `CITY_MAX` (a too-long town is shown as its
|
|
204
|
+
district, a too-long district is cut at a word), so a save never fails on it.
|
|
205
|
+
|
|
206
|
+
**Release:** publish, pin EXACTLY in both consumers, backend before web (a 0.18
|
|
207
|
+
web sends a typed place, which a 0.19 backend refuses). The drawer needs real
|
|
208
|
+
data in `master_db.areas`: AWS dev's was synthetic until the all-India load.
|
|
209
|
+
|
|
107
210
|
## Breaking in 0.18.0: the SSOT audit (2026-10-20)
|
|
108
211
|
|
|
109
212
|
The user, 2026-10-20: "make sure it is the SSOT for data types, validation,
|
|
@@ -757,6 +860,10 @@ Two things that follow from it:
|
|
|
757
860
|
- Removing or renaming an export is the only change here that can break a consumer
|
|
758
861
|
that already compiles. `tests/public-surface.test.ts` fails on any removal, which
|
|
759
862
|
is the cheap version of the publish-time no-loss check.
|
|
863
|
+
- `npm run build` empties `dist` first (0.20.0, `npm run clean`). Up to 0.19.0 the
|
|
864
|
+
publishing checkout kept files from an old layout (`dist/account.js`,
|
|
865
|
+
`dist/common.js` and others), and they were published with every release.
|
|
866
|
+
Nothing imported them, but a package must hold exactly what its source builds.
|
|
760
867
|
|
|
761
868
|
---
|
|
762
869
|
|
|
@@ -836,8 +943,8 @@ member actually changed.
|
|
|
836
943
|
|
|
837
944
|
**A default inside a nested object is the same trap one level down.** A write
|
|
838
945
|
body never fills a nested field in: `locationSchema` has no default country
|
|
839
|
-
since it was removed, and a place is WRITTEN through `
|
|
840
|
-
whole.
|
|
946
|
+
since it was removed, and a place is WRITTEN through `locationPickSchema` (area
|
|
947
|
+
ids, 0.19.0), whole: a locality's id, or `null` for the whole district.
|
|
841
948
|
|
|
842
949
|
**A patch field the member can clear has three states, not two.** Absent means
|
|
843
950
|
do not touch. `null` means clear, and the backend maps it to `$unset`. An empty
|
|
@@ -110,8 +110,8 @@ export type Subject = z.infer<typeof subjectSchema>;
|
|
|
110
110
|
export interface SubjectFields {
|
|
111
111
|
Dashboard: Record<never, never>;
|
|
112
112
|
/**
|
|
113
|
-
* `
|
|
114
|
-
* suspend a superadmin").
|
|
113
|
+
* `roles` is the staff roles held (0.20.0: from their access grant; `appRoles`
|
|
114
|
+
* until 0.19.0), so a rule can protect a role ("never suspend a superadmin").
|
|
115
115
|
*
|
|
116
116
|
* For `message` (0.12.0), what the asker's relation to them is:
|
|
117
117
|
* `contact` says the asker knows them (a teammate, a private community in
|
|
@@ -124,7 +124,7 @@ export interface SubjectFields {
|
|
|
124
124
|
*/
|
|
125
125
|
Member: {
|
|
126
126
|
_id: string;
|
|
127
|
-
|
|
127
|
+
roles: PlatformRole[];
|
|
128
128
|
contact?: boolean;
|
|
129
129
|
standing?: Standing;
|
|
130
130
|
acceptsStrangers?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../src/access/permissions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC5E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAC/E,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,uCAAuC,CAAC;AACvF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,aAAa,mLAkBhB,CAAC;AACX,MAAM,MAAM,MAAM,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,cAAc,6QAqBjB,CAAC;AACX,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;EAAyB,CAAC;AACpD,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEpD;;;;;;;;;GASG;AACH,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAChC;;;;;;;;;;;;OAYG;IACH,MAAM,EAAE;QACN,GAAG,EAAE,MAAM,CAAC;QACZ,
|
|
1
|
+
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../src/access/permissions.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../src/access/permissions.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema: what a role may do, in CASL's terms\n *\n * Permissions are ACTION on SUBJECT, optionally narrowed by conditions on the\n * subject's fields (\"update a Post whose authorId is mine\"). The backend writes\n * one small policy per role from these words; the web receives the resulting\n * rules and asks the same questions to decide what to draw. Because both sides\n * spell actions and subjects from this file, a permission check with a typo is\n * a compile error, not a button that silently never shows.\n *\n * The SERVER decides; the web only mirrors. Every rule the web holds came from\n * the server for the role being acted as, and every request is checked again\n * on the server, so a tampered client can show itself a button and nothing\n * more.\n *\n * @module founder-schema/access/permissions\n */\n\nimport type { ForcedSubject, MongoAbility, RawRuleOf } from '@casl/ability';\nimport { z } from 'zod';\nimport type { OrderStatus, PurchaseKind } from '../domains/billing/billing.js';\nimport type { CommunitySuggestionStatus } from '../domains/communities/communities.js';\nimport type { ReportStatus } from '../domains/feed/feed.js';\nimport type { PlatformRole } from './roles.js';\nimport type { Standing } from './standing.js';\n\n/**\n * The verbs.\n *\n * manage every action (CASL's wildcard). Only superadmin holds it on `all`.\n * create, read, update, delete\n * moderate hide, restore or remove someone else's content, act on a report\n * assign grant or revoke (a platform role); take or hand over (a ticket)\n * suspend stop a member signing in to the product, and lift it\n * resolve close a support ticket or a report\n * reset clear a credential its owner forgot (a staff member's MPIN)\n * represent act for a product as one of its team: tag a post with it, reply\n * to its reviews, mark feedback helpful, confirm a tester\n *\n * Taking part (0.12.0, membership): the verbs a viewer is refused and a member\n * holds. Creating content stays `create`; these are the rest.\n *\n * react a reaction on a post, poll or comment; an upvote on a product;\n * marking a review helpful\n * vote vote in a poll\n * save bookmark a post or poll\n * join join a community, ask to, or accept its invitation; accept an\n * invitation to a product's team\n * message start a conversation with a Member\n * refund give a payment's money back (staff)\n *\n * Append only: a stored or packed rule names these.\n */\nexport const ACTION_VALUES = [\n 'manage',\n 'create',\n 'read',\n 'update',\n 'delete',\n 'moderate',\n 'assign',\n 'suspend',\n 'resolve',\n 'reset',\n 'represent',\n 'react',\n 'vote',\n 'save',\n 'join',\n 'message',\n 'refund',\n] as const;\nexport type Action = (typeof ACTION_VALUES)[number];\n\n/**\n * The things.\n *\n * all CASL's wildcard subject\n * Dashboard the staff overview: counts and trends\n * Member a person's account in this product\n * FounderProfile a member's public profile\n * Post, Poll, Comment\n * Report a member's report of a post, poll or comment\n * SupportTicket a member's request for help\n * SavedReply a support agent's reusable answer\n * PlatformRole a staff role, as something granted and revoked\n * AuditEntry the record of a staff action\n * PlatformSettings product-wide configuration\n * Product something a founder builds, with its team\n * Review a member's stars and words on a product\n * Community an official community (0.9.0)\n * CommunitySuggestion a member's suggestion for a new community (0.9.0)\n * Membership a person's platform membership: granted, revoked (0.12.0)\n * Payment a purchase and its invoice, and its refund (0.12.0)\n * Coupon a code that gives a membership or a discount (0.12.0)\n */\nexport const SUBJECT_VALUES = [\n 'all',\n 'Dashboard',\n 'Member',\n 'FounderProfile',\n 'Post',\n 'Poll',\n 'Comment',\n 'Report',\n 'SupportTicket',\n 'SavedReply',\n 'PlatformRole',\n 'AuditEntry',\n 'PlatformSettings',\n 'Product',\n 'Review',\n 'Community',\n 'CommunitySuggestion',\n 'Membership',\n 'Payment',\n 'Coupon',\n] as const;\nexport const subjectSchema = z.enum(SUBJECT_VALUES);\nexport type Subject = z.infer<typeof subjectSchema>;\n\n/**\n * The fields a rule may set CONDITIONS on, per subject: the server's stored\n * names, ids as strings. Declared here so a condition on a field the subject\n * does not have (`{ ownerId: me }` on a Post) is a compile error, on both sides.\n *\n * A web screen does not evaluate these against an API response: a response\n * names things differently (`author.userId`, not `authorId`). Row-level answers\n * come from the server on the item itself (`viewer.canEdit`); the web's own\n * checks are type-level (\"may I moderate Reports at all\").\n */\nexport interface SubjectFields {\n Dashboard: Record<never, never>;\n /**\n * `appRoles` is the staff roles held, so a rule can protect a role (\"never\n * suspend a superadmin\").\n *\n * For `message` (0.12.0), what the asker's relation to them is:\n * `contact` says the asker knows them (a teammate, a private community in\n * common, a conversation already); `standing` is theirs, so nobody starts a\n * conversation with a viewer, who cannot chat; `acceptsStrangers` is their\n * own setting (Premium and Business members may start one, or only people\n * they know). Optional because only the messaging check computes them: a\n * rule on a field that is absent never matches, so a check that forgets them\n * is refused, never let through.\n */\n Member: {\n _id: string;\n appRoles: PlatformRole[];\n contact?: boolean;\n standing?: Standing;\n acceptsStrangers?: boolean;\n };\n FounderProfile: { userId: string };\n Post: { authorId: string };\n Poll: { authorId: string };\n Comment: { authorId: string };\n Report: { reporterId: string; status: ReportStatus };\n SupportTicket: { memberId: string; assigneeId: string | null };\n SavedReply: { authorId: string };\n /** One role on one person: `memberId` is whose, so \"never your own\" is a rule too. */\n PlatformRole: { role: PlatformRole; memberId: string };\n /** `actorId` is null for an entry a server script wrote. */\n AuditEntry: { actorId: string | null };\n PlatformSettings: Record<never, never>;\n /**\n * The team, as three lists of member ids, so a rule reads \"a product whose\n * managers include me\" (`{ managerIds: me }`). `managerIds` is the owner and\n * the co-founders; `memberIds` is everyone on the team, owner included.\n */\n Product: { ownerId: string; managerIds: string[]; memberIds: string[] };\n Review: { authorId: string };\n /**\n * Its admins and moderators as id lists (0.9.0), so a rule reads \"a\n * community whose admins include me\" (`{ adminIds: me }`). Both short by\n * nature; ordinary members are never listed here (a community has no cap).\n */\n Community: { _id: string; adminIds: string[]; moderatorIds: string[] };\n /** `suggestedBy` so \"your own\" is a rule; `status` so only open ones are decided. */\n CommunitySuggestion: { suggestedBy: string; status: CommunitySuggestionStatus };\n /** Whose membership (0.12.0), so \"never your own\" is a rule. */\n Membership: { userId: string };\n /**\n * Who pays (0.12.0), what for, and where it stands: a viewer may buy only a\n * membership (a plan needs one), only a paid one is refunded, and never by\n * its own payer.\n */\n Payment: { userId: string; status: OrderStatus; kind: PurchaseKind };\n /** Coupons are the platform's (0.12.0): no condition is ever written on one. */\n Coupon: Record<never, never>;\n}\n\n/** A document tagged with its subject type, as CASL's `subject('Post', doc)` makes one. */\nexport type SubjectInstance = {\n [K in keyof SubjectFields]: SubjectFields[K] & ForcedSubject<K>;\n}[keyof SubjectFields];\n\n/**\n * The ability both sides build: the backend from its policy, the web from the\n * rules the backend sends. Mongo-style conditions, typed by `SubjectFields`.\n */\nexport type AppAbility = MongoAbility<[Action, Subject | SubjectInstance]>;\n\n/**\n * Compile-time proof that `SubjectFields` names exactly the subjects, no more\n * and no fewer: adding a subject without its fields fails the build here.\n */\ntype Same<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false;\ntype Assert<T extends true> = T;\nexport type EverySubjectHasFields = Assert<Same<keyof SubjectFields, Exclude<Subject, 'all'>>>;\n\n/**\n * Rules as they cross the wire: CASL's `packRules` output, an array of\n * compact arrays. The web unpacks them with `unpackRules` before building its\n * ability. Each starts with its action(s) and subject(s), comma-joined\n * strings (checked, 0.18.0: an unchecked rule list could silently turn staff\n * routing off); what follows (conditions, inverted, fields, reason) is CASL's\n * own compact form.\n */\nconst zeroOrOne = z.union([z.literal(0), z.literal(1)]);\n/**\n * One packed rule, in exactly the shapes CASL's `PackRule` takes (a union of\n * tuples, not a rest: `unpackRules` needs one of these), with `conditions` as\n * `unknown`, which `isAppRawRule` and the ability itself then judge.\n */\nexport const packedRuleSchema = z.union([\n z.tuple([z.string().min(1), z.string().min(1)]),\n z.tuple([z.string().min(1), z.string().min(1), z.unknown()]),\n z.tuple([z.string().min(1), z.string().min(1), z.unknown(), z.literal(1)]),\n z.tuple([z.string().min(1), z.string().min(1), z.unknown(), zeroOrOne, z.string()]),\n z.tuple([\n z.string().min(1),\n z.string().min(1),\n z.unknown(),\n zeroOrOne,\n z.union([z.string(), z.literal(0)]),\n z.string(),\n ]),\n]);\nexport const packedRulesSchema = z.array(packedRuleSchema);\nexport type PackedRules = z.infer<typeof packedRulesSchema>;\n\n/** One rule as CASL's `unpackRules` gives it back, typed for this app. */\nexport type AppRawRule = RawRuleOf<AppAbility>;\n\nconst ACTION_SET: ReadonlySet<string> = new Set(ACTION_VALUES);\nconst SUBJECT_SET: ReadonlySet<string> = new Set(SUBJECT_VALUES);\nconst everyIn = (value: string | string[] | undefined, known: ReadonlySet<string>) =>\n value !== undefined && (Array.isArray(value) ? value : [value]).every((v) => known.has(v));\n\n/**\n * Whether an UNPACKED rule names only this app's actions and subjects\n * (0.18.0). Packed rules cross the wire as strings (`packRules` joins several\n * actions or subjects with commas, so the wire shape cannot be an enum); this\n * is the check that turns them back into `AppRawRule`s, with no cast:\n * `rules.every(isAppRawRule)` narrows the list, and a list that fails is a\n * contract mismatch, never an ability built from it.\n */\nexport function isAppRawRule(rule: {\n action: string | string[];\n subject?: string | string[];\n}): rule is AppRawRule {\n return everyIn(rule.action, ACTION_SET) && everyIn(rule.subject, SUBJECT_SET);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../src/access/permissions.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema: what a role may do, in CASL's terms\n *\n * Permissions are ACTION on SUBJECT, optionally narrowed by conditions on the\n * subject's fields (\"update a Post whose authorId is mine\"). The backend writes\n * one small policy per role from these words; the web receives the resulting\n * rules and asks the same questions to decide what to draw. Because both sides\n * spell actions and subjects from this file, a permission check with a typo is\n * a compile error, not a button that silently never shows.\n *\n * The SERVER decides; the web only mirrors. Every rule the web holds came from\n * the server for the role being acted as, and every request is checked again\n * on the server, so a tampered client can show itself a button and nothing\n * more.\n *\n * @module founder-schema/access/permissions\n */\n\nimport type { ForcedSubject, MongoAbility, RawRuleOf } from '@casl/ability';\nimport { z } from 'zod';\nimport type { OrderStatus, PurchaseKind } from '../domains/billing/billing.js';\nimport type { CommunitySuggestionStatus } from '../domains/communities/communities.js';\nimport type { ReportStatus } from '../domains/feed/feed.js';\nimport type { PlatformRole } from './roles.js';\nimport type { Standing } from './standing.js';\n\n/**\n * The verbs.\n *\n * manage every action (CASL's wildcard). Only superadmin holds it on `all`.\n * create, read, update, delete\n * moderate hide, restore or remove someone else's content, act on a report\n * assign grant or revoke (a platform role); take or hand over (a ticket)\n * suspend stop a member signing in to the product, and lift it\n * resolve close a support ticket or a report\n * reset clear a credential its owner forgot (a staff member's MPIN)\n * represent act for a product as one of its team: tag a post with it, reply\n * to its reviews, mark feedback helpful, confirm a tester\n *\n * Taking part (0.12.0, membership): the verbs a viewer is refused and a member\n * holds. Creating content stays `create`; these are the rest.\n *\n * react a reaction on a post, poll or comment; an upvote on a product;\n * marking a review helpful\n * vote vote in a poll\n * save bookmark a post or poll\n * join join a community, ask to, or accept its invitation; accept an\n * invitation to a product's team\n * message start a conversation with a Member\n * refund give a payment's money back (staff)\n *\n * Append only: a stored or packed rule names these.\n */\nexport const ACTION_VALUES = [\n 'manage',\n 'create',\n 'read',\n 'update',\n 'delete',\n 'moderate',\n 'assign',\n 'suspend',\n 'resolve',\n 'reset',\n 'represent',\n 'react',\n 'vote',\n 'save',\n 'join',\n 'message',\n 'refund',\n] as const;\nexport type Action = (typeof ACTION_VALUES)[number];\n\n/**\n * The things.\n *\n * all CASL's wildcard subject\n * Dashboard the staff overview: counts and trends\n * Member a person's account in this product\n * FounderProfile a member's public profile\n * Post, Poll, Comment\n * Report a member's report of a post, poll or comment\n * SupportTicket a member's request for help\n * SavedReply a support agent's reusable answer\n * PlatformRole a staff role, as something granted and revoked\n * AuditEntry the record of a staff action\n * PlatformSettings product-wide configuration\n * Product something a founder builds, with its team\n * Review a member's stars and words on a product\n * Community an official community (0.9.0)\n * CommunitySuggestion a member's suggestion for a new community (0.9.0)\n * Membership a person's platform membership: granted, revoked (0.12.0)\n * Payment a purchase and its invoice, and its refund (0.12.0)\n * Coupon a code that gives a membership or a discount (0.12.0)\n */\nexport const SUBJECT_VALUES = [\n 'all',\n 'Dashboard',\n 'Member',\n 'FounderProfile',\n 'Post',\n 'Poll',\n 'Comment',\n 'Report',\n 'SupportTicket',\n 'SavedReply',\n 'PlatformRole',\n 'AuditEntry',\n 'PlatformSettings',\n 'Product',\n 'Review',\n 'Community',\n 'CommunitySuggestion',\n 'Membership',\n 'Payment',\n 'Coupon',\n] as const;\nexport const subjectSchema = z.enum(SUBJECT_VALUES);\nexport type Subject = z.infer<typeof subjectSchema>;\n\n/**\n * The fields a rule may set CONDITIONS on, per subject: the server's stored\n * names, ids as strings. Declared here so a condition on a field the subject\n * does not have (`{ ownerId: me }` on a Post) is a compile error, on both sides.\n *\n * A web screen does not evaluate these against an API response: a response\n * names things differently (`author.userId`, not `authorId`). Row-level answers\n * come from the server on the item itself (`viewer.canEdit`); the web's own\n * checks are type-level (\"may I moderate Reports at all\").\n */\nexport interface SubjectFields {\n Dashboard: Record<never, never>;\n /**\n * `roles` is the staff roles held (0.20.0: from their access grant; `appRoles`\n * until 0.19.0), so a rule can protect a role (\"never suspend a superadmin\").\n *\n * For `message` (0.12.0), what the asker's relation to them is:\n * `contact` says the asker knows them (a teammate, a private community in\n * common, a conversation already); `standing` is theirs, so nobody starts a\n * conversation with a viewer, who cannot chat; `acceptsStrangers` is their\n * own setting (Premium and Business members may start one, or only people\n * they know). Optional because only the messaging check computes them: a\n * rule on a field that is absent never matches, so a check that forgets them\n * is refused, never let through.\n */\n Member: {\n _id: string;\n roles: PlatformRole[];\n contact?: boolean;\n standing?: Standing;\n acceptsStrangers?: boolean;\n };\n FounderProfile: { userId: string };\n Post: { authorId: string };\n Poll: { authorId: string };\n Comment: { authorId: string };\n Report: { reporterId: string; status: ReportStatus };\n SupportTicket: { memberId: string; assigneeId: string | null };\n SavedReply: { authorId: string };\n /** One role on one person: `memberId` is whose, so \"never your own\" is a rule too. */\n PlatformRole: { role: PlatformRole; memberId: string };\n /** `actorId` is null for an entry a server script wrote. */\n AuditEntry: { actorId: string | null };\n PlatformSettings: Record<never, never>;\n /**\n * The team, as three lists of member ids, so a rule reads \"a product whose\n * managers include me\" (`{ managerIds: me }`). `managerIds` is the owner and\n * the co-founders; `memberIds` is everyone on the team, owner included.\n */\n Product: { ownerId: string; managerIds: string[]; memberIds: string[] };\n Review: { authorId: string };\n /**\n * Its admins and moderators as id lists (0.9.0), so a rule reads \"a\n * community whose admins include me\" (`{ adminIds: me }`). Both short by\n * nature; ordinary members are never listed here (a community has no cap).\n */\n Community: { _id: string; adminIds: string[]; moderatorIds: string[] };\n /** `suggestedBy` so \"your own\" is a rule; `status` so only open ones are decided. */\n CommunitySuggestion: { suggestedBy: string; status: CommunitySuggestionStatus };\n /** Whose membership (0.12.0), so \"never your own\" is a rule. */\n Membership: { userId: string };\n /**\n * Who pays (0.12.0), what for, and where it stands: a viewer may buy only a\n * membership (a plan needs one), only a paid one is refunded, and never by\n * its own payer.\n */\n Payment: { userId: string; status: OrderStatus; kind: PurchaseKind };\n /** Coupons are the platform's (0.12.0): no condition is ever written on one. */\n Coupon: Record<never, never>;\n}\n\n/** A document tagged with its subject type, as CASL's `subject('Post', doc)` makes one. */\nexport type SubjectInstance = {\n [K in keyof SubjectFields]: SubjectFields[K] & ForcedSubject<K>;\n}[keyof SubjectFields];\n\n/**\n * The ability both sides build: the backend from its policy, the web from the\n * rules the backend sends. Mongo-style conditions, typed by `SubjectFields`.\n */\nexport type AppAbility = MongoAbility<[Action, Subject | SubjectInstance]>;\n\n/**\n * Compile-time proof that `SubjectFields` names exactly the subjects, no more\n * and no fewer: adding a subject without its fields fails the build here.\n */\ntype Same<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false;\ntype Assert<T extends true> = T;\nexport type EverySubjectHasFields = Assert<Same<keyof SubjectFields, Exclude<Subject, 'all'>>>;\n\n/**\n * Rules as they cross the wire: CASL's `packRules` output, an array of\n * compact arrays. The web unpacks them with `unpackRules` before building its\n * ability. Each starts with its action(s) and subject(s), comma-joined\n * strings (checked, 0.18.0: an unchecked rule list could silently turn staff\n * routing off); what follows (conditions, inverted, fields, reason) is CASL's\n * own compact form.\n */\nconst zeroOrOne = z.union([z.literal(0), z.literal(1)]);\n/**\n * One packed rule, in exactly the shapes CASL's `PackRule` takes (a union of\n * tuples, not a rest: `unpackRules` needs one of these), with `conditions` as\n * `unknown`, which `isAppRawRule` and the ability itself then judge.\n */\nexport const packedRuleSchema = z.union([\n z.tuple([z.string().min(1), z.string().min(1)]),\n z.tuple([z.string().min(1), z.string().min(1), z.unknown()]),\n z.tuple([z.string().min(1), z.string().min(1), z.unknown(), z.literal(1)]),\n z.tuple([z.string().min(1), z.string().min(1), z.unknown(), zeroOrOne, z.string()]),\n z.tuple([\n z.string().min(1),\n z.string().min(1),\n z.unknown(),\n zeroOrOne,\n z.union([z.string(), z.literal(0)]),\n z.string(),\n ]),\n]);\nexport const packedRulesSchema = z.array(packedRuleSchema);\nexport type PackedRules = z.infer<typeof packedRulesSchema>;\n\n/** One rule as CASL's `unpackRules` gives it back, typed for this app. */\nexport type AppRawRule = RawRuleOf<AppAbility>;\n\nconst ACTION_SET: ReadonlySet<string> = new Set(ACTION_VALUES);\nconst SUBJECT_SET: ReadonlySet<string> = new Set(SUBJECT_VALUES);\nconst everyIn = (value: string | string[] | undefined, known: ReadonlySet<string>) =>\n value !== undefined && (Array.isArray(value) ? value : [value]).every((v) => known.has(v));\n\n/**\n * Whether an UNPACKED rule names only this app's actions and subjects\n * (0.18.0). Packed rules cross the wire as strings (`packRules` joins several\n * actions or subjects with commas, so the wire shape cannot be an enum); this\n * is the check that turns them back into `AppRawRule`s, with no cast:\n * `rules.every(isAppRawRule)` narrows the list, and a list that fails is a\n * contract mismatch, never an ability built from it.\n */\nexport function isAppRawRule(rule: {\n action: string | string[];\n subject?: string | string[];\n}): rule is AppRawRule {\n return everyIn(rule.action, ACTION_SET) && everyIn(rule.subject, SUBJECT_SET);\n}\n"]}
|
package/dist/core/common.d.ts
CHANGED
|
@@ -372,38 +372,65 @@ export declare const genderSchema: z.ZodEnum<{
|
|
|
372
372
|
export type Gender = z.infer<typeof genderSchema>;
|
|
373
373
|
/**
|
|
374
374
|
* A full name as it is sent and stored (0.17.0, moved here from the web):
|
|
375
|
-
* trimmed, and every run of spaces made one. `
|
|
375
|
+
* trimmed, and every run of spaces made one. `fullNameSchema` applies it,
|
|
376
376
|
* so the server stores it from every client; a client applies it to show the
|
|
377
377
|
* member what will be kept. What `splitFullName` reads.
|
|
378
378
|
*/
|
|
379
379
|
export declare const tidyFullName: (fullName: string) => string;
|
|
380
380
|
/**
|
|
381
|
-
*
|
|
382
|
-
* onboarding's
|
|
383
|
-
*
|
|
384
|
-
* (`tidyFullName`, 0.17.0: "Asha Rao" from the Me editor was stored as
|
|
385
|
-
* typed, while onboarding sent it tidy). Plain bounds, no custom message:
|
|
381
|
+
* A member's full name as they type it (0.20.0; `displayNameSchema` until
|
|
382
|
+
* 0.19.0): onboarding's "Full name" box and Me's. ONE rule for every body that
|
|
383
|
+
* takes it, stored TIDIED (`tidyFullName`). Plain bounds, no custom message:
|
|
386
384
|
* `too_small` with minimum 1 already says "required", and the client words it.
|
|
385
|
+
*
|
|
386
|
+
* It is never stored as typed: the server splits it (`splitFullName`) and
|
|
387
|
+
* stores the first and last name; the display name is those two joined
|
|
388
|
+
* (`joinFullName`), which is this same tidied string, so nothing is lost.
|
|
387
389
|
*/
|
|
388
|
-
export declare const
|
|
390
|
+
export declare const fullNameSchema: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
389
391
|
/**
|
|
390
|
-
* A full name's first and last name
|
|
391
|
-
*
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
* Rao Kulkarni).
|
|
392
|
+
* A full name's first and last name (the user, 2026-10-18 and 2026-10-22:
|
|
393
|
+
* one "Full name" box, split at the space). The first word is the first name
|
|
394
|
+
* and everything after the first space is the last name, kept as written
|
|
395
|
+
* ("Asha Rao Kulkarni" is Asha and Rao Kulkarni).
|
|
395
396
|
*
|
|
396
397
|
* ⚠ A ONE-WORD NAME IS A WHOLE NAME. Many people in India go by a single name,
|
|
397
398
|
* so `lastName` is then `''`, and nothing may refuse it for that.
|
|
398
399
|
*
|
|
399
|
-
*
|
|
400
|
-
*
|
|
401
|
-
*
|
|
400
|
+
* STORED since 0.20.0 (the user, 2026-10-22: "the display name should be a
|
|
401
|
+
* combination of first name and last name, auto-calculated"): the account
|
|
402
|
+
* keeps `firstName` and `lastName`, and the display name is derived from them
|
|
403
|
+
* (`joinFullName`). So a first name never holds a space, and
|
|
404
|
+
* `joinFullName(splitFullName(x))` is `tidyFullName(x)`.
|
|
402
405
|
*/
|
|
403
406
|
export declare function splitFullName(fullName: string): {
|
|
404
407
|
firstName: string;
|
|
405
408
|
lastName: string;
|
|
406
409
|
};
|
|
410
|
+
/**
|
|
411
|
+
* The display name (0.20.0): the first and last name joined by one space,
|
|
412
|
+
* tidied. The ONLY way a display name is made: the account's model hook calls
|
|
413
|
+
* it, and nothing writes a display name directly.
|
|
414
|
+
*/
|
|
415
|
+
export declare function joinFullName(firstName: string, lastName?: string | null): string;
|
|
416
|
+
/**
|
|
417
|
+
* A string's user-perceived characters: an emoji, a flag or a letter with its
|
|
418
|
+
* accents is ONE, never split (falls back to code points without `Intl.Segmenter`).
|
|
419
|
+
*/
|
|
420
|
+
export declare function graphemesOf(text: string): string[];
|
|
421
|
+
/**
|
|
422
|
+
* The first and last name to seed an account with, from the sign-in service
|
|
423
|
+
* (0.20.0): its `firstName` and `lastName`, joined, cut to `DISPLAY_NAME_MAX`
|
|
424
|
+
* without splitting a character, then split at the first space exactly as the
|
|
425
|
+
* "Full name" box is. So a seeded name keeps the same rule as a typed one: a
|
|
426
|
+
* Google given name "Mary Ann" with family name "Rao" is first name Mary, last
|
|
427
|
+
* name Ann Rao. Empty parts are `''`; the sign-in service always sends a first
|
|
428
|
+
* name, and a blank one stays blank for the caller to handle.
|
|
429
|
+
*/
|
|
430
|
+
export declare function nameFromSignIn(firstName?: string | null, lastName?: string | null): {
|
|
431
|
+
firstName: string;
|
|
432
|
+
lastName: string;
|
|
433
|
+
};
|
|
407
434
|
/** The digits an Indian mobile number has: what `phoneSchema` takes and the field keeps. */
|
|
408
435
|
export declare const PHONE_DIGITS = 10;
|
|
409
436
|
/**
|
|
@@ -438,8 +465,9 @@ export declare function normalizePhone(raw: string): string;
|
|
|
438
465
|
* location has a country, because the model requires one, so the read side
|
|
439
466
|
* loses nothing.
|
|
440
467
|
*
|
|
441
|
-
* This is a DECLARED location, not a GPS reading
|
|
442
|
-
*
|
|
468
|
+
* This is a DECLARED location, not a GPS reading (Jansathi's rule): a GPS
|
|
469
|
+
* fix only ever fills the member's field; the member saves it (0.19.0,
|
|
470
|
+
* `domains/location/areas.ts`).
|
|
443
471
|
*/
|
|
444
472
|
export declare const locationSchema: z.ZodObject<{
|
|
445
473
|
city: z.ZodString;
|
|
@@ -448,18 +476,11 @@ export declare const locationSchema: z.ZodObject<{
|
|
|
448
476
|
}, z.core.$strip>;
|
|
449
477
|
export type FounderLocation = z.infer<typeof locationSchema>;
|
|
450
478
|
/**
|
|
451
|
-
*
|
|
452
|
-
*
|
|
453
|
-
*
|
|
454
|
-
* emptied parameter is "missing", never 0, because 0,0 is a real place in the
|
|
455
|
-
* Gulf of Guinea and would be looked up rather than refused.
|
|
479
|
+
* A whole number in a QUERY (everything in a URL is text, so coercion is right
|
|
480
|
+
* here and only here). An emptied parameter is "not provided": the fallback,
|
|
481
|
+
* never 0 by accident.
|
|
456
482
|
*/
|
|
457
|
-
export declare const
|
|
458
|
-
lat: z.ZodPreprocess<z.ZodCoercedNumber<unknown>, unknown>;
|
|
459
|
-
lng: z.ZodPreprocess<z.ZodCoercedNumber<unknown>, unknown>;
|
|
460
|
-
}, z.core.$strip>;
|
|
461
|
-
export type CoordinatesQuery = z.infer<typeof coordinatesQuerySchema>;
|
|
462
|
-
export type CoordinatesQueryInput = z.input<typeof coordinatesQuerySchema>;
|
|
483
|
+
export declare const queryInt: (min: number, max: number, fallback: number) => z.ZodPreprocess<z.ZodDefault<z.ZodCoercedNumber<unknown>>, unknown>;
|
|
463
484
|
/**
|
|
464
485
|
* Cursor pagination query. Feed and Discover both use it.
|
|
465
486
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/core/common.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/core/common.ts"],"names":[],"mappings":"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"}
|
package/dist/core/common.js
CHANGED
|
@@ -411,38 +411,41 @@ export const genderSchema = z.enum(GENDER_VALUES);
|
|
|
411
411
|
// ─── Display name ────────────────────────────────────────────────────────────
|
|
412
412
|
/**
|
|
413
413
|
* A full name as it is sent and stored (0.17.0, moved here from the web):
|
|
414
|
-
* trimmed, and every run of spaces made one. `
|
|
414
|
+
* trimmed, and every run of spaces made one. `fullNameSchema` applies it,
|
|
415
415
|
* so the server stores it from every client; a client applies it to show the
|
|
416
416
|
* member what will be kept. What `splitFullName` reads.
|
|
417
417
|
*/
|
|
418
418
|
export const tidyFullName = (fullName) => fullName.trim().replace(/\s+/g, ' ');
|
|
419
419
|
/**
|
|
420
|
-
*
|
|
421
|
-
* onboarding's
|
|
422
|
-
*
|
|
423
|
-
* (`tidyFullName`, 0.17.0: "Asha Rao" from the Me editor was stored as
|
|
424
|
-
* typed, while onboarding sent it tidy). Plain bounds, no custom message:
|
|
420
|
+
* A member's full name as they type it (0.20.0; `displayNameSchema` until
|
|
421
|
+
* 0.19.0): onboarding's "Full name" box and Me's. ONE rule for every body that
|
|
422
|
+
* takes it, stored TIDIED (`tidyFullName`). Plain bounds, no custom message:
|
|
425
423
|
* `too_small` with minimum 1 already says "required", and the client words it.
|
|
424
|
+
*
|
|
425
|
+
* It is never stored as typed: the server splits it (`splitFullName`) and
|
|
426
|
+
* stores the first and last name; the display name is those two joined
|
|
427
|
+
* (`joinFullName`), which is this same tidied string, so nothing is lost.
|
|
426
428
|
*/
|
|
427
|
-
export const
|
|
429
|
+
export const fullNameSchema = z
|
|
428
430
|
.string()
|
|
429
431
|
.trim()
|
|
430
432
|
.min(1)
|
|
431
433
|
.max(DISPLAY_NAME_MAX)
|
|
432
434
|
.transform(tidyFullName);
|
|
433
435
|
/**
|
|
434
|
-
* A full name's first and last name
|
|
435
|
-
*
|
|
436
|
-
*
|
|
437
|
-
*
|
|
438
|
-
* Rao Kulkarni).
|
|
436
|
+
* A full name's first and last name (the user, 2026-10-18 and 2026-10-22:
|
|
437
|
+
* one "Full name" box, split at the space). The first word is the first name
|
|
438
|
+
* and everything after the first space is the last name, kept as written
|
|
439
|
+
* ("Asha Rao Kulkarni" is Asha and Rao Kulkarni).
|
|
439
440
|
*
|
|
440
441
|
* ⚠ A ONE-WORD NAME IS A WHOLE NAME. Many people in India go by a single name,
|
|
441
442
|
* so `lastName` is then `''`, and nothing may refuse it for that.
|
|
442
443
|
*
|
|
443
|
-
*
|
|
444
|
-
*
|
|
445
|
-
*
|
|
444
|
+
* STORED since 0.20.0 (the user, 2026-10-22: "the display name should be a
|
|
445
|
+
* combination of first name and last name, auto-calculated"): the account
|
|
446
|
+
* keeps `firstName` and `lastName`, and the display name is derived from them
|
|
447
|
+
* (`joinFullName`). So a first name never holds a space, and
|
|
448
|
+
* `joinFullName(splitFullName(x))` is `tidyFullName(x)`.
|
|
446
449
|
*/
|
|
447
450
|
export function splitFullName(fullName) {
|
|
448
451
|
const name = tidyFullName(fullName);
|
|
@@ -451,6 +454,46 @@ export function splitFullName(fullName) {
|
|
|
451
454
|
? { firstName: name, lastName: '' }
|
|
452
455
|
: { firstName: name.slice(0, space), lastName: name.slice(space + 1) };
|
|
453
456
|
}
|
|
457
|
+
/**
|
|
458
|
+
* The display name (0.20.0): the first and last name joined by one space,
|
|
459
|
+
* tidied. The ONLY way a display name is made: the account's model hook calls
|
|
460
|
+
* it, and nothing writes a display name directly.
|
|
461
|
+
*/
|
|
462
|
+
export function joinFullName(firstName, lastName) {
|
|
463
|
+
return tidyFullName(`${firstName} ${lastName ?? ''}`);
|
|
464
|
+
}
|
|
465
|
+
const graphemeSegmenter = typeof Intl !== 'undefined' && 'Segmenter' in Intl
|
|
466
|
+
? new Intl.Segmenter(undefined, { granularity: 'grapheme' })
|
|
467
|
+
: null;
|
|
468
|
+
/**
|
|
469
|
+
* A string's user-perceived characters: an emoji, a flag or a letter with its
|
|
470
|
+
* accents is ONE, never split (falls back to code points without `Intl.Segmenter`).
|
|
471
|
+
*/
|
|
472
|
+
export function graphemesOf(text) {
|
|
473
|
+
return graphemeSegmenter
|
|
474
|
+
? Array.from(graphemeSegmenter.segment(text), (s) => s.segment)
|
|
475
|
+
: Array.from(text);
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* The first and last name to seed an account with, from the sign-in service
|
|
479
|
+
* (0.20.0): its `firstName` and `lastName`, joined, cut to `DISPLAY_NAME_MAX`
|
|
480
|
+
* without splitting a character, then split at the first space exactly as the
|
|
481
|
+
* "Full name" box is. So a seeded name keeps the same rule as a typed one: a
|
|
482
|
+
* Google given name "Mary Ann" with family name "Rao" is first name Mary, last
|
|
483
|
+
* name Ann Rao. Empty parts are `''`; the sign-in service always sends a first
|
|
484
|
+
* name, and a blank one stays blank for the caller to handle.
|
|
485
|
+
*/
|
|
486
|
+
export function nameFromSignIn(firstName, lastName) {
|
|
487
|
+
const full = graphemesOf(joinFullName(firstName ?? '', lastName))
|
|
488
|
+
.slice(0, DISPLAY_NAME_MAX)
|
|
489
|
+
.join('');
|
|
490
|
+
// A cut can land on a character that is longer than one UTF-16 unit; the
|
|
491
|
+
// model's `maxlength` counts units, so trim whole characters until it fits.
|
|
492
|
+
let fitted = full;
|
|
493
|
+
while (fitted.length > DISPLAY_NAME_MAX)
|
|
494
|
+
fitted = graphemesOf(fitted).slice(0, -1).join('');
|
|
495
|
+
return splitFullName(fitted);
|
|
496
|
+
}
|
|
454
497
|
// ─── Phone ───────────────────────────────────────────────────────────────────
|
|
455
498
|
/** The digits an Indian mobile number has: what `phoneSchema` takes and the field keeps. */
|
|
456
499
|
export const PHONE_DIGITS = 10;
|
|
@@ -498,8 +541,9 @@ export function normalizePhone(raw) {
|
|
|
498
541
|
* location has a country, because the model requires one, so the read side
|
|
499
542
|
* loses nothing.
|
|
500
543
|
*
|
|
501
|
-
* This is a DECLARED location, not a GPS reading
|
|
502
|
-
*
|
|
544
|
+
* This is a DECLARED location, not a GPS reading (Jansathi's rule): a GPS
|
|
545
|
+
* fix only ever fills the member's field; the member saves it (0.19.0,
|
|
546
|
+
* `domains/location/areas.ts`).
|
|
503
547
|
*/
|
|
504
548
|
export const locationSchema = z.object({
|
|
505
549
|
city: z.string().trim().min(1).max(CITY_MAX),
|
|
@@ -507,16 +551,11 @@ export const locationSchema = z.object({
|
|
|
507
551
|
country: z.string().trim().min(1).max(COUNTRY_MAX),
|
|
508
552
|
});
|
|
509
553
|
/**
|
|
510
|
-
*
|
|
511
|
-
*
|
|
512
|
-
*
|
|
513
|
-
* emptied parameter is "missing", never 0, because 0,0 is a real place in the
|
|
514
|
-
* Gulf of Guinea and would be looked up rather than refused.
|
|
554
|
+
* A whole number in a QUERY (everything in a URL is text, so coercion is right
|
|
555
|
+
* here and only here). An emptied parameter is "not provided": the fallback,
|
|
556
|
+
* never 0 by accident.
|
|
515
557
|
*/
|
|
516
|
-
export const
|
|
517
|
-
lat: z.preprocess(emptyStringToUndefined, z.coerce.number().min(-90).max(90)),
|
|
518
|
-
lng: z.preprocess(emptyStringToUndefined, z.coerce.number().min(-180).max(180)),
|
|
519
|
-
});
|
|
558
|
+
export const queryInt = (min, max, fallback) => z.preprocess(emptyStringToUndefined, z.coerce.number().int().min(min).max(max).default(fallback));
|
|
520
559
|
// ─── Pagination ──────────────────────────────────────────────────────────────
|
|
521
560
|
/**
|
|
522
561
|
* Cursor pagination query. Feed and Discover both use it.
|