@thejob/schema 2.1.2 → 2.1.4

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/dist/index.js CHANGED
@@ -1985,6 +1985,14 @@ var JobAlertFrequency = /* @__PURE__ */ ((JobAlertFrequency2) => {
1985
1985
  return JobAlertFrequency2;
1986
1986
  })(JobAlertFrequency || {});
1987
1987
  var SupportedJobAlertFrequencies = Object.values(JobAlertFrequency);
1988
+ var NotificationChannel = /* @__PURE__ */ ((NotificationChannel2) => {
1989
+ NotificationChannel2["GroupActivity"] = "groupActivity";
1990
+ NotificationChannel2["JobActivity"] = "jobActivity";
1991
+ NotificationChannel2["ApplicationUpdates"] = "applicationUpdates";
1992
+ NotificationChannel2["ProductUpdates"] = "productUpdates";
1993
+ return NotificationChannel2;
1994
+ })(NotificationChannel || {});
1995
+ var SupportedNotificationChannels = Object.values(NotificationChannel);
1988
1996
  var SupportedSalaryCurrencies = ["USD", "EUR", "GBP", "SEK", "INR"];
1989
1997
  var MIN_SALARY_LOWER_BOUND = 0;
1990
1998
  var MIN_SALARY_UPPER_BOUND = 1e6;
@@ -2119,8 +2127,69 @@ var DefaultPaginationOptions = {
2119
2127
  limit: 10
2120
2128
  };
2121
2129
 
2130
+ // src/saved-search/saved-search.schema.ts
2131
+ import { number as number9, object as object20, string as string17 } from "yup";
2132
+
2133
+ // src/saved-search/saved-search.constant.ts
2134
+ var SavedSearchStatus = /* @__PURE__ */ ((SavedSearchStatus2) => {
2135
+ SavedSearchStatus2["Active"] = "active";
2136
+ SavedSearchStatus2["Paused"] = "paused";
2137
+ return SavedSearchStatus2;
2138
+ })(SavedSearchStatus || {});
2139
+ var SupportedSavedSearchStatuses = Object.values(SavedSearchStatus);
2140
+
2141
+ // src/saved-search/saved-search.schema.ts
2142
+ var SavedSearchSchema = object20({
2143
+ /** Public identifier. Server-assigned. */
2144
+ shortId: string17().trim().label("Short Id"),
2145
+ /**
2146
+ * Owner's Better Auth account id. Server-assigned from the verified token,
2147
+ * never accepted from a request body.
2148
+ */
2149
+ ownerUserId: string17().trim().label("Owner"),
2150
+ /** What the user calls this search, e.g. "Remote React roles". */
2151
+ label: string17().trim().max(120).required().label("Label"),
2152
+ /**
2153
+ * The free-text query. Optional because a search can be filters-only ("all
2154
+ * remote jobs in Sweden"), which is a legitimate alert to want.
2155
+ */
2156
+ search: string17().trim().max(500).optional().label("Search Text"),
2157
+ /**
2158
+ * Field filters, in the same `{ field: values }` shape the search endpoint
2159
+ * parses from `filter[field]=a,b`. Held as a free-form object rather than a
2160
+ * closed schema because the job facets it addresses live in jobs-service and
2161
+ * change independently; validating them here would put this package in the
2162
+ * business of tracking that vocabulary.
2163
+ */
2164
+ filter: object20().optional().default({}).label("Filters"),
2165
+ /**
2166
+ * How often to mail matches. `off` keeps the search saved but silent, which is
2167
+ * the difference between "I don't want mail" and "delete my search".
2168
+ *
2169
+ * Reuses the cadence enum from `marketingConsent.jobAlertFrequency` rather
2170
+ * than defining a second one: two vocabularies for the same concept is how
2171
+ * they drift.
2172
+ */
2173
+ frequency: string17().oneOf(SupportedJobAlertFrequencies).default("off" /* Off */).label("Alert Frequency"),
2174
+ status: string17().oneOf(SupportedSavedSearchStatuses).default("active" /* Active */).label("Status"),
2175
+ /**
2176
+ * Epoch ms of the last alert sent for this search. The matching sweep reads it
2177
+ * to decide whether the cadence is due, and writes it only AFTER a successful
2178
+ * publish, so a failed run retries next tick rather than silently skipping a
2179
+ * period.
2180
+ */
2181
+ lastNotifiedAt: number9().optional().label("Last Notified At"),
2182
+ /**
2183
+ * Epoch ms high-water mark: only jobs created after this are "new" for the
2184
+ * next alert. Kept separate from `lastNotifiedAt` because a run that finds no
2185
+ * matches should still advance what counts as new, without implying mail was
2186
+ * sent.
2187
+ */
2188
+ lastMatchedAt: number9().optional().label("Last Matched At")
2189
+ }).noUnknown().strict();
2190
+
2122
2191
  // src/report/report.schema.ts
2123
- import { mixed as mixed4, object as object20, string as string17 } from "yup";
2192
+ import { mixed as mixed4, object as object21, string as string18 } from "yup";
2124
2193
 
2125
2194
  // src/report/report.constant.ts
2126
2195
  var ResourceType = /* @__PURE__ */ ((ResourceType3) => {
@@ -2144,15 +2213,15 @@ var ReportReason = /* @__PURE__ */ ((ReportReason3) => {
2144
2213
  var SupportedReportReasons = Object.values(ReportReason);
2145
2214
 
2146
2215
  // src/report/report.schema.ts
2147
- var ReportSchema = object20({
2216
+ var ReportSchema = object21({
2148
2217
  type: mixed4().oneOf(SupportedResourceTypes).required().label("Type"),
2149
- resourceId: string17().required().label("Resource ID"),
2218
+ resourceId: string18().required().label("Resource ID"),
2150
2219
  reason: mixed4().oneOf(SupportedReportReasons).required("Please choose a reason").label("Reason"),
2151
- comment: string17().max(1e3).optional().label("Comment")
2220
+ comment: string18().max(1e3).optional().label("Comment")
2152
2221
  }).label("Report Schema");
2153
2222
 
2154
2223
  // src/feedback/feedback.schema.ts
2155
- import { mixed as mixed5, object as object21, string as string18 } from "yup";
2224
+ import { mixed as mixed5, object as object22, string as string19 } from "yup";
2156
2225
 
2157
2226
  // src/feedback/feedback.constant.ts
2158
2227
  var FeedbackType = /* @__PURE__ */ ((FeedbackType3) => {
@@ -2175,63 +2244,63 @@ var SupportedFeedbackStatuses = Object.values(FeedbackStatus);
2175
2244
  var FEEDBACK_MESSAGE_MAX = 4e3;
2176
2245
 
2177
2246
  // src/feedback/feedback.schema.ts
2178
- var FeedbackSchema = object21({
2247
+ var FeedbackSchema = object22({
2179
2248
  type: mixed5().oneOf(SupportedFeedbackTypes).required().label("Type"),
2180
- message: string18().trim().required("Please enter a message").max(FEEDBACK_MESSAGE_MAX).label("Message"),
2181
- email: string18().email().optional().label("Email"),
2182
- pageUrl: string18().optional().label("Page URL"),
2249
+ message: string19().trim().required("Please enter a message").max(FEEDBACK_MESSAGE_MAX).label("Message"),
2250
+ email: string19().email().optional().label("Email"),
2251
+ pageUrl: string19().optional().label("Page URL"),
2183
2252
  status: mixed5().oneOf(SupportedFeedbackStatuses).optional().label("Status")
2184
2253
  }).label("Feedback Schema");
2185
2254
 
2186
2255
  // src/user/user.schema.ts
2187
- import { array as array14, boolean as boolean15, number as number10, object as object33, string as string31 } from "yup";
2256
+ import { array as array14, boolean as boolean15, number as number11, object as object34, string as string32 } from "yup";
2188
2257
 
2189
2258
  // src/user/work-experience.schema.ts
2190
- import { boolean as boolean12, object as object22, string as string19 } from "yup";
2191
- var WorkExperienceSchema = object22().shape({
2259
+ import { boolean as boolean12, object as object23, string as string20 } from "yup";
2260
+ var WorkExperienceSchema = object23().shape({
2192
2261
  company: PageSchema.pick(["name", "slug", "type", "logo"]).deepPartial().concat(PageSchema.pick(["name", "type"])).required().label("Company"),
2193
- designation: string19().trim().required().label("Designation"),
2262
+ designation: string20().trim().required().label("Designation"),
2194
2263
  duration: DurationSchema({
2195
2264
  format: "YYYY-MM",
2196
2265
  startLabel: "Start Date",
2197
2266
  endLabel: "End Date"
2198
2267
  }).required().label("Duration"),
2199
- description: string19().trim().max(3e3).optional().label("Description"),
2268
+ description: string20().trim().max(3e3).optional().label("Description"),
2200
2269
  location: LocationSchema.required().label("Location"),
2201
2270
  isRemote: boolean12().oneOf([true, false]).default(false).label("Is Remote")
2202
2271
  }).noUnknown().strict().label("Work Experience");
2203
2272
 
2204
2273
  // src/user/education.schema.ts
2205
- import { boolean as boolean13, object as object23, string as string20 } from "yup";
2206
- var EducationSchema = object23({
2274
+ import { boolean as boolean13, object as object24, string as string21 } from "yup";
2275
+ var EducationSchema = object24({
2207
2276
  institute: PageSchema.pick(["name", "slug", "type", "logo"]).deepPartial().concat(PageSchema.pick(["name", "type"])).required().label("Institute"),
2208
- course: string20().trim().required().label("Course"),
2209
- fieldOfStudy: string20().trim().required().label("Field of Study"),
2277
+ course: string21().trim().required().label("Course"),
2278
+ fieldOfStudy: string21().trim().required().label("Field of Study"),
2210
2279
  duration: DurationSchema({
2211
2280
  format: "YYYY-MM",
2212
2281
  startLabel: "Start Date",
2213
2282
  endLabel: "End Date"
2214
2283
  }).required().label("Duration"),
2215
- description: string20().trim().max(3e3).optional().label("Description"),
2284
+ description: string21().trim().max(3e3).optional().label("Description"),
2216
2285
  isDistanceLearning: boolean13().default(false).label("Is Distance Learning"),
2217
2286
  location: LocationSchema.required().label("Location"),
2218
- studyType: string20().oneOf(SupportedStudyTypes).trim().max(50).required().label("Study Type")
2287
+ studyType: string21().oneOf(SupportedStudyTypes).trim().max(50).required().label("Study Type")
2219
2288
  }).noUnknown().strict().label("Education");
2220
2289
 
2221
2290
  // src/user/user-skill.schema.ts
2222
- import { object as object24, string as string21 } from "yup";
2223
- var UserSkillSchema = object24({
2224
- name: string21().required().label("Name"),
2225
- proficiencyLevel: string21().oneOf(SupportedProficiencyLevels).required().label("Proficiency Level"),
2291
+ import { object as object25, string as string22 } from "yup";
2292
+ var UserSkillSchema = object25({
2293
+ name: string22().required().label("Name"),
2294
+ proficiencyLevel: string22().oneOf(SupportedProficiencyLevels).required().label("Proficiency Level"),
2226
2295
  lastUsed: dateString().format("YYYY-MM").nullable().optional().label("Last Used")
2227
2296
  }).noUnknown().strict().label("Skill");
2228
2297
 
2229
2298
  // src/user/project.schema.ts
2230
- import { object as object25, string as string22 } from "yup";
2231
- var UserProjectSchema = object25({
2232
- name: string22().trim().max(50).required().label("Name"),
2233
- url: string22().optional().label("URL"),
2234
- description: string22().trim().min(100).max(3e3).required().label("Description"),
2299
+ import { object as object26, string as string23 } from "yup";
2300
+ var UserProjectSchema = object26({
2301
+ name: string23().trim().max(50).required().label("Name"),
2302
+ url: string23().optional().label("URL"),
2303
+ description: string23().trim().min(100).max(3e3).required().label("Description"),
2235
2304
  duration: DurationSchema({
2236
2305
  format: "YYYY-MM",
2237
2306
  startLabel: "Start Date",
@@ -2240,65 +2309,65 @@ var UserProjectSchema = object25({
2240
2309
  }).noUnknown().strict().label("Project");
2241
2310
 
2242
2311
  // src/user/user-certification.schema.ts
2243
- import { object as object26, string as string23 } from "yup";
2244
- var UserCertificationSchema = object26({
2245
- name: string23().trim().max(100).required().label("Name"),
2312
+ import { object as object27, string as string24 } from "yup";
2313
+ var UserCertificationSchema = object27({
2314
+ name: string24().trim().max(100).required().label("Name"),
2246
2315
  // TODO: Add validation for authority
2247
- authority: string23().trim().max(100).required().label("Authority"),
2248
- licenseNumber: string23().trim().max(50).optional().label("License Number"),
2316
+ authority: string24().trim().max(100).required().label("Authority"),
2317
+ licenseNumber: string24().trim().max(50).optional().label("License Number"),
2249
2318
  duration: DurationSchema({
2250
2319
  format: "YYYY-MM",
2251
2320
  startLabel: "Start Date",
2252
2321
  endLabel: "End Date"
2253
2322
  }).optional().nullable().label("Duration"),
2254
- url: string23().optional().label("URL")
2323
+ url: string24().optional().label("URL")
2255
2324
  }).noUnknown().strict().label("Certification");
2256
2325
 
2257
2326
  // src/user/user-interest.schema.ts
2258
- import { string as string24 } from "yup";
2259
- var UserInterestSchema = string24().trim().required().label("Interest");
2327
+ import { string as string25 } from "yup";
2328
+ var UserInterestSchema = string25().trim().required().label("Interest");
2260
2329
 
2261
2330
  // src/user/user-language.schema.ts
2262
- import { object as object27, string as string25 } from "yup";
2263
- var UserLanguageSchema = object27({
2264
- name: string25().trim().required().label("Name"),
2265
- proficiencyLevel: string25().oneOf(SupportedProficiencyLevels).trim().max(50).required().label("Proficiency Level")
2331
+ import { object as object28, string as string26 } from "yup";
2332
+ var UserLanguageSchema = object28({
2333
+ name: string26().trim().required().label("Name"),
2334
+ proficiencyLevel: string26().oneOf(SupportedProficiencyLevels).trim().max(50).required().label("Proficiency Level")
2266
2335
  }).noUnknown().strict().label("Language");
2267
2336
 
2268
2337
  // src/user/user-additional-info.schema.ts
2269
- import { object as object28, string as string26 } from "yup";
2270
- var UserAdditionalInfoSchema = object28({
2271
- title: string26().trim().max(60).required().label("Title"),
2272
- description: string26().trim().max(1e3).required().label("Description")
2338
+ import { object as object29, string as string27 } from "yup";
2339
+ var UserAdditionalInfoSchema = object29({
2340
+ title: string27().trim().max(60).required().label("Title"),
2341
+ description: string27().trim().max(1e3).required().label("Description")
2273
2342
  }).noUnknown().strict().label("User Additional Info");
2274
2343
 
2275
2344
  // src/user/general-detail.schema.ts
2276
- import { object as object29, string as string27 } from "yup";
2277
- var UserGeneralDetailSchema = object29({
2278
- id: string27().optional().label("ID"),
2279
- name: object29().shape({
2280
- first: string27().trim().max(50).required().label("First Name"),
2281
- last: string27().trim().max(50).optional().label("Last Name")
2345
+ import { object as object30, string as string28 } from "yup";
2346
+ var UserGeneralDetailSchema = object30({
2347
+ id: string28().optional().label("ID"),
2348
+ name: object30().shape({
2349
+ first: string28().trim().max(50).required().label("First Name"),
2350
+ last: string28().trim().max(50).optional().label("Last Name")
2282
2351
  }).required().label("Name"),
2283
- headline: string27().trim().max(200).optional().label("Headline"),
2284
- image: string27().optional().label("Image"),
2285
- aboutMe: string27().trim().max(3e3).optional().label("About Me"),
2286
- email: string27().required().label("Email"),
2287
- mobile: string27().nullable().optional().label("Mobile"),
2288
- emailVerified: string27().nullable().optional().label("Email Verified"),
2289
- mobileVerified: string27().nullable().optional().label("Mobile Verified"),
2290
- experienceLevel: string27().oneOf(SupportedExperienceLevels).required().label("Experience level"),
2352
+ headline: string28().trim().max(200).optional().label("Headline"),
2353
+ image: string28().optional().label("Image"),
2354
+ aboutMe: string28().trim().max(3e3).optional().label("About Me"),
2355
+ email: string28().required().label("Email"),
2356
+ mobile: string28().nullable().optional().label("Mobile"),
2357
+ emailVerified: string28().nullable().optional().label("Email Verified"),
2358
+ mobileVerified: string28().nullable().optional().label("Mobile Verified"),
2359
+ experienceLevel: string28().oneOf(SupportedExperienceLevels).required().label("Experience level"),
2291
2360
  location: LocationSchema.required().label("Location"),
2292
- region: object29().shape({
2293
- country: string27().trim().required().label("Region Country"),
2294
- lang: string27().trim().required().label("Region Language")
2361
+ region: object30().shape({
2362
+ country: string28().trim().required().label("Region Country"),
2363
+ lang: string28().trim().required().label("Region Language")
2295
2364
  }).optional().default(void 0).label("Region"),
2296
- profileVisibility: string27().oneOf(SupportedUserProfileVisibilities).default("public" /* Public */).label("Profile Visibility")
2365
+ profileVisibility: string28().oneOf(SupportedUserProfileVisibilities).default("public" /* Public */).label("Profile Visibility")
2297
2366
  }).noUnknown().strict().label("General Detail");
2298
2367
 
2299
2368
  // src/user/user-job-preferences.schema.ts
2300
- import { array as array11, boolean as boolean14, date as date2, number as number9, object as object30, string as string28 } from "yup";
2301
- var UserJobPreferencesSchema = object30({
2369
+ import { array as array11, boolean as boolean14, date as date2, number as number10, object as object31, string as string29 } from "yup";
2370
+ var UserJobPreferencesSchema = object31({
2302
2371
  /**
2303
2372
  * Whether the job seeker is openly signalling availability. Surfaces the
2304
2373
  * "Open to work" badge on the public profile and boosts visibility in
@@ -2310,7 +2379,7 @@ var UserJobPreferencesSchema = object30({
2310
2379
  * and can downgrade the visibility of expired or low-relevance postings for
2311
2380
  * users in `passively_browsing` mode.
2312
2381
  */
2313
- jobSearchUrgency: string28().oneOf(SupportedJobSearchUrgencies).required().label("Job Search Urgency"),
2382
+ jobSearchUrgency: string29().oneOf(SupportedJobSearchUrgencies).required().label("Job Search Urgency"),
2314
2383
  /**
2315
2384
  * Locations the user wants to work in. Stores the full `LocationSchema`
2316
2385
  * shape (country, city, state, geo, etc.) as returned by the locations
@@ -2327,7 +2396,7 @@ var UserJobPreferencesSchema = object30({
2327
2396
  * Both reuse the same `ExperienceLevel` taxonomy from `common.constant.ts`
2328
2397
  * so search/match logic doesn't have to translate between two vocabularies.
2329
2398
  */
2330
- targetExperienceLevels: array11().of(string28().oneOf(SupportedExperienceLevels).required()).min(1, "Pick at least one experience level.").max(2, "You can select at most 2 experience levels.").required().label("Target Experience Levels"),
2399
+ targetExperienceLevels: array11().of(string29().oneOf(SupportedExperienceLevels).required()).min(1, "Pick at least one experience level.").max(2, "You can select at most 2 experience levels.").required().label("Target Experience Levels"),
2331
2400
  /**
2332
2401
  * Specializations the user is searching for - free-form titles.
2333
2402
  *
@@ -2336,19 +2405,19 @@ var UserJobPreferencesSchema = object30({
2336
2405
  * surfaces a curated "Popular Roles" picker for discovery, but the user
2337
2406
  * can ultimately add any title; matching/normalization happens downstream.
2338
2407
  */
2339
- jobRoles: array11().of(string28().trim().max(120).required()).min(1, "Pick at least one role.").required().label("Job Roles"),
2408
+ jobRoles: array11().of(string29().trim().max(120).required()).min(1, "Pick at least one role.").required().label("Job Roles"),
2340
2409
  /**
2341
2410
  * Minimum acceptable annual base salary, in `minSalaryCurrency`.
2342
2411
  * Stored as an integer in the currency's major unit (e.g. dollars, not cents).
2343
2412
  * Defaults to 0 ("any salary") rather than being optional - every job-seeker
2344
2413
  * has a floor, even if it's zero.
2345
2414
  */
2346
- minSalary: number9().integer().min(MIN_SALARY_LOWER_BOUND).max(MIN_SALARY_UPPER_BOUND).default(0).required().label("Minimum Salary"),
2347
- minSalaryCurrency: string28().oneOf(SupportedSalaryCurrencies).default("USD").required().label("Minimum Salary Currency"),
2415
+ minSalary: number10().integer().min(MIN_SALARY_LOWER_BOUND).max(MIN_SALARY_UPPER_BOUND).default(0).required().label("Minimum Salary"),
2416
+ minSalaryCurrency: string29().oneOf(SupportedSalaryCurrencies).default("USD").required().label("Minimum Salary Currency"),
2348
2417
  /**
2349
2418
  * Marketing-attribution capture from the final onboarding step.
2350
2419
  */
2351
- referralSource: string28().oneOf(SupportedReferralSources).required().label("Referral Source"),
2420
+ referralSource: string29().oneOf(SupportedReferralSources).required().label("Referral Source"),
2352
2421
  /**
2353
2422
  * Resumes the user has uploaded. Embedded directly on the user document
2354
2423
  * (capped at 5) - they're cheap to denormalize, every wizard step that
@@ -2363,15 +2432,15 @@ var UserJobPreferencesSchema = object30({
2363
2432
  * live in their own collection.
2364
2433
  */
2365
2434
  resumes: array11().of(
2366
- object30({
2435
+ object31({
2367
2436
  // Stable id assigned on upload - used to address a specific entry
2368
2437
  // for delete / set-primary operations without exposing the GCS URL
2369
2438
  // as a route key.
2370
- id: string28().required().label("ID"),
2371
- url: string28().required().label("Resume URL"),
2372
- filename: string28().trim().max(255).optional().label("Filename"),
2373
- sizeBytes: number9().integer().min(0).optional().label("Size (bytes)"),
2374
- mimeType: string28().trim().max(120).optional().label("MIME Type"),
2439
+ id: string29().required().label("ID"),
2440
+ url: string29().required().label("Resume URL"),
2441
+ filename: string29().trim().max(255).optional().label("Filename"),
2442
+ sizeBytes: number10().integer().min(0).optional().label("Size (bytes)"),
2443
+ mimeType: string29().trim().max(120).optional().label("MIME Type"),
2375
2444
  uploadedAt: date2().required().label("Uploaded At"),
2376
2445
  isPrimary: boolean14().default(false).label("Is Primary")
2377
2446
  }).noUnknown().strict().label("Resume")
@@ -2400,12 +2469,12 @@ var UserJobPreferencesSchema = object30({
2400
2469
  * APIs. If `user.<field>` is empty there, the correct behavior is empty,
2401
2470
  * because the user has not confirmed it yet.
2402
2471
  */
2403
- pendingPrefill: object30({
2404
- headline: string28().trim().optional().label("Pending Headline"),
2405
- aboutMe: string28().trim().optional().label("Pending About Me"),
2406
- mobile: string28().trim().optional().label("Pending Mobile"),
2472
+ pendingPrefill: object31({
2473
+ headline: string29().trim().optional().label("Pending Headline"),
2474
+ aboutMe: string29().trim().optional().label("Pending About Me"),
2475
+ mobile: string29().trim().optional().label("Pending Mobile"),
2407
2476
  location: LocationSchema.optional().default(void 0).label("Pending Location"),
2408
- experienceLevel: string28().oneOf(SupportedExperienceLevels).optional().label("Pending Experience Level"),
2477
+ experienceLevel: string29().oneOf(SupportedExperienceLevels).optional().label("Pending Experience Level"),
2409
2478
  socialAccounts: array11().of(SocialAccountSchema.required()).optional().label("Pending Social Accounts"),
2410
2479
  workExperiences: array11().of(WorkExperienceSchema.required()).optional().label("Pending Work Experiences"),
2411
2480
  educations: array11().of(EducationSchema.required()).optional().label("Pending Educations")
@@ -2413,35 +2482,35 @@ var UserJobPreferencesSchema = object30({
2413
2482
  }).noUnknown().strict().label("User Job Preferences Schema");
2414
2483
 
2415
2484
  // src/user/user-recruiter-profile.schema.ts
2416
- import { array as array12, object as object31, string as string29 } from "yup";
2417
- var RecruiterPageLinkSchema = object31({
2485
+ import { array as array12, object as object32, string as string30 } from "yup";
2486
+ var RecruiterPageLinkSchema = object32({
2418
2487
  page: PageSchema.pick(["name", "slug", "type", "logo"]).deepPartial().concat(PageSchema.pick(["name", "type"])).required().label("Company Page"),
2419
- jobTitle: string29().trim().max(100).optional().label("Job Title")
2488
+ jobTitle: string30().trim().max(100).optional().label("Job Title")
2420
2489
  }).noUnknown().strict().label("Recruiter Page Link");
2421
- var UserRecruiterProfileSchema = object31({
2422
- recruiterProfile: object31({
2490
+ var UserRecruiterProfileSchema = object32({
2491
+ recruiterProfile: object32({
2423
2492
  hiringFor: array12().of(RecruiterPageLinkSchema).default([]).label("Hiring For")
2424
2493
  }).optional().default(void 0).label("Recruiter Profile")
2425
2494
  }).noUnknown().strict().label("User Recruiter Profile");
2426
2495
 
2427
2496
  // src/user/user-coordinator-profile.schema.ts
2428
- import { array as array13, object as object32, string as string30 } from "yup";
2429
- var CoordinatorPageLinkSchema = object32({
2497
+ import { array as array13, object as object33, string as string31 } from "yup";
2498
+ var CoordinatorPageLinkSchema = object33({
2430
2499
  page: PageSchema.pick(["name", "slug", "type", "logo"]).deepPartial().concat(PageSchema.pick(["name", "type"])).required().label("Institute Page"),
2431
- jobTitle: string30().trim().max(100).optional().label("Job Title")
2500
+ jobTitle: string31().trim().max(100).optional().label("Job Title")
2432
2501
  }).noUnknown().strict().label("Coordinator Page Link");
2433
- var UserCoordinatorProfileSchema = object32({
2434
- coordinatorProfile: object32({
2502
+ var UserCoordinatorProfileSchema = object33({
2503
+ coordinatorProfile: object33({
2435
2504
  coordinatesAt: array13().of(CoordinatorPageLinkSchema).default([]).label("Coordinates At")
2436
2505
  }).optional().default(void 0).label("Coordinator Profile")
2437
2506
  }).noUnknown().strict().label("User Coordinator Profile");
2438
2507
 
2439
2508
  // src/user/user.schema.ts
2440
- var UserSchema = object33({
2509
+ var UserSchema = object34({
2441
2510
  /**
2442
2511
  * Related auth account id from auth Server (e.g. Better auth https://auth.thejob.dev)
2443
2512
  */
2444
- authAccountId: string31().required().label("Auth Account ID"),
2513
+ authAccountId: string32().required().label("Auth Account ID"),
2445
2514
  /**
2446
2515
  * Social media information about the user (e.g. LinkedIn, GitHub, etc.)
2447
2516
  */
@@ -2481,11 +2550,11 @@ var UserSchema = object33({
2481
2550
  /**
2482
2551
  * Status of the user account
2483
2552
  */
2484
- status: string31().oneOf(SupportedUserStatuses).required().label("Status"),
2553
+ status: string32().oneOf(SupportedUserStatuses).required().label("Status"),
2485
2554
  /**
2486
2555
  * Roles assigned to the user
2487
2556
  */
2488
- roles: array14().of(string31().oneOf(SupportedUserRoles)).required().default(DefaultUserRoles).label("Roles"),
2557
+ roles: array14().of(string32().oneOf(SupportedUserRoles)).required().default(DefaultUserRoles).label("Roles"),
2489
2558
  /**
2490
2559
  * Explicit consent to receive marketing email (newsletters, job alerts by
2491
2560
  * broadcast, product updates). Marketing sends are HARD-GATED on
@@ -2497,33 +2566,57 @@ var UserSchema = object33({
2497
2566
  * unsubscribe sets `subscribed=false` so consent and the email-service
2498
2567
  * suppression list stay consistent. Defaults to NOT subscribed (opt-in).
2499
2568
  */
2500
- marketingConsent: object33({
2569
+ marketingConsent: object34({
2501
2570
  subscribed: boolean15().default(false).label("Subscribed to marketing email"),
2502
2571
  /** Epoch ms of the last consent change (opt-in or opt-out). */
2503
- updatedAt: number10().optional().label("Consent updated at"),
2572
+ updatedAt: number11().optional().label("Consent updated at"),
2504
2573
  /** Where the consent change originated, e.g. `onboarding`, `unsubscribe`, `settings`. */
2505
- source: string31().optional().label("Consent source"),
2574
+ source: string32().optional().label("Consent source"),
2506
2575
  /**
2507
2576
  * How often the user receives job-alert emails. `off` disables job alerts
2508
2577
  * only; other marketing stays governed by `subscribed`. Unset means the
2509
2578
  * user has not chosen a cadence yet.
2510
2579
  */
2511
- jobAlertFrequency: string31().oneOf(SupportedJobAlertFrequencies).optional().label("Job alert frequency")
2580
+ jobAlertFrequency: string32().oneOf(SupportedJobAlertFrequencies).optional().label("Job alert frequency")
2512
2581
  }).default({ subscribed: false }).label("Marketing Consent"),
2582
+ /**
2583
+ * Per-channel toggles for NOTIFICATION email — mail about activity on the
2584
+ * user's own account (a group invite, a job of theirs expiring).
2585
+ *
2586
+ * Deliberately separate from `marketingConsent`, which they must NOT be
2587
+ * collapsed into: the two have different legal footing, which the
2588
+ * email-service footer rules already encode. Notifications are opt-OUT
2589
+ * (default `true`) because a user who declined marketing still needs to hear
2590
+ * that their group invite arrived; marketing stays opt-IN. `productUpdates`
2591
+ * is the exception — it is promotional in character, so it defaults off.
2592
+ *
2593
+ * Transactional mail (magic-link, verification) ignores this object entirely.
2594
+ */
2595
+ notificationPrefs: object34({
2596
+ groupActivity: boolean15().default(true).label("Group activity email"),
2597
+ jobActivity: boolean15().default(true).label("Job activity email"),
2598
+ applicationUpdates: boolean15().default(true).label("Application update email"),
2599
+ productUpdates: boolean15().default(false).label("Product update email")
2600
+ }).default({
2601
+ groupActivity: true,
2602
+ jobActivity: true,
2603
+ applicationUpdates: true,
2604
+ productUpdates: false
2605
+ }).label("Notification Preferences"),
2513
2606
  /**
2514
2607
  * Vector embedding of the user's profile for semantic/hybrid search.
2515
2608
  * Generated from a structured summary of skills, experience, education, etc.
2516
2609
  * Only generated for public profiles with sufficient completeness (≥60%).
2517
2610
  */
2518
- embedding: object33({
2519
- vector: array14(number10().required()).optional().label("Embedding vector"),
2520
- model: string31().optional().label("Embedding model")
2611
+ embedding: object34({
2612
+ vector: array14(number11().required()).optional().label("Embedding vector"),
2613
+ model: string32().optional().label("Embedding model")
2521
2614
  }).nullable().optional().label("Embedding")
2522
2615
  }).concat(UserGeneralDetailSchema).concat(UserJobPreferencesSchema).concat(UserRecruiterProfileSchema).concat(UserCoordinatorProfileSchema).noUnknown().strict().label("User Schema");
2523
2616
 
2524
2617
  // src/user/user-completeness.schema.ts
2525
- import { object as object34 } from "yup";
2526
- var UserCompletenessSchema = object34({
2618
+ import { object as object35 } from "yup";
2619
+ var UserCompletenessSchema = object35({
2527
2620
  additionalInfo: CompletenessScoreSchema(0).label("Additional Info"),
2528
2621
  // Optional
2529
2622
  certifications: CompletenessScoreSchema(0).label("Certifications"),
@@ -2595,6 +2688,7 @@ export {
2595
2688
  MIN_SALARY_LOWER_BOUND,
2596
2689
  MIN_SALARY_UPPER_BOUND,
2597
2690
  MailingListSchema,
2691
+ NotificationChannel,
2598
2692
  PREFILL_MIRRORED_KEYS,
2599
2693
  PRIVATE_PROFILE_FIELDS,
2600
2694
  PageSchema,
@@ -2614,6 +2708,8 @@ export {
2614
2708
  ResourceType,
2615
2709
  SITEMAP_FORMAT,
2616
2710
  SYSTEM_DATE_FORMAT,
2711
+ SavedSearchSchema,
2712
+ SavedSearchStatus,
2617
2713
  SkillSchema,
2618
2714
  SocialAccount,
2619
2715
  SocialAccountSchema,
@@ -2636,6 +2732,7 @@ export {
2636
2732
  SupportedJobSearchUrgencies,
2637
2733
  SupportedJobStatuses,
2638
2734
  SupportedJobSubCategories,
2735
+ SupportedNotificationChannels,
2639
2736
  SupportedPageStatuses,
2640
2737
  SupportedPageTypes,
2641
2738
  SupportedPostStatuses,
@@ -2645,6 +2742,7 @@ export {
2645
2742
  SupportedReportReasons,
2646
2743
  SupportedResourceTypes,
2647
2744
  SupportedSalaryCurrencies,
2745
+ SupportedSavedSearchStatuses,
2648
2746
  SupportedSocialAccounts,
2649
2747
  SupportedStudyTypes,
2650
2748
  SupportedUserProfileVisibilities,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thejob/schema",
3
- "version": "2.1.2",
3
+ "version": "2.1.4",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",
package/src/index.ts CHANGED
@@ -62,6 +62,12 @@ export * from "./marketing/marketing.constant.js";
62
62
  export * from "./pagination/pagination.schema.js";
63
63
  export * from "./pagination/pagination.constant.js";
64
64
 
65
+ /**
66
+ * Saved-search schemas and constants (job alerts).
67
+ */
68
+ export * from "./saved-search/saved-search.schema.js";
69
+ export * from "./saved-search/saved-search.constant.js";
70
+
65
71
  /**
66
72
  * Report schemas and constants.
67
73
  */
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Lifecycle of a saved search. Lowercase values, like every other enum in this
3
+ * package: the string is stored in Mongo and compared as a literal across
4
+ * services, so its casing is data rather than style.
5
+ */
6
+ export enum SavedSearchStatus {
7
+ /** Matching runs and alerts may be sent. */
8
+ Active = "active",
9
+ /**
10
+ * Kept for the user to see and re-enable, but skipped by the matching sweep.
11
+ * Distinct from deleting: a user pausing alerts should not lose the query they
12
+ * spent time building.
13
+ */
14
+ Paused = "paused",
15
+ }
16
+
17
+ export const SupportedSavedSearchStatuses = Object.values(SavedSearchStatus);