@better-giving/endowment 1.1.13 → 1.1.15

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/src/schema.mts CHANGED
@@ -18,211 +18,178 @@ export {
18
18
  donateMethodId,
19
19
  donateMethodIds,
20
20
  };
21
- import {
22
- url,
23
- type InferInput,
24
- type InferOutput,
25
- array,
26
- base64,
27
- boolean,
28
- check,
29
- custom,
30
- excludes,
31
- filterItems,
32
- integer,
33
- isoTimestamp,
34
- keyof,
35
- lazy,
36
- literal,
37
- maxLength,
38
- maxValue,
39
- minLength,
40
- minValue,
41
- nonEmpty,
42
- nullish,
43
- number,
44
- object,
45
- omit,
46
- optional,
47
- partial,
48
- picklist,
49
- pipe,
50
- regex,
51
- startsWith,
52
- string,
53
- transform,
54
- trim,
55
- union,
56
- uuid,
57
- } from "valibot";
58
-
59
- export const str = pipe(string(), trim());
60
- export const csv = lazy((x) => {
21
+ import * as v from "valibot";
22
+
23
+ export const str = v.pipe(v.string(), v.trim());
24
+ export const csv = v.lazy((x) => {
61
25
  if (!x) return str;
62
- return pipe(str, regex(/^[^,]+(?:,[^,]+)*$/, "invalid csv"));
26
+ return v.pipe(str, v.regex(/^[^,]+(?:,[^,]+)*$/, "invalid csv"));
63
27
  });
64
- export const csvStrs = pipe(
28
+ export const csvStrs = v.pipe(
65
29
  csv,
66
- transform((x) => x.split(",")),
67
- filterItems((x) => x.length > 0)
30
+ v.transform((x) => x.split(",")),
31
+ v.filterItems((x) => x.length > 0)
68
32
  );
69
33
 
70
- const pct = pipe(number(), integer(), minValue(0), maxValue(100));
71
- export const allocation = pipe(
72
- object({
34
+ const pct = v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(100));
35
+ export const allocation = v.pipe(
36
+ v.object({
73
37
  cash: pct,
74
38
  liq: pct,
75
39
  lock: pct,
76
40
  }),
77
- check((x) => x.cash + x.liq + x.lock === 100, "must total to 100")
41
+ v.check((x) => x.cash + x.liq + x.lock === 100, "must total to 100")
78
42
  );
79
43
 
80
- export interface Allocation extends InferOutput<typeof allocation> {}
44
+ export interface Allocation extends v.InferOutput<typeof allocation> {}
81
45
 
82
- export const endowId = pipe(number(), integer(), minValue(1));
83
- export type EndowId = InferOutput<typeof endowId>;
84
- export const endowIdParam = pipe(
85
- string(),
86
- transform((x) => +x),
87
- endowId
46
+ export const int_id = v.pipe(v.number(), v.integer(), v.minValue(1));
47
+ export type EndowId = v.InferOutput<typeof int_id>;
48
+ export const endowIdParam = v.pipe(
49
+ v.string(),
50
+ v.transform((x) => +x),
51
+ int_id
88
52
  );
89
53
 
90
54
  export const segmentMaxChars = 30;
91
- export const segment = pipe(
55
+ export const segment = v.pipe(
92
56
  str,
93
- maxLength(
57
+ v.maxLength(
94
58
  segmentMaxChars,
95
59
  ({ requirement: r }) => `cannot exceed ${r} chars`
96
60
  ),
97
61
  //must not be id-like
98
- regex(/^(?!^\d+$)/, "should not be an id"),
62
+ v.regex(/^(?!^\d+$)/, "should not be an id"),
99
63
  //valid characters
100
- regex(/^[a-zA-Z0-9-._~]+$/, "allowed: numbers | letters | - | . | _ | ~"),
101
- excludes("..", "should not contain double periods"),
102
- custom((x) => !(x as string).startsWith("."), "should not start with dot"),
103
- custom((x) => !(x as string).endsWith("."), "should not end with dot")
64
+ v.regex(/^[a-zA-Z0-9-._~]+$/, "allowed: numbers | letters | - | . | _ | ~"),
65
+ v.excludes("..", "should not contain double periods"),
66
+ v.custom((x) => !(x as string).startsWith("."), "should not start with dot"),
67
+ v.custom((x) => !(x as string).endsWith("."), "should not end with dot")
104
68
  );
105
69
 
106
- export const slug = lazy((x) => (x ? segment : str));
70
+ export const slug = v.lazy((x) => (x ? segment : str));
107
71
 
108
- export const reg_number = pipe(
72
+ export const reg_number = v.pipe(
109
73
  str,
110
- nonEmpty("required"),
111
- regex(/^[a-zA-Z0-9]+$/, "must only contain letters and numbers")
74
+ v.nonEmpty("required"),
75
+ v.regex(/^[a-zA-Z0-9]+$/, "must only contain letters and numbers")
112
76
  );
113
77
 
114
- export const _url = pipe(str, url());
78
+ export const _url = v.pipe(str, v.url());
115
79
 
116
- export const httpsUrl = pipe(
80
+ export const httpsUrl = v.pipe(
117
81
  str,
118
- startsWith("https://", "should start with https://"),
119
- custom((x) => x !== "https://", "incomplete url"),
120
- url("invalid url")
82
+ v.startsWith("https://", "should start with https://"),
83
+ v.custom((x) => x !== "https://", "incomplete url"),
84
+ v.url("invalid url")
121
85
  );
122
86
 
123
- export const maybeEmptyHttpsUrl = lazy((x) => {
87
+ export const maybeEmptyHttpsUrl = v.lazy((x) => {
124
88
  if (!x) return str;
125
89
  return httpsUrl;
126
90
  });
127
91
 
128
- export const social_media_urls = object({
129
- facebook: optional(maybeEmptyHttpsUrl),
130
- twitter: optional(maybeEmptyHttpsUrl),
131
- linkedin: optional(maybeEmptyHttpsUrl),
132
- discord: optional(maybeEmptyHttpsUrl),
133
- instagram: optional(maybeEmptyHttpsUrl),
134
- youtube: optional(maybeEmptyHttpsUrl),
135
- tiktok: optional(maybeEmptyHttpsUrl),
92
+ export const social_media_urls = v.object({
93
+ facebook: v.optional(maybeEmptyHttpsUrl),
94
+ twitter: v.optional(maybeEmptyHttpsUrl),
95
+ linkedin: v.optional(maybeEmptyHttpsUrl),
96
+ discord: v.optional(maybeEmptyHttpsUrl),
97
+ instagram: v.optional(maybeEmptyHttpsUrl),
98
+ youtube: v.optional(maybeEmptyHttpsUrl),
99
+ tiktok: v.optional(maybeEmptyHttpsUrl),
136
100
  });
137
101
 
138
102
  export interface SocialMediaURLs
139
- extends InferOutput<typeof social_media_urls> {}
103
+ extends v.InferOutput<typeof social_media_urls> {}
140
104
 
141
105
  export const MAX_RECEIPT_MSG_CHAR = 500;
142
106
 
143
- export const incrementVal = pipe(
107
+ export const incrementVal = v.pipe(
144
108
  str,
145
- nonEmpty("required"),
146
- transform((x) => +x),
147
- number("must be a number"),
148
- minValue(0, "must be greater than 0"),
109
+ v.nonEmpty("required"),
110
+ v.transform((x) => +x),
111
+ v.number("must be a number"),
112
+ v.minValue(0, "must be greater than 0"),
149
113
  //parsed output
150
- transform((x) => x.toString())
114
+ v.transform((x) => x.toString())
151
115
  );
152
116
 
153
117
  export const MAX_NUM_INCREMENTS = 4;
154
118
  export const incrementLabelMaxChars = 60;
155
119
  export const taglineMaxChars = 140;
156
120
 
157
- export const incrementLabel = pipe(
121
+ export const incrementLabel = v.pipe(
158
122
  str,
159
- maxLength(
123
+ v.maxLength(
160
124
  incrementLabelMaxChars,
161
125
  ({ requirement: r }) => `cannot exceed ${r} characters`
162
126
  )
163
127
  );
164
128
 
165
- export const increment = object({
129
+ export const increment = v.object({
166
130
  value: incrementVal,
167
131
  label: incrementLabel,
168
132
  });
169
133
 
170
- export interface Increment extends InferOutput<typeof increment> {}
134
+ export interface Increment extends v.InferOutput<typeof increment> {}
171
135
 
172
- export const endowment = object({
173
- id: endowId,
136
+ export const endowment = v.object({
137
+ id: int_id,
174
138
  env,
175
- slug: optional(slug),
139
+ slug: v.optional(slug),
176
140
  registration_number: reg_number,
177
- name: pipe(str, nonEmpty("required")),
141
+ name: v.pipe(str, v.nonEmpty("required")),
178
142
  endow_designation: orgDesignation,
179
- overview: optional(str),
180
- tagline: optional(pipe(str, maxLength(taglineMaxChars))),
181
- image: optional(_url),
182
- logo: optional(_url),
183
- card_img: optional(_url),
184
- hq_country: pipe(str, nonEmpty("required")),
185
- active_in_countries: pipe(array(str), nonEmpty("required")),
186
- street_address: optional(str),
143
+ overview: v.optional(str),
144
+ tagline: v.optional(v.pipe(str, v.maxLength(taglineMaxChars))),
145
+ image: v.optional(_url),
146
+ logo: v.optional(_url),
147
+ card_img: v.optional(_url),
148
+ hq_country: v.pipe(str, v.nonEmpty("required")),
149
+ active_in_countries: v.pipe(v.array(str), v.nonEmpty("required")),
150
+ street_address: v.optional(str),
187
151
  social_media_urls,
188
152
  /** website */
189
- url: optional(maybeEmptyHttpsUrl),
190
- sdgs: pipe(array(unSdgNum), minLength(1)),
191
- receiptMsg: optional(pipe(str, maxLength(MAX_RECEIPT_MSG_CHAR))),
153
+ url: v.optional(maybeEmptyHttpsUrl),
154
+ sdgs: v.pipe(v.array(unSdgNum), v.minLength(1)),
155
+ receiptMsg: v.optional(v.pipe(str, v.maxLength(MAX_RECEIPT_MSG_CHAR))),
192
156
  //can be optional, default false and need not be explicit
193
- hide_bg_tip: optional(boolean()),
194
- published: optional(boolean()),
157
+ hide_bg_tip: v.optional(v.boolean()),
158
+ published: v.optional(v.boolean()),
195
159
  /** allowed by default */
196
- progDonationsAllowed: optional(boolean()),
197
-
198
- allocation: optional(allocation),
199
- donateMethods: optional(array(donateMethodId)),
200
- increments: optional(pipe(array(increment), maxLength(MAX_NUM_INCREMENTS))),
201
- fund_opt_in: optional(boolean()),
202
- target: optional(
203
- union([
204
- literal("smart"),
160
+ progDonationsAllowed: v.optional(v.boolean()),
161
+
162
+ allocation: v.optional(allocation),
163
+ donateMethods: v.optional(v.array(donateMethodId)),
164
+ increments: v.optional(
165
+ v.pipe(v.array(increment), v.maxLength(MAX_NUM_INCREMENTS))
166
+ ),
167
+ fund_opt_in: v.optional(v.boolean()),
168
+ target: v.optional(
169
+ v.union([
170
+ v.literal("smart"),
205
171
  // "0" - none, `${number}` - fixed
206
- pipe(
207
- string(),
208
- transform((v) => +v),
209
- number(),
210
- minValue(0),
211
- transform((v) => v.toString())
172
+ v.pipe(
173
+ v.string(),
174
+ v.transform((v) => +v),
175
+ v.number(),
176
+ v.minValue(0),
177
+ v.transform((v) => v.toString())
212
178
  ),
213
179
  ])
214
180
  ),
215
181
  /** endowment is not claimed if `false` only */
216
- claimed: boolean(),
217
- kyc_donors_only: boolean(),
218
- fiscal_sponsored: boolean(),
219
- referral_id: optional(pipe(str, nonEmpty("required"))),
220
- referrer: optional(pipe(str, nonEmpty("required"))),
221
- referrer_expiry: optional(pipe(str, isoTimestamp())),
182
+ claimed: v.boolean(),
183
+ kyc_donors_only: v.boolean(),
184
+ fiscal_sponsored: v.boolean(),
185
+ referral_id: v.optional(v.pipe(str, v.nonEmpty("required"))),
186
+ referrer: v.optional(v.pipe(str, v.nonEmpty("required"))),
187
+ referrer_expiry: v.optional(v.pipe(str, v.isoTimestamp())),
188
+ w_form: v.optional(v.string()),
222
189
  });
223
190
 
224
- export const endowUpdate = partial(
225
- omit(endowment, [
191
+ export const endowUpdate = v.partial(
192
+ v.omit(endowment, [
226
193
  "id",
227
194
  "claimed",
228
195
  "kyc_donors_only",
@@ -231,98 +198,101 @@ export const endowUpdate = partial(
231
198
  ])
232
199
  );
233
200
 
234
- export const endowFields = keyof(endowment);
235
- export interface Endowment extends InferOutput<typeof endowment> {}
236
- export interface EndowUpdate extends InferOutput<typeof endowUpdate> {}
237
- export type EndowFields = InferOutput<typeof endowFields>;
201
+ export const endowFields = v.keyof(endowment);
202
+ export interface Endowment extends v.InferOutput<typeof endowment> {}
203
+ export interface EndowUpdate extends v.InferOutput<typeof endowUpdate> {}
204
+ export type EndowFields = v.InferOutput<typeof endowFields>;
238
205
 
239
206
  /** for ein path, only fields in reg-num/env gsi is available */
240
- export const endowQueryParams = object({
241
- fields: optional(pipe(csvStrs, array(endowFields))),
207
+ export const endowQueryParams = v.object({
208
+ fields: v.optional(v.pipe(csvStrs, v.array(endowFields))),
242
209
  });
243
210
 
244
- export interface EndowQueryParams extends InferInput<typeof endowQueryParams> {}
211
+ export interface EndowQueryParams
212
+ extends v.InferInput<typeof endowQueryParams> {}
245
213
 
246
- const amnt = pipe(number(), minValue(0));
247
- export const programId = pipe(str, uuid());
248
- export type ProgramId = InferOutput<typeof programId>;
249
- export const milestoneId = pipe(str, uuid());
250
- export type MilestoneId = InferOutput<typeof milestoneId>;
214
+ const amnt = v.pipe(v.number(), v.minValue(0));
215
+ export const programId = v.pipe(str, v.uuid());
216
+ export type ProgramId = v.InferOutput<typeof programId>;
217
+ export const milestoneId = v.pipe(str, v.uuid());
218
+ export type MilestoneId = v.InferOutput<typeof milestoneId>;
251
219
 
252
- export const newMilestone = object({
253
- date: pipe(str, isoTimestamp()),
220
+ export const newMilestone = v.object({
221
+ date: v.pipe(str, v.isoTimestamp()),
254
222
  title: str,
255
223
  description: str,
256
- media: optional(_url),
224
+ media: v.optional(_url),
257
225
  });
258
226
 
259
- export const milestoneUpdate = partial(newMilestone, ["date"]);
260
- export interface MilestoneUpdate extends InferOutput<typeof milestoneUpdate> {}
227
+ export const milestoneUpdate = v.partial(newMilestone, ["date"]);
228
+ export interface MilestoneUpdate
229
+ extends v.InferOutput<typeof milestoneUpdate> {}
261
230
 
262
- export interface NewMilestone extends InferOutput<typeof newMilestone> {}
263
- export const milestone = object({ ...newMilestone.entries, id: milestoneId });
264
- export interface Milestone extends InferOutput<typeof milestone> {}
231
+ export interface NewMilestone extends v.InferOutput<typeof newMilestone> {}
232
+ export const milestone = v.object({ ...newMilestone.entries, id: milestoneId });
233
+ export interface Milestone extends v.InferOutput<typeof milestone> {}
265
234
 
266
- export const newProgram = object({
235
+ export const newProgram = v.object({
267
236
  title: str,
268
237
  description: str,
269
- banner: optional(_url),
238
+ banner: v.optional(_url),
270
239
  /** null unsets target */
271
- targetRaise: nullish(amnt),
272
- milestones: pipe(array(newMilestone), maxLength(24)),
240
+ targetRaise: v.nullish(amnt),
241
+ milestones: v.pipe(v.array(newMilestone), v.maxLength(24)),
273
242
  });
274
243
 
275
- export const program = object({
276
- ...omit(newProgram, ["milestones"]).entries,
244
+ export const program = v.object({
245
+ ...v.omit(newProgram, ["milestones"]).entries,
277
246
  /** in USD */
278
- totalDonations: optional(number()),
247
+ totalDonations: v.optional(v.number()),
279
248
  id: programId,
280
249
  });
281
250
 
282
- export const programUpdate = partial(omit(newProgram, ["milestones"]));
251
+ export const programUpdate = v.partial(v.omit(newProgram, ["milestones"]));
283
252
 
284
- export interface NewProgram extends InferOutput<typeof newProgram> {}
285
- export interface Program extends InferOutput<typeof program> {}
286
- export interface ProgramUpdate extends InferOutput<typeof programUpdate> {}
253
+ export interface NewProgram extends v.InferOutput<typeof newProgram> {}
254
+ export interface Program extends v.InferOutput<typeof program> {}
255
+ export interface ProgramUpdate extends v.InferOutput<typeof programUpdate> {}
287
256
 
288
- export const mediaUrl = pipe(str, url());
257
+ export const mediaUrl = v.pipe(str, v.url());
289
258
  /**
290
259
  * so that media is automatically sorted by date
291
260
  * @see https://github.com/segmentio/ksuid
292
261
  * */
293
- export const mediaKsuid = pipe(str, nonEmpty() /** base62? */);
262
+ export const mediaKsuid = v.pipe(str, v.nonEmpty() /** base62? */);
294
263
  export const mediaTypes = ["album", "article", "video"] as const;
295
- export const mediaType = picklist(mediaTypes);
296
- export type MediaType = InferOutput<typeof mediaType>;
264
+ export const mediaType = v.picklist(mediaTypes);
265
+ export type MediaType = v.InferOutput<typeof mediaType>;
297
266
 
298
- export const mediaUpdate = object({
299
- url: optional(mediaUrl),
300
- featured: optional(boolean()),
267
+ export const mediaUpdate = v.object({
268
+ url: v.optional(mediaUrl),
269
+ featured: v.optional(v.boolean()),
301
270
  });
302
- export interface MediaUpdate extends InferOutput<typeof mediaUpdate> {}
271
+ export interface MediaUpdate extends v.InferOutput<typeof mediaUpdate> {}
303
272
 
304
- export type MediaId = InferOutput<typeof mediaKsuid>;
273
+ export type MediaId = v.InferOutput<typeof mediaKsuid>;
305
274
 
306
- export const mediaQueryParams = object({
307
- type: optional(mediaType),
308
- nextPageKey: optional(pipe(str, base64())),
309
- featured: optional(
310
- pipe(
275
+ export const mediaQueryParams = v.object({
276
+ type: v.optional(mediaType),
277
+ nextPageKey: v.optional(v.pipe(str, v.base64())),
278
+ featured: v.optional(
279
+ v.pipe(
311
280
  str,
312
- transform((x) => Boolean(x)),
313
- boolean()
281
+ v.transform((x) => Boolean(x)),
282
+ v.boolean()
314
283
  )
315
284
  ),
316
- limit: optional(
317
- pipe(
318
- string(),
319
- transform((x) => +x),
320
- number(),
321
- integer(),
322
- minValue(1)
285
+ limit: v.optional(
286
+ v.pipe(
287
+ v.string(),
288
+ v.transform((x) => +x),
289
+ v.number(),
290
+ v.integer(),
291
+ v.minValue(1)
323
292
  )
324
293
  ),
325
294
  });
326
- export interface MediaQueryParams extends InferInput<typeof mediaQueryParams> {}
295
+ export interface MediaQueryParams
296
+ extends v.InferInput<typeof mediaQueryParams> {}
327
297
  export interface MediaQueryParamsObj
328
- extends InferOutput<typeof mediaQueryParams> {}
298
+ extends v.InferOutput<typeof mediaQueryParams> {}