@thejob/schema 2.1.3 → 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
@@ -2127,8 +2127,69 @@ var DefaultPaginationOptions = {
2127
2127
  limit: 10
2128
2128
  };
2129
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
+
2130
2191
  // src/report/report.schema.ts
2131
- 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";
2132
2193
 
2133
2194
  // src/report/report.constant.ts
2134
2195
  var ResourceType = /* @__PURE__ */ ((ResourceType3) => {
@@ -2152,15 +2213,15 @@ var ReportReason = /* @__PURE__ */ ((ReportReason3) => {
2152
2213
  var SupportedReportReasons = Object.values(ReportReason);
2153
2214
 
2154
2215
  // src/report/report.schema.ts
2155
- var ReportSchema = object20({
2216
+ var ReportSchema = object21({
2156
2217
  type: mixed4().oneOf(SupportedResourceTypes).required().label("Type"),
2157
- resourceId: string17().required().label("Resource ID"),
2218
+ resourceId: string18().required().label("Resource ID"),
2158
2219
  reason: mixed4().oneOf(SupportedReportReasons).required("Please choose a reason").label("Reason"),
2159
- comment: string17().max(1e3).optional().label("Comment")
2220
+ comment: string18().max(1e3).optional().label("Comment")
2160
2221
  }).label("Report Schema");
2161
2222
 
2162
2223
  // src/feedback/feedback.schema.ts
2163
- 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";
2164
2225
 
2165
2226
  // src/feedback/feedback.constant.ts
2166
2227
  var FeedbackType = /* @__PURE__ */ ((FeedbackType3) => {
@@ -2183,63 +2244,63 @@ var SupportedFeedbackStatuses = Object.values(FeedbackStatus);
2183
2244
  var FEEDBACK_MESSAGE_MAX = 4e3;
2184
2245
 
2185
2246
  // src/feedback/feedback.schema.ts
2186
- var FeedbackSchema = object21({
2247
+ var FeedbackSchema = object22({
2187
2248
  type: mixed5().oneOf(SupportedFeedbackTypes).required().label("Type"),
2188
- message: string18().trim().required("Please enter a message").max(FEEDBACK_MESSAGE_MAX).label("Message"),
2189
- email: string18().email().optional().label("Email"),
2190
- 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"),
2191
2252
  status: mixed5().oneOf(SupportedFeedbackStatuses).optional().label("Status")
2192
2253
  }).label("Feedback Schema");
2193
2254
 
2194
2255
  // src/user/user.schema.ts
2195
- 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";
2196
2257
 
2197
2258
  // src/user/work-experience.schema.ts
2198
- import { boolean as boolean12, object as object22, string as string19 } from "yup";
2199
- var WorkExperienceSchema = object22().shape({
2259
+ import { boolean as boolean12, object as object23, string as string20 } from "yup";
2260
+ var WorkExperienceSchema = object23().shape({
2200
2261
  company: PageSchema.pick(["name", "slug", "type", "logo"]).deepPartial().concat(PageSchema.pick(["name", "type"])).required().label("Company"),
2201
- designation: string19().trim().required().label("Designation"),
2262
+ designation: string20().trim().required().label("Designation"),
2202
2263
  duration: DurationSchema({
2203
2264
  format: "YYYY-MM",
2204
2265
  startLabel: "Start Date",
2205
2266
  endLabel: "End Date"
2206
2267
  }).required().label("Duration"),
2207
- description: string19().trim().max(3e3).optional().label("Description"),
2268
+ description: string20().trim().max(3e3).optional().label("Description"),
2208
2269
  location: LocationSchema.required().label("Location"),
2209
2270
  isRemote: boolean12().oneOf([true, false]).default(false).label("Is Remote")
2210
2271
  }).noUnknown().strict().label("Work Experience");
2211
2272
 
2212
2273
  // src/user/education.schema.ts
2213
- import { boolean as boolean13, object as object23, string as string20 } from "yup";
2214
- var EducationSchema = object23({
2274
+ import { boolean as boolean13, object as object24, string as string21 } from "yup";
2275
+ var EducationSchema = object24({
2215
2276
  institute: PageSchema.pick(["name", "slug", "type", "logo"]).deepPartial().concat(PageSchema.pick(["name", "type"])).required().label("Institute"),
2216
- course: string20().trim().required().label("Course"),
2217
- fieldOfStudy: string20().trim().required().label("Field of Study"),
2277
+ course: string21().trim().required().label("Course"),
2278
+ fieldOfStudy: string21().trim().required().label("Field of Study"),
2218
2279
  duration: DurationSchema({
2219
2280
  format: "YYYY-MM",
2220
2281
  startLabel: "Start Date",
2221
2282
  endLabel: "End Date"
2222
2283
  }).required().label("Duration"),
2223
- description: string20().trim().max(3e3).optional().label("Description"),
2284
+ description: string21().trim().max(3e3).optional().label("Description"),
2224
2285
  isDistanceLearning: boolean13().default(false).label("Is Distance Learning"),
2225
2286
  location: LocationSchema.required().label("Location"),
2226
- studyType: string20().oneOf(SupportedStudyTypes).trim().max(50).required().label("Study Type")
2287
+ studyType: string21().oneOf(SupportedStudyTypes).trim().max(50).required().label("Study Type")
2227
2288
  }).noUnknown().strict().label("Education");
2228
2289
 
2229
2290
  // src/user/user-skill.schema.ts
2230
- import { object as object24, string as string21 } from "yup";
2231
- var UserSkillSchema = object24({
2232
- name: string21().required().label("Name"),
2233
- 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"),
2234
2295
  lastUsed: dateString().format("YYYY-MM").nullable().optional().label("Last Used")
2235
2296
  }).noUnknown().strict().label("Skill");
2236
2297
 
2237
2298
  // src/user/project.schema.ts
2238
- import { object as object25, string as string22 } from "yup";
2239
- var UserProjectSchema = object25({
2240
- name: string22().trim().max(50).required().label("Name"),
2241
- url: string22().optional().label("URL"),
2242
- 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"),
2243
2304
  duration: DurationSchema({
2244
2305
  format: "YYYY-MM",
2245
2306
  startLabel: "Start Date",
@@ -2248,65 +2309,65 @@ var UserProjectSchema = object25({
2248
2309
  }).noUnknown().strict().label("Project");
2249
2310
 
2250
2311
  // src/user/user-certification.schema.ts
2251
- import { object as object26, string as string23 } from "yup";
2252
- var UserCertificationSchema = object26({
2253
- 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"),
2254
2315
  // TODO: Add validation for authority
2255
- authority: string23().trim().max(100).required().label("Authority"),
2256
- 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"),
2257
2318
  duration: DurationSchema({
2258
2319
  format: "YYYY-MM",
2259
2320
  startLabel: "Start Date",
2260
2321
  endLabel: "End Date"
2261
2322
  }).optional().nullable().label("Duration"),
2262
- url: string23().optional().label("URL")
2323
+ url: string24().optional().label("URL")
2263
2324
  }).noUnknown().strict().label("Certification");
2264
2325
 
2265
2326
  // src/user/user-interest.schema.ts
2266
- import { string as string24 } from "yup";
2267
- var UserInterestSchema = string24().trim().required().label("Interest");
2327
+ import { string as string25 } from "yup";
2328
+ var UserInterestSchema = string25().trim().required().label("Interest");
2268
2329
 
2269
2330
  // src/user/user-language.schema.ts
2270
- import { object as object27, string as string25 } from "yup";
2271
- var UserLanguageSchema = object27({
2272
- name: string25().trim().required().label("Name"),
2273
- 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")
2274
2335
  }).noUnknown().strict().label("Language");
2275
2336
 
2276
2337
  // src/user/user-additional-info.schema.ts
2277
- import { object as object28, string as string26 } from "yup";
2278
- var UserAdditionalInfoSchema = object28({
2279
- title: string26().trim().max(60).required().label("Title"),
2280
- 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")
2281
2342
  }).noUnknown().strict().label("User Additional Info");
2282
2343
 
2283
2344
  // src/user/general-detail.schema.ts
2284
- import { object as object29, string as string27 } from "yup";
2285
- var UserGeneralDetailSchema = object29({
2286
- id: string27().optional().label("ID"),
2287
- name: object29().shape({
2288
- first: string27().trim().max(50).required().label("First Name"),
2289
- 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")
2290
2351
  }).required().label("Name"),
2291
- headline: string27().trim().max(200).optional().label("Headline"),
2292
- image: string27().optional().label("Image"),
2293
- aboutMe: string27().trim().max(3e3).optional().label("About Me"),
2294
- email: string27().required().label("Email"),
2295
- mobile: string27().nullable().optional().label("Mobile"),
2296
- emailVerified: string27().nullable().optional().label("Email Verified"),
2297
- mobileVerified: string27().nullable().optional().label("Mobile Verified"),
2298
- 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"),
2299
2360
  location: LocationSchema.required().label("Location"),
2300
- region: object29().shape({
2301
- country: string27().trim().required().label("Region Country"),
2302
- 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")
2303
2364
  }).optional().default(void 0).label("Region"),
2304
- profileVisibility: string27().oneOf(SupportedUserProfileVisibilities).default("public" /* Public */).label("Profile Visibility")
2365
+ profileVisibility: string28().oneOf(SupportedUserProfileVisibilities).default("public" /* Public */).label("Profile Visibility")
2305
2366
  }).noUnknown().strict().label("General Detail");
2306
2367
 
2307
2368
  // src/user/user-job-preferences.schema.ts
2308
- import { array as array11, boolean as boolean14, date as date2, number as number9, object as object30, string as string28 } from "yup";
2309
- 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({
2310
2371
  /**
2311
2372
  * Whether the job seeker is openly signalling availability. Surfaces the
2312
2373
  * "Open to work" badge on the public profile and boosts visibility in
@@ -2318,7 +2379,7 @@ var UserJobPreferencesSchema = object30({
2318
2379
  * and can downgrade the visibility of expired or low-relevance postings for
2319
2380
  * users in `passively_browsing` mode.
2320
2381
  */
2321
- jobSearchUrgency: string28().oneOf(SupportedJobSearchUrgencies).required().label("Job Search Urgency"),
2382
+ jobSearchUrgency: string29().oneOf(SupportedJobSearchUrgencies).required().label("Job Search Urgency"),
2322
2383
  /**
2323
2384
  * Locations the user wants to work in. Stores the full `LocationSchema`
2324
2385
  * shape (country, city, state, geo, etc.) as returned by the locations
@@ -2335,7 +2396,7 @@ var UserJobPreferencesSchema = object30({
2335
2396
  * Both reuse the same `ExperienceLevel` taxonomy from `common.constant.ts`
2336
2397
  * so search/match logic doesn't have to translate between two vocabularies.
2337
2398
  */
2338
- 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"),
2339
2400
  /**
2340
2401
  * Specializations the user is searching for - free-form titles.
2341
2402
  *
@@ -2344,19 +2405,19 @@ var UserJobPreferencesSchema = object30({
2344
2405
  * surfaces a curated "Popular Roles" picker for discovery, but the user
2345
2406
  * can ultimately add any title; matching/normalization happens downstream.
2346
2407
  */
2347
- 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"),
2348
2409
  /**
2349
2410
  * Minimum acceptable annual base salary, in `minSalaryCurrency`.
2350
2411
  * Stored as an integer in the currency's major unit (e.g. dollars, not cents).
2351
2412
  * Defaults to 0 ("any salary") rather than being optional - every job-seeker
2352
2413
  * has a floor, even if it's zero.
2353
2414
  */
2354
- minSalary: number9().integer().min(MIN_SALARY_LOWER_BOUND).max(MIN_SALARY_UPPER_BOUND).default(0).required().label("Minimum Salary"),
2355
- 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"),
2356
2417
  /**
2357
2418
  * Marketing-attribution capture from the final onboarding step.
2358
2419
  */
2359
- referralSource: string28().oneOf(SupportedReferralSources).required().label("Referral Source"),
2420
+ referralSource: string29().oneOf(SupportedReferralSources).required().label("Referral Source"),
2360
2421
  /**
2361
2422
  * Resumes the user has uploaded. Embedded directly on the user document
2362
2423
  * (capped at 5) - they're cheap to denormalize, every wizard step that
@@ -2371,15 +2432,15 @@ var UserJobPreferencesSchema = object30({
2371
2432
  * live in their own collection.
2372
2433
  */
2373
2434
  resumes: array11().of(
2374
- object30({
2435
+ object31({
2375
2436
  // Stable id assigned on upload - used to address a specific entry
2376
2437
  // for delete / set-primary operations without exposing the GCS URL
2377
2438
  // as a route key.
2378
- id: string28().required().label("ID"),
2379
- url: string28().required().label("Resume URL"),
2380
- filename: string28().trim().max(255).optional().label("Filename"),
2381
- sizeBytes: number9().integer().min(0).optional().label("Size (bytes)"),
2382
- 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"),
2383
2444
  uploadedAt: date2().required().label("Uploaded At"),
2384
2445
  isPrimary: boolean14().default(false).label("Is Primary")
2385
2446
  }).noUnknown().strict().label("Resume")
@@ -2408,12 +2469,12 @@ var UserJobPreferencesSchema = object30({
2408
2469
  * APIs. If `user.<field>` is empty there, the correct behavior is empty,
2409
2470
  * because the user has not confirmed it yet.
2410
2471
  */
2411
- pendingPrefill: object30({
2412
- headline: string28().trim().optional().label("Pending Headline"),
2413
- aboutMe: string28().trim().optional().label("Pending About Me"),
2414
- 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"),
2415
2476
  location: LocationSchema.optional().default(void 0).label("Pending Location"),
2416
- experienceLevel: string28().oneOf(SupportedExperienceLevels).optional().label("Pending Experience Level"),
2477
+ experienceLevel: string29().oneOf(SupportedExperienceLevels).optional().label("Pending Experience Level"),
2417
2478
  socialAccounts: array11().of(SocialAccountSchema.required()).optional().label("Pending Social Accounts"),
2418
2479
  workExperiences: array11().of(WorkExperienceSchema.required()).optional().label("Pending Work Experiences"),
2419
2480
  educations: array11().of(EducationSchema.required()).optional().label("Pending Educations")
@@ -2421,35 +2482,35 @@ var UserJobPreferencesSchema = object30({
2421
2482
  }).noUnknown().strict().label("User Job Preferences Schema");
2422
2483
 
2423
2484
  // src/user/user-recruiter-profile.schema.ts
2424
- import { array as array12, object as object31, string as string29 } from "yup";
2425
- var RecruiterPageLinkSchema = object31({
2485
+ import { array as array12, object as object32, string as string30 } from "yup";
2486
+ var RecruiterPageLinkSchema = object32({
2426
2487
  page: PageSchema.pick(["name", "slug", "type", "logo"]).deepPartial().concat(PageSchema.pick(["name", "type"])).required().label("Company Page"),
2427
- jobTitle: string29().trim().max(100).optional().label("Job Title")
2488
+ jobTitle: string30().trim().max(100).optional().label("Job Title")
2428
2489
  }).noUnknown().strict().label("Recruiter Page Link");
2429
- var UserRecruiterProfileSchema = object31({
2430
- recruiterProfile: object31({
2490
+ var UserRecruiterProfileSchema = object32({
2491
+ recruiterProfile: object32({
2431
2492
  hiringFor: array12().of(RecruiterPageLinkSchema).default([]).label("Hiring For")
2432
2493
  }).optional().default(void 0).label("Recruiter Profile")
2433
2494
  }).noUnknown().strict().label("User Recruiter Profile");
2434
2495
 
2435
2496
  // src/user/user-coordinator-profile.schema.ts
2436
- import { array as array13, object as object32, string as string30 } from "yup";
2437
- var CoordinatorPageLinkSchema = object32({
2497
+ import { array as array13, object as object33, string as string31 } from "yup";
2498
+ var CoordinatorPageLinkSchema = object33({
2438
2499
  page: PageSchema.pick(["name", "slug", "type", "logo"]).deepPartial().concat(PageSchema.pick(["name", "type"])).required().label("Institute Page"),
2439
- jobTitle: string30().trim().max(100).optional().label("Job Title")
2500
+ jobTitle: string31().trim().max(100).optional().label("Job Title")
2440
2501
  }).noUnknown().strict().label("Coordinator Page Link");
2441
- var UserCoordinatorProfileSchema = object32({
2442
- coordinatorProfile: object32({
2502
+ var UserCoordinatorProfileSchema = object33({
2503
+ coordinatorProfile: object33({
2443
2504
  coordinatesAt: array13().of(CoordinatorPageLinkSchema).default([]).label("Coordinates At")
2444
2505
  }).optional().default(void 0).label("Coordinator Profile")
2445
2506
  }).noUnknown().strict().label("User Coordinator Profile");
2446
2507
 
2447
2508
  // src/user/user.schema.ts
2448
- var UserSchema = object33({
2509
+ var UserSchema = object34({
2449
2510
  /**
2450
2511
  * Related auth account id from auth Server (e.g. Better auth https://auth.thejob.dev)
2451
2512
  */
2452
- authAccountId: string31().required().label("Auth Account ID"),
2513
+ authAccountId: string32().required().label("Auth Account ID"),
2453
2514
  /**
2454
2515
  * Social media information about the user (e.g. LinkedIn, GitHub, etc.)
2455
2516
  */
@@ -2489,11 +2550,11 @@ var UserSchema = object33({
2489
2550
  /**
2490
2551
  * Status of the user account
2491
2552
  */
2492
- status: string31().oneOf(SupportedUserStatuses).required().label("Status"),
2553
+ status: string32().oneOf(SupportedUserStatuses).required().label("Status"),
2493
2554
  /**
2494
2555
  * Roles assigned to the user
2495
2556
  */
2496
- roles: array14().of(string31().oneOf(SupportedUserRoles)).required().default(DefaultUserRoles).label("Roles"),
2557
+ roles: array14().of(string32().oneOf(SupportedUserRoles)).required().default(DefaultUserRoles).label("Roles"),
2497
2558
  /**
2498
2559
  * Explicit consent to receive marketing email (newsletters, job alerts by
2499
2560
  * broadcast, product updates). Marketing sends are HARD-GATED on
@@ -2505,18 +2566,18 @@ var UserSchema = object33({
2505
2566
  * unsubscribe sets `subscribed=false` so consent and the email-service
2506
2567
  * suppression list stay consistent. Defaults to NOT subscribed (opt-in).
2507
2568
  */
2508
- marketingConsent: object33({
2569
+ marketingConsent: object34({
2509
2570
  subscribed: boolean15().default(false).label("Subscribed to marketing email"),
2510
2571
  /** Epoch ms of the last consent change (opt-in or opt-out). */
2511
- updatedAt: number10().optional().label("Consent updated at"),
2572
+ updatedAt: number11().optional().label("Consent updated at"),
2512
2573
  /** Where the consent change originated, e.g. `onboarding`, `unsubscribe`, `settings`. */
2513
- source: string31().optional().label("Consent source"),
2574
+ source: string32().optional().label("Consent source"),
2514
2575
  /**
2515
2576
  * How often the user receives job-alert emails. `off` disables job alerts
2516
2577
  * only; other marketing stays governed by `subscribed`. Unset means the
2517
2578
  * user has not chosen a cadence yet.
2518
2579
  */
2519
- jobAlertFrequency: string31().oneOf(SupportedJobAlertFrequencies).optional().label("Job alert frequency")
2580
+ jobAlertFrequency: string32().oneOf(SupportedJobAlertFrequencies).optional().label("Job alert frequency")
2520
2581
  }).default({ subscribed: false }).label("Marketing Consent"),
2521
2582
  /**
2522
2583
  * Per-channel toggles for NOTIFICATION email — mail about activity on the
@@ -2531,7 +2592,7 @@ var UserSchema = object33({
2531
2592
  *
2532
2593
  * Transactional mail (magic-link, verification) ignores this object entirely.
2533
2594
  */
2534
- notificationPrefs: object33({
2595
+ notificationPrefs: object34({
2535
2596
  groupActivity: boolean15().default(true).label("Group activity email"),
2536
2597
  jobActivity: boolean15().default(true).label("Job activity email"),
2537
2598
  applicationUpdates: boolean15().default(true).label("Application update email"),
@@ -2547,15 +2608,15 @@ var UserSchema = object33({
2547
2608
  * Generated from a structured summary of skills, experience, education, etc.
2548
2609
  * Only generated for public profiles with sufficient completeness (≥60%).
2549
2610
  */
2550
- embedding: object33({
2551
- vector: array14(number10().required()).optional().label("Embedding vector"),
2552
- 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")
2553
2614
  }).nullable().optional().label("Embedding")
2554
2615
  }).concat(UserGeneralDetailSchema).concat(UserJobPreferencesSchema).concat(UserRecruiterProfileSchema).concat(UserCoordinatorProfileSchema).noUnknown().strict().label("User Schema");
2555
2616
 
2556
2617
  // src/user/user-completeness.schema.ts
2557
- import { object as object34 } from "yup";
2558
- var UserCompletenessSchema = object34({
2618
+ import { object as object35 } from "yup";
2619
+ var UserCompletenessSchema = object35({
2559
2620
  additionalInfo: CompletenessScoreSchema(0).label("Additional Info"),
2560
2621
  // Optional
2561
2622
  certifications: CompletenessScoreSchema(0).label("Certifications"),
@@ -2647,6 +2708,8 @@ export {
2647
2708
  ResourceType,
2648
2709
  SITEMAP_FORMAT,
2649
2710
  SYSTEM_DATE_FORMAT,
2711
+ SavedSearchSchema,
2712
+ SavedSearchStatus,
2650
2713
  SkillSchema,
2651
2714
  SocialAccount,
2652
2715
  SocialAccountSchema,
@@ -2679,6 +2742,7 @@ export {
2679
2742
  SupportedReportReasons,
2680
2743
  SupportedResourceTypes,
2681
2744
  SupportedSalaryCurrencies,
2745
+ SupportedSavedSearchStatuses,
2682
2746
  SupportedSocialAccounts,
2683
2747
  SupportedStudyTypes,
2684
2748
  SupportedUserProfileVisibilities,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thejob/schema",
3
- "version": "2.1.3",
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);
@@ -0,0 +1,84 @@
1
+ import { number, object, string } from "yup";
2
+ import { JobAlertFrequency, SupportedJobAlertFrequencies } from "../user/user.constant.js";
3
+ import { SavedSearchStatus, SupportedSavedSearchStatuses } from "./saved-search.constant.js";
4
+
5
+ /**
6
+ * A search a user asked to be kept, and optionally to be alerted about.
7
+ *
8
+ * Two jobs in one document, deliberately: the query the user built, and the
9
+ * cadence at which they want it re-run for them. Splitting them would mean a
10
+ * user with three saved searches and one alert preference has no way to say
11
+ * which search the alert is for.
12
+ *
13
+ * The stored query mirrors what `GET /jobs/search-v3` accepts — a free-text
14
+ * `search` plus a `filter` map — so replaying a saved search is running the same
15
+ * search the user originally ran, not a second, subtly different implementation
16
+ * that drifts from it.
17
+ */
18
+ export const SavedSearchSchema = object({
19
+ /** Public identifier. Server-assigned. */
20
+ shortId: string().trim().label("Short Id"),
21
+
22
+ /**
23
+ * Owner's Better Auth account id. Server-assigned from the verified token,
24
+ * never accepted from a request body.
25
+ */
26
+ ownerUserId: string().trim().label("Owner"),
27
+
28
+ /** What the user calls this search, e.g. "Remote React roles". */
29
+ label: string().trim().max(120).required().label("Label"),
30
+
31
+ /**
32
+ * The free-text query. Optional because a search can be filters-only ("all
33
+ * remote jobs in Sweden"), which is a legitimate alert to want.
34
+ */
35
+ search: string().trim().max(500).optional().label("Search Text"),
36
+
37
+ /**
38
+ * Field filters, in the same `{ field: values }` shape the search endpoint
39
+ * parses from `filter[field]=a,b`. Held as a free-form object rather than a
40
+ * closed schema because the job facets it addresses live in jobs-service and
41
+ * change independently; validating them here would put this package in the
42
+ * business of tracking that vocabulary.
43
+ */
44
+ filter: object().optional().default({}).label("Filters"),
45
+
46
+ /**
47
+ * How often to mail matches. `off` keeps the search saved but silent, which is
48
+ * the difference between "I don't want mail" and "delete my search".
49
+ *
50
+ * Reuses the cadence enum from `marketingConsent.jobAlertFrequency` rather
51
+ * than defining a second one: two vocabularies for the same concept is how
52
+ * they drift.
53
+ */
54
+ frequency: string()
55
+ .oneOf(SupportedJobAlertFrequencies)
56
+ .default(JobAlertFrequency.Off)
57
+ .label("Alert Frequency"),
58
+
59
+ status: string()
60
+ .oneOf(SupportedSavedSearchStatuses)
61
+ .default(SavedSearchStatus.Active)
62
+ .label("Status"),
63
+
64
+ /**
65
+ * Epoch ms of the last alert sent for this search. The matching sweep reads it
66
+ * to decide whether the cadence is due, and writes it only AFTER a successful
67
+ * publish, so a failed run retries next tick rather than silently skipping a
68
+ * period.
69
+ */
70
+ lastNotifiedAt: number().optional().label("Last Notified At"),
71
+
72
+ /**
73
+ * Epoch ms high-water mark: only jobs created after this are "new" for the
74
+ * next alert. Kept separate from `lastNotifiedAt` because a run that finds no
75
+ * matches should still advance what counts as new, without implying mail was
76
+ * sent.
77
+ */
78
+ lastMatchedAt: number().optional().label("Last Matched At"),
79
+ })
80
+ // `createdAt`/`updatedAt` are deliberately absent, matching every other schema
81
+ // here: audit timestamps are the storing service's concern, not part of the
82
+ // shape a client sends or validates.
83
+ .noUnknown()
84
+ .strict();