founder-schema 0.16.0 → 0.18.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 +263 -19
- package/dist/access/access.d.ts +1 -1
- package/dist/access/access.d.ts.map +1 -1
- package/dist/access/access.js +4 -1
- package/dist/access/access.js.map +1 -1
- package/dist/access/permissions.d.ts +27 -29
- package/dist/access/permissions.d.ts.map +1 -1
- package/dist/access/permissions.js +39 -3
- package/dist/access/permissions.js.map +1 -1
- package/dist/core/common.d.ts +117 -23
- package/dist/core/common.d.ts.map +1 -1
- package/dist/core/common.js +104 -21
- package/dist/core/common.js.map +1 -1
- package/dist/core/errors.d.ts +7 -5
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +17 -11
- package/dist/core/errors.js.map +1 -1
- package/dist/core/limits.d.ts +55 -17
- package/dist/core/limits.d.ts.map +1 -1
- package/dist/core/limits.js +59 -22
- package/dist/core/limits.js.map +1 -1
- package/dist/core/money.d.ts +33 -0
- package/dist/core/money.d.ts.map +1 -0
- package/dist/core/money.js +36 -0
- package/dist/core/money.js.map +1 -0
- package/dist/core/responses.d.ts +14 -4
- package/dist/core/responses.d.ts.map +1 -1
- package/dist/core/responses.js.map +1 -1
- package/dist/core/validation.d.ts +32 -2
- package/dist/core/validation.d.ts.map +1 -1
- package/dist/core/validation.js +32 -2
- package/dist/core/validation.js.map +1 -1
- package/dist/domains/billing/billing.d.ts +61 -8
- package/dist/domains/billing/billing.d.ts.map +1 -1
- package/dist/domains/billing/billing.js +58 -22
- package/dist/domains/billing/billing.js.map +1 -1
- package/dist/domains/billing/gst.d.ts.map +1 -1
- package/dist/domains/billing/gst.js +4 -3
- package/dist/domains/billing/gst.js.map +1 -1
- package/dist/domains/chat/chat.d.ts +0 -21
- package/dist/domains/chat/chat.d.ts.map +1 -1
- package/dist/domains/chat/chat.js +0 -20
- package/dist/domains/chat/chat.js.map +1 -1
- package/dist/domains/communities/communities.d.ts +30 -12
- package/dist/domains/communities/communities.d.ts.map +1 -1
- package/dist/domains/communities/communities.js +43 -15
- package/dist/domains/communities/communities.js.map +1 -1
- package/dist/domains/contributions/contributions.d.ts +1 -1
- package/dist/domains/contributions/contributions.js +1 -1
- package/dist/domains/contributions/contributions.js.map +1 -1
- package/dist/domains/dashboard/dashboard.d.ts +3 -1
- package/dist/domains/dashboard/dashboard.d.ts.map +1 -1
- package/dist/domains/dashboard/dashboard.js +23 -7
- package/dist/domains/dashboard/dashboard.js.map +1 -1
- package/dist/domains/discover/discover.d.ts +8 -8
- package/dist/domains/feed/feed.d.ts +55 -153
- package/dist/domains/feed/feed.d.ts.map +1 -1
- package/dist/domains/feed/feed.js +53 -58
- package/dist/domains/feed/feed.js.map +1 -1
- package/dist/domains/location/india.d.ts +215 -0
- package/dist/domains/location/india.d.ts.map +1 -0
- package/dist/domains/location/india.js +1203 -0
- package/dist/domains/location/india.js.map +1 -0
- package/dist/domains/members/members.d.ts +2 -2
- package/dist/domains/membership/membership.d.ts +26 -0
- package/dist/domains/membership/membership.d.ts.map +1 -1
- package/dist/domains/membership/membership.js +55 -1
- package/dist/domains/membership/membership.js.map +1 -1
- package/dist/domains/moderation/moderation.d.ts +2 -2
- package/dist/domains/moderation/moderation.js +3 -3
- package/dist/domains/moderation/moderation.js.map +1 -1
- package/dist/domains/notifications/notifications.d.ts +1 -10
- package/dist/domains/notifications/notifications.d.ts.map +1 -1
- package/dist/domains/notifications/notifications.js +2 -3
- package/dist/domains/notifications/notifications.js.map +1 -1
- package/dist/domains/onboarding/onboarding-answers.d.ts +112 -0
- package/dist/domains/onboarding/onboarding-answers.d.ts.map +1 -0
- package/dist/domains/onboarding/onboarding-answers.js +164 -0
- package/dist/domains/onboarding/onboarding-answers.js.map +1 -0
- package/dist/domains/onboarding/onboarding.d.ts +398 -62
- package/dist/domains/onboarding/onboarding.d.ts.map +1 -1
- package/dist/domains/onboarding/onboarding.js +229 -68
- package/dist/domains/onboarding/onboarding.js.map +1 -1
- package/dist/domains/products/products.d.ts +43 -8
- package/dist/domains/products/products.d.ts.map +1 -1
- package/dist/domains/products/products.js +57 -11
- package/dist/domains/products/products.js.map +1 -1
- package/dist/domains/profile/founder-profile.d.ts +120 -68
- package/dist/domains/profile/founder-profile.d.ts.map +1 -1
- package/dist/domains/profile/founder-profile.js +80 -29
- package/dist/domains/profile/founder-profile.js.map +1 -1
- package/dist/domains/profile/my-profile.d.ts +3 -3
- package/dist/domains/reviews/reviews.d.ts +1 -1
- package/dist/domains/reviews/reviews.d.ts.map +1 -1
- package/dist/domains/reviews/reviews.js +2 -2
- package/dist/domains/reviews/reviews.js.map +1 -1
- package/dist/domains/support/support.d.ts +22 -3
- package/dist/domains/support/support.d.ts.map +1 -1
- package/dist/domains/support/support.js +19 -5
- package/dist/domains/support/support.js.map +1 -1
- 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/domains/uploads/uploads.d.ts +7 -22
- package/dist/domains/uploads/uploads.d.ts.map +1 -1
- package/dist/domains/uploads/uploads.js +7 -21
- package/dist/domains/uploads/uploads.js.map +1 -1
- package/dist/env.d.ts +26 -13
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +58 -23
- package/dist/env.js.map +1 -1
- package/dist/index.d.ts +18 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,11 +29,11 @@ Consumed by:
|
|
|
29
29
|
|
|
30
30
|
## Installing it
|
|
31
31
|
|
|
32
|
-
On npm since 0.1.0. Pin the EXACT version, with `-E` so npm writes `0.
|
|
33
|
-
not `^0.
|
|
32
|
+
On npm since 0.1.0. Pin the EXACT version, with `-E` so npm writes `0.18.0` and
|
|
33
|
+
not `^0.18.0` (a caret on `0.x` pins the minor; see Publishing):
|
|
34
34
|
|
|
35
35
|
```sh
|
|
36
|
-
npm install -E founder-schema@0.
|
|
36
|
+
npm install -E founder-schema@0.18.0
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
**Working against a change that is not published yet** is the path TEAM.md
|
|
@@ -86,19 +86,251 @@ SAME schema object, so the two can never disagree about what is allowed.
|
|
|
86
86
|
(`ONBOARDING_STEP_SCHEMAS`), so a step the wizard lets through is a step the
|
|
87
87
|
finish accepts. The step list (`FOUNDER_ONBOARDING_STEPS`) is built from those
|
|
88
88
|
slices, including whether a step may be skipped.
|
|
89
|
-
- **No copy.**
|
|
90
|
-
(`
|
|
89
|
+
- **No copy.** A step's label and title, a topic category and a goal category
|
|
90
|
+
are translation keys or plain keys (`labelKey`, `titleKey`, `key`); the words
|
|
91
|
+
live in each client. The web keys its category words by `key`, type-checked
|
|
92
|
+
against it, and its step words by the step's own key (`OnboardingStepKey`);
|
|
93
|
+
`labelKey` / `titleKey` are there for a translated client (i18n), and
|
|
94
|
+
nothing reads them yet.
|
|
91
95
|
- **Bodies are typed JSON; only queries coerce.** `teamSize: "5"` is refused in a
|
|
92
96
|
body, and `?limit=5` is read as 5.
|
|
93
|
-
- **Shapes are derived, never copied.** One `locationSchema
|
|
94
|
-
`
|
|
95
|
-
places pick, omit or reuse
|
|
97
|
+
- **Shapes are derived, never copied.** One `locationSchema` to READ a place and
|
|
98
|
+
one `indiaLocationSchema` to WRITE one (0.17.0), one `founderLinksSchema`, one
|
|
99
|
+
`ventureSchema`, one `mediaImageSchema`; the other places pick, omit or reuse
|
|
100
|
+
them, and a test checks by object identity.
|
|
96
101
|
|
|
97
102
|
`zod` is a **peer** dependency (`^4.4.0`), on purpose. The consumer owns the Zod
|
|
98
103
|
instance; this package must not bring its own.
|
|
99
104
|
|
|
100
105
|
---
|
|
101
106
|
|
|
107
|
+
## Breaking in 0.18.0: the SSOT audit (2026-10-20)
|
|
108
|
+
|
|
109
|
+
The user, 2026-10-20: "make sure it is the SSOT for data types, validation,
|
|
110
|
+
etc., for front end and back end, implemented thoroughly end to end, no
|
|
111
|
+
patchwork or lazy workaround, and a proper cleanup". Ten read-only reviewers,
|
|
112
|
+
every high finding verified in code. Each rule below lived in two places, or in
|
|
113
|
+
one consumer only, or as a hand-written literal; it is one export now, and both
|
|
114
|
+
consumers use it (`tests/ssot-0-18.test.ts`, one test per rule).
|
|
115
|
+
|
|
116
|
+
**Removed** (no consumer imports any; listed in `public-surface.test.ts`):
|
|
117
|
+
`chatInvitation*`, the 0.4.0 upload-key code (`uploadKey*`, `UPLOAD_KEY_*`,
|
|
118
|
+
`uploadKeyInvalid`; the picker's extensions are `UPLOAD_FILE_EXTENSIONS` now),
|
|
119
|
+
`PRODUCTS_OWNED_MAX`, `REVIEWS_REWARDED_PER_DAY`, the never-sent codes
|
|
120
|
+
`ORDER_EXPIRED` and `COUPON_FOUNDING_EXISTS`, the unused zod wrappers
|
|
121
|
+
`actionSchema`, `purchaseKindSchema`, `notificationPreferenceSchema`,
|
|
122
|
+
`onboardingProgressSchema`, the uploaded-video shape (`feedVideoSchema`: post
|
|
123
|
+
media is images only) and the link preview no server writes (`linkPreview*`,
|
|
124
|
+
`LINK_PREVIEW_*`).
|
|
125
|
+
|
|
126
|
+
**Stricter or changed on the wire:**
|
|
127
|
+
- `PATCH /onboarding` takes the draft in the FORM's shape
|
|
128
|
+
(`onboardingFormSchema.partial().strict()`): typed and bounded, half-answered
|
|
129
|
+
still fine. It took any JSON before.
|
|
130
|
+
- `PATCH /profiles/me` takes only its own fields: `headline`, `about`, `skills`,
|
|
131
|
+
`goals`, `links`. The name and the city are the personal details', the venture
|
|
132
|
+
the business editor's. `goals` keeps at least one (`requiredGoalListSchema`,
|
|
133
|
+
the user), as onboarding's finish does.
|
|
134
|
+
- `PATCH /feed/:id` may clear the text; the server refuses only a post left with
|
|
135
|
+
nothing (`postHasContent`, the create body's rule too).
|
|
136
|
+
- A vote names each option once (`pollVoteDuplicate`). An emptied `?cursor=`
|
|
137
|
+
means none; a cursor is at most `CURSOR_MAX`.
|
|
138
|
+
- A blank ban reason means no reason. The MPIN rule reads `MPIN_LENGTH`.
|
|
139
|
+
- Responses: `grantFailed` is a `GRANT_FAILURE_VALUES`; the dashboard's support
|
|
140
|
+
block adds `waitingTickets` and `openTickets` is status `open` only; product
|
|
141
|
+
links are READ leniently (`productLinksReadSchema`), so an older link cannot
|
|
142
|
+
break a page; ids and usernames are typed where they were strings; excerpts
|
|
143
|
+
are bounded; packed CASL rules are checked (action and subject).
|
|
144
|
+
- New codes for refusals that were a bare `FORBIDDEN`:
|
|
145
|
+
`COMMUNITY_NOT_ON_YOURSELF`, `HIDDEN_BY_STAFF`, `TICKET_MEMBER_ONLY`; body
|
|
146
|
+
messages `bodyTooLarge`, `bodyNotJson`.
|
|
147
|
+
- A community's last admin, alone in it, may leave: that ARCHIVES it (on the
|
|
148
|
+
audit record, as a staff archive is). A plain member last in an adminless
|
|
149
|
+
community leaves it as it was.
|
|
150
|
+
- Every request body types what a caller SENDS: no body field is a
|
|
151
|
+
`z.preprocess` (whose input type is `unknown`), so a web typing a body by
|
|
152
|
+
`z.input<typeof xBodySchema>` cannot send the wrong type and compile.
|
|
153
|
+
`optionalTrimmedString` and `indiaLocationSchema.state` were the two; a test
|
|
154
|
+
walks every exported body and fails on any preprocess. Same runtime rules.
|
|
155
|
+
- A model refusing what the schema accepted answers `500 INTERNAL_ERROR` (our
|
|
156
|
+
mismatch), never `400`; body-parse refusals carry message KEYS.
|
|
157
|
+
- `PATCH /profiles/me` is STRICT: a key that belongs to another editor is a
|
|
158
|
+
400 naming it, never dropped without a word.
|
|
159
|
+
- `grantFailed` adds `GRANT_REFUSED`, any other refusal of a grant: still
|
|
160
|
+
recorded and refunded, never a capture rolled back for a reason not listed.
|
|
161
|
+
- `changesDiscoverCard` ignores points (`FOUNDER_CARD_EDIT_FIELDS`): other
|
|
162
|
+
people's acts move them, and they never lift a member up Discover.
|
|
163
|
+
- New: `keptPaise` (what is kept of a payment: the Overview's revenue) and
|
|
164
|
+
`refundLeftPaise` (what a refund returns: never zeroed by a credit note, which
|
|
165
|
+
a failed refund carries), `CONTRIBUTION_PAIR_WINDOW_DAYS` (the pair cap's own
|
|
166
|
+
window), `RAZORPAY_EVENT_RETENTION_DAYS`, `communityTypeChangeAllowed` (an
|
|
167
|
+
archived community narrows, never opens), `communityViewerSchema.leaveArchives`
|
|
168
|
+
(the server says what leaving does), `envVar.optionalOneOf`;
|
|
169
|
+
`onboardingDraftForStep` answers the form's own type, so a step save needs no
|
|
170
|
+
cast.
|
|
171
|
+
- `founder-schema/env`: a BLANK line never takes a real fallback (a port,
|
|
172
|
+
localhost, `development`, a database name): it is refused at start-up and
|
|
173
|
+
named. Unset still takes it; a blank fallback (`''`, a feature off) still
|
|
174
|
+
takes a blank line. `optionalText({ refuseBlank: true })` is for a setting
|
|
175
|
+
whose absence is a legitimate "off" but whose blank line in a deployment
|
|
176
|
+
would switch a feature off silently (the web's media host).
|
|
177
|
+
- `indiaLocationSchema.state`: missing, blank and unknown all refuse as the
|
|
178
|
+
choice they are (`invalid_value`, as 0.17 did), so a form words "Choose your
|
|
179
|
+
state".
|
|
180
|
+
|
|
181
|
+
**One rule each, moved or named here:** the upgrade price and India's calendar
|
|
182
|
+
months (`upgradePrice`, `addPlanPeriod`, `UPGRADE_PER_*`: the Terms state it
|
|
183
|
+
from these), `rupees`, `INDIA_UTC_OFFSET_MS` / `DAY_MS`, `eachOnce`,
|
|
184
|
+
`missingForListing`, `productIsOnDiscover`, `changesDiscoverCard`,
|
|
185
|
+
`CONTACT_VISIBILITY_AUDIENCE` (the user: members only, all four),
|
|
186
|
+
`canonicalCityName`, the one YouTube rule (posts and products: `/v/`, and the
|
|
187
|
+
music and www.youtu.be hosts), `ventureBodyFrom` / `foundedYearDigits`,
|
|
188
|
+
`QueryArgs<S>` (type a list's arguments by the PARSED shape: a query field's
|
|
189
|
+
input is `unknown`), and the values and limits the backend copied by hand:
|
|
190
|
+
`TICKET_SIDE_VALUES`, `INVOICE_KIND_VALUES`, `COUPON_KIND_VALUES`,
|
|
191
|
+
`REPORT_STATUS_VALUES`, `COMMUNITY_SUGGESTION_DECISION_VALUES`,
|
|
192
|
+
`TICKET_CHANGE_VALUES`, `REFUND_ASK_CATEGORY`, `COUPON_PERCENT_*`,
|
|
193
|
+
`COUPON_LABEL_MIN`, `BILLING_NAME_MIN` / `BILLING_ADDRESS_MIN`, `GSTIN_LENGTH`,
|
|
194
|
+
`INVOICE_NUMBER_MAX`, `RAZORPAY_*_MAX`, `RATING_MIN` / `RATING_MAX`,
|
|
195
|
+
`DASHBOARD_RECENT_DAYS`, `PHONE_INPUT_MAX` / `DATE_INPUT_MAX` /
|
|
196
|
+
`GUIDELINES_VERSION_MAX`. The permission fields type their statuses.
|
|
197
|
+
|
|
198
|
+
**Deploy order: the backend, then the web.** The web reads the new response
|
|
199
|
+
fields; a 0.17 web on a 0.18 backend reads the dashboard and payments as before
|
|
200
|
+
(unknown keys are dropped).
|
|
201
|
+
|
|
202
|
+
## Breaking in 0.17.0: the onboarding photo is saved, and onboarding's single source of truth (2026-10-19)
|
|
203
|
+
|
|
204
|
+
Two pieces of the user's work on onboarding, 2026-10-19.
|
|
205
|
+
|
|
206
|
+
**The photo.** Going back to the photo step showed no photo, though the member
|
|
207
|
+
had pressed Save and continue on it. Every other answer came back from the
|
|
208
|
+
draft; the photo was held for the visit only, so any reload or a resume the
|
|
209
|
+
next day lost it.
|
|
210
|
+
|
|
211
|
+
- **New:** `onboardingStatusSchema.photo` (optional, a `mediaImageSchema`): the
|
|
212
|
+
photo the member saved, as it will appear. Absent when there is none.
|
|
213
|
+
- **Changed meaning:** a step save carrying `draft.avatarMediaId` (an upload id,
|
|
214
|
+
or `null`) is the photo step SAVING its photo. The server holds that upload
|
|
215
|
+
for the draft, so the 24 hour unattached sweep never deletes it, and lets go
|
|
216
|
+
of the one it replaces; `null` lets go of it. It is not kept in the draft:
|
|
217
|
+
`readOnboardingDraft` still gives `null` for it, and the photo comes back only
|
|
218
|
+
as `photo`, from what the server holds.
|
|
219
|
+
- **Stricter:** `onboardingProgressUpdateSchema` refuses a `draft.avatarMediaId`
|
|
220
|
+
that is neither an upload id nor `null` (`invalidId`, at
|
|
221
|
+
`draft.avatarMediaId`). No client sent the key before 0.17.0.
|
|
222
|
+
- **Backend:** a held photo is a media row attached to the member's onboarding
|
|
223
|
+
progress row (attachment kind `onboarding`, backend only). Finish moves it to
|
|
224
|
+
the new profile in the same transaction; a scheduled job lets go of a held
|
|
225
|
+
photo whose progress row the 90 day TTL deleted (Privacy: sign-up answers go
|
|
226
|
+
90 days after they were last changed). New partial index on `media` for that
|
|
227
|
+
job; run `sync-indexes --apply` after deploy.
|
|
228
|
+
- **Deploy order: the backend, then the web.** The web reads the status with
|
|
229
|
+
`z.object`, which drops a key it does not know, so a 0.16 web on a 0.17
|
|
230
|
+
backend simply shows no photo on Back, as today.
|
|
231
|
+
- **Rollback floor:** a backend below 0.17 does not know the `onboarding`
|
|
232
|
+
attachment, so photos held by then stay attached and are never swept until
|
|
233
|
+
0.17 runs again, and a member who finishes on the old build is told the photo
|
|
234
|
+
expired and asked to add it again (the old finish takes only an unattached
|
|
235
|
+
upload). A rollback that is KEPT therefore needs a one-off release of every
|
|
236
|
+
`media` row attached as kind `onboarding`, or those photos outlive the
|
|
237
|
+
Privacy's 90 days (account deletion still erases them).
|
|
238
|
+
|
|
239
|
+
**One source of truth** (the user: "all the data for all the onboarding steps
|
|
240
|
+
and all the errors and validation types ... should come from the founder
|
|
241
|
+
circle schema"). What a client or the backend kept its own copy of now lives
|
|
242
|
+
here, and each consumer reads it:
|
|
243
|
+
|
|
244
|
+
- **New, data:** `GOAL_CATEGORIES` / `GOAL_CATEGORY_KEYS` (the goals step's
|
|
245
|
+
three groups, from the web); `domains/location/india`: `INDIA`,
|
|
246
|
+
`INDIA_DIALING_CODE`, `INDIA_STATES` (each state's cities and other names,
|
|
247
|
+
from the web) with names typed against `GST_STATES` (ONE list of state
|
|
248
|
+
names), `INDIA_STATE_NAMES`, `STATE_ALIASES`, `CITY_ALIASES`,
|
|
249
|
+
`findIndiaState`, `listedCityName`, `canonicalIndiaLocation`;
|
|
250
|
+
`ONBOARDING_DRAFT_RETENTION_DAYS` (90, from the backend's TTL);
|
|
251
|
+
`PHONE_DIGITS`.
|
|
252
|
+
- **New, rules every side applies the same way:** `normalizePhone`,
|
|
253
|
+
`normalizeLinkUrl`, `tidyFullName`, `teamSizeDigits` (from the web);
|
|
254
|
+
`isRealIsoDate`, `todayInIndia`, `dateOfBirthBounds` (a picker's window,
|
|
255
|
+
exact at both ends), `isUnderAgeDateOfBirth` (the draft's age rule, from the
|
|
256
|
+
backend, now with the real-date check), `isoDateToDate` / `dateToIsoDate`;
|
|
257
|
+
`goalListSchema` / `skillListSchema` (one list rule for the finish and Me).
|
|
258
|
+
- **New, from the answers to what is sent** (`domains/onboarding/
|
|
259
|
+
onboarding-answers`, from the web): `emptyOnboardingForm`, `ventureStarted`,
|
|
260
|
+
`onboardingDraftForStep` (exactly a step's own fields), and
|
|
261
|
+
`completeOnboardingBodyFrom` (the finish body: every unanswered optional
|
|
262
|
+
answer absent, each answer cleaned as above), with the
|
|
263
|
+
`CompleteOnboardingBodyCandidate` type; `locationStarted` (a place opened
|
|
264
|
+
but nothing chosen is "no location").
|
|
265
|
+
- **New, the keys of shapes** (so no consumer lists them by hand):
|
|
266
|
+
`FOUNDER_LINK_KEYS` / `FounderLinkKey`, `VENTURE_KEYS` /
|
|
267
|
+
`VENTURE_OPTIONAL_KEYS` / `VentureKey`; types `IndiaLocation`, `IndiaState`,
|
|
268
|
+
`IndiaStateName`, `GoalCategory`.
|
|
269
|
+
- **New message keys:** `guidelinesRequired`, `photoUnavailable`,
|
|
270
|
+
`ventureRequired`, `stateRequired`, `stateUnknown`, `goalsDuplicate`,
|
|
271
|
+
`skillsDuplicate`. Each client words them in the same change (a client
|
|
272
|
+
holding its words as `Record<ValidationMessageKey, string>` fails to build
|
|
273
|
+
until it does).
|
|
274
|
+
- **Stricter:**
|
|
275
|
+
- every location WRITE (`completeOnboardingBodySchema`,
|
|
276
|
+
`updateFounderProfileBodySchema`, `updateMyPersonalDetailsBodySchema`, and
|
|
277
|
+
the server's `suggestedLocationSchema`) is `indiaLocationSchema`: country
|
|
278
|
+
`India`, a state from the list (required: `stateRequired`; not on it:
|
|
279
|
+
`stateUnknown`; read in any common spelling, "KARNATAKA", "Orissa",
|
|
280
|
+
"Jammu & Kashmir", and kept as the list's name), a city. READS keep
|
|
281
|
+
`locationSchema`, so a place stored before this is read as it is;
|
|
282
|
+
- `businessType` / `legalStructure` without a `venture` are each refused ON
|
|
283
|
+
THEIR OWN FIELD (`ventureRequired`), on the finish body and the business
|
|
284
|
+
step; the backend used to drop them without a word, and the web dropped a
|
|
285
|
+
lone registration (hary's ruling: no silent drop). `ventureStarted` no
|
|
286
|
+
longer counts the type: it describes a business, it does not start one;
|
|
287
|
+
- `displayNameSchema` stores the name tidied (`tidyFullName`): every body
|
|
288
|
+
that writes a name, the Me patches included;
|
|
289
|
+
- goals and skills each once (`goalsDuplicate`, `skillsDuplicate`), on the
|
|
290
|
+
finish and the Me patch;
|
|
291
|
+
- the guidelines: a box left unticked is `guidelinesRequired` (it was
|
|
292
|
+
`guidelinesOutdated`, the words for an older text).
|
|
293
|
+
- **Breaking:** `completeOnboardingBodySchema` and
|
|
294
|
+
`ONBOARDING_STEP_SCHEMAS.business` carry the venture rule (refinements), so
|
|
295
|
+
Zod 4.6 throws on `.pick`, `.omit`, `.partial` and `.merge` of them, and on
|
|
296
|
+
an `.extend` that overwrites a key. Read a field with `.shape.<field>`, and a
|
|
297
|
+
step's fields with `ONBOARDING_STEP_SCHEMAS.<step>` (picked before the rule;
|
|
298
|
+
the business slice carries it again). The web picked from the finish body in
|
|
299
|
+
three places (the `/dummy` showcase, at module scope, and two tests); they
|
|
300
|
+
read `.shape` now.
|
|
301
|
+
- **Changed type:** `displayNameSchema` is a pipe (`.transform(tidyFullName)`),
|
|
302
|
+
no longer a `ZodString`, so `.min` / `.max` / `.regex` cannot be chained on
|
|
303
|
+
it. Nothing did; a length is read through the pipe.
|
|
304
|
+
- **Breaking (no consumer reads it):** `FOUNDER_ONBOARDING_STEPS[i].helpKey`
|
|
305
|
+
is gone (there is no help line any more; the user, 2026-10-19), and
|
|
306
|
+
`labelKey` names the step's short label.
|
|
307
|
+
- **Moved, same export name:** `suggestedLocationSchema` / `SuggestedLocation`
|
|
308
|
+
now come from `domains/location/india`.
|
|
309
|
+
- **Deploy order: still the backend first.** A 0.16 web is refused a little
|
|
310
|
+
that it used to send, on its field: a place with no state (an old draft's,
|
|
311
|
+
or the 0.16 Me editor saving an OLD stateless place, which it sends without
|
|
312
|
+
one) at `location.state`. (It never sends a lone business type: its
|
|
313
|
+
`ventureStarted` counted the type, so a type on its own went with an empty
|
|
314
|
+
venture, refused at `venture.name` / `industry` / `stage` as on 0.16.) A
|
|
315
|
+
0.17 web needs the 0.17 backend for the photo. It reads the location
|
|
316
|
+
suggestion with the strict write shape, which takes a state in any common
|
|
317
|
+
spelling ("KARNATAKA"), so a 0.16 backend's answer is read unless it has no
|
|
318
|
+
state, or one the list does not know: then it is no suggestion.
|
|
319
|
+
- **Rollback floor (also):** the 0.17 web on a backend rolled below 0.17 saves
|
|
320
|
+
no photo, and shows no suggestion where the old backend answered without a
|
|
321
|
+
state the list knows.
|
|
322
|
+
- **Known, follow-up (hary, 2026-10-19), not in 0.17.0:**
|
|
323
|
+
- a step save racing a finish when the member has NO progress row (the TTL
|
|
324
|
+
deleted it while a tab stayed open) can leave a photo held for a finished
|
|
325
|
+
member, until the TTL takes that new row: the finish's update of a row
|
|
326
|
+
that is not there writes nothing, so the two transactions never conflict.
|
|
327
|
+
Fix: the finish upserts its completion stamp;
|
|
328
|
+
- finish's retry loop sets its result inside the transaction callback, so a
|
|
329
|
+
commit that fails and a retry that then meets a username clash could
|
|
330
|
+
answer success for a profile never committed. Fix: reset the result on
|
|
331
|
+
every attempt. Do both together: an 11000 on that upsert would otherwise
|
|
332
|
+
read as a username clash.
|
|
333
|
+
|
|
102
334
|
## Breaking in 0.16.0: onboarding in nine steps (2026-10-18)
|
|
103
335
|
|
|
104
336
|
The user's rework of onboarding. The finish body takes the same answers plus one.
|
|
@@ -504,8 +736,8 @@ correction of something the backend, the web or the data already disagreed on.
|
|
|
504
736
|
| `discoverFacetsSchema` options | `{ value, label, count }` | `{ value, count }` |
|
|
505
737
|
| `updateUsernameBodySchema` | format only | format, and not reserved |
|
|
506
738
|
|
|
507
|
-
Deprecated,
|
|
508
|
-
`openChatBodySchema`) and `chatInvitationSchema` (describes nothing that
|
|
739
|
+
Deprecated in 0.2.0, kept until 0.18.0 removed them: `chatInvitationBodySchema`
|
|
740
|
+
(use `openChatBodySchema`) and `chatInvitationSchema` (describes nothing that
|
|
509
741
|
exists).
|
|
510
742
|
|
|
511
743
|
---
|
|
@@ -535,25 +767,36 @@ src/
|
|
|
535
767
|
index.ts the public surface. Everything a consumer imports.
|
|
536
768
|
env.ts env field builders, its own entry: `founder-schema/env`
|
|
537
769
|
core/ what every domain builds on (imports no domain)
|
|
538
|
-
limits.ts every length, size and count limit, by name
|
|
770
|
+
limits.ts every length, size and count limit, by name, and India's
|
|
771
|
+
offset and DAY_MS (imports nothing)
|
|
539
772
|
validation.ts message keys, the wire shape of an issue, toValidationIssues
|
|
540
773
|
errors.ts every API error code (API_ERROR_CODE_VALUES)
|
|
541
774
|
common.ts usernames (and the reserved list), the enums, URLs, phone,
|
|
542
|
-
location, coordinates, pagination
|
|
543
|
-
responses.ts success / error / paginated / { id } envelopes
|
|
775
|
+
location, coordinates, pagination, eachOnce, YouTube links
|
|
776
|
+
responses.ts success / error / paginated / { id } envelopes, QueryArgs
|
|
777
|
+
money.ts rupees(): paise as people read them
|
|
544
778
|
access/ who may do what (CASL's words, shared by server and web)
|
|
545
779
|
roles.ts the roles, who may grant which, the active-role window
|
|
546
780
|
permissions.ts actions, subjects, the fields a rule may name, AppAbility
|
|
547
781
|
access.ts GET /me/access, switching role, the MPIN
|
|
548
|
-
|
|
549
|
-
|
|
782
|
+
standing.ts viewer / member (staff count as members), the refusal reasons
|
|
783
|
+
capabilities.ts powers given to selected members (create_communities), who grants them
|
|
784
|
+
domains/<domain>/ one folder per product area: its bodies, params, answers
|
|
785
|
+
and the rules both sides apply
|
|
786
|
+
account/ audit/ billing/ chat/ communities/ contributions/ dashboard/
|
|
787
|
+
discover/ feed/ location/ media/ members/ membership/ moderation/
|
|
788
|
+
notifications/ onboarding/ products/ profile/ reviews/ support/ team/
|
|
789
|
+
uploads/ waitlist/
|
|
550
790
|
tests/
|
|
791
|
+
the domains' own tests, plus:
|
|
551
792
|
ssot.test.ts derived not copied, no body coercion, URLs, uploads, codes
|
|
793
|
+
ssot-0-18.test.ts the 0.18.0 audit: each rule moved here, on the rule
|
|
552
794
|
validation.test.ts keys not sentences, no input leaves, limits applied
|
|
553
795
|
onboarding-steps.test.ts steps, step schemas and the finish body agree
|
|
554
796
|
responses.test.ts the envelopes, pinned
|
|
555
797
|
access.test.ts roles, the grant table, the MPIN, rules across the wire
|
|
556
|
-
public-surface.test.ts export-removal tripwire
|
|
798
|
+
public-surface.test.ts export-removal tripwire, the 0.18.0 removals, and the
|
|
799
|
+
values the database is pinned to
|
|
557
800
|
```
|
|
558
801
|
|
|
559
802
|
A consumer imports from the package root and never from a deep path:
|
|
@@ -591,9 +834,10 @@ cannot see it and the request looks correct in the network tab. Build a write bo
|
|
|
591
834
|
as its own object with no defaults, and have the client send only the keys the
|
|
592
835
|
member actually changed.
|
|
593
836
|
|
|
594
|
-
**A default inside a nested object is the same trap one level down.** A
|
|
595
|
-
|
|
596
|
-
`
|
|
837
|
+
**A default inside a nested object is the same trap one level down.** A write
|
|
838
|
+
body never fills a nested field in: `locationSchema` has no default country
|
|
839
|
+
since it was removed, and a place is WRITTEN through `indiaLocationSchema`,
|
|
840
|
+
whole.
|
|
597
841
|
|
|
598
842
|
**A patch field the member can clear has three states, not two.** Absent means
|
|
599
843
|
do not touch. `null` means clear, and the backend maps it to `$unset`. An empty
|
package/dist/access/access.d.ts
CHANGED
|
@@ -49,7 +49,7 @@ export declare const meAccessResponseSchema: z.ZodObject<{
|
|
|
49
49
|
viewer: "viewer";
|
|
50
50
|
member: "member";
|
|
51
51
|
}>;
|
|
52
|
-
rules: z.ZodArray<z.
|
|
52
|
+
rules: z.ZodArray<z.ZodUnion<readonly [z.ZodTuple<[z.ZodString, z.ZodString], null>, z.ZodTuple<[z.ZodString, z.ZodString, z.ZodUnknown], null>, z.ZodTuple<[z.ZodString, z.ZodString, z.ZodUnknown, z.ZodLiteral<1>], null>, z.ZodTuple<[z.ZodString, z.ZodString, z.ZodUnknown, z.ZodUnion<readonly [z.ZodLiteral<0>, z.ZodLiteral<1>]>, z.ZodString], null>, z.ZodTuple<[z.ZodString, z.ZodString, z.ZodUnknown, z.ZodUnion<readonly [z.ZodLiteral<0>, z.ZodLiteral<1>]>, z.ZodUnion<readonly [z.ZodString, z.ZodLiteral<0>]>, z.ZodString], null>]>>;
|
|
53
53
|
}, z.core.$strip>;
|
|
54
54
|
export type MeAccessResponse = z.infer<typeof meAccessResponseSchema>;
|
|
55
55
|
/** `PUT /me/active-role`. Back to member needs nothing; any staff role needs the MPIN. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../../src/access/access.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB,eAAO,MAAM,WAAW,IAAI,CAAC;AAC7B,yCAAyC;AACzC,eAAO,MAAM,iBAAiB,IAAI,CAAC;AACnC,2CAA2C;AAC3C,eAAO,MAAM,YAAY,QAAiB,CAAC;AAE3C,eAAO,MAAM,UAAU,
|
|
1
|
+
{"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../../src/access/access.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB,eAAO,MAAM,WAAW,IAAI,CAAC;AAC7B,yCAAyC;AACzC,eAAO,MAAM,iBAAiB,IAAI,CAAC;AACnC,2CAA2C;AAC3C,eAAO,MAAM,YAAY,QAAiB,CAAC;AAE3C,eAAO,MAAM,UAAU,aAG0B,CAAC;AAElD,2DAA2D;AAC3D,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;iBAiBjC,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,0FAA0F;AAC1F,eAAO,MAAM,oBAAoB;;;;;;;;;iBAQ7B,CAAC;AACL,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAMlE,oDAAoD;AACpD,eAAO,MAAM,iBAAiB;;;iBAEA,CAAC;AAC/B,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,sEAAsE;AACtE,eAAO,MAAM,oBAAoB;;;;iBAEH,CAAC;AAC/B,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC"}
|
package/dist/access/access.js
CHANGED
|
@@ -30,7 +30,10 @@ export const MPIN_LENGTH = 6;
|
|
|
30
30
|
export const MPIN_MAX_ATTEMPTS = 5;
|
|
31
31
|
/** How long a locked MPIN stays locked. */
|
|
32
32
|
export const MPIN_LOCK_MS = 15 * 60 * 1000;
|
|
33
|
-
export const mpinSchema = z
|
|
33
|
+
export const mpinSchema = z
|
|
34
|
+
.string()
|
|
35
|
+
.length(MPIN_LENGTH, VALIDATION_MESSAGE.mpinInvalid)
|
|
36
|
+
.regex(/^\d+$/, VALIDATION_MESSAGE.mpinInvalid);
|
|
34
37
|
/** The answer to `GET /me/access`, and to every switch. */
|
|
35
38
|
export const meAccessResponseSchema = z.object({
|
|
36
39
|
/** Every role held, member always among them, highest first. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access.js","sourceRoot":"","sources":["../../src/access/access.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC;AAC7B,yCAAyC;AACzC,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC;AACnC,2CAA2C;AAC3C,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAE3C,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"access.js","sourceRoot":"","sources":["../../src/access/access.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC;AAC7B,yCAAyC;AACzC,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC;AACnC,2CAA2C;AAC3C,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAE3C,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC;KACxB,MAAM,EAAE;KACR,MAAM,CAAC,WAAW,EAAE,kBAAkB,CAAC,WAAW,CAAC;KACnD,KAAK,CAAC,OAAO,EAAE,kBAAkB,CAAC,WAAW,CAAC,CAAC;AAElD,2DAA2D;AAC3D,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7C,gEAAgE;IAChE,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACjC,6EAA6E;IAC7E,UAAU,EAAE,UAAU;IACtB,uFAAuF;IACvF,mBAAmB,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAChD,4FAA4F;IAC5F,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;IACpB;;;;OAIG;IACH,QAAQ,EAAE,cAAc;IACxB,wFAAwF;IACxF,KAAK,EAAE,iBAAiB;CACzB,CAAC,CAAC;AAGH,0FAA0F;AAC1F,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC;KAClC,MAAM,CAAC;IACN,IAAI,EAAE,UAAU;IAChB,IAAI,EAAE,UAAU,CAAC,QAAQ,EAAE;CAC5B,CAAC;KACD,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,WAAW,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE;IACtE,IAAI,EAAE,CAAC,MAAM,CAAC;IACd,KAAK,EAAE,kBAAkB,CAAC,YAAY;CACvC,CAAC,CAAC;AAGL,MAAM,SAAS,GAAG,CAAkD,IAAO,EAAE,EAAE,CAC7E,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,WAAW,CAAC;AACjC,MAAM,QAAQ,GAAG,EAAE,IAAI,EAAE,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,kBAAkB,CAAC,YAAY,EAAE,CAAC;AAEnF,oDAAoD;AACpD,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC;KAC/B,MAAM,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC;KACrD,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAG/B,sEAAsE;AACtE,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC;KAClC,MAAM,CAAC,EAAE,WAAW,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC;KAC9E,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC","sourcesContent":["/**\n * founder-schema: the access API\n *\n * GET /api/v1/me/access who I am acting as, what I hold, my rules\n * PUT /api/v1/me/active-role switch role (a staff role needs the MPIN)\n * POST /api/v1/me/mpin set the MPIN, once\n * PUT /api/v1/me/mpin change it, with the current one\n *\n * THE MPIN is a 6-digit code that stands between a signed-in member and their\n * staff roles. Signing in proves who you are; the MPIN proves it is still you\n * at the moment you pick up admin powers, on a phone that may have been left\n * unlocked. Set once by its owner; a forgotten one is cleared by a superadmin\n * (an audited action), after which the owner sets a new one. `MPIN_MAX_ATTEMPTS`\n * wrong tries lock it for `MPIN_LOCK_MS`.\n *\n * THE ACTIVE ROLE is per account (the user's ruling, 2026-10-05): switching on\n * one device switches every device. It lasts `ACTIVE_ROLE_TTL_MS`, and the\n * server re-checks it on EVERY request: a role that was revoked, or that\n * expired, is acting as a member again at once, not at the next sign-in.\n *\n * @module founder-schema/access/access\n */\n\nimport { z } from 'zod';\nimport { VALIDATION_MESSAGE } from '../core/validation.js';\nimport { packedRulesSchema } from './permissions.js';\nimport { MEMBER_ROLE, roleSchema } from './roles.js';\nimport { standingSchema } from './standing.js';\n\nexport const MPIN_LENGTH = 6;\n/** Wrong tries before the MPIN locks. */\nexport const MPIN_MAX_ATTEMPTS = 5;\n/** How long a locked MPIN stays locked. */\nexport const MPIN_LOCK_MS = 15 * 60 * 1000;\n\nexport const mpinSchema = z\n .string()\n .length(MPIN_LENGTH, VALIDATION_MESSAGE.mpinInvalid)\n .regex(/^\\d+$/, VALIDATION_MESSAGE.mpinInvalid);\n\n/** The answer to `GET /me/access`, and to every switch. */\nexport const meAccessResponseSchema = z.object({\n /** Every role held, member always among them, highest first. */\n roles: z.array(roleSchema).min(1),\n /** The role being acted as now. Member when no staff role is switched on. */\n activeRole: roleSchema,\n /** When the active staff role lapses back to member. Null while acting as a member. */\n activeRoleExpiresAt: z.iso.datetime().nullable(),\n /** Whether an MPIN is set, so the switcher knows to ask for one or to offer setting one. */\n mpinSet: z.boolean(),\n /**\n * Whether they take part or only look (0.12.0, `access/standing`). The\n * rules already say what follows from it; this says it plainly, for the\n * places that word it (\"Become a member to take part\").\n */\n standing: standingSchema,\n /** The active role's permissions, packed (`packRules`), refusals with their reasons. */\n rules: packedRulesSchema,\n});\nexport type MeAccessResponse = z.infer<typeof meAccessResponseSchema>;\n\n/** `PUT /me/active-role`. Back to member needs nothing; any staff role needs the MPIN. */\nexport const switchRoleBodySchema = z\n .object({\n role: roleSchema,\n mpin: mpinSchema.optional(),\n })\n .refine((body) => body.role === MEMBER_ROLE || body.mpin !== undefined, {\n path: ['mpin'],\n error: VALIDATION_MESSAGE.mpinRequired,\n });\nexport type SwitchRoleBody = z.infer<typeof switchRoleBodySchema>;\n\nconst confirmed = <T extends { mpin: string; confirmMpin: string }>(body: T) =>\n body.mpin === body.confirmMpin;\nconst mismatch = { path: ['confirmMpin'], error: VALIDATION_MESSAGE.mpinMismatch };\n\n/** `POST /me/mpin`: the first MPIN, typed twice. */\nexport const setMpinBodySchema = z\n .object({ mpin: mpinSchema, confirmMpin: mpinSchema })\n .refine(confirmed, mismatch);\nexport type SetMpinBody = z.infer<typeof setMpinBodySchema>;\n\n/** `PUT /me/mpin`: the current MPIN, then the new one typed twice. */\nexport const changeMpinBodySchema = z\n .object({ currentMpin: mpinSchema, mpin: mpinSchema, confirmMpin: mpinSchema })\n .refine(confirmed, mismatch);\nexport type ChangeMpinBody = z.infer<typeof changeMpinBodySchema>;\n"]}
|
|
@@ -15,9 +15,11 @@
|
|
|
15
15
|
*
|
|
16
16
|
* @module founder-schema/access/permissions
|
|
17
17
|
*/
|
|
18
|
-
import type { ForcedSubject, MongoAbility } from '@casl/ability';
|
|
18
|
+
import type { ForcedSubject, MongoAbility, RawRuleOf } from '@casl/ability';
|
|
19
19
|
import { z } from 'zod';
|
|
20
|
-
import type { PurchaseKind } from '../domains/billing/billing.js';
|
|
20
|
+
import type { OrderStatus, PurchaseKind } from '../domains/billing/billing.js';
|
|
21
|
+
import type { CommunitySuggestionStatus } from '../domains/communities/communities.js';
|
|
22
|
+
import type { ReportStatus } from '../domains/feed/feed.js';
|
|
21
23
|
import type { PlatformRole } from './roles.js';
|
|
22
24
|
import type { Standing } from './standing.js';
|
|
23
25
|
/**
|
|
@@ -48,26 +50,7 @@ import type { Standing } from './standing.js';
|
|
|
48
50
|
* Append only: a stored or packed rule names these.
|
|
49
51
|
*/
|
|
50
52
|
export declare const ACTION_VALUES: readonly ["manage", "create", "read", "update", "delete", "moderate", "assign", "suspend", "resolve", "reset", "represent", "react", "vote", "save", "join", "message", "refund"];
|
|
51
|
-
export
|
|
52
|
-
join: "join";
|
|
53
|
-
message: "message";
|
|
54
|
-
manage: "manage";
|
|
55
|
-
create: "create";
|
|
56
|
-
read: "read";
|
|
57
|
-
update: "update";
|
|
58
|
-
delete: "delete";
|
|
59
|
-
moderate: "moderate";
|
|
60
|
-
assign: "assign";
|
|
61
|
-
suspend: "suspend";
|
|
62
|
-
resolve: "resolve";
|
|
63
|
-
reset: "reset";
|
|
64
|
-
represent: "represent";
|
|
65
|
-
react: "react";
|
|
66
|
-
vote: "vote";
|
|
67
|
-
save: "save";
|
|
68
|
-
refund: "refund";
|
|
69
|
-
}>;
|
|
70
|
-
export type Action = z.infer<typeof actionSchema>;
|
|
53
|
+
export type Action = (typeof ACTION_VALUES)[number];
|
|
71
54
|
/**
|
|
72
55
|
* The things.
|
|
73
56
|
*
|
|
@@ -160,7 +143,7 @@ export interface SubjectFields {
|
|
|
160
143
|
};
|
|
161
144
|
Report: {
|
|
162
145
|
reporterId: string;
|
|
163
|
-
status:
|
|
146
|
+
status: ReportStatus;
|
|
164
147
|
};
|
|
165
148
|
SupportTicket: {
|
|
166
149
|
memberId: string;
|
|
@@ -205,7 +188,7 @@ export interface SubjectFields {
|
|
|
205
188
|
/** `suggestedBy` so "your own" is a rule; `status` so only open ones are decided. */
|
|
206
189
|
CommunitySuggestion: {
|
|
207
190
|
suggestedBy: string;
|
|
208
|
-
status:
|
|
191
|
+
status: CommunitySuggestionStatus;
|
|
209
192
|
};
|
|
210
193
|
/** Whose membership (0.12.0), so "never your own" is a rule. */
|
|
211
194
|
Membership: {
|
|
@@ -218,7 +201,7 @@ export interface SubjectFields {
|
|
|
218
201
|
*/
|
|
219
202
|
Payment: {
|
|
220
203
|
userId: string;
|
|
221
|
-
status:
|
|
204
|
+
status: OrderStatus;
|
|
222
205
|
kind: PurchaseKind;
|
|
223
206
|
};
|
|
224
207
|
/** Coupons are the platform's (0.12.0): no condition is ever written on one. */
|
|
@@ -241,11 +224,26 @@ type Same<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false;
|
|
|
241
224
|
type Assert<T extends true> = T;
|
|
242
225
|
export type EverySubjectHasFields = Assert<Same<keyof SubjectFields, Exclude<Subject, 'all'>>>;
|
|
243
226
|
/**
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
227
|
+
* One packed rule, in exactly the shapes CASL's `PackRule` takes (a union of
|
|
228
|
+
* tuples, not a rest: `unpackRules` needs one of these), with `conditions` as
|
|
229
|
+
* `unknown`, which `isAppRawRule` and the ability itself then judge.
|
|
247
230
|
*/
|
|
248
|
-
export declare const
|
|
231
|
+
export declare const packedRuleSchema: z.ZodUnion<readonly [z.ZodTuple<[z.ZodString, z.ZodString], null>, z.ZodTuple<[z.ZodString, z.ZodString, z.ZodUnknown], null>, z.ZodTuple<[z.ZodString, z.ZodString, z.ZodUnknown, z.ZodLiteral<1>], null>, z.ZodTuple<[z.ZodString, z.ZodString, z.ZodUnknown, z.ZodUnion<readonly [z.ZodLiteral<0>, z.ZodLiteral<1>]>, z.ZodString], null>, z.ZodTuple<[z.ZodString, z.ZodString, z.ZodUnknown, z.ZodUnion<readonly [z.ZodLiteral<0>, z.ZodLiteral<1>]>, z.ZodUnion<readonly [z.ZodString, z.ZodLiteral<0>]>, z.ZodString], null>]>;
|
|
232
|
+
export declare const packedRulesSchema: z.ZodArray<z.ZodUnion<readonly [z.ZodTuple<[z.ZodString, z.ZodString], null>, z.ZodTuple<[z.ZodString, z.ZodString, z.ZodUnknown], null>, z.ZodTuple<[z.ZodString, z.ZodString, z.ZodUnknown, z.ZodLiteral<1>], null>, z.ZodTuple<[z.ZodString, z.ZodString, z.ZodUnknown, z.ZodUnion<readonly [z.ZodLiteral<0>, z.ZodLiteral<1>]>, z.ZodString], null>, z.ZodTuple<[z.ZodString, z.ZodString, z.ZodUnknown, z.ZodUnion<readonly [z.ZodLiteral<0>, z.ZodLiteral<1>]>, z.ZodUnion<readonly [z.ZodString, z.ZodLiteral<0>]>, z.ZodString], null>]>>;
|
|
249
233
|
export type PackedRules = z.infer<typeof packedRulesSchema>;
|
|
234
|
+
/** One rule as CASL's `unpackRules` gives it back, typed for this app. */
|
|
235
|
+
export type AppRawRule = RawRuleOf<AppAbility>;
|
|
236
|
+
/**
|
|
237
|
+
* Whether an UNPACKED rule names only this app's actions and subjects
|
|
238
|
+
* (0.18.0). Packed rules cross the wire as strings (`packRules` joins several
|
|
239
|
+
* actions or subjects with commas, so the wire shape cannot be an enum); this
|
|
240
|
+
* is the check that turns them back into `AppRawRule`s, with no cast:
|
|
241
|
+
* `rules.every(isAppRawRule)` narrows the list, and a list that fails is a
|
|
242
|
+
* contract mismatch, never an ability built from it.
|
|
243
|
+
*/
|
|
244
|
+
export declare function isAppRawRule(rule: {
|
|
245
|
+
action: string | string[];
|
|
246
|
+
subject?: string | string[];
|
|
247
|
+
}): rule is AppRawRule;
|
|
250
248
|
export {};
|
|
251
249
|
//# sourceMappingURL=permissions.d.ts.map
|
|
@@ -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,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../src/access/permissions.ts"],"names":[],"mappings":"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"}
|
|
@@ -62,7 +62,6 @@ export const ACTION_VALUES = [
|
|
|
62
62
|
'message',
|
|
63
63
|
'refund',
|
|
64
64
|
];
|
|
65
|
-
export const actionSchema = z.enum(ACTION_VALUES);
|
|
66
65
|
/**
|
|
67
66
|
* The things.
|
|
68
67
|
*
|
|
@@ -111,7 +110,44 @@ export const subjectSchema = z.enum(SUBJECT_VALUES);
|
|
|
111
110
|
/**
|
|
112
111
|
* Rules as they cross the wire: CASL's `packRules` output, an array of
|
|
113
112
|
* compact arrays. The web unpacks them with `unpackRules` before building its
|
|
114
|
-
* ability
|
|
113
|
+
* ability. Each starts with its action(s) and subject(s), comma-joined
|
|
114
|
+
* strings (checked, 0.18.0: an unchecked rule list could silently turn staff
|
|
115
|
+
* routing off); what follows (conditions, inverted, fields, reason) is CASL's
|
|
116
|
+
* own compact form.
|
|
115
117
|
*/
|
|
116
|
-
|
|
118
|
+
const zeroOrOne = z.union([z.literal(0), z.literal(1)]);
|
|
119
|
+
/**
|
|
120
|
+
* One packed rule, in exactly the shapes CASL's `PackRule` takes (a union of
|
|
121
|
+
* tuples, not a rest: `unpackRules` needs one of these), with `conditions` as
|
|
122
|
+
* `unknown`, which `isAppRawRule` and the ability itself then judge.
|
|
123
|
+
*/
|
|
124
|
+
export const packedRuleSchema = z.union([
|
|
125
|
+
z.tuple([z.string().min(1), z.string().min(1)]),
|
|
126
|
+
z.tuple([z.string().min(1), z.string().min(1), z.unknown()]),
|
|
127
|
+
z.tuple([z.string().min(1), z.string().min(1), z.unknown(), z.literal(1)]),
|
|
128
|
+
z.tuple([z.string().min(1), z.string().min(1), z.unknown(), zeroOrOne, z.string()]),
|
|
129
|
+
z.tuple([
|
|
130
|
+
z.string().min(1),
|
|
131
|
+
z.string().min(1),
|
|
132
|
+
z.unknown(),
|
|
133
|
+
zeroOrOne,
|
|
134
|
+
z.union([z.string(), z.literal(0)]),
|
|
135
|
+
z.string(),
|
|
136
|
+
]),
|
|
137
|
+
]);
|
|
138
|
+
export const packedRulesSchema = z.array(packedRuleSchema);
|
|
139
|
+
const ACTION_SET = new Set(ACTION_VALUES);
|
|
140
|
+
const SUBJECT_SET = new Set(SUBJECT_VALUES);
|
|
141
|
+
const everyIn = (value, known) => value !== undefined && (Array.isArray(value) ? value : [value]).every((v) => known.has(v));
|
|
142
|
+
/**
|
|
143
|
+
* Whether an UNPACKED rule names only this app's actions and subjects
|
|
144
|
+
* (0.18.0). Packed rules cross the wire as strings (`packRules` joins several
|
|
145
|
+
* actions or subjects with commas, so the wire shape cannot be an enum); this
|
|
146
|
+
* is the check that turns them back into `AppRawRule`s, with no cast:
|
|
147
|
+
* `rules.every(isAppRawRule)` narrows the list, and a list that fails is a
|
|
148
|
+
* contract mismatch, never an ability built from it.
|
|
149
|
+
*/
|
|
150
|
+
export function isAppRawRule(rule) {
|
|
151
|
+
return everyIn(rule.action, ACTION_SET) && everyIn(rule.subject, SUBJECT_SET);
|
|
152
|
+
}
|
|
117
153
|
//# sourceMappingURL=permissions.js.map
|